From 3c8233869d34713860c48b1230c3ea06b8767b88 Mon Sep 17 00:00:00 2001
From: Tavian Barnes <tavianator@tavianator.com>
Date: Sat, 22 Oct 2016 21:05:47 -0400
Subject: Implement -ignore_readdir_race.

---
 bfs.h                   |  2 ++
 bftw.c                  |  5 ++++-
 eval.c                  | 42 +++++++++++++++++++++++++-----------------
 parse.c                 | 24 +++++++++++++++++++-----
 tests.sh                | 30 ++++++++++++++++++++++--------
 tests/remove-sibling.sh | 10 ++++++++++
 6 files changed, 82 insertions(+), 31 deletions(-)
 create mode 100755 tests/remove-sibling.sh

diff --git a/bfs.h b/bfs.h
index e3090c9..b504f0c 100644
--- a/bfs.h
+++ b/bfs.h
@@ -106,6 +106,8 @@ struct cmdline {
 	int optlevel;
 	/** Debugging flags. */
 	enum debug_flags debug;
+	/** Whether to ignore deletions that race with bfs. */
+	bool ignore_races;
 
 	/** The command line expression. */
 	struct expr *expr;
diff --git a/bftw.c b/bftw.c
index 45d2d27..f48d3f8 100644
--- a/bftw.c
+++ b/bftw.c
@@ -713,9 +713,12 @@ static void bftw_path_trim(struct bftw_state *state) {
  * Record an error.
  */
 static void bftw_set_error(struct bftw_state *state, int error) {
-	state->error = error;
 	state->ftwbuf.error = error;
 	state->ftwbuf.typeflag = BFTW_ERROR;
+
+	if (!(state->flags & BFTW_RECOVER)) {
+		state->error = error;
+	}
 }
 
 /**
diff --git a/eval.c b/eval.c
index 4778b5d..f930bac 100644
--- a/eval.c
+++ b/eval.c
@@ -51,13 +51,24 @@ struct eval_state {
 	struct stat statbuf;
 };
 
+/**
+ * Check if an error should be ignored.
+ */
+static bool eval_should_ignore(const struct eval_state *state, int error) {
+	return state->cmdline->ignore_races
+		&& error == ENOENT
+		&& state->ftwbuf->depth > 0;
+}
+
 /**
  * Report an error that occurs during evaluation.
  */
 static void eval_error(struct eval_state *state) {
-	pretty_error(state->cmdline->stderr_colors,
-	             "'%s': %s\n", state->ftwbuf->path, strerror(errno));
-	state->ret = -1;
+	if (!eval_should_ignore(state, errno)) {
+		pretty_error(state->cmdline->stderr_colors,
+		             "'%s': %s\n", state->ftwbuf->path, strerror(errno));
+		state->ret = -1;
+	}
 }
 
 /**
@@ -905,8 +916,6 @@ struct callback_args {
 	const struct cmdline *cmdline;
 	/** Eventual return value from eval_cmdline(). */
 	int ret;
-	/** The last error code seen. */
-	int last_error;
 };
 
 /**
@@ -917,12 +926,6 @@ static enum bftw_action cmdline_callback(struct BFTW *ftwbuf, void *ptr) {
 
 	const struct cmdline *cmdline = args->cmdline;
 
-	if (ftwbuf->typeflag == BFTW_ERROR) {
-		args->last_error = ftwbuf->error;
-		pretty_error(cmdline->stderr_colors, "'%s': %s\n", ftwbuf->path, strerror(ftwbuf->error));
-		return BFTW_SKIP_SUBTREE;
-	}
-
 	struct eval_state state = {
 		.ftwbuf = ftwbuf,
 		.cmdline = cmdline,
@@ -930,6 +933,15 @@ static enum bftw_action cmdline_callback(struct BFTW *ftwbuf, void *ptr) {
 		.ret = args->ret,
 	};
 
+	if (ftwbuf->typeflag == BFTW_ERROR) {
+		if (!eval_should_ignore(&state, ftwbuf->error)) {
+			state.ret = -1;
+			pretty_error(cmdline->stderr_colors, "'%s': %s\n", ftwbuf->path, strerror(ftwbuf->error));
+		}
+		state.action = BFTW_SKIP_SUBTREE;
+		goto done;
+	}
+
 	if (ftwbuf->depth >= cmdline->maxdepth) {
 		state.action = BFTW_SKIP_SUBTREE;
 	}
@@ -948,6 +960,7 @@ static enum bftw_action cmdline_callback(struct BFTW *ftwbuf, void *ptr) {
 		eval_expr(cmdline->expr, &state);
 	}
 
+done:
 	if ((cmdline->debug & DEBUG_STAT) && ftwbuf->statbuf) {
 		debug_stat(&state);
 	}
@@ -1024,14 +1037,9 @@ int eval_cmdline(const struct cmdline *cmdline) {
 	};
 
 	for (struct root *root = cmdline->roots; root; root = root->next) {
-		args.last_error = 0;
-
 		if (bftw(root->path, cmdline_callback, nopenfd, cmdline->flags, &args) != 0) {
 			args.ret = -1;
-
-			if (errno != args.last_error) {
-				perror("bftw()");
-			}
+			perror("bftw()");
 		}
 	}
 
diff --git a/parse.c b/parse.c
index 023d414..b17192e 100644
--- a/parse.c
+++ b/parse.c
@@ -1034,6 +1034,14 @@ static struct expr *parse_hidden(struct parser_state *state, int arg1, int arg2)
 	return parse_nullary_test(state, eval_hidden);
 }
 
+/**
+ * Parse -(no)?ignore_readdir_race.
+ */
+static struct expr *parse_ignore_races(struct parser_state *state, int ignore, int arg2) {
+	state->cmdline->ignore_races = ignore;
+	return parse_nullary_option(state);
+}
+
 /**
  * Parse -inum N.
  */
@@ -1443,6 +1451,7 @@ static const struct table_entry parse_table[] = {
 	{"group", false, parse_group},
 	{"help", false, parse_help},
 	{"hidden", false, parse_hidden},
+	{"ignore_readdir_race", false, parse_ignore_races, true},
 	{"ilname", false, parse_lname, true},
 	{"iname", false, parse_name, true},
 	{"inum", false, parse_inum},
@@ -1460,6 +1469,7 @@ static const struct table_entry parse_table[] = {
 	{"newer", true, parse_newerxy},
 	{"nocolor", false, parse_color, false},
 	{"nohidden", false, parse_nohidden},
+	{"noignore_readdir_race", false, parse_ignore_races, false},
 	{"noleaf", false, parse_noleaf},
 	{"not"},
 	{"nowarn", false, parse_warn, false},
@@ -1932,9 +1942,17 @@ void dump_cmdline(const struct cmdline *cmdline, bool verbose) {
 		fprintf(stderr, "%s ", root->path);
 	}
 
+	if (cmdline->stdout_colors) {
+		fputs("-color ", stderr);
+	} else {
+		fputs("-nocolor ", stderr);
+	}
 	if (cmdline->flags & BFTW_DEPTH) {
 		fputs("-depth ", stderr);
 	}
+	if (cmdline->ignore_races) {
+		fputs("-ignore_readdir_race ", stderr);
+	}
 	if (cmdline->flags & BFTW_MOUNT) {
 		fputs("-mount ", stderr);
 	}
@@ -1944,11 +1962,6 @@ void dump_cmdline(const struct cmdline *cmdline, bool verbose) {
 	if (cmdline->maxdepth != INT_MAX) {
 		fprintf(stderr, "-maxdepth %d ", cmdline->maxdepth);
 	}
-	if (cmdline->stdout_colors) {
-		fputs("-color ", stderr);
-	} else {
-		fputs("-nocolor ", stderr);
-	}
 
 	dump_expr(cmdline->expr, verbose);
 
@@ -1993,6 +2006,7 @@ struct cmdline *parse_cmdline(int argc, char *argv[]) {
 	cmdline->flags = BFTW_RECOVER;
 	cmdline->optlevel = 3;
 	cmdline->debug = 0;
+	cmdline->ignore_races = false;
 	cmdline->expr = &expr_true;
 	cmdline->nopen_files = 0;
 
diff --git a/tests.sh b/tests.sh
index 63de3ee..2206877 100755
--- a/tests.sh
+++ b/tests.sh
@@ -62,11 +62,12 @@ function make_weirdnames() {
 weirdnames="$(mktemp -d "${TMPDIR:-/tmp}"/bfs.weirdnames.XXXXXXXXXX)"
 make_weirdnames "$weirdnames"
 
-out="$(mktemp -d "${TMPDIR:-/tmp}"/bfs.out.XXXXXXXXXX)"
+# Create a scratch directory that tests can modify
+scratch="$(mktemp -d "${TMPDIR:-/tmp}"/bfs.weirdnames.XXXXXXXXXX)"
 
 # Clean up temporary directories on exit
 function cleanup() {
-    rm -rf "$out"
+    rm -rf "$scratch"
     rm -rf "$weirdnames"
     rm -rf "$links"
     rm -rf "$perms"
@@ -359,12 +360,12 @@ function test_0064() {
 }
 
 function test_0065() {
-    find "$basic" -fprint "$out/out.find"
-    "$BFS" "$basic" -fprint "$out/out.bfs"
+    find "$basic" -fprint "$scratch/out.find"
+    "$BFS" "$basic" -fprint "$scratch/out.bfs"
 
-    sort -o "$out/out.find" "$out/out.find"
-    sort -o "$out/out.bfs" "$out/out.bfs"
-    diff -u "$out/out.find" "$out/out.bfs"
+    sort -o "$scratch/out.find" "$scratch/out.find"
+    sort -o "$scratch/out.bfs" "$scratch/out.bfs"
+    diff -u "$scratch/out.find" "$scratch/out.bfs"
 }
 
 function test_0066() {
@@ -377,7 +378,20 @@ function test_0067() {
     find_diff -L -- -type f
 }
 
-for i in {1..67}; do
+function test_0068() {
+    # Make sure -ignore_readdir_race doesn't suppress ENOENT at the root
+    ! "$BFS" "$basic/nonexistent" -ignore_readdir_race 2>/dev/null
+}
+
+function test_0069() {
+    rm -rf "$scratch"/*
+    touch "$scratch"/{foo,bar}
+
+    # -links 1 forces a stat() call, which will fail for the second file
+    "$BFS" "$scratch" -ignore_readdir_race -links 1 -exec ./tests/remove-sibling.sh '{}' ';'
+}
+
+for i in {1..69}; do
     test="test_$(printf '%04d' $i)"
     ("$test" "$dir")
     status=$?
diff --git a/tests/remove-sibling.sh b/tests/remove-sibling.sh
new file mode 100755
index 0000000..ddf81d7
--- /dev/null
+++ b/tests/remove-sibling.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+for file in "${1%/*}"/*; do
+    if [ "$file" != "$1" ]; then
+        rm "$file"
+        exit $?
+    fi
+done
+
+exit 1
-- 
cgit v1.2.3