From 78d52a7c0e1eef84a61bdb847d6aa83b6dcdccb2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 15 Apr 2017 19:44:09 -0400 Subject: Implement -exec/-execdir ... + --- Makefile | 2 +- bfs.h | 19 +- eval.c | 204 ++------------------ exec.c | 456 ++++++++++++++++++++++++++++++++++++++++++++ exec.h | 103 ++++++++++ parse.c | 35 ++-- tests.sh | 10 + tests/sort-args.sh | 4 + tests/test_exec_plus.out | 1 + tests/test_execdir_plus.out | 11 ++ 10 files changed, 618 insertions(+), 227 deletions(-) create mode 100644 exec.c create mode 100644 exec.h create mode 100755 tests/sort-args.sh create mode 100644 tests/test_exec_plus.out create mode 100644 tests/test_execdir_plus.out diff --git a/Makefile b/Makefile index e193b19..50148f5 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ ALL_LDFLAGS = $(ALL_CFLAGS) $(LDFLAGS) all: bfs -bfs: bftw.o color.o dstring.o eval.o main.o parse.o printf.o typo.o util.o +bfs: bftw.o color.o dstring.o eval.o exec.o main.o parse.o printf.o typo.o util.o $(CC) $(ALL_LDFLAGS) $^ -o $@ release: CFLAGS := -O3 -flto $(WFLAGS) -DNDEBUG diff --git a/bfs.h b/bfs.h index e3f4263..53c8b1e 100644 --- a/bfs.h +++ b/bfs.h @@ -13,6 +13,7 @@ #define BFS_H #include "color.h" +#include "exec.h" #include "printf.h" #include #include @@ -179,18 +180,6 @@ enum size_unit { SIZE_PB, }; -/** - * Flags for the -exec actions. - */ -enum exec_flags { - /** 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; @@ -247,12 +236,12 @@ struct expr { /** File to output to. */ CFILE *cfile; - /** Optional -exec flags. */ - enum exec_flags exec_flags; - /** Optional compiled regex. */ regex_t *regex; + /** Optional exec command. */ + struct bfs_exec *execbuf; + /** Optional printf command. */ struct bfs_printf *printf; diff --git a/eval.c b/eval.c index 8763a6f..929eeba 100644 --- a/eval.c +++ b/eval.c @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -278,206 +277,29 @@ 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->exec_flags & EXEC_CHDIR)) { - return ftwbuf->path; +/** Finish any pending -exec ... + operations. */ +static int eval_exec_finish(const struct expr *expr) { + int ret = 0; + if (expr->execbuf && bfs_exec_finish(expr->execbuf) != 0) { + ret = -1; } - - const char *name = ftwbuf->path + ftwbuf->nameoff; - - if (name[0] == '/') { - // Must be a root path ("/", "//", etc.) - return name; - } - - // For compatibility with GNU find, use './name' instead of just 'name' - char *path = dstralloc(2 + strlen(name)); - if (!path) { - return NULL; + if (expr->lhs && eval_exec_finish(expr->lhs) != 0) { + ret = -1; } - - if (dstrcat(&path, "./") != 0) { - goto err; - } - if (dstrcat(&path, name) != 0) { - 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 && path != ftwbuf->path + ftwbuf->nameoff) { - 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) { - return NULL; - } - - char *last = arg; - do { - if (dstrncat(&ret, last, match - last) != 0) { - goto err; - } - if (dstrcat(&ret, path) != 0) { - goto err; - } - - last = match + 2; - match = strstr(last, "{}"); - } while (match); - - if (dstrcat(&ret, last) != 0) { - goto err; + if (expr->rhs && eval_exec_finish(expr->rhs) != 0) { + ret = -1; } - return ret; - -err: - dstrfree(ret); - return NULL; -} - -static void exec_free_argv(size_t argc, char **argv, char **template) { - for (size_t i = 0; i < argc; ++i) { - if (argv[i] != template[i]) { - dstrfree(argv[i]); - } - } - free(argv); -} - -static char **exec_format_argv(size_t argc, char **template, 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(template[i], path); - if (!argv[i]) { - exec_free_argv(i, argv, template); - 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; - } - - size_t nameoff = ftwbuf->nameoff; - - if (nameoff == 0 && ftwbuf->path[nameoff] != '/') { - // The path is something like "foo", so we're already in the - // right directory - return; - } - - char *path = strdup(ftwbuf->path); - if (!path) { - perror("strdup()"); - _Exit(EXIT_FAILURE); - } - - if (nameoff > 0) { - path[nameoff] = '\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) { + bool ret = bfs_exec(expr->execbuf, state->ftwbuf) == 0; + if (errno != 0) { eval_error(state); - goto out; } - - size_t argc = expr->argc - 2; - char **template = expr->argv + 1; - char **argv = exec_format_argv(argc, template, path); - if (!argv) { - eval_error(state); - goto out_path; - } - - if (expr->exec_flags & 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) { - eval_error(state); - goto out_argv; - } else if (pid > 0) { - int status; - if (waitpid(pid, &status, 0) < 0) { - eval_error(state); - goto out_argv; - } - - ret = WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS; - } else { - if (expr->exec_flags & EXEC_CHDIR) { - exec_chdir(ftwbuf); - } - - execvp(argv[0], argv); - perror("execvp()"); - _Exit(EXIT_FAILURE); - } - -out_argv: - exec_free_argv(argc, argv, template); -out_path: - exec_free_path(path, ftwbuf); -out: return ret; } @@ -1246,6 +1068,10 @@ int eval_cmdline(const struct cmdline *cmdline) { } } + if (eval_exec_finish(cmdline->expr) != 0) { + args.ret = -1; + } + if (cmdline->debug & DEBUG_RATES) { dump_cmdline(cmdline, true); } diff --git a/exec.c b/exec.c new file mode 100644 index 0000000..ddc52ad --- /dev/null +++ b/exec.c @@ -0,0 +1,456 @@ +/********************************************************************* + * bfs * + * Copyright (C) 2017 Tavian Barnes * + * * + * This program is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +#include "exec.h" +#include "bftw.h" +#include "color.h" +#include "dstring.h" +#include "util.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct bfs_exec *parse_bfs_exec(char **argv, enum bfs_exec_flags flags, CFILE *cerr) { + struct bfs_exec *execbuf = malloc(sizeof(*execbuf)); + if (!execbuf) { + perror("malloc()"); + goto fail; + } + + execbuf->flags = flags; + execbuf->placeholder = 0; + execbuf->argv = NULL; + execbuf->argc = 0; + execbuf->wd_fd = -1; + execbuf->wd_path = NULL; + execbuf->wd_len = 0; + execbuf->ret = 0; + + size_t i; + const char *arg; + for (i = 1, arg = argv[i]; arg; arg = argv[++i]) { + if (strcmp(arg, ";") == 0) { + break; + } else if (strcmp(arg, "+") == 0) { + execbuf->flags |= BFS_EXEC_MULTI; + break; + } + } + if (!arg) { + cfprintf(cerr, "%{er}error: %s: Expected ';' or '+'.%{rs}\n", argv[0]); + goto fail; + } + + if ((execbuf->flags & BFS_EXEC_CONFIRM) && (execbuf->flags & BFS_EXEC_MULTI)) { + cfprintf(cerr, "%{er}error: %s ... + is not supported.%{rs}\n", argv[0]); + goto fail; + } + + execbuf->tmpl_argv = argv + 1; + execbuf->tmpl_argc = i - 1; + + if (execbuf->flags & BFS_EXEC_MULTI) { + long arg_max = sysconf(_SC_ARG_MAX); + if (arg_max < 0) { + execbuf->arg_max = _POSIX_ARG_MAX; + } else { + execbuf->arg_max = arg_max; + } + } else { + execbuf->arg_max = execbuf->tmpl_argc; + } + + execbuf->argv = malloc((execbuf->arg_max + 1)*sizeof(*execbuf->argv)); + if (!execbuf->argv) { + perror("malloc()"); + goto fail; + } + + if (execbuf->flags & BFS_EXEC_MULTI) { + for (i = 0; i < execbuf->tmpl_argc; ++i) { + if (strstr(execbuf->tmpl_argv[i], "{}")) { + execbuf->placeholder = i; + break; + } + } + if (i == execbuf->tmpl_argc) { + cfprintf(cerr, "%{er}error: %s ... +: Expected '{}'.%{rs}\n", argv[0]); + goto fail; + } + for (++i; i < execbuf->tmpl_argc; ++i) { + if (strstr(execbuf->tmpl_argv[i], "{}")) { + cfprintf(cerr, "%{er}error: %s ... +: Only one '{}' is supported.%{rs}\n", argv[0]); + goto fail; + } + } + + for (i = 0; i < execbuf->placeholder; ++i) { + execbuf->argv[i] = execbuf->tmpl_argv[i]; + } + execbuf->argc = i; + } + + return execbuf; + +fail: + free_bfs_exec(execbuf); + return NULL; +} + +/** Format the current path for use as a command line argument. */ +static const char *bfs_exec_format_path(const struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { + if (!(execbuf->flags & BFS_EXEC_CHDIR)) { + return ftwbuf->path; + } + + const char *name = ftwbuf->path + ftwbuf->nameoff; + + if (name[0] == '/') { + // Must be a root path ("/", "//", etc.) + return name; + } + + // For compatibility with GNU find, use './name' instead of just 'name' + char *path = dstralloc(2 + strlen(name)); + if (!path) { + return NULL; + } + + if (dstrcat(&path, "./") != 0) { + goto err; + } + if (dstrcat(&path, name) != 0) { + goto err; + } + + return path; + +err: + dstrfree(path); + return NULL; +} + +/** Free a formatted path. */ +static void bfs_exec_free_path(const char *path, const struct BFTW *ftwbuf) { + if (path != ftwbuf->path && path != ftwbuf->path + ftwbuf->nameoff) { + dstrfree((char *)path); + } +} + +/** Format an argument, expanding "{}" to the current path. */ +static char *bfs_exec_format_arg(char *arg, const char *path) { + char *match = strstr(arg, "{}"); + if (!match) { + return arg; + } + + char *ret = dstralloc(0); + if (!ret) { + return NULL; + } + + char *last = arg; + do { + if (dstrncat(&ret, last, match - last) != 0) { + goto err; + } + if (dstrcat(&ret, path) != 0) { + goto err; + } + + last = match + 2; + match = strstr(last, "{}"); + } while (match); + + if (dstrcat(&ret, last) != 0) { + goto err; + } + + return ret; + +err: + dstrfree(ret); + return NULL; +} + +/** Free a formatted argument. */ +static void bfs_exec_free_arg(char *arg, const char *tmpl) { + if (arg != tmpl) { + dstrfree(arg); + } +} + +/** Open a file to use as the working directory. */ +static int bfs_exec_openwd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { + assert(execbuf->wd_fd < 0); + assert(!execbuf->wd_path); + + if (ftwbuf->at_fd != AT_FDCWD) { + execbuf->wd_fd = ftwbuf->at_fd; + if (!(execbuf->flags & BFS_EXEC_MULTI)) { + return 0; + } + + execbuf->wd_fd = dup_cloexec(execbuf->wd_fd); + if (execbuf->wd_fd < 0) { + return -1; + } + } + + execbuf->wd_len = ftwbuf->nameoff; + if (execbuf->wd_len == 0) { + if (ftwbuf->path[0] == '/') { + ++execbuf->wd_len; + } else { + // The path is something like "foo", so we're already in the right directory + return 0; + } + } + + execbuf->wd_path = strndup(ftwbuf->path, execbuf->wd_len); + if (!execbuf->wd_path) { + return -1; + } + + if (execbuf->wd_fd < 0) { + int flags = O_RDONLY | O_CLOEXEC; +#ifdef O_DIRECTORY + flags |= O_DIRECTORY; +#endif + execbuf->wd_fd = open(execbuf->wd_path, flags); + } + + if (execbuf->wd_fd < 0) { + return -1; + } + + return 0; +} + +/** Close the working directory. */ +static int bfs_exec_closewd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { + int ret = 0; + + if (execbuf->wd_fd >= 0) { + if (ftwbuf && execbuf->wd_fd != ftwbuf->at_fd) { + ret = close(execbuf->wd_fd); + } + execbuf->wd_fd = -1; + } + + if (execbuf->wd_path) { + free(execbuf->wd_path); + execbuf->wd_path = NULL; + execbuf->wd_len = 0; + } + + return ret; +} + +/** Actually spawn the process. */ +static int bfs_exec_spawn(const struct bfs_exec *execbuf) { + if (execbuf->flags & BFS_EXEC_CONFIRM) { + for (size_t i = 0; i < execbuf->argc; ++i) { + fprintf(stderr, "%s ", execbuf->argv[i]); + } + fprintf(stderr, "? "); + fflush(stderr); + + int c = getchar(); + bool exec = c == 'y' || c == 'Y'; + while (c != '\n' && c != EOF) { + c = getchar(); + } + if (!exec) { + errno = 0; + return -1; + } + } + + pid_t pid = fork(); + + if (pid < 0) { + return -1; + } else if (pid > 0) { + int status; + if (waitpid(pid, &status, 0) < 0) { + return -1; + } + + errno = 0; + if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) { + return 0; + } else { + return -1; + } + } else { + if (execbuf->wd_fd >= 0) { + if (fchdir(execbuf->wd_fd) != 0) { + perror("fchdir()"); + goto fail; + } + } + + execvp(execbuf->argv[0], execbuf->argv); + perror("execvp()"); + } + +fail: + _Exit(EXIT_FAILURE); +} + +/** exec() a command for a single file. */ +static int bfs_exec_single(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { + int ret = -1, error = 0; + + const char *path = bfs_exec_format_path(execbuf, ftwbuf); + if (!path) { + goto out; + } + + size_t i; + for (i = 0; i < execbuf->tmpl_argc; ++i) { + execbuf->argv[i] = bfs_exec_format_arg(execbuf->tmpl_argv[i], path); + if (!execbuf->argv[i]) { + goto out_free; + } + } + execbuf->argv[i] = NULL; + execbuf->argc = i; + + if (execbuf->flags & BFS_EXEC_CHDIR) { + if (bfs_exec_openwd(execbuf, ftwbuf) != 0) { + goto out_free; + } + } + + ret = bfs_exec_spawn(execbuf); + +out_free: + error = errno; + + bfs_exec_closewd(execbuf, ftwbuf); + + for (size_t j = 0; j < i; ++j) { + bfs_exec_free_arg(execbuf->argv[j], execbuf->tmpl_argv[j]); + } + + bfs_exec_free_path(path, ftwbuf); + + errno = error; + +out: + return ret; +} + +/** Execute the pending command from a multi-execbuf. */ +static void bfs_exec_flush(struct bfs_exec *execbuf) { + size_t last_path = execbuf->argc; + if (last_path > execbuf->placeholder) { + for (size_t i = execbuf->placeholder + 1; i < execbuf->tmpl_argc; ++i, ++execbuf->argc) { + execbuf->argv[execbuf->argc] = execbuf->tmpl_argv[i]; + } + execbuf->argv[execbuf->argc] = NULL; + + if (bfs_exec_spawn(execbuf) != 0) { + execbuf->ret = -1; + } + } + + bfs_exec_closewd(execbuf, NULL); + + for (size_t i = execbuf->placeholder; i < last_path; ++i) { + bfs_exec_free_arg(execbuf->argv[i], execbuf->tmpl_argv[execbuf->placeholder]); + } + execbuf->argc = execbuf->placeholder; +} + +/** Check if a flush is needed before a new file is processed. */ +static bool bfs_exec_needs_flush(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { + if (execbuf->flags & BFS_EXEC_CHDIR) { + if (ftwbuf->nameoff > execbuf->wd_len) { + return true; + } + if (execbuf->wd_path && strncmp(ftwbuf->path, execbuf->wd_path, execbuf->wd_len) != 0) { + return true; + } + } + + size_t tail = execbuf->tmpl_argc - execbuf->placeholder - 1; + if (execbuf->argc + tail >= execbuf->arg_max) { + return true; + } + + return false; +} + +/** Push a new file to a multi-execbuf. */ +static void bfs_exec_multi(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { + if (bfs_exec_needs_flush(execbuf, ftwbuf)) { + bfs_exec_flush(execbuf); + } + + if ((execbuf->flags & BFS_EXEC_CHDIR) && execbuf->wd_fd < 0) { + if (bfs_exec_openwd(execbuf, ftwbuf) != 0) { + execbuf->ret = -1; + return; + } + } + + const char *path = bfs_exec_format_path(execbuf, ftwbuf); + if (!path) { + execbuf->ret = -1; + return; + } + + char *arg = bfs_exec_format_arg(execbuf->tmpl_argv[execbuf->placeholder], path); + if (!arg) { + execbuf->ret = -1; + goto out_path; + } + + execbuf->argv[execbuf->argc++] = arg; + +out_path: + bfs_exec_free_path(path, ftwbuf); +} + +int bfs_exec(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { + if (execbuf->flags & BFS_EXEC_MULTI) { + bfs_exec_multi(execbuf, ftwbuf); + // -exec ... + never returns false + return 0; + } else { + return bfs_exec_single(execbuf, ftwbuf); + } +} + +int bfs_exec_finish(struct bfs_exec *execbuf) { + if (execbuf->flags & BFS_EXEC_MULTI) { + bfs_exec_flush(execbuf); + } + return execbuf->ret; +} + +void free_bfs_exec(struct bfs_exec *execbuf) { + if (execbuf) { + bfs_exec_closewd(execbuf, NULL); + free(execbuf->argv); + free(execbuf); + } +} diff --git a/exec.h b/exec.h new file mode 100644 index 0000000..ab548af --- /dev/null +++ b/exec.h @@ -0,0 +1,103 @@ +/********************************************************************* + * bfs * + * Copyright (C) 2017 Tavian Barnes * + * * + * This program is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +#ifndef BFS_EXEC_H +#define BFS_EXEC_H + +#include "bftw.h" +#include "color.h" + +/** + * Flags for the -exec actions. + */ +enum bfs_exec_flags { + /** Prompt the user before executing (-ok, -okdir). */ + BFS_EXEC_CONFIRM = 1 << 0, + /** Run the command in the file's parent directory (-execdir, -okdir). */ + BFS_EXEC_CHDIR = 1 << 1, + /** Pass multiple files at once to the command (-exec ... {} +). */ + BFS_EXEC_MULTI = 1 << 2, +}; + +/** + * Buffer for a command line to be executed. + */ +struct bfs_exec { + /** Flags for this exec buffer. */ + enum bfs_exec_flags flags; + + /** Command line template. */ + char **tmpl_argv; + /** Command line template size. */ + size_t tmpl_argc; + + /** For BFS_EXEC_MULTI, the index of the placeholder argument. */ + size_t placeholder; + + /** The built command line. */ + char **argv; + /** Number of command line arguments. */ + size_t argc; + /** Maximum argc before E2BIG. */ + size_t arg_max; + + /** A file descriptor for the working directory, for BFS_EXEC_CHDIR. */ + int wd_fd; + /** The path to the working directory, for BFS_EXEC_CHDIR. */ + char *wd_path; + /** Length of the working directory path. */ + size_t wd_len; + + /** The ultimate return value for bfs_exec_finish(). */ + int ret; +}; + +/** + * Parse an exec action. + * + * @param argv + * The (bfs) command line argument to parse. + * @param flags + * Any flags for this exec action. + * @param cerr + * For error messages. + * @return The parsed exec action, or NULL on failure. + */ +struct bfs_exec *parse_bfs_exec(char **argv, enum bfs_exec_flags flags, CFILE *cerr); + +/** + * Execute the command for a file. + * + * @param execbuf + * The parsed exec action. + * @param ftwbuf + * The bftw() data for the current file. + * @return 0 if the command succeeded, -1 if it failed. If the command could + * be executed, -1 is returned, and errno will be non-zero. For + * BFS_EXEC_MULTI, errors will not be reported until bfs_exec_finish(). + */ +int bfs_exec(struct bfs_exec *execbuf, const struct BFTW *ftwbuf); + +/** + * Finish executing any commands. + * + * @param execbuf + * The parsed exec action. + * @return 0 on success, -1 if any errors were encountered. + */ +int bfs_exec_finish(struct bfs_exec *execbuf); + +/** + * Free a parsed exec action. + */ +void free_bfs_exec(struct bfs_exec *execbuf); + +#endif // BFS_EXEC_H diff --git a/parse.c b/parse.c index 31ee7fe..193278a 100644 --- a/parse.c +++ b/parse.c @@ -10,6 +10,7 @@ *********************************************************************/ #include "bfs.h" +#include "exec.h" #include "printf.h" #include "typo.h" #include "util.h" @@ -81,6 +82,7 @@ static void free_expr(struct expr *expr) { } free_bfs_printf(expr->printf); + free_bfs_exec(expr->execbuf); free_expr(expr->lhs); free_expr(expr->rhs); @@ -112,6 +114,7 @@ static struct expr *new_expr(eval_fn *eval, bool pure, size_t argc, char **argv) expr->argv = argv; expr->cfile = NULL; expr->regex = NULL; + expr->execbuf = NULL; expr->printf = NULL; return expr; } @@ -896,33 +899,21 @@ static struct expr *parse_empty(struct parser_state *state, int arg1, int arg2) } /** - * Parse -exec[dir]/-ok[dir]. + * Parse -exec(dir)?/-ok(dir)?. */ static struct expr *parse_exec(struct parser_state *state, int flags, int arg2) { - size_t i = 1; - const char *arg; - while ((arg = state->argv[i++])) { - if (strcmp(arg, ";") == 0) { - break; - } else if (strcmp(arg, "+") == 0) { - flags |= EXEC_MULTI; - break; - } - } - - if (!arg) { - cfprintf(state->cmdline->cerr, "%{er}error: %s: Expected ';' or '+'.%{rs}\n", state->argv[0]); + struct bfs_exec *execbuf = parse_bfs_exec(state->argv, flags, state->cmdline->cerr); + if (!execbuf) { return NULL; } - if (flags & EXEC_MULTI) { - cfprintf(state->cmdline->cerr, "%{er}error: %s ... {} + is not supported yet.%{rs}\n", state->argv[0]); - return NULL; + if ((execbuf->flags & BFS_EXEC_CHDIR) && (execbuf->flags & BFS_EXEC_MULTI)) { + ++state->cmdline->nopen_files; } - struct expr *expr = parse_action(state, eval_exec, i); + struct expr *expr = parse_action(state, eval_exec, execbuf->tmpl_argc + 2); if (expr) { - expr->exec_flags = flags; + expr->execbuf = execbuf; } return expr; } @@ -1970,7 +1961,7 @@ static const struct table_entry parse_table[] = { {"depth", false, parse_depth_n}, {"empty", false, parse_empty}, {"exec", false, parse_exec, 0}, - {"execdir", false, parse_exec, EXEC_CHDIR}, + {"execdir", false, parse_exec, BFS_EXEC_CHDIR}, {"executable", false, parse_access, X_OK}, {"f", false, parse_f}, {"false", false, parse_const, false}, @@ -2011,8 +2002,8 @@ static const struct table_entry parse_table[] = { {"nouser", false, parse_nouser}, {"nowarn", false, parse_warn, false}, {"o"}, - {"ok", false, parse_exec, EXEC_CONFIRM}, - {"okdir", false, parse_exec, EXEC_CONFIRM | EXEC_CHDIR}, + {"ok", false, parse_exec, BFS_EXEC_CONFIRM}, + {"okdir", false, parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR}, {"or"}, {"path", false, parse_path, false}, {"perm", false, parse_perm}, diff --git a/tests.sh b/tests.sh index ca27ff8..0c83beb 100755 --- a/tests.sh +++ b/tests.sh @@ -142,6 +142,7 @@ posix_tests=( test_size_plus test_size_bytes test_exec + test_exec_plus test_flag_comma test_perm_222 test_perm_222_minus @@ -303,6 +304,7 @@ bfs_tests=( test_perm_symbolic_missing_action test_perm_leading_plus_symbolic test_perm_octal_plus + test_execdir_plus test_hidden test_nohidden test_path_flag_expr @@ -648,6 +650,10 @@ function test_exec() { bfs_diff basic -exec echo '{}' ';' } +function test_exec_plus() { + bfs_diff basic -exec "$TESTS/sort-args.sh" '{}' + +} + function test_exec_substring() { bfs_diff basic -exec echo '-{}-' ';' } @@ -656,6 +662,10 @@ function test_execdir() { bfs_diff basic -execdir echo '{}' ';' } +function test_execdir_plus() { + bfs_diff basic -execdir "$TESTS/sort-args.sh" '{}' + +} + function test_execdir_substring() { bfs_diff basic -execdir echo '-{}-' ';' } diff --git a/tests/sort-args.sh b/tests/sort-args.sh new file mode 100755 index 0000000..5214598 --- /dev/null +++ b/tests/sort-args.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +args=($({ for arg; do echo "$arg"; done } | sort)) +echo "${args[@]}" diff --git a/tests/test_exec_plus.out b/tests/test_exec_plus.out new file mode 100644 index 0000000..f6b423b --- /dev/null +++ b/tests/test_exec_plus.out @@ -0,0 +1 @@ +basic basic/a basic/b basic/c basic/c/d basic/e basic/e/f basic/g basic/g/h basic/i basic/j basic/j/foo basic/k basic/k/foo basic/k/foo/bar basic/l basic/l/foo basic/l/foo/bar basic/l/foo/bar/baz diff --git a/tests/test_execdir_plus.out b/tests/test_execdir_plus.out new file mode 100644 index 0000000..5bbb758 --- /dev/null +++ b/tests/test_execdir_plus.out @@ -0,0 +1,11 @@ +./bar +./bar +./basic +./baz +./d +./f +./foo +./foo +./foo +./h +./a ./b ./c ./e ./g ./i ./j ./k ./l -- cgit v1.2.3