From 8ea44cab76a20f1c5ac473b1651611bea0ee9e6b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 22 May 2016 13:17:02 -0400 Subject: Implement -{exec,ok}{,dir}. --- bfs.h | 18 +++++- eval.c | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- parse.c | 56 +++++++++++++++-- tests.sh | 22 ++++++- 4 files changed, 297 insertions(+), 8 deletions(-) diff --git a/bfs.h b/bfs.h index d5cc343..42bbdd4 100644 --- a/bfs.h +++ b/bfs.h @@ -143,6 +143,18 @@ enum sizeunit { SIZE_GB, }; +/** + * Flags for the -exec actions. + */ +enum execflags { + /** Prompt the user before executing (-ok, -okdir). */ + EXEC_CONFIRM = 1 << 0, + /** Run the command in the file's parent directory (-execdir, -okdir). */ + EXEC_CHDIR = 1 << 1, + /** Pass multiple files at once to the command (-exec ... {} +). */ + EXEC_MULTI = 1 << 2, +}; + struct expr { /** The function that evaluates this expression. */ eval_fn *eval; @@ -161,7 +173,7 @@ struct expr { size_t nargs; /** The optional comparison flag. */ - enum cmpflag cmp; + enum cmpflag cmpflag; /** The optional reference time. */ struct timespec reftime; @@ -178,6 +190,9 @@ struct expr { /** Optional inode number for a target file. */ ino_t ino; + /** Optional -exec flags. */ + enum execflags execflags; + /** Optional integer data for this expression. */ long long idata; @@ -227,6 +242,7 @@ bool eval_name(const struct expr *expr, struct eval_state *state); bool eval_path(const struct expr *expr, struct eval_state *state); bool eval_delete(const struct expr *expr, struct eval_state *state); +bool eval_exec(const struct expr *expr, struct eval_state *state); bool eval_nohidden(const struct expr *expr, struct eval_state *state); bool eval_print(const struct expr *expr, struct eval_state *state); bool eval_print0(const struct expr *expr, struct eval_state *state); diff --git a/eval.c b/eval.c index 1eb3c86..41afedb 100644 --- a/eval.c +++ b/eval.c @@ -11,6 +11,7 @@ #include "bfs.h" #include "bftw.h" +#include "dstring.h" #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include @@ -76,7 +78,7 @@ static time_t timespec_diff(const struct timespec *lhs, const struct timespec *r * Perform a comparison. */ static bool do_cmp(const struct expr *expr, long long n) { - switch (expr->cmp) { + switch (expr->cmpflag) { case CMP_EXACT: return n == expr->idata; case CMP_LESS: @@ -230,6 +232,211 @@ bool eval_delete(const struct expr *expr, struct eval_state *state) { return true; } +static const char *exec_format_path(const struct expr *expr, const struct BFTW *ftwbuf) { + if (!(expr->execflags & EXEC_CHDIR)) { + return ftwbuf->path; + } + + // For compatibility with GNU find, use './name' instead of just 'name' + const char *name = ftwbuf->path + ftwbuf->nameoff; + + char *path = dstralloc(2 + strlen(name)); + if (!path) { + perror("dstralloc()"); + return NULL; + } + + if (dstrcat(&path, "./") != 0) { + perror("dstrcat()"); + goto err; + } + if (dstrcat(&path, name) != 0) { + perror("dstrcat()"); + goto err; + } + + return path; + +err: + dstrfree(path); + return NULL; +} + +static void exec_free_path(const char *path, const struct BFTW *ftwbuf) { + if (path != ftwbuf->path) { + dstrfree((char *)path); + } +} + +static char *exec_format_arg(char *arg, const char *path) { + char *match = strstr(arg, "{}"); + if (!match) { + return arg; + } + + char *ret = dstralloc(0); + if (!ret) { + perror("dstralloc()"); + return NULL; + } + + char *last = arg; + do { + if (dstrncat(&ret, last, match - last) != 0) { + perror("dstrncat()"); + goto err; + } + if (dstrcat(&ret, path) != 0) { + perror("dstrcat()"); + goto err; + } + + last = match + 2; + match = strstr(last, "{}"); + } while (match); + + if (dstrcat(&ret, last) != 0) { + perror("dstrcat()"); + goto err; + } + + return ret; + +err: + dstrfree(ret); + return NULL; +} + +static void exec_free_argv(size_t argc, char **argv, char **args) { + for (size_t i = 0; i < argc; ++i) { + if (argv[i] != args[i]) { + dstrfree(argv[i]); + } + } + free(argv); +} + +static char **exec_format_argv(size_t argc, char **args, const char *path) { + char **argv = malloc((argc + 1)*sizeof(char *)); + if (!argv) { + return NULL; + } + + for (size_t i = 0; i < argc; ++i) { + argv[i] = exec_format_arg(args[i], path); + if (!argv[i]) { + exec_free_argv(i, argv, args); + return NULL; + } + } + argv[argc] = NULL; + + return argv; +} + +static void exec_chdir(const struct BFTW *ftwbuf) { + if (ftwbuf->at_fd != AT_FDCWD) { + if (fchdir(ftwbuf->at_fd) != 0) { + perror("fchdir()"); + _Exit(EXIT_FAILURE); + } + return; + } + + char *path = strdup(ftwbuf->path); + if (!path) { + perror("strdup()"); + _Exit(EXIT_FAILURE); + } + + // Skip trailing slashes + char *end = path + strlen(path); + while (end > path && end[-1] == '/') { + --end; + } + + // Remove the last component + while (end > path && end[-1] != '/') { + --end; + } + if (end > path) { + *end = '\0'; + } + + if (chdir(path) != 0) { + perror("chdir()"); + _Exit(EXIT_FAILURE); + } +} + +/** + * -exec[dir]/-ok[dir] actions. + */ +bool eval_exec(const struct expr *expr, struct eval_state *state) { + bool ret = false; + + const struct BFTW *ftwbuf = state->ftwbuf; + + const char *path = exec_format_path(expr, ftwbuf); + if (!path) { + goto out; + } + + size_t argc = expr->nargs - 2; + char **args = expr->args + 1; + char **argv = exec_format_argv(argc, args, path); + if (!argv) { + goto out_path; + } + + if (expr->execflags & EXEC_CONFIRM) { + for (size_t i = 0; i < argc; ++i) { + fprintf(stderr, "%s ", argv[i]); + } + fprintf(stderr, "? "); + fflush(stderr); + + int c = getchar(); + bool exec = c == 'y' || c == 'Y'; + while (c != '\n' && c != EOF) { + c = getchar(); + } + if (!exec) { + goto out_argv; + } + } + + pid_t pid = fork(); + + if (pid < 0) { + perror("fork()"); + goto out_argv; + } else if (pid > 0) { + int status; + if (waitpid(pid, &status, 0) < 0) { + perror("waitpid()"); + return false; + } + + ret = WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS; + } else { + if (expr->execflags & EXEC_CHDIR) { + exec_chdir(ftwbuf); + } + + execvp(argv[0], argv); + perror("execvp()"); + _Exit(EXIT_FAILURE); + } + +out_argv: + exec_free_argv(argc, argv, args); +out_path: + exec_free_path(path, ftwbuf); +out: + return ret; +} + /** * -empty test. */ diff --git a/parse.c b/parse.c index 53d42eb..d51c43e 100644 --- a/parse.c +++ b/parse.c @@ -281,15 +281,15 @@ bad: static const char *parse_icmp(const struct parser_state *state, const char *str, struct expr *expr, enum intflags flags) { switch (str[0]) { case '-': - expr->cmp = CMP_LESS; + expr->cmpflag = CMP_LESS; ++str; break; case '+': - expr->cmp = CMP_GREATER; + expr->cmpflag = CMP_GREATER; ++str; break; default: - expr->cmp = CMP_EXACT; + expr->cmpflag = CMP_EXACT; break; } @@ -570,6 +570,40 @@ static struct expr *parse_depth(struct parser_state *state, int *depth) { return parse_unary_option(state); } +/** + * Parse -exec[dir]/-ok[dir]. + */ +static struct expr *parse_exec(struct parser_state *state, enum execflags flags) { + size_t i = 1; + const char *arg; + while ((arg = state->args[i++])) { + if (strcmp(arg, ";") == 0) { + break; + } else if (strcmp(arg, "+") == 0) { + flags |= EXEC_MULTI; + break; + } + } + + if (!arg) { + pretty_error(state->cmdline->stderr_colors, + "error: %s: Expected ';' or '+'.\n", state->args[0]); + return NULL; + } + + if (flags & EXEC_MULTI) { + pretty_error(state->cmdline->stderr_colors, + "error: %s ... {} + is not supported yet\n", state->args[0]); + return NULL; + } + + struct expr *expr = parse_action(state, eval_exec, i); + if (expr) { + expr->execflags = flags; + } + return expr; +} + /** * Parse -group. */ @@ -599,7 +633,7 @@ static struct expr *parse_group(struct parser_state *state) { goto error; } - expr->cmp = CMP_EXACT; + expr->cmpflag = CMP_EXACT; return expr; error: @@ -640,7 +674,7 @@ static struct expr *parse_user(struct parser_state *state) { goto error; } - expr->cmp = CMP_EXACT; + expr->cmpflag = CMP_EXACT; return expr; error: @@ -1028,6 +1062,10 @@ static struct expr *parse_literal(struct parser_state *state) { case 'e': if (strcmp(arg, "-empty") == 0) { return parse_nullary_test(state, eval_empty); + } else if (strcmp(arg, "-exec") == 0) { + return parse_exec(state, 0); + } else if (strcmp(arg, "-execdir") == 0) { + return parse_exec(state, EXEC_CHDIR); } else if (strcmp(arg, "-executable") == 0) { return parse_access(state, X_OK); } @@ -1115,6 +1153,14 @@ static struct expr *parse_literal(struct parser_state *state) { } break; + case 'o': + if (strcmp(arg, "-ok") == 0) { + return parse_exec(state, EXEC_CONFIRM); + } else if (strcmp(arg, "-okdir") == 0) { + return parse_exec(state, EXEC_CONFIRM | EXEC_CHDIR); + } + break; + case 'p': if (strcmp(arg, "-path") == 0) { return parse_path(state, false); diff --git a/tests.sh b/tests.sh index 006496b..8cf493c 100755 --- a/tests.sh +++ b/tests.sh @@ -283,7 +283,27 @@ function test_0053() { find_diff "$basic" -size 9223372036854775807 } -for i in {1..53}; do +function test_0054() { + find_diff "$basic" -exec echo '{}' ';' +} + +function test_0055() { + find_diff "$basic" -exec echo '-{}-' ';' +} + +function test_0056() { + find_diff "$basic" -execdir pwd ';' +} + +function test_0057() { + find_diff "$basic" -execdir echo '{}' ';' +} + +function test_0058() { + find_diff "$basic" -execdir echo '-{}-' ';' +} + +for i in {1..58}; do test="test_$(printf '%04d' $i)" "$test" "$dir" status=$? -- cgit v1.2.3