find: fix handling of trailing slashes in -name PATTERN comparisons
authorDenys Vlasenko <vda.linux@googlemail.com>
Fri, 25 Nov 2016 19:14:33 +0000 (20:14 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Fri, 25 Nov 2016 19:14:33 +0000 (20:14 +0100)
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
findutils/find.c
testsuite/find.tests

index d71c697827079c7d12e3c627beae89c33e528377..27698e537a1d68fc4f1ed7e92e9b0add574cd261 100644 (file)
@@ -502,26 +502,54 @@ static char *strcpy_upcase(char *dst, const char *src)
 
 ACTF(name)
 {
+       int r;
        const char *tmp = bb_basename(fileName);
-       if (tmp != fileName && *tmp == '\0') {
-               /* "foo/bar/". Oh no... go back to 'b' */
-               tmp--;
-               while (tmp != fileName && *--tmp != '/')
-                       continue;
-               if (*tmp == '/')
-                       tmp++;
+       /* GNU findutils: find DIR/ -name DIR
+        * prints "DIR/" (DIR// prints "DIR//" etc).
+        * Need to strip trailing "/".
+        * Such names can come only from top-level names, but
+        * we can't do this before recursive_action() call,
+        * since then "find FILE/ -name FILE"
+        * would also work (on non-directories), which is wrong.
+        */
+       char *trunc_slash = NULL;
+
+       if (*tmp == '\0') {
+               /* "foo/bar/[//...]" */
+               while (tmp != fileName && tmp[-1] == '/')
+                       tmp--;
+               if (tmp == fileName) { /* entire fileName is "//.."? */
+                       /* yes, convert "//..." to "/"
+                        * Testcases:
+                        * find / -maxdepth 1 -name /: prints /
+                        * find // -maxdepth 1 -name /: prints //
+                        * find / -maxdepth 1 -name //: prints nothing
+                        * find // -maxdepth 1 -name //: prints nothing
+                        */
+                       if (tmp[1])
+                               trunc_slash = (char*)tmp + 1;
+               } else {
+                       /* no, it's "foo/bar/[//...]", go back to 'b' */
+                       trunc_slash = (char*)tmp;
+                       while (tmp != fileName && tmp[-1] != '/')
+                               tmp--;
+               }
        }
+
        /* Was using FNM_PERIOD flag too,
         * but somewhere between 4.1.20 and 4.4.0 GNU find stopped using it.
         * find -name '*foo' should match .foo too:
         */
+       if (trunc_slash) *trunc_slash = '\0';
 #if FNM_CASEFOLD
-       return fnmatch(ap->pattern, tmp, (ap->iname ? FNM_CASEFOLD : 0)) == 0;
+       r = fnmatch(ap->pattern, tmp, (ap->iname ? FNM_CASEFOLD : 0));
 #else
        if (ap->iname)
                tmp = strcpy_upcase(alloca(strlen(tmp) + 1), tmp);
-       return fnmatch(ap->pattern, tmp, 0) == 0;
+       r = fnmatch(ap->pattern, tmp, 0);
 #endif
+       if (trunc_slash) *trunc_slash = '/';
+       return r == 0;
 }
 
 #if ENABLE_FEATURE_FIND_PATH
index 78dfa123033e9f881d144c22159ba88aba5ee663..138236c81bc90e5710ecf8050cb1844c80ec891a 100755 (executable)
@@ -41,6 +41,33 @@ testing "find -exec exitcode 4" \
        "1\n" \
        "" ""
 SKIP=
+optional FEATURE_FIND_MAXDEPTH
+testing "find / -maxdepth 0 -name /" \
+       "find / -maxdepth 0 -name /" \
+       "/\n" \
+       "" ""
+testing "find // -maxdepth 0 -name /" \
+       "find // -maxdepth 0 -name /" \
+       "//\n" \
+       "" ""
+testing "find / -maxdepth 0 -name //" \
+       "find / -maxdepth 0 -name //" \
+       "" \
+       "" ""
+testing "find // -maxdepth 0 -name //" \
+       "find // -maxdepth 0 -name //" \
+       "" \
+       "" ""
+SKIP=
+
+testing "find ./// -name ." \
+       "find ./// -name ." \
+       ".///\n" \
+       "" ""
+testing "find ./// -name .///" \
+       "find ./// -name .///" \
+       "" \
+       "" ""
 
 # testing "description" "command" "result" "infile" "stdin"