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