This patch makes find accept things like "find -PP .".
From c10a0126dd56e42a8118494cf67a6c74eb58c0db Mon Sep 17 00:00:00 2001
From: James Youngman <ja...@youngman.org>
Date: Mon, 3 Jun 2024 08:44:12 +0100
Subject: [PATCH] find: Process -P -H -L options in a POSIX-compliant way.
To: findutils-patc...@gnu.org

* tests/find/posix-options.sh: New test for option combinations like
find -LP which previously were not recognised.
* tests/local.mk(sh_tests): add tests/find/posix-options.sh.
* find/util.c(process_leading_options): process leading options with
getopt (instead of strcmp) for greater POSIX compliance
* NEWS: mention this bugfix.
---
 NEWS                        |   4 ++
 find/util.c                 | 106 ++++++++++++++++++++++--------------
 tests/find/posix-options.sh |  97 +++++++++++++++++++++++++++++++++
 tests/local.mk              |   1 +
 4 files changed, 167 insertions(+), 41 deletions(-)
 create mode 100755 tests/find/posix-options.sh

diff --git a/NEWS b/NEWS
index e2a8e067..40fd0309 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ GNU findutils NEWS - User visible changes.      -*- outline -*- (allout)
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** Bug Fixes
+
+  Find now accepts combinations of leading options, for example "find
+  -PP".  Previously, these were incorrectly rejected.
 
 * Noteworthy changes in release 4.10.0 (2024-06-01) [stable]
 
diff --git a/find/util.c b/find/util.c
index a1736807..709e2518 100644
--- a/find/util.c
+++ b/find/util.c
@@ -25,6 +25,7 @@
 #include <fcntl.h>
 #include <limits.h>
 #include <string.h>
+#include <unistd.h>   /* for getopt() */
 #include <sys/stat.h> /* for fstatat() */
 #include <sys/time.h>
 #include <sys/utsname.h>
@@ -908,56 +909,79 @@ process_optimisation_option (const char *arg)
 int
 process_leading_options (int argc, char *argv[])
 {
-  int i, end_of_leading_options;
+  bool done = false;
+  int opt;
 
-  for (i=1; (end_of_leading_options = i) < argc; ++i)
+  /* We don't want getopt to print its own error messages. */
+  opterr = 0;
+
+  /* We put + as the first character of optstring in order to prevent
+   * getopt permuting the order of the options.  Therefore we require
+   * GNU getopt, since this is an extension.  We use ':' as the second
+   * character to change the way getopt handles missing
+   * option-arguments (as specified by POSIX).
+   */
+  while (!done && (opt = getopt(argc, argv, "+:HLPD:O:")) != -1)
     {
-      if (0 == strcmp ("-H", argv[i]))
-        {
+      switch (opt)
+	{
+	case 'H':
           /* Meaning: dereference symbolic links on command line, but nowhere else. */
           set_follow_state (SYMLINK_DEREF_ARGSONLY);
-        }
-      else if (0 == strcmp ("-L", argv[i]))
-        {
+	  break;
+
+	case 'L':
           /* Meaning: dereference all symbolic links. */
           set_follow_state (SYMLINK_ALWAYS_DEREF);
-        }
-      else if (0 == strcmp ("-P", argv[i]))
-        {
+	  break;
+
+	case 'P':
           /* Meaning: never dereference symbolic links (default). */
           set_follow_state (SYMLINK_NEVER_DEREF);
-        }
-      else if (0 == strcmp ("--", argv[i]))
-        {
-          /* -- signifies the end of options. */
-          end_of_leading_options = i+1; /* Next time start with the next option */
-          break;
-        }
-      else if (0 == strcmp ("-D", argv[i]))
-        {
-          if (argc <= i+1)
-            {
-              error (0, 0, _("Missing argument after the -D option."));
-              usage (EXIT_FAILURE);
-            }
-          process_debug_options (argv[i+1]);
-          ++i;                  /* skip the argument too. */
-        }
-      else if (0 == strncmp ("-O", argv[i], 2))
-        {
-          process_optimisation_option (argv[i]+2);
-        }
-      else
-        {
-          /* Hmm, must be one of
-           * (a) A path name
-           * (b) A predicate
-           */
-          end_of_leading_options = i; /* Next time start with this option */
-          break;
-        }
+	  break;
+
+	case 'D':
+          process_debug_options (optarg);
+	  break;
+
+	case 'O':
+          process_optimisation_option (optarg);
+	  break;
+
+	case ':':
+	  /* There is a missing option-argument.  We use this switch
+	   * statement below to ensure that the existing translations
+	   * for the error messages are used.  Once translations have
+	   * caught up and include the error message which uses %c, we
+	   * can remove the special cases for -E and -D and use only
+	   * the generic error message.
+	   */
+	  switch (optopt)
+	    {
+	    case 'E':
+	      error (1, 0, _("Missing argument after the -E option."));
+	      break;
+	    case 'D':
+	      error (1, 0, _("Missing argument after the -D option."));
+	      break;
+	    default:
+	      /* TRANSLATORS: At the moment this case is not actually
+	       * reached but it is included so that this message gets
+	       * translated.  Eventually this will be the only
+	       * message.  See the comment above for an explanation.
+	       */
+	      error (1, 0, _("Missing argument after the -%c option."), optopt);
+	    }
+	  break;
+
+	case '?':
+	default:
+	  /* This is the first argument that isn't a leading option. */
+	  done = true;
+	  break;
+	}
     }
-  return end_of_leading_options;
+  return optind;
 }
 
 static struct timespec
diff --git a/tests/find/posix-options.sh b/tests/find/posix-options.sh
new file mode 100755
index 00000000..055dab58
--- /dev/null
+++ b/tests/find/posix-options.sh
@@ -0,0 +1,97 @@
+#!/bin/sh
+# Verify that -P, -L, -H are processed in a POSIX-compliant way.
+
+# 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
+
+# create test files.
+touch empty
+ln -s empty symlink
+ln -s does-not-exist broken-symlink
+
+# Verify that we don't follow symbolic links by default.
+echo "== testing: find with no options"
+echo symlink >| expected
+find symlink -type l >| out
+compare expected out || { fail=1; cat out; }
+
+# Verify that we don't follow symbolic links with -P.
+echo "== testing: find -P"
+echo symlink >| expected
+find -P symlink -type l >| out
+compare expected out || { fail=1; cat out; }
+
+# Verify that we accept -PP
+echo "== testing: find -PP"
+echo symlink >| expected
+find -PP symlink -type l >| out
+compare expected out || { fail=1; cat out; }
+
+# Verify that with -L we resolve the symlink.
+echo "== testing: find -L"
+# Expect no output since -L doesn't think symlink is a symlink
+# (because it gets resolved).
+true >| expected
+find -L symlink -type l >| out
+compare expected out || { fail=1; cat out; }
+
+# Verify that -LL is accepted.
+echo "== testing: find -LL"
+# Expect no output since -L doesn't think symlink is a symlink
+# (because it gets resolved).
+true >| expected
+find -LL symlink -type l >| out
+compare expected out || { fail=1; cat out; }
+
+# Verify that with -L a broken symlink still looks like a symlink.
+echo "== testing: find -L with broken symlink"
+echo broken-symlink >| expected
+find -L broken-symlink -type l >| out
+compare expected out || { fail=1; cat out; }
+
+# Verify that -LP is erquivalent to -P
+echo "== testing: find -LP"
+echo symlink >| expected
+find -P symlink -type l >| out
+compare expected out || { fail=1; cat out; }
+
+# Verify that -H resolves symlinks on the command line.
+echo "== testing: find -H"
+# Expect no output since -H doesn't think symlink is a symlink
+# (because it gets resolved).
+true >| expected
+find -H symlink -type l >| out
+compare expected out || { fail=1; cat out; }
+
+# Verify that -PH is equivalent to -H
+echo "== testing: find -PH"
+# Expect no output since -H doesn't think symlink is a symlink
+# (because it gets resolved).
+true >| expected
+find -PH symlink -type l >| out
+compare expected out || { fail=1; cat out; }
+
+# Verify that -LH is equivalent to -H
+echo "== testing: find -LH"
+# Expect no output since -H doesn't think symlink is a symlink
+# (because it gets resolved).
+true >| expected
+find -LH symlink -type l >| out
+compare expected out || { fail=1; cat out; }
+
+Exit $fail
diff --git a/tests/local.mk b/tests/local.mk
index 1fe14c01..cfe2a661 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -127,6 +127,7 @@ sh_tests = \
   tests/find/used.sh \
   tests/find/newer.sh \
   tests/find/opt-numeric-arg.sh \
+  tests/find/posix-options.sh \
   tests/find/user-group-max.sh \
   tests/xargs/conflicting_opts.sh \
   tests/xargs/verbose-quote.sh \
-- 
2.39.2

Reply via email to