From 695ea13d191c903b86b0d5795a2686a8c6e18015 Mon Sep 17 00:00:00 2001
From: Tavian Barnes <tavianator@tavianator.com>
Date: Fri, 11 Feb 2022 12:16:52 -0500
Subject: exec: Flush I/O streams before executing anything

Otherwise output from commands may appear unexpectedly earlier than
output from bfs.  We use fflush(NULL) to flush all streams, which is
more than GNU find does, but seems to be a useful extension.
---
 exec.c                           |  9 +++++++++
 tests.sh                         | 38 +++++++++++++++++++++++++++++++++++++-
 tests/test_exec_flush.out        | 19 +++++++++++++++++++
 tests/test_exec_flush_fprint.out |  1 +
 tests/test_exec_plus_flush.out   |  1 +
 5 files changed, 67 insertions(+), 1 deletion(-)
 create mode 100644 tests/test_exec_flush.out
 create mode 100644 tests/test_exec_flush_fprint.out
 create mode 100644 tests/test_exec_plus_flush.out

diff --git a/exec.c b/exec.c
index 310756b..4cfb2e0 100644
--- a/exec.c
+++ b/exec.c
@@ -325,6 +325,15 @@ static void bfs_exec_closewd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf
 
 /** Actually spawn the process. */
 static int bfs_exec_spawn(const struct bfs_exec *execbuf) {
+	// Before executing anything, flush all open streams.  This ensures that
+	// - the user sees everything relevant before an -ok[dir] prompt
+	// - output from commands is interleaved consistently with bfs
+	// - executed commands can rely on I/O from other bfs actions
+	//
+	// We do not check errors here, but they will be caught at cleanup time
+	// with ferror().
+	fflush(NULL);
+
 	if (execbuf->flags & BFS_EXEC_CONFIRM) {
 		for (size_t i = 0; i < execbuf->argc; ++i) {
 			fprintf(stderr, "%s ", execbuf->argv[i]);
diff --git a/tests.sh b/tests.sh
index 25517ca..ed502fd 100755
--- a/tests.sh
+++ b/tests.sh
@@ -2,7 +2,7 @@
 
 ############################################################################
 # bfs                                                                      #
-# Copyright (C) 2015-2021 Tavian Barnes <tavianator@tavianator.com>        #
+# Copyright (C) 2015-2022 Tavian Barnes <tavianator@tavianator.com>        #
 #                                                                          #
 # Permission to use, copy, modify, and/or distribute this software for any #
 # purpose with or without fee is hereby granted.                           #
@@ -492,6 +492,12 @@ gnu_tests=(
 
     test_exec_nothing
     test_exec_substring
+    test_exec_flush
+    test_exec_flush_fail
+    test_exec_flush_fprint
+    test_exec_flush_fprint_fail
+    test_exec_plus_flush
+    test_exec_plus_flush_fail
 
     test_execdir
     test_execdir_substring
@@ -1754,6 +1760,36 @@ function test_exec_substring() {
     bfs_diff basic -exec echo '-{}-' ';'
 }
 
+function test_exec_flush() {
+    # IO streams should be flushed before executing programs
+    bfs_diff basic -printf '%p ' -exec echo found \;
+}
+
+function test_exec_flush_fail() {
+    # Failure to flush streams before exec should be caught
+    skip_if test ! -e /dev/full
+    fail quiet invoke_bfs basic -printf '%p ' -exec true \; >/dev/full
+}
+
+function test_exec_flush_fprint() {
+    # Even non-stdstreams should be flushed
+    bfs_diff basic/a -fprint scratch/foo -exec cat scratch/foo \;
+}
+
+function test_exec_flush_fprint_fail() {
+    skip_if test ! -e /dev/full
+    fail quiet invoke_bfs basic/a -fprint /dev/full -exec true \;
+}
+
+function test_exec_plus_flush() {
+    bfs_diff basic/a -printf '%p ' -exec echo found {} +
+}
+
+function test_exec_plus_flush_fail() {
+    skip_if test ! -e /dev/full
+    fail quiet invoke_bfs basic/a -printf '%p ' -exec echo found {} + >/dev/full
+}
+
 function test_execdir() {
     bfs_diff basic -execdir echo '{}' ';'
 }
diff --git a/tests/test_exec_flush.out b/tests/test_exec_flush.out
new file mode 100644
index 0000000..0e6234a
--- /dev/null
+++ b/tests/test_exec_flush.out
@@ -0,0 +1,19 @@
+basic found
+basic/a found
+basic/b found
+basic/c found
+basic/e found
+basic/g found
+basic/i found
+basic/j found
+basic/k found
+basic/l found
+basic/c/d found
+basic/e/f found
+basic/g/h found
+basic/j/foo found
+basic/k/foo found
+basic/l/foo found
+basic/k/foo/bar found
+basic/l/foo/bar found
+basic/l/foo/bar/baz found
diff --git a/tests/test_exec_flush_fprint.out b/tests/test_exec_flush_fprint.out
new file mode 100644
index 0000000..511198f
--- /dev/null
+++ b/tests/test_exec_flush_fprint.out
@@ -0,0 +1 @@
+basic/a
diff --git a/tests/test_exec_plus_flush.out b/tests/test_exec_plus_flush.out
new file mode 100644
index 0000000..7c587ba
--- /dev/null
+++ b/tests/test_exec_plus_flush.out
@@ -0,0 +1 @@
+basic/a found basic/a
-- 
cgit v1.2.3