1 /*- 2 * Copyright (c) 1990, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Cimarron D. Taylor of the University of California, Berkeley. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #ifndef lint 38 #if 0 39 static const char sccsid[] = "@(#)function.c 8.10 (Berkeley) 5/4/95"; 40 #else 41 static const char rcsid[] = 42 "$FreeBSD$"; 43 #endif 44 #endif /* not lint */ 45 46 #include <sys/param.h> 47 #include <sys/ucred.h> 48 #include <sys/stat.h> 49 #include <sys/wait.h> 50 #include <sys/mount.h> 51 52 #include <dirent.h> 53 #include <err.h> 54 #include <errno.h> 55 #include <fnmatch.h> 56 #include <fts.h> 57 #include <grp.h> 58 #include <pwd.h> 59 #include <regex.h> 60 #include <stdio.h> 61 #include <stdlib.h> 62 #include <string.h> 63 #include <unistd.h> 64 65 #include "find.h" 66 67 #define COMPARE(a, b) { \ 68 switch (plan->flags) { \ 69 case F_EQUAL: \ 70 return (a == b); \ 71 case F_LESSTHAN: \ 72 return (a < b); \ 73 case F_GREATER: \ 74 return (a > b); \ 75 default: \ 76 abort(); \ 77 } \ 78 } 79 80 static int do_f_regex __P((PLAN *, FTSENT *, int)); 81 static PLAN *do_c_regex __P((char *, int)); 82 static PLAN *palloc __P((enum ntype, int (*) __P((PLAN *, FTSENT *)))); 83 84 /* 85 * find_parsenum -- 86 * Parse a string of the form [+-]# and return the value. 87 */ 88 static long long 89 find_parsenum(plan, option, vp, endch) 90 PLAN *plan; 91 char *option, *vp, *endch; 92 { 93 long long value; 94 char *endchar, *str; /* Pointer to character ending conversion. */ 95 96 /* Determine comparison from leading + or -. */ 97 str = vp; 98 switch (*str) { 99 case '+': 100 ++str; 101 plan->flags = F_GREATER; 102 break; 103 case '-': 104 ++str; 105 plan->flags = F_LESSTHAN; 106 break; 107 default: 108 plan->flags = F_EQUAL; 109 break; 110 } 111 112 /* 113 * Convert the string with strtoq(). Note, if strtoq() returns zero 114 * and endchar points to the beginning of the string we know we have 115 * a syntax error. 116 */ 117 value = strtoq(str, &endchar, 10); 118 if (value == 0 && endchar == str) 119 errx(1, "%s: %s: illegal numeric value", option, vp); 120 if (endchar[0] && (endch == NULL || endchar[0] != *endch)) 121 errx(1, "%s: %s: illegal trailing character", option, vp); 122 if (endch) 123 *endch = endchar[0]; 124 return (value); 125 } 126 127 /* 128 * The value of n for the inode times (atime, ctime, and mtime) is a range, 129 * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with 130 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the 131 * user wanted. Correct so that -1 is "less than 1". 132 */ 133 #define TIME_CORRECT(p, ttype) \ 134 if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \ 135 ++((p)->t_data); 136 137 /* 138 * -amin n functions -- 139 * 140 * True if the difference between the file access time and the 141 * current time is n min periods. 142 */ 143 int 144 f_amin(plan, entry) 145 PLAN *plan; 146 FTSENT *entry; 147 { 148 extern time_t now; 149 150 COMPARE((now - entry->fts_statp->st_atime + 151 60 - 1) / 60, plan->t_data); 152 } 153 154 PLAN * 155 c_amin(arg) 156 char *arg; 157 { 158 PLAN *new; 159 160 ftsoptions &= ~FTS_NOSTAT; 161 162 new = palloc(N_AMIN, f_amin); 163 new->t_data = find_parsenum(new, "-amin", arg, NULL); 164 TIME_CORRECT(new, N_AMIN); 165 return (new); 166 } 167 168 169 /* 170 * -atime n functions -- 171 * 172 * True if the difference between the file access time and the 173 * current time is n 24 hour periods. 174 */ 175 int 176 f_atime(plan, entry) 177 PLAN *plan; 178 FTSENT *entry; 179 { 180 extern time_t now; 181 182 COMPARE((now - entry->fts_statp->st_atime + 183 86400 - 1) / 86400, plan->t_data); 184 } 185 186 PLAN * 187 c_atime(arg) 188 char *arg; 189 { 190 PLAN *new; 191 192 ftsoptions &= ~FTS_NOSTAT; 193 194 new = palloc(N_ATIME, f_atime); 195 new->t_data = find_parsenum(new, "-atime", arg, NULL); 196 TIME_CORRECT(new, N_ATIME); 197 return (new); 198 } 199 200 201 /* 202 * -cmin n functions -- 203 * 204 * True if the difference between the last change of file 205 * status information and the current time is n min periods. 206 */ 207 int 208 f_cmin(plan, entry) 209 PLAN *plan; 210 FTSENT *entry; 211 { 212 extern time_t now; 213 214 COMPARE((now - entry->fts_statp->st_ctime + 215 60 - 1) / 60, plan->t_data); 216 } 217 218 PLAN * 219 c_cmin(arg) 220 char *arg; 221 { 222 PLAN *new; 223 224 ftsoptions &= ~FTS_NOSTAT; 225 226 new = palloc(N_CMIN, f_cmin); 227 new->t_data = find_parsenum(new, "-cmin", arg, NULL); 228 TIME_CORRECT(new, N_CMIN); 229 return (new); 230 } 231 232 /* 233 * -ctime n functions -- 234 * 235 * True if the difference between the last change of file 236 * status information and the current time is n 24 hour periods. 237 */ 238 int 239 f_ctime(plan, entry) 240 PLAN *plan; 241 FTSENT *entry; 242 { 243 extern time_t now; 244 245 COMPARE((now - entry->fts_statp->st_ctime + 246 86400 - 1) / 86400, plan->t_data); 247 } 248 249 PLAN * 250 c_ctime(arg) 251 char *arg; 252 { 253 PLAN *new; 254 255 ftsoptions &= ~FTS_NOSTAT; 256 257 new = palloc(N_CTIME, f_ctime); 258 new->t_data = find_parsenum(new, "-ctime", arg, NULL); 259 TIME_CORRECT(new, N_CTIME); 260 return (new); 261 } 262 263 264 /* 265 * -depth functions -- 266 * 267 * Always true, causes descent of the directory hierarchy to be done 268 * so that all entries in a directory are acted on before the directory 269 * itself. 270 */ 271 int 272 f_always_true(plan, entry) 273 PLAN *plan; 274 FTSENT *entry; 275 { 276 return (1); 277 } 278 279 PLAN * 280 c_depth() 281 { 282 isdepth = 1; 283 284 return (palloc(N_DEPTH, f_always_true)); 285 } 286 287 /* 288 * [-exec | -ok] utility [arg ... ] ; functions -- 289 * 290 * True if the executed utility returns a zero value as exit status. 291 * The end of the primary expression is delimited by a semicolon. If 292 * "{}" occurs anywhere, it gets replaced by the current pathname. 293 * The current directory for the execution of utility is the same as 294 * the current directory when the find utility was started. 295 * 296 * The primary -ok is different in that it requests affirmation of the 297 * user before executing the utility. 298 */ 299 int 300 f_exec(plan, entry) 301 register PLAN *plan; 302 FTSENT *entry; 303 { 304 extern int dotfd; 305 register int cnt; 306 pid_t pid; 307 int status; 308 309 for (cnt = 0; plan->e_argv[cnt]; ++cnt) 310 if (plan->e_len[cnt]) 311 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt], 312 entry->fts_path, plan->e_len[cnt]); 313 314 if (plan->flags == F_NEEDOK && !queryuser(plan->e_argv)) 315 return (0); 316 317 /* make sure find output is interspersed correctly with subprocesses */ 318 fflush(stdout); 319 320 switch (pid = fork()) { 321 case -1: 322 err(1, "fork"); 323 /* NOTREACHED */ 324 case 0: 325 if (fchdir(dotfd)) { 326 warn("chdir"); 327 _exit(1); 328 } 329 execvp(plan->e_argv[0], plan->e_argv); 330 warn("%s", plan->e_argv[0]); 331 _exit(1); 332 } 333 pid = waitpid(pid, &status, 0); 334 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status)); 335 } 336 337 /* 338 * c_exec -- 339 * build three parallel arrays, one with pointers to the strings passed 340 * on the command line, one with (possibly duplicated) pointers to the 341 * argv array, and one with integer values that are lengths of the 342 * strings, but also flags meaning that the string has to be massaged. 343 */ 344 PLAN * 345 c_exec(argvp, isok) 346 char ***argvp; 347 int isok; 348 { 349 PLAN *new; /* node returned */ 350 register int cnt; 351 register char **argv, **ap, *p; 352 353 isoutput = 1; 354 355 new = palloc(N_EXEC, f_exec); 356 if (isok) 357 new->flags = F_NEEDOK; 358 359 for (ap = argv = *argvp;; ++ap) { 360 if (!*ap) 361 errx(1, 362 "%s: no terminating \";\"", isok ? "-ok" : "-exec"); 363 if (**ap == ';') 364 break; 365 } 366 367 cnt = ap - *argvp + 1; 368 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *)); 369 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *)); 370 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int)); 371 372 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) { 373 new->e_orig[cnt] = *argv; 374 for (p = *argv; *p; ++p) 375 if (p[0] == '{' && p[1] == '}') { 376 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN); 377 new->e_len[cnt] = MAXPATHLEN; 378 break; 379 } 380 if (!*p) { 381 new->e_argv[cnt] = *argv; 382 new->e_len[cnt] = 0; 383 } 384 } 385 new->e_argv[cnt] = new->e_orig[cnt] = NULL; 386 387 *argvp = argv + 1; 388 return (new); 389 } 390 391 /* 392 * -empty functions -- 393 * 394 * True if the file or directory is empty 395 */ 396 int 397 f_empty(plan, entry) 398 PLAN *plan; 399 FTSENT *entry; 400 { 401 if (S_ISREG(entry->fts_statp->st_mode) && entry->fts_statp->st_size == 0) 402 return (1); 403 if (S_ISDIR(entry->fts_statp->st_mode)) { 404 struct dirent *dp; 405 int empty; 406 DIR *dir; 407 408 empty = 1; 409 dir = opendir(entry->fts_accpath); 410 if (dir == NULL) 411 err(1, "%s", entry->fts_accpath); 412 for (dp = readdir(dir); dp; dp = readdir(dir)) 413 if (dp->d_name[0] != '.' || 414 (dp->d_name[1] != '\0' && 415 (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) { 416 empty = 0; 417 break; 418 } 419 closedir(dir); 420 return (empty); 421 } 422 return (0); 423 } 424 425 PLAN * 426 c_empty() 427 { 428 ftsoptions &= ~FTS_NOSTAT; 429 430 return (palloc(N_EMPTY, f_empty)); 431 } 432 433 /* 434 * -execdir utility [arg ... ] ; functions -- 435 * 436 * True if the executed utility returns a zero value as exit status. 437 * The end of the primary expression is delimited by a semicolon. If 438 * "{}" occurs anywhere, it gets replaced by the unqualified pathname. 439 * The current directory for the execution of utility is the same as 440 * the directory where the file lives. 441 */ 442 int 443 f_execdir(plan, entry) 444 register PLAN *plan; 445 FTSENT *entry; 446 { 447 register int cnt; 448 pid_t pid; 449 int status; 450 char *file; 451 452 /* XXX - if file/dir ends in '/' this will not work -- can it? */ 453 if ((file = strrchr(entry->fts_path, '/'))) 454 file++; 455 else 456 file = entry->fts_path; 457 458 for (cnt = 0; plan->e_argv[cnt]; ++cnt) 459 if (plan->e_len[cnt]) 460 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt], 461 file, plan->e_len[cnt]); 462 463 /* don't mix output of command with find output */ 464 fflush(stdout); 465 fflush(stderr); 466 467 switch (pid = fork()) { 468 case -1: 469 err(1, "fork"); 470 /* NOTREACHED */ 471 case 0: 472 execvp(plan->e_argv[0], plan->e_argv); 473 warn("%s", plan->e_argv[0]); 474 _exit(1); 475 } 476 pid = waitpid(pid, &status, 0); 477 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status)); 478 } 479 480 /* 481 * c_execdir -- 482 * build three parallel arrays, one with pointers to the strings passed 483 * on the command line, one with (possibly duplicated) pointers to the 484 * argv array, and one with integer values that are lengths of the 485 * strings, but also flags meaning that the string has to be massaged. 486 */ 487 PLAN * 488 c_execdir(argvp) 489 char ***argvp; 490 { 491 PLAN *new; /* node returned */ 492 register int cnt; 493 register char **argv, **ap, *p; 494 495 ftsoptions &= ~FTS_NOSTAT; 496 isoutput = 1; 497 498 new = palloc(N_EXECDIR, f_execdir); 499 500 for (ap = argv = *argvp;; ++ap) { 501 if (!*ap) 502 errx(1, 503 "-execdir: no terminating \";\""); 504 if (**ap == ';') 505 break; 506 } 507 508 cnt = ap - *argvp + 1; 509 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *)); 510 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *)); 511 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int)); 512 513 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) { 514 new->e_orig[cnt] = *argv; 515 for (p = *argv; *p; ++p) 516 if (p[0] == '{' && p[1] == '}') { 517 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN); 518 new->e_len[cnt] = MAXPATHLEN; 519 break; 520 } 521 if (!*p) { 522 new->e_argv[cnt] = *argv; 523 new->e_len[cnt] = 0; 524 } 525 } 526 new->e_argv[cnt] = new->e_orig[cnt] = NULL; 527 528 *argvp = argv + 1; 529 return (new); 530 } 531 532 /* 533 * -follow functions -- 534 * 535 * Always true, causes symbolic links to be followed on a global 536 * basis. 537 */ 538 PLAN * 539 c_follow() 540 { 541 ftsoptions &= ~FTS_PHYSICAL; 542 ftsoptions |= FTS_LOGICAL; 543 544 return (palloc(N_FOLLOW, f_always_true)); 545 } 546 547 /* 548 * -fstype functions -- 549 * 550 * True if the file is of a certain type. 551 */ 552 int 553 f_fstype(plan, entry) 554 PLAN *plan; 555 FTSENT *entry; 556 { 557 static dev_t curdev; /* need a guaranteed illegal dev value */ 558 static int first = 1; 559 struct statfs sb; 560 static int val_type, val_flags; 561 char *p, save[2]; 562 563 /* Only check when we cross mount point. */ 564 if (first || curdev != entry->fts_statp->st_dev) { 565 curdev = entry->fts_statp->st_dev; 566 567 /* 568 * Statfs follows symlinks; find wants the link's file system, 569 * not where it points. 570 */ 571 if (entry->fts_info == FTS_SL || 572 entry->fts_info == FTS_SLNONE) { 573 if ((p = strrchr(entry->fts_accpath, '/')) != NULL) 574 ++p; 575 else 576 p = entry->fts_accpath; 577 save[0] = p[0]; 578 p[0] = '.'; 579 save[1] = p[1]; 580 p[1] = '\0'; 581 582 } else 583 p = NULL; 584 585 if (statfs(entry->fts_accpath, &sb)) 586 err(1, "%s", entry->fts_accpath); 587 588 if (p) { 589 p[0] = save[0]; 590 p[1] = save[1]; 591 } 592 593 first = 0; 594 595 /* 596 * Further tests may need both of these values, so 597 * always copy both of them. 598 */ 599 val_flags = sb.f_flags; 600 val_type = sb.f_type; 601 } 602 switch (plan->flags) { 603 case F_MTFLAG: 604 return (val_flags & plan->mt_data) != 0; 605 case F_MTTYPE: 606 return (val_type == plan->mt_data); 607 default: 608 abort(); 609 } 610 } 611 612 #if !defined(__NetBSD__) 613 int 614 f_always_false(plan, entry) 615 PLAN *plan; 616 FTSENT *entry; 617 { 618 return (0); 619 } 620 621 PLAN * 622 c_fstype(arg) 623 char *arg; 624 { 625 register PLAN *new; 626 struct vfsconf vfc; 627 628 ftsoptions &= ~FTS_NOSTAT; 629 630 new = palloc(N_FSTYPE, f_fstype); 631 632 /* 633 * Check first for a filesystem name. 634 */ 635 if (getvfsbyname(arg, &vfc) == 0) { 636 new->flags = F_MTTYPE; 637 new->mt_data = vfc.vfc_typenum; 638 return (new); 639 } 640 641 switch (*arg) { 642 case 'l': 643 if (!strcmp(arg, "local")) { 644 new->flags = F_MTFLAG; 645 new->mt_data = MNT_LOCAL; 646 return (new); 647 } 648 break; 649 case 'r': 650 if (!strcmp(arg, "rdonly")) { 651 new->flags = F_MTFLAG; 652 new->mt_data = MNT_RDONLY; 653 return (new); 654 } 655 break; 656 } 657 /* 658 * We need to make filesystem checks for filesystems 659 * that exists but aren't in the kernel work. 660 */ 661 fprintf(stderr, "Warning: Unknown filesystem type %s\n", arg); 662 free(new); 663 664 return (palloc(N_FSTYPE, f_always_false)); 665 } 666 #endif 667 668 /* 669 * -group gname functions -- 670 * 671 * True if the file belongs to the group gname. If gname is numeric and 672 * an equivalent of the getgrnam() function does not return a valid group 673 * name, gname is taken as a group ID. 674 */ 675 int 676 f_group(plan, entry) 677 PLAN *plan; 678 FTSENT *entry; 679 { 680 return (entry->fts_statp->st_gid == plan->g_data); 681 } 682 683 PLAN * 684 c_group(gname) 685 char *gname; 686 { 687 PLAN *new; 688 struct group *g; 689 gid_t gid; 690 691 ftsoptions &= ~FTS_NOSTAT; 692 693 g = getgrnam(gname); 694 if (g == NULL) { 695 gid = atoi(gname); 696 if (gid == 0 && gname[0] != '0') 697 errx(1, "-group: %s: no such group", gname); 698 } else 699 gid = g->gr_gid; 700 701 new = palloc(N_GROUP, f_group); 702 new->g_data = gid; 703 return (new); 704 } 705 706 /* 707 * -inum n functions -- 708 * 709 * True if the file has inode # n. 710 */ 711 int 712 f_inum(plan, entry) 713 PLAN *plan; 714 FTSENT *entry; 715 { 716 COMPARE(entry->fts_statp->st_ino, plan->i_data); 717 } 718 719 PLAN * 720 c_inum(arg) 721 char *arg; 722 { 723 PLAN *new; 724 725 ftsoptions &= ~FTS_NOSTAT; 726 727 new = palloc(N_INUM, f_inum); 728 new->i_data = find_parsenum(new, "-inum", arg, NULL); 729 return (new); 730 } 731 732 /* 733 * -links n functions -- 734 * 735 * True if the file has n links. 736 */ 737 int 738 f_links(plan, entry) 739 PLAN *plan; 740 FTSENT *entry; 741 { 742 COMPARE(entry->fts_statp->st_nlink, plan->l_data); 743 } 744 745 PLAN * 746 c_links(arg) 747 char *arg; 748 { 749 PLAN *new; 750 751 ftsoptions &= ~FTS_NOSTAT; 752 753 new = palloc(N_LINKS, f_links); 754 new->l_data = (nlink_t)find_parsenum(new, "-links", arg, NULL); 755 return (new); 756 } 757 758 /* 759 * -ls functions -- 760 * 761 * Always true - prints the current entry to stdout in "ls" format. 762 */ 763 int 764 f_ls(plan, entry) 765 PLAN *plan; 766 FTSENT *entry; 767 { 768 printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp); 769 return (1); 770 } 771 772 PLAN * 773 c_ls() 774 { 775 ftsoptions &= ~FTS_NOSTAT; 776 isoutput = 1; 777 778 return (palloc(N_LS, f_ls)); 779 } 780 781 /* 782 * -maxdepth n functions -- 783 * 784 * Does the same as -prune if the level of the current file is greater 785 * than the specified maximum depth. 786 * 787 * Note that -maxdepth and -mindepth are handled specially in 788 * find_execute() so their f_* functions here do nothing. 789 */ 790 int 791 f_maxdepth(plan, entry) 792 PLAN *plan; 793 FTSENT *entry; 794 { 795 return (1); 796 } 797 798 PLAN * 799 c_maxdepth(arg) 800 char *arg; 801 { 802 PLAN *new; 803 804 if (*arg == '-') 805 /* all other errors handled by find_parsenum() */ 806 errx(1, "-maxdepth: %s: value must be positive", arg); 807 808 new = palloc(N_MAXDEPTH, f_maxdepth); 809 maxdepth = find_parsenum(new, "-maxdepth", arg, NULL); 810 return (new); 811 } 812 813 /* 814 * -mindepth n functions -- 815 * 816 * True if the current file is at or deeper than the specified minimum 817 * depth. 818 */ 819 int 820 f_mindepth(plan, entry) 821 PLAN *plan; 822 FTSENT *entry; 823 { 824 return (1); 825 } 826 827 PLAN * 828 c_mindepth(arg) 829 char *arg; 830 { 831 PLAN *new; 832 833 if (*arg == '-') 834 /* all other errors handled by find_parsenum() */ 835 errx(1, "-maxdepth: %s: value must be positive", arg); 836 837 new = palloc(N_MINDEPTH, f_mindepth); 838 mindepth = find_parsenum(new, "-mindepth", arg, NULL); 839 return (new); 840 } 841 842 /* 843 * -mtime n functions -- 844 * 845 * True if the difference between the file modification time and the 846 * current time is n 24 hour periods. 847 */ 848 int 849 f_mtime(plan, entry) 850 PLAN *plan; 851 FTSENT *entry; 852 { 853 extern time_t now; 854 855 COMPARE((now - entry->fts_statp->st_mtime + 86400 - 1) / 856 86400, plan->t_data); 857 } 858 859 PLAN * 860 c_mtime(arg) 861 char *arg; 862 { 863 PLAN *new; 864 865 ftsoptions &= ~FTS_NOSTAT; 866 867 new = palloc(N_MTIME, f_mtime); 868 new->t_data = find_parsenum(new, "-mtime", arg, NULL); 869 TIME_CORRECT(new, N_MTIME); 870 return (new); 871 } 872 873 /* 874 * -mmin n functions -- 875 * 876 * True if the difference between the file modification time and the 877 * current time is n min periods. 878 */ 879 int 880 f_mmin(plan, entry) 881 PLAN *plan; 882 FTSENT *entry; 883 { 884 extern time_t now; 885 886 COMPARE((now - entry->fts_statp->st_mtime + 60 - 1) / 887 60, plan->t_data); 888 } 889 890 PLAN * 891 c_mmin(arg) 892 char *arg; 893 { 894 PLAN *new; 895 896 ftsoptions &= ~FTS_NOSTAT; 897 898 new = palloc(N_MMIN, f_mmin); 899 new->t_data = find_parsenum(new, "-mmin", arg, NULL); 900 TIME_CORRECT(new, N_MMIN); 901 return (new); 902 } 903 904 905 /* 906 * -name functions -- 907 * 908 * True if the basename of the filename being examined 909 * matches pattern using Pattern Matching Notation S3.14 910 */ 911 int 912 f_name(plan, entry) 913 PLAN *plan; 914 FTSENT *entry; 915 { 916 return (!fnmatch(plan->c_data, entry->fts_name, 0)); 917 } 918 919 PLAN * 920 c_name(pattern) 921 char *pattern; 922 { 923 PLAN *new; 924 925 new = palloc(N_NAME, f_name); 926 new->c_data = pattern; 927 return (new); 928 } 929 930 931 /* 932 * -iname functions -- 933 * 934 * Like -iname, but the match is case insensitive. 935 */ 936 int 937 f_iname(plan, entry) 938 PLAN *plan; 939 FTSENT *entry; 940 { 941 return (!fnmatch(plan->c_data, entry->fts_name, FNM_CASEFOLD)); 942 } 943 944 PLAN * 945 c_iname(pattern) 946 char *pattern; 947 { 948 PLAN *new; 949 950 new = palloc(N_INAME, f_iname); 951 new->c_data = pattern; 952 return (new); 953 } 954 955 956 /* 957 * -regex functions -- 958 * 959 * True if the whole path of the file matches pattern using 960 * regular expression. 961 */ 962 int 963 f_regex(plan, entry) 964 PLAN *plan; 965 FTSENT *entry; 966 { 967 return (do_f_regex(plan, entry, 0)); 968 } 969 970 PLAN * 971 c_regex(pattern) 972 char *pattern; 973 { 974 return (do_c_regex(pattern, 0)); 975 } 976 977 /* 978 * -iregex functions -- 979 * 980 * Like -regex, but the match is case insensitive. 981 */ 982 int 983 f_iregex(plan, entry) 984 PLAN *plan; 985 FTSENT *entry; 986 { 987 return (do_f_regex(plan, entry, REG_ICASE)); 988 } 989 990 PLAN * 991 c_iregex(pattern) 992 char *pattern; 993 { 994 return (do_c_regex(pattern, REG_ICASE)); 995 } 996 997 static int 998 do_f_regex(plan, entry, icase) 999 PLAN *plan; 1000 FTSENT *entry; 1001 int icase; 1002 { 1003 char *str; 1004 size_t len; 1005 regex_t *pre; 1006 regmatch_t pmatch; 1007 int errcode; 1008 char errbuf[LINE_MAX]; 1009 int matched; 1010 1011 pre = plan->re_data; 1012 str = entry->fts_path; 1013 len = strlen(str); 1014 matched = 0; 1015 1016 pmatch.rm_so = 0; 1017 pmatch.rm_eo = len; 1018 1019 errcode = regexec(pre, str, 1, &pmatch, REG_STARTEND); 1020 1021 if (errcode != 0 && errcode != REG_NOMATCH) { 1022 regerror(errcode, pre, errbuf, sizeof errbuf); 1023 errx(1, "%s: %s", 1024 icase == 0 ? "-regex" : "-iregex", errbuf); 1025 } 1026 1027 if (errcode == 0 && pmatch.rm_so == 0 && pmatch.rm_eo == len) 1028 matched = 1; 1029 1030 return (matched); 1031 } 1032 1033 PLAN * 1034 do_c_regex(pattern, icase) 1035 char *pattern; 1036 int icase; 1037 { 1038 PLAN *new; 1039 regex_t *pre; 1040 int errcode; 1041 char errbuf[LINE_MAX]; 1042 1043 if ((pre = malloc(sizeof(regex_t))) == NULL) 1044 err(1, NULL); 1045 1046 if ((errcode = regcomp(pre, pattern, regexp_flags | icase)) != 0) { 1047 regerror(errcode, pre, errbuf, sizeof errbuf); 1048 errx(1, "%s: %s: %s", 1049 icase == 0 ? "-regex" : "-iregex", pattern, errbuf); 1050 } 1051 1052 new = icase == 0 ? palloc(N_REGEX, f_regex) : palloc(N_IREGEX, f_iregex); 1053 new->re_data = pre; 1054 return (new); 1055 } 1056 1057 /* 1058 * -newer file functions -- 1059 * 1060 * True if the current file has been modified more recently 1061 * then the modification time of the file named by the pathname 1062 * file. 1063 */ 1064 int 1065 f_newer(plan, entry) 1066 PLAN *plan; 1067 FTSENT *entry; 1068 { 1069 return (entry->fts_statp->st_mtime > plan->t_data); 1070 } 1071 1072 PLAN * 1073 c_newer(filename) 1074 char *filename; 1075 { 1076 PLAN *new; 1077 struct stat sb; 1078 1079 ftsoptions &= ~FTS_NOSTAT; 1080 1081 if (stat(filename, &sb)) 1082 err(1, "%s", filename); 1083 new = palloc(N_NEWER, f_newer); 1084 new->t_data = sb.st_mtime; 1085 return (new); 1086 } 1087 1088 /* 1089 * -nogroup functions -- 1090 * 1091 * True if file belongs to a user ID for which the equivalent 1092 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL. 1093 */ 1094 int 1095 f_nogroup(plan, entry) 1096 PLAN *plan; 1097 FTSENT *entry; 1098 { 1099 return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1); 1100 } 1101 1102 PLAN * 1103 c_nogroup() 1104 { 1105 ftsoptions &= ~FTS_NOSTAT; 1106 1107 return (palloc(N_NOGROUP, f_nogroup)); 1108 } 1109 1110 /* 1111 * -nouser functions -- 1112 * 1113 * True if file belongs to a user ID for which the equivalent 1114 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL. 1115 */ 1116 int 1117 f_nouser(plan, entry) 1118 PLAN *plan; 1119 FTSENT *entry; 1120 { 1121 return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1); 1122 } 1123 1124 PLAN * 1125 c_nouser() 1126 { 1127 ftsoptions &= ~FTS_NOSTAT; 1128 1129 return (palloc(N_NOUSER, f_nouser)); 1130 } 1131 1132 /* 1133 * -path functions -- 1134 * 1135 * True if the path of the filename being examined 1136 * matches pattern using Pattern Matching Notation S3.14 1137 */ 1138 int 1139 f_path(plan, entry) 1140 PLAN *plan; 1141 FTSENT *entry; 1142 { 1143 return (!fnmatch(plan->c_data, entry->fts_path, 0)); 1144 } 1145 1146 PLAN * 1147 c_path(pattern) 1148 char *pattern; 1149 { 1150 PLAN *new; 1151 1152 new = palloc(N_PATH, f_path); 1153 new->c_data = pattern; 1154 return (new); 1155 } 1156 1157 /* 1158 * -ipath functions -- 1159 * 1160 * Like -path, but the match is case insensitive. 1161 */ 1162 int 1163 f_ipath(plan, entry) 1164 PLAN *plan; 1165 FTSENT *entry; 1166 { 1167 return (!fnmatch(plan->c_data, entry->fts_path, FNM_CASEFOLD)); 1168 } 1169 1170 PLAN * 1171 c_ipath(pattern) 1172 char *pattern; 1173 { 1174 PLAN *new; 1175 1176 new = palloc(N_IPATH, f_ipath); 1177 new->c_data = pattern; 1178 return (new); 1179 } 1180 1181 /* 1182 * -perm functions -- 1183 * 1184 * The mode argument is used to represent file mode bits. If it starts 1185 * with a leading digit, it's treated as an octal mode, otherwise as a 1186 * symbolic mode. 1187 */ 1188 int 1189 f_perm(plan, entry) 1190 PLAN *plan; 1191 FTSENT *entry; 1192 { 1193 mode_t mode; 1194 1195 mode = entry->fts_statp->st_mode & 1196 (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO); 1197 if (plan->flags == F_ATLEAST) 1198 return ((plan->m_data | mode) == mode); 1199 else if (plan->flags == F_ANY ) 1200 return (plan->m_data & mode); 1201 else 1202 return (mode == plan->m_data); 1203 /* NOTREACHED */ 1204 } 1205 1206 PLAN * 1207 c_perm(perm) 1208 char *perm; 1209 { 1210 PLAN *new; 1211 mode_t *set; 1212 1213 ftsoptions &= ~FTS_NOSTAT; 1214 1215 new = palloc(N_PERM, f_perm); 1216 1217 if (*perm == '-') { 1218 new->flags = F_ATLEAST; 1219 ++perm; 1220 } else if (*perm == '+') { 1221 new->flags = F_ANY; 1222 ++perm; 1223 } 1224 1225 if ((set = setmode(perm)) == NULL) 1226 errx(1, "-perm: %s: illegal mode string", perm); 1227 1228 new->m_data = getmode(set, 0); 1229 free(set); 1230 return (new); 1231 } 1232 1233 /* 1234 * -flags functions -- 1235 * 1236 * The flags argument is used to represent file flags bits. 1237 */ 1238 int 1239 f_flags(plan, entry) 1240 PLAN *plan; 1241 FTSENT *entry; 1242 { 1243 u_long flags; 1244 1245 flags = entry->fts_statp->st_flags & 1246 (UF_NODUMP | UF_IMMUTABLE | UF_APPEND | UF_OPAQUE | 1247 SF_ARCHIVED | SF_IMMUTABLE | SF_APPEND); 1248 if (plan->flags == F_ATLEAST) 1249 /* note that plan->fl_flags always is a subset of 1250 plan->fl_mask */ 1251 return (flags & plan->fl_mask) == plan->fl_flags; 1252 else 1253 return flags == plan->fl_flags; 1254 /* NOTREACHED */ 1255 } 1256 1257 PLAN * 1258 c_flags(flags_str) 1259 char *flags_str; 1260 { 1261 PLAN *new; 1262 u_long flags, notflags; 1263 1264 ftsoptions &= ~FTS_NOSTAT; 1265 1266 new = palloc(N_FLAGS, f_flags); 1267 1268 if (*flags_str == '-') { 1269 new->flags = F_ATLEAST; 1270 flags_str++; 1271 } 1272 if (strtofflags(&flags_str, &flags, ¬flags) == 1) 1273 errx(1, "-flags: %s: illegal flags string", flags_str); 1274 1275 new->fl_flags = flags; 1276 new->fl_mask = flags | notflags; 1277 #if 0 1278 printf("flags = %08x, mask = %08x (%08x, %08x)\n", 1279 new->fl_flags, new->fl_mask, flags, notflags); 1280 #endif 1281 return new; 1282 } 1283 1284 /* 1285 * -print functions -- 1286 * 1287 * Always true, causes the current pathame to be written to 1288 * standard output. 1289 */ 1290 int 1291 f_print(plan, entry) 1292 PLAN *plan; 1293 FTSENT *entry; 1294 { 1295 (void)puts(entry->fts_path); 1296 return (1); 1297 } 1298 1299 PLAN * 1300 c_print() 1301 { 1302 isoutput = 1; 1303 1304 return (palloc(N_PRINT, f_print)); 1305 } 1306 1307 /* 1308 * -print0 functions -- 1309 * 1310 * Always true, causes the current pathame to be written to 1311 * standard output followed by a NUL character 1312 */ 1313 int 1314 f_print0(plan, entry) 1315 PLAN *plan; 1316 FTSENT *entry; 1317 { 1318 fputs(entry->fts_path, stdout); 1319 fputc('\0', stdout); 1320 return (1); 1321 } 1322 1323 PLAN * 1324 c_print0() 1325 { 1326 isoutput = 1; 1327 1328 return (palloc(N_PRINT0, f_print0)); 1329 } 1330 1331 /* 1332 * -prune functions -- 1333 * 1334 * Prune a portion of the hierarchy. 1335 */ 1336 int 1337 f_prune(plan, entry) 1338 PLAN *plan; 1339 FTSENT *entry; 1340 { 1341 extern FTS *tree; 1342 1343 if (fts_set(tree, entry, FTS_SKIP)) 1344 err(1, "%s", entry->fts_path); 1345 return (1); 1346 } 1347 1348 PLAN * 1349 c_prune() 1350 { 1351 return (palloc(N_PRUNE, f_prune)); 1352 } 1353 1354 /* 1355 * -size n[c] functions -- 1356 * 1357 * True if the file size in bytes, divided by an implementation defined 1358 * value and rounded up to the next integer, is n. If n is followed by 1359 * a c, the size is in bytes. 1360 */ 1361 #define FIND_SIZE 512 1362 static int divsize = 1; 1363 1364 int 1365 f_size(plan, entry) 1366 PLAN *plan; 1367 FTSENT *entry; 1368 { 1369 off_t size; 1370 1371 size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) / 1372 FIND_SIZE : entry->fts_statp->st_size; 1373 COMPARE(size, plan->o_data); 1374 } 1375 1376 PLAN * 1377 c_size(arg) 1378 char *arg; 1379 { 1380 PLAN *new; 1381 char endch; 1382 1383 ftsoptions &= ~FTS_NOSTAT; 1384 1385 new = palloc(N_SIZE, f_size); 1386 endch = 'c'; 1387 new->o_data = find_parsenum(new, "-size", arg, &endch); 1388 if (endch == 'c') 1389 divsize = 0; 1390 return (new); 1391 } 1392 1393 /* 1394 * -type c functions -- 1395 * 1396 * True if the type of the file is c, where c is b, c, d, p, f or w 1397 * for block special file, character special file, directory, FIFO, 1398 * regular file or whiteout respectively. 1399 */ 1400 int 1401 f_type(plan, entry) 1402 PLAN *plan; 1403 FTSENT *entry; 1404 { 1405 return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data); 1406 } 1407 1408 PLAN * 1409 c_type(typestring) 1410 char *typestring; 1411 { 1412 PLAN *new; 1413 mode_t mask; 1414 1415 ftsoptions &= ~FTS_NOSTAT; 1416 1417 switch (typestring[0]) { 1418 case 'b': 1419 mask = S_IFBLK; 1420 break; 1421 case 'c': 1422 mask = S_IFCHR; 1423 break; 1424 case 'd': 1425 mask = S_IFDIR; 1426 break; 1427 case 'f': 1428 mask = S_IFREG; 1429 break; 1430 case 'l': 1431 mask = S_IFLNK; 1432 break; 1433 case 'p': 1434 mask = S_IFIFO; 1435 break; 1436 case 's': 1437 mask = S_IFSOCK; 1438 break; 1439 #ifdef FTS_WHITEOUT 1440 case 'w': 1441 mask = S_IFWHT; 1442 ftsoptions |= FTS_WHITEOUT; 1443 break; 1444 #endif /* FTS_WHITEOUT */ 1445 default: 1446 errx(1, "-type: %s: unknown type", typestring); 1447 } 1448 1449 new = palloc(N_TYPE, f_type); 1450 new->m_data = mask; 1451 return (new); 1452 } 1453 1454 /* 1455 * -delete functions -- 1456 * 1457 * True always. Makes it's best shot and continues on regardless. 1458 */ 1459 int 1460 f_delete(plan, entry) 1461 PLAN *plan; 1462 FTSENT *entry; 1463 { 1464 /* ignore these from fts */ 1465 if (strcmp(entry->fts_accpath, ".") == 0 || 1466 strcmp(entry->fts_accpath, "..") == 0) 1467 return (1); 1468 1469 /* sanity check */ 1470 if (isdepth == 0 || /* depth off */ 1471 (ftsoptions & FTS_NOSTAT) || /* not stat()ing */ 1472 !(ftsoptions & FTS_PHYSICAL) || /* physical off */ 1473 (ftsoptions & FTS_LOGICAL)) /* or finally, logical on */ 1474 errx(1, "-delete: insecure options got turned on"); 1475 1476 /* Potentially unsafe - do not accept relative paths whatsoever */ 1477 if (strchr(entry->fts_accpath, '/') != NULL) 1478 errx(1, "-delete: %s: relative path potentially not safe", 1479 entry->fts_accpath); 1480 1481 /* Turn off user immutable bits if running as root */ 1482 if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) && 1483 !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && 1484 geteuid() == 0) 1485 chflags(entry->fts_accpath, 1486 entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)); 1487 1488 /* rmdir directories, unlink everything else */ 1489 if (S_ISDIR(entry->fts_statp->st_mode)) { 1490 if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY) 1491 warn("-delete: rmdir(%s)", entry->fts_path); 1492 } else { 1493 if (unlink(entry->fts_accpath) < 0) 1494 warn("-delete: unlink(%s)", entry->fts_path); 1495 } 1496 1497 /* "succeed" */ 1498 return (1); 1499 } 1500 1501 PLAN * 1502 c_delete() 1503 { 1504 1505 ftsoptions &= ~FTS_NOSTAT; /* no optimise */ 1506 ftsoptions |= FTS_PHYSICAL; /* disable -follow */ 1507 ftsoptions &= ~FTS_LOGICAL; /* disable -follow */ 1508 isoutput = 1; /* possible output */ 1509 isdepth = 1; /* -depth implied */ 1510 1511 return (palloc(N_DELETE, f_delete)); 1512 } 1513 1514 /* 1515 * -user uname functions -- 1516 * 1517 * True if the file belongs to the user uname. If uname is numeric and 1518 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not 1519 * return a valid user name, uname is taken as a user ID. 1520 */ 1521 int 1522 f_user(plan, entry) 1523 PLAN *plan; 1524 FTSENT *entry; 1525 { 1526 return (entry->fts_statp->st_uid == plan->u_data); 1527 } 1528 1529 PLAN * 1530 c_user(username) 1531 char *username; 1532 { 1533 PLAN *new; 1534 struct passwd *p; 1535 uid_t uid; 1536 1537 ftsoptions &= ~FTS_NOSTAT; 1538 1539 p = getpwnam(username); 1540 if (p == NULL) { 1541 uid = atoi(username); 1542 if (uid == 0 && username[0] != '0') 1543 errx(1, "-user: %s: no such user", username); 1544 } else 1545 uid = p->pw_uid; 1546 1547 new = palloc(N_USER, f_user); 1548 new->u_data = uid; 1549 return (new); 1550 } 1551 1552 /* 1553 * -xdev functions -- 1554 * 1555 * Always true, causes find not to decend past directories that have a 1556 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1]) 1557 */ 1558 PLAN * 1559 c_xdev() 1560 { 1561 ftsoptions |= FTS_XDEV; 1562 1563 return (palloc(N_XDEV, f_always_true)); 1564 } 1565 1566 /* 1567 * ( expression ) functions -- 1568 * 1569 * True if expression is true. 1570 */ 1571 int 1572 f_expr(plan, entry) 1573 PLAN *plan; 1574 FTSENT *entry; 1575 { 1576 register PLAN *p; 1577 register int state; 1578 1579 state = 0; 1580 for (p = plan->p_data[0]; 1581 p && (state = (p->eval)(p, entry)); p = p->next); 1582 return (state); 1583 } 1584 1585 /* 1586 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are 1587 * eliminated during phase 2 of find_formplan() --- the '(' node is converted 1588 * to a N_EXPR node containing the expression and the ')' node is discarded. 1589 */ 1590 PLAN * 1591 c_openparen() 1592 { 1593 return (palloc(N_OPENPAREN, (int (*)())-1)); 1594 } 1595 1596 PLAN * 1597 c_closeparen() 1598 { 1599 return (palloc(N_CLOSEPAREN, (int (*)())-1)); 1600 } 1601 1602 /* 1603 * ! expression functions -- 1604 * 1605 * Negation of a primary; the unary NOT operator. 1606 */ 1607 int 1608 f_not(plan, entry) 1609 PLAN *plan; 1610 FTSENT *entry; 1611 { 1612 register PLAN *p; 1613 register int state; 1614 1615 state = 0; 1616 for (p = plan->p_data[0]; 1617 p && (state = (p->eval)(p, entry)); p = p->next); 1618 return (!state); 1619 } 1620 1621 PLAN * 1622 c_not() 1623 { 1624 return (palloc(N_NOT, f_not)); 1625 } 1626 1627 /* 1628 * expression -o expression functions -- 1629 * 1630 * Alternation of primaries; the OR operator. The second expression is 1631 * not evaluated if the first expression is true. 1632 */ 1633 int 1634 f_or(plan, entry) 1635 PLAN *plan; 1636 FTSENT *entry; 1637 { 1638 register PLAN *p; 1639 register int state; 1640 1641 state = 0; 1642 for (p = plan->p_data[0]; 1643 p && (state = (p->eval)(p, entry)); p = p->next); 1644 1645 if (state) 1646 return (1); 1647 1648 for (p = plan->p_data[1]; 1649 p && (state = (p->eval)(p, entry)); p = p->next); 1650 return (state); 1651 } 1652 1653 PLAN * 1654 c_or() 1655 { 1656 return (palloc(N_OR, f_or)); 1657 } 1658 1659 static PLAN * 1660 palloc(t, f) 1661 enum ntype t; 1662 int (*f) __P((PLAN *, FTSENT *)); 1663 { 1664 PLAN *new; 1665 1666 if ((new = malloc(sizeof(PLAN))) == NULL) 1667 err(1, NULL); 1668 new->type = t; 1669 new->eval = f; 1670 new->flags = 0; 1671 new->next = NULL; 1672 return (new); 1673 } 1674