diff options
-rw-r--r-- | Makefile | 18 | ||||
-rw-r--r-- | src/main.c | 225 | ||||
-rw-r--r-- | tests/getopts.sh | 2 | ||||
-rw-r--r-- | tests/run.sh | 10 |
4 files changed, 236 insertions, 19 deletions
@@ -35,12 +35,12 @@ gen/config.mk:: ## Build phase (`make`) # The main binary -bfs: bin/bfs +bfs: bin/find2fd .PHONY: bfs # All binaries BINS := \ - bin/bfs \ + bin/find2fd \ bin/tests/mksock \ bin/tests/units \ bin/tests/xspawnee \ @@ -50,7 +50,7 @@ all: ${BINS} .PHONY: all # The main binary -bin/bfs: ${LIBBFS} obj/src/main.o +bin/find2fd: ${LIBBFS} obj/src/main.o ${BINS}: @${MKDIR} ${@D} @@ -120,19 +120,19 @@ INTEGRATIONS := default dfs ids eds j1 j2 j3 s INTEGRATION_TESTS := ${INTEGRATIONS:%=check-%} # Check just `bfs` -check-default: bin/bfs ${ITEST_BINS} +check-default: bin/find2fd ${ITEST_BINS} +${MSG} "[TEST] bfs" \ - ./tests/tests.sh --make="${MAKE}" --bfs="bin/bfs" ${TEST_FLAGS} + ./tests/tests.sh --make="${MAKE}" --bfs="bin/find2fd" ${TEST_FLAGS} # Check the different search strategies -check-dfs check-ids check-eds: bin/bfs ${ITEST_BINS} +check-dfs check-ids check-eds: bin/find2fd ${ITEST_BINS} +${MSG} "[TEST] bfs -S ${@:check-%=%}" \ - ./tests/tests.sh --make="${MAKE}" --bfs="bin/bfs -S ${@:check-%=%}" ${TEST_FLAGS} + ./tests/tests.sh --make="${MAKE}" --bfs="bin/find2fd -S ${@:check-%=%}" ${TEST_FLAGS} # Check various flags -check-j1 check-j2 check-j3 check-s: bin/bfs ${ITEST_BINS} +check-j1 check-j2 check-j3 check-s: bin/find2fd ${ITEST_BINS} +${MSG} "[TEST] bfs -${@:check-%=%}" \ - ./tests/tests.sh --make="${MAKE}" --bfs="bin/bfs -${@:check-%=%}" ${TEST_FLAGS} + ./tests/tests.sh --make="${MAKE}" --bfs="bin/find2fd -${@:check-%=%}" ${TEST_FLAGS} # Run the integration tests integration-tests: ${INTEGRATION_TESTS} @@ -50,13 +50,15 @@ #include "ctx.h" #include "diag.h" #include "eval.h" +#include "exec.h" +#include "expr.h" #include "parse.h" #include <errno.h> #include <fcntl.h> +#include <limits.h> #include <locale.h> #include <stdio.h> #include <stdlib.h> -#include <time.h> #include <unistd.h> /** @@ -108,6 +110,21 @@ static int open_std_streams(void) { return 0; } +static void find2fd_extract(struct bfs_exprs *exprs, struct bfs_expr *expr) { + SLIST_INIT(exprs); + + if (expr->eval_fn == eval_and) { + SLIST_EXTEND(exprs, &expr->children); + } else { + SLIST_APPEND(exprs, expr); + } +} + +static void shellesc(dchar **cmdline, const char *str) { + dstrcat(cmdline, " "); + dstrescat(cmdline, str, WESC_SHELL | WESC_TTY); +} + /** * bfs entry point. */ @@ -137,13 +154,207 @@ int main(int argc, char *argv[]) { bfs_warning(ctx, "Failed to set locale: %s\n\n", xstrerror(locale_err)); } - // Walk the file system tree, evaluating the expression on each file - int ret = bfs_eval(ctx); + bool hidden = true; + if (ctx->exclude->eval_fn == eval_hidden) { + hidden = false; + } else if (ctx->exclude->eval_fn != eval_false) { + bfs_expr_error(ctx, ctx->exclude); + bfs_error(ctx, "${ex}fd${rs} does not support ${red}-exclude${rs}.\n"); + return EXIT_FAILURE; + } + + struct bfs_exprs exprs; + find2fd_extract(&exprs, ctx->expr); + + struct bfs_expr *pattern = NULL; + struct bfs_expr *type = NULL; + struct bfs_expr *executable = NULL; + struct bfs_expr *empty = NULL; + struct bfs_expr *action = NULL; + for_slist (struct bfs_expr, expr, &exprs) { + struct bfs_expr **target = NULL; + if (expr->eval_fn == eval_name + || expr->eval_fn == eval_path + || expr->eval_fn == eval_regex) { + target = &pattern; + } else if (expr->eval_fn == eval_type) { + target = &type; + } else if (expr->eval_fn == eval_access && expr->num == X_OK) { + target = &executable; + } else if (expr->eval_fn == eval_empty) { + target = ∅ + } else if ((expr->eval_fn == eval_fprint + || expr->eval_fn == eval_fprint0 + || expr->eval_fn == eval_fls) + && expr->cfile == ctx->cout) { + target = &action; + } else if (expr->eval_fn == eval_exec + && !(expr->exec->flags & (BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR))) { + target = &action; + } + + if (!target) { + bfs_expr_error(ctx, expr); + if (bfs_expr_is_parent(expr)) { + bfs_error(ctx, "Too complicated to convert to ${ex}fd${rs}.\n"); + } else { + bfs_error(ctx, "No equivalent ${ex}fd${rs} option.\n"); + } + return EXIT_FAILURE; + } + + if (*target) { + bfs_expr_error(ctx, *target); + bfs_expr_error(ctx, expr); + bfs_error(ctx, "${ex}fd${rs} doesn't support both of these at once.\n"); + return EXIT_FAILURE; + } + + if (action && target != &action) { + bfs_expr_error(ctx, expr); + bfs_error(ctx, "${ex}fd${rs} doesn't support this ...\n"); + bfs_expr_error(ctx, *target); + bfs_error(ctx, "... after this.\n"); + return EXIT_FAILURE; + } + + *target = expr; + } + + if (!action) { + bfs_expr_error(ctx, ctx->expr); + bfs_error(ctx, "Missing action.\n"); + return EXIT_FAILURE; + } + + dchar *cmdline = dstralloc(0); + + dstrcat(&cmdline, "fd --no-ignore"); - // Free the parsed command line, and detect any last-minute errors - if (bfs_ctx_free(ctx) != 0 && ret == EXIT_SUCCESS) { - ret = EXIT_FAILURE; + if (hidden) { + dstrcat(&cmdline, " --hidden"); } - return ret; + if (ctx->flags & BFTW_POST_ORDER) { + bfs_error(ctx, "${ex}fd${rs} doesn't support ${blu}-depth${rs}.\n"); + return EXIT_FAILURE; + } + if (ctx->flags & BFTW_SORT) { + bfs_error(ctx, "${ex}fd${rs} doesn't support ${cyn}-s${rs}.\n"); + return EXIT_FAILURE; + } + if (ctx->flags & BFTW_FOLLOW_ALL) { + dstrcat(&cmdline, " --follow"); + } + if (ctx->flags & (BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) { + dstrcat(&cmdline, " --one-file-system"); + } + + if (ctx->mindepth == ctx->maxdepth) { + dstrcatf(&cmdline, " --exact-depth %d", ctx->mindepth); + } else { + if (ctx->mindepth > 0) { + dstrcatf(&cmdline, " --min-depth %d", ctx->mindepth); + } + if (ctx->maxdepth < INT_MAX) { + dstrcatf(&cmdline, " --max-depth %d", ctx->mindepth); + } + } + + if (type) { + unsigned int types = type->num; + if (types & (1 << BFS_REG)) { + dstrcat(&cmdline, " --type file"); + types ^= (1 << BFS_REG); + } + if (types & (1 << BFS_DIR)) { + dstrcat(&cmdline, " --type directory"); + types ^= (1 << BFS_DIR); + } + if (types & (1 << BFS_LNK)) { + dstrcat(&cmdline, " --type symlink"); + types ^= (1 << BFS_LNK); + } + if (types & (1 << BFS_SOCK)) { + dstrcat(&cmdline, " --type socket"); + types ^= (1 << BFS_SOCK); + } + if (types & (1 << BFS_FIFO)) { + dstrcat(&cmdline, " --type pipe"); + types ^= (1 << BFS_FIFO); + } + if (types) { + bfs_expr_error(ctx, type); + bfs_error(ctx, "${ex}fd${rs} doesn't support this type.\n"); + return EXIT_FAILURE; + } + } + + if (executable) { + dstrcat(&cmdline, " --type executable"); + } + + if (empty) { + dstrcat(&cmdline, " --type empty"); + } + + if (action->eval_fn == eval_fprint0) { + dstrcat(&cmdline, " --print0"); + } else if (action->eval_fn == eval_fls) { + dstrcat(&cmdline, " --list-details"); + } + + if (pattern) { + if (pattern->eval_fn != eval_name) { + dstrcat(&cmdline, " --full-path"); + } + if (pattern->eval_fn != eval_regex) { + dstrcat(&cmdline, " --glob"); + } + if (pattern->argv[0][1] == 'i') { + dstrcat(&cmdline, " --ignore-case"); + } else { + dstrcat(&cmdline, " --case-sensitive"); + } + shellesc(&cmdline, pattern->argv[1]); + } + + for (size_t i = 0; i < ctx->npaths; ++i) { + const char *path = ctx->paths[i]; + if (!pattern || path[0] == '-') { + dstrcat(&cmdline, " --search-path"); + } + shellesc(&cmdline, path); + } + + if (action->eval_fn == eval_exec) { + struct bfs_exec *execbuf = action->exec; + + dstrcat(&cmdline, " --exec"); + if (execbuf->flags & BFS_EXEC_MULTI) { + dstrcat(&cmdline, "-batch"); + } + + bool placeholder = false; + for (size_t i = 0; i < execbuf->tmpl_argc; ++i) { + const char *arg = execbuf->tmpl_argv[i]; + if (strstr(arg, "{}")) { + placeholder = true; + if (i == execbuf->tmpl_argc - 1 && strcmp(arg, "{}") == 0) { + // fd adds it automatically + break; + } + } + shellesc(&cmdline, arg); + } + + if (!placeholder) { + bfs_expr_error(ctx, action); + bfs_error(ctx, "${ex}fd${rs} doesn't support ${blu}%s${rs} without a placeholder.\n", action->argv[0]); + return EXIT_FAILURE; + } + } + + printf("%s\n", cmdline); + return EXIT_SUCCESS; } diff --git a/tests/getopts.sh b/tests/getopts.sh index 5214e9f..b987e49 100644 --- a/tests/getopts.sh +++ b/tests/getopts.sh @@ -150,7 +150,7 @@ parse_args() { # Try to resolve the path to $BFS before we cd, while also supporting # --bfs="./bin/bfs -S ids" - read -a BFS <<<"${BFS:-$BIN/bfs}" + read -a BFS <<<"${BFS:-$BIN/find2fd}" BFS[0]=$(_realpath "$(command -v "${BFS[0]}")") if ((${#PATTERNS[@]} == 0)); then diff --git a/tests/run.sh b/tests/run.sh index ad9c0be..df3bd88 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -359,6 +359,7 @@ bfs_verbose_impl() { # Run the bfs we're testing invoke_bfs() { + skip bfs_verbose "$@" local ret=0 @@ -454,9 +455,14 @@ diff_output() { # Run bfs, and diff it against the expected output bfs_diff() { - local ret=0 - invoke_bfs "$@" >"$OUT" || ret=$? + local fd + if ! fd=$("${BFS[@]}" "$@"); then + skip + fi + local ret=0 + eval "$fd" >"$OUT" || ret=$? + sed -i 's|/$||' "$OUT" sort_output diff_output || exit $EX_DIFF |