[PATCH] find: -exec is terminated by + only if the prior arg is exactly '{}'

James Youngman <[email protected]>
Newsgroups gmane.comp.gnu.findutils.patches,gmane.comp.gnu.findutils.bugs
Message-ID <[email protected]>
A "+" only terminates -exec when it immediately follows an argument
which is exactly "{}" (and not, for example, "{}x").  This fixes
Savannah bug 66365.

* NEWS: explain this change.
* doc/find.texi: update one place which omitted the '{}' before '+'.
* find/parser.c (insert_exec_ok): consider + to be special ony if it
  follows an argument which is exactly '{}'.
* tests/find/sv-bug-66365-exec.sh: test for this bug.
* tests/local.mk: add the new test file.
---
 NEWS                            |  6 ++++++
 doc/find.texi                   |  2 +-
 find/parser.c                   | 23 +++++++++++++++--------
 tests/find/sv-bug-66365-exec.sh | 27 +++++++++++++++++++++++++++
 tests/local.mk                  |  1 +
 5 files changed, 50 insertions(+), 9 deletions(-)
 create mode 100644 tests/find/sv-bug-66365-exec.sh

diff --git a/NEWS b/NEWS
index ae8c4a26..3276887a 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,12 @@ GNU findutils NEWS - User visible changes.      -*- outline -*- (allout)
  'find -ignore_readdir_race' now has a race between FTS read and the visiting
  of the entry when the file was removed. [#45930]
 
+ To fix a POSIX compatibility bug, -exec foo Z{} + is no longer a
+ complete predicate, because '+' is only a terminator when it follows
+ an argument which is exactly '{}'.  The findutils documentation
+ already states this, and now find's behaviour matches the
+ documentation.
+
 ** Documentation Changes
 
  The forthcoming Issue 8 of the POSIX standard will standardise "find
diff --git a/doc/find.texi b/doc/find.texi
index 4cae7419..9f63c8fb 100644
--- a/doc/find.texi
+++ b/doc/find.texi
@@ -1536,7 +1536,7 @@ This is different to @samp{-prune} because @samp{-prune} only applies
 to the contents of pruned directories, while @samp{-quit} simply makes
 @code{find} stop immediately.  No child processes will be left
 running.  Any command lines which have been built by @samp{-exec
-... \+} or @samp{-execdir ... \+} are invoked before the program is
+... @{@} +} or @samp{-execdir ... \+} are invoked before the program is
 exited.  After @samp{-quit} is executed, no more files specified on
 the command line will be processed.  For example, @samp{find /tmp/foo
 /tmp/bar -print -quit} will print only @samp{/tmp/foo}.  One common
diff --git a/find/parser.c b/find/parser.c
index ad3b9904..1b389b3c 100644
--- a/find/parser.c
+++ b/find/parser.c
@@ -2770,7 +2770,7 @@ insert_exec_ok (const char *action,
 {
   int start, end;               /* Indexes in ARGV of start & end of cmd. */
   int i;                        /* Index into cmd args */
-  int saw_braces;               /* True if previous arg was '{}'. */
+  bool prev_was_braces_only;    /* Previous arg was '{}' (not e.g. 'Q' or '{}x'). */
   bool allow_plus;              /* True if + is a valid terminator */
   int brace_count;              /* Number of instances of {}. */
   const char *brace_arg;        /* Which arg did {} appear in? */
@@ -2827,28 +2827,35 @@ insert_exec_ok (const char *action,
    * Also figure out if the command is terminated by ";" or by "+".
    */
   start = *arg_ptr;
-  for (end = start, saw_braces=0, brace_count=0, brace_arg=NULL;
+  for (end = start, prev_was_braces_only=false, brace_count=0, brace_arg=NULL;
        (argv[end] != NULL)
        && ((argv[end][0] != ';') || (argv[end][1] != '\0'));
        end++)
     {
       /* For -exec and -execdir, "{} +" can terminate the command. */
-      if ( allow_plus
-           && argv[end][0] == '+' && argv[end][1] == 0
-           && saw_braces)
+      if (allow_plus && prev_was_braces_only
+           && argv[end][0] == '+' && argv[end][1] == 0)
         {
           our_pred->args.exec_vec.multiple = 1;
           break;
         }
 
-      saw_braces = 0;
+      prev_was_braces_only = false;
       if (mbsstr (argv[end], "{}"))
         {
-          saw_braces = 1;
+          if (0 == strcmp(argv[end], "{}"))
+            {
+              /* Savannah bug 66365: + only terminates the predicate
+               * immediately after an argument which is exactly, "{}".
+               * However, the "{}" in "x{}" should get expanded for
+               * the ";" case.
+               */
+              prev_was_braces_only = true;
+            }
           brace_arg = argv[end];
           ++brace_count;
 
-          if (0 == end && (func == pred_execdir || func == pred_okdir))
+          if (start == end && (func == pred_execdir || func == pred_okdir))
             {
               /* The POSIX standard says that {} replacement should
                * occur even in the utility name.  This is insecure
diff --git a/tests/find/sv-bug-66365-exec.sh b/tests/find/sv-bug-66365-exec.sh
new file mode 100644
index 00000000..79ac3f3d
--- /dev/null
+++ b/tests/find/sv-bug-66365-exec.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Test that find -exec ... + treats the + as a terminator only when it
+# immediately follows a {}.  See Savannah bug #66365.
+
+# Copyright (C) 2024 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; fu_path_prepend_
+print_ver_ find
+
+find . -prune -exec echo x{} + \; >| out
+echo 'x. +' >| exp || framework_failure_
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/local.mk b/tests/local.mk
index 7a602c5e..34798451 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -130,6 +130,7 @@ sh_tests = \
   tests/find/used.sh \
   tests/find/newer.sh \
   tests/find/opt-numeric-arg.sh \
+  tests/find/sv-bug-66365-exec.sh \
   tests/find/user-group-max.sh \
   tests/xargs/conflicting_opts.sh \
   tests/xargs/verbose-quote.sh \
-- 
2.39.5
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.