diff options
Diffstat (limited to 'parse.c')
-rw-r--r-- | parse.c | 3462 |
1 files changed, 0 insertions, 3462 deletions
diff --git a/parse.c b/parse.c deleted file mode 100644 index fbc987c..0000000 --- a/parse.c +++ /dev/null @@ -1,3462 +0,0 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2019 Tavian Barnes <tavianator@tavianator.com> * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ - -/** - * The command line parser. Expressions are parsed by recursive descent, with a - * grammar described in the comments of the parse_*() functions. The parser - * also accepts flags and paths at any point in the expression, by treating - * flags like always-true options, and skipping over paths wherever they appear. - */ - -#include "bfs.h" -#include "cmdline.h" -#include "diag.h" -#include "dstring.h" -#include "eval.h" -#include "exec.h" -#include "expr.h" -#include "fsade.h" -#include "mtab.h" -#include "printf.h" -#include "spawn.h" -#include "stat.h" -#include "typo.h" -#include "util.h" -#include <assert.h> -#include <ctype.h> -#include <errno.h> -#include <fcntl.h> -#include <fnmatch.h> -#include <grp.h> -#include <limits.h> -#include <pwd.h> -#include <regex.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/time.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/wait.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_print_arg = "-print"; -static char *fake_true_arg = "-true"; - -// Cost estimation constants -#define FAST_COST 40.0 -#define STAT_COST 1000.0 -#define PRINT_COST 20000.0 - -struct expr expr_true = { - .eval = eval_true, - .lhs = NULL, - .rhs = NULL, - .pure = true, - .always_true = true, - .cost = FAST_COST, - .probability = 1.0, - .argc = 1, - .argv = &fake_true_arg, -}; - -struct expr expr_false = { - .eval = eval_false, - .lhs = NULL, - .rhs = NULL, - .pure = true, - .always_false = true, - .cost = FAST_COST, - .probability = 0.0, - .argc = 1, - .argv = &fake_false_arg, -}; - -/** - * Free an expression. - */ -void free_expr(struct expr *expr) { - if (!expr || expr == &expr_true || expr == &expr_false) { - return; - } - - if (expr->regex) { - regfree(expr->regex); - free(expr->regex); - } - - free_bfs_printf(expr->printf); - free_bfs_exec(expr->execbuf); - - free_expr(expr->lhs); - free_expr(expr->rhs); - - free(expr); -} - -struct expr *new_expr(eval_fn *eval, size_t argc, char **argv) { - struct expr *expr = malloc(sizeof(*expr)); - if (!expr) { - perror("malloc()"); - return NULL; - } - - expr->eval = eval; - expr->lhs = NULL; - expr->rhs = NULL; - expr->pure = false; - expr->always_true = false; - expr->always_false = false; - expr->cost = FAST_COST; - expr->probability = 0.5; - expr->evaluations = 0; - expr->successes = 0; - expr->elapsed.tv_sec = 0; - expr->elapsed.tv_nsec = 0; - expr->argc = argc; - expr->argv = argv; - expr->cfile = NULL; - expr->regex = NULL; - expr->execbuf = NULL; - expr->printf = NULL; - expr->persistent_fds = 0; - expr->ephemeral_fds = 0; - return expr; -} - -/** - * Create a new unary expression. - */ -static struct expr *new_unary_expr(eval_fn *eval, struct expr *rhs, char **argv) { - struct expr *expr = new_expr(eval, 1, argv); - if (!expr) { - free_expr(rhs); - return NULL; - } - - expr->rhs = rhs; - expr->persistent_fds = rhs->persistent_fds; - expr->ephemeral_fds = rhs->ephemeral_fds; - return expr; -} - -/** - * Create a new binary expression. - */ -static struct expr *new_binary_expr(eval_fn *eval, struct expr *lhs, struct expr *rhs, char **argv) { - struct expr *expr = new_expr(eval, 1, argv); - if (!expr) { - free_expr(rhs); - free_expr(lhs); - return NULL; - } - - expr->lhs = lhs; - expr->rhs = rhs; - 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; -} - -/** - * Check if an expression never returns. - */ -bool expr_never_returns(const struct expr *expr) { - // Expressions that never return are vacuously both always true and always false - return expr->always_true && expr->always_false; -} - -/** - * Set an expression to always return true. - */ -static void expr_set_always_true(struct expr *expr) { - expr->always_true = true; - expr->probability = 1.0; -} - -/** - * Set an expression to never return. - */ -static void expr_set_never_returns(struct expr *expr) { - expr->always_true = expr->always_false = true; -} - -/** - * Dump the parsed expression tree, for debugging. - */ -void dump_expr(CFILE *cfile, const struct expr *expr, bool verbose) { - fputs("(", cfile->file); - - if (expr->lhs || expr->rhs) { - cfprintf(cfile, "${red}%s${rs}", expr->argv[0]); - } else { - cfprintf(cfile, "${blu}%s${rs}", expr->argv[0]); - } - - for (size_t i = 1; i < expr->argc; ++i) { - cfprintf(cfile, " ${bld}%s${rs}", expr->argv[i]); - } - - if (verbose) { - double rate = 0.0, time = 0.0; - if (expr->evaluations) { - rate = 100.0*expr->successes/expr->evaluations; - time = (1.0e9*expr->elapsed.tv_sec + expr->elapsed.tv_nsec)/expr->evaluations; - } - cfprintf(cfile, " [${ylw}%zu${rs}/${ylw}%zu${rs}=${ylw}%g%%${rs}; ${ylw}%gns${rs}]", expr->successes, expr->evaluations, rate, time); - } - - if (expr->lhs) { - fputs(" ", cfile->file); - dump_expr(cfile, expr->lhs, verbose); - } - - if (expr->rhs) { - fputs(" ", cfile->file); - dump_expr(cfile, expr->rhs, verbose); - } - - fputs(")", cfile->file); -} - -/** - * An open file for the command line. - */ -struct open_file { - /** The file itself. */ - CFILE *cfile; - /** The path to the file (for diagnostics). */ - const char *path; -}; - -/** - * Free the parsed command line. - */ -int free_cmdline(struct cmdline *cmdline) { - int ret = 0; - - if (cmdline) { - CFILE *cout = cmdline->cout; - CFILE *cerr = cmdline->cerr; - - free_expr(cmdline->expr); - - free_bfs_mtab(cmdline->mtab); - - struct trie_leaf *leaf; - while ((leaf = trie_first_leaf(&cmdline->open_files))) { - struct open_file *ofile = leaf->value; - - if (cfclose(ofile->cfile) != 0) { - if (cerr) { - bfs_error(cmdline, "'%s': %m.\n", ofile->path); - } - ret = -1; - } - - free(ofile); - trie_remove(&cmdline->open_files, leaf); - } - trie_destroy(&cmdline->open_files); - - if (cout && fflush(cout->file) != 0) { - if (cerr) { - bfs_error(cmdline, "standard output: %m.\n"); - } - ret = -1; - } - - cfclose(cout); - cfclose(cerr); - - free_colors(cmdline->colors); - free(cmdline->paths); - free(cmdline->argv); - free(cmdline); - } - - return ret; -} - -/** - * Color use flags. - */ -enum use_color { - COLOR_NEVER, - COLOR_AUTO, - COLOR_ALWAYS, -}; - -/** - * Ephemeral state for parsing the command line. - */ -struct parser_state { - /** The command line being constructed. */ - struct cmdline *cmdline; - /** The command line arguments being parsed. */ - char **argv; - /** The name of this program. */ - const char *command; - - /** The current regex flags to use. */ - int regex_flags; - - /** 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 warnings are enabled (see -warn, -nowarn). */ - bool warn; - /** Whether the expression has started. */ - bool expr_started; - /** Whether any non-option arguments have been encountered. */ - bool non_option_seen; - /** Whether an information option like -help or -version was passed. */ - bool just_info; - - /** The last non-path argument. */ - const char *last_arg; - /** A "-depth"-type argument if any. */ - const char *depth_arg; - /** A "-prune"-type argument if any. */ - const char *prune_arg; - - /** The current time. */ - 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 an error message during parsing. - */ -BFS_FORMATTER(2, 3) -static void parse_error(const struct parser_state *state, const char *format, ...) { - va_list args; - va_start(args, format); - bfs_verror(state->cmdline, format, args); - va_end(args); -} - -/** - * Print a warning message during parsing. - */ -BFS_FORMATTER(2, 3) -static void parse_warning(const struct parser_state *state, const char *format, ...) { - va_list args; - va_start(args, format); - bfs_vwarning(state->cmdline, format, args); - va_end(args); -} - -/** - * Fill in a "-print"-type expression. - */ -static void init_print_expr(struct parser_state *state, struct expr *expr) { - expr_set_always_true(expr); - expr->cost = PRINT_COST; - expr->cfile = state->cmdline->cout; -} - -/** - * Open a file for an expression. - */ -static int expr_open(struct parser_state *state, struct expr *expr, const char *path) { - int ret = -1; - - struct cmdline *cmdline = state->cmdline; - - CFILE *cfile = cfopen(path, state->use_color ? cmdline->colors : NULL); - if (!cfile) { - parse_error(state, "'%s': %m.\n", path); - goto out; - } - - struct bfs_stat sb; - if (bfs_stat(fileno(cfile->file), NULL, 0, &sb) != 0) { - parse_error(state, "'%s': %m.\n", path); - goto out_close; - } - - bfs_file_id id; - bfs_stat_id(&sb, &id); - - struct trie_leaf *leaf = trie_insert_mem(&cmdline->open_files, id, sizeof(id)); - if (leaf->value) { - struct open_file *ofile = leaf->value; - expr->cfile = ofile->cfile; - ret = 0; - goto out_close; - } - - struct open_file *ofile = malloc(sizeof(*ofile)); - if (!ofile) { - perror("malloc()"); - trie_remove(&cmdline->open_files, leaf); - goto out_close; - } - - ofile->cfile = cfile; - ofile->path = path; - leaf->value = ofile; - ++cmdline->nopen_files; - - expr->cfile = cfile; - - ret = 0; - goto out; - -out_close: - cfclose(cfile); -out: - return ret; -} - -/** - * Invoke bfs_stat() on an argument. - */ -static int stat_arg(const struct parser_state *state, struct expr *expr, struct bfs_stat *sb) { - const struct cmdline *cmdline = state->cmdline; - - bool follow = cmdline->flags & (BFTW_COMFOLLOW | BFTW_LOGICAL); - enum bfs_stat_flag flags = follow ? BFS_STAT_TRYFOLLOW : BFS_STAT_NOFOLLOW; - - int ret = bfs_stat(AT_FDCWD, expr->sdata, flags, sb); - if (ret != 0) { - parse_error(state, "'%s': %m.\n", expr->sdata); - } - return ret; -} - -/** - * Parse the expression specified on the command line. - */ -static struct expr *parse_expr(struct parser_state *state); - -/** - * 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; - - if (type != T_OPTION) { - state->non_option_seen = true; - } - } - - if (type != T_PATH) { - state->last_arg = *state->argv; - } - - char **argv = state->argv; - state->argv += argc; - return argv; -} - -/** - * Parse a root path. - */ -static int parse_root(struct parser_state *state, const char *path) { - struct cmdline *cmdline = state->cmdline; - size_t i = cmdline->npaths; - if ((i & (i + 1)) == 0) { - const char **paths = realloc(cmdline->paths, 2*(i + 1)*sizeof(*paths)); - if (!paths) { - return -1; - } - cmdline->paths = paths; - } - - cmdline->paths[i] = path; - cmdline->npaths = i + 1; - return 0; -} - -/** - * While parsing an expression, skip any paths and add them to the cmdline. - */ -static int skip_paths(struct parser_state *state) { - while (true) { - const char *arg = state->argv[0]; - if (!arg) { - return 0; - } - - if (arg[0] == '-') { - if (strcmp(arg, "--") == 0) { - // 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); - continue; - } - if (strcmp(arg, "-") != 0) { - // - by itself is a file name. Anything else - // starting with - is a flag/predicate. - return 0; - } - } - - // By POSIX, these are always options - if (strcmp(arg, "(") == 0 || strcmp(arg, "!") == 0) { - return 0; - } - - if (state->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) { - return 0; - } - } - - if (parse_root(state, arg) != 0) { - return -1; - } - - parser_advance(state, T_PATH, 1); - } -} - -/** Integer parsing flags. */ -enum int_flags { - IF_BASE_MASK = 0x03F, - IF_INT = 0x040, - IF_LONG = 0x080, - IF_LONG_LONG = 0x0C0, - IF_SIZE_MASK = 0x0C0, - IF_UNSIGNED = 0x100, - IF_PARTIAL_OK = 0x200, - IF_QUIET = 0x400, -}; - -/** - * Parse an integer. - */ -static const char *parse_int(const struct parser_state *state, const char *str, void *result, enum int_flags flags) { - char *endptr; - - int base = flags & IF_BASE_MASK; - if (base == 0) { - base = 10; - } - - errno = 0; - long long value = strtoll(str, &endptr, base); - if (errno != 0) { - goto bad; - } - - if (endptr == str) { - goto bad; - } - - if (!(flags & IF_PARTIAL_OK) && *endptr != '\0') { - goto bad; - } - - if ((flags & IF_UNSIGNED) && value < 0) { - goto bad; - } - - switch (flags & IF_SIZE_MASK) { - case IF_INT: - if (value < INT_MIN || value > INT_MAX) { - goto bad; - } - *(int *)result = value; - break; - - case IF_LONG: - if (value < LONG_MIN || value > LONG_MAX) { - goto bad; - } - *(long *)result = value; - break; - - case IF_LONG_LONG: - *(long long *)result = value; - break; - } - - return endptr; - -bad: - if (!(flags & IF_QUIET)) { - parse_error(state, "'%s' is not a valid integer.\n", str); - } - return NULL; -} - -/** - * Parse an integer and a comparison flag. - */ -static const char *parse_icmp(const struct parser_state *state, const char *str, struct expr *expr, enum int_flags flags) { - switch (str[0]) { - case '-': - expr->cmp_flag = CMP_LESS; - ++str; - break; - case '+': - expr->cmp_flag = CMP_GREATER; - ++str; - break; - default: - expr->cmp_flag = CMP_EXACT; - break; - } - - return parse_int(state, str, &expr->idata, flags | IF_LONG_LONG | IF_UNSIGNED); -} - -/** - * Check if a string could be an integer comparison. - */ -static bool looks_like_icmp(const char *str) { - int i; - - // One +/- for the comparison flag, one for the sign - for (i = 0; i < 2; ++i) { - if (str[i] != '-' && str[i] != '+') { - break; - } - } - - return str[i] >= '0' && str[i] <= '9'; -} - -/** - * Parse a single flag. - */ -static struct expr *parse_flag(struct parser_state *state, size_t argc) { - parser_advance(state, T_FLAG, argc); - return &expr_true; -} - -/** - * Parse a flag that doesn't take a value. - */ -static struct expr *parse_nullary_flag(struct parser_state *state) { - return parse_flag(state, 1); -} - -/** - * Parse a flag that takes a single value. - */ -static struct expr *parse_unary_flag(struct parser_state *state) { - return parse_flag(state, 2); -} - -/** - * Parse a single option. - */ -static struct expr *parse_option(struct parser_state *state, size_t argc) { - const char *arg = *parser_advance(state, T_OPTION, argc); - - if (state->warn && state->non_option_seen) { - parse_warning(state, - "The '%s' option applies to the entire command line. For clarity, place\n" - "it before any non-option arguments.\n\n", - arg); - } - - - return &expr_true; -} - -/** - * Parse an option that doesn't take a value. - */ -static struct expr *parse_nullary_option(struct parser_state *state) { - return parse_option(state, 1); -} - -/** - * Parse an option that takes a value. - */ -static struct expr *parse_unary_option(struct parser_state *state) { - return parse_option(state, 2); -} - -/** - * Parse a single positional option. - */ -static struct expr *parse_positional_option(struct parser_state *state, size_t argc) { - parser_advance(state, T_OPTION, argc); - return &expr_true; -} - -/** - * Parse a positional option that doesn't take a value. - */ -static struct expr *parse_nullary_positional_option(struct parser_state *state) { - return parse_positional_option(state, 1); -} - -/** - * Parse a positional option that takes a single value. - */ -static struct expr *parse_unary_positional_option(struct parser_state *state, const char **value) { - const char *arg = state->argv[0]; - *value = state->argv[1]; - if (!*value) { - parse_error(state, "%s needs a value.\n", arg); - return NULL; - } - - return parse_positional_option(state, 2); -} - -/** - * Parse a single test. - */ -static struct expr *parse_test(struct parser_state *state, eval_fn *eval, size_t argc) { - char **argv = parser_advance(state, T_TEST, argc); - struct expr *expr = new_expr(eval, argc, argv); - if (expr) { - expr->pure = true; - } - return expr; -} - -/** - * Parse a test that doesn't take a value. - */ -static struct expr *parse_nullary_test(struct parser_state *state, eval_fn *eval) { - return parse_test(state, eval, 1); -} - -/** - * Parse a test that takes a value. - */ -static struct expr *parse_unary_test(struct parser_state *state, eval_fn *eval) { - const char *arg = state->argv[0]; - const char *value = state->argv[1]; - if (!value) { - parse_error(state, "%s needs a value.\n", arg); - return NULL; - } - - struct expr *expr = parse_test(state, eval, 2); - if (expr) { - expr->sdata = value; - } - return expr; -} - -/** - * Parse a single action. - */ -static struct expr *parse_action(struct parser_state *state, eval_fn *eval, size_t argc) { - if (eval != eval_nohidden && eval != eval_prune && eval != eval_quit) { - state->implicit_print = false; - } - - char **argv = parser_advance(state, T_ACTION, argc); - return new_expr(eval, argc, argv); -} - -/** - * Parse an action that takes no arguments. - */ -static struct expr *parse_nullary_action(struct parser_state *state, eval_fn *eval) { - return parse_action(state, eval, 1); -} - -/** - * Parse an action that takes one argument. - */ -static struct expr *parse_unary_action(struct parser_state *state, eval_fn *eval) { - const char *arg = state->argv[0]; - const char *value = state->argv[1]; - if (!value) { - parse_error(state, "%s needs a value.\n", arg); - return NULL; - } - - struct expr *expr = parse_action(state, eval, 2); - if (expr) { - expr->sdata = value; - } - return expr; -} - -/** - * Parse a test expression with integer data and a comparison flag. - */ -static struct expr *parse_test_icmp(struct parser_state *state, eval_fn *eval) { - struct expr *expr = parse_unary_test(state, eval); - if (!expr) { - return NULL; - } - - if (!parse_icmp(state, expr->sdata, expr, 0)) { - free_expr(expr); - return NULL; - } - - return expr; -} - -/** - * Print usage information for -D. - */ -static void debug_help(CFILE *cfile) { - cfprintf(cfile, "Supported debug flags:\n\n"); - - cfprintf(cfile, " ${bld}help${rs}: This message.\n"); - cfprintf(cfile, " ${bld}cost${rs}: Show cost estimates.\n"); - cfprintf(cfile, " ${bld}exec${rs}: Print executed command details.\n"); - cfprintf(cfile, " ${bld}opt${rs}: Print optimization details.\n"); - cfprintf(cfile, " ${bld}rates${rs}: Print predicate success rates.\n"); - cfprintf(cfile, " ${bld}search${rs}: Trace the filesystem traversal.\n"); - cfprintf(cfile, " ${bld}stat${rs}: Trace all stat() calls.\n"); - cfprintf(cfile, " ${bld}tree${rs}: Print the parse tree.\n"); - cfprintf(cfile, " ${bld}all${rs}: All debug flags at once.\n"); -} - -/** A named debug flag. */ -struct debug_flag { - enum debug_flags flag; - const char *name; -}; - -/** The table of debug flags. */ -struct debug_flag debug_flags[] = { - {DEBUG_ALL, "all"}, - {DEBUG_COST, "cost"}, - {DEBUG_EXEC, "exec"}, - {DEBUG_OPT, "opt"}, - {DEBUG_RATES, "rates"}, - {DEBUG_SEARCH, "search"}, - {DEBUG_STAT, "stat"}, - {DEBUG_TREE, "tree"}, - {0}, -}; - -/** Check if a substring matches a debug flag. */ -static bool parse_debug_flag(const char *flag, size_t len, const char *expected) { - if (len == strlen(expected)) { - return strncmp(flag, expected, len) == 0; - } else { - return false; - } -} - -/** - * Parse -D FLAG. - */ -static struct expr *parse_debug(struct parser_state *state, int arg1, int arg2) { - struct cmdline *cmdline = state->cmdline; - - const char *arg = state->argv[0]; - const char *flags = state->argv[1]; - if (!flags) { - parse_error(state, "%s needs a flag.\n\n", arg); - debug_help(cmdline->cerr); - return NULL; - } - - bool unrecognized = false; - - for (const char *flag = flags, *next; flag; flag = next) { - size_t len = strcspn(flag, ","); - if (flag[len]) { - next = flag + len + 1; - } else { - next = NULL; - } - - if (parse_debug_flag(flag, len, "help")) { - debug_help(cmdline->cout); - state->just_info = true; - return NULL; - } - - for (int i = 0; ; ++i) { - const char *expected = debug_flags[i].name; - if (!expected) { - parse_warning(state, "Unrecognized debug flag '"); - fwrite(flag, 1, len, stderr); - fputs("'\n\n", stderr); - unrecognized = true; - break; - } - - if (parse_debug_flag(flag, len, expected)) { - cmdline->debug |= debug_flags[i].flag; - break; - } - } - } - - if (unrecognized) { - debug_help(cmdline->cerr); - cfprintf(cmdline->cerr, "\n"); - } - - return parse_unary_flag(state); -} - -/** - * Parse -On. - */ -static struct expr *parse_optlevel(struct parser_state *state, int arg1, int arg2) { - int *optlevel = &state->cmdline->optlevel; - - if (strcmp(state->argv[0], "-Ofast") == 0) { - *optlevel = 4; - } else if (!parse_int(state, state->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) { - return NULL; - } - - if (state->warn && *optlevel > 4) { - parse_warning(state, "%s is the same as -O4.\n\n", state->argv[0]); - } - - return parse_nullary_flag(state); -} - -/** - * Parse -[PHL], -(no)?follow. - */ -static struct expr *parse_follow(struct parser_state *state, int flags, int option) { - struct cmdline *cmdline = state->cmdline; - cmdline->flags &= ~(BFTW_COMFOLLOW | BFTW_LOGICAL); - cmdline->flags |= flags; - if (option) { - return parse_nullary_positional_option(state); - } else { - return parse_nullary_flag(state); - } -} - -/** - * Parse -X. - */ -static struct expr *parse_xargs_safe(struct parser_state *state, int arg1, int arg2) { - state->cmdline->xargs_safe = true; - return parse_nullary_flag(state); -} - -/** - * Parse -executable, -readable, -writable - */ -static struct expr *parse_access(struct parser_state *state, int flag, int arg2) { - struct expr *expr = parse_nullary_test(state, eval_access); - if (!expr) { - return NULL; - } - - expr->idata = flag; - expr->cost = STAT_COST; - - switch (flag) { - case R_OK: - expr->probability = 0.99; - break; - case W_OK: - expr->probability = 0.8; - break; - case X_OK: - expr->probability = 0.2; - break; - } - - return expr; -} - -/** - * Parse -acl. - */ -static struct expr *parse_acl(struct parser_state *state, int flag, int arg2) { -#if BFS_CAN_CHECK_ACL - struct expr *expr = parse_nullary_test(state, eval_acl); - if (expr) { - expr->cost = STAT_COST; - expr->probability = 0.00002; - } - return expr; -#else - parse_error(state, "%s is missing platform support.\n", state->argv[0]); - return NULL; -#endif -} - -/** - * Parse -[aBcm]?newer. - */ -static struct expr *parse_newer(struct parser_state *state, int field, int arg2) { - struct expr *expr = parse_unary_test(state, eval_newer); - if (!expr) { - return NULL; - } - - struct bfs_stat sb; - if (stat_arg(state, expr, &sb) != 0) { - goto fail; - } - - expr->cost = STAT_COST; - expr->reftime = sb.mtime; - expr->stat_field = field; - return expr; - -fail: - free_expr(expr); - return NULL; -} - -/** - * Parse -[aBcm]{min,time}. - */ -static struct expr *parse_time(struct parser_state *state, int field, int unit) { - struct expr *expr = parse_test_icmp(state, eval_time); - if (!expr) { - return NULL; - } - - expr->cost = STAT_COST; - expr->reftime = state->now; - expr->stat_field = field; - expr->time_unit = unit; - return expr; -} - -/** - * Parse -capable. - */ -static struct expr *parse_capable(struct parser_state *state, int flag, int arg2) { -#if BFS_CAN_CHECK_CAPABILITIES - struct expr *expr = parse_nullary_test(state, eval_capable); - if (expr) { - expr->cost = STAT_COST; - expr->probability = 0.000002; - } - return expr; -#else - parse_error(state, "%s is missing platform support.\n", state->argv[0]); - return NULL; -#endif -} - -/** - * Parse -(no)?color. - */ -static struct expr *parse_color(struct parser_state *state, int color, int arg2) { - struct cmdline *cmdline = state->cmdline; - struct colors *colors = cmdline->colors; - if (color) { - state->use_color = COLOR_ALWAYS; - cmdline->cout->colors = colors; - cmdline->cerr->colors = colors; - } else { - state->use_color = COLOR_NEVER; - cmdline->cout->colors = NULL; - cmdline->cerr->colors = NULL; - } - return parse_nullary_option(state); -} - -/** - * Parse -{false,true}. - */ -static struct expr *parse_const(struct parser_state *state, int value, int arg2) { - parser_advance(state, T_TEST, 1); - return value ? &expr_true : &expr_false; -} - -/** - * Parse -daystart. - */ -static struct expr *parse_daystart(struct parser_state *state, int arg1, int arg2) { - struct tm tm; - if (xlocaltime(&state->now.tv_sec, &tm) != 0) { - perror("xlocaltime()"); - return NULL; - } - - if (tm.tm_hour || tm.tm_min || tm.tm_sec || state->now.tv_nsec) { - ++tm.tm_mday; - } - tm.tm_hour = 0; - tm.tm_min = 0; - tm.tm_sec = 0; - - time_t time = mktime(&tm); - if (time == -1) { - perror("mktime()"); - return NULL; - } - - state->now.tv_sec = time; - state->now.tv_nsec = 0; - - return parse_nullary_positional_option(state); -} - -/** - * Parse -delete. - */ -static struct expr *parse_delete(struct parser_state *state, int arg1, int arg2) { - state->cmdline->flags |= BFTW_DEPTH; - state->depth_arg = state->argv[0]; - return parse_nullary_action(state, eval_delete); -} - -/** - * Parse -d. - */ -static struct expr *parse_depth(struct parser_state *state, int arg1, int arg2) { - state->cmdline->flags |= BFTW_DEPTH; - state->depth_arg = state->argv[0]; - return parse_nullary_flag(state); -} - -/** - * Parse -depth [N]. - */ -static struct expr *parse_depth_n(struct parser_state *state, int arg1, int arg2) { - const char *arg = state->argv[1]; - if (arg && looks_like_icmp(arg)) { - return parse_test_icmp(state, eval_depth); - } else { - return parse_depth(state, arg1, arg2); - } -} - -/** - * Parse -{min,max}depth N. - */ -static struct expr *parse_depth_limit(struct parser_state *state, int is_min, int arg2) { - struct cmdline *cmdline = state->cmdline; - const char *arg = state->argv[0]; - const char *value = state->argv[1]; - if (!value) { - parse_error(state, "%s needs a value.\n", arg); - return NULL; - } - - int *depth = is_min ? &cmdline->mindepth : &cmdline->maxdepth; - if (!parse_int(state, value, depth, IF_INT | IF_UNSIGNED)) { - return NULL; - } - - return parse_unary_option(state); -} - -/** - * Parse -empty. - */ -static struct expr *parse_empty(struct parser_state *state, int arg1, int arg2) { - struct expr *expr = parse_nullary_test(state, eval_empty); - if (!expr) { - return NULL; - } - - expr->cost = 2000.0; - expr->probability = 0.01; - - if (state->cmdline->optlevel < 4) { - // Since -empty attempts to open and read directories, it may - // have side effects such as reporting permission errors, and - // thus shouldn't be re-ordered without aggressive optimizations - expr->pure = false; - } - - expr->ephemeral_fds = 1; - - return expr; -} - -/** - * Parse -exec(dir)?/-ok(dir)?. - */ -static struct expr *parse_exec(struct parser_state *state, int flags, int arg2) { - struct bfs_exec *execbuf = parse_bfs_exec(state->argv, flags, state->cmdline); - if (!execbuf) { - return NULL; - } - - struct expr *expr = parse_action(state, eval_exec, execbuf->tmpl_argc + 2); - if (!expr) { - free_bfs_exec(execbuf); - return NULL; - } - - expr->execbuf = execbuf; - - if (execbuf->flags & BFS_EXEC_MULTI) { - expr_set_always_true(expr); - } else { - expr->cost = 1000000.0; - } - - expr->ephemeral_fds = 2; - if (execbuf->flags & BFS_EXEC_CHDIR) { - if (execbuf->flags & BFS_EXEC_MULTI) { - expr->persistent_fds = 1; - } else { - ++expr->ephemeral_fds; - } - } - - return expr; -} - -/** - * Parse -exit [STATUS]. - */ -static struct expr *parse_exit(struct parser_state *state, int arg1, int arg2) { - size_t argc = 1; - const char *value = state->argv[1]; - - int status = EXIT_SUCCESS; - if (value && parse_int(state, value, &status, IF_INT | IF_UNSIGNED | IF_QUIET)) { - argc = 2; - } - - struct expr *expr = parse_action(state, eval_exit, argc); - if (expr) { - expr_set_never_returns(expr); - expr->idata = status; - } - return expr; -} - -/** - * Parse -f PATH. - */ -static struct expr *parse_f(struct parser_state *state, int arg1, int arg2) { - parser_advance(state, T_FLAG, 1); - - const char *path = state->argv[0]; - if (!path) { - parse_error(state, "-f requires a path.\n"); - return NULL; - } - - if (parse_root(state, path) != 0) { - return NULL; - } - - parser_advance(state, T_PATH, 1); - return &expr_true; -} - -/** - * Parse -fls FILE. - */ -static struct expr *parse_fls(struct parser_state *state, int arg1, int arg2) { - struct expr *expr = parse_unary_action(state, eval_fls); - if (expr) { - expr_set_always_true(expr); - expr->cost = PRINT_COST; - if (expr_open(state, expr, expr->sdata) != 0) { - go |