summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bfs.h18
-rw-r--r--eval.c209
-rw-r--r--parse.c56
-rwxr-xr-xtests.sh22
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 <assert.h>
#include <dirent.h>
#include <errno.h>
@@ -21,6 +22,7 @@
#include <string.h>
#include <sys/resource.h>
#include <sys/stat.h>
+#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
@@ -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;
}
@@ -571,6 +571,40 @@ static struct expr *parse_depth(struct parser_state *state, int *depth) {
}
/**
+ * 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.
*/
static struct expr *parse_group(struct parser_state *state) {
@@ -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=$?