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