summaryrefslogtreecommitdiffstats
path: root/parse.c
diff options
context:
space:
mode:
Diffstat (limited to 'parse.c')
-rw-r--r--parse.c3462
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