summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2021-05-08 11:58:09 -0400
committerTavian Barnes <tavianator@tavianator.com>2021-09-15 13:56:20 -0400
commitb6b7a68190703d30912d2a1c3d8d64e3de81a612 (patch)
treed99e0b9dd0060deed2980b43a99fd2f359f9ad30
parentb2d85ea84c930ebcfefc6449414ed64cd80e2f89 (diff)
downloadbfs-b6b7a68190703d30912d2a1c3d8d64e3de81a612.tar.xz
Implement -files0-from FILE
See https://savannah.gnu.org/bugs/?60383 for the development of the corresponding GNU find feature.
-rw-r--r--bfs.19
-rw-r--r--ctx.c5
-rw-r--r--parse.c93
-rwxr-xr-xtests.sh37
-rw-r--r--tests/test_files0_from_file.out50
-rw-r--r--tests/test_files0_from_stdin.out50
6 files changed, 240 insertions, 4 deletions
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, &copy) != 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;
}
@@ -1233,6 +1252,58 @@ static struct expr *parse_f(struct parser_state *state, int arg1, int arg2) {
}
/**
+ * 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.
*/
static struct expr *parse_flags(struct parser_state *state, int arg1, int arg2) {
@@ -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