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 the GPL version 2, see the file LICENSE in this tarball.
  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#include <fnmatch.h>
  57#include "libbb.h"
  58#if ENABLE_FEATURE_FIND_REGEX
  59#include "xregex.h"
  60#endif
  61
  62/* This is a NOEXEC applet. Be very careful! */
  63
  64
  65typedef int (*action_fp)(const char *fileName, const struct stat *statbuf, void *) FAST_FUNC;
  66
  67typedef struct {
  68        action_fp f;
  69#if ENABLE_FEATURE_FIND_NOT
  70        bool invert;
  71#endif
  72} action;
  73
  74#define ACTS(name, ...) typedef struct { action a; __VA_ARGS__ } action_##name;
  75#define ACTF(name) \
  76        static int FAST_FUNC func_##name(const char *fileName UNUSED_PARAM, \
  77                const struct stat *statbuf UNUSED_PARAM, \
  78                action_##name* ap UNUSED_PARAM)
  79
  80                        ACTS(print)
  81                        ACTS(name,  const char *pattern; bool iname;)
  82IF_FEATURE_FIND_PATH(   ACTS(path,  const char *pattern;))
  83IF_FEATURE_FIND_REGEX(  ACTS(regex, regex_t compiled_pattern;))
  84IF_FEATURE_FIND_PRINT0( ACTS(print0))
  85IF_FEATURE_FIND_TYPE(   ACTS(type,  int type_mask;))
  86IF_FEATURE_FIND_PERM(   ACTS(perm,  char perm_char; mode_t perm_mask;))
  87IF_FEATURE_FIND_MTIME(  ACTS(mtime, char mtime_char; unsigned mtime_days;))
  88IF_FEATURE_FIND_MMIN(   ACTS(mmin,  char mmin_char; unsigned mmin_mins;))
  89IF_FEATURE_FIND_NEWER(  ACTS(newer, time_t newer_mtime;))
  90IF_FEATURE_FIND_INUM(   ACTS(inum,  ino_t inode_num;))
  91IF_FEATURE_FIND_USER(   ACTS(user,  uid_t uid;))
  92IF_FEATURE_FIND_SIZE(   ACTS(size,  char size_char; off_t size;))
  93IF_FEATURE_FIND_CONTEXT(ACTS(context, security_context_t context;))
  94IF_FEATURE_FIND_PAREN(  ACTS(paren, action ***subexpr;))
  95IF_FEATURE_FIND_PRUNE(  ACTS(prune))
  96IF_FEATURE_FIND_DELETE( ACTS(delete))
  97IF_FEATURE_FIND_EXEC(   ACTS(exec,  char **exec_argv; unsigned *subst_count; int exec_argc;))
  98IF_FEATURE_FIND_GROUP(  ACTS(group, gid_t gid;))
  99IF_FEATURE_FIND_LINKS(  ACTS(links, char links_char; int links_count;))
 100
 101struct globals {
 102        IF_FEATURE_FIND_XDEV(dev_t *xdev_dev;)
 103        IF_FEATURE_FIND_XDEV(int xdev_count;)
 104        action ***actions;
 105        bool need_print;
 106        recurse_flags_t recurse_flags;
 107};
 108#define G (*(struct globals*)&bb_common_bufsiz1)
 109#define INIT_G() do { \
 110        struct G_sizecheck { \
 111                char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \
 112        }; \
 113        /* we have to zero it out because of NOEXEC */ \
 114        memset(&G, 0, offsetof(struct globals, need_print)); \
 115        G.need_print = 1; \
 116        G.recurse_flags = ACTION_RECURSE; \
 117} while (0)
 118
 119#if ENABLE_FEATURE_FIND_EXEC
 120static unsigned count_subst(const char *str)
 121{
 122        unsigned count = 0;
 123        while ((str = strstr(str, "{}")) != NULL) {
 124                count++;
 125                str++;
 126        }
 127        return count;
 128}
 129
 130
 131static char* subst(const char *src, unsigned count, const char* filename)
 132{
 133        char *buf, *dst, *end;
 134        size_t flen = strlen(filename);
 135        /* we replace each '{}' with filename: growth by strlen-2 */
 136        buf = dst = xmalloc(strlen(src) + count*(flen-2) + 1);
 137        while ((end = strstr(src, "{}"))) {
 138                memcpy(dst, src, end - src);
 139                dst += end - src;
 140                src = end + 2;
 141                memcpy(dst, filename, flen);
 142                dst += flen;
 143        }
 144        strcpy(dst, src);
 145        return buf;
 146}
 147#endif
 148
 149/* Return values of ACTFs ('action functions') are a bit mask:
 150 * bit 1=1: prune (use SKIP constant for setting it)
 151 * bit 0=1: matched successfully (TRUE)
 152 */
 153
 154static int exec_actions(action ***appp, const char *fileName, const struct stat *statbuf)
 155{
 156        int cur_group;
 157        int cur_action;
 158        int rc = 0;
 159        action **app, *ap;
 160
 161        /* "action group" is a set of actions ANDed together.
 162         * groups are ORed together.
 163         * We simply evaluate each group until we find one in which all actions
 164         * succeed. */
 165
 166        /* -prune is special: if it is encountered, then we won't
 167         * descend into current directory. It doesn't matter whether
 168         * action group (in which -prune sits) will succeed or not:
 169         * find * -prune -name 'f*' -o -name 'm*' -- prunes every dir
 170         * find * -name 'f*' -o -prune -name 'm*' -- prunes all dirs
 171         *     not starting with 'f' */
 172
 173        /* We invert TRUE bit (bit 0). Now 1 there means 'failure'.
 174         * and bitwise OR in "rc |= TRUE ^ ap->f()" will:
 175         * (1) make SKIP (-prune) bit stick; and (2) detect 'failure'.
 176         * On return, bit is restored.  */
 177
 178        cur_group = -1;
 179        while ((app = appp[++cur_group]) != NULL) {
 180                rc &= ~TRUE; /* 'success' so far, clear TRUE bit */
 181                cur_action = -1;
 182                while (1) {
 183                        ap = app[++cur_action];
 184                        if (!ap) /* all actions in group were successful */
 185                                return rc ^ TRUE; /* restore TRUE bit */
 186                        rc |= TRUE ^ ap->f(fileName, statbuf, ap);
 187#if ENABLE_FEATURE_FIND_NOT
 188                        if (ap->invert) rc ^= TRUE;
 189#endif
 190                        if (rc & TRUE) /* current group failed, try next */
 191                                break;
 192                }
 193        }
 194        return rc ^ TRUE; /* restore TRUE bit */
 195}
 196
 197
 198ACTF(name)
 199{
 200        const char *tmp = bb_basename(fileName);
 201        if (tmp != fileName && !*tmp) { /* "foo/bar/". Oh no... go back to 'b' */
 202                tmp--;
 203                while (tmp != fileName && *--tmp != '/')
 204                        continue;
 205                if (*tmp == '/')
 206                        tmp++;
 207        }
 208        return fnmatch(ap->pattern, tmp, FNM_PERIOD | (ap->iname ? FNM_CASEFOLD : 0)) == 0;
 209}
 210
 211#if ENABLE_FEATURE_FIND_PATH
 212ACTF(path)
 213{
 214        return fnmatch(ap->pattern, fileName, 0) == 0;
 215}
 216#endif
 217#if ENABLE_FEATURE_FIND_REGEX
 218ACTF(regex)
 219{
 220        regmatch_t match;
 221        if (regexec(&ap->compiled_pattern, fileName, 1, &match, 0 /*eflags*/))
 222                return 0; /* no match */
 223        if (match.rm_so)
 224                return 0; /* match doesn't start at pos 0 */
 225        if (fileName[match.rm_eo])
 226                return 0; /* match doesn't end exactly at end of pathname */
 227        return 1;
 228}
 229#endif
 230#if ENABLE_FEATURE_FIND_TYPE
 231ACTF(type)
 232{
 233        return ((statbuf->st_mode & S_IFMT) == ap->type_mask);
 234}
 235#endif
 236#if ENABLE_FEATURE_FIND_PERM
 237ACTF(perm)
 238{
 239        /* -perm +mode: at least one of perm_mask bits are set */
 240        if (ap->perm_char == '+')
 241                return (statbuf->st_mode & ap->perm_mask) != 0;
 242        /* -perm -mode: all of perm_mask are set */
 243        if (ap->perm_char == '-')
 244                return (statbuf->st_mode & ap->perm_mask) == ap->perm_mask;
 245        /* -perm mode: file mode must match perm_mask */
 246        return (statbuf->st_mode & 07777) == ap->perm_mask;
 247}
 248#endif
 249#if ENABLE_FEATURE_FIND_MTIME
 250ACTF(mtime)
 251{
 252        time_t file_age = time(NULL) - statbuf->st_mtime;
 253        time_t mtime_secs = ap->mtime_days * 24*60*60;
 254        if (ap->mtime_char == '+')
 255                return file_age >= mtime_secs + 24*60*60;
 256        if (ap->mtime_char == '-')
 257                return file_age < mtime_secs;
 258        /* just numeric mtime */
 259        return file_age >= mtime_secs && file_age < (mtime_secs + 24*60*60);
 260}
 261#endif
 262#if ENABLE_FEATURE_FIND_MMIN
 263ACTF(mmin)
 264{
 265        time_t file_age = time(NULL) - statbuf->st_mtime;
 266        time_t mmin_secs = ap->mmin_mins * 60;
 267        if (ap->mmin_char == '+')
 268                return file_age >= mmin_secs + 60;
 269        if (ap->mmin_char == '-')
 270                return file_age < mmin_secs;
 271        /* just numeric mmin */
 272        return file_age >= mmin_secs && file_age < (mmin_secs + 60);
 273}
 274#endif
 275#if ENABLE_FEATURE_FIND_NEWER
 276ACTF(newer)
 277{
 278        return (ap->newer_mtime < statbuf->st_mtime);
 279}
 280#endif
 281#if ENABLE_FEATURE_FIND_INUM
 282ACTF(inum)
 283{
 284        return (statbuf->st_ino == ap->inode_num);
 285}
 286#endif
 287#if ENABLE_FEATURE_FIND_EXEC
 288ACTF(exec)
 289{
 290        int i, rc;
 291#if ENABLE_USE_PORTABLE_CODE
 292        char **argv = alloca(sizeof(char*) * (ap->exec_argc + 1));
 293#else /* gcc 4.3.1 generates smaller code: */
 294        char *argv[ap->exec_argc + 1];
 295#endif
 296        for (i = 0; i < ap->exec_argc; i++)
 297                argv[i] = subst(ap->exec_argv[i], ap->subst_count[i], fileName);
 298        argv[i] = NULL; /* terminate the list */
 299
 300        rc = spawn_and_wait(argv);
 301        if (rc < 0)
 302                bb_simple_perror_msg(argv[0]);
 303
 304        i = 0;
 305        while (argv[i])
 306                free(argv[i++]);
 307        return rc == 0; /* return 1 if exitcode 0 */
 308}
 309#endif
 310#if ENABLE_FEATURE_FIND_USER
 311ACTF(user)
 312{
 313        return (statbuf->st_uid == ap->uid);
 314}
 315#endif
 316#if ENABLE_FEATURE_FIND_GROUP
 317ACTF(group)
 318{
 319        return (statbuf->st_gid == ap->gid);
 320}
 321#endif
 322#if ENABLE_FEATURE_FIND_PRINT0
 323ACTF(print0)
 324{
 325        printf("%s%c", fileName, '\0');
 326        return TRUE;
 327}
 328#endif
 329ACTF(print)
 330{
 331        puts(fileName);
 332        return TRUE;
 333}
 334#if ENABLE_FEATURE_FIND_PAREN
 335ACTF(paren)
 336{
 337        return exec_actions(ap->subexpr, fileName, statbuf);
 338}
 339#endif
 340#if ENABLE_FEATURE_FIND_SIZE
 341ACTF(size)
 342{
 343        if (ap->size_char == '+')
 344                return statbuf->st_size > ap->size;
 345        if (ap->size_char == '-')
 346                return statbuf->st_size < ap->size;
 347        return statbuf->st_size == ap->size;
 348}
 349#endif
 350#if ENABLE_FEATURE_FIND_PRUNE
 351/*
 352 * -prune: if -depth is not given, return true and do not descend
 353 * current dir; if -depth is given, return false with no effect.
 354 * Example:
 355 * find dir -name 'asm-*' -prune -o -name '*.[chS]' -print
 356 */
 357ACTF(prune)
 358{
 359        return SKIP + TRUE;
 360}
 361#endif
 362#if ENABLE_FEATURE_FIND_DELETE
 363ACTF(delete)
 364{
 365        int rc;
 366        if (S_ISDIR(statbuf->st_mode)) {
 367                rc = rmdir(fileName);
 368        } else {
 369                rc = unlink(fileName);
 370        }
 371        if (rc < 0)
 372                bb_simple_perror_msg(fileName);
 373        return TRUE;
 374}
 375#endif
 376#if ENABLE_FEATURE_FIND_CONTEXT
 377ACTF(context)
 378{
 379        security_context_t con;
 380        int rc;
 381
 382        if (G.recurse_flags & ACTION_FOLLOWLINKS) {
 383                rc = getfilecon(fileName, &con);
 384        } else {
 385                rc = lgetfilecon(fileName, &con);
 386        }
 387        if (rc < 0)
 388                return FALSE;
 389        rc = strcmp(ap->context, con);
 390        freecon(con);
 391        return rc == 0;
 392}
 393#endif
 394#if ENABLE_FEATURE_FIND_LINKS
 395ACTF(links)
 396{
 397        switch(ap->links_char) {
 398        case '-' : return (statbuf->st_nlink <  ap->links_count);
 399        case '+' : return (statbuf->st_nlink >  ap->links_count);
 400        default:   return (statbuf->st_nlink == ap->links_count);
 401        }
 402}
 403#endif
 404
 405static int FAST_FUNC fileAction(const char *fileName,
 406                struct stat *statbuf,
 407                void *userData IF_NOT_FEATURE_FIND_MAXDEPTH(UNUSED_PARAM),
 408                int depth IF_NOT_FEATURE_FIND_MAXDEPTH(UNUSED_PARAM))
 409{
 410        int r;
 411#if ENABLE_FEATURE_FIND_MAXDEPTH
 412#define minmaxdepth ((int*)userData)
 413
 414        if (depth < minmaxdepth[0])
 415                return TRUE; /* skip this, continue recursing */
 416        if (depth > minmaxdepth[1])
 417                return SKIP; /* stop recursing */
 418#endif
 419
 420        r = exec_actions(G.actions, fileName, statbuf);
 421        /* Had no explicit -print[0] or -exec? then print */
 422        if ((r & TRUE) && G.need_print)
 423                puts(fileName);
 424
 425#if ENABLE_FEATURE_FIND_MAXDEPTH
 426        if (S_ISDIR(statbuf->st_mode)) {
 427                if (depth == minmaxdepth[1])
 428                        return SKIP;
 429        }
 430#endif
 431#if ENABLE_FEATURE_FIND_XDEV
 432        /* -xdev stops on mountpoints, but AFTER mountpoit itself
 433         * is processed as usual */
 434        if (S_ISDIR(statbuf->st_mode)) {
 435                if (G.xdev_count) {
 436                        int i;
 437                        for (i = 0; i < G.xdev_count; i++) {
 438                                if (G.xdev_dev[i] == statbuf->st_dev)
 439                                        goto found;
 440                        }
 441                        return SKIP;
 442 found: ;
 443                }
 444        }
 445#endif
 446
 447        /* Cannot return 0: our caller, recursive_action(),
 448         * will perror() and skip dirs (if called on dir) */
 449        return (r & SKIP) ? SKIP : TRUE;
 450#undef minmaxdepth
 451}
 452
 453
 454#if ENABLE_FEATURE_FIND_TYPE
 455static int find_type(const char *type)
 456{
 457        int mask = 0;
 458
 459        if (*type == 'b')
 460                mask = S_IFBLK;
 461        else if (*type == 'c')
 462                mask = S_IFCHR;
 463        else if (*type == 'd')
 464                mask = S_IFDIR;
 465        else if (*type == 'p')
 466                mask = S_IFIFO;
 467        else if (*type == 'f')
 468                mask = S_IFREG;
 469        else if (*type == 'l')
 470                mask = S_IFLNK;
 471        else if (*type == 's')
 472                mask = S_IFSOCK;
 473
 474        if (mask == 0 || type[1] != '\0')
 475                bb_error_msg_and_die(bb_msg_invalid_arg, type, "-type");
 476
 477        return mask;
 478}
 479#endif
 480
 481#if ENABLE_FEATURE_FIND_PERM \
 482 || ENABLE_FEATURE_FIND_MTIME || ENABLE_FEATURE_FIND_MMIN \
 483 || ENABLE_FEATURE_FIND_SIZE  || ENABLE_FEATURE_FIND_LINKS
 484static const char* plus_minus_num(const char* str)
 485{
 486        if (*str == '-' || *str == '+')
 487                str++;
 488        return str;
 489}
 490#endif
 491
 492static action*** parse_params(char **argv)
 493{
 494        enum {
 495                                PARM_a         ,
 496                                PARM_o         ,
 497        IF_FEATURE_FIND_NOT(    PARM_char_not  ,)
 498#if ENABLE_DESKTOP
 499                                PARM_and       ,
 500                                PARM_or        ,
 501        IF_FEATURE_FIND_NOT(    PARM_not       ,)
 502#endif
 503                                PARM_print     ,
 504        IF_FEATURE_FIND_PRINT0( PARM_print0    ,)
 505        IF_FEATURE_FIND_DEPTH(  PARM_depth     ,)
 506        IF_FEATURE_FIND_PRUNE(  PARM_prune     ,)
 507        IF_FEATURE_FIND_DELETE( PARM_delete    ,)
 508        IF_FEATURE_FIND_EXEC(   PARM_exec      ,)
 509        IF_FEATURE_FIND_PAREN(  PARM_char_brace,)
 510        /* All options starting from here require argument */
 511                                PARM_name      ,
 512                                PARM_iname     ,
 513        IF_FEATURE_FIND_PATH(   PARM_path      ,)
 514        IF_FEATURE_FIND_REGEX(  PARM_regex     ,)
 515        IF_FEATURE_FIND_TYPE(   PARM_type      ,)
 516        IF_FEATURE_FIND_PERM(   PARM_perm      ,)
 517        IF_FEATURE_FIND_MTIME(  PARM_mtime     ,)
 518        IF_FEATURE_FIND_MMIN(   PARM_mmin      ,)
 519        IF_FEATURE_FIND_NEWER(  PARM_newer     ,)
 520        IF_FEATURE_FIND_INUM(   PARM_inum      ,)
 521        IF_FEATURE_FIND_USER(   PARM_user      ,)
 522        IF_FEATURE_FIND_GROUP(  PARM_group     ,)
 523        IF_FEATURE_FIND_SIZE(   PARM_size      ,)
 524        IF_FEATURE_FIND_CONTEXT(PARM_context   ,)
 525        IF_FEATURE_FIND_LINKS(  PARM_links     ,)
 526        };
 527
 528        static const char params[] ALIGN1 =
 529                                 "-a\0"
 530                                 "-o\0"
 531        IF_FEATURE_FIND_NOT(    "!\0"       )
 532#if ENABLE_DESKTOP
 533                                 "-and\0"
 534                                 "-or\0"
 535        IF_FEATURE_FIND_NOT(     "-not\0"    )
 536#endif
 537                                 "-print\0"
 538        IF_FEATURE_FIND_PRINT0( "-print0\0" )
 539        IF_FEATURE_FIND_DEPTH(  "-depth\0"  )
 540        IF_FEATURE_FIND_PRUNE(  "-prune\0"  )
 541        IF_FEATURE_FIND_DELETE( "-delete\0" )
 542        IF_FEATURE_FIND_EXEC(   "-exec\0"   )
 543        IF_FEATURE_FIND_PAREN(  "(\0"       )
 544        /* All options starting from here require argument */
 545                                 "-name\0"
 546                                 "-iname\0"
 547        IF_FEATURE_FIND_PATH(   "-path\0"   )
 548        IF_FEATURE_FIND_REGEX(  "-regex\0"  )
 549        IF_FEATURE_FIND_TYPE(   "-type\0"   )
 550        IF_FEATURE_FIND_PERM(   "-perm\0"   )
 551        IF_FEATURE_FIND_MTIME(  "-mtime\0"  )
 552        IF_FEATURE_FIND_MMIN(   "-mmin\0"   )
 553        IF_FEATURE_FIND_NEWER(  "-newer\0"  )
 554        IF_FEATURE_FIND_INUM(   "-inum\0"   )
 555        IF_FEATURE_FIND_USER(   "-user\0"   )
 556        IF_FEATURE_FIND_GROUP(  "-group\0"  )
 557        IF_FEATURE_FIND_SIZE(   "-size\0"   )
 558        IF_FEATURE_FIND_CONTEXT("-context\0")
 559        IF_FEATURE_FIND_LINKS(  "-links\0"  )
 560                                 ;
 561
 562        action*** appp;
 563        unsigned cur_group = 0;
 564        unsigned cur_action = 0;
 565        IF_FEATURE_FIND_NOT( bool invert_flag = 0; )
 566
 567        /* This is the only place in busybox where we use nested function.
 568         * So far more standard alternatives were bigger. */
 569        /* Suppress a warning "func without a prototype" */
 570        auto action* alloc_action(int sizeof_struct, action_fp f);
 571        action* alloc_action(int sizeof_struct, action_fp f)
 572        {
 573                action *ap;
 574                appp[cur_group] = xrealloc(appp[cur_group], (cur_action+2) * sizeof(*appp));
 575                appp[cur_group][cur_action++] = ap = xmalloc(sizeof_struct);
 576                appp[cur_group][cur_action] = NULL;
 577                ap->f = f;
 578                IF_FEATURE_FIND_NOT( ap->invert = invert_flag; )
 579                IF_FEATURE_FIND_NOT( invert_flag = 0; )
 580                return ap;
 581        }
 582
 583#define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name)
 584
 585        appp = xzalloc(2 * sizeof(appp[0])); /* appp[0],[1] == NULL */
 586
 587/* Actions have side effects and return a true or false value
 588 * We implement: -print, -print0, -exec
 589 *
 590 * The rest are tests.
 591 *
 592 * Tests and actions are grouped by operators
 593 * ( expr )              Force precedence
 594 * ! expr                True if expr is false
 595 * -not expr             Same as ! expr
 596 * expr1 [-a[nd]] expr2  And; expr2 is not evaluated if expr1 is false
 597 * expr1 -o[r] expr2     Or; expr2 is not evaluated if expr1 is true
 598 * expr1 , expr2         List; both expr1 and expr2 are always evaluated
 599 * We implement: (), -a, -o
 600 */
 601        while (*argv) {
 602                const char *arg = argv[0];
 603                int parm = index_in_strings(params, arg);
 604                const char *arg1 = argv[1];
 605
 606                if (parm >= PARM_name) {
 607                        /* All options starting from -name require argument */
 608                        if (!arg1)
 609                                bb_error_msg_and_die(bb_msg_requires_arg, arg);
 610                        argv++;
 611                }
 612
 613                /* We can use big switch() here, but on i386
 614                 * it doesn't give smaller code. Other arches? */
 615
 616        /* --- Operators --- */
 617                if (parm == PARM_a IF_DESKTOP(|| parm == PARM_and)) {
 618                        /* no further special handling required */
 619                }
 620                else if (parm == PARM_o IF_DESKTOP(|| parm == PARM_or)) {
 621                        /* start new OR group */
 622                        cur_group++;
 623                        appp = xrealloc(appp, (cur_group+2) * sizeof(*appp));
 624                        /*appp[cur_group] = NULL; - already NULL */
 625                        appp[cur_group+1] = NULL;
 626                        cur_action = 0;
 627                }
 628#if ENABLE_FEATURE_FIND_NOT
 629                else if (parm == PARM_char_not IF_DESKTOP(|| parm == PARM_not)) {
 630                        /* also handles "find ! ! -name 'foo*'" */
 631                        invert_flag ^= 1;
 632                }
 633#endif
 634
 635        /* --- Tests and actions --- */
 636                else if (parm == PARM_print) {
 637                        G.need_print = 0;
 638                        /* GNU find ignores '!' here: "find ! -print" */
 639                        IF_FEATURE_FIND_NOT( invert_flag = 0; )
 640                        (void) ALLOC_ACTION(print);
 641                }
 642#if ENABLE_FEATURE_FIND_PRINT0
 643                else if (parm == PARM_print0) {
 644                        G.need_print = 0;
 645                        IF_FEATURE_FIND_NOT( invert_flag = 0; )
 646                        (void) ALLOC_ACTION(print0);
 647                }
 648#endif
 649#if ENABLE_FEATURE_FIND_DEPTH
 650                else if (parm == PARM_depth) {
 651                        G.recurse_flags |= ACTION_DEPTHFIRST;
 652                }
 653#endif
 654#if ENABLE_FEATURE_FIND_PRUNE
 655                else if (parm == PARM_prune) {
 656                        IF_FEATURE_FIND_NOT( invert_flag = 0; )
 657                        (void) ALLOC_ACTION(prune);
 658                }
 659#endif
 660#if ENABLE_FEATURE_FIND_DELETE
 661                else if (parm == PARM_delete) {
 662                        G.need_print = 0;
 663                        G.recurse_flags |= ACTION_DEPTHFIRST;
 664                        (void) ALLOC_ACTION(delete);
 665                }
 666#endif
 667#if ENABLE_FEATURE_FIND_EXEC
 668                else if (parm == PARM_exec) {
 669                        int i;
 670                        action_exec *ap;
 671                        G.need_print = 0;
 672                        IF_FEATURE_FIND_NOT( invert_flag = 0; )
 673                        ap = ALLOC_ACTION(exec);
 674                        ap->exec_argv = ++argv; /* first arg after -exec */
 675                        ap->exec_argc = 0;
 676                        while (1) {
 677                                if (!*argv) /* did not see ';' until end */
 678                                        bb_error_msg_and_die("-exec CMD must end by ';'");
 679                                if (LONE_CHAR(argv[0], ';'))
 680                                        break;
 681                                argv++;
 682                                ap->exec_argc++;
 683                        }
 684                        if (ap->exec_argc == 0)
 685                                bb_error_msg_and_die(bb_msg_requires_arg, arg);
 686                        ap->subst_count = xmalloc(ap->exec_argc * sizeof(int));
 687                        i = ap->exec_argc;
 688                        while (i--)
 689                                ap->subst_count[i] = count_subst(ap->exec_argv[i]);
 690                }
 691#endif
 692#if ENABLE_FEATURE_FIND_PAREN
 693                else if (parm == PARM_char_brace) {
 694                        action_paren *ap;
 695                        char **endarg;
 696                        unsigned nested = 1;
 697
 698                        endarg = argv;
 699                        while (1) {
 700                                if (!*++endarg)
 701                                        bb_error_msg_and_die("unpaired '('");
 702                                if (LONE_CHAR(*endarg, '('))
 703                                        nested++;
 704                                else if (LONE_CHAR(*endarg, ')') && !--nested) {
 705                                        *endarg = NULL;
 706                                        break;
 707                                }
 708                        }
 709                        ap = ALLOC_ACTION(paren);
 710                        ap->subexpr = parse_params(argv + 1);
 711                        *endarg = (char*) ")"; /* restore NULLed parameter */
 712                        argv = endarg;
 713                }
 714#endif
 715                else if (parm == PARM_name || parm == PARM_iname) {
 716                        action_name *ap;
 717                        ap = ALLOC_ACTION(name);
 718                        ap->pattern = arg1;
 719                        ap->iname = (parm == PARM_iname);
 720                }
 721#if ENABLE_FEATURE_FIND_PATH
 722                else if (parm == PARM_path) {
 723                        action_path *ap;
 724                        ap = ALLOC_ACTION(path);
 725                        ap->pattern = arg1;
 726                }
 727#endif
 728#if ENABLE_FEATURE_FIND_REGEX
 729                else if (parm == PARM_regex) {
 730                        action_regex *ap;
 731                        ap = ALLOC_ACTION(regex);
 732                        xregcomp(&ap->compiled_pattern, arg1, 0 /*cflags*/);
 733                }
 734#endif
 735#if ENABLE_FEATURE_FIND_TYPE
 736                else if (parm == PARM_type) {
 737                        action_type *ap;
 738                        ap = ALLOC_ACTION(type);
 739                        ap->type_mask = find_type(arg1);
 740                }
 741#endif
 742#if ENABLE_FEATURE_FIND_PERM
 743/* -perm mode   File's permission bits are exactly mode (octal or symbolic).
 744 *              Symbolic modes use mode 0 as a point of departure.
 745 * -perm -mode  All of the permission bits mode are set for the file.
 746 * -perm +mode  Any of the permission bits mode are set for the file.
 747 */
 748                else if (parm == PARM_perm) {
 749                        action_perm *ap;
 750                        ap = ALLOC_ACTION(perm);
 751                        ap->perm_char = arg1[0];
 752                        arg1 = plus_minus_num(arg1);
 753                        ap->perm_mask = 0;
 754                        if (!bb_parse_mode(arg1, &ap->perm_mask))
 755                                bb_error_msg_and_die("invalid mode: %s", arg1);
 756                }
 757#endif
 758#if ENABLE_FEATURE_FIND_MTIME
 759                else if (parm == PARM_mtime) {
 760                        action_mtime *ap;
 761                        ap = ALLOC_ACTION(mtime);
 762                        ap->mtime_char = arg1[0];
 763                        ap->mtime_days = xatoul(plus_minus_num(arg1));
 764                }
 765#endif
 766#if ENABLE_FEATURE_FIND_MMIN
 767                else if (parm == PARM_mmin) {
 768                        action_mmin *ap;
 769                        ap = ALLOC_ACTION(mmin);
 770                        ap->mmin_char = arg1[0];
 771                        ap->mmin_mins = xatoul(plus_minus_num(arg1));
 772                }
 773#endif
 774#if ENABLE_FEATURE_FIND_NEWER
 775                else if (parm == PARM_newer) {
 776                        struct stat stat_newer;
 777                        action_newer *ap;
 778                        ap = ALLOC_ACTION(newer);
 779                        xstat(arg1, &stat_newer);
 780                        ap->newer_mtime = stat_newer.st_mtime;
 781                }
 782#endif
 783#if ENABLE_FEATURE_FIND_INUM
 784                else if (parm == PARM_inum) {
 785                        action_inum *ap;
 786                        ap = ALLOC_ACTION(inum);
 787                        ap->inode_num = xatoul(arg1);
 788                }
 789#endif
 790#if ENABLE_FEATURE_FIND_USER
 791                else if (parm == PARM_user) {
 792                        action_user *ap;
 793                        ap = ALLOC_ACTION(user);
 794                        ap->uid = bb_strtou(arg1, NULL, 10);
 795                        if (errno)
 796                                ap->uid = xuname2uid(arg1);
 797                }
 798#endif
 799#if ENABLE_FEATURE_FIND_GROUP
 800                else if (parm == PARM_group) {
 801                        action_group *ap;
 802                        ap = ALLOC_ACTION(group);
 803                        ap->gid = bb_strtou(arg1, NULL, 10);
 804                        if (errno)
 805                                ap->gid = xgroup2gid(arg1);
 806                }
 807#endif
 808#if ENABLE_FEATURE_FIND_SIZE
 809                else if (parm == PARM_size) {
 810/* -size n[bckw]: file uses n units of space
 811 * b (default): units are 512-byte blocks
 812 * c: 1 byte
 813 * k: kilobytes
 814 * w: 2-byte words
 815 */
 816#if ENABLE_LFS
 817#define XATOU_SFX xatoull_sfx
 818#else
 819#define XATOU_SFX xatoul_sfx
 820#endif
 821                        static const struct suffix_mult find_suffixes[] = {
 822                                { "c", 1 },
 823                                { "w", 2 },
 824                                { "", 512 },
 825                                { "b", 512 },
 826                                { "k", 1024 },
 827                                { "", 0 }
 828                        };
 829                        action_size *ap;
 830                        ap = ALLOC_ACTION(size);
 831                        ap->size_char = arg1[0];
 832                        ap->size = XATOU_SFX(plus_minus_num(arg1), find_suffixes);
 833                }
 834#endif
 835#if ENABLE_FEATURE_FIND_CONTEXT
 836                else if (parm == PARM_context) {
 837                        action_context *ap;
 838                        ap = ALLOC_ACTION(context);
 839                        ap->context = NULL;
 840                        /* SELinux headers erroneously declare non-const parameter */
 841                        if (selinux_raw_to_trans_context((char*)arg1, &ap->context))
 842                                bb_simple_perror_msg(arg1);
 843                }
 844#endif
 845#if ENABLE_FEATURE_FIND_LINKS
 846                else if (parm == PARM_links) {
 847                        action_links *ap;
 848                        ap = ALLOC_ACTION(links);
 849                        ap->links_char = arg1[0];
 850                        ap->links_count = xatoul(plus_minus_num(arg1));
 851                }
 852#endif
 853                else {
 854                        bb_error_msg("unrecognized: %s", arg);
 855                        bb_show_usage();
 856                }
 857                argv++;
 858        }
 859        return appp;
 860#undef ALLOC_ACTION
 861}
 862
 863
 864int find_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 865int find_main(int argc UNUSED_PARAM, char **argv)
 866{
 867        static const char options[] ALIGN1 =
 868                          "-follow\0"
 869IF_FEATURE_FIND_XDEV(    "-xdev\0"    )
 870IF_FEATURE_FIND_MAXDEPTH("-mindepth\0""-maxdepth\0")
 871                          ;
 872        enum {
 873                          OPT_FOLLOW,
 874IF_FEATURE_FIND_XDEV(    OPT_XDEV    ,)
 875IF_FEATURE_FIND_MAXDEPTH(OPT_MINDEPTH,)
 876        };
 877
 878        char *arg;
 879        char **argp;
 880        int i, firstopt, status = EXIT_SUCCESS;
 881#if ENABLE_FEATURE_FIND_MAXDEPTH
 882        int minmaxdepth[2] = { 0, INT_MAX };
 883#else
 884#define minmaxdepth NULL
 885#endif
 886
 887        INIT_G();
 888
 889        for (firstopt = 1; argv[firstopt]; firstopt++) {
 890                if (argv[firstopt][0] == '-')
 891                        break;
 892                if (ENABLE_FEATURE_FIND_NOT && LONE_CHAR(argv[firstopt], '!'))
 893                        break;
 894#if ENABLE_FEATURE_FIND_PAREN
 895                if (LONE_CHAR(argv[firstopt], '('))
 896                        break;
 897#endif
 898        }
 899        if (firstopt == 1) {
 900                argv[0] = (char*)".";
 901                argv--;
 902                firstopt++;
 903        }
 904
 905/* All options always return true. They always take effect
 906 * rather than being processed only when their place in the
 907 * expression is reached.
 908 * We implement: -follow, -xdev, -maxdepth
 909 */
 910        /* Process options, and replace then with -a */
 911        /* (-a will be ignored by recursive parser later) */
 912        argp = &argv[firstopt];
 913        while ((arg = argp[0])) {
 914                int opt = index_in_strings(options, arg);
 915                if (opt == OPT_FOLLOW) {
 916                        G.recurse_flags |= ACTION_FOLLOWLINKS | ACTION_DANGLING_OK;
 917                        argp[0] = (char*)"-a";
 918                }
 919#if ENABLE_FEATURE_FIND_XDEV
 920                if (opt == OPT_XDEV) {
 921                        struct stat stbuf;
 922                        if (!G.xdev_count) {
 923                                G.xdev_count = firstopt - 1;
 924                                G.xdev_dev = xzalloc(G.xdev_count * sizeof(G.xdev_dev[0]));
 925                                for (i = 1; i < firstopt; i++) {
 926                                        /* not xstat(): shouldn't bomb out on
 927                                         * "find not_exist exist -xdev" */
 928                                        if (stat(argv[i], &stbuf) == 0)
 929                                                G.xdev_dev[i-1] = stbuf.st_dev;
 930                                        /* else G.xdev_dev[i-1] stays 0 and
 931                                         * won't match any real device dev_t */
 932                                }
 933                        }
 934                        argp[0] = (char*)"-a";
 935                }
 936#endif
 937#if ENABLE_FEATURE_FIND_MAXDEPTH
 938                if (opt == OPT_MINDEPTH || opt == OPT_MINDEPTH + 1) {
 939                        if (!argp[1])
 940                                bb_show_usage();
 941                        minmaxdepth[opt - OPT_MINDEPTH] = xatoi_u(argp[1]);
 942                        argp[0] = (char*)"-a";
 943                        argp[1] = (char*)"-a";
 944                        argp++;
 945                }
 946#endif
 947                argp++;
 948        }
 949
 950        G.actions = parse_params(&argv[firstopt]);
 951
 952        for (i = 1; i < firstopt; i++) {
 953                if (!recursive_action(argv[i],
 954                                G.recurse_flags,/* flags */
 955                                fileAction,     /* file action */
 956                                fileAction,     /* dir action */
 957#if ENABLE_FEATURE_FIND_MAXDEPTH
 958                                minmaxdepth,    /* user data */
 959#else
 960                                NULL,           /* user data */
 961#endif
 962                                0))             /* depth */
 963                        status = EXIT_FAILURE;
 964        }
 965        return status;
 966}
 967