diff options
Diffstat (limited to 'src/parse.c')
-rw-r--r-- | src/parse.c | 2381 |
1 files changed, 1280 insertions, 1101 deletions
diff --git a/src/parse.c b/src/parse.c index 3416d9e..9c39d6b 100644 --- a/src/parse.c +++ b/src/parse.c @@ -9,19 +9,20 @@ */ #include "parse.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" -#include "darray.h" #include "diag.h" #include "dir.h" #include "eval.h" #include "exec.h" #include "expr.h" #include "fsade.h" +#include "list.h" #include "opt.h" #include "printf.h" #include "pwcache.h" @@ -31,6 +32,7 @@ #include "xregex.h" #include "xspawn.h" #include "xtime.h" + #include <errno.h> #include <fcntl.h> #include <fnmatch.h> @@ -41,109 +43,18 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/time.h> #include <sys/stat.h> -#include <sys/wait.h> +#include <sys/types.h> #include <time.h> #include <unistd.h> // Strings printed by -D tree for "fake" expressions -static char *fake_and_arg = "-a"; -static char *fake_false_arg = "-false"; +static char *fake_and_arg = "-and"; static char *fake_hidden_arg = "-hidden"; -static char *fake_or_arg = "-o"; +static char *fake_or_arg = "-or"; static char *fake_print_arg = "-print"; static char *fake_true_arg = "-true"; -struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { - struct bfs_expr *expr = ZALLOC(struct bfs_expr); - if (!expr) { - perror("zalloc()"); - return NULL; - } - - expr->eval_fn = eval_fn; - expr->argc = argc; - expr->argv = argv; - expr->probability = 0.5; - return expr; -} - -bool bfs_expr_is_parent(const struct bfs_expr *expr) { - return expr->eval_fn == eval_and - || expr->eval_fn == eval_or - || expr->eval_fn == eval_not - || expr->eval_fn == eval_comma; -} - -bool bfs_expr_never_returns(const struct bfs_expr *expr) { - // Expressions that never return are vacuously both always true and always false - return expr->always_true && expr->always_false; -} - -void bfs_expr_free(struct bfs_expr *expr) { - if (!expr) { - return; - } - - if (bfs_expr_is_parent(expr)) { - bfs_expr_free(expr->rhs); - bfs_expr_free(expr->lhs); - } else if (expr->eval_fn == eval_exec) { - bfs_exec_free(expr->exec); - } else if (expr->eval_fn == eval_fprintf) { - bfs_printf_free(expr->printf); - } else if (expr->eval_fn == eval_regex) { - bfs_regfree(expr->regex); - } - - free(expr); -} - -/** - * Create a new unary expression. - */ -static struct bfs_expr *new_unary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *rhs, char **argv) { - struct bfs_expr *expr = bfs_expr_new(eval_fn, 1, argv); - if (!expr) { - bfs_expr_free(rhs); - return NULL; - } - - expr->lhs = NULL; - expr->rhs = rhs; - bfs_assert(bfs_expr_is_parent(expr)); - - expr->persistent_fds = rhs->persistent_fds; - expr->ephemeral_fds = rhs->ephemeral_fds; - return expr; -} - -/** - * Create a new binary expression. - */ -static struct bfs_expr *new_binary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *lhs, struct bfs_expr *rhs, char **argv) { - struct bfs_expr *expr = bfs_expr_new(eval_fn, 1, argv); - if (!expr) { - bfs_expr_free(rhs); - bfs_expr_free(lhs); - return NULL; - } - - expr->lhs = lhs; - expr->rhs = rhs; - bfs_assert(bfs_expr_is_parent(expr)); - - expr->persistent_fds = lhs->persistent_fds + rhs->persistent_fds; - if (lhs->ephemeral_fds > rhs->ephemeral_fds) { - expr->ephemeral_fds = lhs->ephemeral_fds; - } else { - expr->ephemeral_fds = rhs->ephemeral_fds; - } - - return expr; -} - /** * Color use flags. */ @@ -154,9 +65,9 @@ enum use_color { }; /** - * Ephemeral state for parsing the command line. + * Command line parser state. */ -struct parser_state { +struct bfs_parser { /** The command line being constructed. */ struct bfs_ctx *ctx; /** The command line arguments being parsed. */ @@ -169,14 +80,10 @@ struct parser_state { /** Whether stdout is a terminal. */ bool stdout_tty; - /** Whether this session is interactive (stdin and stderr are each a terminal). */ - bool interactive; /** Whether -color or -nocolor has been passed. */ enum use_color use_color; /** Whether a -print action is implied. */ bool implicit_print; - /** Whether the default root "." should be used. */ - bool implicit_root; /** Whether the expression has started. */ bool expr_started; /** Whether an information option like -help or -version was passed. */ @@ -186,46 +93,30 @@ struct parser_state { /** The last non-path argument. */ char **last_arg; - /** A "-depth"-type argument, if any. */ - char **depth_arg; - /** A "-prune" argument, if any. */ - char **prune_arg; - /** A "-mount" argument, if any. */ - char **mount_arg; - /** An "-xdev" argument, if any. */ - char **xdev_arg; - /** A "-files0-from -" argument, if any. */ - char **files0_stdin_arg; - /** An "-ok"-type expression, if any. */ - const struct bfs_expr *ok_expr; + /** A "-depth"-type expression, if any. */ + const struct bfs_expr *depth_expr; + /** A "-limit" expression, if any. */ + const struct bfs_expr *limit_expr; + /** A "-prune" expression, if any. */ + const struct bfs_expr *prune_expr; + /** A "-mount" expression, if any. */ + const struct bfs_expr *mount_expr; + /** An "-xdev" expression, if any. */ + const struct bfs_expr *xdev_expr; + /** A "-files0-from" expression, if any. */ + const struct bfs_expr *files0_expr; + /** An expression that consumes stdin, if any. */ + const struct bfs_expr *stdin_expr; /** The current time (maybe modified by -daystart). */ struct timespec now; }; /** - * Possible token types. - */ -enum token_type { - /** A flag. */ - T_FLAG, - /** A root path. */ - T_PATH, - /** An option. */ - T_OPTION, - /** A test. */ - T_TEST, - /** An action. */ - T_ACTION, - /** An operator. */ - T_OPERATOR, -}; - -/** * Print a low-level error message during parsing. */ -static void parse_perror(const struct parser_state *state, const char *str) { - bfs_perror(state->ctx, str); +static void parse_perror(const struct bfs_parser *parser, const char *str) { + bfs_perror(parser->ctx, str); } /** Initialize an empty highlighted range. */ @@ -247,30 +138,27 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, /** * Print an error message during parsing. */ -BFS_FORMATTER(2, 3) -static void parse_error(const struct parser_state *state, const char *format, ...) { - int error = errno; - const struct bfs_ctx *ctx = state->ctx; +_printf(2, 3) +static void parse_error(const struct bfs_parser *parser, const char *format, ...) { + const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); - highlight_args(ctx, state->argv, 1, highlight); + highlight_args(ctx, parser->argv, 1, highlight); bfs_argv_error(ctx, highlight); va_list args; va_start(args, format); - errno = error; - bfs_verror(state->ctx, format, args); + bfs_verror(parser->ctx, format, args); va_end(args); } /** * Print an error about some command line arguments. */ -BFS_FORMATTER(4, 5) -static void parse_argv_error(const struct parser_state *state, char **argv, size_t argc, const char *format, ...) { - int error = errno; - const struct bfs_ctx *ctx = state->ctx; +_printf(4, 5) +static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_t argc, const char *format, ...) { + const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); @@ -279,7 +167,6 @@ static void parse_argv_error(const struct parser_state *state, char **argv, size va_list args; va_start(args, format); - errno = error; bfs_verror(ctx, format, args); va_end(args); } @@ -287,20 +174,18 @@ static void parse_argv_error(const struct parser_state *state, char **argv, size /** * Print an error about conflicting command line arguments. */ -BFS_FORMATTER(6, 7) -static void parse_conflict_error(const struct parser_state *state, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { - int error = errno; - const struct bfs_ctx *ctx = state->ctx; +_printf(4, 5) +static void parse_conflict_error(const struct bfs_parser *parser, const struct bfs_expr *expr1, const struct bfs_expr *expr2, const char *format, ...) { + const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); - highlight_args(ctx, argv1, argc1, highlight); - highlight_args(ctx, argv2, argc2, highlight); + highlight_args(ctx, expr1->argv, expr1->argc, highlight); + highlight_args(ctx, expr2->argv, expr2->argc, highlight); bfs_argv_error(ctx, highlight); va_list args; va_start(args, format); - errno = error; bfs_verror(ctx, format, args); va_end(args); } @@ -308,16 +193,14 @@ static void parse_conflict_error(const struct parser_state *state, char **argv1, /** * Print an error about an expression. */ -BFS_FORMATTER(3, 4) -static void parse_expr_error(const struct parser_state *state, const struct bfs_expr *expr, const char *format, ...) { - int error = errno; - const struct bfs_ctx *ctx = state->ctx; +_printf(3, 4) +static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) { + const struct bfs_ctx *ctx = parser->ctx; bfs_expr_error(ctx, expr); va_list args; va_start(args, format); - errno = error; bfs_verror(ctx, format, args); va_end(args); } @@ -325,22 +208,20 @@ static void parse_expr_error(const struct parser_state *state, const struct bfs_ /** * Print a warning message during parsing. */ -BFS_FORMATTER(2, 3) -static bool parse_warning(const struct parser_state *state, const char *format, ...) { - int error = errno; - const struct bfs_ctx *ctx = state->ctx; +_printf(2, 3) +static bool parse_warning(const struct bfs_parser *parser, const char *format, ...) { + const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); - highlight_args(ctx, state->argv, 1, highlight); + highlight_args(ctx, parser->argv, 1, highlight); if (!bfs_argv_warning(ctx, highlight)) { return false; } va_list args; va_start(args, format); - errno = error; - bool ret = bfs_vwarning(state->ctx, format, args); + bool ret = bfs_vwarning(parser->ctx, format, args); va_end(args); return ret; } @@ -348,22 +229,20 @@ static bool parse_warning(const struct parser_state *state, const char *format, /** * Print a warning about conflicting command line arguments. */ -BFS_FORMATTER(6, 7) -static bool parse_conflict_warning(const struct parser_state *state, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { - int error = errno; - const struct bfs_ctx *ctx = state->ctx; +_printf(4, 5) +static bool parse_conflict_warning(const struct bfs_parser *parser, const struct bfs_expr *expr1, const struct bfs_expr *expr2, const char *format, ...) { + const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); - highlight_args(ctx, argv1, argc1, highlight); - highlight_args(ctx, argv2, argc2, highlight); + highlight_args(ctx, expr1->argv, expr1->argc, highlight); + highlight_args(ctx, expr2->argv, expr2->argc, highlight); if (!bfs_argv_warning(ctx, highlight)) { return false; } va_list args; va_start(args, format); - errno = error; bool ret = bfs_vwarning(ctx, format, args); va_end(args); return ret; @@ -372,10 +251,9 @@ static bool parse_conflict_warning(const struct parser_state *state, char **argv /** * Print a warning about an expression. */ -BFS_FORMATTER(3, 4) -static bool parse_expr_warning(const struct parser_state *state, const struct bfs_expr *expr, const char *format, ...) { - int error = errno; - const struct bfs_ctx *ctx = state->ctx; +_printf(3, 4) +static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) { + const struct bfs_ctx *ctx = parser->ctx; if (!bfs_expr_warning(ctx, expr)) { return false; @@ -383,25 +261,79 @@ static bool parse_expr_warning(const struct parser_state *state, const struct bf va_list args; va_start(args, format); - errno = error; bool ret = bfs_vwarning(ctx, format, args); va_end(args); return ret; } /** + * Report an error if stdin is already consumed, then consume it. + */ +static bool consume_stdin(struct bfs_parser *parser, const struct bfs_expr *expr) { + if (parser->stdin_expr) { + parse_conflict_error(parser, parser->stdin_expr, expr, + "%pX and %pX can't both use standard input.\n", + parser->stdin_expr, expr); + return false; + } + + parser->stdin_expr = expr; + return true; +} + +/** + * Allocate a new expression. + */ +static struct bfs_expr *parse_new_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc, char **argv, enum bfs_kind kind) { + struct bfs_expr *expr = bfs_expr_new(parser->ctx, eval_fn, argc, argv, kind); + if (!expr) { + parse_perror(parser, "bfs_expr_new()"); + } + return expr; +} + +/** + * Create a new unary expression. + */ +static struct bfs_expr *new_unary_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, struct bfs_expr *rhs, char **argv) { + struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv, BFS_OPERATOR); + if (!expr) { + return NULL; + } + + bfs_assert(bfs_expr_is_parent(expr)); + bfs_expr_append(expr, rhs); + return expr; +} + +/** + * Create a new binary expression. + */ +static struct bfs_expr *new_binary_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, struct bfs_expr *lhs, struct bfs_expr *rhs, char **argv) { + struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv, BFS_OPERATOR); + if (!expr) { + return NULL; + } + + bfs_assert(bfs_expr_is_parent(expr)); + bfs_expr_append(expr, lhs); + bfs_expr_append(expr, rhs); + return expr; +} + +/** * Fill in a "-print"-type expression. */ -static void init_print_expr(struct parser_state *state, struct bfs_expr *expr) { - expr->cfile = state->ctx->cout; +static void init_print_expr(struct bfs_parser *parser, struct bfs_expr *expr) { + expr->cfile = parser->ctx->cout; expr->path = NULL; } /** * Open a file for an expression. */ -static int expr_open(struct parser_state *state, struct bfs_expr *expr, const char *path) { - struct bfs_ctx *ctx = state->ctx; +static int expr_open(struct bfs_parser *parser, struct bfs_expr *expr, const char *path) { + struct bfs_ctx *ctx = parser->ctx; FILE *file = NULL; CFILE *cfile = NULL; @@ -411,7 +343,7 @@ static int expr_open(struct parser_state *state, struct bfs_expr *expr, const ch goto fail; } - cfile = cfwrap(file, state->use_color ? ctx->colors : NULL, true); + cfile = cfwrap(file, parser->use_color ? ctx->colors : NULL, true); if (!cfile) { goto fail; } @@ -430,7 +362,7 @@ static int expr_open(struct parser_state *state, struct bfs_expr *expr, const ch return 0; fail: - parse_expr_error(state, expr, "%m.\n"); + parse_expr_error(parser, expr, "%s.\n", errstr()); if (cfile) { cfclose(cfile); } else if (file) { @@ -442,15 +374,15 @@ fail: /** * Invoke bfs_stat() on an argument. */ -static int stat_arg(const struct parser_state *state, char **arg, struct bfs_stat *sb) { - const struct bfs_ctx *ctx = state->ctx; +static int stat_arg(const struct bfs_parser *parser, char **arg, struct bfs_stat *sb) { + const struct bfs_ctx *ctx = parser->ctx; bool follow = ctx->flags & (BFTW_FOLLOW_ROOTS | BFTW_FOLLOW_ALL); enum bfs_stat_flags flags = follow ? BFS_STAT_TRYFOLLOW : BFS_STAT_NOFOLLOW; int ret = bfs_stat(AT_FDCWD, *arg, flags, sb); if (ret != 0) { - parse_argv_error(state, arg, 1, "%m.\n"); + parse_argv_error(parser, arg, 1, "%s.\n", errstr()); } return ret; } @@ -458,52 +390,57 @@ static int stat_arg(const struct parser_state *state, char **arg, struct bfs_sta /** * Parse the expression specified on the command line. */ -static struct bfs_expr *parse_expr(struct parser_state *state); +static struct bfs_expr *parse_expr(struct bfs_parser *parser); /** * Advance by a single token. */ -static char **parser_advance(struct parser_state *state, enum token_type type, size_t argc) { - if (type != T_FLAG && type != T_PATH) { - state->expr_started = true; +static char **parser_advance(struct bfs_parser *parser, enum bfs_kind kind, size_t argc) { + struct bfs_ctx *ctx = parser->ctx; + + if (kind != BFS_FLAG && kind != BFS_PATH) { + parser->expr_started = true; } - if (type != T_PATH) { - state->last_arg = state->argv; + if (kind != BFS_PATH) { + parser->last_arg = parser->argv; } - char **argv = state->argv; - state->argv += argc; + size_t i = parser->argv - ctx->argv; + ctx->kinds[i] = kind; + + char **argv = parser->argv; + parser->argv += argc; return argv; } /** * Parse a root path. */ -static int parse_root(struct parser_state *state, const char *path) { - char *copy = strdup(path); - if (!copy) { - parse_perror(state, "strdup()"); +static int parse_root(struct bfs_parser *parser, const char *path) { + struct bfs_ctx *ctx = parser->ctx; + const char **root = RESERVE(const char *, &ctx->paths, &ctx->npaths); + if (!root) { + parse_perror(parser, "RESERVE()"); return -1; } - struct bfs_ctx *ctx = state->ctx; - if (DARRAY_PUSH(&ctx->paths, ©) != 0) { - parse_perror(state, "DARRAY_PUSH()"); - free(copy); + *root = strdup(path); + if (!*root) { + --ctx->npaths; + parse_perror(parser, "strdup()"); return -1; } - state->implicit_root = false; return 0; } /** * While parsing an expression, skip any paths and add them to ctx->paths. */ -static int skip_paths(struct parser_state *state) { +static int skip_paths(struct bfs_parser *parser) { while (true) { - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (!arg) { return 0; } @@ -513,7 +450,7 @@ static int skip_paths(struct parser_state *state) { // find uses -- to separate flags from the rest // of the command line. We allow mixing flags // and paths/predicates, so we just ignore --. - parser_advance(state, T_FLAG, 1); + parser_advance(parser, BFS_FLAG, 1); continue; } if (strcmp(arg, "-") != 0) { @@ -528,7 +465,7 @@ static int skip_paths(struct parser_state *state) { return 0; } - if (state->expr_started) { + if (parser->expr_started) { // By POSIX, these can be paths. We only treat them as // such at the beginning of the command line. if (strcmp(arg, ")") == 0 || strcmp(arg, ",") == 0) { @@ -536,16 +473,16 @@ static int skip_paths(struct parser_state *state) { } } - if (state->excluding) { - parse_warning(state, "This path will not be excluded. Use a test like ${blu}-name${rs} or ${blu}-path${rs}\n"); - bfs_warning(state->ctx, "within ${red}-exclude${rs} to exclude matching files.\n\n"); + if (parser->excluding) { + parse_warning(parser, "This path will not be excluded. Use a test like ${blu}-name${rs} or ${blu}-path${rs}\n"); + bfs_warning(parser->ctx, "within ${red}-exclude${rs} to exclude matching files.\n\n"); } - if (parse_root(state, arg) != 0) { + if (parse_root(parser, arg) != 0) { return -1; } - parser_advance(state, T_PATH, 1); + parser_advance(parser, BFS_PATH, 1); } } @@ -564,17 +501,15 @@ enum int_flags { /** * Parse an integer. */ -static const char *parse_int(const struct parser_state *state, char **arg, const char *str, void *result, enum int_flags flags) { - char *endptr; - +static const char *parse_int(const struct bfs_parser *parser, char **arg, const char *str, void *result, enum int_flags flags) { int base = flags & IF_BASE_MASK; if (base == 0) { base = 10; } - errno = 0; - long long value = strtoll(str, &endptr, base); - if (errno != 0) { + char *endptr; + long long value; + if (xstrtoll(str, &endptr, base, &value) != 0) { if (errno == ERANGE) { goto range; } else { @@ -582,13 +517,6 @@ static const char *parse_int(const struct parser_state *state, char **arg, const } } - // https://github.com/llvm/llvm-project/issues/64946 - sanitize_init(&endptr); - - if (endptr == str) { - goto bad; - } - if (!(flags & IF_PARTIAL_OK) && *endptr != '\0') { goto bad; } @@ -625,19 +553,19 @@ static const char *parse_int(const struct parser_state *state, char **arg, const bad: if (!(flags & IF_QUIET)) { - parse_argv_error(state, arg, 1, "${bld}%pq${rs} is not a valid integer.\n", str); + parse_argv_error(parser, arg, 1, "${bld}%pq${rs} is not a valid integer.\n", str); } return NULL; negative: if (!(flags & IF_QUIET)) { - parse_argv_error(state, arg, 1, "Negative integer ${bld}%pq${rs} is not allowed here.\n", str); + parse_argv_error(parser, arg, 1, "Negative integer ${bld}%pq${rs} is not allowed here.\n", str); } return NULL; range: if (!(flags & IF_QUIET)) { - parse_argv_error(state, arg, 1, "${bld}%pq${rs} is too large an integer.\n", str); + parse_argv_error(parser, arg, 1, "${bld}%pq${rs} is too large an integer.\n", str); } return NULL; } @@ -645,7 +573,7 @@ range: /** * Parse an integer and a comparison flag. */ -static const char *parse_icmp(const struct parser_state *state, struct bfs_expr *expr, enum int_flags flags) { +static const char *parse_icmp(const struct bfs_parser *parser, struct bfs_expr *expr, enum int_flags flags) { char **arg = &expr->argv[1]; const char *str = *arg; switch (str[0]) { @@ -662,7 +590,7 @@ static const char *parse_icmp(const struct parser_state *state, struct bfs_expr break; } - return parse_int(state, arg, str, &expr->num, flags | IF_LONG_LONG | IF_UNSIGNED); + return parse_int(parser, arg, str, &expr->num, flags | IF_LONG_LONG | IF_UNSIGNED); } /** @@ -684,153 +612,164 @@ static bool looks_like_icmp(const char *str) { /** * Parse a single flag. */ -static struct bfs_expr *parse_flag(struct parser_state *state, size_t argc) { - char **argv = parser_advance(state, T_FLAG, argc); - return bfs_expr_new(eval_true, argc, argv); +static struct bfs_expr *parse_flag(struct bfs_parser *parser, size_t argc) { + char **argv = parser_advance(parser, BFS_FLAG, argc); + return parse_new_expr(parser, eval_true, argc, argv, BFS_FLAG); } /** * Parse a flag that doesn't take a value. */ -static struct bfs_expr *parse_nullary_flag(struct parser_state *state) { - return parse_flag(state, 1); +static struct bfs_expr *parse_nullary_flag(struct bfs_parser *parser) { + return parse_flag(parser, 1); } /** * Parse a flag that takes a value. */ -static struct bfs_expr *parse_unary_flag(struct parser_state *state) { - const char *arg = state->argv[0]; - const char *value = state->argv[1]; +static struct bfs_expr *parse_unary_flag(struct bfs_parser *parser) { + const char *arg = parser->argv[0]; + char flag = arg[strlen(arg) - 1]; + + const char *value = parser->argv[1]; if (!value) { - parse_error(state, "${cyn}%s${rs} needs a value.\n", arg); + parse_error(parser, "${cyn}-%c${rs} needs a value.\n", flag); + return NULL; + } + + return parse_flag(parser, 2); +} + +/** + * Parse a prefix flag like -O3, -j8, etc. + */ +static struct bfs_expr *parse_prefix_flag(struct bfs_parser *parser, char flag, bool allow_separate, const char **value) { + const char *arg = parser->argv[0]; + + const char *suffix = strchr(arg, flag) + 1; + if (*suffix) { + *value = suffix; + return parse_nullary_flag(parser); + } + + suffix = parser->argv[1]; + if (allow_separate && suffix) { + *value = suffix; + } else { + parse_error(parser, "${cyn}-%c${rs} needs a value.\n", flag); return NULL; } - return parse_flag(state, 2); + return parse_unary_flag(parser); } /** * Parse a single option. */ -static struct bfs_expr *parse_option(struct parser_state *state, size_t argc) { - char **argv = parser_advance(state, T_OPTION, argc); - return bfs_expr_new(eval_true, argc, argv); +static struct bfs_expr *parse_option(struct bfs_parser *parser, size_t argc) { + char **argv = parser_advance(parser, BFS_OPTION, argc); + return parse_new_expr(parser, eval_true, argc, argv, BFS_OPTION); } /** * Parse an option that doesn't take a value. */ -static struct bfs_expr *parse_nullary_option(struct parser_state *state) { - return parse_option(state, 1); +static struct bfs_expr *parse_nullary_option(struct bfs_parser *parser) { + return parse_option(parser, 1); } /** * Parse an option that takes a value. */ -static struct bfs_expr *parse_unary_option(struct parser_state *state) { - const char *arg = state->argv[0]; - const char *value = state->argv[1]; +static struct bfs_expr *parse_unary_option(struct bfs_parser *parser) { + const char *arg = parser->argv[0]; + const char *value = parser->argv[1]; if (!value) { - parse_error(state, "${blu}%s${rs} needs a value.\n", arg); + parse_error(parser, "${blu}%s${rs} needs a value.\n", arg); return NULL; } - return parse_option(state, 2); + return parse_option(parser, 2); } /** * Parse a single test. */ -static struct bfs_expr *parse_test(struct parser_state *state, bfs_eval_fn *eval_fn, size_t argc) { - char **argv = parser_advance(state, T_TEST, argc); - return bfs_expr_new(eval_fn, argc, argv); +static struct bfs_expr *parse_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) { + char **argv = parser_advance(parser, BFS_TEST, argc); + return parse_new_expr(parser, eval_fn, argc, argv, BFS_TEST); } /** * Parse a test that doesn't take a value. */ -static struct bfs_expr *parse_nullary_test(struct parser_state *state, bfs_eval_fn *eval_fn) { - return parse_test(state, eval_fn, 1); +static struct bfs_expr *parse_nullary_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { + return parse_test(parser, eval_fn, 1); } /** * Parse a test that takes a value. */ -static struct bfs_expr *parse_unary_test(struct parser_state *state, bfs_eval_fn *eval_fn) { - const char *arg = state->argv[0]; - const char *value = state->argv[1]; +static struct bfs_expr *parse_unary_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { + const char *arg = parser->argv[0]; + const char *value = parser->argv[1]; if (!value) { - parse_error(state, "${blu}%s${rs} needs a value.\n", arg); + parse_error(parser, "${blu}%s${rs} needs a value.\n", arg); return NULL; } - return parse_test(state, eval_fn, 2); + return parse_test(parser, eval_fn, 2); } /** * Parse a single action. */ -static struct bfs_expr *parse_action(struct parser_state *state, bfs_eval_fn *eval_fn, size_t argc) { - char **argv = parser_advance(state, T_ACTION, argc); +static struct bfs_expr *parse_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) { + char **argv = parser_advance(parser, BFS_ACTION, argc); - if (state->excluding) { - parse_argv_error(state, argv, argc, "This action is not supported within ${red}-exclude${rs}.\n"); + if (parser->excluding) { + parse_argv_error(parser, argv, argc, "This action is not supported within ${red}-exclude${rs}.\n"); return NULL; } - if (eval_fn != eval_prune && eval_fn != eval_quit) { - state->implicit_print = false; + if (eval_fn != eval_limit && eval_fn != eval_prune && eval_fn != eval_quit) { + parser->implicit_print = false; } - return bfs_expr_new(eval_fn, argc, argv); + return parse_new_expr(parser, eval_fn, argc, argv, BFS_ACTION); } /** * Parse an action that takes no arguments. */ -static struct bfs_expr *parse_nullary_action(struct parser_state *state, bfs_eval_fn *eval_fn) { - return parse_action(state, eval_fn, 1); +static struct bfs_expr *parse_nullary_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { + return parse_action(parser, eval_fn, 1); } /** * Parse an action that takes one argument. */ -static struct bfs_expr *parse_unary_action(struct parser_state *state, bfs_eval_fn *eval_fn) { - const char *arg = state->argv[0]; - const char *value = state->argv[1]; +static struct bfs_expr *parse_unary_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { + const char *arg = parser->argv[0]; + const char *value = parser->argv[1]; if (!value) { - parse_error(state, "${blu}%s${rs} needs a value.\n", arg); + parse_error(parser, "${blu}%s${rs} needs a value.\n", arg); return NULL; } - return parse_action(state, eval_fn, 2); -} - -/** - * Add an expression to the exclusions. - */ -static int parse_exclude(struct parser_state *state, struct bfs_expr *expr) { - struct bfs_ctx *ctx = state->ctx; - ctx->exclude = new_binary_expr(eval_or, ctx->exclude, expr, &fake_or_arg); - if (ctx->exclude) { - return 0; - } else { - return -1; - } + return parse_action(parser, eval_fn, 2); } /** * Parse a test expression with integer data and a comparison flag. */ -static struct bfs_expr *parse_test_icmp(struct parser_state *state, bfs_eval_fn *eval_fn) { - struct bfs_expr *expr = parse_unary_test(state, eval_fn); +static struct bfs_expr *parse_test_icmp(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { + struct bfs_expr *expr = parse_unary_test(parser, eval_fn); if (!expr) { return NULL; } - if (!parse_icmp(state, expr, 0)) { - bfs_expr_free(expr); + if (!parse_icmp(parser, expr, 0)) { return NULL; } @@ -866,10 +805,11 @@ static bool parse_debug_flag(const char *flag, size_t len, const char *expected) /** * Parse -D FLAG. */ -static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int arg2) { - struct bfs_ctx *ctx = state->ctx; +static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_ctx *ctx = parser->ctx; - struct bfs_expr *expr = parse_unary_flag(state); + const char *flags; + struct bfs_expr *expr = parse_prefix_flag(parser, 'D', true, &flags); if (!expr) { cfprintf(ctx->cerr, "\n"); debug_help(ctx->cerr); @@ -878,7 +818,7 @@ static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int ar bool unrecognized = false; - for (const char *flag = expr->argv[1], *next; flag; flag = next) { + for (const char *flag = flags, *next; flag; flag = next) { size_t len = strcspn(flag, ","); if (flag[len]) { next = flag + len + 1; @@ -888,8 +828,7 @@ static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int ar if (parse_debug_flag(flag, len, "help")) { debug_help(ctx->cout); - state->just_info = true; - bfs_expr_free(expr); + parser->just_info = true; return NULL; } else if (parse_debug_flag(flag, len, "all")) { ctx->debug = DEBUG_ALL; @@ -907,7 +846,7 @@ static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int ar if (DEBUG_ALL & i) { ctx->debug |= i; } else { - if (parse_expr_warning(state, expr, "Unrecognized debug flag ${bld}")) { + if (parse_expr_warning(parser, expr, "Unrecognized debug flag ${bld}")) { fwrite(flag, 1, len, stderr); cfprintf(ctx->cerr, "${rs}.\n\n"); unrecognized = true; @@ -926,23 +865,23 @@ static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int ar /** * Parse -On. */ -static struct bfs_expr *parse_optlevel(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_flag(state); +static struct bfs_expr *parse_optlevel(struct bfs_parser *parser, int arg1, int arg2) { + const char *arg; + struct bfs_expr *expr = parse_prefix_flag(parser, 'O', false, &arg); if (!expr) { return NULL; } - int *optlevel = &state->ctx->optlevel; + int *optlevel = &parser->ctx->optlevel; - if (strcmp(expr->argv[0], "-Ofast") == 0) { + if (strcmp(arg, "fast") == 0) { *optlevel = 4; - } else if (!parse_int(state, expr->argv, expr->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) { - bfs_expr_free(expr); + } else if (!parse_int(parser, expr->argv, arg, optlevel, IF_INT | IF_UNSIGNED)) { return NULL; } if (*optlevel > 4) { - parse_expr_warning(state, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", expr->argv[0] + 2); + parse_expr_warning(parser, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", arg); } return expr; @@ -951,30 +890,30 @@ static struct bfs_expr *parse_optlevel(struct parser_state *state, int arg1, int /** * Parse -[PHL], -follow. */ -static struct bfs_expr *parse_follow(struct parser_state *state, int flags, int option) { - struct bfs_ctx *ctx = state->ctx; +static struct bfs_expr *parse_follow(struct bfs_parser *parser, int flags, int option) { + struct bfs_ctx *ctx = parser->ctx; ctx->flags &= ~(BFTW_FOLLOW_ROOTS | BFTW_FOLLOW_ALL); ctx->flags |= flags; if (option) { - return parse_nullary_option(state); + return parse_nullary_option(parser); } else { - return parse_nullary_flag(state); + return parse_nullary_flag(parser); } } /** * Parse -X. */ -static struct bfs_expr *parse_xargs_safe(struct parser_state *state, int arg1, int arg2) { - state->ctx->xargs_safe = true; - return parse_nullary_flag(state); +static struct bfs_expr *parse_xargs_safe(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->xargs_safe = true; + return parse_nullary_flag(parser); } /** * Parse -executable, -readable, -writable */ -static struct bfs_expr *parse_access(struct parser_state *state, int flag, int arg2) { - struct bfs_expr *expr = parse_nullary_test(state, eval_access); +static struct bfs_expr *parse_access(struct bfs_parser *parser, int flag, int arg2) { + struct bfs_expr *expr = parse_nullary_test(parser, eval_access); if (expr) { expr->num = flag; } @@ -984,11 +923,11 @@ static struct bfs_expr *parse_access(struct parser_state *state, int flag, int a /** * Parse -acl. */ -static struct bfs_expr *parse_acl(struct parser_state *state, int flag, int arg2) { +static struct bfs_expr *parse_acl(struct bfs_parser *parser, int flag, int arg2) { #if BFS_CAN_CHECK_ACL - return parse_nullary_test(state, eval_acl); + return parse_nullary_test(parser, eval_acl); #else - parse_error(state, "Missing platform support.\n"); + parse_error(parser, "Missing platform support.\n"); return NULL; #endif } @@ -996,36 +935,32 @@ static struct bfs_expr *parse_acl(struct parser_state *state, int flag, int arg2 /** * Parse -[aBcm]?newer. */ -static struct bfs_expr *parse_newer(struct parser_state *state, int field, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_newer); +static struct bfs_expr *parse_newer(struct bfs_parser *parser, int field, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_newer); if (!expr) { return NULL; } struct bfs_stat sb; - if (stat_arg(state, &expr->argv[1], &sb) != 0) { - goto fail; + if (stat_arg(parser, &expr->argv[1], &sb) != 0) { + return NULL; } expr->reftime = sb.mtime; expr->stat_field = field; return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -[aBcm]min. */ -static struct bfs_expr *parse_min(struct parser_state *state, int field, int arg2) { - struct bfs_expr *expr = parse_test_icmp(state, eval_time); +static struct bfs_expr *parse_min(struct bfs_parser *parser, int field, int arg2) { + struct bfs_expr *expr = parse_test_icmp(parser, eval_time); if (!expr) { return NULL; } - expr->reftime = state->now; + expr->reftime = parser->now; expr->stat_field = field; expr->time_unit = BFS_MINUTES; return expr; @@ -1034,18 +969,18 @@ static struct bfs_expr *parse_min(struct parser_state *state, int field, int arg /** * Parse -[aBcm]time. */ -static struct bfs_expr *parse_time(struct parser_state *state, int field, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_time); +static struct bfs_expr *parse_time(struct bfs_parser *parser, int field, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_time); if (!expr) { return NULL; } - expr->reftime = state->now; + expr->reftime = parser->now; expr->stat_field = field; - const char *tail = parse_icmp(state, expr, IF_PARTIAL_OK); + const char *tail = parse_icmp(parser, expr, IF_PARTIAL_OK); if (!tail) { - goto fail; + return NULL; } if (!*tail) { @@ -1060,21 +995,21 @@ static struct bfs_expr *parse_time(struct parser_state *state, int field, int ar switch (*tail) { case 'w': time *= 7; - fallthru; + _fallthrough; case 'd': time *= 24; - fallthru; + _fallthrough; case 'h': time *= 60; - fallthru; + _fallthrough; case 'm': time *= 60; - fallthru; + _fallthrough; case 's': break; default: - parse_expr_error(state, expr, "Unknown time unit ${bld}%c${rs}.\n", *tail); - goto fail; + parse_expr_error(parser, expr, "Unknown time unit ${bld}%c${rs}.\n", *tail); + return NULL; } expr->num += time; @@ -1083,32 +1018,28 @@ static struct bfs_expr *parse_time(struct parser_state *state, int field, int ar break; } - tail = parse_int(state, &expr->argv[1], tail, &time, IF_PARTIAL_OK | IF_LONG_LONG | IF_UNSIGNED); + tail = parse_int(parser, &expr->argv[1], tail, &time, IF_PARTIAL_OK | IF_LONG_LONG | IF_UNSIGNED); if (!tail) { - goto fail; + return NULL; } if (!*tail) { - parse_expr_error(state, expr, "Missing time unit.\n"); - goto fail; + parse_expr_error(parser, expr, "Missing time unit.\n"); + return NULL; } } expr->time_unit = BFS_SECONDS; return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -capable. */ -static struct bfs_expr *parse_capable(struct parser_state *state, int flag, int arg2) { +static struct bfs_expr *parse_capable(struct bfs_parser *parser, int flag, int arg2) { #if BFS_CAN_CHECK_CAPABILITIES - return parse_nullary_test(state, eval_capable); + return parse_nullary_test(parser, eval_capable); #else - parse_error(state, "Missing platform support.\n"); + parse_error(parser, "Missing platform support.\n"); return NULL; #endif } @@ -1116,27 +1047,26 @@ static struct bfs_expr *parse_capable(struct parser_state *state, int flag, int /** * Parse -(no)?color. */ -static struct bfs_expr *parse_color(struct parser_state *state, int color, int arg2) { - struct bfs_expr *expr = parse_nullary_option(state); +static struct bfs_expr *parse_color(struct bfs_parser *parser, int color, int arg2) { + struct bfs_expr *expr = parse_nullary_option(parser); if (!expr) { return NULL; } - struct bfs_ctx *ctx = state->ctx; + struct bfs_ctx *ctx = parser->ctx; struct colors *colors = ctx->colors; if (color) { if (!colors) { - parse_expr_error(state, expr, "Error parsing $$LS_COLORS: %s.\n", strerror(ctx->colors_error)); - bfs_expr_free(expr); + parse_expr_error(parser, expr, "Error parsing $$LS_COLORS: %s.\n", xstrerror(ctx->colors_error)); return NULL; } - state->use_color = COLOR_ALWAYS; + parser->use_color = COLOR_ALWAYS; ctx->cout->colors = colors; ctx->cerr->colors = colors; } else { - state->use_color = COLOR_NEVER; + parser->use_color = COLOR_NEVER; ctx->cout->colors = NULL; ctx->cerr->colors = NULL; } @@ -1145,23 +1075,84 @@ static struct bfs_expr *parse_color(struct parser_state *state, int color, int a } /** + * Common code for fnmatch() tests. + */ +static struct bfs_expr *parse_fnmatch(const struct bfs_parser *parser, struct bfs_expr *expr, bool casefold) { + if (!expr) { + return NULL; + } + + expr->pattern = expr->argv[1]; + + if (casefold) { +#ifdef FNM_CASEFOLD + expr->fnm_flags = FNM_CASEFOLD; +#else + parse_expr_error(parser, expr, "Missing platform support.\n"); + return NULL; +#endif + } else { + expr->fnm_flags = 0; + } + + // POSIX says, about fnmatch(): + // + // If pattern ends with an unescaped <backslash>, fnmatch() shall + // return a non-zero value (indicating either no match or an error). + // + // But not all implementations obey this, so check for it ourselves. + size_t i, len = strlen(expr->pattern); + for (i = 0; i < len; ++i) { + if (expr->pattern[len - i - 1] != '\\') { + break; + } + } + if (i % 2 != 0) { + parse_expr_warning(parser, expr, "Unescaped trailing backslash.\n\n"); + expr->eval_fn = eval_false; + return expr; + } + + // strcmp() can be much faster than fnmatch() since it doesn't have to + // parse the pattern, so special-case patterns with no wildcards. + // + // https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_14_01 + expr->literal = strcspn(expr->pattern, "?*\\[") == len; + + return expr; +} + +/** + * Parse -context. + */ +static struct bfs_expr *parse_context(struct bfs_parser *parser, int flag, int arg2) { +#if BFS_CAN_CHECK_CONTEXT + struct bfs_expr *expr = parse_unary_test(parser, eval_context); + return parse_fnmatch(parser, expr, false); +#else + parse_error(parser, "Missing platform support.\n"); + return NULL; +#endif +} + +/** * Parse -{false,true}. */ -static struct bfs_expr *parse_const(struct parser_state *state, int value, int arg2) { - return parse_nullary_test(state, value ? eval_true : eval_false); +static struct bfs_expr *parse_const(struct bfs_parser *parser, int value, int arg2) { + return parse_nullary_test(parser, value ? eval_true : eval_false); } /** * Parse -daystart. */ -static struct bfs_expr *parse_daystart(struct parser_state *state, int arg1, int arg2) { +static struct bfs_expr *parse_daystart(struct bfs_parser *parser, int arg1, int arg2) { struct tm tm; - if (xlocaltime(&state->now.tv_sec, &tm) != 0) { - parse_perror(state, "xlocaltime()"); + if (!localtime_r(&parser->now.tv_sec, &tm)) { + parse_perror(parser, "localtime_r()"); return NULL; } - if (tm.tm_hour || tm.tm_min || tm.tm_sec || state->now.tv_nsec) { + if (tm.tm_hour || tm.tm_min || tm.tm_sec || parser->now.tv_nsec) { ++tm.tm_mday; } tm.tm_hour = 0; @@ -1170,60 +1161,74 @@ static struct bfs_expr *parse_daystart(struct parser_state *state, int arg1, int time_t time; if (xmktime(&tm, &time) != 0) { - parse_perror(state, "xmktime()"); + parse_perror(parser, "xmktime()"); return NULL; } - state->now.tv_sec = time; - state->now.tv_nsec = 0; + parser->now.tv_sec = time; + parser->now.tv_nsec = 0; - return parse_nullary_option(state); + return parse_nullary_option(parser); } /** * Parse -delete. */ -static struct bfs_expr *parse_delete(struct parser_state *state, int arg1, int arg2) { - state->ctx->flags |= BFTW_POST_ORDER; - state->depth_arg = state->argv; - return parse_nullary_action(state, eval_delete); +static struct bfs_expr *parse_delete(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_delete); + if (!expr) { + return NULL; + } + + struct bfs_ctx *ctx = parser->ctx; + ctx->flags |= BFTW_POST_ORDER; + ctx->dangerous = true; + + parser->depth_expr = expr; + return expr; } /** * Parse -d. */ -static struct bfs_expr *parse_depth(struct parser_state *state, int arg1, int arg2) { - state->ctx->flags |= BFTW_POST_ORDER; - state->depth_arg = state->argv; - return parse_nullary_flag(state); +static struct bfs_expr *parse_depth(struct bfs_parser *parser, int flag, int arg2) { + struct bfs_expr *expr = flag + ? parse_nullary_flag(parser) + : parse_nullary_option(parser); + if (!expr) { + return NULL; + } + + parser->ctx->flags |= BFTW_POST_ORDER; + parser->depth_expr = expr; + return expr; } /** * Parse -depth [N]. */ -static struct bfs_expr *parse_depth_n(struct parser_state *state, int arg1, int arg2) { - const char *arg = state->argv[1]; +static struct bfs_expr *parse_depth_n(struct bfs_parser *parser, int arg1, int arg2) { + const char *arg = parser->argv[1]; if (arg && looks_like_icmp(arg)) { - return parse_test_icmp(state, eval_depth); + return parse_test_icmp(parser, eval_depth); } else { - return parse_depth(state, arg1, arg2); + return parse_depth(parser, arg1, arg2); } } /** * Parse -{min,max}depth N. */ -static struct bfs_expr *parse_depth_limit(struct parser_state *state, int is_min, int arg2) { - struct bfs_expr *expr = parse_unary_option(state); +static struct bfs_expr *parse_depth_limit(struct bfs_parser *parser, int is_min, int arg2) { + struct bfs_expr *expr = parse_unary_option(parser); if (!expr) { return NULL; } - struct bfs_ctx *ctx = state->ctx; + struct bfs_ctx *ctx = parser->ctx; int *depth = is_min ? &ctx->mindepth : &ctx->maxdepth; char **arg = &expr->argv[1]; - if (!parse_int(state, arg, *arg, depth, IF_INT | IF_UNSIGNED)) { - bfs_expr_free(expr); + if (!parse_int(parser, arg, *arg, depth, IF_INT | IF_UNSIGNED)) { return NULL; } @@ -1233,8 +1238,8 @@ static struct bfs_expr *parse_depth_limit(struct parser_state *state, int is_min /** * Parse -empty. */ -static struct bfs_expr *parse_empty(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_test(state, eval_empty); +static struct bfs_expr *parse_empty(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_test(parser, eval_empty); if (expr) { // For opendir() expr->ephemeral_fds = 1; @@ -1245,13 +1250,15 @@ static struct bfs_expr *parse_empty(struct parser_state *state, int arg1, int ar /** * Parse -exec(dir)?/-ok(dir)?. */ -static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int arg2) { - struct bfs_exec *execbuf = bfs_exec_parse(state->ctx, state->argv, flags); +static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg2) { + struct bfs_ctx *ctx = parser->ctx; + + struct bfs_exec *execbuf = bfs_exec_parse(ctx, parser->argv, flags); if (!execbuf) { return NULL; } - struct bfs_expr *expr = parse_action(state, eval_exec, execbuf->tmpl_argc + 2); + struct bfs_expr *expr = parse_action(parser, eval_exec, execbuf->tmpl_argc + 2); if (!expr) { bfs_exec_free(execbuf); return NULL; @@ -1263,6 +1270,28 @@ static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int ar expr->ephemeral_fds = 2; if (execbuf->flags & BFS_EXEC_CHDIR) { + // Check for relative paths in $PATH + const char *path = getenv("PATH"); + while (path) { + if (*path != '/') { + size_t len = strcspn(path, ":"); + char *comp = strndup(path, len); + if (comp) { + parse_expr_error(parser, expr, + "This action would be unsafe, since ${bld}$$PATH${rs} contains the relative path ${bld}%pq${rs}\n", comp); + free(comp); + } else { + parse_perror(parser, "strndup()"); + } + return NULL; + } + + path = strchr(path, ':'); + if (path) { + ++path; + } + } + // To dup() the parent directory if (execbuf->flags & BFS_EXEC_MULTI) { ++expr->persistent_fds; @@ -1272,7 +1301,11 @@ static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int ar } if (execbuf->flags & BFS_EXEC_CONFIRM) { - state->ok_expr = expr; + if (!consume_stdin(parser, expr)) { + return NULL; + } + } else { + ctx->dangerous = true; } return expr; @@ -1281,16 +1314,16 @@ static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int ar /** * Parse -exit [STATUS]. */ -static struct bfs_expr *parse_exit(struct parser_state *state, int arg1, int arg2) { +static struct bfs_expr *parse_exit(struct bfs_parser *parser, int arg1, int arg2) { size_t argc = 1; - const char *value = state->argv[1]; + const char *value = parser->argv[1]; int status = EXIT_SUCCESS; - if (value && parse_int(state, NULL, value, &status, IF_INT | IF_UNSIGNED | IF_QUIET)) { + if (value && parse_int(parser, NULL, value, &status, IF_INT | IF_UNSIGNED | IF_QUIET)) { argc = 2; } - struct bfs_expr *expr = parse_action(state, eval_exit, argc); + struct bfs_expr *expr = parse_action(parser, eval_exit, argc); if (expr) { expr->num = status; } @@ -1300,14 +1333,19 @@ static struct bfs_expr *parse_exit(struct parser_state *state, int arg1, int arg /** * Parse -f PATH. */ -static struct bfs_expr *parse_f(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_flag(state); +static struct bfs_expr *parse_f(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_ctx *ctx = parser->ctx; + + struct bfs_expr *expr = parse_unary_flag(parser); if (!expr) { return NULL; } - if (parse_root(state, expr->argv[1]) != 0) { - bfs_expr_free(expr); + // Mark the path as a path, not a regular argument + size_t i = expr->argv - ctx->argv; + ctx->kinds[i + 1] = BFS_PATH; + + if (parse_root(parser, expr->argv[1]) != 0) { return NULL; } @@ -1317,64 +1355,27 @@ static struct bfs_expr *parse_f(struct parser_state *state, int arg1, int arg2) /** * Parse -files0-from PATH. */ -static struct bfs_expr *parse_files0_from(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_option(state); +static struct bfs_expr *parse_files0_from(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_option(parser); if (!expr) { return NULL; } - const char *from = expr->argv[1]; - - FILE *file; - if (strcmp(from, "-") == 0) { - file = stdin; - } else { - file = xfopen(from, O_RDONLY | O_CLOEXEC); - } - if (!file) { - parse_expr_error(state, expr, "%m.\n"); - goto fail; - } - - while (true) { - char *path = xgetdelim(file, '\0'); - if (!path) { - if (errno) { - goto fail; - } else { - break; - } - } - - int ret = parse_root(state, path); - free(path); - if (ret != 0) { - goto fail; - } - } - - if (file == stdin) { - state->files0_stdin_arg = expr->argv; - } else { - fclose(file); - } - - state->implicit_root = false; + // For compatibility with GNU find, + // + // bfs -files0-from a -files0-from b + // + // should *only* use b, not a. So stash the expression here and only + // process the last one at the end of parsing. + parser->files0_expr = expr; return expr; - -fail: - if (file && file != stdin) { - fclose(file); - } - bfs_expr_free(expr); - return NULL; } /** * Parse -flags FLAGS. */ -static struct bfs_expr *parse_flags(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_flags); +static struct bfs_expr *parse_flags(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_flags); if (!expr) { return NULL; } @@ -1396,11 +1397,10 @@ static struct bfs_expr *parse_flags(struct parser_state *state, int arg1, int ar if (xstrtofflags(&flags, &expr->set_flags, &expr->clear_flags) != 0) { if (errno == ENOTSUP) { - parse_expr_error(state, expr, "Missing platform support.\n"); + parse_expr_error(parser, expr, "Missing platform support.\n"); } else { - parse_expr_error(state, expr, "Invalid flags.\n"); + parse_expr_error(parser, expr, "Invalid flags.\n"); } - bfs_expr_free(expr); return NULL; } @@ -1410,107 +1410,96 @@ static struct bfs_expr *parse_flags(struct parser_state *state, int arg1, int ar /** * Parse -fls FILE. */ -static struct bfs_expr *parse_fls(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_action(state, eval_fls); +static struct bfs_expr *parse_fls(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_action(parser, eval_fls); if (!expr) { - goto fail; + return NULL; } - if (expr_open(state, expr, expr->argv[1]) != 0) { - goto fail; + if (expr_open(parser, expr, expr->argv[1]) != 0) { + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -fprint FILE. */ -static struct bfs_expr *parse_fprint(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_action(state, eval_fprint); - if (expr) { - if (expr_open(state, expr, expr->argv[1]) != 0) { - goto fail; - } +static struct bfs_expr *parse_fprint(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_action(parser, eval_fprint); + if (!expr) { + return NULL; } - return expr; -fail: - bfs_expr_free(expr); - return NULL; + if (expr_open(parser, expr, expr->argv[1]) != 0) { + return NULL; + } + + return expr; } /** * Parse -fprint0 FILE. */ -static struct bfs_expr *parse_fprint0(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_action(state, eval_fprint0); - if (expr) { - if (expr_open(state, expr, expr->argv[1]) != 0) { - goto fail; - } +static struct bfs_expr *parse_fprint0(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_action(parser, eval_fprint0); + if (!expr) { + return NULL; } - return expr; -fail: - bfs_expr_free(expr); - return NULL; + if (expr_open(parser, expr, expr->argv[1]) != 0) { + return NULL; + } + + return expr; } /** * Parse -fprintf FILE FORMAT. */ -static struct bfs_expr *parse_fprintf(struct parser_state *state, int arg1, int arg2) { - const char *arg = state->argv[0]; +static struct bfs_expr *parse_fprintf(struct bfs_parser *parser, int arg1, int arg2) { + const char *arg = parser->argv[0]; - const char *file = state->argv[1]; + const char *file = parser->argv[1]; if (!file) { - parse_error(state, "${blu}%s${rs} needs a file.\n", arg); + parse_error(parser, "${blu}%s${rs} needs a file.\n", arg); return NULL; } - const char *format = state->argv[2]; + const char *format = parser->argv[2]; if (!format) { - parse_error(state, "${blu}%s${rs} needs a format string.\n", arg); + parse_error(parser, "${blu}%s${rs} needs a format string.\n", arg); return NULL; } - struct bfs_expr *expr = parse_action(state, eval_fprintf, 3); + struct bfs_expr *expr = parse_action(parser, eval_fprintf, 3); if (!expr) { return NULL; } - if (expr_open(state, expr, file) != 0) { - goto fail; + if (expr_open(parser, expr, file) != 0) { + return NULL; } - if (bfs_printf_parse(state->ctx, expr, format) != 0) { - goto fail; + if (bfs_printf_parse(parser->ctx, expr, format) != 0) { + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -fstype TYPE. */ -static struct bfs_expr *parse_fstype(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_fstype); +static struct bfs_expr *parse_fstype(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_fstype); if (!expr) { return NULL; } - if (!bfs_ctx_mtab(state->ctx)) { - parse_expr_error(state, expr, "Couldn't parse the mount table: %m.\n"); - bfs_expr_free(expr); + if (!bfs_ctx_mtab(parser->ctx)) { + parse_expr_error(parser, expr, "Couldn't parse the mount table: %s.\n", errstr()); return NULL; } @@ -1520,237 +1509,199 @@ static struct bfs_expr *parse_fstype(struct parser_state *state, int arg1, int a /** * Parse -gid/-group. */ -static struct bfs_expr *parse_group(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_gid); +static struct bfs_expr *parse_group(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_gid); if (!expr) { return NULL; } - const struct group *grp = bfs_getgrnam(state->ctx->groups, expr->argv[1]); + const struct group *grp = bfs_getgrnam(parser->ctx->groups, expr->argv[1]); if (grp) { expr->num = grp->gr_gid; expr->int_cmp = BFS_INT_EQUAL; } else if (looks_like_icmp(expr->argv[1])) { - if (!parse_icmp(state, expr, 0)) { - goto fail; + if (!parse_icmp(parser, expr, 0)) { + return NULL; } } else if (errno) { - parse_expr_error(state, expr, "%m.\n"); - goto fail; + parse_expr_error(parser, expr, "%s.\n", errstr()); + return NULL; } else { - parse_expr_error(state, expr, "No such group.\n"); - goto fail; + parse_expr_error(parser, expr, "No such group.\n"); + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -unique. */ -static struct bfs_expr *parse_unique(struct parser_state *state, int arg1, int arg2) { - state->ctx->unique = true; - return parse_nullary_option(state); +static struct bfs_expr *parse_unique(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->unique = true; + return parse_nullary_option(parser); } /** * Parse -used N. */ -static struct bfs_expr *parse_used(struct parser_state *state, int arg1, int arg2) { - return parse_test_icmp(state, eval_used); +static struct bfs_expr *parse_used(struct bfs_parser *parser, int arg1, int arg2) { + return parse_test_icmp(parser, eval_used); } /** * Parse -uid/-user. */ -static struct bfs_expr *parse_user(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_uid); +static struct bfs_expr *parse_user(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_uid); if (!expr) { return NULL; } - const struct passwd *pwd = bfs_getpwnam(state->ctx->users, expr->argv[1]); + const struct passwd *pwd = bfs_getpwnam(parser->ctx->users, expr->argv[1]); if (pwd) { expr->num = pwd->pw_uid; expr->int_cmp = BFS_INT_EQUAL; } else if (looks_like_icmp(expr->argv[1])) { - if (!parse_icmp(state, expr, 0)) { - goto fail; + if (!parse_icmp(parser, expr, 0)) { + return NULL; } } else if (errno) { - parse_expr_error(state, expr, "%m.\n"); - goto fail; + parse_expr_error(parser, expr, "%s.\n", errstr()); + return NULL; } else { - parse_expr_error(state, expr, "No such user.\n"); - goto fail; + parse_expr_error(parser, expr, "No such user.\n"); + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -hidden. */ -static struct bfs_expr *parse_hidden(struct parser_state *state, int arg1, int arg2) { - return parse_nullary_test(state, eval_hidden); +static struct bfs_expr *parse_hidden(struct bfs_parser *parser, int arg1, int arg2) { + return parse_nullary_test(parser, eval_hidden); } /** * Parse -(no)?ignore_readdir_race. */ -static struct bfs_expr *parse_ignore_races(struct parser_state *state, int ignore, int arg2) { - state->ctx->ignore_races = ignore; - return parse_nullary_option(state); +static struct bfs_expr *parse_ignore_races(struct bfs_parser *parser, int ignore, int arg2) { + parser->ctx->ignore_races = ignore; + return parse_nullary_option(parser); } /** * Parse -inum N. */ -static struct bfs_expr *parse_inum(struct parser_state *state, int arg1, int arg2) { - return parse_test_icmp(state, eval_inum); +static struct bfs_expr *parse_inum(struct bfs_parser *parser, int arg1, int arg2) { + return parse_test_icmp(parser, eval_inum); } /** * Parse -j<n>. */ -static struct bfs_expr *parse_jobs(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_flag(state); +static struct bfs_expr *parse_jobs(struct bfs_parser *parser, int arg1, int arg2) { + const char *arg; + struct bfs_expr *expr = parse_prefix_flag(parser, 'j', false, &arg); if (!expr) { return NULL; } unsigned int n; - if (!parse_int(state, expr->argv, expr->argv[0] + 2, &n, IF_INT | IF_UNSIGNED)) { - bfs_expr_free(expr); + if (!parse_int(parser, expr->argv, arg, &n, IF_INT | IF_UNSIGNED)) { return NULL; } if (n == 0) { - parse_expr_error(state, expr, "${bld}0${rs} is not enough threads.\n"); - bfs_expr_free(expr); + parse_expr_error(parser, expr, "${bld}0${rs} is not enough threads.\n"); return NULL; } - state->ctx->threads = n; + parser->ctx->threads = n; return expr; } /** - * Parse -links N. + * Parse -limit N. */ -static struct bfs_expr *parse_links(struct parser_state *state, int arg1, int arg2) { - return parse_test_icmp(state, eval_links); -} - -/** - * Parse -ls. - */ -static struct bfs_expr *parse_ls(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_action(state, eval_fls); +static struct bfs_expr *parse_limit(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_action(parser, eval_limit); if (!expr) { return NULL; } - init_print_expr(state, expr); + char **arg = &expr->argv[1]; + if (!parse_int(parser, arg, *arg, &expr->num, IF_LONG_LONG)) { + return NULL; + } + + if (expr->num <= 0) { + parse_expr_error(parser, expr, "The %pX must be at least ${bld}1${rs}.\n", expr); + return NULL; + } + + parser->limit_expr = expr; return expr; } /** - * Parse -mount. + * Parse -links N. + */ +static struct bfs_expr *parse_links(struct bfs_parser *parser, int arg1, int arg2) { + return parse_test_icmp(parser, eval_links); +} + +/** + * Parse -ls. */ -static struct bfs_expr *parse_mount(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_option(state); +static struct bfs_expr *parse_ls(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_fls); if (!expr) { return NULL; } - parse_expr_warning(state, expr, "In the future, ${blu}%s${rs} will skip mount points entirely, unlike\n", expr->argv[0]); - bfs_warning(state->ctx, "${blu}-xdev${rs}, due to http://austingroupbugs.net/view.php?id=1133.\n\n"); - - state->ctx->flags |= BFTW_PRUNE_MOUNTS; - state->mount_arg = expr->argv; + init_print_expr(parser, expr); return expr; } /** - * Common code for fnmatch() tests. + * Parse -mount. */ -static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct bfs_expr *expr, bool casefold) { +static struct bfs_expr *parse_mount(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_option(parser); if (!expr) { return NULL; } - expr->pattern = expr->argv[1]; - - if (casefold) { -#ifdef FNM_CASEFOLD - expr->fnm_flags = FNM_CASEFOLD; -#else - parse_expr_error(state, expr, "Missing platform support.\n"); - bfs_expr_free(expr); - return NULL; -#endif - } else { - expr->fnm_flags = 0; - } - - // POSIX says, about fnmatch(): - // - // If pattern ends with an unescaped <backslash>, fnmatch() shall - // return a non-zero value (indicating either no match or an error). - // - // But not all implementations obey this, so check for it ourselves. - size_t i, len = strlen(expr->pattern); - for (i = 0; i < len; ++i) { - if (expr->pattern[len - i - 1] != '\\') { - break; - } - } - if (i % 2 != 0) { - parse_expr_warning(state, expr, "Unescaped trailing backslash.\n\n"); - expr->eval_fn = eval_false; - return expr; - } - - // strcmp() can be much faster than fnmatch() since it doesn't have to - // parse the pattern, so special-case patterns with no wildcards. - // - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01 - expr->literal = strcspn(expr->pattern, "?*\\[") == len; - + parser->ctx->flags |= BFTW_SKIP_MOUNTS; + parser->mount_expr = expr; return expr; } /** * Parse -i?name. */ -static struct bfs_expr *parse_name(struct parser_state *state, int casefold, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_name); - return parse_fnmatch(state, expr, casefold); +static struct bfs_expr *parse_name(struct bfs_parser *parser, int casefold, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_name); + return parse_fnmatch(parser, expr, casefold); } /** * Parse -i?path, -i?wholename. */ -static struct bfs_expr *parse_path(struct parser_state *state, int casefold, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_path); - return parse_fnmatch(state, expr, casefold); +static struct bfs_expr *parse_path(struct bfs_parser *parser, int casefold, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_path); + return parse_fnmatch(parser, expr, casefold); } /** * Parse -i?lname. */ -static struct bfs_expr *parse_lname(struct parser_state *state, int casefold, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_lname); - return parse_fnmatch(state, expr, casefold); +static struct bfs_expr *parse_lname(struct bfs_parser *parser, int casefold, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_lname); + return parse_fnmatch(parser, expr, casefold); } /** Get the bfs_stat_field for X/Y in -newerXY. */ @@ -1770,20 +1721,20 @@ static enum bfs_stat_field parse_newerxy_field(char c) { } /** Parse an explicit reference timestamp for -newerXt and -*since. */ -static int parse_reftime(const struct parser_state *state, struct bfs_expr *expr) { +static int parse_reftime(const struct bfs_parser *parser, struct bfs_expr *expr) { if (xgetdate(expr->argv[1], &expr->reftime) == 0) { return 0; } else if (errno != EINVAL) { - parse_expr_error(state, expr, "%m.\n"); + parse_expr_error(parser, expr, "%s.\n", errstr()); return -1; } - parse_expr_error(state, expr, "Invalid timestamp.\n\n"); + parse_expr_error(parser, expr, "Invalid timestamp.\n\n"); fprintf(stderr, "Supported timestamp formats are ISO 8601-like, e.g.\n\n"); struct tm tm; - if (xlocaltime(&state->now.tv_sec, &tm) != 0) { - parse_perror(state, "xlocaltime()"); + if (!localtime_r(&parser->now.tv_sec, &tm)) { + parse_perror(parser, "localtime_r()"); return -1; } @@ -1792,18 +1743,18 @@ static int parse_reftime(const struct parser_state *state, struct bfs_expr *expr fprintf(stderr, " - %04d-%02d-%02d\n", year, month, tm.tm_mday); fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); -#if __FreeBSD__ +#if BFS_HAS_TM_GMTOFF int gmtoff = tm.tm_gmtoff; #else int gmtoff = -timezone; #endif - int tz_hour = gmtoff/3600; - int tz_min = (labs(gmtoff)/60)%60; + int tz_hour = gmtoff / 3600; + int tz_min = (labs(gmtoff) / 60) % 60; fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d%+03d:%02d\n", - year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tz_hour, tz_min); + year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tz_hour, tz_min); - if (xgmtime(&state->now.tv_sec, &tm) != 0) { - parse_perror(state, "xgmtime()"); + if (!gmtime_r(&parser->now.tv_sec, &tm)) { + parse_perror(parser, "gmtime_r()"); return -1; } @@ -1817,66 +1768,69 @@ static int parse_reftime(const struct parser_state *state, struct bfs_expr *expr /** * Parse -newerXY. */ -static struct bfs_expr *parse_newerxy(struct parser_state *state, int arg1, int arg2) { - const char *arg = state->argv[0]; +static struct bfs_expr *parse_newerxy(struct bfs_parser *parser, int arg1, int arg2) { + const char *arg = parser->argv[0]; if (strlen(arg) != 8) { - parse_error(state, "Expected ${blu}-newer${bld}XY${rs}; found ${blu}-newer${bld}%pq${rs}.\n", arg + 6); + parse_error(parser, "Expected ${blu}-newer${bld}XY${rs}; found ${blu}-newer${bld}%pq${rs}.\n", arg + 6); return NULL; } - struct bfs_expr *expr = parse_unary_test(state, eval_newer); + struct bfs_expr *expr = parse_unary_test(parser, eval_newer); if (!expr) { - goto fail; + return NULL; } expr->stat_field = parse_newerxy_field(arg[6]); if (!expr->stat_field) { - parse_expr_error(state, expr, - "For ${blu}-newer${bld}XY${rs}, ${bld}X${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, or ${bld}B${rs}, not ${err}%c${rs}.\n", - arg[6]); - goto fail; + parse_expr_error(parser, expr, + "For ${blu}-newer${bld}XY${rs}, ${bld}X${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, or ${bld}B${rs}, not ${err}%c${rs}.\n", + arg[6]); + return NULL; } if (arg[7] == 't') { - if (parse_reftime(state, expr) != 0) { - goto fail; + if (parse_reftime(parser, expr) != 0) { + return NULL; } } else { enum bfs_stat_field field = parse_newerxy_field(arg[7]); if (!field) { - parse_expr_error(state, expr, - "For ${blu}-newer${bld}XY${rs}, ${bld}Y${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, ${bld}B${rs}, or ${bld}t${rs}, not ${err}%c${rs}.\n", - arg[7]); - goto fail; + parse_expr_error(parser, expr, + "For ${blu}-newer${bld}XY${rs}, ${bld}Y${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, ${bld}B${rs}, or ${bld}t${rs}, not ${err}%c${rs}.\n", + arg[7]); + return NULL; } struct bfs_stat sb; - if (stat_arg(state, &expr->argv[1], &sb) != 0) { - goto fail; + if (stat_arg(parser, &expr->argv[1], &sb) != 0) { + return NULL; } - const struct timespec *reftime = bfs_stat_time(&sb, field); if (!reftime) { - parse_expr_error(state, expr, "Couldn't get file %s.\n", bfs_stat_field_name(field)); - goto fail; + parse_expr_error(parser, expr, "Couldn't get file %s.\n", bfs_stat_field_name(field)); + return NULL; } expr->reftime = *reftime; } return expr; +} -fail: - bfs_expr_free(expr); - return NULL; +/** + * Parse -noerror. + */ +static struct bfs_expr *parse_noerror(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->ignore_errors = true; + return parse_nullary_option(parser); } /** * Parse -nogroup. */ -static struct bfs_expr *parse_nogroup(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_test(state, eval_nogroup); +static struct bfs_expr *parse_nogroup(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_test(parser, eval_nogroup); if (expr) { // Who knows how many FDs getgrgid_r() needs? expr->ephemeral_fds = 3; @@ -1887,33 +1841,36 @@ static struct bfs_expr *parse_nogroup(struct parser_state *state, int arg1, int /** * Parse -nohidden. */ -static struct bfs_expr *parse_nohidden(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *hidden = bfs_expr_new(eval_hidden, 1, &fake_hidden_arg); +static struct bfs_expr *parse_nohidden(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *hidden = parse_new_expr(parser, eval_hidden, 1, &fake_hidden_arg, BFS_TEST); if (!hidden) { return NULL; } - if (parse_exclude(state, hidden) != 0) { - return NULL; - } - - return parse_nullary_option(state); + bfs_expr_append(parser->ctx->exclude, hidden); + return parse_nullary_option(parser); } /** * Parse -noleaf. */ -static struct bfs_expr *parse_noleaf(struct parser_state *state, int arg1, int arg2) { - parse_warning(state, "${ex}%s${rs} does not apply the optimization that ${blu}%s${rs} inhibits.\n\n", - BFS_COMMAND, state->argv[0]); - return parse_nullary_option(state); +static struct bfs_expr *parse_noleaf(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_option(parser); + if (!expr) { + return NULL; + } + + parse_expr_warning(parser, expr, + "${ex}%s${rs} does not apply the optimization that %px inhibits.\n\n", + BFS_COMMAND, expr); + return expr; } /** * Parse -nouser. */ -static struct bfs_expr *parse_nouser(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_test(state, eval_nouser); +static struct bfs_expr *parse_nouser(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_test(parser, eval_nouser); if (expr) { // Who knows how many FDs getpwuid_r() needs? expr->ephemeral_fds = 3; @@ -1924,10 +1881,10 @@ static struct bfs_expr *parse_nouser(struct parser_state *state, int arg1, int a /** * Parse a permission mode like chmod(1). */ -static int parse_mode(const struct parser_state *state, const char *mode, struct bfs_expr *expr) { +static int parse_mode(const struct bfs_parser *parser, const char *mode, struct bfs_expr *expr) { if (mode[0] >= '0' && mode[0] <= '9') { unsigned int parsed; - if (!parse_int(state, NULL, mode, &parsed, 8 | IF_INT | IF_UNSIGNED | IF_QUIET)) { + if (!parse_int(parser, NULL, mode, &parsed, 8 | IF_INT | IF_UNSIGNED | IF_QUIET)) { goto fail; } if (parsed > 07777) { @@ -1939,6 +1896,8 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct return 0; } + mode_t umask = parser->ctx->umask; + expr->file_mode = 0; expr->dir_mode = 0; @@ -1967,25 +1926,27 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct MODE_ACTION_APPLY, MODE_OP, MODE_PERM, - } mstate = MODE_CLAUSE; + } state = MODE_CLAUSE; enum { MODE_PLUS, MODE_MINUS, MODE_EQUALS, - } op = uninit(op, MODE_EQUALS); + } op uninit(MODE_EQUALS); - mode_t who = uninit(who, 0); - mode_t file_change = uninit(file_change, 0); - mode_t dir_change = uninit(dir_change, 0); + mode_t who uninit(0); + mode_t mask uninit(0); + mode_t file_change uninit(0); + mode_t dir_change uninit(0); const char *i = mode; while (true) { - switch (mstate) { + switch (state) { case MODE_CLAUSE: who = 0; - mstate = MODE_WHO; - fallthru; + mask = 0777; + state = MODE_WHO; + _fallthrough; case MODE_WHO: switch (*i) { @@ -2002,7 +1963,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct who |= 0777; break; default: - mstate = MODE_ACTION; + state = MODE_ACTION; continue; } break; @@ -2012,7 +1973,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct case MODE_EQUALS: expr->file_mode &= ~who; expr->dir_mode &= ~who; - fallthru; + _fallthrough; case MODE_PLUS: expr->file_mode |= file_change; expr->dir_mode |= dir_change; @@ -2022,37 +1983,40 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct expr->dir_mode &= ~dir_change; break; } - fallthru; + _fallthrough; case MODE_ACTION: if (who == 0) { who = 0777; + mask = who & ~umask; + } else { + mask = who; } switch (*i) { case '+': op = MODE_PLUS; - mstate = MODE_OP; + state = MODE_OP; break; case '-': op = MODE_MINUS; - mstate = MODE_OP; + state = MODE_OP; break; case '=': op = MODE_EQUALS; - mstate = MODE_OP; + state = MODE_OP; break; case ',': - if (mstate == MODE_ACTION_APPLY) { - mstate = MODE_CLAUSE; + if (state == MODE_ACTION_APPLY) { + state = MODE_CLAUSE; } else { goto fail; } break; case '\0': - if (mstate == MODE_ACTION_APPLY) { + if (state == MODE_ACTION_APPLY) { goto done; } else { goto fail; @@ -2081,32 +2045,32 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct default: file_change = 0; dir_change = 0; - mstate = MODE_PERM; + state = MODE_PERM; continue; } file_change |= (file_change << 6) | (file_change << 3); - file_change &= who; + file_change &= mask; dir_change |= (dir_change << 6) | (dir_change << 3); - dir_change &= who; - mstate = MODE_ACTION_APPLY; + dir_change &= mask; + state = MODE_ACTION_APPLY; break; case MODE_PERM: switch (*i) { case 'r': - file_change |= who & 0444; - dir_change |= who & 0444; + file_change |= mask & 0444; + dir_change |= mask & 0444; break; case 'w': - file_change |= who & 0222; - dir_change |= who & 0222; + file_change |= mask & 0222; + dir_change |= mask & 0222; break; case 'x': - file_change |= who & 0111; - fallthru; + file_change |= mask & 0111; + _fallthrough; case 'X': - dir_change |= who & 0111; + dir_change |= mask & 0111; break; case 's': if (who & 0700) { @@ -2125,7 +2089,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct } break; default: - mstate = MODE_ACTION_APPLY; + state = MODE_ACTION_APPLY; continue; } break; @@ -2138,15 +2102,15 @@ done: return 0; fail: - parse_expr_error(state, expr, "Invalid mode.\n"); + parse_expr_error(parser, expr, "Invalid mode.\n"); return -1; } /** * Parse -perm MODE. */ -static struct bfs_expr *parse_perm(struct parser_state *state, int field, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_perm); +static struct bfs_expr *parse_perm(struct bfs_parser *parser, int field, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_perm); if (!expr) { return NULL; } @@ -2167,30 +2131,26 @@ static struct bfs_expr *parse_perm(struct parser_state *state, int field, int ar ++mode; break; } - fallthru; + _fallthrough; default: expr->mode_cmp = BFS_MODE_EQUAL; break; } - if (parse_mode(state, mode, expr) != 0) { - goto fail; + if (parse_mode(parser, mode, expr) != 0) { + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -print. */ -static struct bfs_expr *parse_print(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_action(state, eval_fprint); +static struct bfs_expr *parse_print(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_fprint); if (expr) { - init_print_expr(state, expr); + init_print_expr(parser, expr); } return expr; } @@ -2198,10 +2158,10 @@ static struct bfs_expr *parse_print(struct parser_state *state, int arg1, int ar /** * Parse -print0. */ -static struct bfs_expr *parse_print0(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_action(state, eval_fprint0); +static struct bfs_expr *parse_print0(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_fprint0); if (expr) { - init_print_expr(state, expr); + init_print_expr(parser, expr); } return expr; } @@ -2209,16 +2169,15 @@ static struct bfs_expr *parse_print0(struct parser_state *state, int arg1, int a /** * Parse -printf FORMAT. */ -static struct bfs_expr *parse_printf(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_action(state, eval_fprintf); +static struct bfs_expr *parse_printf(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_action(parser, eval_fprintf); if (!expr) { return NULL; } - init_print_expr(state, expr); + init_print_expr(parser, expr); - if (bfs_printf_parse(state->ctx, expr, expr->argv[1]) != 0) { - bfs_expr_free(expr); + if (bfs_printf_parse(parser->ctx, expr, expr->argv[1]) != 0) { return NULL; } @@ -2228,10 +2187,10 @@ static struct bfs_expr *parse_printf(struct parser_state *state, int arg1, int a /** * Parse -printx. */ -static struct bfs_expr *parse_printx(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_action(state, eval_fprintx); +static struct bfs_expr *parse_printx(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_fprintx); if (expr) { - init_print_expr(state, expr); + init_print_expr(parser, expr); } return expr; } @@ -2239,67 +2198,67 @@ static struct bfs_expr *parse_printx(struct parser_state *state, int arg1, int a /** * Parse -prune. */ -static struct bfs_expr *parse_prune(struct parser_state *state, int arg1, int arg2) { - state->prune_arg = state->argv; - return parse_nullary_action(state, eval_prune); +static struct bfs_expr *parse_prune(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_prune); + if (!expr) { + return NULL; + } + + parser->prune_expr = expr; + return expr; } /** * Parse -quit. */ -static struct bfs_expr *parse_quit(struct parser_state *state, int arg1, int arg2) { - return parse_nullary_action(state, eval_quit); +static struct bfs_expr *parse_quit(struct bfs_parser *parser, int arg1, int arg2) { + return parse_nullary_action(parser, eval_quit); } /** * Parse -i?regex. */ -static struct bfs_expr *parse_regex(struct parser_state *state, int flags, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_regex); +static struct bfs_expr *parse_regex(struct bfs_parser *parser, int flags, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_regex); if (!expr) { - goto fail; + return NULL; } - if (bfs_regcomp(&expr->regex, expr->argv[1], state->regex_type, flags) != 0) { - if (!expr->regex) { - parse_perror(state, "bfs_regcomp()"); - goto fail; - } - - char *str = bfs_regerror(expr->regex); - if (!str) { - parse_perror(state, "bfs_regerror()"); - goto fail; + if (bfs_regcomp(&expr->regex, expr->argv[1], parser->regex_type, flags) != 0) { + if (expr->regex) { + char *str = bfs_regerror(expr->regex); + if (str) { + parse_expr_error(parser, expr, "%s.\n", str); + free(str); + } else { + parse_perror(parser, "bfs_regerror()"); + } + } else { + parse_perror(parser, "bfs_regcomp()"); } - parse_expr_error(state, expr, "%s.\n", str); - free(str); - goto fail; + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -E. */ -static struct bfs_expr *parse_regex_extended(struct parser_state *state, int arg1, int arg2) { - state->regex_type = BFS_REGEX_POSIX_EXTENDED; - return parse_nullary_flag(state); +static struct bfs_expr *parse_regex_extended(struct bfs_parser *parser, int arg1, int arg2) { + parser->regex_type = BFS_REGEX_POSIX_EXTENDED; + return parse_nullary_flag(parser); } /** * Parse -regextype TYPE. */ -static struct bfs_expr *parse_regextype(struct parser_state *state, int arg1, int arg2) { - struct bfs_ctx *ctx = state->ctx; +static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_ctx *ctx = parser->ctx; CFILE *cfile = ctx->cerr; - struct bfs_expr *expr = parse_unary_option(state); + struct bfs_expr *expr = parse_unary_option(parser); if (!expr) { cfprintf(cfile, "\n"); goto list_types; @@ -2308,23 +2267,34 @@ static struct bfs_expr *parse_regextype(struct parser_state *state, int arg1, in // See https://www.gnu.org/software/gnulib/manual/html_node/Predefined-Syntaxes.html const char *type = expr->argv[1]; if (strcmp(type, "posix-basic") == 0 + || strcmp(type, "posix-minimal-basic") == 0 || strcmp(type, "ed") == 0 || strcmp(type, "sed") == 0) { - state->regex_type = BFS_REGEX_POSIX_BASIC; + parser->regex_type = BFS_REGEX_POSIX_BASIC; } else if (strcmp(type, "posix-extended") == 0) { - state->regex_type = BFS_REGEX_POSIX_EXTENDED; -#if BFS_USE_ONIGURUMA + parser->regex_type = BFS_REGEX_POSIX_EXTENDED; +#if BFS_WITH_ONIGURUMA + } else if (strcmp(type, "awk") == 0 + || strcmp(type, "posix-awk") == 0) { + parser->regex_type = BFS_REGEX_AWK; + } else if (strcmp(type, "gnu-awk") == 0) { + parser->regex_type = BFS_REGEX_GNU_AWK; } else if (strcmp(type, "emacs") == 0) { - state->regex_type = BFS_REGEX_EMACS; + parser->regex_type = BFS_REGEX_EMACS; } else if (strcmp(type, "grep") == 0) { - state->regex_type = BFS_REGEX_GREP; + parser->regex_type = BFS_REGEX_GREP; + } else if (strcmp(type, "egrep") == 0 + || strcmp(type, "posix-egrep") == 0) { + parser->regex_type = BFS_REGEX_EGREP; + } else if (strcmp(type, "findutils-default") == 0) { + parser->regex_type = BFS_REGEX_GNU_FIND; #endif } else if (strcmp(type, "help") == 0) { - state->just_info = true; + parser->just_info = true; cfile = ctx->cout; goto list_types; } else { - parse_expr_error(state, expr, "Unsupported regex type.\n\n"); + parse_expr_error(parser, expr, "Unsupported regex type.\n\n"); goto list_types; } @@ -2332,39 +2302,45 @@ static struct bfs_expr *parse_regextype(struct parser_state *state, int arg1, in list_types: cfprintf(cfile, "Supported types are:\n\n"); - cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n"); - cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n"); - cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n"); -#if BFS_USE_ONIGURUMA - cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n"); - cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n"); -#endif - cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n"); + cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n"); + cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n"); + cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n\n"); - bfs_expr_free(expr); + cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n\n"); + +#if BFS_WITH_ONIGURUMA + cfprintf(cfile, " [${bld}posix-${rs}]${bld}awk${rs}: Like ${grn}awk${rs}\n"); + cfprintf(cfile, " ${bld}gnu-awk${rs}: Like GNU ${grn}awk${rs}\n\n"); + + cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n\n"); + + cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n"); + cfprintf(cfile, " [${bld}posix-${rs}]${bld}egrep${rs}: Like ${grn}grep${rs} ${cyn}-E${rs}\n\n"); + + cfprintf(cfile, " ${bld}findutils-default${rs}: Like GNU ${grn}find${rs}\n"); +#endif return NULL; } /** * Parse -s. */ -static struct bfs_expr *parse_s(struct parser_state *state, int arg1, int arg2) { - state->ctx->flags |= BFTW_SORT; - return parse_nullary_flag(state); +static struct bfs_expr *parse_s(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->flags |= BFTW_SORT; + return parse_nullary_flag(parser); } /** * Parse -samefile FILE. */ -static struct bfs_expr *parse_samefile(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_samefile); +static struct bfs_expr *parse_samefile(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_samefile); if (!expr) { return NULL; } struct bfs_stat sb; - if (stat_arg(state, &expr->argv[1], &sb) != 0) { - bfs_expr_free(expr); + if (stat_arg(parser, &expr->argv[1], &sb) != 0) { return NULL; } @@ -2376,17 +2352,17 @@ static struct bfs_expr *parse_samefile(struct parser_state *state, int arg1, int /** * Parse -S STRATEGY. */ -static struct bfs_expr *parse_search_strategy(struct parser_state *state, int arg1, int arg2) { - struct bfs_ctx *ctx = state->ctx; +static struct bfs_expr *parse_search_strategy(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_ctx *ctx = parser->ctx; CFILE *cfile = ctx->cerr; - struct bfs_expr *expr = parse_unary_flag(state); + const char *arg; + struct bfs_expr *expr = parse_prefix_flag(parser, 'S', true, &arg); if (!expr) { cfprintf(cfile, "\n"); goto list_strategies; } - const char *arg = expr->argv[1]; if (strcmp(arg, "bfs") == 0) { ctx->strategy = BFTW_BFS; } else if (strcmp(arg, "dfs") == 0) { @@ -2396,11 +2372,11 @@ static struct bfs_expr *parse_search_strategy(struct parser_state *state, int ar } else if (strcmp(arg, "eds") == 0) { ctx->strategy = BFTW_EDS; } else if (strcmp(arg, "help") == 0) { - state->just_info = true; + parser->just_info = true; cfile = ctx->cout; goto list_strategies; } else { - parse_expr_error(state, expr, "Unrecognized search strategy.\n\n"); + parse_expr_error(parser, expr, "Unrecognized search strategy.\n\n"); goto list_strategies; } @@ -2412,44 +2388,38 @@ list_strategies: cfprintf(cfile, " ${bld}dfs${rs}: depth-first search\n"); cfprintf(cfile, " ${bld}ids${rs}: iterative deepening search\n"); cfprintf(cfile, " ${bld}eds${rs}: exponential deepening search\n"); - - bfs_expr_free(expr); return NULL; } /** * Parse -[aBcm]?since. */ -static struct bfs_expr *parse_since(struct parser_state *state, int field, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_newer); +static struct bfs_expr *parse_since(struct bfs_parser *parser, int field, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_newer); if (!expr) { return NULL; } - if (parse_reftime(state, expr) != 0) { - goto fail; + if (parse_reftime(parser, expr) != 0) { + return NULL; } expr->stat_field = field; return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -size N[cwbkMGTP]?. */ -static struct bfs_expr *parse_size(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_size); +static struct bfs_expr *parse_size(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_size); if (!expr) { return NULL; } - const char *unit = parse_icmp(state, expr, IF_PARTIAL_OK); + const char *unit = parse_icmp(parser, expr, IF_PARTIAL_OK); if (!unit) { - goto fail; + return NULL; } if (strlen(unit) > 1) { @@ -2484,39 +2454,37 @@ static struct bfs_expr *parse_size(struct parser_state *state, int arg1, int arg break; default: - goto bad_unit; + bad_unit: + parse_expr_error(parser, expr, "Expected a size unit (one of ${bld}cwbkMGTP${rs}); found ${err}%pq${rs}.\n", unit); + return NULL; } return expr; - -bad_unit: - parse_expr_error(state, expr, "Expected a size unit (one of ${bld}cwbkMGTP${rs}); found ${err}%pq${rs}.\n", unit); -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -sparse. */ -static struct bfs_expr *parse_sparse(struct parser_state *state, int arg1, int arg2) { - return parse_nullary_test(state, eval_sparse); +static struct bfs_expr *parse_sparse(struct bfs_parser *parser, int arg1, int arg2) { + return parse_nullary_test(parser, eval_sparse); } /** * Parse -status. */ -static struct bfs_expr *parse_status(struct parser_state *state, int arg1, int arg2) { - state->ctx->status = true; - return parse_nullary_option(state); +static struct bfs_expr *parse_status(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->status = true; + return parse_nullary_option(parser); } /** * Parse -x?type [bcdpflsD]. */ -static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) { +static struct bfs_expr *parse_type(struct bfs_parser *parser, int x, int arg2) { + struct bfs_ctx *ctx = parser->ctx; + bfs_eval_fn *eval = x ? eval_xtype : eval_type; - struct bfs_expr *expr = parse_unary_test(state, eval); + struct bfs_expr *expr = parse_unary_test(parser, eval); if (!expr) { return NULL; } @@ -2552,15 +2520,16 @@ static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) break; case 'w': expr->num |= 1 << BFS_WHT; + ctx->flags |= BFTW_WHITEOUTS; break; case '\0': - parse_expr_error(state, expr, "Expected a type flag.\n"); - goto fail; + parse_expr_error(parser, expr, "Expected a type flag.\n"); + return NULL; default: - parse_expr_error(state, expr, "Unknown type flag ${err}%c${rs}; expected one of [${bld}bcdpflsD${rs}].\n", *c); - goto fail; + parse_expr_error(parser, expr, "Unknown type flag ${err}%c${rs}; expected one of [${bld}bcdpflsD${rs}].\n", *c); + return NULL; } ++c; @@ -2570,34 +2539,30 @@ static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) ++c; continue; } else { - parse_expr_error(state, expr, "Types must be comma-separated.\n"); - goto fail; + parse_expr_error(parser, expr, "Types must be comma-separated.\n"); + return NULL; } } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** * Parse -(no)?warn. */ -static struct bfs_expr *parse_warn(struct parser_state *state, int warn, int arg2) { - state->ctx->warn = warn; - return parse_nullary_option(state); +static struct bfs_expr *parse_warn(struct bfs_parser *parser, int warn, int arg2) { + parser->ctx->warn = warn; + return parse_nullary_option(parser); } /** * Parse -xattr. */ -static struct bfs_expr *parse_xattr(struct parser_state *state, int arg1, int arg2) { +static struct bfs_expr *parse_xattr(struct bfs_parser *parser, int arg1, int arg2) { #if BFS_CAN_CHECK_XATTRS - return parse_nullary_test(state, eval_xattr); + return parse_nullary_test(parser, eval_xattr); #else - parse_error(state, "Missing platform support.\n"); + parse_error(parser, "Missing platform support.\n"); return NULL; #endif } @@ -2605,11 +2570,11 @@ static struct bfs_expr *parse_xattr(struct parser_state *state, int arg1, int ar /** * Parse -xattrname. */ -static struct bfs_expr *parse_xattrname(struct parser_state *state, int arg1, int arg2) { +static struct bfs_expr *parse_xattrname(struct bfs_parser *parser, int arg1, int arg2) { #if BFS_CAN_CHECK_XATTRS - return parse_unary_test(state, eval_xattrname); + return parse_unary_test(parser, eval_xattrname); #else - parse_error(state, "Missing platform support.\n"); + parse_error(parser, "Missing platform support.\n"); return NULL; #endif } @@ -2617,10 +2582,15 @@ static struct bfs_expr *parse_xattrname(struct parser_state *state, int arg1, in /** * Parse -xdev. */ -static struct bfs_expr *parse_xdev(struct parser_state *state, int arg1, int arg2) { - state->ctx->flags |= BFTW_PRUNE_MOUNTS; - state->xdev_arg = state->argv; - return parse_nullary_option(state); +static struct bfs_expr *parse_xdev(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_option(parser); + if (!expr) { + return NULL; + } + + parser->ctx->flags |= BFTW_PRUNE_MOUNTS; + parser->xdev_expr = expr; + return expr; } /** @@ -2721,21 +2691,21 @@ fail: /** * "Parse" -help. */ -static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg2) { - CFILE *cout = state->ctx->cout; +static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2) { + CFILE *cout = parser->ctx->cout; pid_t pager = -1; - if (state->stdout_tty) { + if (parser->stdout_tty) { cout = launch_pager(&pager, cout); } cfprintf(cout, "Usage: ${ex}%s${rs} [${cyn}flags${rs}...] [${mag}paths${rs}...] [${blu}expression${rs}...]\n\n", - state->command); + parser->command); cfprintf(cout, "${ex}%s${rs} is compatible with ${ex}find${rs}, with some extensions. " - "${cyn}Flags${rs} (${cyn}-H${rs}/${cyn}-L${rs}/${cyn}-P${rs} etc.), ${mag}paths${rs},\n" - "and ${blu}expressions${rs} may be freely mixed in any order.\n\n", - BFS_COMMAND); + "${cyn}Flags${rs} (${cyn}-H${rs}/${cyn}-L${rs}/${cyn}-P${rs} etc.), ${mag}paths${rs},\n" + "and ${blu}expressions${rs} may be freely mixed in any order.\n\n", + BFS_COMMAND); cfprintf(cout, "${bld}Flags:${rs}\n\n"); @@ -2807,14 +2777,15 @@ static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg cfprintf(cout, " ${blu}-ignore_readdir_race${rs}\n"); cfprintf(cout, " ${blu}-noignore_readdir_race${rs}\n"); cfprintf(cout, " Whether to report an error if ${ex}%s${rs} detects that the file tree is modified\n", - BFS_COMMAND); + BFS_COMMAND); cfprintf(cout, " during the search (default: ${blu}-noignore_readdir_race${rs})\n"); cfprintf(cout, " ${blu}-maxdepth${rs} ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-mindepth${rs} ${bld}N${rs}\n"); cfprintf(cout, " Ignore files deeper/shallower than ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-mount${rs}\n"); - cfprintf(cout, " Don't descend into other mount points (same as ${blu}-xdev${rs} for now, but will\n"); - cfprintf(cout, " skip mount points entirely in the future)\n"); + cfprintf(cout, " Exclude mount points entirely from the results\n"); + cfprintf(cout, " ${blu}-noerror${rs}\n"); + cfprintf(cout, " Ignore any errors that occur during traversal\n"); cfprintf(cout, " ${blu}-nohidden${rs}\n"); cfprintf(cout, " Exclude hidden files\n"); cfprintf(cout, " ${blu}-noleaf${rs}\n"); @@ -2850,6 +2821,10 @@ static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg cfprintf(cout, " ${blu}-capable${rs}\n"); cfprintf(cout, " Find files with POSIX.1e capabilities set\n"); #endif +#if BFS_CAN_CHECK_CONTEXT + cfprintf(cout, " ${blu}-context${rs} ${bld}GLOB${rs}\n"); + cfprintf(cout, " Find files with SELinux context matching a glob pattern\n"); +#endif cfprintf(cout, " ${blu}-depth${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with depth ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-empty${rs}\n"); @@ -2952,6 +2927,8 @@ static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg cfprintf(cout, " ${blu}-fprintf${rs} ${bld}FILE${rs} ${bld}FORMAT${rs}\n"); cfprintf(cout, " Like ${blu}-ls${rs}/${blu}-print${rs}/${blu}-print0${rs}/${blu}-printf${rs}, but write to ${bld}FILE${rs} instead of standard\n" " output\n"); + cfprintf(cout, " ${blu}-limit${rs} ${bld}N${rs}\n"); + cfprintf(cout, " Quit after this action is evaluated ${bld}N${rs} times\n"); cfprintf(cout, " ${blu}-ls${rs}\n"); cfprintf(cout, " List files like ${ex}ls${rs} ${bld}-dils${rs}\n"); cfprintf(cout, " ${blu}-print${rs}\n"); @@ -2978,161 +2955,207 @@ static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg if (pager > 0) { cfclose(cout); - waitpid(pager, NULL, 0); + xwaitpid(pager, NULL, 0); } - state->just_info = true; + parser->just_info = true; return NULL; } +/** Print the bfs "logo". */ +static void print_logo(CFILE *cout) { + if (!cout->colors) { + goto boring; + } + + size_t vwidth = xstrwidth(bfs_version); + dchar *spaces = dstrepeat(" ", vwidth); + dchar *lines = dstrepeat("─", vwidth); + if (!spaces || !lines) { + dstrfree(lines); + dstrfree(spaces); + goto boring; + } + + // We do ----\r<emoji> rather than <emoji>--- so we don't have to assume + // anything about the width of the emoji + cfprintf(cout, "╭─────%s╮\r📂\n", lines); + cfprintf(cout, "├${ex}b${rs} %s │\n", spaces); + cfprintf(cout, "╰├${ex}f${rs} ${bld}%s${rs} │\n", bfs_version); + cfprintf(cout, " ╰├${ex}s${rs} %s │\n", spaces); + cfprintf(cout, " ╰──%s─╯\n\n", lines); + + dstrfree(lines); + dstrfree(spaces); + return; + +boring: + printf("%s %s\n\n", BFS_COMMAND, bfs_version); +} + /** * "Parse" -version. */ -static struct bfs_expr *parse_version(struct parser_state *state, int arg1, int arg2) { - cfprintf(state->ctx->cout, "${ex}%s${rs} ${bld}%s${rs}\n\n", BFS_COMMAND, BFS_VERSION); +static struct bfs_expr *parse_version(struct bfs_parser *parser, int arg1, int arg2) { + print_logo(parser->ctx->cout); - printf("%s\n", BFS_HOMEPAGE); + printf("Copyright © Tavian Barnes and the bfs contributors\n"); + printf("No rights reserved (https://opensource.org/license/0BSD)\n\n"); - state->just_info = true; + printf("CONFFLAGS := %s\n", bfs_confflags); + printf("CC := %s\n", bfs_cc); + printf("CPPFLAGS := %s\n", bfs_cppflags); + printf("CFLAGS := %s\n", bfs_cflags); + printf("LDFLAGS := %s\n", bfs_ldflags); + printf("LDLIBS := %s\n", bfs_ldlibs); + + printf("\n%s\n", BFS_HOMEPAGE); + + parser->just_info = true; return NULL; } -typedef struct bfs_expr *parse_fn(struct parser_state *state, int arg1, int arg2); +/** Parser callback function type. */ +typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2); /** * An entry in the parse table for primary expressions. */ struct table_entry { char *arg; - enum token_type type; + enum bfs_kind kind; parse_fn *parse; int arg1; int arg2; bool prefix; + bool needs_arg; }; /** * The parse table for primary expressions. */ static const struct table_entry parse_table[] = { - {"--", T_FLAG}, - {"--help", T_ACTION, parse_help}, - {"--version", T_ACTION, parse_version}, - {"-Bmin", T_TEST, parse_min, BFS_STAT_BTIME}, - {"-Bnewer", T_TEST, parse_newer, BFS_STAT_BTIME}, - {"-Bsince", T_TEST, parse_since, BFS_STAT_BTIME}, - {"-Btime", T_TEST, parse_time, BFS_STAT_BTIME}, - {"-D", T_FLAG, parse_debug}, - {"-E", T_FLAG, parse_regex_extended}, - {"-H", T_FLAG, parse_follow, BFTW_FOLLOW_ROOTS, false}, - {"-L", T_FLAG, parse_follow, BFTW_FOLLOW_ALL, false}, - {"-O", T_FLAG, parse_optlevel, 0, 0, true}, - {"-P", T_FLAG, parse_follow, 0, false}, - {"-S", T_FLAG, parse_search_strategy}, - {"-X", T_FLAG, parse_xargs_safe}, - {"-a", T_OPERATOR}, - {"-acl", T_TEST, parse_acl}, - {"-amin", T_TEST, parse_min, BFS_STAT_ATIME}, - {"-and", T_OPERATOR}, - {"-anewer", T_TEST, parse_newer, BFS_STAT_ATIME}, - {"-asince", T_TEST, parse_since, BFS_STAT_ATIME}, - {"-atime", T_TEST, parse_time, BFS_STAT_ATIME}, - {"-capable", T_TEST, parse_capable}, - {"-cmin", T_TEST, parse_min, BFS_STAT_CTIME}, - {"-cnewer", T_TEST, parse_newer, BFS_STAT_CTIME}, - {"-color", T_OPTION, parse_color, true}, - {"-csince", T_TEST, parse_since, BFS_STAT_CTIME}, - {"-ctime", T_TEST, parse_time, BFS_STAT_CTIME}, - {"-d", T_FLAG, parse_depth}, - {"-daystart", T_OPTION, parse_daystart}, - {"-delete", T_ACTION, parse_delete}, - {"-depth", T_OPTION, parse_depth_n}, - {"-empty", T_TEST, parse_empty}, - {"-exclude", T_OPERATOR}, - {"-exec", T_ACTION, parse_exec, 0}, - {"-execdir", T_ACTION, parse_exec, BFS_EXEC_CHDIR}, - {"-executable", T_TEST, parse_access, X_OK}, - {"-exit", T_ACTION, parse_exit}, - {"-f", T_FLAG, parse_f}, - {"-false", T_TEST, parse_const, false}, - {"-files0-from", T_OPTION, parse_files0_from}, - {"-flags", T_TEST, parse_flags}, - {"-fls", T_ACTION, parse_fls}, - {"-follow", T_OPTION, parse_follow, BFTW_FOLLOW_ALL, true}, - {"-fprint", T_ACTION, parse_fprint}, - {"-fprint0", T_ACTION, parse_fprint0}, - {"-fprintf", T_ACTION, parse_fprintf}, - {"-fstype", T_TEST, parse_fstype}, - {"-gid", T_TEST, parse_group}, - {"-group", T_TEST, parse_group}, - {"-help", T_ACTION, parse_help}, - {"-hidden", T_TEST, parse_hidden}, - {"-ignore_readdir_race", T_OPTION, parse_ignore_races, true}, - {"-ilname", T_TEST, parse_lname, true}, - {"-iname", T_TEST, parse_name, true}, - {"-inum", T_TEST, parse_inum}, - {"-ipath", T_TEST, parse_path, true}, - {"-iregex", T_TEST, parse_regex, BFS_REGEX_ICASE}, - {"-iwholename", T_TEST, parse_path, true}, - {"-j", T_FLAG, parse_jobs, 0, 0, true}, - {"-links", T_TEST, parse_links}, - {"-lname", T_TEST, parse_lname, false}, - {"-ls", T_ACTION, parse_ls}, - {"-maxdepth", T_OPTION, parse_depth_limit, false}, - {"-mindepth", T_OPTION, parse_depth_limit, true}, - {"-mmin", T_TEST, parse_min, BFS_STAT_MTIME}, - {"-mnewer", T_TEST, parse_newer, BFS_STAT_MTIME}, - {"-mount", T_OPTION, parse_mount}, - {"-msince", T_TEST, parse_since, BFS_STAT_MTIME}, - {"-mtime", T_TEST, parse_time, BFS_STAT_MTIME}, - {"-name", T_TEST, parse_name, false}, - {"-newer", T_TEST, parse_newer, BFS_STAT_MTIME}, - {"-newer", T_TEST, parse_newerxy, 0, 0, true}, - {"-nocolor", T_OPTION, parse_color, false}, - {"-nogroup", T_TEST, parse_nogroup}, - {"-nohidden", T_TEST, parse_nohidden}, - {"-noignore_readdir_race", T_OPTION, parse_ignore_races, false}, - {"-noleaf", T_OPTION, parse_noleaf}, - {"-not", T_OPERATOR}, - {"-nouser", T_TEST, parse_nouser}, - {"-nowarn", T_OPTION, parse_warn, false}, - {"-o", T_OPERATOR}, - {"-ok", T_ACTION, parse_exec, BFS_EXEC_CONFIRM}, - {"-okdir", T_ACTION, parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR}, - {"-or", T_OPERATOR}, - {"-path", T_TEST, parse_path, false}, - {"-perm", T_TEST, parse_perm}, - {"-print", T_ACTION, parse_print}, - {"-print0", T_ACTION, parse_print0}, - {"-printf", T_ACTION, parse_printf}, - {"-printx", T_ACTION, parse_printx}, - {"-prune", T_ACTION, parse_prune}, - {"-quit", T_ACTION, parse_quit}, - {"-readable", T_TEST, parse_access, R_OK}, - {"-regex", T_TEST, parse_regex, 0}, - {"-regextype", T_OPTION, parse_regextype}, - {"-rm", T_ACTION, parse_delete}, - {"-s", T_FLAG, parse_s}, - {"-samefile", T_TEST, parse_samefile}, - {"-since", T_TEST, parse_since, BFS_STAT_MTIME}, - {"-size", T_TEST, parse_size}, - {"-sparse", T_TEST, parse_sparse}, - {"-status", T_OPTION, parse_status}, - {"-true", T_TEST, parse_const, true}, - {"-type", T_TEST, parse_type, false}, - {"-uid", T_TEST, parse_user}, - {"-unique", T_OPTION, parse_unique}, - {"-used", T_TEST, parse_used}, - {"-user", T_TEST, parse_user}, - {"-version", T_ACTION, parse_version}, - {"-warn", T_OPTION, parse_warn, true}, - {"-wholename", T_TEST, parse_path, false}, - {"-writable", T_TEST, parse_access, W_OK}, - {"-x", T_FLAG, parse_xdev}, - {"-xattr", T_TEST, parse_xattr}, - {"-xattrname", T_TEST, parse_xattrname}, - {"-xdev", T_OPTION, parse_xdev}, - {"-xtype", T_TEST, parse_type, true}, + {"--", BFS_FLAG}, + {"--help", BFS_ACTION, parse_help}, + {"--version", BFS_ACTION, parse_version}, + {"-Bmin", BFS_TEST, parse_min, BFS_STAT_BTIME}, + {"-Bnewer", BFS_TEST, parse_newer, BFS_STAT_BTIME}, + {"-Bsince", BFS_TEST, parse_since, BFS_STAT_BTIME}, + {"-Btime", BFS_TEST, parse_time, BFS_STAT_BTIME}, + {"-D", BFS_FLAG, parse_debug, .prefix = true}, + {"-E", BFS_FLAG, parse_regex_extended}, + {"-H", BFS_FLAG, parse_follow, BFTW_FOLLOW_ROOTS, false}, + {"-L", BFS_FLAG, parse_follow, BFTW_FOLLOW_ALL, false}, + {"-O", BFS_FLAG, parse_optlevel, .prefix = true}, + {"-P", BFS_FLAG, parse_follow, 0, false}, + {"-S", BFS_FLAG, parse_search_strategy, .prefix = true}, + {"-X", BFS_FLAG, parse_xargs_safe}, + {"-a", BFS_OPERATOR}, + {"-acl", BFS_TEST, parse_acl}, + {"-amin", BFS_TEST, parse_min, BFS_STAT_ATIME}, + {"-and", BFS_OPERATOR}, + {"-anewer", BFS_TEST, parse_newer, BFS_STAT_ATIME}, + {"-asince", BFS_TEST, parse_since, BFS_STAT_ATIME}, + {"-atime", BFS_TEST, parse_time, BFS_STAT_ATIME}, + {"-capable", BFS_TEST, parse_capable}, + {"-cmin", BFS_TEST, parse_min, BFS_STAT_CTIME}, + {"-cnewer", BFS_TEST, parse_newer, BFS_STAT_CTIME}, + {"-color", BFS_OPTION, parse_color, true}, + {"-context", BFS_TEST, parse_context, true}, + {"-csince", BFS_TEST, parse_since, BFS_STAT_CTIME}, + {"-ctime", BFS_TEST, parse_time, BFS_STAT_CTIME}, + {"-d", BFS_FLAG, parse_depth, true}, + {"-daystart", BFS_OPTION, parse_daystart}, + {"-delete", BFS_ACTION, parse_delete}, + {"-depth", BFS_OPTION, parse_depth_n, false}, + {"-empty", BFS_TEST, parse_empty}, + {"-exclude", BFS_OPERATOR}, + {"-exec", BFS_ACTION, parse_exec, 0}, + {"-execdir", BFS_ACTION, parse_exec, BFS_EXEC_CHDIR}, + {"-executable", BFS_TEST, parse_access, X_OK}, + {"-exit", BFS_ACTION, parse_exit}, + {"-f", BFS_FLAG, parse_f, .needs_arg = true}, + {"-false", BFS_TEST, parse_const, false}, + {"-files0-from", BFS_OPTION, parse_files0_from}, + {"-flags", BFS_TEST, parse_flags}, + {"-fls", BFS_ACTION, parse_fls}, + {"-follow", BFS_OPTION, parse_follow, BFTW_FOLLOW_ALL, true}, + {"-fprint", BFS_ACTION, parse_fprint}, + {"-fprint0", BFS_ACTION, parse_fprint0}, + {"-fprintf", BFS_ACTION, parse_fprintf}, + {"-fstype", BFS_TEST, parse_fstype}, + {"-gid", BFS_TEST, parse_group}, + {"-group", BFS_TEST, parse_group}, + {"-help", BFS_ACTION, parse_help}, + {"-hidden", BFS_TEST, parse_hidden}, + {"-ignore_readdir_race", BFS_OPTION, parse_ignore_races, true}, + {"-ilname", BFS_TEST, parse_lname, true}, + {"-iname", BFS_TEST, parse_name, true}, + {"-inum", BFS_TEST, parse_inum}, + {"-ipath", BFS_TEST, parse_path, true}, + {"-iregex", BFS_TEST, parse_regex, BFS_REGEX_ICASE}, + {"-iwholename", BFS_TEST, parse_path, true}, + {"-j", BFS_FLAG, parse_jobs, .prefix = true}, + {"-limit", BFS_ACTION, parse_limit}, + {"-links", BFS_TEST, parse_links}, + {"-lname", BFS_TEST, parse_lname, false}, + {"-ls", BFS_ACTION, parse_ls}, + {"-maxdepth", BFS_OPTION, parse_depth_limit, false}, + {"-mindepth", BFS_OPTION, parse_depth_limit, true}, + {"-mmin", BFS_TEST, parse_min, BFS_STAT_MTIME}, + {"-mnewer", BFS_TEST, parse_newer, BFS_STAT_MTIME}, + {"-mount", BFS_OPTION, parse_mount}, + {"-msince", BFS_TEST, parse_since, BFS_STAT_MTIME}, + {"-mtime", BFS_TEST, parse_time, BFS_STAT_MTIME}, + {"-name", BFS_TEST, parse_name, false}, + {"-newer", BFS_TEST, parse_newer, BFS_STAT_MTIME}, + {"-newer", BFS_TEST, parse_newerxy, .prefix = true}, + {"-nocolor", BFS_OPTION, parse_color, false}, + {"-noerror", BFS_OPTION, parse_noerror}, + {"-nogroup", BFS_TEST, parse_nogroup}, + {"-nohidden", BFS_TEST, parse_nohidden}, + {"-noignore_readdir_race", BFS_OPTION, parse_ignore_races, false}, + {"-noleaf", BFS_OPTION, parse_noleaf}, + {"-not", BFS_OPERATOR}, + {"-nouser", BFS_TEST, parse_nouser}, + {"-nowarn", BFS_OPTION, parse_warn, false}, + {"-o", BFS_OPERATOR}, + {"-ok", BFS_ACTION, parse_exec, BFS_EXEC_CONFIRM}, + {"-okdir", BFS_ACTION, parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR}, + {"-or", BFS_OPERATOR}, + {"-path", BFS_TEST, parse_path, false}, + {"-perm", BFS_TEST, parse_perm}, + {"-print", BFS_ACTION, parse_print}, + {"-print0", BFS_ACTION, parse_print0}, + {"-printf", BFS_ACTION, parse_printf}, + {"-printx", BFS_ACTION, parse_printx}, + {"-prune", BFS_ACTION, parse_prune}, + {"-quit", BFS_ACTION, parse_quit}, + {"-readable", BFS_TEST, parse_access, R_OK}, + {"-regex", BFS_TEST, parse_regex, 0}, + {"-regextype", BFS_OPTION, parse_regextype}, + {"-rm", BFS_ACTION, parse_delete}, + {"-s", BFS_FLAG, parse_s}, + {"-samefile", BFS_TEST, parse_samefile}, + {"-since", BFS_TEST, parse_since, BFS_STAT_MTIME}, + {"-size", BFS_TEST, parse_size}, + {"-sparse", BFS_TEST, parse_sparse}, + {"-status", BFS_OPTION, parse_status}, + {"-true", BFS_TEST, parse_const, true}, + {"-type", BFS_TEST, parse_type, false}, + {"-uid", BFS_TEST, parse_user}, + {"-unique", BFS_OPTION, parse_unique}, + {"-used", BFS_TEST, parse_used}, + {"-user", BFS_TEST, parse_user}, + {"-version", BFS_ACTION, parse_version}, + {"-warn", BFS_OPTION, parse_warn, true}, + {"-wholename", BFS_TEST, parse_path, false}, + {"-writable", BFS_TEST, parse_access, W_OK}, + {"-x", BFS_FLAG, parse_xdev}, + {"-xattr", BFS_TEST, parse_xattr}, + {"-xattrname", BFS_TEST, parse_xattrname}, + {"-xdev", BFS_OPTION, parse_xdev}, + {"-xtype", BFS_TEST, parse_type, true}, {0}, }; @@ -3153,6 +3176,83 @@ static const struct table_entry *table_lookup(const char *arg) { return NULL; } +/** Look up a single-character flag in the parse table. */ +static const struct table_entry *flag_lookup(char flag) { + for (const struct table_entry *entry = parse_table; entry->arg; ++entry) { + enum bfs_kind kind = entry->kind; + if (kind == BFS_FLAG && entry->arg[1] == flag && !entry->arg[2]) { + return entry; + } + } + + return NULL; +} + +/** Check for a multi-flag argument like -LEXO2. */ +static bool is_flag_group(const char *arg) { + // We enforce that at least one flag in a flag group must be a capital + // letter, to avoid ambiguity with primary expressions + bool has_upper = false; + + // Flags that take an argument must appear last + bool needs_arg = false; + + for (size_t i = 1; arg[i]; ++i) { + char c = arg[i]; + if (c >= 'A' && c <= 'Z') { + has_upper = true; + } + + if (needs_arg) { + return false; + } + + const struct table_entry *entry = flag_lookup(c); + if (!entry || !entry->parse) { + return false; + } + + if (entry->prefix) { + // The rest is the flag's argument + break; + } + + needs_arg |= entry->needs_arg; + } + + return has_upper; +} + +/** Parse a multi-flag argument. */ +static struct bfs_expr *parse_flag_group(struct bfs_parser *parser) { + struct bfs_expr *expr = NULL; + + char **start = parser->argv; + char **end = start; + const char *arg = start[0]; + + for (size_t i = 1; arg[i]; ++i) { + parser->argv = start; + + const struct table_entry *entry = flag_lookup(arg[i]); + expr = entry->parse(parser, entry->arg1, entry->arg2); + + if (parser->argv > end) { + end = parser->argv; + } + + if (!expr || entry->prefix) { + break; + } + } + + if (expr) { + bfs_assert(parser->argv == end, "Didn't eat enough tokens"); + } + + return expr; +} + /** Search for a fuzzy match in the parse table. */ static const struct table_entry *table_lookup_fuzzy(const char *arg) { const struct table_entry *best = NULL; @@ -3174,9 +3274,11 @@ static const struct table_entry *table_lookup_fuzzy(const char *arg) { * | TEST * | ACTION */ -static struct bfs_expr *parse_primary(struct parser_state *state) { +static struct bfs_expr *parse_primary(struct bfs_parser *parser) { + struct bfs_ctx *ctx = parser->ctx; + // Paths are already skipped at this point - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (arg[0] != '-') { goto unexpected; @@ -3191,15 +3293,19 @@ static struct bfs_expr *parse_primary(struct parser_state *state) { } } + if (is_flag_group(arg)) { + return parse_flag_group(parser); + } + match = table_lookup_fuzzy(arg); - CFILE *cerr = state->ctx->cerr; - parse_error(state, "Unknown argument; did you mean "); - switch (match->type) { - case T_FLAG: + CFILE *cerr = ctx->cerr; + parse_error(parser, "Unknown argument; did you mean "); + switch (match->kind) { + case BFS_FLAG: cfprintf(cerr, "${cyn}%s${rs}?", match->arg); break; - case T_OPERATOR: + case BFS_OPERATOR: cfprintf(cerr, "${red}%s${rs}?", match->arg); break; default: @@ -3207,7 +3313,7 @@ static struct bfs_expr *parse_primary(struct parser_state *state) { break; } - if (!state->interactive || !match->parse) { + if (!ctx->interactive || !match->parse) { fprintf(stderr, "\n"); goto unmatched; } @@ -3218,16 +3324,16 @@ static struct bfs_expr *parse_primary(struct parser_state *state) { } fprintf(stderr, "\n"); - state->argv[0] = match->arg; + parser->argv[0] = match->arg; matched: - return match->parse(state, match->arg1, match->arg2); + return match->parse(parser, match->arg1, match->arg2); unmatched: return NULL; unexpected: - parse_error(state, "Expected a predicate.\n"); + parse_error(parser, "Expected a predicate.\n"); return NULL; } @@ -3237,71 +3343,66 @@ unexpected: * | "-exclude" FACTOR * | PRIMARY */ -static struct bfs_expr *parse_factor(struct parser_state *state) { - if (skip_paths(state) != 0) { +static struct bfs_expr *parse_factor(struct bfs_parser *parser) { + if (skip_paths(parser) != 0) { return NULL; } - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (!arg) { - parse_argv_error(state, state->last_arg, 1, "Expression terminated prematurely here.\n"); + parse_argv_error(parser, parser->last_arg, 1, "Expression terminated prematurely here.\n"); return NULL; } if (strcmp(arg, "(") == 0) { - parser_advance(state, T_OPERATOR, 1); + parser_advance(parser, BFS_OPERATOR, 1); - struct bfs_expr *expr = parse_expr(state); + struct bfs_expr *expr = parse_expr(parser); if (!expr) { return NULL; } - if (skip_paths(state) != 0) { - bfs_expr_free(expr); + if (skip_paths(parser) != 0) { return NULL; } - arg = state->argv[0]; + arg = parser->argv[0]; if (!arg || strcmp(arg, ")") != 0) { - parse_argv_error(state, state->last_arg, 1, "Expected a ${red})${rs}.\n"); - bfs_expr_free(expr); + parse_argv_error(parser, parser->last_arg, 1, "Expected a ${red})${rs}.\n"); return NULL; } - parser_advance(state, T_OPERATOR, 1); + parser_advance(parser, BFS_OPERATOR, 1); return expr; } else if (strcmp(arg, "-exclude") == 0) { - if (state->excluding) { - parse_error(state, "${err}%s${rs} is not supported within ${red}-exclude${rs}.\n", arg); + if (parser->excluding) { + parse_error(parser, "${err}%s${rs} is not supported within ${red}-exclude${rs}.\n", arg); return NULL; } - char **argv = parser_advance(state, T_OPERATOR, 1); - state->excluding = true; + char **argv = parser_advance(parser, BFS_OPERATOR, 1); + parser->excluding = true; - struct bfs_expr *factor = parse_factor(state); + struct bfs_expr *factor = parse_factor(parser); if (!factor) { return NULL; } - state->excluding = false; + parser->excluding = false; - if (parse_exclude(state, factor) != 0) { - return NULL; - } - - return bfs_expr_new(eval_true, state->argv - argv, argv); + bfs_expr_append(parser->ctx->exclude, factor); + return parse_new_expr(parser, eval_true, parser->argv - argv, argv, BFS_OPERATOR); } else if (strcmp(arg, "!") == 0 || strcmp(arg, "-not") == 0) { - char **argv = parser_advance(state, T_OPERATOR, 1); + char **argv = parser_advance(parser, BFS_OPERATOR, 1); - struct bfs_expr *factor = parse_factor(state); + struct bfs_expr *factor = parse_factor(parser); if (!factor) { return NULL; } - return new_unary_expr(eval_not, factor, argv); + return new_unary_expr(parser, eval_not, factor, argv); } else { - return parse_primary(state); + return parse_primary(parser); } } @@ -3311,16 +3412,15 @@ static struct bfs_expr *parse_factor(struct parser_state *state) { * | TERM "-a" FACTOR * | TERM "-and" FACTOR */ -static struct bfs_expr *parse_term(struct parser_state *state) { - struct bfs_expr *term = parse_factor(state); +static struct bfs_expr *parse_term(struct bfs_parser *parser) { + struct bfs_expr *term = parse_factor(parser); while (term) { - if (skip_paths(state) != 0) { - bfs_expr_free(term); + if (skip_paths(parser) != 0) { return NULL; } - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (!arg) { break; } @@ -3333,17 +3433,16 @@ static struct bfs_expr *parse_term(struct parser_state *state) { char **argv = &fake_and_arg; if (strcmp(arg, "-a") == 0 || strcmp(arg, "-and") == 0) { - argv = parser_advance(state, T_OPERATOR, 1); + argv = parser_advance(parser, BFS_OPERATOR, 1); } struct bfs_expr *lhs = term; - struct bfs_expr *rhs = parse_factor(state); + struct bfs_expr *rhs = parse_factor(parser); if (!rhs) { - bfs_expr_free(lhs); return NULL; } - term = new_binary_expr(eval_and, lhs, rhs, argv); + term = new_binary_expr(parser, eval_and, lhs, rhs, argv); } return term; @@ -3354,16 +3453,15 @@ static struct bfs_expr *parse_term(struct parser_state *state) { * | CLAUSE "-o" TERM * | CLAUSE "-or" TERM */ -static struct bfs_expr *parse_clause(struct parser_state *state) { - struct bfs_expr *clause = parse_term(state); +static struct bfs_expr *parse_clause(struct bfs_parser *parser) { + struct bfs_expr *clause = parse_term(parser); while (clause) { - if (skip_paths(state) != 0) { - bfs_expr_free(clause); + if (skip_paths(parser) != 0) { return NULL; } - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (!arg) { break; } @@ -3372,16 +3470,15 @@ static struct bfs_expr *parse_clause(struct parser_state *state) { break; } - char **argv = parser_advance(state, T_OPERATOR, 1); + char **argv = parser_advance(parser, BFS_OPERATOR, 1); struct bfs_expr *lhs = clause; - struct bfs_expr *rhs = parse_term(state); + struct bfs_expr *rhs = parse_term(parser); if (!rhs) { - bfs_expr_free(lhs); return NULL; } - clause = new_binary_expr(eval_or, lhs, rhs, argv); + clause = new_binary_expr(parser, eval_or, lhs, rhs, argv); } return clause; @@ -3391,16 +3488,15 @@ static struct bfs_expr *parse_clause(struct parser_state *state) { * EXPR : CLAUSE * | EXPR "," CLAUSE */ -static struct bfs_expr *parse_expr(struct parser_state *state) { - struct bfs_expr *expr = parse_clause(state); +static struct bfs_expr *parse_expr(struct bfs_parser *parser) { + struct bfs_expr *expr = parse_clause(parser); while (expr) { - if (skip_paths(state) != 0) { - bfs_expr_free(expr); + if (skip_paths(parser) != 0) { return NULL; } - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (!arg) { break; } @@ -3409,90 +3505,165 @@ static struct bfs_expr *parse_expr(struct parser_state *state) { break; } - char **argv = parser_advance(state, T_OPERATOR, 1); + char **argv = parser_advance(parser, BFS_OPERATOR, 1); struct bfs_expr *lhs = expr; - struct bfs_expr *rhs = parse_clause(state); + struct bfs_expr *rhs = parse_clause(parser); if (!rhs) { - bfs_expr_free(lhs); return NULL; } - expr = new_binary_expr(eval_comma, lhs, rhs, argv); + expr = new_binary_expr(parser, eval_comma, lhs, rhs, argv); } return expr; } +/** Handle -files0-from after parsing. */ +static int parse_files0_roots(struct bfs_parser *parser) { + const struct bfs_ctx *ctx = parser->ctx; + const struct bfs_expr *expr = parser->files0_expr; + + if (ctx->npaths > 0) { + bool highlight[ctx->argc]; + init_highlight(ctx, highlight); + highlight_args(ctx, expr->argv, expr->argc, highlight); + + for (size_t i = 0; i < ctx->argc; ++i) { + if (ctx->kinds[i] == BFS_PATH) { + highlight[i] = true; + } + } + + bfs_argv_error(ctx, highlight); + bfs_error(ctx, "Cannot combine %pX with explicit root paths.\n", expr); + return -1; + } + + const char *from = expr->argv[1]; + + FILE *file; + if (strcmp(from, "-") == 0) { + if (!consume_stdin(parser, expr)) { + return -1; + } + file = stdin; + } else { + file = xfopen(from, O_RDONLY | O_CLOEXEC); + } + if (!file) { + parse_expr_error(parser, expr, "%s.\n", errstr()); + return -1; + } + + while (true) { + char *path = xgetdelim(file, '\0'); + if (!path) { + if (errno) { + goto fail; + } else { + break; + } + } + + int ret = parse_root(parser, path); + free(path); + if (ret != 0) { + goto fail; + } + } + + if (file != stdin) { + fclose(file); + } + + return 0; + +fail: + if (file != stdin) { + fclose(file); + } + return -1; +} + /** * Parse the top-level expression. */ -static struct bfs_expr *parse_whole_expr(struct parser_state *state) { - if (skip_paths(state) != 0) { +static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { + struct bfs_ctx *ctx = parser->ctx; + + if (skip_paths(parser) != 0) { return NULL; } struct bfs_expr *expr; - if (state->argv[0]) { - expr = parse_expr(state); + if (parser->argv[0]) { + expr = parse_expr(parser); } else { - expr = bfs_expr_new(eval_true, 1, &fake_true_arg); + expr = parse_new_expr(parser, eval_true, 1, &fake_true_arg, BFS_TEST); } if (!expr) { return NULL; } - if (state->argv[0]) { - parse_error(state, "Unexpected argument.\n"); - goto fail; + if (parser->argv[0]) { + parse_error(parser, "Unexpected argument.\n"); + return NULL; } - if (state->implicit_print) { - struct bfs_expr *print = bfs_expr_new(eval_fprint, 1, &fake_print_arg); + if (parser->files0_expr) { + if (parse_files0_roots(parser) != 0) { + return NULL; + } + } else if (ctx->npaths == 0) { + if (parse_root(parser, ".") != 0) { + return NULL; + } + } + + if (parser->implicit_print) { + const struct bfs_expr *limit = parser->limit_expr; + if (limit) { + parse_expr_error(parser, limit, + "With %pX, you must specify an action explicitly; for example, ${blu}-print${rs} %px.\n", + limit, limit); + return NULL; + } + + struct bfs_expr *print = parse_new_expr(parser, eval_fprint, 1, &fake_print_arg, BFS_ACTION); if (!print) { - goto fail; + return NULL; } - init_print_expr(state, print); + init_print_expr(parser, print); - expr = new_binary_expr(eval_and, expr, print, &fake_and_arg); + expr = new_binary_expr(parser, eval_and, expr, print, &fake_and_arg); if (!expr) { - goto fail; + return NULL; } } - if (state->mount_arg && state->xdev_arg) { - parse_conflict_warning(state, state->mount_arg, 1, state->xdev_arg, 1, - "${blu}%s${rs} is redundant in the presence of ${blu}%s${rs}.\n\n", - state->xdev_arg[0], state->mount_arg[0]); + if (parser->mount_expr && parser->xdev_expr) { + parse_conflict_warning(parser, parser->mount_expr, parser->xdev_expr, + "%px is redundant in the presence of %px.\n\n", + parser->xdev_expr, parser->mount_expr); } - if (state->ctx->warn && state->depth_arg && state->prune_arg) { - parse_conflict_warning(state, state->depth_arg, 1, state->prune_arg, 1, - "${blu}%s${rs} does not work in the presence of ${blu}%s${rs}.\n", - state->prune_arg[0], state->depth_arg[0]); + if (ctx->warn && parser->depth_expr && parser->prune_expr) { + parse_conflict_warning(parser, parser->depth_expr, parser->prune_expr, + "%px does not work in the presence of %px.\n", + parser->prune_expr, parser->depth_expr); - if (state->interactive) { - bfs_warning(state->ctx, "Do you want to continue? "); - if (ynprompt() == 0) { - goto fail; + if (ctx->interactive) { + bfs_warning(ctx, "Do you want to continue? "); + if (ynprompt() <= 0) { + return NULL; } } fprintf(stderr, "\n"); } - if (state->ok_expr && state->files0_stdin_arg) { - parse_conflict_error(state, state->ok_expr->argv, state->ok_expr->argc, state->files0_stdin_arg, 2, - "${blu}%s${rs} conflicts with ${blu}%s${rs} ${bld}%s${rs}.\n", - state->ok_expr->argv[0], state->files0_stdin_arg[0], state->files0_stdin_arg[1]); - goto fail; - } - return expr; - -fail: - bfs_expr_free(expr); - return NULL; } static const char *bftw_strategy_name(enum bftw_strategy strategy) { @@ -3518,19 +3689,29 @@ static void dump_expr_multiline(const struct bfs_ctx *ctx, enum debug_flags flag cfprintf(ctx->cerr, " "); } + bool close = true; + if (bfs_expr_is_parent(expr)) { - cfprintf(ctx->cerr, "(${red}%s${rs}\n", expr->argv[0]); - if (expr->lhs) { - dump_expr_multiline(ctx, flag, expr->lhs, indent + 1, 0); + if (SLIST_EMPTY(&expr->children)) { + cfprintf(ctx->cerr, "(${red}%s${rs}", expr->argv[0]); + ++rparens; + } else { + cfprintf(ctx->cerr, "(${red}%s${rs}\n", expr->argv[0]); + for_expr (child, expr) { + int parens = child->next ? 0 : rparens + 1; + dump_expr_multiline(ctx, flag, child, indent + 1, parens); + } + close = false; } - dump_expr_multiline(ctx, flag, expr->rhs, indent + 1, rparens + 1); } else { if (flag == DEBUG_RATES) { cfprintf(ctx->cerr, "%pE", expr); } else { cfprintf(ctx->cerr, "%pe", expr); } + } + if (close) { for (int i = 0; i < rparens; ++i) { cfprintf(ctx->cerr, ")"); } @@ -3563,14 +3744,12 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { cfprintf(cerr, " ${cyn}-s${rs}"); } + cfprintf(cerr, " ${cyn}-j${bld}%d${rs}", ctx->threads); + if (ctx->optlevel != 3) { cfprintf(cerr, " ${cyn}-O${bld}%d${rs}", ctx->optlevel); } - if (ctx->threads > 0) { - cfprintf(cerr, " ${cyn}-j${bld}%d${rs}", ctx->threads); - } - cfprintf(cerr, " ${cyn}-S${rs} ${bld}%s${rs}", bftw_strategy_name(ctx->strategy)); enum debug_flags debug = ctx->debug; @@ -3589,7 +3768,7 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { } } - for (size_t i = 0; i < darray_length(ctx->paths); ++i) { + for (size_t i = 0; i < ctx->npaths; ++i) { const char *path = ctx->paths[i]; char c = path[0]; if (c == '-' || c == '(' || c == ')' || c == '!' || c == ',') { @@ -3630,10 +3809,8 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { fputs("\n", stderr); - if (ctx->exclude->eval_fn != eval_false) { - bfs_debug(ctx, flag, "(${red}-exclude${rs}\n"); - dump_expr_multiline(ctx, flag, ctx->exclude, 1, 1); - } + bfs_debug(ctx, flag, "(${red}-exclude${rs}\n"); + dump_expr_multiline(ctx, flag, ctx->exclude, 1, 1); dump_expr_multiline(ctx, flag, ctx->expr, 0, 0); } @@ -3644,17 +3821,17 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { static void dump_costs(const struct bfs_ctx *ctx) { const struct bfs_expr *expr = ctx->expr; bfs_debug(ctx, DEBUG_COST, " Cost: ~${ylw}%g${rs}\n", expr->cost); - bfs_debug(ctx, DEBUG_COST, "Probability: ~${ylw}%g%%${rs}\n", 100.0*expr->probability); + bfs_debug(ctx, DEBUG_COST, "Probability: ~${ylw}%g%%${rs}\n", 100.0 * expr->probability); } struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { struct bfs_ctx *ctx = bfs_ctx_new(); if (!ctx) { - perror("bfs_new_ctx()"); + perror("bfs_ctx_new()"); goto fail; } - static char* default_argv[] = {BFS_COMMAND, NULL}; + static char *default_argv[] = {BFS_COMMAND, NULL}; if (argc < 1) { argc = 1; argv = default_argv; @@ -3667,8 +3844,15 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { goto fail; } + ctx->kinds = ZALLOC_ARRAY(enum bfs_kind, argc); + if (!ctx->kinds) { + perror("zalloc()"); + goto fail; + } + enum use_color use_color = COLOR_AUTO; - if (getenv("NO_COLOR")) { + const char *no_color = getenv("NO_COLOR"); + if (no_color && *no_color) { // https://no-color.org/ use_color = COLOR_NEVER; } @@ -3704,55 +3888,50 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { } else { ctx->warn = stdin_tty; } + ctx->interactive = stdin_tty && stderr_tty; - struct parser_state state = { + struct bfs_parser parser = { .ctx = ctx, .argv = ctx->argv + 1, .command = ctx->argv[0], .regex_type = BFS_REGEX_POSIX_BASIC, .stdout_tty = stdout_tty, - .interactive = stdin_tty && stderr_tty, .use_color = use_color, .implicit_print = true, - .implicit_root = true, .just_info = false, .excluding = false, .last_arg = NULL, - .depth_arg = NULL, - .prune_arg = NULL, - .mount_arg = NULL, - .xdev_arg = NULL, - .files0_stdin_arg = NULL, - .ok_expr = NULL, + .depth_expr = NULL, + .prune_expr = NULL, + .mount_expr = NULL, + .xdev_expr = NULL, + .stdin_expr = NULL, .now = ctx->now, }; - ctx->exclude = bfs_expr_new(eval_false, 1, &fake_false_arg); + ctx->exclude = parse_new_expr(&parser, eval_or, 1, &fake_or_arg, BFS_OPERATOR); if (!ctx->exclude) { goto fail; } - ctx->expr = parse_whole_expr(&state); + ctx->expr = parse_whole_expr(&parser); if (!ctx->expr) { - if (state.just_info) { + if (parser.just_info) { goto done; } else { goto fail; } } - if (state.use_color == COLOR_AUTO && !ctx->colors) { - bfs_warning(ctx, "Error parsing $$LS_COLORS: %s.\n\n", strerror(ctx->colors_error)); + if (parser.use_color == COLOR_AUTO && !ctx->colors) { + bfs_warning(ctx, "Error parsing $$LS_COLORS: %s.\n\n", xstrerror(ctx->colors_error)); } if (bfs_optimize(ctx) != 0) { - goto fail; - } - - if (darray_length(ctx->paths) == 0 && state.implicit_root) { - if (parse_root(&state, ".") != 0) { - goto fail; + if (errno != 0) { + bfs_perror(ctx, "bfs_optimize()"); } + goto fail; } if ((ctx->flags & BFTW_FOLLOW_ALL) && !ctx->unique) { |