userspace: Add basic find utility
This is mostly working but is missing -exec and -ok support. It can search files though :^)
This commit is contained in:
11
userspace/programs/find/CMakeLists.txt
Normal file
11
userspace/programs/find/CMakeLists.txt
Normal 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
userspace/programs/find/Node.cpp
Normal file
433
userspace/programs/find/Node.cpp
Normal 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
userspace/programs/find/Node.h
Normal file
69
userspace/programs/find/Node.h
Normal 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
userspace/programs/find/Token.cpp
Normal file
57
userspace/programs/find/Token.cpp
Normal 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
userspace/programs/find/Token.h
Normal file
24
userspace/programs/find/Token.h
Normal 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
userspace/programs/find/main.cpp
Normal file
137
userspace/programs/find/main.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user