summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2017-04-15 19:44:09 -0400
committerTavian Barnes <tavianator@tavianator.com>2017-04-15 19:44:09 -0400
commit78d52a7c0e1eef84a61bdb847d6aa83b6dcdccb2 (patch)
treedd3060c6eed2c593160f87612b52a5ce19dd9a9c
parent5212c26ae12f01ec5fd66f8a3fcb41fb03c43e98 (diff)
downloadbfs-78d52a7c0e1eef84a61bdb847d6aa83b6dcdccb2.tar.xz
Implement -exec/-execdir ... +
-rw-r--r--Makefile2
-rw-r--r--bfs.h19
-rw-r--r--eval.c204
-rw-r--r--exec.c456
-rw-r--r--exec.h103
-rw-r--r--parse.c35
-rwxr-xr-xtests.sh10
-rwxr-xr-xtests/sort-args.sh4
-rw-r--r--tests/test_exec_plus.out1
-rw-r--r--tests/test_execdir_plus.out11
10 files changed, 618 insertions, 227 deletions
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 <regex.h>
#include <stdbool.h>
@@ -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 <string.h>
#include <sys/resource.h>
#include <sys/stat.h>
-#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
@@ -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 <tavianator@tavianator.com> *
+ * *
+ * 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 <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+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 <tavianator@tavianator.com> *
+ * *
+ * 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