suppress a few "unused function" warnings
[oweals/busybox.git] / findutils / find.c
index 32d830337f1d4040e27e181dbe71954aee07f5c7..9a2c61b757329187b407fbb932485548e2d940f6 100644 (file)
@@ -9,13 +9,12 @@
  *
  * Licensed under GPLv2, see file LICENSE in this source tree.
  */
-
 /* findutils-4.1.20:
  *
  * # find file.txt -exec 'echo {}' '{}  {}' ';'
  * find: echo file.txt: No such file or directory
  * # find file.txt -exec 'echo' '{}  {}' '; '
- * find: missing argument to `-exec'
+ * find: missing argument to '-exec'
  * # find file.txt -exec 'echo {}' '{}  {}' ';' junk
  * find: paths must precede expression
  * # find file.txt -exec 'echo {}' '{}  {}' ';' junk ';'
@@ -44,7 +43,6 @@
  * # find t z t z '(' -name '*t*' -o -name '*z*' ')' -o -print
  * (no output)
  */
-
 /* Testing script
  * ./busybox find "$@" | tee /tmp/bb_find
  * echo ==================
  * echo ==================
  * diff -u /tmp/std_find /tmp/bb_find && echo Identical
  */
-
 //config:config FIND
-//config:      bool "find"
+//config:      bool "find (14 kb)"
 //config:      default y
 //config:      help
-//config:        find is used to search your system to find specified files.
+//config:      find is used to search your system to find specified files.
 //config:
 //config:config FEATURE_FIND_PRINT0
 //config:      bool "Enable -print0: NUL-terminated output"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        Causes output names to be separated by a NUL character
-//config:        rather than a newline. This allows names that contain
-//config:        newlines and other whitespace to be more easily
-//config:        interpreted by other programs.
+//config:      Causes output names to be separated by a NUL character
+//config:      rather than a newline. This allows names that contain
+//config:      newlines and other whitespace to be more easily
+//config:      interpreted by other programs.
 //config:
 //config:config FEATURE_FIND_MTIME
 //config:      bool "Enable -mtime: modified time matching"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        Allow searching based on the modification time of
-//config:        files, in days.
+//config:      Allow searching based on the modification time of
+//config:      files, in days.
 //config:
 //config:config FEATURE_FIND_MMIN
 //config:      bool "Enable -mmin: modified time matching by minutes"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        Allow searching based on the modification time of
-//config:        files, in minutes.
+//config:      Allow searching based on the modification time of
+//config:      files, in minutes.
 //config:
 //config:config FEATURE_FIND_PERM
 //config:      bool "Enable -perm: permissions matching"
 //config:      default y
 //config:      depends on FIND
-//config:      help
-//config:        Enable searching based on file permissions.
 //config:
 //config:config FEATURE_FIND_TYPE
 //config:      bool "Enable -type: file type matching (file/dir/link/...)"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        Enable searching based on file type (file,
-//config:        directory, socket, device, etc.).
+//config:      Enable searching based on file type (file,
+//config:      directory, socket, device, etc.).
+//config:
+//config:config FEATURE_FIND_EXECUTABLE
+//config:      bool "Enable -executable: file is executable"
+//config:      default y
+//config:      depends on FIND
 //config:
 //config:config FEATURE_FIND_XDEV
 //config:      bool "Enable -xdev: 'stay in filesystem'"
 //config:      default y
 //config:      depends on FIND
-//config:      help
-//config:        This option allows find to restrict searches to a single filesystem.
 //config:
 //config:config FEATURE_FIND_MAXDEPTH
 //config:      bool "Enable -mindepth N and -maxdepth N"
 //config:      default y
 //config:      depends on FIND
-//config:      help
-//config:        This option enables -mindepth N and -maxdepth N option.
 //config:
 //config:config FEATURE_FIND_NEWER
 //config:      bool "Enable -newer: compare file modification times"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        Support the 'find -newer' option for finding any files which have
-//config:        modification time that is more recent than the specified FILE.
+//config:      Support the 'find -newer' option for finding any files which have
+//config:      modification time that is more recent than the specified FILE.
 //config:
 //config:config FEATURE_FIND_INUM
 //config:      bool "Enable -inum: inode number matching"
 //config:      default y
 //config:      depends on FIND
-//config:      help
-//config:        Support the 'find -inum' option for searching by inode number.
 //config:
 //config:config FEATURE_FIND_EXEC
 //config:      bool "Enable -exec: execute commands"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        Support the 'find -exec' option for executing commands based upon
-//config:        the files matched.
+//config:      Support the 'find -exec' option for executing commands based upon
+//config:      the files matched.
 //config:
 //config:config FEATURE_FIND_EXEC_PLUS
 //config:      bool "Enable -exec ... {} +"
 //config:      default y
 //config:      depends on FEATURE_FIND_EXEC
 //config:      help
-//config:        Support the 'find -exec ... {} +' option for executing commands
-//config:        for all matched files at once.
-//config:        Without this option, -exec + is a synonym for -exec ;
-//config:        (IOW: it works correctly, but without expected speedup)
+//config:      Support the 'find -exec ... {} +' option for executing commands
+//config:      for all matched files at once.
+//config:      Without this option, -exec + is a synonym for -exec ;
+//config:      (IOW: it works correctly, but without expected speedup)
 //config:
 //config:config FEATURE_FIND_USER
 //config:      bool "Enable -user: username/uid matching"
 //config:      default y
 //config:      depends on FIND
-//config:      help
-//config:        Support the 'find -user' option for searching by username or uid.
 //config:
 //config:config FEATURE_FIND_GROUP
 //config:      bool "Enable -group: group/gid matching"
 //config:      default y
 //config:      depends on FIND
-//config:      help
-//config:        Support the 'find -group' option for searching by group name or gid.
 //config:
 //config:config FEATURE_FIND_NOT
 //config:      bool "Enable the 'not' (!) operator"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        Support the '!' operator to invert the test results.
-//config:        If 'Enable full-blown desktop' is enabled, then will also support
-//config:        the non-POSIX notation '-not'.
+//config:      Support the '!' operator to invert the test results.
+//config:      If 'Enable full-blown desktop' is enabled, then will also support
+//config:      the non-POSIX notation '-not'.
 //config:
 //config:config FEATURE_FIND_DEPTH
 //config:      bool "Enable -depth"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        Process each directory's contents before the directory itself.
+//config:      Process each directory's contents before the directory itself.
 //config:
 //config:config FEATURE_FIND_PAREN
 //config:      bool "Enable parens in options"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        Enable usage of parens '(' to specify logical order of arguments.
+//config:      Enable usage of parens '(' to specify logical order of arguments.
 //config:
 //config:config FEATURE_FIND_SIZE
 //config:      bool "Enable -size: file size matching"
 //config:      default y
 //config:      depends on FIND
-//config:      help
-//config:        Support the 'find -size' option for searching by file size.
 //config:
 //config:config FEATURE_FIND_PRUNE
 //config:      bool "Enable -prune: exclude subdirectories"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        If the file is a directory, dont descend into it. Useful for
-//config:        exclusion .svn and CVS directories.
+//config:      If the file is a directory, don't descend into it. Useful for
+//config:      exclusion .svn and CVS directories.
+//config:
+//config:config FEATURE_FIND_QUIT
+//config:      bool "Enable -quit: exit"
+//config:      default y
+//config:      depends on FIND
+//config:      help
+//config:      If this action is reached, 'find' exits.
 //config:
 //config:config FEATURE_FIND_DELETE
 //config:      bool "Enable -delete: delete files/dirs"
 //config:      default y
 //config:      depends on FIND && FEATURE_FIND_DEPTH
 //config:      help
-//config:        Support the 'find -delete' option for deleting files and directories.
-//config:        WARNING: This option can do much harm if used wrong. Busybox will not
-//config:        try to protect the user from doing stupid things. Use with care.
+//config:      Support the 'find -delete' option for deleting files and directories.
+//config:      WARNING: This option can do much harm if used wrong. Busybox will not
+//config:      try to protect the user from doing stupid things. Use with care.
+//config:
+//config:config FEATURE_FIND_EMPTY
+//config:      bool "Enable -empty: match empty files or directories"
+//config:      default y
+//config:      depends on FIND
+//config:      help
+//config:      Support the 'find -empty' option to find empty regular files
+//config:      or directories.
 //config:
 //config:config FEATURE_FIND_PATH
 //config:      bool "Enable -path: match pathname with shell pattern"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        The -path option matches whole pathname instead of just filename.
+//config:      The -path option matches whole pathname instead of just filename.
 //config:
 //config:config FEATURE_FIND_REGEX
 //config:      bool "Enable -regex: match pathname with regex"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        The -regex option matches whole pathname against regular expression.
+//config:      The -regex option matches whole pathname against regular expression.
 //config:
 //config:config FEATURE_FIND_CONTEXT
 //config:      bool "Enable -context: security context matching"
 //config:      default n
 //config:      depends on FIND && SELINUX
 //config:      help
-//config:        Support the 'find -context' option for matching security context.
+//config:      Support the 'find -context' option for matching security context.
 //config:
 //config:config FEATURE_FIND_LINKS
 //config:      bool "Enable -links: link count matching"
 //config:      default y
 //config:      depends on FIND
 //config:      help
-//config:        Support the 'find -links' option for matching number of links.
+//config:      Support the 'find -links' option for matching number of links.
 
 //applet:IF_FIND(APPLET_NOEXEC(find, find, BB_DIR_USR_BIN, BB_SUID_DROP, find))
 
 //usage:     "\n       -regex PATTERN  Match path to regex PATTERN"
 //usage:       )
 //usage:       IF_FEATURE_FIND_TYPE(
-//usage:     "\n       -type X         File type is X (one of: f,d,l,b,c,...)"
+//usage:     "\n       -type X         File type is X (one of: f,d,l,b,c,s,p)"
+//usage:       )
+//usage:       IF_FEATURE_FIND_EXECUTABLE(
+//usage:     "\n       -executable     File is executable"
 //usage:       )
 //usage:       IF_FEATURE_FIND_PERM(
 //usage:     "\n       -perm MASK      At least one mask bit (+MASK), all bits (-MASK),"
 //usage:       IF_FEATURE_FIND_CONTEXT(
 //usage:     "\n       -context CTX    File has specified security context"
 //usage:       )
+//usage:       IF_FEATURE_FIND_EMPTY(
+//usage:     "\n       -empty          Match empty file/directory"
+//usage:       )
 //usage:       IF_FEATURE_FIND_PRUNE(
 //usage:     "\n       -prune          If current file is directory, don't descend into it"
 //usage:       )
 //usage:       IF_FEATURE_FIND_DELETE(
 //usage:     "\n       -delete         Delete current file/directory. Turns on -depth option"
 //usage:       )
+//usage:       IF_FEATURE_FIND_QUIT(
+//usage:     "\n       -quit           Exit"
+//usage:       )
 //usage:
 //usage:#define find_example_usage
 //usage:       "$ find / -name passwd\n"
@@ -382,6 +394,7 @@ IF_FEATURE_FIND_PATH(   ACTS(path,  const char *pattern; bool ipath;))
 IF_FEATURE_FIND_REGEX(  ACTS(regex, regex_t compiled_pattern;))
 IF_FEATURE_FIND_PRINT0( ACTS(print0))
 IF_FEATURE_FIND_TYPE(   ACTS(type,  int type_mask;))
+IF_FEATURE_FIND_EXECUTABLE(ACTS(executable))
 IF_FEATURE_FIND_PERM(   ACTS(perm,  char perm_char; mode_t perm_mask;))
 IF_FEATURE_FIND_MTIME(  ACTS(mtime, char mtime_char; unsigned mtime_days;))
 IF_FEATURE_FIND_MMIN(   ACTS(mmin,  char mmin_char; unsigned mmin_mins;))
@@ -392,7 +405,9 @@ IF_FEATURE_FIND_SIZE(   ACTS(size,  char size_char; off_t size;))
 IF_FEATURE_FIND_CONTEXT(ACTS(context, security_context_t context;))
 IF_FEATURE_FIND_PAREN(  ACTS(paren, action ***subexpr;))
 IF_FEATURE_FIND_PRUNE(  ACTS(prune))
+IF_FEATURE_FIND_QUIT(   ACTS(quit))
 IF_FEATURE_FIND_DELETE( ACTS(delete))
+IF_FEATURE_FIND_EMPTY(  ACTS(empty))
 IF_FEATURE_FIND_EXEC(   ACTS(exec,
                                char **exec_argv; /* -exec ARGS */
                                unsigned *subst_count;
@@ -419,11 +434,13 @@ struct globals {
        action ***actions;
        smallint need_print;
        smallint xdev_on;
+       smalluint exitstatus;
        recurse_flags_t recurse_flags;
        IF_FEATURE_FIND_EXEC_PLUS(unsigned max_argv_len;)
 } FIX_ALIASING;
 #define G (*(struct globals*)bb_common_bufsiz1)
 #define INIT_G() do { \
+       setup_common_bufsiz(); \
        BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \
        /* we have to zero it out because of NOEXEC */ \
        memset(&G, 0, sizeof(G)); \
@@ -501,26 +518,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
@@ -554,6 +599,12 @@ ACTF(type)
        return ((statbuf->st_mode & S_IFMT) == ap->type_mask);
 }
 #endif
+#if ENABLE_FEATURE_FIND_EXECUTABLE
+ACTF(executable)
+{
+       return access(fileName, X_OK) == 0;
+}
+#endif
 #if ENABLE_FEATURE_FIND_PERM
 ACTF(perm)
 {
@@ -762,6 +813,12 @@ ACTF(prune)
        return SKIP + TRUE;
 }
 #endif
+#if ENABLE_FEATURE_FIND_QUIT
+ACTF(quit)
+{
+       exit(G.exitstatus);
+}
+#endif
 #if ENABLE_FEATURE_FIND_DELETE
 ACTF(delete)
 {
@@ -779,6 +836,30 @@ ACTF(delete)
        return TRUE;
 }
 #endif
+#if ENABLE_FEATURE_FIND_EMPTY
+ACTF(empty)
+{
+       if (S_ISDIR(statbuf->st_mode)) {
+               DIR *dir;
+               struct dirent *dent;
+
+               dir = opendir(fileName);
+               if (!dir) {
+                       bb_simple_perror_msg(fileName);
+                       return FALSE;
+               }
+
+               while ((dent = readdir(dir)) != NULL
+                && DOT_OR_DOTDOT(dent->d_name)
+               ) {
+                       continue;
+               }
+               closedir(dir);
+               return dent == NULL;
+       }
+       return S_ISREG(statbuf->st_mode) && statbuf->st_size == 0;
+}
+#endif
 #if ENABLE_FEATURE_FIND_CONTEXT
 ACTF(context)
 {
@@ -942,8 +1023,11 @@ static action*** parse_params(char **argv)
                                PARM_print     ,
        IF_FEATURE_FIND_PRINT0( PARM_print0    ,)
        IF_FEATURE_FIND_PRUNE(  PARM_prune     ,)
+       IF_FEATURE_FIND_QUIT(   PARM_quit      ,)
        IF_FEATURE_FIND_DELETE( PARM_delete    ,)
+       IF_FEATURE_FIND_EMPTY(  PARM_empty     ,)
        IF_FEATURE_FIND_EXEC(   PARM_exec      ,)
+       IF_FEATURE_FIND_EXECUTABLE(PARM_executable,)
        IF_FEATURE_FIND_PAREN(  PARM_char_brace,)
        /* All options/actions starting from here require argument */
                                PARM_name      ,
@@ -985,8 +1069,11 @@ static action*** parse_params(char **argv)
                                "-print\0"
        IF_FEATURE_FIND_PRINT0( "-print0\0" )
        IF_FEATURE_FIND_PRUNE(  "-prune\0"  )
+       IF_FEATURE_FIND_QUIT(   "-quit\0"  )
        IF_FEATURE_FIND_DELETE( "-delete\0" )
+       IF_FEATURE_FIND_EMPTY(  "-empty\0"  )
        IF_FEATURE_FIND_EXEC(   "-exec\0"   )
+       IF_FEATURE_FIND_EXECUTABLE("-executable\0")
        IF_FEATURE_FIND_PAREN(  "(\0"       )
        /* All options/actions starting from here require argument */
                                "-name\0"
@@ -1140,6 +1227,12 @@ static action*** parse_params(char **argv)
                        (void) ALLOC_ACTION(prune);
                }
 #endif
+#if ENABLE_FEATURE_FIND_QUIT
+               else if (parm == PARM_quit) {
+                       dbg("%d", __LINE__);
+                       (void) ALLOC_ACTION(quit);
+               }
+#endif
 #if ENABLE_FEATURE_FIND_DELETE
                else if (parm == PARM_delete) {
                        dbg("%d", __LINE__);
@@ -1148,6 +1241,12 @@ static action*** parse_params(char **argv)
                        (void) ALLOC_ACTION(delete);
                }
 #endif
+#if ENABLE_FEATURE_FIND_EMPTY
+               else if (parm == PARM_empty) {
+                       dbg("%d", __LINE__);
+                       (void) ALLOC_ACTION(empty);
+               }
+#endif
 #if ENABLE_FEATURE_FIND_EXEC
                else if (parm == PARM_exec) {
                        int i;
@@ -1190,7 +1289,7 @@ static action*** parse_params(char **argv)
                         * coreutils expects {} to appear only once in "-exec +"
                         */
                        if (all_subst != 1 && ap->filelist)
-                               bb_error_msg_and_die("only one '{}' allowed for -exec +");
+                               bb_simple_error_msg_and_die("only one '{}' allowed for -exec +");
 # endif
                }
 #endif
@@ -1204,7 +1303,7 @@ static action*** parse_params(char **argv)
                        endarg = argv;
                        while (1) {
                                if (!*++endarg)
-                                       bb_error_msg_and_die("unpaired '('");
+                                       bb_simple_error_msg_and_die("unpaired '('");
                                if (LONE_CHAR(*endarg, '('))
                                        nested++;
                                else if (LONE_CHAR(*endarg, ')') && !--nested) {
@@ -1250,6 +1349,11 @@ static action*** parse_params(char **argv)
                        dbg("created:type mask:%x", ap->type_mask);
                }
 #endif
+#if ENABLE_FEATURE_FIND_EXECUTABLE
+               else if (parm == PARM_executable) {
+                       (void) ALLOC_ACTION(executable);
+               }
+#endif
 #if ENABLE_FEATURE_FIND_PERM
 /* -perm BITS   File's mode bits are exactly BITS (octal or symbolic).
  *              Symbolic modes use mode 0 as a point of departure.
@@ -1389,7 +1493,7 @@ static action*** parse_params(char **argv)
 int find_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int find_main(int argc UNUSED_PARAM, char **argv)
 {
-       int i, firstopt, status = EXIT_SUCCESS;
+       int i, firstopt;
        char **past_HLP, *saved;
 
        INIT_G();
@@ -1407,6 +1511,12 @@ int find_main(int argc UNUSED_PARAM, char **argv)
                        break;
                if (!saved[1])
                        break; /* it is "-" */
+               if (saved[1] == '-' && !saved[2]) {
+                       /* it is "--" */
+                       /* Try: find -- /dev/null */
+                       saved = *++past_HLP;
+                       break;
+               }
                if ((saved+1)[strspn(saved+1, "HLP")] != '\0')
                        break;
        }
@@ -1463,10 +1573,10 @@ int find_main(int argc UNUSED_PARAM, char **argv)
                                NULL,           /* user data */
                                0)              /* depth */
                ) {
-                       status |= EXIT_FAILURE;
+                       G.exitstatus |= EXIT_FAILURE;
                }
        }
 
-       IF_FEATURE_FIND_EXEC_PLUS(status |= flush_exec_plus();)
-       return status;
+       IF_FEATURE_FIND_EXEC_PLUS(G.exitstatus |= flush_exec_plus();)
+       return G.exitstatus;
 }