From c3f52a2e3c22a8e05b60f94b8344501b14e87794 Mon Sep 17 00:00:00 2001
From: Tavian Barnes <tavianator@tavianator.com>
Date: Fri, 16 Aug 2024 09:24:30 -0400
Subject: New -noerror option to suppress error messages

Closes: https://github.com/tavianator/bfs/issues/142
---
 src/ctx.h   |  2 ++
 src/eval.c  | 17 ++++++++++++++++-
 src/parse.c | 11 +++++++++++
 3 files changed, 29 insertions(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/ctx.h b/src/ctx.h
index c7ebc20..4ca4a34 100644
--- a/src/ctx.h
+++ b/src/ctx.h
@@ -75,6 +75,8 @@ struct bfs_ctx {
 	bool interactive;
 	/** Whether to print warnings (-warn/-nowarn). */
 	bool warn;
+	/** Whether to report errors (-noerror). */
+	bool ignore_errors;
 	/** Whether any dangerous actions (-delete/-exec) are present. */
 	bool dangerous;
 
diff --git a/src/eval.c b/src/eval.c
index d139ed3..9676cf3 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -53,6 +53,8 @@ struct bfs_eval {
 	enum bftw_action action;
 	/** The bfs_eval() return value. */
 	int *ret;
+	/** The number of errors that have occurred. */
+	size_t *nerrors;
 	/** Whether to quit immediately. */
 	bool quit;
 };
@@ -62,10 +64,16 @@ struct bfs_eval {
  */
 _printf(2, 3)
 static void eval_error(struct bfs_eval *state, const char *format, ...) {
+	const struct bfs_ctx *ctx = state->ctx;
+
+	++*state->nerrors;
+	if (ctx->ignore_errors) {
+		return;
+	}
+
 	// By POSIX, any errors should be accompanied by a non-zero exit status
 	*state->ret = EXIT_FAILURE;
 
-	const struct bfs_ctx *ctx = state->ctx;
 	CFILE *cerr = ctx->cerr;
 
 	bfs_error(ctx, "%pP: ", state->ftwbuf);
@@ -1389,6 +1397,8 @@ struct callback_args {
 	/** The set of seen files. */
 	struct trie *seen;
 
+	/** The number of errors that have occurred. */
+	size_t nerrors;
 	/** Eventual return value from bfs_eval(). */
 	int ret;
 };
@@ -1407,6 +1417,7 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
 	state.ctx = ctx;
 	state.action = BFTW_CONTINUE;
 	state.ret = &args->ret;
+	state.nerrors = &args->nerrors;
 	state.quit = false;
 
 	// Check whether SIGINFO was delivered and show/hide the bar
@@ -1740,5 +1751,9 @@ int bfs_eval(struct bfs_ctx *ctx) {
 	sigunhook(info_hook);
 	bfs_bar_hide(args.bar);
 
+	if (args.nerrors > 0) {
+		bfs_warning(ctx, "suppressed errors: %zu\n", args.nerrors);
+	}
+
 	return args.ret;
 }
diff --git a/src/parse.c b/src/parse.c
index ddb67aa..fe0c10d 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -1816,6 +1816,14 @@ static struct bfs_expr *parse_newerxy(struct bfs_parser *parser, int arg1, int a
 	return expr;
 }
 
+/**
+ * Parse -noerror.
+ */
+static struct bfs_expr *parse_noerror(struct bfs_parser *parser, int arg1, int arg2) {
+	parser->ctx->ignore_errors = true;
+	return parse_nullary_option(parser);
+}
+
 /**
  * Parse -nogroup.
  */
@@ -2759,6 +2767,8 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2
 	cfprintf(cout, "  ${blu}-mount${rs}\n");
 	cfprintf(cout, "      Don't descend into other mount points (same as ${blu}-xdev${rs} for now, but will\n");
 	cfprintf(cout, "      skip mount points entirely in the future)\n");
+	cfprintf(cout, "  ${blu}-noerror${rs}\n");
+	cfprintf(cout, "      Ignore any errors that occur during traversal\n");
 	cfprintf(cout, "  ${blu}-nohidden${rs}\n");
 	cfprintf(cout, "      Exclude hidden files\n");
 	cfprintf(cout, "  ${blu}-noleaf${rs}\n");
@@ -3053,6 +3063,7 @@ static const struct table_entry parse_table[] = {
 	{"-newer", BFS_TEST, parse_newer, BFS_STAT_MTIME},
 	{"-newer", BFS_TEST, parse_newerxy, .prefix = true},
 	{"-nocolor", BFS_OPTION, parse_color, false},
+	{"-noerror", BFS_OPTION, parse_noerror},
 	{"-nogroup", BFS_TEST, parse_nogroup},
 	{"-nohidden", BFS_TEST, parse_nohidden},
 	{"-noignore_readdir_race", BFS_OPTION, parse_ignore_races, false},
-- 
cgit v1.2.3