From b6b7a68190703d30912d2a1c3d8d64e3de81a612 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 8 May 2021 11:58:09 -0400 Subject: Implement -files0-from FILE See https://savannah.gnu.org/bugs/?60383 for the development of the corresponding GNU find feature. --- bfs.1 | 9 ++++ ctx.c | 5 +++ parse.c | 93 ++++++++++++++++++++++++++++++++++++++-- tests.sh | 37 ++++++++++++++++ tests/test_files0_from_file.out | 50 +++++++++++++++++++++ tests/test_files0_from_stdin.out | 50 +++++++++++++++++++++ 6 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 tests/test_files0_from_file.out create mode 100644 tests/test_files0_from_stdin.out diff --git a/bfs.1 b/bfs.1 index 0d95fd7..36089f8 100644 --- a/bfs.1 +++ b/bfs.1 @@ -267,6 +267,15 @@ Search in post-order (descendents first). .B \-follow Follow all symbolic links (same as .BR \-L ). +.TP +\fB\-files0\-from \fIFILE\fR +Treat the NUL ('\\0')-separated paths in +.I FILE +as starting points for the search. +Pass +.B \-files0\-from +.I \- +to read the paths from standard input. .PP \fB\-ignore_readdir_race\fR .br diff --git a/ctx.c b/ctx.c index 5c9c9bf..0b7296c 100644 --- a/ctx.c +++ b/ctx.c @@ -256,7 +256,12 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { cfclose(cerr); free_colors(ctx->colors); + + for (size_t i = 0; i < darray_length(ctx->paths); ++i) { + free((char *)ctx->paths[i]); + } darray_free(ctx->paths); + free(ctx->argv); free(ctx); } diff --git a/parse.c b/parse.c index 1129ebd..2a189e3 100644 --- a/parse.c +++ b/parse.c @@ -237,10 +237,14 @@ struct parser_state { bool stdout_tty; /** Whether this session is interactive (stdin and stderr are each a terminal). */ bool interactive; + /** Whether stdin has been consumed by -files0-from -. */ + bool stdin_consumed; /** 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 any non-option arguments have been encountered. */ @@ -260,6 +264,8 @@ struct parser_state { const char *mount_arg; /** An "-xdev"-type argument if any. */ const char *xdev_arg; + /** An "-ok"-type argument if any. */ + const char *ok_arg; /** The current time. */ struct timespec now; @@ -383,12 +389,21 @@ static char **parser_advance(struct parser_state *state, enum token_type type, s * 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()"); + return -1; + } + struct bfs_ctx *ctx = state->ctx; - int ret = DARRAY_PUSH(&ctx->paths, &path); - if (ret != 0) { + if (DARRAY_PUSH(&ctx->paths, ©) != 0) { parse_perror(state, "DARRAY_PUSH()"); + free(copy); + return -1; } - return ret; + + state->implicit_root = false; + return 0; } /** @@ -1189,6 +1204,10 @@ static struct expr *parse_exec(struct parser_state *state, int flags, int arg2) } } + if (execbuf->flags & BFS_EXEC_CONFIRM) { + state->ok_arg = expr->argv[0]; + } + return expr; } @@ -1232,6 +1251,58 @@ static struct expr *parse_f(struct parser_state *state, int arg1, int arg2) { return &expr_true; } +/** + * Parse -files0-from PATH. + */ +static struct expr *parse_files0_from(struct parser_state *state, int arg1, int arg2) { + const char *arg = state->argv[0]; + const char *from = state->argv[1]; + if (!from) { + parse_error(state, "${blu}%s${rs} requires a path.\n", arg); + return NULL; + } + + FILE *file; + if (strcmp(from, "-") == 0) { + file = stdin; + } else { + file = fopen(from, "rb"); + } + if (!file) { + parse_error(state, "${blu}%s${rs} ${bld}%s${rs}: %m.\n", arg, from); + return NULL; + } + + struct expr *expr = parse_unary_positional_option(state); + + while (true) { + char *path = xgetdelim(file, '\0'); + if (!path) { + if (errno) { + parse_error(state, "${blu}%s${rs} ${bld}%s${rs}: %m.\n", arg, from); + expr = NULL; + } + break; + } + + int ret = parse_root(state, path); + free(path); + if (ret != 0) { + expr = NULL; + break; + } + } + + if (file == stdin) { + state->stdin_consumed = true; + } else { + fclose(file); + } + + state->implicit_root = false; + return expr; +} + /** * Parse -flags FLAGS. */ @@ -2736,6 +2807,8 @@ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { cfprintf(cout, " Measure times relative to the start of today\n"); cfprintf(cout, " ${blu}-depth${rs}\n"); cfprintf(cout, " Search in post-order (descendents first)\n"); + cfprintf(cout, " ${blu}-files0-from${rs} ${bld}FILE${rs}\n"); + cfprintf(cout, " Search the NUL ('\\0')-separated paths from ${bld}FILE${rs} (${bld}-${rs} for standard input).\n"); cfprintf(cout, " ${blu}-follow${rs}\n"); cfprintf(cout, " Follow all symbolic links (same as ${cyn}-L${rs})\n"); cfprintf(cout, " ${blu}-ignore_readdir_race${rs}\n"); @@ -2988,6 +3061,7 @@ static const struct table_entry parse_table[] = { {"-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}, @@ -3408,6 +3482,11 @@ static struct expr *parse_whole_expr(struct parser_state *state) { fprintf(stderr, "\n"); } + if (state->ok_arg && state->stdin_consumed) { + parse_error(state, "${blu}%s${rs} conflicts with ${blu}-files0-from${rs} ${bld}-${rs}.\n", state->ok_arg); + goto fail; + } + return expr; fail: @@ -3627,8 +3706,10 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .regex_flags = 0, .stdout_tty = stdout_tty, .interactive = stdin_tty && stderr_tty, + .stdin_consumed = false, .use_color = use_color, .implicit_print = true, + .implicit_root = true, .non_option_seen = false, .just_info = false, .excluding = false, @@ -3637,6 +3718,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .prune_arg = NULL, .mount_arg = NULL, .xdev_arg = NULL, + .ok_arg = NULL, }; if (strcmp(xbasename(state.command), "find") == 0) { @@ -3663,7 +3745,10 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { } if (darray_length(ctx->paths) == 0) { - if (parse_root(&state, ".") != 0) { + if (!state.implicit_root) { + parse_error(&state, "No root paths specified.\n"); + goto fail; + } else if (parse_root(&state, ".") != 0) { goto fail; } } diff --git a/tests.sh b/tests.sh index 48fec87..46debd9 100755 --- a/tests.sh +++ b/tests.sh @@ -503,6 +503,14 @@ gnu_tests=( test_false + test_files0_from_file + test_files0_from_stdin + test_files0_from_none + test_files0_from_empty + test_files0_from_nowhere + test_files0_from_nothing + test_files0_from_ok + test_fls test_follow @@ -2999,6 +3007,35 @@ function test_flags() { bfs_diff scratch -flags -offline,nohidden } +function test_files0_from_file() { + invoke_bfs basic -fprint0 scratch/files0.in + bfs_diff -files0-from scratch/files0.in +} + +function test_files0_from_stdin() { + invoke_bfs basic -print0 | bfs_diff -files0-from - +} + +function test_files0_from_none() { + ! printf "" | quiet invoke_bfs -files0-from - +} + +function test_files0_from_empty() { + ! printf "\0" | quiet invoke_bfs -files0-from - +} + +function test_files0_from_nowhere() { + ! quiet invoke_bfs -files0-from +} + +function test_files0_from_nothing() { + ! quiet invoke_bfs -files0-from basic/nonexistent +} + +function test_files0_from_ok() { + ! printf "basic\0" | quiet invoke_bfs -files0-from - -ok echo {} \; +} + BOL= EOL='\n' diff --git a/tests/test_files0_from_file.out b/tests/test_files0_from_file.out new file mode 100644 index 0000000..203a461 --- /dev/null +++ b/tests/test_files0_from_file.out @@ -0,0 +1,50 @@ +basic +basic/a +basic/a +basic/b +basic/b +basic/c +basic/c +basic/e +basic/e +basic/g +basic/g +basic/i +basic/i +basic/j +basic/j +basic/k +basic/k +basic/l +basic/l +basic/c/d +basic/c/d +basic/c/d +basic/e/f +basic/e/f +basic/e/f +basic/g/h +basic/g/h +basic/g/h +basic/j/foo +basic/j/foo +basic/j/foo +basic/k/foo +basic/k/foo +basic/k/foo +basic/l/foo +basic/l/foo +basic/l/foo +basic/k/foo/bar +basic/k/foo/bar +basic/k/foo/bar +basic/k/foo/bar +basic/l/foo/bar +basic/l/foo/bar +basic/l/foo/bar +basic/l/foo/bar +basic/l/foo/bar/baz +basic/l/foo/bar/baz +basic/l/foo/bar/baz +basic/l/foo/bar/baz +basic/l/foo/bar/baz diff --git a/tests/test_files0_from_stdin.out b/tests/test_files0_from_stdin.out new file mode 100644 index 0000000..203a461 --- /dev/null +++ b/tests/test_files0_from_stdin.out @@ -0,0 +1,50 @@ +basic +basic/a +basic/a +basic/b +basic/b +basic/c +basic/c +basic/e +basic/e +basic/g +basic/g +basic/i +basic/i +basic/j +basic/j +basic/k +basic/k +basic/l +basic/l +basic/c/d +basic/c/d +basic/c/d +basic/e/f +basic/e/f +basic/e/f +basic/g/h +basic/g/h +basic/g/h +basic/j/foo +basic/j/foo +basic/j/foo +basic/k/foo +basic/k/foo +basic/k/foo +basic/l/foo +basic/l/foo +basic/l/foo +basic/k/foo/bar +basic/k/foo/bar +basic/k/foo/bar +basic/k/foo/bar +basic/l/foo/bar +basic/l/foo/bar +basic/l/foo/bar +basic/l/foo/bar +basic/l/foo/bar/baz +basic/l/foo/bar/baz +basic/l/foo/bar/baz +basic/l/foo/bar/baz +basic/l/foo/bar/baz -- cgit v1.2.3