Compare commits

...
5 Commits
Author SHA1 Message Date
Bananymous 3148c28c3d userspace: Add basic find utility
This is mostly working but is missing -exec and -ok support. It can
search files though :^)
2026-07-08 21:48:14 +03:00
Bananymous fa89645542 userspace: Implement simple sort utility
This supports most(?) options except for the separate fields. Should
work for simple use cases

Also merge is currently just a wrapper around sort so its not optimized
:D
2026-07-08 21:48:14 +03:00
Bananymous ddf8fbc085 BuildSystem: Use ccache if found 2026-07-08 21:48:14 +03:00
Bananymous ecfd98fad0 bootloader: Don't use random packing in memory map 2026-07-08 21:48:14 +03:00
Bananymous 2e9f7077c9 TaskBar: Fix division by zero error
If a CPU does not have updated stats to show, just display ??.??%
2026-07-07 17:50:00 +03:00
13 changed files with 1129 additions and 13 deletions
+6
View File
@@ -27,6 +27,12 @@ set(BUILD_SHARED_LIBS True)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_program(CCACHE_PROGRAM "ccache")
if (CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()
# include headers of ${library} to ${target}
function(banan_include_headers target library)
target_include_directories(${target} PUBLIC $<TARGET_PROPERTY:${library},SOURCE_DIR>/include)
+11 -7
View File
@@ -2,6 +2,8 @@
.section .stage2
.set mmap_entry_size, 24
# fills memory map data structure
# doesn't return on error
# NO REGISTERS SAVED
@@ -12,7 +14,7 @@ get_memory_map:
movl $0x0000E820, %eax
movl $0x534D4150, %edx
xorl %ebx, %ebx
movl $20, %ecx
movl $mmap_entry_size, %ecx
movw $memory_map_entries, %di
clc
@@ -24,15 +26,15 @@ get_memory_map:
cmpl $0x534D4150, %eax
jne .get_memory_map_error
# FIXME: don't assume BIOS to always return 20 bytes
# confirm that we got at least 20 bytes
cmpl $20, %ecx
jne .get_memory_map_error
jl .get_memory_map_error
# increment entry count
incl (memory_map_entry_count)
# increment entry pointer
addw %cx, %di
addw $mmap_entry_size, %di
# BIOS can indicate end of list by 0 in ebx
testl %ebx, %ebx
@@ -107,7 +109,7 @@ print_memory_map:
call print_newline
addw $20, %si
addw $mmap_entry_size, %si
decl %edx
jnz .loop_memory_map
@@ -127,6 +129,8 @@ memory_map_error_msg:
memory_map:
memory_map_entry_count:
.skip 4
# 100 entries should be enough...
memory_map_padding:
.skip 4
# 128 entries should be enough...
memory_map_entries:
.skip 20 * 100
.skip mmap_entry_size * 128
+8 -3
View File
@@ -21,13 +21,17 @@ struct BananBootloaderMemoryMapEntry
uint64_t address;
uint64_t length;
uint32_t type;
} __attribute__((packed));
uint32_t unused;
};
static_assert(sizeof(BananBootloaderMemoryMapEntry) == 24);
struct BananBootloaderMemoryMapInfo
{
uint32_t entry_count;
uint32_t padding;
struct BananBootloaderMemoryMapEntry entries[];
} __attribute__((packed));
};
static_assert(sizeof(BananBootloaderMemoryMapInfo) == 8);
struct BananBootloaderInfo
{
@@ -35,4 +39,5 @@ struct BananBootloaderInfo
uint32_t framebuffer_addr;
uint32_t memory_map_addr;
uint32_t kernel_paddr;
} __attribute__((packed));
};
static_assert(sizeof(BananBootloaderInfo) == 16);
+2
View File
@@ -17,6 +17,7 @@ set(USERSPACE_PROGRAMS
echo
env
false
find
getopt
http-server
id
@@ -42,6 +43,7 @@ set(USERSPACE_PROGRAMS
Shell
sleep
snake
sort
stat
sudo
sync
+9 -3
View File
@@ -116,11 +116,17 @@ static BAN::String get_cpu_load(bool show_long)
}
BAN::String result;
(void)result.append("CPU");
(void)result.append("CPU"_sv);
for (size_t i = 0; i < delta_stats.size(); i++)
for (const auto stats : delta_stats)
{
const uint64_t idle_10000 = 10'000 * delta_stats[i].idle_ns / delta_stats[i].total_ns;
if (stats.total_ns == 0)
{
(void)result.append(" ??.??%"_sv);
continue;
}
const uint64_t idle_10000 = 10'000 * stats.idle_ns / stats.total_ns;
const uint64_t load_10000 = 10'000 - idle_10000;
auto string = BAN::String::formatted(" {}.{2}%", load_10000 / 100, load_10000 % 100);
+11
View File
@@ -0,0 +1,11 @@
set(SOURCES
main.cpp
Node.cpp
Token.cpp
)
add_executable(find ${SOURCES})
banan_link_library(find ban)
banan_link_library(find libc)
install(TARGETS find OPTIONAL)
+433
View File
@@ -0,0 +1,433 @@
#include "Node.h"
#include <fnmatch.h>
#include <grp.h>
#include <pwd.h>
#include <stdarg.h>
#include <sys/stat.h>
bool g_mount_specified { false };
bool g_xdev_specified { false };
bool g_prune_specified { false };
bool g_depth_specified { false };
static bool s_needs_implicit_print { false };
extern const char* g_argv0;
Node::Node(Type type, decltype(Node::as) as)
: type(type), as(as)
{
switch (type)
{
case Type::NOD_MOUNT:
g_mount_specified = true;
break;
case Type::NOD_XDEV:
g_xdev_specified = true;
break;
case Type::NOD_PRUNE:
g_prune_specified = true;
break;
case Type::NOD_DEPTH:
g_depth_specified = true;
break;
case Type::NOD_EXEC:
case Type::NOD_OK:
case Type::NOD_PRINT:
case Type::NOD_PRINT0:
s_needs_implicit_print = false;
break;
default:
break;
}
}
Node::Node(Type type)
: Node(type, {})
{ }
static bool token_match(BAN::Span<const Token>& tokens, Token::Type type)
{
if (tokens[0].type != type)
return false;
tokens = tokens.slice(1);
return true;
}
[[noreturn]] static void print_error_and_exit(const char* format, ...)
{
va_list args;
va_start(args, format);
fprintf(stderr, "%s: ", g_argv0);
vfprintf(stderr, format, args);
va_end(args);
exit(1);
}
static Node* compile_expr(BAN::Span<const Token>& tokens);
static Node* compile_primary(BAN::Span<const Token>& tokens)
{
constexpr Node::Type type_map[] {
[0]=(Node::Type)0, [1]=(Node::Type)0,
[2]=(Node::Type)0, [3]=(Node::Type)0,
[4]=(Node::Type)0, [5]=(Node::Type)0,
[6]=(Node::Type)0, [7]=(Node::Type)0,
[Token::TOK_NAME] = Node::Type::NOD_NAME,
[Token::TOK_INAME] = Node::Type::NOD_INAME,
[Token::TOK_PATH] = Node::Type::NOD_PATH,
[Token::TOK_NOUSER] = Node::Type::NOD_NOUSER,
[Token::TOK_NOGROUP] = Node::Type::NOD_NOGROUP,
[Token::TOK_MOUNT] = Node::Type::NOD_MOUNT,
[Token::TOK_XDEV] = Node::Type::NOD_XDEV,
[Token::TOK_PRUNE] = Node::Type::NOD_PRUNE,
[Token::TOK_PERM] = Node::Type::NOD_PERM,
[Token::TOK_TYPE] = Node::Type::NOD_TYPE,
[Token::TOK_LINKS] = Node::Type::NOD_LINKS,
[Token::TOK_USER] = Node::Type::NOD_USER,
[Token::TOK_GROUP] = Node::Type::NOD_GROUP,
[Token::TOK_SIZE] = Node::Type::NOD_SIZE,
[Token::TOK_ATIME] = Node::Type::NOD_ATIME,
[Token::TOK_CTIME] = Node::Type::NOD_CTIME,
[Token::TOK_MTIME] = Node::Type::NOD_MTIME,
[Token::TOK_EXEC] = Node::Type::NOD_EXEC,
[Token::TOK_OK] = Node::Type::NOD_OK,
[Token::TOK_PRINT] = Node::Type::NOD_PRINT,
[Token::TOK_PRINT0] = Node::Type::NOD_PRINT0,
[Token::TOK_NEWER] = Node::Type::NOD_NEWER,
[Token::TOK_DEPTH] = Node::Type::NOD_DEPTH,
};
const auto parse_perm = [](const char* string) {
const bool hyphen = (*string == '-');
char* endp;
const mode_t value = strtoul(string + hyphen, &endp, 8);
if (*endp != '\0')
print_error_and_exit("invalid perm '%s'\n", string);
return hyphen ? value | 017777 : value & 07777;
};
const auto parse_type = [](const char* string) {
if (string[1] != '\0');
else switch (*string)
{
case 'b': return S_IFBLK;
case 'c': return S_IFCHR;
case 'd': return S_IFDIR;
case 'l': return S_IFLNK;
case 'p': return S_IFIFO;
case 'f': return S_IFREG;
case 's': return S_IFSOCK;
}
print_error_and_exit("invalid type '%s'\n", string);
};
const auto parse_n = [](const char* string) -> decltype(Node::as.n) {
const bool plus = (*string == '+');
const bool minus = (*string == '-');
char* endp;
const uint64_t value = strtoull(string + (plus || minus), &endp, 10);
if (*endp != '\0')
print_error_and_exit("invalid n '%s'\n", string);
return {
.value = value,
.comp = plus ? Node::Comp::Gt : minus ? Node::Comp::Lt : Node::Comp::Eq,
};
};
const auto parse_uid = [](const char* string) -> uid_t {
if (passwd* passwd = getpwnam(string))
return passwd->pw_uid;
char* endp;
const uid_t value = strtol(string, &endp, 10);
if (*endp != '\0')
print_error_and_exit("invalid user '%s'\n", string);
return value;
};
const auto parse_gid = [](const char* string) -> gid_t {
if (group* group = getgrnam(string))
return group->gr_gid;
char* endp;
const uid_t value = strtol(string, &endp, 10);
if (*endp != '\0')
print_error_and_exit("invalid group '%s'\n", string);
return value;
};
const auto parse_newer = [](const char* string) -> timespec {
// FIXME: this should stat/lstat based on -H/-L/-P
struct stat st;
if (stat(string, &st) == -1)
print_error_and_exit("'%s': %s\n", string, strerror(errno));
return st.st_mtim;
};
const auto error_if_no_arg = [&] {
if (tokens[1].type == Token::TOK_END)
print_error_and_exit("missing argument to '%s'\n", tokens[0].string);
};
Node* node {};
if (Token::TOK_NAME <= tokens[0].type && tokens[0].type <= Token::TOK_DEPTH)
node = new Node { type_map[tokens[0].type] };
switch (tokens[0].type)
{
case Token::TOK_LPAREN:
tokens = tokens.slice(1);
node = compile_expr(tokens);
if (!token_match(tokens, Token::TOK_RPAREN))
print_error_and_exit("unexpected token '%s'\n", tokens[0].string);
return node;
case Token::TOK_NAME:
case Token::TOK_INAME:
case Token::TOK_PATH:
error_if_no_arg();
node->as.pattern = tokens[1].string;
tokens = tokens.slice(2);
return node;
case Token::TOK_NOUSER:
case Token::TOK_NOGROUP:
case Token::TOK_MOUNT:
case Token::TOK_XDEV:
case Token::TOK_PRUNE:
tokens = tokens.slice(1);
return node;
case Token::TOK_PERM:
error_if_no_arg();
node->as.mode = parse_perm(tokens[1].string);
tokens = tokens.slice(2);
return node;
case Token::TOK_TYPE:
error_if_no_arg();
node->as.mode = parse_type(tokens[1].string);
tokens = tokens.slice(2);
return node;
case Token::TOK_LINKS:
case Token::TOK_ATIME:
case Token::TOK_CTIME:
case Token::TOK_MTIME:
error_if_no_arg();
node->as.n = parse_n(tokens[1].string);
tokens = tokens.slice(2);
return node;
case Token::TOK_SIZE:
error_if_no_arg();
node->as.n = parse_n(tokens[1].string);
// FIXME: support `-size <n>c`
node->as.n.value *= 512;
tokens = tokens.slice(2);
return node;
case Token::TOK_USER:
error_if_no_arg();
node->as.uid = parse_uid(tokens[1].string);
tokens = tokens.slice(2);
return node;
case Token::TOK_GROUP:
error_if_no_arg();
node->as.uid = parse_gid(tokens[1].string);
tokens = tokens.slice(2);
return node;
case Token::TOK_EXEC:
case Token::TOK_OK:
print_error_and_exit("unsupported token '%s'\n", tokens[0].string);
case Token::TOK_PRINT:
case Token::TOK_PRINT0:
tokens = tokens.slice(1);
return node;
case Token::TOK_NEWER:
error_if_no_arg();
node->as.mtime = parse_newer(tokens[1].string);
tokens = tokens.slice(2);
return node;
case Token::TOK_DEPTH:
tokens = tokens.slice(1);
return node;
default:
print_error_and_exit("unexpected token '%s'\n", tokens[0].string);
}
}
static Node* compile_not(BAN::Span<const Token>& tokens)
{
if (!token_match(tokens, Token::TOK_NOT))
return compile_primary(tokens);
return new Node {
Node::Type::NOD_NOT, {
.child = compile_not(tokens),
},
};
}
static Node* compile_and(BAN::Span<const Token>& tokens)
{
constexpr auto is_implicit_and = [](Token::Type type) {
switch (type)
{
case Token::TOK_AND:
case Token::TOK_OR:
case Token::TOK_RPAREN:
case Token::TOK_END:
return false;
default:
return true;
}
};
Node* node = compile_not(tokens);
while (token_match(tokens, Token::TOK_AND) || is_implicit_and(tokens[0].type))
{
node = new Node {
Node::Type::NOD_AND, {
.bin_op = {
.lhs = node,
.rhs = compile_not(tokens),
}
},
};
}
return node;
}
static Node* compile_or(BAN::Span<const Token>& tokens)
{
Node* node = compile_and(tokens);
while (token_match(tokens, Token::TOK_OR))
{
node = new Node {
Node::Type::NOD_OR, {
.bin_op = {
.lhs = node,
.rhs = compile_and(tokens),
},
},
};
}
return node;
}
static Node* compile_expr(BAN::Span<const Token>& tokens)
{
return compile_or(tokens);
}
Node* compile_tokens(BAN::Span<const Token> tokens)
{
s_needs_implicit_print = true;
Node* node = compile_expr(tokens);
if (tokens[0].type != Token::TOK_END)
print_error_and_exit("unexpected token '%s'\n", tokens[0].string);
if (s_needs_implicit_print)
{
node = new Node {
Node::Type::NOD_AND, {
.bin_op = {
.lhs = node,
.rhs = new Node { Node::Type::NOD_PRINT },
},
},
};
}
return node;
}
bool evaluate_node(Node* node, const char* path, struct stat& st)
{
constexpr auto compare_n = [](uint64_t value, const decltype(Node::as.n)& n) {
if (n.comp == Node::Comp::Gt)
return value > n.value;
if (n.comp == Node::Comp::Lt)
return value < n.value;
return value == n.value;
};
const auto get_basename = [](const char* path) {
if (const char* slash = strrchr(path, '/'))
return slash + 1;
return path;
};
switch (node->type)
{
case Node::Type::NOD_AND:
return evaluate_node(node->as.bin_op.lhs, path, st)
&& evaluate_node(node->as.bin_op.rhs, path, st);
case Node::Type::NOD_OR:
return evaluate_node(node->as.bin_op.lhs, path, st)
|| evaluate_node(node->as.bin_op.rhs, path, st);
case Node::Type::NOD_NOT:
return !evaluate_node(node->as.child, path, st);
case Node::Type::NOD_NAME:
return !fnmatch(node->as.pattern, get_basename(path), 0);
case Node::Type::NOD_INAME:
return !fnmatch(node->as.pattern, get_basename(path), FNM_CASEFOLD);
case Node::Type::NOD_PATH:
return !fnmatch(node->as.pattern, path, 0);
case Node::Type::NOD_NOUSER:
return getpwuid(st.st_uid) == nullptr;
case Node::Type::NOD_NOGROUP:
return getgrgid(st.st_gid) == nullptr;
case Node::Type::NOD_TYPE:
return node->as.mode == (st.st_mode & S_IFMT);
case Node::Type::NOD_USER:
return node->as.uid == st.st_uid;
case Node::Type::NOD_GROUP:
return node->as.gid == st.st_gid;
case Node::Type::NOD_PERM:
if (const mode_t mode = node->as.mode & 07777; mode < node->as.mode)
return (mode & st.st_mode) == mode;
return node->as.mode == (st.st_mode & 07777);
case Node::Type::NOD_LINKS:
return compare_n(st.st_nlink, node->as.n);
case Node::Type::NOD_SIZE:
return compare_n(st.st_size, node->as.n);
case Node::Type::NOD_ATIME:
return compare_n((time(nullptr) - st.st_atime) / 86400, node->as.n);
case Node::Type::NOD_CTIME:
return compare_n((time(nullptr) - st.st_ctime) / 86400, node->as.n);
case Node::Type::NOD_MTIME:
return compare_n((time(nullptr) - st.st_mtime) / 86400, node->as.n);
case Node::Type::NOD_NEWER:
if (st.st_mtim.tv_sec > node->as.mtime.tv_sec)
return true;
if (st.st_mtim.tv_sec < node->as.mtime.tv_sec)
return false;
return st.st_mtim.tv_nsec > node->as.mtime.tv_nsec;
case Node::Type::NOD_EXEC:
case Node::Type::NOD_OK:
ASSERT_NOT_REACHED();
case Node::Type::NOD_PRINT:
fputs(path, stdout);
fputc('\n', stdout);
return true;
case Node::Type::NOD_PRINT0:
fputs(path, stdout);
fputc('\0', stdout);
return true;
case Node::Type::NOD_MOUNT:
case Node::Type::NOD_XDEV:
case Node::Type::NOD_PRUNE:
case Node::Type::NOD_DEPTH:
return true;
}
ASSERT_NOT_REACHED();
}
+69
View File
@@ -0,0 +1,69 @@
#pragma once
#include "Token.h"
#include <sys/types.h>
#include <time.h>
struct Node
{
enum class Type
{
NOD_AND, NOD_OR, NOD_NOT,
NOD_NAME, NOD_INAME, NOD_PATH,
NOD_NOUSER, NOD_NOGROUP, NOD_MOUNT,
NOD_XDEV, NOD_PRUNE, NOD_PERM,
NOD_TYPE, NOD_LINKS, NOD_USER,
NOD_GROUP, NOD_SIZE, NOD_ATIME,
NOD_CTIME, NOD_MTIME, NOD_EXEC,
NOD_OK, NOD_PRINT, NOD_PRINT0,
NOD_NEWER, NOD_DEPTH,
};
enum class Comp
{
Eq, Gt, Lt
};
Type type;
union {
// AND, OR
struct {
Node* lhs;
Node* rhs;
} bin_op;
// NOT
Node* child;
// NAME, INAME, PATH
const char* pattern;
// PERM, TYPE
mode_t mode;
// USER
uid_t uid;
// GROUP
gid_t gid;
// LINKS, SIZE, ATIME, CTIME, MTIME
struct {
uint64_t value;
Comp comp;
} n;
// EXEC, OK
struct {
const char* args;
size_t count;
} args;
// NEWER
timespec mtime;
} as;
Node(Type type, decltype(Node::as) as);
Node(Type type);
};
extern bool g_mount_specified;
extern bool g_xdev_specified;
extern bool g_prune_specified;
extern bool g_depth_specified;
Node* compile_tokens(BAN::Span<const Token>);
bool evaluate_node(Node*, const char* path, struct stat&);
+57
View File
@@ -0,0 +1,57 @@
#include "Token.h"
BAN::Vector<Token> tokenize(const char* const* strings)
{
constexpr Token type_map[] {
{ Token::TOK_NOT, "!" },
{ Token::TOK_LPAREN, "(" },
{ Token::TOK_RPAREN, ")" },
{ Token::TOK_CURLIES, "{}" },
{ Token::TOK_PLUS, "+" },
{ Token::TOK_SEMICOLON, ";" },
{ Token::TOK_AND, "-a" },
{ Token::TOK_OR, "-o" },
{ Token::TOK_NAME, "-name" },
{ Token::TOK_INAME, "-iname" },
{ Token::TOK_PATH, "-path" },
{ Token::TOK_NOUSER, "-nouser" },
{ Token::TOK_NOGROUP, "-nogroup" },
{ Token::TOK_MOUNT, "-mount" },
{ Token::TOK_XDEV, "-xdev" },
{ Token::TOK_PRUNE, "-prune" },
{ Token::TOK_PERM, "-perm" },
{ Token::TOK_TYPE, "-type" },
{ Token::TOK_LINKS, "-links" },
{ Token::TOK_USER, "-user" },
{ Token::TOK_GROUP, "-group" },
{ Token::TOK_SIZE, "-size" },
{ Token::TOK_ATIME, "-atime" },
{ Token::TOK_CTIME, "-ctime" },
{ Token::TOK_MTIME, "-mtime" },
{ Token::TOK_EXEC, "-exec" },
{ Token::TOK_OK, "-ok" },
{ Token::TOK_PRINT, "-print" },
{ Token::TOK_PRINT0, "-print0" },
{ Token::TOK_NEWER, "-newer" },
{ Token::TOK_DEPTH, "-depth" },
};
BAN::Vector<Token> tokens;
for (size_t i = 0; strings[i]; i++)
{
Token::Type type { Token::TOK_LITERAL };
for (const auto& entry : type_map)
{
if (strcmp(strings[i], entry.string) != 0)
continue;
type = entry.type;
break;
}
MUST(tokens.push_back({ type, strings[i] }));
}
MUST(tokens.push_back({ Token::TOK_END, "<EOF>" }));
return tokens;
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <BAN/Vector.h>
struct Token
{
enum Type
{
TOK_NOT, TOK_LPAREN, TOK_RPAREN, TOK_CURLIES,
TOK_PLUS, TOK_SEMICOLON, TOK_AND, TOK_OR,
TOK_NAME, TOK_INAME, TOK_PATH, TOK_NOUSER,
TOK_NOGROUP, TOK_MOUNT, TOK_XDEV, TOK_PRUNE,
TOK_PERM, TOK_TYPE, TOK_LINKS, TOK_USER,
TOK_GROUP, TOK_SIZE, TOK_ATIME, TOK_CTIME,
TOK_MTIME, TOK_EXEC, TOK_OK, TOK_PRINT,
TOK_PRINT0, TOK_NEWER, TOK_DEPTH, TOK_LITERAL,
TOK_END
};
Type type;
const char* string;
};
BAN::Vector<Token> tokenize(const char* const* strings);
+137
View File
@@ -0,0 +1,137 @@
#include "Node.h"
#include "Token.h"
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
enum class LinkMode
{
Physical, Logical, Half
};
const char* g_argv0 { nullptr };
static dev_t s_first_st_dev { 0 };
static void recurse_path(int fd, const char* name, const char* path, Node* node, LinkMode link_mode)
{
int stat_flag = 0;
if (link_mode == LinkMode::Physical)
stat_flag = AT_SYMLINK_NOFOLLOW;
else if (link_mode == LinkMode::Half && (fd != AT_FDCWD))
stat_flag = AT_SYMLINK_NOFOLLOW;
struct stat st;
if (fstatat(fd, name, &st, stat_flag) == -1)
{
fprintf(stderr, "%s: '%s': %s\n", g_argv0, path, strerror(errno));
return;
}
if (fd == AT_FDCWD)
s_first_st_dev = st.st_dev;
if (g_mount_specified && st.st_dev != s_first_st_dev)
return;
if (!g_depth_specified || !S_ISDIR(st.st_mode))
evaluate_node(node, path, st);
if (S_ISDIR(st.st_mode) && (!g_prune_specified || g_depth_specified) && (!g_xdev_specified || st.st_dev == s_first_st_dev))
{
int open_flag = stat_flag ? O_NOFOLLOW : 0;
int dirfd = openat(fd, name, O_RDONLY | O_DIRECTORY | open_flag);
if (dirfd < 0)
fprintf(stderr, "%s: '%s': %s\n", g_argv0, path, strerror(errno));
DIR* dirp = nullptr;
if ((dirfd >= 0) && (dirp = fdopendir(dirfd)))
{
char buffer[PATH_MAX];
char* buffer_end = stpcpy(buffer, path);
if (*path && buffer_end[-1] != '/')
*buffer_end++ = '/';
dirent* dirent;
while ((errno = 0) || (dirent = readdir(dirp)))
{
if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
continue;
strcpy(buffer_end, dirent->d_name);
recurse_path(dirfd, dirent->d_name, buffer, node, link_mode);
}
if (errno)
fprintf(stderr, "%s: '%s': %s\n", g_argv0, path, strerror(errno));
closedir(dirp);
}
if (dirp == nullptr && dirfd >= 0)
{
fprintf(stderr, "%s: '%s': %s\n", g_argv0, path, strerror(errno));
close(dirfd);
}
}
if (g_depth_specified && S_ISDIR(st.st_mode))
evaluate_node(node, path, st);
}
int main(int argc, char** argv)
{
g_argv0 = argv[0];
LinkMode link_mode { LinkMode::Physical };
int idx = 1;
for (; idx < argc; idx++)
{
if (argv[idx][0] != '-')
break;
else if (strcmp(argv[idx], "-P") == 0)
link_mode = LinkMode::Physical;
else if (strcmp(argv[idx], "-L") == 0)
link_mode = LinkMode::Logical;
else if (strcmp(argv[idx], "-H") == 0)
link_mode = LinkMode::Half;
else if (strcmp(argv[idx], "--help") == 0)
{
fprintf(stderr, "usage: %s [-P|-L|-H] PATH... [EXPRESSION...]\n", argv[0]);
fprintf(stderr, " find files based on expressions\n");
fprintf(stderr, "OPTIONS:\n");
fprintf(stderr, " -P never follow symbolic links\n");
fprintf(stderr, " -L follow symbolic links\n");
fprintf(stderr, " -H only follow symbolic links in PATH...\n");
fprintf(stderr, " --help show this message and exit\n");
return 0;
}
else break;
}
if (idx < argc && strcmp(argv[idx], "--") == 0)
idx++;
constexpr auto is_path_start = [](char ch) {
return ch != '-' && ch != '!' && ch != '(';
};
size_t path_count = 0;
while (idx + path_count < static_cast<size_t>(argc) && is_path_start(argv[idx + path_count][0]))
path_count++;
auto tokens = tokenize(&argv[idx + path_count]);
auto* node = compile_tokens(tokens.span());
if (path_count == 0)
recurse_path(AT_FDCWD, ".", ".", node, link_mode);
else for (size_t i = 0; i < path_count; i++)
recurse_path(AT_FDCWD, argv[idx + i], argv[idx + i], node, link_mode);
return 0;
}
+9
View File
@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(sort ${SOURCES})
banan_link_library(sort ban)
banan_link_library(sort libc)
install(TARGETS sort OPTIONAL)
+353
View File
@@ -0,0 +1,353 @@
#include <BAN/Vector.h>
#include <BAN/String.h>
#include <BAN/Sort.h>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <float.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct SortInfo
{
enum class SortType {
Default, Numeric,
};
SortType type { SortType::Default };
bool reverse { false };
bool unique { false };
bool only_blank_alnum { false };
bool ignore_case { false };
bool ignore_nonprint { false };
};
static const char* s_argv0 { nullptr };
static double get_numberic(const BAN::String& line)
{
// FIXME: this isn't quite right :)
errno = 0;
const double value = strtod(line.data(), nullptr);
return errno ? DBL_MAX : value;
}
static int compar(const BAN::String& lhs, const BAN::String& rhs, SortInfo info)
{
const int mult = info.reverse ? -1 : 1;
switch (info.type)
{
case SortInfo::SortType::Numeric:
{
const double lhs_value = get_numberic(lhs);
const double rhs_value = get_numberic(rhs);
if (lhs_value == rhs_value)
return 0;
return mult * (lhs_value < rhs_value ? -1 : 1);
}
case SortInfo::SortType::Default:
{
auto lhs_sv = lhs.sv().substring(0, lhs.size() - 1);
auto rhs_sv = rhs.sv().substring(0, rhs.size() - 1);
const auto skip_to_unignored = [&info](BAN::StringView& string) {
while (info.only_blank_alnum && !string.empty() && !(isblank(string.front()) || isalnum(string.front())))
string = string.substring(1);
while (info.ignore_nonprint && !string.empty() && !(isprint(string.front())))
string = string.substring(1);
};
for (;;)
{
skip_to_unignored(lhs_sv);
skip_to_unignored(rhs_sv);
if (lhs_sv.empty() && rhs_sv.empty())
return 0;
if (lhs_sv.empty() || rhs_sv.empty())
return mult * (lhs_sv.empty() ? -1 : 1);
const int lhs_ch = info.ignore_case ? toupper(lhs_sv.front()) : lhs_sv.front();
const int rhs_ch = info.ignore_case ? toupper(rhs_sv.front()) : rhs_sv.front();
if (lhs_ch != rhs_ch)
return mult * (lhs_ch < rhs_ch ? -1 : 1);
lhs_sv = lhs_sv.substring(1);
rhs_sv = rhs_sv.substring(1);
}
}
}
ASSERT_NOT_REACHED();
}
static BAN::Optional<BAN::String> read_next_line(const char* path, FILE* fp)
{
BAN::String line;
while (line.empty() || line.back() != '\n')
{
char buffer[128];
if (fgets(buffer, sizeof(buffer), fp) == nullptr)
{
if (feof(fp))
break;
fprintf(stderr, "%s: fgets %s: %s\n", s_argv0, path, strerror(errno));
return {};
}
if (auto ret = line.append(buffer); ret.is_error())
{
fprintf(stderr, "%s: malloc %s: %s\n", s_argv0, path, ret.error().get_message());
return {};
}
}
if (line.empty() || line.back() == '\n')
return line;
if (auto ret = line.push_back('\n'); ret.is_error())
{
fprintf(stderr, "%s: malloc %s: %s\n", s_argv0, path, ret.error().get_message());
return {};
}
return line;
}
static bool do_sort(const char* out_path, const char** paths, size_t path_count, SortInfo info)
{
BAN::Vector<BAN::String> lines;
for (size_t i = 0; i < path_count; i++)
{
FILE* fp = strcmp(paths[i], "-") == 0 ? stdin : fopen(paths[i], "r");
if (fp == nullptr)
{
fprintf(stderr, "%s: fopen %s: %s\n", s_argv0, paths[i], strerror(errno));
return false;
}
for (;;)
{
auto opt_line = read_next_line(paths[i], fp);
if (!opt_line.has_value())
return false;
if (opt_line->empty())
break;
if (auto ret = lines.push_back(opt_line.release_value()); ret.is_error())
{
fprintf(stderr, "%s: malloc %s: %s\n", s_argv0, paths[i], ret.error().get_message());
return false;
}
}
fclose(fp);
}
BAN::sort::sort(lines.begin(), lines.end(), [info](const auto& lhs, const auto& rhs) {
return compar(lhs, rhs, info) <= 0;
});
FILE* out_fp = strcmp(out_path, "-") == 0 ? stdout : fopen(out_path, "w");
if (out_fp == nullptr)
{
fprintf(stderr, "%s: fopen %s: %s\n", s_argv0, out_path, strerror(errno));
return false;
}
for (size_t i = 0; i < lines.size(); i++)
{
if (info.unique && i != 0 && compar(lines[i - 1], lines[i], info) == 0)
continue;
size_t written = 0;
while (written < lines[i].size())
{
const size_t nwrite = fwrite(lines[i].data() + written, 1, lines[i].size() - written, out_fp);
if (nwrite == 0)
{
fprintf(stderr, "%s: fwrite %s: %s\n", s_argv0, out_path, strerror(errno));
return false;
}
written += nwrite;
}
}
fclose(out_fp);
return true;
}
static bool do_merge(const char* out_path, const char** paths, size_t path_count, SortInfo info)
{
// FIXME: optimize
return do_sort(out_path, paths, path_count, info);
}
static bool do_check(const char** paths, size_t path_count, SortInfo info, bool silent)
{
BAN::String prev_line;
size_t line_idx = 0;
for (size_t i = 0; i < path_count; i++)
{
FILE* fp = strcmp(paths[i], "-") == 0 ? stdin : fopen(paths[i], "r");
if (fp == nullptr)
{
fprintf(stderr, "%s: fopen %s: %s\n", s_argv0, paths[i], strerror(errno));
return false;
}
for (;;)
{
auto opt_line = read_next_line(paths[i], fp);
if (!opt_line.has_value())
return false;
if (opt_line->empty())
break;
line_idx++;
if (!prev_line.empty())
{
const int result = compar(prev_line, opt_line.value(), info);
if (info.unique ? (result >= 0) : (result > 0))
{
if (!silent)
fprintf(stderr, "%s: %s:%zu: disorder: %s", s_argv0, paths[i], line_idx, opt_line->data());
return false;
}
}
prev_line = opt_line.release_value();
}
fclose(fp);
}
return true;
}
int main(int argc, char** argv)
{
s_argv0 = argv[0];
enum class Operation {
Sort, Merge, Check, CheckSilent,
};
Operation operation { Operation::Sort };
const char* output = "-";
SortInfo info {};
for (;;)
{
static option long_options[] {
{ "check", optional_argument, nullptr, 'c' },
{ "merge", no_argument, nullptr, 'm' },
{ "output", required_argument, nullptr, 'o' },
{ "unique", no_argument, nullptr, 'u' },
{ "reverse", no_argument, nullptr, 'r' },
{ "dictionary-order", no_argument, nullptr, 'd' },
{ "ignore-case", no_argument, nullptr, 'f' },
{ "ignore-nonprinting", no_argument, nullptr, 'i' },
{ "numeric-sort", no_argument, nullptr, 'n' },
{ "help", no_argument, nullptr, 0 },
{}
};
int ch = getopt_long(argc, argv, "cCmo:urdfinbt", long_options, nullptr);
if (ch == -1)
break;
switch (ch)
{
case 'c':
case 'C':
if (optarg == nullptr)
operation = (ch == 'c') ? Operation::Check : Operation::CheckSilent;
else if (strcmp(optarg, "silent") == 0 || strcmp(optarg, "quiet") == 0)
operation = Operation::CheckSilent;
else
{
fprintf(stderr, "%s: unknown argument '%s' for --check\n", argv[0], optarg);
fprintf(stderr, "see '%s --help' for usage\n", argv[0]);
return 1;
}
break;
case 'm':
operation = Operation::Merge;
break;
case 'o':
output = optarg;
break;
case 'u':
info.unique = true;
break;
case 'r':
info.reverse = true;
break;
case 'd':
info.only_blank_alnum = true;
break;
case 'f':
info.ignore_case = true;
break;
case 'i':
info.ignore_nonprint = true;
break;
case 'n':
info.type = SortInfo::SortType::Numeric;
break;
case 0:
fprintf(stderr, "usage: %s [OPTION]... [FILE]...\n", argv[0]);
fprintf(stderr, " sort and merge input FILE(s)\n");
fprintf(stderr, "OPTIONS:\n");
fprintf(stderr, " -c, --check check if input is sorted (and unique with -u) and print a warning message and exit accordingly\n");
fprintf(stderr, " -C, --check=silent|quiet same as -c but don't print a warning message\n");
fprintf(stderr, " -m, --merge merge only, input is assumed to be sorted\n");
fprintf(stderr, " -o, --output=FILE output into FILE instead of stdout\n");
fprintf(stderr, " -u, --unique remove all but one line with equal keys\n");
fprintf(stderr, " -r, --reverse reverse the result of output\n");
fprintf(stderr, " -d, --directory-order sort only according to blank and alphanumeric characters\n");
fprintf(stderr, " -f, --ignore-case compare using upper case variants of each character\n");
fprintf(stderr, " -i, --ignore-nonprinting sort only according to printable characters\n");
fprintf(stderr, " -n, --numeric-sort sort based on numberic values optionally preceded by blanks\n");
fprintf(stderr, " --help show this message and exit\n");
return 0;
case ':' : case '?':
fprintf(stderr, "see '%s --help' for usage\n", argv[0]);
return 1;
}
}
const char* fallback_in = "-";
const char* const* paths = (optind == argc) ? &fallback_in : &argv[optind];
const size_t path_count = (optind == argc) ? 1 : argc - optind;
bool success;
switch (operation)
{
case Operation::Sort:
success = do_sort(output, const_cast<const char**>(paths), path_count, info);
break;
case Operation::Merge:
success = do_merge(output, const_cast<const char**>(paths), path_count, info);
break;
case Operation::Check:
case Operation::CheckSilent:
success = do_check(const_cast<const char**>(paths), path_count, info, operation == Operation::CheckSilent);
break;
}
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}