7b76bb
From 443166adaf1c8b91e16a716f3b13f47493b895cc Mon Sep 17 00:00:00 2001
7b76bb
From: Bernhard Voelker <mail@bernhard-voelker.de>
7b76bb
Date: Tue, 31 May 2016 10:38:52 +0200
7b76bb
Subject: [PATCH] Fix bug #48030: find: -exec + does not pass all arguments in
7b76bb
 certain cases
7b76bb
7b76bb
When the -exec arguments buffer (usually 128k) is full and the given
7b76bb
command has been executed with all that arguments, find(1) missed to
7b76bb
execute the command yet another time if only 1 another file would have
7b76bb
to be processed.
7b76bb
Both find(1), i.e., nowadays FTS-version, and oldfind are affected.
7b76bb
This bug was present since the implementation of '-exec +' in 2005,
7b76bb
see commit FINDUTILS_4_2_11-1-25-gf0a6ac6.
7b76bb
7b76bb
* lib/buildcmd.c (bc_push_arg): Move the assignment to set 'state->todo'
7b76bb
to 1 down after the immediate execution which resets that flag.
7b76bb
* find/testsuite/sv-48030-exec-plus-bug.sh: Add a test.
7b76bb
* find/testsuite/Makefile.am (test_shell_progs): Reference the test.
7b76bb
* NEWS (Bug Fixes): Mention the fix.
7b76bb
7b76bb
Reported by Joe Philip Ninan <indiajoe@gmail.com> in
7b76bb
https://savannah.gnu.org/bugs/?48030
7b76bb
7b76bb
Upstream-commit: 8cdc9767e305c9566f537af9d1acf71d1bc6ee8e
7b76bb
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
7b76bb
---
7b76bb
 find/testsuite/Makefile.am               |   3 +-
7b76bb
 find/testsuite/sv-48030-exec-plus-bug.sh | 143 +++++++++++++++++++++++++++++++
7b76bb
 lib/buildcmd.c                           |  10 +--
7b76bb
 3 files changed, 150 insertions(+), 6 deletions(-)
7b76bb
 create mode 100644 find/testsuite/sv-48030-exec-plus-bug.sh
7b76bb
7b76bb
diff --git a/find/testsuite/Makefile.am b/find/testsuite/Makefile.am
7b76bb
index c1369c3..ab5dbe8 100644
7b76bb
--- a/find/testsuite/Makefile.am
7b76bb
+++ b/find/testsuite/Makefile.am
7b76bb
@@ -258,7 +258,8 @@ test_escapechars.sh \
7b76bb
 test_escape_c.sh \
7b76bb
 test_inode.sh \
7b76bb
 sv-34079.sh \
7b76bb
-sv-34976-execdir-fd-leak.sh
7b76bb
+sv-34976-execdir-fd-leak.sh \
7b76bb
+sv-48030-exec-plus-bug.sh
7b76bb
 
7b76bb
 EXTRA_DIST = $(EXTRA_DIST_EXP) $(EXTRA_DIST_XO) $(EXTRA_DIST_GOLDEN) \
7b76bb
 	$(test_shell_progs) binary_locations.sh checklists.py
7b76bb
diff --git a/find/testsuite/sv-48030-exec-plus-bug.sh b/find/testsuite/sv-48030-exec-plus-bug.sh
7b76bb
new file mode 100755
7b76bb
index 0000000..4dbf149
7b76bb
--- /dev/null
7b76bb
+++ b/find/testsuite/sv-48030-exec-plus-bug.sh
7b76bb
@@ -0,0 +1,143 @@
7b76bb
+#! /bin/sh
7b76bb
+# Copyright (C) 2016 Free Software Foundation, Inc.
7b76bb
+#
7b76bb
+# This program is free software: you can redistribute it and/or modify
7b76bb
+# it under the terms of the GNU General Public License as published by
7b76bb
+# the Free Software Foundation, either version 3 of the License, or
7b76bb
+# (at your option) any later version.
7b76bb
+#
7b76bb
+# This program is distributed in the hope that it will be useful,
7b76bb
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
7b76bb
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7b76bb
+# GNU General Public License for more details.
7b76bb
+#
7b76bb
+# You should have received a copy of the GNU General Public License
7b76bb
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
7b76bb
+#
7b76bb
+
7b76bb
+# This test verifies that find invokes the given command for the
7b76bb
+# multiple-argument sytax '-exec CMD {} +'.  Between FINDUTILS-4.2.12
7b76bb
+# and v4.6.0, find(1) would have failed to execute CMD another time
7b76bb
+# if there was only one last single file argument.
7b76bb
+
7b76bb
+testname="$(basename $0)"
7b76bb
+
7b76bb
+. "${srcdir}"/binary_locations.sh
7b76bb
+
7b76bb
+die() {
7b76bb
+  echo "$@" >&2
7b76bb
+  exit 1
7b76bb
+}
7b76bb
+
7b76bb
+# This is used to simplify checking of the return value
7b76bb
+# which is useful when ensuring a command fails as desired.
7b76bb
+# I.e., just doing `command ... &&fail=1` will not catch
7b76bb
+# a segfault in command for example.  With this helper you
7b76bb
+# instead check an explicit exit code like
7b76bb
+#   returns_ 1 command ... || fail
7b76bb
+returns_ () {
7b76bb
+  # Disable tracing so it doesn't interfere with stderr of the wrapped command
7b76bb
+  { set +x; } 2>/dev/null
7b76bb
+
7b76bb
+  local exp_exit="$1"
7b76bb
+  shift
7b76bb
+  "$@"
7b76bb
+  test $? -eq $exp_exit && ret_=0 || ret_=1
7b76bb
+
7b76bb
+  set -x
7b76bb
+  { return $ret_; } 2>/dev/null
7b76bb
+}
7b76bb
+
7b76bb
+# Define the nicest compare available (borrowed from gnulib).
7b76bb
+if diff_out_=`exec 2>/dev/null; diff -u "$0" "$0" < /dev/null` \
7b76bb
+   && diff -u Makefile "$0" 2>/dev/null | grep '^[+]#!' >/dev/null; then
7b76bb
+  # diff accepts the -u option and does not (like AIX 7 'diff') produce an
7b76bb
+  # extra space on column 1 of every content line.
7b76bb
+  if test -z "$diff_out_"; then
7b76bb
+    compare () { diff -u "$@"; }
7b76bb
+  else
7b76bb
+    compare ()
7b76bb
+    {
7b76bb
+      if diff -u "$@" > diff.out; then
7b76bb
+        # No differences were found, but Solaris 'diff' produces output
7b76bb
+        # "No differences encountered". Hide this output.
7b76bb
+        rm -f diff.out
7b76bb
+        true
7b76bb
+      else
7b76bb
+        cat diff.out
7b76bb
+        rm -f diff.out
7b76bb
+        false
7b76bb
+      fi
7b76bb
+    }
7b76bb
+  fi
7b76bb
+elif diff_out_=`exec 2>/dev/null; diff -c "$0" "$0" < /dev/null`; then
7b76bb
+  if test -z "$diff_out_"; then
7b76bb
+    compare () { diff -c "$@"; }
7b76bb
+  else
7b76bb
+    compare ()
7b76bb
+    {
7b76bb
+      if diff -c "$@" > diff.out; then
7b76bb
+        # No differences were found, but AIX and HP-UX 'diff' produce output
7b76bb
+        # "No differences encountered" or "There are no differences between the
7b76bb
+        # files.". Hide this output.
7b76bb
+        rm -f diff.out
7b76bb
+        true
7b76bb
+      else
7b76bb
+        cat diff.out
7b76bb
+        rm -f diff.out
7b76bb
+        false
7b76bb
+      fi
7b76bb
+    }
7b76bb
+  fi
7b76bb
+elif cmp -s /dev/null /dev/null 2>/dev/null; then
7b76bb
+  compare () { cmp -s "$@"; }
7b76bb
+else
7b76bb
+  compare () { cmp "$@"; }
7b76bb
+fi
7b76bb
+
7b76bb
+DIR='RashuBug'
7b76bb
+# Name of the CMD to execute: the file name must be 6 characters long
7b76bb
+# (to trigger the bug in combination with the test files).
7b76bb
+CMD='tstcmd'
7b76bb
+
7b76bb
+# Create test files.
7b76bb
+make_test_data() {
7b76bb
+  # Create the CMD script and check that it works.
7b76bb
+  mkdir "$DIR" 'bin' \
7b76bb
+    && echo 'printf "%s\n" "$@"' > "bin/$CMD" \
7b76bb
+    && chmod +x "bin/$CMD" \
7b76bb
+    && PATH="$PWD/bin:$PATH" \
7b76bb
+    && [ $( "${ftsfind}" bin -maxdepth 0 -exec "$CMD" '{}' + ) = 'bin' ] \
7b76bb
+    || return 1
7b76bb
+
7b76bb
+  # Create expected output file - also used for creating the test data.
7b76bb
+  { seq -f "${DIR}/abcdefghijklmnopqrstuv%04g" 901 &&
7b76bb
+    seq -f "${DIR}/abcdefghijklmnopqrstu%04g" 902 3719
7b76bb
+  } > exp2 \
7b76bb
+    && LC_ALL=C sort exp2 > exp \
7b76bb
+    && rm exp2 \
7b76bb
+    || return 1
7b76bb
+
7b76bb
+  # Create test files, and check if test data has been created correctly.
7b76bb
+  xargs touch < exp \
7b76bb
+    && [ -f "${DIR}/abcdefghijklmnopqrstu3719" ] \
7b76bb
+    && [ 3719 = $( "${ftsfind}" "$DIR" -type f | wc -l ) ] \
7b76bb
+    || return 1
7b76bb
+}
7b76bb
+
7b76bb
+set -x
7b76bb
+tmpdir="$(mktemp -d)" \
7b76bb
+  && cd "$tmpdir" \
7b76bb
+  && make_test_data "${tmpdir}" \
7b76bb
+  || die "FAIL: failed to set up the test in ${tmpdir}"
7b76bb
+
7b76bb
+fail=0
7b76bb
+for exe in "${ftsfind}" "${oldfind}"; do
7b76bb
+  "$exe" "$DIR" -type f -exec "$CMD" '{}' + > out || fail=1
7b76bb
+  LC_ALL=C sort out > out2 || fail=1
7b76bb
+  compare exp out2 || fail=1
7b76bb
+done
7b76bb
+
7b76bb
+cd ..
7b76bb
+rm -rf "${tmpdir}" || exit 1
7b76bb
+exit $fail
7b76bb
diff --git a/lib/buildcmd.c b/lib/buildcmd.c
7b76bb
index a58f67e..27e9ce5 100644
7b76bb
--- a/lib/buildcmd.c
7b76bb
+++ b/lib/buildcmd.c
7b76bb
@@ -356,11 +356,6 @@ bc_push_arg (struct buildcmd_control *ctl,
7b76bb
 
7b76bb
   assert (arg != NULL);
7b76bb
 
7b76bb
-  if (!initial_args)
7b76bb
-    {
7b76bb
-      state->todo = 1;
7b76bb
-    }
7b76bb
-
7b76bb
   if (!terminate)
7b76bb
     {
7b76bb
       if (state->cmd_argv_chars + len + pfxlen > ctl->arg_max)
7b76bb
@@ -380,6 +375,11 @@ bc_push_arg (struct buildcmd_control *ctl,
7b76bb
             bc_do_exec (ctl, state);
7b76bb
     }
7b76bb
 
7b76bb
+  if (!initial_args)
7b76bb
+    {
7b76bb
+      state->todo = 1;
7b76bb
+    }
7b76bb
+
7b76bb
   if (state->cmd_argc >= state->cmd_argv_alloc)
7b76bb
     {
7b76bb
       /* XXX: we could use extendbuf() here. */
7b76bb
-- 
7b76bb
2.5.5
7b76bb