busybox/findutils/find.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Mini find implementation for busybox
   4 *
   5 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
   6 *
   7 * Reworked by David Douthitt <n9ubh@callsign.net> and
   8 *  Matt Kraai <kraai@alumni.carnegiemellon.edu>.
   9 *
  10 * Licensed under GPLv2, see file LICENSE in this source tree.
  11 */
  12
  13/* findutils-4.1.20:
  14 *
  15 * # find file.txt -exec 'echo {}' '{}  {}' ';'
  16 * find: echo file.txt: No such file or directory
  17 * # find file.txt -exec 'echo' '{}  {}' '; '
  18 * find: missing argument to `-exec'
  19 * # find file.txt -exec 'echo {}' '{}  {}' ';' junk
  20 * find: paths must precede expression
  21 * # find file.txt -exec 'echo {}' '{}  {}' ';' junk ';'
  22 * find: paths must precede expression
  23 * # find file.txt -exec 'echo' '{}  {}' ';'
  24 * file.txt  file.txt
  25 * (strace: execve("/bin/echo", ["echo", "file.txt  file.txt"], [ 30 vars ]))
  26 * # find file.txt -exec 'echo' '{}  {}' ';' -print -exec pwd ';'
  27 * file.txt  file.txt
  28 * file.txt
  29 * /tmp
  30 * # find -name '*.c' -o -name '*.h'
  31 * [shows files, *.c and *.h intermixed]
  32 * # find file.txt -name '*f*' -o -name '*t*'
  33 * file.txt
  34 * # find file.txt -name '*z*' -o -name '*t*'
  35 * file.txt
  36 * # find file.txt -name '*f*' -o -name '*z*'
  37 * file.txt
  38 *
  39 * # find t z -name '*t*' -print -o -name '*z*'
  40 * t
  41 * # find t z t z -name '*t*' -o -name '*z*' -print
  42 * z
  43 * z
  44 * # find t z t z '(' -name '*t*' -o -name '*z*' ')' -o -print
  45 * (no output)
  46 */
  47
  48/* Testing script
  49 * ./busybox find "$@" | tee /tmp/bb_find
  50 * echo ==================
  51 * /path/to/gnu/find "$@" | tee /tmp/std_find
  52 * echo ==================
  53 * diff -u /tmp/std_find /tmp/bb_find && echo Identical
  54 */
  55
  56//config:config FIND
  57//config:       bool "find"
  58//config:       default y
  59//config:       help
  60//config:         find is used to search your system to find specified files.
  61//config:
  62//config:config FEATURE_FIND_PRINT0
  63//config:       bool "Enable -print0: NUL-terminated output"
  64//config:       default y
  65//config:       depends on FIND
  66//config:       help
  67//config:         Causes output names to be separated by a NUL character
  68//config:         rather than a newline. This allows names that contain
  69//config:         newlines and other whitespace to be more easily
  70//config:         interpreted by other programs.
  71//config:
  72//config:config FEATURE_FIND_MTIME
  73//config:       bool "Enable -mtime: modified time matching"
  74//config:       default y
  75//config:       depends on FIND
  76//config:       help
  77//config:         Allow searching based on the modification time of
  78//config:         files, in days.
  79//config:
  80//config:config FEATURE_FIND_MMIN
  81//config:       bool "Enable -mmin: modified time matching by minutes"
  82//config:       default y
  83//config:       depends on FIND
  84//config:       help
  85//config:         Allow searching based on the modification time of
  86//config:         files, in minutes.
  87//config:
  88//config:config FEATURE_FIND_PERM
  89//config:       bool "Enable -perm: permissions matching"
  90//config:       default y
  91//config:       depends on FIND
  92//config:       help
  93//config:         Enable searching based on file permissions.
  94//config:
  95//config:config FEATURE_FIND_TYPE
  96//config:       bool "Enable -type: file type matching (file/dir/link/...)"
  97//config:       default y
  98//config:       depends on FIND
  99//config:       help
 100//config:         Enable searching based on file type (file,
 101//config:         directory, socket, device, etc.).
 102//config:
 103//config:config FEATURE_FIND_XDEV
 104//config:       bool "Enable -xdev: 'stay in filesystem'"
 105//config:       default y
 106//config:       depends on FIND
 107//config:       help
 108//config:         This option allows find to restrict searches to a single filesystem.
 109//config:
 110//config:config FEATURE_FIND_MAXDEPTH
 111//config:       bool "Enable -mindepth N and -maxdepth N"
 112//config:       default y
 113//config:       depends on FIND
 114//config:       help
 115//config:         This option enables -mindepth N and -maxdepth N option.
 116//config:
 117//config:config FEATURE_FIND_NEWER
 118//config:       bool "Enable -newer: compare file modification times"
 119//config:       default y
 120//config:       depends on FIND
 121//config:       help
 122//config:         Support the 'find -newer' option for finding any files which have
 123//config:         modification time that is more recent than the specified FILE.
 124//config:
 125//config:config FEATURE_FIND_INUM
 126//config:       bool "Enable -inum: inode number matching"
 127//config:       default y
 128//config:       depends on FIND
 129//config:       help
 130//config:         Support the 'find -inum' option for searching by inode number.
 131//config:
 132//config:config FEATURE_FIND_EXEC
 133//config:       bool "Enable -exec: execute commands"
 134//config:       default y
 135//config:       depends on FIND
 136//config:       help
 137//config:         Support the 'find -exec' option for executing commands based upon
 138//config:         the files matched.
 139//config:
 140//config:config FEATURE_FIND_USER
 141//config:       bool "Enable -user: username/uid matching"
 142//config:       default y
 143//config:       depends on FIND
 144//config:       help
 145//config:         Support the 'find -user' option for searching by username or uid.
 146//config:
 147//config:config FEATURE_FIND_GROUP
 148//config:       bool "Enable -group: group/gid matching"
 149//config:       default y
 150//config:       depends on FIND
 151//config:       help
 152//config:         Support the 'find -group' option for searching by group name or gid.
 153//config:
 154//config:config FEATURE_FIND_NOT
 155//config:       bool "Enable the 'not' (!) operator"
 156//config:       default y
 157//config:       depends on FIND
 158//config:       help
 159//config:         Support the '!' operator to invert the test results.
 160//config:         If 'Enable full-blown desktop' is enabled, then will also support
 161//config:         the non-POSIX notation '-not'.
 162//config:
 163//config:config FEATURE_FIND_DEPTH
 164//config:       bool "Enable -depth"
 165//config:       default y
 166//config:       depends on FIND
 167//config:       help
 168//config:         Process each directory's contents before the directory itself.
 169//config:
 170//config:config FEATURE_FIND_PAREN
 171//config:       bool "Enable parens in options"
 172//config:       default y
 173//config:       depends on FIND
 174//config:       help
 175//config:         Enable usage of parens '(' to specify logical order of arguments.
 176//config:
 177//config:config FEATURE_FIND_SIZE
 178//config:       bool "Enable -size: file size matching"
 179//config:       default y
 180//config:       depends on FIND
 181//config:       help
 182//config:         Support the 'find -size' option for searching by file size.
 183//config:
 184//config:config FEATURE_FIND_PRUNE
 185//config:       bool "Enable -prune: exclude subdirectories"
 186//config:       default y
 187//config:       depends on FIND
 188//config:       help
 189//config:         If the file is a directory, dont descend into it. Useful for
 190//config:         exclusion .svn and CVS directories.
 191//config:
 192//config:config FEATURE_FIND_DELETE
 193//config:       bool "Enable -delete: delete files/dirs"
 194//config:       default y
 195//config:       depends on FIND && FEATURE_FIND_DEPTH
 196//config:       help
 197//config:         Support the 'find -delete' option for deleting files and directories.
 198//config:         WARNING: This option can do much harm if used wrong. Busybox will not
 199//config:         try to protect the user from doing stupid things. Use with care.
 200//config:
 201//config:config FEATURE_FIND_PATH
 202//config:       bool "Enable -path: match pathname with shell pattern"
 203//config:       default y
 204//config:       depends on FIND
 205//config:       help
 206//config:         The -path option matches whole pathname instead of just filename.
 207//config:
 208//config:config FEATURE_FIND_REGEX
 209//config:       bool "Enable -regex: match pathname with regex"
 210//config:       default y
 211//config:       depends on FIND
 212//config:       help
 213//config:         The -regex option matches whole pathname against regular expression.
 214//config:
 215//config:config FEATURE_FIND_CONTEXT
 216//config:       bool "Enable -context: security context matching"
 217//config:       default n
 218//config:       depends on FIND && SELINUX
 219//config:       help
 220//config:         Support the 'find -context' option for matching security context.
 221//config:
 222//config:config FEATURE_FIND_LINKS
 223//config:       bool "Enable -links: link count matching"
 224//config:       default y
 225//config:       depends on FIND
 226//config:       help
 227//config:         Support the 'find -links' option for matching number of links.
 228
 229//applet:IF_FIND(APPLET_NOEXEC(find, find, BB_DIR_USR_BIN, BB_SUID_DROP, find))
 230
 231//kbuild:lib-$(CONFIG_FIND) += find.o
 232
 233//usage:#define find_trivial_usage
 234//usage:       "[PATH]... [OPTIONS] [ACTIONS]"
 235//usage:#define find_full_usage "\n\n"
 236//usage:       "Search for files and perform actions on them.\n"
 237//usage:       "First failed action stops processing of current file.\n"
 238//usage:       "Defaults: PATH is current directory, action is '-print'\n"
 239//usage:     "\n        -follow         Follow symlinks"
 240//usage:        IF_FEATURE_FIND_XDEV(
 241//usage:     "\n        -xdev           Don't descend directories on other filesystems"
 242//usage:        )
 243//usage:        IF_FEATURE_FIND_MAXDEPTH(
 244//usage:     "\n        -maxdepth N     Descend at most N levels. -maxdepth 0 applies"
 245//usage:     "\n                        actions to command line arguments only"
 246//usage:     "\n        -mindepth N     Don't act on first N levels"
 247//usage:        )
 248//usage:        IF_FEATURE_FIND_DEPTH(
 249//usage:     "\n        -depth          Act on directory *after* traversing it"
 250//usage:        )
 251//usage:     "\n"
 252//usage:     "\nActions:"
 253//usage:        IF_FEATURE_FIND_PAREN(
 254//usage:     "\n        ( ACTIONS )     Group actions for -o / -a"
 255//usage:        )
 256//usage:        IF_FEATURE_FIND_NOT(
 257//usage:     "\n        ! ACT           Invert ACT's success/failure"
 258//usage:        )
 259//usage:     "\n        ACT1 [-a] ACT2  If ACT1 fails, stop, else do ACT2"
 260//usage:     "\n        ACT1 -o ACT2    If ACT1 succeeds, stop, else do ACT2"
 261//usage:     "\n                        Note: -a has higher priority than -o"
 262//usage:     "\n        -name PATTERN   Match file name (w/o directory name) to PATTERN"
 263//usage:     "\n        -iname PATTERN  Case insensitive -name"
 264//usage:        IF_FEATURE_FIND_PATH(
 265//usage:     "\n        -path PATTERN   Match path to PATTERN"
 266//usage:     "\n        -ipath PATTERN  Case insensitive -path"
 267//usage:        )
 268//usage:        IF_FEATURE_FIND_REGEX(
 269//usage:     "\n        -regex PATTERN  Match path to regex PATTERN"
 270//usage:        )
 271//usage:        IF_FEATURE_FIND_TYPE(
 272//usage:     "\n        -type X         File type is X (one of: f,d,l,b,c,...)"
 273//usage:        )
 274//usage:        IF_FEATURE_FIND_PERM(
 275//usage:     "\n        -perm MASK      At least one mask bit (+MASK), all bits (-MASK),"
 276//usage:     "\n                        or exactly MASK bits are set in file's mode"
 277//usage:        )
 278//usage:        IF_FEATURE_FIND_MTIME(
 279//usage:     "\n        -mtime DAYS     mtime is greater than (+N), less than (-N),"
 280//usage:     "\n                        or exactly N days in the past"
 281//usage:        )
 282//usage:        IF_FEATURE_FIND_MMIN(
 283//usage:     "\n        -mmin MINS      mtime is greater than (+N), less than (-N),"
 284//usage:     "\n                        or exactly N minutes in the past"
 285//usage:        )
 286//usage:        IF_FEATURE_FIND_NEWER(
 287//usage:     "\n        -newer FILE     mtime is more recent than FILE's"
 288//usage:        )
 289//usage:        IF_FEATURE_FIND_INUM(
 290//usage:     "\n        -inum N         File has inode number N"
 291//usage:        )
 292//usage:        IF_FEATURE_FIND_USER(
 293//usage:     "\n        -user NAME/ID   File is owned by given user"
 294//usage:        )
 295//usage:        IF_FEATURE_FIND_GROUP(
 296//usage:     "\n        -group NAME/ID  File is owned by given group"
 297//usage:        )
 298//usage:        IF_FEATURE_FIND_SIZE(
 299//usage:     "\n        -size N[bck]    File size is N (c:bytes,k:kbytes,b:512 bytes(def.))"
 300//usage:     "\n                        +/-N: file size is bigger/smaller than N"
 301//usage:        )
 302//usage:        IF_FEATURE_FIND_LINKS(
 303//usage:     "\n        -links N        Number of links is greater than (+N), less than (-N),"
 304//usage:     "\n                        or exactly N"
 305//usage:        )
 306//usage:        IF_FEATURE_FIND_CONTEXT(
 307//usage:     "\n        -context CTX    File has specified security context"
 308//usage:        )
 309//usage:        IF_FEATURE_FIND_PRUNE(
 310//usage:     "\n        -prune          If current file is directory, don't descend into it"
 311//usage:        )
 312//usage:     "\nIf none of the following actions is specified, -print is assumed"
 313//usage:     "\n        -print          Print file name"
 314//usage:        IF_FEATURE_FIND_PRINT0(
 315//usage:     "\n        -print0         Print file name, NUL terminated"
 316//usage:        )
 317//usage:        IF_FEATURE_FIND_EXEC(
 318//usage:     "\n        -exec CMD ARG ; Run CMD with all instances of {} replaced by"
 319//usage:     "\n                        file name. Fails if CMD exits with nonzero"
 320//usage:        )
 321//usage:        IF_FEATURE_FIND_DELETE(
 322//usage:     "\n        -delete         Delete current file/directory. Turns on -depth option"
 323//usage:        )
 324//usage:
 325//usage:#define find_example_usage
 326//usage:       "$ find / -name passwd\n"
 327//usage:       "/etc/passwd\n"
 328
 329#include <fnmatch.h>
 330#include "libbb.h"
 331#if ENABLE_FEATURE_FIND_REGEX
 332# include "xregex.h"
 333#endif
 334/* GNUism: */
 335#ifndef FNM_CASEFOLD
 336# define FNM_CASEFOLD 0
 337#endif
 338
 339#define dbg(...) ((void)0)
 340/* #define dbg(...) bb_error_msg(__VA_ARGS__) */
 341
 342/* This is a NOEXEC applet. Be very careful! */
 343
 344
 345typedef int (*action_fp)(const char *fileName, const struct stat *statbuf, void *) FAST_FUNC;
 346
 347typedef struct {
 348        action_fp f;
 349#if ENABLE_FEATURE_FIND_NOT
 350        bool invert;
 351#endif
 352} action;
 353
 354#define ACTS(name, ...) typedef struct { action a; __VA_ARGS__ } action_##name;
 355#define ACTF(name) \
 356        static int FAST_FUNC func_##name(const char *fileName UNUSED_PARAM, \
 357                const struct stat *statbuf UNUSED_PARAM, \
 358                action_##name* ap UNUSED_PARAM)
 359
 360                        ACTS(print)
 361                        ACTS(name,  const char *pattern; bool iname;)
 362IF_FEATURE_FIND_PATH(   ACTS(path,  const char *pattern; bool ipath;))
 363IF_FEATURE_FIND_REGEX(  ACTS(regex, regex_t compiled_pattern;))
 364IF_FEATURE_FIND_PRINT0( ACTS(print0))
 365IF_FEATURE_FIND_TYPE(   ACTS(type,  int type_mask;))
 366IF_FEATURE_FIND_PERM(   ACTS(perm,  char perm_char; mode_t perm_mask;))
 367IF_FEATURE_FIND_MTIME(  ACTS(mtime, char mtime_char; unsigned mtime_days;))
 368IF_FEATURE_FIND_MMIN(   ACTS(mmin,  char mmin_char; unsigned mmin_mins;))
 369IF_FEATURE_FIND_NEWER(  ACTS(newer, time_t newer_mtime;))
 370IF_FEATURE_FIND_INUM(   ACTS(inum,  ino_t inode_num;))
 371IF_FEATURE_FIND_USER(   ACTS(user,  uid_t uid;))
 372IF_FEATURE_FIND_SIZE(   ACTS(size,  char size_char; off_t size;))
 373IF_FEATURE_FIND_CONTEXT(ACTS(context, security_context_t context;))
 374IF_FEATURE_FIND_PAREN(  ACTS(paren, action ***subexpr;))
 375IF_FEATURE_FIND_PRUNE(  ACTS(prune))
 376IF_FEATURE_FIND_DELETE( ACTS(delete))
 377IF_FEATURE_FIND_EXEC(   ACTS(exec,  char **exec_argv; unsigned *subst_count; int exec_argc;))
 378IF_FEATURE_FIND_GROUP(  ACTS(group, gid_t gid;))
 379IF_FEATURE_FIND_LINKS(  ACTS(links, char links_char; int links_count;))
 380
 381struct globals {
 382        IF_FEATURE_FIND_XDEV(dev_t *xdev_dev;)
 383        IF_FEATURE_FIND_XDEV(int xdev_count;)
 384#if ENABLE_FEATURE_FIND_MAXDEPTH
 385        int minmaxdepth[2];
 386#endif
 387        action ***actions;
 388        smallint need_print;
 389        smallint xdev_on;
 390        recurse_flags_t recurse_flags;
 391} FIX_ALIASING;
 392#define G (*(struct globals*)&bb_common_bufsiz1)
 393#define INIT_G() do { \
 394        struct G_sizecheck { \
 395                char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \
 396        }; \
 397        /* we have to zero it out because of NOEXEC */ \
 398        memset(&G, 0, sizeof(G)); \
 399        IF_FEATURE_FIND_MAXDEPTH(G.minmaxdepth[1] = INT_MAX;) \
 400        G.need_print = 1; \
 401        G.recurse_flags = ACTION_RECURSE; \
 402} while (0)
 403
 404#if ENABLE_FEATURE_FIND_EXEC
 405static unsigned count_subst(const char *str)
 406{
 407        unsigned count = 0;
 408        while ((str = strstr(str, "{}")) != NULL) {
 409                count++;
 410                str++;
 411        }
 412        return count;
 413}
 414
 415
 416static char* subst(const char *src, unsigned count, const char* filename)
 417{
 418        char *buf, *dst, *end;
 419        size_t flen = strlen(filename);
 420        /* we replace each '{}' with filename: growth by strlen-2 */
 421        buf = dst = xmalloc(strlen(src) + count*(flen-2) + 1);
 422        while ((end = strstr(src, "{}"))) {
 423                memcpy(dst, src, end - src);
 424                dst += end - src;
 425                src = end + 2;
 426                memcpy(dst, filename, flen);
 427                dst += flen;
 428        }
 429        strcpy(dst, src);
 430        return buf;
 431}
 432#endif
 433
 434/* Return values of ACTFs ('action functions') are a bit mask:
 435 * bit 1=1: prune (use SKIP constant for setting it)
 436 * bit 0=1: matched successfully (TRUE)
 437 */
 438
 439static int exec_actions(action ***appp, const char *fileName, const struct stat *statbuf)
 440{
 441        int cur_group;
 442        int cur_action;
 443        int rc = 0;
 444        action **app, *ap;
 445
 446        /* "action group" is a set of actions ANDed together.
 447         * groups are ORed together.
 448         * We simply evaluate each group until we find one in which all actions
 449         * succeed. */
 450
 451        /* -prune is special: if it is encountered, then we won't
 452         * descend into current directory. It doesn't matter whether
 453         * action group (in which -prune sits) will succeed or not:
 454         * find * -prune -name 'f*' -o -name 'm*' -- prunes every dir
 455         * find * -name 'f*' -o -prune -name 'm*' -- prunes all dirs
 456         *     not starting with 'f' */
 457
 458        /* We invert TRUE bit (bit 0). Now 1 there means 'failure'.
 459         * and bitwise OR in "rc |= TRUE ^ ap->f()" will:
 460         * (1) make SKIP (-prune) bit stick; and (2) detect 'failure'.
 461         * On return, bit is restored.  */
 462
 463        cur_group = -1;
 464        while ((app = appp[++cur_group]) != NULL) {
 465                rc &= ~TRUE; /* 'success' so far, clear TRUE bit */
 466                cur_action = -1;
 467                while (1) {
 468                        ap = app[++cur_action];
 469                        if (!ap) /* all actions in group were successful */
 470                                return rc ^ TRUE; /* restore TRUE bit */
 471                        rc |= TRUE ^ ap->f(fileName, statbuf, ap);
 472#if ENABLE_FEATURE_FIND_NOT
 473                        if (ap->invert) rc ^= TRUE;
 474#endif
 475                        dbg("grp %d action %d rc:0x%x", cur_group, cur_action, rc);
 476                        if (rc & TRUE) /* current group failed, try next */
 477                                break;
 478                }
 479        }
 480        dbg("returning:0x%x", rc ^ TRUE);
 481        return rc ^ TRUE; /* restore TRUE bit */
 482}
 483
 484
 485#if !FNM_CASEFOLD
 486static char *strcpy_upcase(char *dst, const char *src)
 487{
 488        char *d = dst;
 489        while (1) {
 490                unsigned char ch = *src++;
 491                if (ch >= 'a' && ch <= 'z')
 492                        ch -= ('a' - 'A');
 493                *d++ = ch;
 494                if (ch == '\0')
 495                        break;
 496        }
 497        return dst;
 498}
 499#endif
 500
 501ACTF(name)
 502{
 503        const char *tmp = bb_basename(fileName);
 504        if (tmp != fileName && *tmp == '\0') {
 505                /* "foo/bar/". Oh no... go back to 'b' */
 506                tmp--;
 507                while (tmp != fileName && *--tmp != '/')
 508                        continue;
 509                if (*tmp == '/')
 510                        tmp++;
 511        }
 512        /* Was using FNM_PERIOD flag too,
 513         * but somewhere between 4.1.20 and 4.4.0 GNU find stopped using it.
 514         * find -name '*foo' should match .foo too:
 515         */
 516#if FNM_CASEFOLD
 517        return fnmatch(ap->pattern, tmp, (ap->iname ? FNM_CASEFOLD : 0)) == 0;
 518#else
 519        if (ap->iname)
 520                tmp = strcpy_upcase(alloca(strlen(tmp) + 1), tmp);
 521        return fnmatch(ap->pattern, tmp, 0) == 0;
 522#endif
 523}
 524
 525#if ENABLE_FEATURE_FIND_PATH
 526ACTF(path)
 527{
 528# if FNM_CASEFOLD
 529        return fnmatch(ap->pattern, fileName, (ap->ipath ? FNM_CASEFOLD : 0)) == 0;
 530# else
 531        if (ap->ipath)
 532                fileName = strcpy_upcase(alloca(strlen(fileName) + 1), fileName);
 533        return fnmatch(ap->pattern, fileName, 0) == 0;
 534# endif
 535}
 536#endif
 537#if ENABLE_FEATURE_FIND_REGEX
 538ACTF(regex)
 539{
 540        regmatch_t match;
 541        if (regexec(&ap->compiled_pattern, fileName, 1, &match, 0 /*eflags*/))
 542                return 0; /* no match */
 543        if (match.rm_so)
 544                return 0; /* match doesn't start at pos 0 */
 545        if (fileName[match.rm_eo])
 546                return 0; /* match doesn't end exactly at end of pathname */
 547        return 1;
 548}
 549#endif
 550#if ENABLE_FEATURE_FIND_TYPE
 551ACTF(type)
 552{
 553        return ((statbuf->st_mode & S_IFMT) == ap->type_mask);
 554}
 555#endif
 556#if ENABLE_FEATURE_FIND_PERM
 557ACTF(perm)
 558{
 559        /* -perm +mode: at least one of perm_mask bits are set */
 560        if (ap->perm_char == '+')
 561                return (statbuf->st_mode & ap->perm_mask) != 0;
 562        /* -perm -mode: all of perm_mask are set */
 563        if (ap->perm_char == '-')
 564                return (statbuf->st_mode & ap->perm_mask) == ap->perm_mask;
 565        /* -perm mode: file mode must match perm_mask */
 566        return (statbuf->st_mode & 07777) == ap->perm_mask;
 567}
 568#endif
 569#if ENABLE_FEATURE_FIND_MTIME
 570ACTF(mtime)
 571{
 572        time_t file_age = time(NULL) - statbuf->st_mtime;
 573        time_t mtime_secs = ap->mtime_days * 24*60*60;
 574        if (ap->mtime_char == '+')
 575                return file_age >= mtime_secs + 24*60*60;
 576        if (ap->mtime_char == '-')
 577                return file_age < mtime_secs;
 578        /* just numeric mtime */
 579        return file_age >= mtime_secs && file_age < (mtime_secs + 24*60*60);
 580}
 581#endif
 582#if ENABLE_FEATURE_FIND_MMIN
 583ACTF(mmin)
 584{
 585        time_t file_age = time(NULL) - statbuf->st_mtime;
 586        time_t mmin_secs = ap->mmin_mins * 60;
 587        if (ap->mmin_char == '+')
 588                return file_age >= mmin_secs + 60;
 589        if (ap->mmin_char == '-')
 590                return file_age < mmin_secs;
 591        /* just numeric mmin */
 592        return file_age >= mmin_secs && file_age < (mmin_secs + 60);
 593}
 594#endif
 595#if ENABLE_FEATURE_FIND_NEWER
 596ACTF(newer)
 597{
 598        return (ap->newer_mtime < statbuf->st_mtime);
 599}
 600#endif
 601#if ENABLE_FEATURE_FIND_INUM
 602ACTF(inum)
 603{
 604        return (statbuf->st_ino == ap->inode_num);
 605}
 606#endif
 607#if ENABLE_FEATURE_FIND_EXEC
 608ACTF(exec)
 609{
 610        int i, rc;
 611#if ENABLE_USE_PORTABLE_CODE
 612        char **argv = alloca(sizeof(char*) * (ap->exec_argc + 1));
 613#else /* gcc 4.3.1 generates smaller code: */
 614        char *argv[ap->exec_argc + 1];
 615#endif
 616        for (i = 0; i < ap->exec_argc; i++)
 617                argv[i] = subst(ap->exec_argv[i], ap->subst_count[i], fileName);
 618        argv[i] = NULL; /* terminate the list */
 619
 620        rc = spawn_and_wait(argv);
 621        if (rc < 0)
 622                bb_simple_perror_msg(argv[0]);
 623
 624        i = 0;
 625        while (argv[i])
 626                free(argv[i++]);
 627        return rc == 0; /* return 1 if exitcode 0 */
 628}
 629#endif
 630#if ENABLE_FEATURE_FIND_USER
 631ACTF(user)
 632{
 633        return (statbuf->st_uid == ap->uid);
 634}
 635#endif
 636#if ENABLE_FEATURE_FIND_GROUP
 637ACTF(group)
 638{
 639        return (statbuf->st_gid == ap->gid);
 640}
 641#endif
 642#if ENABLE_FEATURE_FIND_PRINT0
 643ACTF(print0)
 644{
 645        printf("%s%c", fileName, '\0');
 646        return TRUE;
 647}
 648#endif
 649ACTF(print)
 650{
 651        puts(fileName);
 652        return TRUE;
 653}
 654#if ENABLE_FEATURE_FIND_PAREN
 655ACTF(paren)
 656{
 657        return exec_actions(ap->subexpr, fileName, statbuf);
 658}
 659#endif
 660#if ENABLE_FEATURE_FIND_SIZE
 661ACTF(size)
 662{
 663        if (ap->size_char == '+')
 664                return statbuf->st_size > ap->size;
 665        if (ap->size_char == '-')
 666                return statbuf->st_size < ap->size;
 667        return statbuf->st_size == ap->size;
 668}
 669#endif
 670#if ENABLE_FEATURE_FIND_PRUNE
 671/*
 672 * -prune: if -depth is not given, return true and do not descend
 673 * current dir; if -depth is given, return false with no effect.
 674 * Example:
 675 * find dir -name 'asm-*' -prune -o -name '*.[chS]' -print
 676 */
 677ACTF(prune)
 678{
 679        return SKIP + TRUE;
 680}
 681#endif
 682#if ENABLE_FEATURE_FIND_DELETE
 683ACTF(delete)
 684{
 685        int rc;
 686        if (S_ISDIR(statbuf->st_mode)) {
 687                rc = rmdir(fileName);
 688        } else {
 689                rc = unlink(fileName);
 690        }
 691        if (rc < 0)
 692                bb_simple_perror_msg(fileName);
 693        return TRUE;
 694}
 695#endif
 696#if ENABLE_FEATURE_FIND_CONTEXT
 697ACTF(context)
 698{
 699        security_context_t con;
 700        int rc;
 701
 702        if (G.recurse_flags & ACTION_FOLLOWLINKS) {
 703                rc = getfilecon(fileName, &con);
 704        } else {
 705                rc = lgetfilecon(fileName, &con);
 706        }
 707        if (rc < 0)
 708                return FALSE;
 709        rc = strcmp(ap->context, con);
 710        freecon(con);
 711        return rc == 0;
 712}
 713#endif
 714#if ENABLE_FEATURE_FIND_LINKS
 715ACTF(links)
 716{
 717        switch(ap->links_char) {
 718        case '-' : return (statbuf->st_nlink <  ap->links_count);
 719        case '+' : return (statbuf->st_nlink >  ap->links_count);
 720        default:   return (statbuf->st_nlink == ap->links_count);
 721        }
 722}
 723#endif
 724
 725static int FAST_FUNC fileAction(const char *fileName,
 726                struct stat *statbuf,
 727                void *userData UNUSED_PARAM,
 728                int depth IF_NOT_FEATURE_FIND_MAXDEPTH(UNUSED_PARAM))
 729{
 730        int r;
 731
 732#if ENABLE_FEATURE_FIND_MAXDEPTH
 733        if (depth < G.minmaxdepth[0])
 734                return TRUE; /* skip this, continue recursing */
 735        if (depth > G.minmaxdepth[1])
 736                return SKIP; /* stop recursing */
 737#endif
 738
 739        r = exec_actions(G.actions, fileName, statbuf);
 740        /* Had no explicit -print[0] or -exec? then print */
 741        if ((r & TRUE) && G.need_print)
 742                puts(fileName);
 743
 744#if ENABLE_FEATURE_FIND_MAXDEPTH
 745        if (S_ISDIR(statbuf->st_mode)) {
 746                if (depth == G.minmaxdepth[1])
 747                        return SKIP;
 748        }
 749#endif
 750#if ENABLE_FEATURE_FIND_XDEV
 751        /* -xdev stops on mountpoints, but AFTER mountpoit itself
 752         * is processed as usual */
 753        if (S_ISDIR(statbuf->st_mode)) {
 754                if (G.xdev_count) {
 755                        int i;
 756                        for (i = 0; i < G.xdev_count; i++) {
 757                                if (G.xdev_dev[i] == statbuf->st_dev)
 758                                        goto found;
 759                        }
 760                        return SKIP;
 761 found: ;
 762                }
 763        }
 764#endif
 765
 766        /* Cannot return 0: our caller, recursive_action(),
 767         * will perror() and skip dirs (if called on dir) */
 768        return (r & SKIP) ? SKIP : TRUE;
 769}
 770
 771
 772#if ENABLE_FEATURE_FIND_TYPE
 773static int find_type(const char *type)
 774{
 775        int mask = 0;
 776
 777        if (*type == 'b')
 778                mask = S_IFBLK;
 779        else if (*type == 'c')
 780                mask = S_IFCHR;
 781        else if (*type == 'd')
 782                mask = S_IFDIR;
 783        else if (*type == 'p')
 784                mask = S_IFIFO;
 785        else if (*type == 'f')
 786                mask = S_IFREG;
 787        else if (*type == 'l')
 788                mask = S_IFLNK;
 789        else if (*type == 's')
 790                mask = S_IFSOCK;
 791
 792        if (mask == 0 || type[1] != '\0')
 793                bb_error_msg_and_die(bb_msg_invalid_arg, type, "-type");
 794
 795        return mask;
 796}
 797#endif
 798
 799#if ENABLE_FEATURE_FIND_PERM \
 800 || ENABLE_FEATURE_FIND_MTIME || ENABLE_FEATURE_FIND_MMIN \
 801 || ENABLE_FEATURE_FIND_SIZE  || ENABLE_FEATURE_FIND_LINKS
 802static const char* plus_minus_num(const char* str)
 803{
 804        if (*str == '-' || *str == '+')
 805                str++;
 806        return str;
 807}
 808#endif
 809
 810static action*** parse_params(char **argv)
 811{
 812        enum {
 813                                OPT_FOLLOW     ,
 814        IF_FEATURE_FIND_XDEV(   OPT_XDEV       ,)
 815        IF_FEATURE_FIND_DEPTH(  OPT_DEPTH      ,)
 816                                PARM_a         ,
 817                                PARM_o         ,
 818        IF_FEATURE_FIND_NOT(    PARM_char_not  ,)
 819#if ENABLE_DESKTOP
 820                                PARM_and       ,
 821                                PARM_or        ,
 822        IF_FEATURE_FIND_NOT(    PARM_not       ,)
 823#endif
 824                                PARM_print     ,
 825        IF_FEATURE_FIND_PRINT0( PARM_print0    ,)
 826        IF_FEATURE_FIND_PRUNE(  PARM_prune     ,)
 827        IF_FEATURE_FIND_DELETE( PARM_delete    ,)
 828        IF_FEATURE_FIND_EXEC(   PARM_exec      ,)
 829        IF_FEATURE_FIND_PAREN(  PARM_char_brace,)
 830        /* All options/actions starting from here require argument */
 831                                PARM_name      ,
 832                                PARM_iname     ,
 833        IF_FEATURE_FIND_PATH(   PARM_path      ,)
 834#if ENABLE_DESKTOP
 835        /* -wholename is a synonym for -path */
 836        /* We support it because Linux kernel's "make tags" uses it */
 837        IF_FEATURE_FIND_PATH(   PARM_wholename ,)
 838#endif
 839        IF_FEATURE_FIND_PATH(   PARM_ipath     ,)
 840        IF_FEATURE_FIND_REGEX(  PARM_regex     ,)
 841        IF_FEATURE_FIND_TYPE(   PARM_type      ,)
 842        IF_FEATURE_FIND_PERM(   PARM_perm      ,)
 843        IF_FEATURE_FIND_MTIME(  PARM_mtime     ,)
 844        IF_FEATURE_FIND_MMIN(   PARM_mmin      ,)
 845        IF_FEATURE_FIND_NEWER(  PARM_newer     ,)
 846        IF_FEATURE_FIND_INUM(   PARM_inum      ,)
 847        IF_FEATURE_FIND_USER(   PARM_user      ,)
 848        IF_FEATURE_FIND_GROUP(  PARM_group     ,)
 849        IF_FEATURE_FIND_SIZE(   PARM_size      ,)
 850        IF_FEATURE_FIND_CONTEXT(PARM_context   ,)
 851        IF_FEATURE_FIND_LINKS(  PARM_links     ,)
 852        IF_FEATURE_FIND_MAXDEPTH(OPT_MINDEPTH,OPT_MAXDEPTH,)
 853        };
 854
 855        static const char params[] ALIGN1 =
 856                                "-follow\0"
 857        IF_FEATURE_FIND_XDEV(   "-xdev\0"                 )
 858        IF_FEATURE_FIND_DEPTH(  "-depth\0"                )
 859                                "-a\0"
 860                                "-o\0"
 861        IF_FEATURE_FIND_NOT(    "!\0"       )
 862#if ENABLE_DESKTOP
 863                                "-and\0"
 864                                "-or\0"
 865        IF_FEATURE_FIND_NOT(    "-not\0"    )
 866#endif
 867                                "-print\0"
 868        IF_FEATURE_FIND_PRINT0( "-print0\0" )
 869        IF_FEATURE_FIND_PRUNE(  "-prune\0"  )
 870        IF_FEATURE_FIND_DELETE( "-delete\0" )
 871        IF_FEATURE_FIND_EXEC(   "-exec\0"   )
 872        IF_FEATURE_FIND_PAREN(  "(\0"       )
 873        /* All options/actions starting from here require argument */
 874                                 "-name\0"
 875                                 "-iname\0"
 876        IF_FEATURE_FIND_PATH(   "-path\0"   )
 877#if ENABLE_DESKTOP
 878        IF_FEATURE_FIND_PATH(   "-wholename\0")
 879#endif
 880        IF_FEATURE_FIND_PATH(   "-ipath\0"  )
 881        IF_FEATURE_FIND_REGEX(  "-regex\0"  )
 882        IF_FEATURE_FIND_TYPE(   "-type\0"   )
 883        IF_FEATURE_FIND_PERM(   "-perm\0"   )
 884        IF_FEATURE_FIND_MTIME(  "-mtime\0"  )
 885        IF_FEATURE_FIND_MMIN(   "-mmin\0"   )
 886        IF_FEATURE_FIND_NEWER(  "-newer\0"  )
 887        IF_FEATURE_FIND_INUM(   "-inum\0"   )
 888        IF_FEATURE_FIND_USER(   "-user\0"   )
 889        IF_FEATURE_FIND_GROUP(  "-group\0"  )
 890        IF_FEATURE_FIND_SIZE(   "-size\0"   )
 891        IF_FEATURE_FIND_CONTEXT("-context\0")
 892        IF_FEATURE_FIND_LINKS(  "-links\0"  )
 893        IF_FEATURE_FIND_MAXDEPTH("-mindepth\0""-maxdepth\0")
 894        ;
 895
 896        action*** appp;
 897        unsigned cur_group = 0;
 898        unsigned cur_action = 0;
 899        IF_FEATURE_FIND_NOT( bool invert_flag = 0; )
 900
 901        /* This is the only place in busybox where we use nested function.
 902         * So far more standard alternatives were bigger. */
 903        /* Auto decl suppresses "func without a prototype" warning: */
 904        auto action* alloc_action(int sizeof_struct, action_fp f);
 905        action* alloc_action(int sizeof_struct, action_fp f)
 906        {
 907                action *ap;
 908                appp[cur_group] = xrealloc(appp[cur_group], (cur_action+2) * sizeof(*appp));
 909                appp[cur_group][cur_action++] = ap = xzalloc(sizeof_struct);
 910                appp[cur_group][cur_action] = NULL;
 911                ap->f = f;
 912                IF_FEATURE_FIND_NOT( ap->invert = invert_flag; )
 913                IF_FEATURE_FIND_NOT( invert_flag = 0; )
 914                return ap;
 915        }
 916
 917#define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name)
 918
 919        appp = xzalloc(2 * sizeof(appp[0])); /* appp[0],[1] == NULL */
 920
 921        while (*argv) {
 922                const char *arg = argv[0];
 923                int parm = index_in_strings(params, arg);
 924                const char *arg1 = argv[1];
 925
 926                dbg("arg:'%s' arg1:'%s' parm:%d PARM_type:%d", arg, arg1, parm, PARM_type);
 927
 928                if (parm >= PARM_name) {
 929                        /* All options/actions starting from -name require argument */
 930                        if (!arg1)
 931                                bb_error_msg_and_die(bb_msg_requires_arg, arg);
 932                        argv++;
 933                }
 934
 935                /* We can use big switch() here, but on i386
 936                 * it doesn't give smaller code. Other arches? */
 937
 938/* Options always return true. They always take effect
 939 * rather than being processed only when their place in the
 940 * expression is reached.
 941 */
 942                /* Options */
 943                if (parm == OPT_FOLLOW) {
 944                        dbg("follow enabled: %d", __LINE__);
 945                        G.recurse_flags |= ACTION_FOLLOWLINKS | ACTION_DANGLING_OK;
 946                }
 947#if ENABLE_FEATURE_FIND_XDEV
 948                else if (parm == OPT_XDEV) {
 949                        dbg("%d", __LINE__);
 950                        G.xdev_on = 1;
 951                }
 952#endif
 953#if ENABLE_FEATURE_FIND_MAXDEPTH
 954                else if (parm == OPT_MINDEPTH || parm == OPT_MINDEPTH + 1) {
 955                        dbg("%d", __LINE__);
 956                        G.minmaxdepth[parm - OPT_MINDEPTH] = xatoi_positive(arg1);
 957                }
 958#endif
 959#if ENABLE_FEATURE_FIND_DEPTH
 960                else if (parm == OPT_DEPTH) {
 961                        dbg("%d", __LINE__);
 962                        G.recurse_flags |= ACTION_DEPTHFIRST;
 963                }
 964#endif
 965/* Actions are grouped by operators
 966 * ( expr )              Force precedence
 967 * ! expr                True if expr is false
 968 * -not expr             Same as ! expr
 969 * expr1 [-a[nd]] expr2  And; expr2 is not evaluated if expr1 is false
 970 * expr1 -o[r] expr2     Or; expr2 is not evaluated if expr1 is true
 971 * expr1 , expr2         List; both expr1 and expr2 are always evaluated
 972 * We implement: (), -a, -o
 973 */
 974                /* Operators */
 975                else if (parm == PARM_a IF_DESKTOP(|| parm == PARM_and)) {
 976                        dbg("%d", __LINE__);
 977                        /* no further special handling required */
 978                }
 979                else if (parm == PARM_o IF_DESKTOP(|| parm == PARM_or)) {
 980                        dbg("%d", __LINE__);
 981                        /* start new OR group */
 982                        cur_group++;
 983                        appp = xrealloc(appp, (cur_group+2) * sizeof(*appp));
 984                        /*appp[cur_group] = NULL; - already NULL */
 985                        appp[cur_group+1] = NULL;
 986                        cur_action = 0;
 987                }
 988#if ENABLE_FEATURE_FIND_NOT
 989                else if (parm == PARM_char_not IF_DESKTOP(|| parm == PARM_not)) {
 990                        /* also handles "find ! ! -name 'foo*'" */
 991                        invert_flag ^= 1;
 992                        dbg("invert_flag:%d", invert_flag);
 993                }
 994#endif
 995                /* Actions */
 996                else if (parm == PARM_print) {
 997                        dbg("%d", __LINE__);
 998                        G.need_print = 0;
 999                        (void) ALLOC_ACTION(print);
1000                }
1001#if ENABLE_FEATURE_FIND_PRINT0
1002                else if (parm == PARM_print0) {
1003                        dbg("%d", __LINE__);
1004                        G.need_print = 0;
1005                        (void) ALLOC_ACTION(print0);
1006                }
1007#endif
1008#if ENABLE_FEATURE_FIND_PRUNE
1009                else if (parm == PARM_prune) {
1010                        dbg("%d", __LINE__);
1011                        (void) ALLOC_ACTION(prune);
1012                }
1013#endif
1014#if ENABLE_FEATURE_FIND_DELETE
1015                else if (parm == PARM_delete) {
1016                        dbg("%d", __LINE__);
1017                        G.need_print = 0;
1018                        G.recurse_flags |= ACTION_DEPTHFIRST;
1019                        (void) ALLOC_ACTION(delete);
1020                }
1021#endif
1022#if ENABLE_FEATURE_FIND_EXEC
1023                else if (parm == PARM_exec) {
1024                        int i;
1025                        action_exec *ap;
1026                        dbg("%d", __LINE__);
1027                        G.need_print = 0;
1028                        ap = ALLOC_ACTION(exec);
1029                        ap->exec_argv = ++argv; /* first arg after -exec */
1030                        /*ap->exec_argc = 0; - ALLOC_ACTION did it */
1031                        while (1) {
1032                                if (!*argv) /* did not see ';' or '+' until end */
1033                                        bb_error_msg_and_die(bb_msg_requires_arg, "-exec");
1034                                // find -exec echo Foo ">{}<" ";"
1035                                // executes "echo Foo >FILENAME<",
1036                                // find -exec echo Foo ">{}<" "+"
1037                                // executes "echo Foo FILENAME1 FILENAME2 FILENAME3...".
1038                                // TODO (so far we treat "+" just like ";")
1039                                if ((argv[0][0] == ';' || argv[0][0] == '+')
1040                                 && argv[0][1] == '\0'
1041                                ) {
1042                                        break;
1043                                }
1044                                argv++;
1045                                ap->exec_argc++;
1046                        }
1047                        if (ap->exec_argc == 0)
1048                                bb_error_msg_and_die(bb_msg_requires_arg, arg);
1049                        ap->subst_count = xmalloc(ap->exec_argc * sizeof(int));
1050                        i = ap->exec_argc;
1051                        while (i--)
1052                                ap->subst_count[i] = count_subst(ap->exec_argv[i]);
1053                }
1054#endif
1055#if ENABLE_FEATURE_FIND_PAREN
1056                else if (parm == PARM_char_brace) {
1057                        action_paren *ap;
1058                        char **endarg;
1059                        unsigned nested = 1;
1060
1061                        dbg("%d", __LINE__);
1062                        endarg = argv;
1063                        while (1) {
1064                                if (!*++endarg)
1065                                        bb_error_msg_and_die("unpaired '('");
1066                                if (LONE_CHAR(*endarg, '('))
1067                                        nested++;
1068                                else if (LONE_CHAR(*endarg, ')') && !--nested) {
1069                                        *endarg = NULL;
1070                                        break;
1071                                }
1072                        }
1073                        ap = ALLOC_ACTION(paren);
1074                        ap->subexpr = parse_params(argv + 1);
1075                        *endarg = (char*) ")"; /* restore NULLed parameter */
1076                        argv = endarg;
1077                }
1078#endif
1079                else if (parm == PARM_name || parm == PARM_iname) {
1080                        action_name *ap;
1081                        dbg("%d", __LINE__);
1082                        ap = ALLOC_ACTION(name);
1083                        ap->pattern = arg1;
1084                        ap->iname = (parm == PARM_iname);
1085                }
1086#if ENABLE_FEATURE_FIND_PATH
1087                else if (parm == PARM_path IF_DESKTOP(|| parm == PARM_wholename) || parm == PARM_ipath) {
1088                        action_path *ap;
1089                        dbg("%d", __LINE__);
1090                        ap = ALLOC_ACTION(path);
1091                        ap->pattern = arg1;
1092                        ap->ipath = (parm == PARM_ipath);
1093                }
1094#endif
1095#if ENABLE_FEATURE_FIND_REGEX
1096                else if (parm == PARM_regex) {
1097                        action_regex *ap;
1098                        dbg("%d", __LINE__);
1099                        ap = ALLOC_ACTION(regex);
1100                        xregcomp(&ap->compiled_pattern, arg1, 0 /*cflags*/);
1101                }
1102#endif
1103#if ENABLE_FEATURE_FIND_TYPE
1104                else if (parm == PARM_type) {
1105                        action_type *ap;
1106                        ap = ALLOC_ACTION(type);
1107                        ap->type_mask = find_type(arg1);
1108                        dbg("created:type mask:%x", ap->type_mask);
1109                }
1110#endif
1111#if ENABLE_FEATURE_FIND_PERM
1112/* -perm BITS   File's mode bits are exactly BITS (octal or symbolic).
1113 *              Symbolic modes use mode 0 as a point of departure.
1114 * -perm -BITS  All of the BITS are set in file's mode.
1115 * -perm +BITS  At least one of the BITS is set in file's mode.
1116 */
1117                else if (parm == PARM_perm) {
1118                        action_perm *ap;
1119                        dbg("%d", __LINE__);
1120                        ap = ALLOC_ACTION(perm);
1121                        ap->perm_char = arg1[0];
1122                        arg1 = plus_minus_num(arg1);
1123                        /*ap->perm_mask = 0; - ALLOC_ACTION did it */
1124                        if (!bb_parse_mode(arg1, &ap->perm_mask))
1125                                bb_error_msg_and_die("invalid mode '%s'", arg1);
1126                }
1127#endif
1128#if ENABLE_FEATURE_FIND_MTIME
1129                else if (parm == PARM_mtime) {
1130                        action_mtime *ap;
1131                        dbg("%d", __LINE__);
1132                        ap = ALLOC_ACTION(mtime);
1133                        ap->mtime_char = arg1[0];
1134                        ap->mtime_days = xatoul(plus_minus_num(arg1));
1135                }
1136#endif
1137#if ENABLE_FEATURE_FIND_MMIN
1138                else if (parm == PARM_mmin) {
1139                        action_mmin *ap;
1140                        dbg("%d", __LINE__);
1141                        ap = ALLOC_ACTION(mmin);
1142                        ap->mmin_char = arg1[0];
1143                        ap->mmin_mins = xatoul(plus_minus_num(arg1));
1144                }
1145#endif
1146#if ENABLE_FEATURE_FIND_NEWER
1147                else if (parm == PARM_newer) {
1148                        struct stat stat_newer;
1149                        action_newer *ap;
1150                        dbg("%d", __LINE__);
1151                        ap = ALLOC_ACTION(newer);
1152                        xstat(arg1, &stat_newer);
1153                        ap->newer_mtime = stat_newer.st_mtime;
1154                }
1155#endif
1156#if ENABLE_FEATURE_FIND_INUM
1157                else if (parm == PARM_inum) {
1158                        action_inum *ap;
1159                        dbg("%d", __LINE__);
1160                        ap = ALLOC_ACTION(inum);
1161                        ap->inode_num = xatoul(arg1);
1162                }
1163#endif
1164#if ENABLE_FEATURE_FIND_USER
1165                else if (parm == PARM_user) {
1166                        action_user *ap;
1167                        dbg("%d", __LINE__);
1168                        ap = ALLOC_ACTION(user);
1169                        ap->uid = bb_strtou(arg1, NULL, 10);
1170                        if (errno)
1171                                ap->uid = xuname2uid(arg1);
1172                }
1173#endif
1174#if ENABLE_FEATURE_FIND_GROUP
1175                else if (parm == PARM_group) {
1176                        action_group *ap;
1177                        dbg("%d", __LINE__);
1178                        ap = ALLOC_ACTION(group);
1179                        ap->gid = bb_strtou(arg1, NULL, 10);
1180                        if (errno)
1181                                ap->gid = xgroup2gid(arg1);
1182                }
1183#endif
1184#if ENABLE_FEATURE_FIND_SIZE
1185                else if (parm == PARM_size) {
1186/* -size n[bckw]: file uses n units of space
1187 * b (default): units are 512-byte blocks
1188 * c: 1 byte
1189 * k: kilobytes
1190 * w: 2-byte words
1191 */
1192#if ENABLE_LFS
1193#define XATOU_SFX xatoull_sfx
1194#else
1195#define XATOU_SFX xatoul_sfx
1196#endif
1197                        static const struct suffix_mult find_suffixes[] = {
1198                                { "c", 1 },
1199                                { "w", 2 },
1200                                { "", 512 },
1201                                { "b", 512 },
1202                                { "k", 1024 },
1203                                { "", 0 }
1204                        };
1205                        action_size *ap;
1206                        dbg("%d", __LINE__);
1207                        ap = ALLOC_ACTION(size);
1208                        ap->size_char = arg1[0];
1209                        ap->size = XATOU_SFX(plus_minus_num(arg1), find_suffixes);
1210                }
1211#endif
1212#if ENABLE_FEATURE_FIND_CONTEXT
1213                else if (parm == PARM_context) {
1214                        action_context *ap;
1215                        dbg("%d", __LINE__);
1216                        ap = ALLOC_ACTION(context);
1217                        /*ap->context = NULL; - ALLOC_ACTION did it */
1218                        /* SELinux headers erroneously declare non-const parameter */
1219                        if (selinux_raw_to_trans_context((char*)arg1, &ap->context))
1220                                bb_simple_perror_msg(arg1);
1221                }
1222#endif
1223#if ENABLE_FEATURE_FIND_LINKS
1224                else if (parm == PARM_links) {
1225                        action_links *ap;
1226                        dbg("%d", __LINE__);
1227                        ap = ALLOC_ACTION(links);
1228                        ap->links_char = arg1[0];
1229                        ap->links_count = xatoul(plus_minus_num(arg1));
1230                }
1231#endif
1232                else {
1233                        bb_error_msg("unrecognized: %s", arg);
1234                        bb_show_usage();
1235                }
1236                argv++;
1237        }
1238        dbg("exiting %s", __func__);
1239        return appp;
1240#undef ALLOC_ACTION
1241}
1242
1243int find_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1244int find_main(int argc UNUSED_PARAM, char **argv)
1245{
1246        int i, firstopt, status = EXIT_SUCCESS;
1247
1248        INIT_G();
1249
1250        argv++;
1251        for (firstopt = 0; argv[firstopt]; firstopt++) {
1252                if (argv[firstopt][0] == '-')
1253                        break;
1254                if (ENABLE_FEATURE_FIND_NOT && LONE_CHAR(argv[firstopt], '!'))
1255                        break;
1256                if (ENABLE_FEATURE_FIND_PAREN && LONE_CHAR(argv[firstopt], '('))
1257                        break;
1258        }
1259        if (firstopt == 0) {
1260                *--argv = (char*)".";
1261                firstopt++;
1262        }
1263
1264        G.actions = parse_params(&argv[firstopt]);
1265        argv[firstopt] = NULL;
1266
1267#if ENABLE_FEATURE_FIND_XDEV
1268        if (G.xdev_on) {
1269                struct stat stbuf;
1270
1271                G.xdev_count = firstopt;
1272                G.xdev_dev = xzalloc(G.xdev_count * sizeof(G.xdev_dev[0]));
1273                for (i = 0; argv[i]; i++) {
1274                        /* not xstat(): shouldn't bomb out on
1275                         * "find not_exist exist -xdev" */
1276                        if (stat(argv[i], &stbuf) == 0)
1277                                G.xdev_dev[i] = stbuf.st_dev;
1278                        /* else G.xdev_dev[i] stays 0 and
1279                         * won't match any real device dev_t
1280                         */
1281                }
1282        }
1283#endif
1284
1285        for (i = 0; argv[i]; i++) {
1286                if (!recursive_action(argv[i],
1287                                G.recurse_flags,/* flags */
1288                                fileAction,     /* file action */
1289                                fileAction,     /* dir action */
1290                                NULL,           /* user data */
1291                                0)              /* depth */
1292                ) {
1293                        status = EXIT_FAILURE;
1294                }
1295        }
1296
1297        return status;
1298}
1299