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 static char sccsid[] = "@(#)function.c 8.10 (Berkeley) 5/4/95"; 39 static char rcsid[] = "$FreeBSD$"; 40 #endif /* not lint */ 41 42 #include <sys/param.h> 43 #include <sys/ucred.h> 44 #include <sys/stat.h> 45 #include <sys/wait.h> 46 #include <sys/mount.h> 47 48 #include <err.h> 49 #include <errno.h> 50 #include <fnmatch.h> 51 #include <fts.h> 52 #include <grp.h> 53 #include <pwd.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <unistd.h> 58 59 #include "find.h" 60 61 #define COMPARE(a, b) { \ 62 switch (plan->flags) { \ 63 case F_EQUAL: \ 64 return (a == b); \ 65 case F_LESSTHAN: \ 66 return (a < b); \ 67 case F_GREATER: \ 68 return (a > b); \ 69 default: \ 70 abort(); \ 71 } \ 72 } 73 74 u_long setflags __P((char **, u_long *, u_long *)); 75 76 static PLAN *palloc __P((enum ntype, int (*) __P((PLAN *, FTSENT *)))); 77 78 /* 79 * find_parsenum -- 80 * Parse a string of the form [+-]# and return the value. 81 */ 82 static long long 83 find_parsenum(plan, option, vp, endch) 84 PLAN *plan; 85 char *option, *vp, *endch; 86 { 87 long long value; 88 char *endchar, *str; /* Pointer to character ending conversion. */ 89 90 /* Determine comparison from leading + or -. */ 91 str = vp; 92 switch (*str) { 93 case '+': 94 ++str; 95 plan->flags = F_GREATER; 96 break; 97 case '-': 98 ++str; 99 plan->flags = F_LESSTHAN; 100 break; 101 default: 102 plan->flags = F_EQUAL; 103 break; 104 } 105 106 /* 107 * Convert the string with strtoq(). Note, if strtoq() returns zero 108 * and endchar points to the beginning of the string we know we have 109 * a syntax error. 110 */ 111 value = strtoq(str, &endchar, 10); 112 if (value == 0 && endchar == str) 113 errx(1, "%s: %s: illegal numeric value", option, vp); 114 if (endchar[0] && (endch == NULL || endchar[0] != *endch)) 115 errx(1, "%s: %s: illegal trailing character", option, vp); 116 if (endch) 117 *endch = endchar[0]; 118 return (value); 119 } 120 121 /* 122 * The value of n for the inode times (atime, ctime, and mtime) is a range, 123 * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with 124 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the 125 * user wanted. Correct so that -1 is "less than 1". 126 */ 127 #define TIME_CORRECT(p, ttype) \ 128 if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \ 129 ++((p)->t_data); 130 131 /* 132 * -amin n functions -- 133 * 134 * True if the difference between the file access time and the 135 * current time is n min periods. 136 */ 137 int 138 f_amin(plan, entry) 139 PLAN *plan; 140 FTSENT *entry; 141 { 142 extern time_t now; 143 144 COMPARE((now - entry->fts_statp->st_atime + 145 60 - 1) / 60, plan->t_data); 146 } 147 148 PLAN * 149 c_amin(arg) 150 char *arg; 151 { 152 PLAN *new; 153 154 ftsoptions &= ~FTS_NOSTAT; 155 156 new = palloc(N_AMIN, f_amin); 157 new->t_data = find_parsenum(new, "-amin", arg, NULL); 158 TIME_CORRECT(new, N_AMIN); 159 return (new); 160 } 161 162 163 /* 164 * -atime n functions -- 165 * 166 * True if the difference between the file access time and the 167 * current time is n 24 hour periods. 168 */ 169 int 170 f_atime(plan, entry) 171 PLAN *plan; 172 FTSENT *entry; 173 { 174 extern time_t now; 175 176 COMPARE((now - entry->fts_statp->st_atime + 177 86400 - 1) / 86400, plan->t_data); 178 } 179 180 PLAN * 181 c_atime(arg) 182 char *arg; 183 { 184 PLAN *new; 185 186 ftsoptions &= ~FTS_NOSTAT; 187 188 new = palloc(N_ATIME, f_atime); 189 new->t_data = find_parsenum(new, "-atime", arg, NULL); 190 TIME_CORRECT(new, N_ATIME); 191 return (new); 192 } 193 194 195 /* 196 * -cmin n functions -- 197 * 198 * True if the difference between the last change of file 199 * status information and the current time is n min periods. 200 */ 201 int 202 f_cmin(plan, entry) 203 PLAN *plan; 204 FTSENT *entry; 205 { 206 extern time_t now; 207 208 COMPARE((now - entry->fts_statp->st_ctime + 209 60 - 1) / 60, plan->t_data); 210 } 211 212 PLAN * 213 c_cmin(arg) 214 char *arg; 215 { 216 PLAN *new; 217 218 ftsoptions &= ~FTS_NOSTAT; 219 220 new = palloc(N_CMIN, f_cmin); 221 new->t_data = find_parsenum(new, "-cmin", arg, NULL); 222 TIME_CORRECT(new, N_CMIN); 223 return (new); 224 } 225 226 /* 227 * -ctime n functions -- 228 * 229 * True if the difference between the last change of file 230 * status information and the current time is n 24 hour periods. 231 */ 232 int 233 f_ctime(plan, entry) 234 PLAN *plan; 235 FTSENT *entry; 236 { 237 extern time_t now; 238 239 COMPARE((now - entry->fts_statp->st_ctime + 240 86400 - 1) / 86400, plan->t_data); 241 } 242 243 PLAN * 244 c_ctime(arg) 245 char *arg; 246 { 247 PLAN *new; 248 249 ftsoptions &= ~FTS_NOSTAT; 250 251 new = palloc(N_CTIME, f_ctime); 252 new->t_data = find_parsenum(new, "-ctime", arg, NULL); 253 TIME_CORRECT(new, N_CTIME); 254 return (new); 255 } 256 257 258 /* 259 * -depth functions -- 260 * 261 * Always true, causes descent of the directory hierarchy to be done 262 * so that all entries in a directory are acted on before the directory 263 * itself. 264 */ 265 int 266 f_always_true(plan, entry) 267 PLAN *plan; 268 FTSENT *entry; 269 { 270 return (1); 271 } 272 273 PLAN * 274 c_depth() 275 { 276 isdepth = 1; 277 278 return (palloc(N_DEPTH, f_always_true)); 279 } 280 281 /* 282 * [-exec | -ok] utility [arg ... ] ; functions -- 283 * 284 * True if the executed utility returns a zero value as exit status. 285 * The end of the primary expression is delimited by a semicolon. If 286 * "{}" occurs anywhere, it gets replaced by the current pathname. 287 * The current directory for the execution of utility is the same as 288 * the current directory when the find utility was started. 289 * 290 * The primary -ok is different in that it requests affirmation of the 291 * user before executing the utility. 292 */ 293 int 294 f_exec(plan, entry) 295 register PLAN *plan; 296 FTSENT *entry; 297 { 298 extern int dotfd; 299 register int cnt; 300 pid_t pid; 301 int status; 302 303 for (cnt = 0; plan->e_argv[cnt]; ++cnt) 304 if (plan->e_len[cnt]) 305 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt], 306 entry->fts_path, plan->e_len[cnt]); 307 308 if (plan->flags == F_NEEDOK && !queryuser(plan->e_argv)) 309 return (0); 310 311 /* make sure find output is interspersed correctly with subprocesses */ 312 fflush(stdout); 313 314 switch (pid = fork()) { 315 case -1: 316 err(1, "fork"); 317 /* NOTREACHED */ 318 case 0: 319 if (fchdir(dotfd)) { 320 warn("chdir"); 321 _exit(1); 322 } 323 execvp(plan->e_argv[0], plan->e_argv); 324 warn("%s", plan->e_argv[0]); 325 _exit(1); 326 } 327 pid = waitpid(pid, &status, 0); 328 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status)); 329 } 330 331 /* 332 * c_exec -- 333 * build three parallel arrays, one with pointers to the strings passed 334 * on the command line, one with (possibly duplicated) pointers to the 335 * argv array, and one with integer values that are lengths of the 336 * strings, but also flags meaning that the string has to be massaged. 337 */ 338 PLAN * 339 c_exec(argvp, isok) 340 char ***argvp; 341 int isok; 342 { 343 PLAN *new; /* node returned */ 344 register int cnt; 345 register char **argv, **ap, *p; 346 347 isoutput = 1; 348 349 new = palloc(N_EXEC, f_exec); 350 if (isok) 351 new->flags = F_NEEDOK; 352 353 for (ap = argv = *argvp;; ++ap) { 354 if (!*ap) 355 errx(1, 356 "%s: no terminating \";\"", isok ? "-ok" : "-exec"); 357 if (**ap == ';') 358 break; 359 } 360 361 cnt = ap - *argvp + 1; 362 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *)); 363 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *)); 364 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int)); 365 366 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) { 367 new->e_orig[cnt] = *argv; 368 for (p = *argv; *p; ++p) 369 if (p[0] == '{' && p[1] == '}') { 370 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN); 371 new->e_len[cnt] = MAXPATHLEN; 372 break; 373 } 374 if (!*p) { 375 new->e_argv[cnt] = *argv; 376 new->e_len[cnt] = 0; 377 } 378 } 379 new->e_argv[cnt] = new->e_orig[cnt] = NULL; 380 381 *argvp = argv + 1; 382 return (new); 383 } 384 385 /* 386 * -execdir utility [arg ... ] ; functions -- 387 * 388 * True if the executed utility returns a zero value as exit status. 389 * The end of the primary expression is delimited by a semicolon. If 390 * "{}" occurs anywhere, it gets replaced by the unqualified pathname. 391 * The current directory for the execution of utility is the same as 392 * the directory where the file lives. 393 */ 394 int 395 f_execdir(plan, entry) 396 register PLAN *plan; 397 FTSENT *entry; 398 { 399 register int cnt; 400 pid_t pid; 401 int status; 402 char *file; 403 404 /* XXX - if file/dir ends in '/' this will not work -- can it? */ 405 if ((file = strrchr(entry->fts_path, '/'))) 406 file++; 407 else 408 file = entry->fts_path; 409 410 for (cnt = 0; plan->e_argv[cnt]; ++cnt) 411 if (plan->e_len[cnt]) 412 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt], 413 file, plan->e_len[cnt]); 414 415 /* don't mix output of command with find output */ 416 fflush(stdout); 417 fflush(stderr); 418 419 switch (pid = fork()) { 420 case -1: 421 err(1, "fork"); 422 /* NOTREACHED */ 423 case 0: 424 execvp(plan->e_argv[0], plan->e_argv); 425 warn("%s", plan->e_argv[0]); 426 _exit(1); 427 } 428 pid = waitpid(pid, &status, 0); 429 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status)); 430 } 431 432 /* 433 * c_execdir -- 434 * build three parallel arrays, one with pointers to the strings passed 435 * on the command line, one with (possibly duplicated) pointers to the 436 * argv array, and one with integer values that are lengths of the 437 * strings, but also flags meaning that the string has to be massaged. 438 */ 439 PLAN * 440 c_execdir(argvp) 441 char ***argvp; 442 { 443 PLAN *new; /* node returned */ 444 register int cnt; 445 register char **argv, **ap, *p; 446 447 ftsoptions &= ~FTS_NOSTAT; 448 isoutput = 1; 449 450 new = palloc(N_EXECDIR, f_execdir); 451 452 for (ap = argv = *argvp;; ++ap) { 453 if (!*ap) 454 errx(1, 455 "-execdir: no terminating \";\""); 456 if (**ap == ';') 457 break; 458 } 459 460 cnt = ap - *argvp + 1; 461 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *)); 462 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *)); 463 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int)); 464 465 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) { 466 new->e_orig[cnt] = *argv; 467 for (p = *argv; *p; ++p) 468 if (p[0] == '{' && p[1] == '}') { 469 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN); 470 new->e_len[cnt] = MAXPATHLEN; 471 break; 472 } 473 if (!*p) { 474 new->e_argv[cnt] = *argv; 475 new->e_len[cnt] = 0; 476 } 477 } 478 new->e_argv[cnt] = new->e_orig[cnt] = NULL; 479 480 *argvp = argv + 1; 481 return (new); 482 } 483 484 /* 485 * -follow functions -- 486 * 487 * Always true, causes symbolic links to be followed on a global 488 * basis. 489 */ 490 PLAN * 491 c_follow() 492 { 493 ftsoptions &= ~FTS_PHYSICAL; 494 ftsoptions |= FTS_LOGICAL; 495 496 return (palloc(N_FOLLOW, f_always_true)); 497 } 498 499 /* 500 * -fstype functions -- 501 * 502 * True if the file is of a certain type. 503 */ 504 int 505 f_fstype(plan, entry) 506 PLAN *plan; 507 FTSENT *entry; 508 { 509 static dev_t curdev; /* need a guaranteed illegal dev value */ 510 static int first = 1; 511 struct statfs sb; 512 static int val_type, val_flags; 513 char *p, save[2]; 514 515 /* Only check when we cross mount point. */ 516 if (first || curdev != entry->fts_statp->st_dev) { 517 curdev = entry->fts_statp->st_dev; 518 519 /* 520 * Statfs follows symlinks; find wants the link's file system, 521 * not where it points. 522 */ 523 if (entry->fts_info == FTS_SL || 524 entry->fts_info == FTS_SLNONE) { 525 if ((p = strrchr(entry->fts_accpath, '/')) != NULL) 526 ++p; 527 else 528 p = entry->fts_accpath; 529 save[0] = p[0]; 530 p[0] = '.'; 531 save[1] = p[1]; 532 p[1] = '\0'; 533 534 } else 535 p = NULL; 536 537 if (statfs(entry->fts_accpath, &sb)) 538 err(1, "%s", entry->fts_accpath); 539 540 if (p) { 541 p[0] = save[0]; 542 p[1] = save[1]; 543 } 544 545 first = 0; 546 547 /* 548 * Further tests may need both of these values, so 549 * always copy both of them. 550 */ 551 val_flags = sb.f_flags; 552 val_type = sb.f_type; 553 } 554 switch (plan->flags) { 555 case F_MTFLAG: 556 return (val_flags & plan->mt_data) != 0; 557 case F_MTTYPE: 558 return (val_type == plan->mt_data); 559 default: 560 abort(); 561 } 562 } 563 564 #if !defined(__NetBSD__) 565 PLAN * 566 c_fstype(arg) 567 char *arg; 568 { 569 register PLAN *new; 570 struct vfsconf vfc; 571 572 ftsoptions &= ~FTS_NOSTAT; 573 574 new = palloc(N_FSTYPE, f_fstype); 575 576 /* 577 * Check first for a filesystem name. 578 */ 579 if (getvfsbyname(arg, &vfc) == 0) { 580 new->flags = F_MTTYPE; 581 new->mt_data = vfc.vfc_typenum; 582 return (new); 583 } 584 585 switch (*arg) { 586 case 'l': 587 if (!strcmp(arg, "local")) { 588 new->flags = F_MTFLAG; 589 new->mt_data = MNT_LOCAL; 590 return (new); 591 } 592 break; 593 case 'r': 594 if (!strcmp(arg, "rdonly")) { 595 new->flags = F_MTFLAG; 596 new->mt_data = MNT_RDONLY; 597 return (new); 598 } 599 break; 600 } 601 errx(1, "%s: unknown file type", arg); 602 /* NOTREACHED */ 603 } 604 #endif 605 606 /* 607 * -group gname functions -- 608 * 609 * True if the file belongs to the group gname. If gname is numeric and 610 * an equivalent of the getgrnam() function does not return a valid group 611 * name, gname is taken as a group ID. 612 */ 613 int 614 f_group(plan, entry) 615 PLAN *plan; 616 FTSENT *entry; 617 { 618 return (entry->fts_statp->st_gid == plan->g_data); 619 } 620 621 PLAN * 622 c_group(gname) 623 char *gname; 624 { 625 PLAN *new; 626 struct group *g; 627 gid_t gid; 628 629 ftsoptions &= ~FTS_NOSTAT; 630 631 g = getgrnam(gname); 632 if (g == NULL) { 633 gid = atoi(gname); 634 if (gid == 0 && gname[0] != '0') 635 errx(1, "-group: %s: no such group", gname); 636 } else 637 gid = g->gr_gid; 638 639 new = palloc(N_GROUP, f_group); 640 new->g_data = gid; 641 return (new); 642 } 643 644 /* 645 * -inum n functions -- 646 * 647 * True if the file has inode # n. 648 */ 649 int 650 f_inum(plan, entry) 651 PLAN *plan; 652 FTSENT *entry; 653 { 654 COMPARE(entry->fts_statp->st_ino, plan->i_data); 655 } 656 657 PLAN * 658 c_inum(arg) 659 char *arg; 660 { 661 PLAN *new; 662 663 ftsoptions &= ~FTS_NOSTAT; 664 665 new = palloc(N_INUM, f_inum); 666 new->i_data = find_parsenum(new, "-inum", arg, NULL); 667 return (new); 668 } 669 670 /* 671 * -links n functions -- 672 * 673 * True if the file has n links. 674 */ 675 int 676 f_links(plan, entry) 677 PLAN *plan; 678 FTSENT *entry; 679 { 680 COMPARE(entry->fts_statp->st_nlink, plan->l_data); 681 } 682 683 PLAN * 684 c_links(arg) 685 char *arg; 686 { 687 PLAN *new; 688 689 ftsoptions &= ~FTS_NOSTAT; 690 691 new = palloc(N_LINKS, f_links); 692 new->l_data = (nlink_t)find_parsenum(new, "-links", arg, NULL); 693 return (new); 694 } 695 696 /* 697 * -ls functions -- 698 * 699 * Always true - prints the current entry to stdout in "ls" format. 700 */ 701 int 702 f_ls(plan, entry) 703 PLAN *plan; 704 FTSENT *entry; 705 { 706 printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp); 707 return (1); 708 } 709 710 PLAN * 711 c_ls() 712 { 713 ftsoptions &= ~FTS_NOSTAT; 714 isoutput = 1; 715 716 return (palloc(N_LS, f_ls)); 717 } 718 719 /* 720 * -mtime n functions -- 721 * 722 * True if the difference between the file modification time and the 723 * current time is n 24 hour periods. 724 */ 725 int 726 f_mtime(plan, entry) 727 PLAN *plan; 728 FTSENT *entry; 729 { 730 extern time_t now; 731 732 COMPARE((now - entry->fts_statp->st_mtime + 86400 - 1) / 733 86400, plan->t_data); 734 } 735 736 PLAN * 737 c_mtime(arg) 738 char *arg; 739 { 740 PLAN *new; 741 742 ftsoptions &= ~FTS_NOSTAT; 743 744 new = palloc(N_MTIME, f_mtime); 745 new->t_data = find_parsenum(new, "-mtime", arg, NULL); 746 TIME_CORRECT(new, N_MTIME); 747 return (new); 748 } 749 750 /* 751 * -mmin n functions -- 752 * 753 * True if the difference between the file modification time and the 754 * current time is n min periods. 755 */ 756 int 757 f_mmin(plan, entry) 758 PLAN *plan; 759 FTSENT *entry; 760 { 761 extern time_t now; 762 763 COMPARE((now - entry->fts_statp->st_mtime + 60 - 1) / 764 60, plan->t_data); 765 } 766 767 PLAN * 768 c_mmin(arg) 769 char *arg; 770 { 771 PLAN *new; 772 773 ftsoptions &= ~FTS_NOSTAT; 774 775 new = palloc(N_MMIN, f_mmin); 776 new->t_data = find_parsenum(new, "-mmin", arg, NULL); 777 TIME_CORRECT(new, N_MMIN); 778 return (new); 779 } 780 781 782 /* 783 * -name functions -- 784 * 785 * True if the basename of the filename being examined 786 * matches pattern using Pattern Matching Notation S3.14 787 */ 788 int 789 f_name(plan, entry) 790 PLAN *plan; 791 FTSENT *entry; 792 { 793 return (!fnmatch(plan->c_data, entry->fts_name, 0)); 794 } 795 796 PLAN * 797 c_name(pattern) 798 char *pattern; 799 { 800 PLAN *new; 801 802 new = palloc(N_NAME, f_name); 803 new->c_data = pattern; 804 return (new); 805 } 806 807 /* 808 * -newer file functions -- 809 * 810 * True if the current file has been modified more recently 811 * then the modification time of the file named by the pathname 812 * file. 813 */ 814 int 815 f_newer(plan, entry) 816 PLAN *plan; 817 FTSENT *entry; 818 { 819 return (entry->fts_statp->st_mtime > plan->t_data); 820 } 821 822 PLAN * 823 c_newer(filename) 824 char *filename; 825 { 826 PLAN *new; 827 struct stat sb; 828 829 ftsoptions &= ~FTS_NOSTAT; 830 831 if (stat(filename, &sb)) 832 err(1, "%s", filename); 833 new = palloc(N_NEWER, f_newer); 834 new->t_data = sb.st_mtime; 835 return (new); 836 } 837 838 /* 839 * -nogroup functions -- 840 * 841 * True if file belongs to a user ID for which the equivalent 842 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL. 843 */ 844 int 845 f_nogroup(plan, entry) 846 PLAN *plan; 847 FTSENT *entry; 848 { 849 char *group_from_gid(); 850 851 return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1); 852 } 853 854 PLAN * 855 c_nogroup() 856 { 857 ftsoptions &= ~FTS_NOSTAT; 858 859 return (palloc(N_NOGROUP, f_nogroup)); 860 } 861 862 /* 863 * -nouser functions -- 864 * 865 * True if file belongs to a user ID for which the equivalent 866 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL. 867 */ 868 int 869 f_nouser(plan, entry) 870 PLAN *plan; 871 FTSENT *entry; 872 { 873 char *user_from_uid(); 874 875 return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1); 876 } 877 878 PLAN * 879 c_nouser() 880 { 881 ftsoptions &= ~FTS_NOSTAT; 882 883 return (palloc(N_NOUSER, f_nouser)); 884 } 885 886 /* 887 * -path functions -- 888 * 889 * True if the path of the filename being examined 890 * matches pattern using Pattern Matching Notation S3.14 891 */ 892 int 893 f_path(plan, entry) 894 PLAN *plan; 895 FTSENT *entry; 896 { 897 return (!fnmatch(plan->c_data, entry->fts_path, 0)); 898 } 899 900 PLAN * 901 c_path(pattern) 902 char *pattern; 903 { 904 PLAN *new; 905 906 new = palloc(N_NAME, f_path); 907 new->c_data = pattern; 908 return (new); 909 } 910 911 /* 912 * -perm functions -- 913 * 914 * The mode argument is used to represent file mode bits. If it starts 915 * with a leading digit, it's treated as an octal mode, otherwise as a 916 * symbolic mode. 917 */ 918 int 919 f_perm(plan, entry) 920 PLAN *plan; 921 FTSENT *entry; 922 { 923 mode_t mode; 924 925 mode = entry->fts_statp->st_mode & 926 (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO); 927 if (plan->flags == F_ATLEAST) 928 return ((plan->m_data | mode) == mode); 929 else 930 return (mode == plan->m_data); 931 /* NOTREACHED */ 932 } 933 934 PLAN * 935 c_perm(perm) 936 char *perm; 937 { 938 PLAN *new; 939 mode_t *set; 940 941 ftsoptions &= ~FTS_NOSTAT; 942 943 new = palloc(N_PERM, f_perm); 944 945 if (*perm == '-') { 946 new->flags = F_ATLEAST; 947 ++perm; 948 } 949 950 if ((set = setmode(perm)) == NULL) 951 errx(1, "-perm: %s: illegal mode string", perm); 952 953 new->m_data = getmode(set, 0); 954 free(set); 955 return (new); 956 } 957 958 /* 959 * -flags functions -- 960 * 961 * The flags argument is used to represent file flags bits. 962 */ 963 int 964 f_flags(plan, entry) 965 PLAN *plan; 966 FTSENT *entry; 967 { 968 u_long flags; 969 970 flags = entry->fts_statp->st_flags & 971 (UF_NODUMP | UF_IMMUTABLE | UF_APPEND | UF_OPAQUE | 972 SF_ARCHIVED | SF_IMMUTABLE | SF_APPEND); 973 if (plan->flags == F_ATLEAST) 974 /* note that plan->fl_flags always is a subset of 975 plan->fl_mask */ 976 return (flags & plan->fl_mask) == plan->fl_flags; 977 else 978 return flags == plan->fl_flags; 979 /* NOTREACHED */ 980 } 981 982 PLAN * 983 c_flags(flags_str) 984 char *flags_str; 985 { 986 PLAN *new; 987 u_long flags, notflags; 988 989 ftsoptions &= ~FTS_NOSTAT; 990 991 new = palloc(N_FLAGS, f_flags); 992 993 if (*flags_str == '-') { 994 new->flags = F_ATLEAST; 995 flags_str++; 996 } 997 if (setflags(&flags_str, &flags, ¬flags) == 1) 998 errx(1, "-flags: %s: illegal flags string", flags_str); 999 1000 new->fl_flags = flags; 1001 new->fl_mask = flags | notflags; 1002 #if 0 1003 printf("flags = %08x, mask = %08x (%08x, %08x)\n", 1004 new->fl_flags, new->fl_mask, flags, notflags); 1005 #endif 1006 return new; 1007 } 1008 1009 /* 1010 1011 1012 /* 1013 * -print functions -- 1014 * 1015 * Always true, causes the current pathame to be written to 1016 * standard output. 1017 */ 1018 int 1019 f_print(plan, entry) 1020 PLAN *plan; 1021 FTSENT *entry; 1022 { 1023 (void)puts(entry->fts_path); 1024 return (1); 1025 } 1026 1027 PLAN * 1028 c_print() 1029 { 1030 isoutput = 1; 1031 1032 return (palloc(N_PRINT, f_print)); 1033 } 1034 1035 /* 1036 * -print0 functions -- 1037 * 1038 * Always true, causes the current pathame to be written to 1039 * standard output followed by a NUL character 1040 */ 1041 int 1042 f_print0(plan, entry) 1043 PLAN *plan; 1044 FTSENT *entry; 1045 { 1046 fputs(entry->fts_path, stdout); 1047 fputc('\0', stdout); 1048 return (1); 1049 } 1050 1051 PLAN * 1052 c_print0() 1053 { 1054 isoutput = 1; 1055 1056 return (palloc(N_PRINT0, f_print0)); 1057 } 1058 1059 /* 1060 * -prune functions -- 1061 * 1062 * Prune a portion of the hierarchy. 1063 */ 1064 int 1065 f_prune(plan, entry) 1066 PLAN *plan; 1067 FTSENT *entry; 1068 { 1069 extern FTS *tree; 1070 1071 if (fts_set(tree, entry, FTS_SKIP)) 1072 err(1, "%s", entry->fts_path); 1073 return (1); 1074 } 1075 1076 PLAN * 1077 c_prune() 1078 { 1079 return (palloc(N_PRUNE, f_prune)); 1080 } 1081 1082 /* 1083 * -size n[c] functions -- 1084 * 1085 * True if the file size in bytes, divided by an implementation defined 1086 * value and rounded up to the next integer, is n. If n is followed by 1087 * a c, the size is in bytes. 1088 */ 1089 #define FIND_SIZE 512 1090 static int divsize = 1; 1091 1092 int 1093 f_size(plan, entry) 1094 PLAN *plan; 1095 FTSENT *entry; 1096 { 1097 off_t size; 1098 1099 size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) / 1100 FIND_SIZE : entry->fts_statp->st_size; 1101 COMPARE(size, plan->o_data); 1102 } 1103 1104 PLAN * 1105 c_size(arg) 1106 char *arg; 1107 { 1108 PLAN *new; 1109 char endch; 1110 1111 ftsoptions &= ~FTS_NOSTAT; 1112 1113 new = palloc(N_SIZE, f_size); 1114 endch = 'c'; 1115 new->o_data = find_parsenum(new, "-size", arg, &endch); 1116 if (endch == 'c') 1117 divsize = 0; 1118 return (new); 1119 } 1120 1121 /* 1122 * -type c functions -- 1123 * 1124 * True if the type of the file is c, where c is b, c, d, p, f or w 1125 * for block special file, character special file, directory, FIFO, 1126 * regular file or whiteout respectively. 1127 */ 1128 int 1129 f_type(plan, entry) 1130 PLAN *plan; 1131 FTSENT *entry; 1132 { 1133 return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data); 1134 } 1135 1136 PLAN * 1137 c_type(typestring) 1138 char *typestring; 1139 { 1140 PLAN *new; 1141 mode_t mask; 1142 1143 ftsoptions &= ~FTS_NOSTAT; 1144 1145 switch (typestring[0]) { 1146 case 'b': 1147 mask = S_IFBLK; 1148 break; 1149 case 'c': 1150 mask = S_IFCHR; 1151 break; 1152 case 'd': 1153 mask = S_IFDIR; 1154 break; 1155 case 'f': 1156 mask = S_IFREG; 1157 break; 1158 case 'l': 1159 mask = S_IFLNK; 1160 break; 1161 case 'p': 1162 mask = S_IFIFO; 1163 break; 1164 case 's': 1165 mask = S_IFSOCK; 1166 break; 1167 #ifdef FTS_WHITEOUT 1168 case 'w': 1169 mask = S_IFWHT; 1170 ftsoptions |= FTS_WHITEOUT; 1171 break; 1172 #endif /* FTS_WHITEOUT */ 1173 default: 1174 errx(1, "-type: %s: unknown type", typestring); 1175 } 1176 1177 new = palloc(N_TYPE, f_type); 1178 new->m_data = mask; 1179 return (new); 1180 } 1181 1182 /* 1183 * -delete functions -- 1184 * 1185 * True always. Makes it's best shot and continues on regardless. 1186 */ 1187 int 1188 f_delete(plan, entry) 1189 PLAN *plan; 1190 FTSENT *entry; 1191 { 1192 /* ignore these from fts */ 1193 if (strcmp(entry->fts_accpath, ".") == 0 || 1194 strcmp(entry->fts_accpath, "..") == 0) 1195 return (1); 1196 1197 /* sanity check */ 1198 if (isdepth == 0 || /* depth off */ 1199 (ftsoptions & FTS_NOSTAT) || /* not stat()ing */ 1200 !(ftsoptions & FTS_PHYSICAL) || /* physical off */ 1201 (ftsoptions & FTS_LOGICAL)) /* or finally, logical on */ 1202 errx(1, "-delete: insecure options got turned on"); 1203 1204 /* Potentially unsafe - do not accept relative paths whatsoever */ 1205 if (strchr(entry->fts_accpath, '/') != NULL) 1206 errx(1, "-delete: %s: relative path potentially not safe", 1207 entry->fts_accpath); 1208 1209 /* Turn off user immutable bits if running as root */ 1210 if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) && 1211 !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && 1212 geteuid() == 0) 1213 chflags(entry->fts_accpath, 1214 entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)); 1215 1216 /* rmdir directories, unlink everything else */ 1217 if (S_ISDIR(entry->fts_statp->st_mode)) { 1218 if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY) 1219 warn("-delete: rmdir(%s)", entry->fts_path); 1220 } else { 1221 if (unlink(entry->fts_accpath) < 0) 1222 warn("-delete: unlink(%s)", entry->fts_path); 1223 } 1224 1225 /* "succeed" */ 1226 return (1); 1227 } 1228 1229 PLAN * 1230 c_delete() 1231 { 1232 1233 ftsoptions &= ~FTS_NOSTAT; /* no optimise */ 1234 ftsoptions |= FTS_PHYSICAL; /* disable -follow */ 1235 ftsoptions &= ~FTS_LOGICAL; /* disable -follow */ 1236 isoutput = 1; /* possible output */ 1237 isdepth = 1; /* -depth implied */ 1238 1239 return (palloc(N_DELETE, f_delete)); 1240 } 1241 1242 /* 1243 * -user uname functions -- 1244 * 1245 * True if the file belongs to the user uname. If uname is numeric and 1246 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not 1247 * return a valid user name, uname is taken as a user ID. 1248 */ 1249 int 1250 f_user(plan, entry) 1251 PLAN *plan; 1252 FTSENT *entry; 1253 { 1254 return (entry->fts_statp->st_uid == plan->u_data); 1255 } 1256 1257 PLAN * 1258 c_user(username) 1259 char *username; 1260 { 1261 PLAN *new; 1262 struct passwd *p; 1263 uid_t uid; 1264 1265 ftsoptions &= ~FTS_NOSTAT; 1266 1267 p = getpwnam(username); 1268 if (p == NULL) { 1269 uid = atoi(username); 1270 if (uid == 0 && username[0] != '0') 1271 errx(1, "-user: %s: no such user", username); 1272 } else 1273 uid = p->pw_uid; 1274 1275 new = palloc(N_USER, f_user); 1276 new->u_data = uid; 1277 return (new); 1278 } 1279 1280 /* 1281 * -xdev functions -- 1282 * 1283 * Always true, causes find not to decend past directories that have a 1284 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1]) 1285 */ 1286 PLAN * 1287 c_xdev() 1288 { 1289 ftsoptions |= FTS_XDEV; 1290 1291 return (palloc(N_XDEV, f_always_true)); 1292 } 1293 1294 /* 1295 * ( expression ) functions -- 1296 * 1297 * True if expression is true. 1298 */ 1299 int 1300 f_expr(plan, entry) 1301 PLAN *plan; 1302 FTSENT *entry; 1303 { 1304 register PLAN *p; 1305 register int state; 1306 1307 state = 0; 1308 for (p = plan->p_data[0]; 1309 p && (state = (p->eval)(p, entry)); p = p->next); 1310 return (state); 1311 } 1312 1313 /* 1314 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are 1315 * eliminated during phase 2 of find_formplan() --- the '(' node is converted 1316 * to a N_EXPR node containing the expression and the ')' node is discarded. 1317 */ 1318 PLAN * 1319 c_openparen() 1320 { 1321 return (palloc(N_OPENPAREN, (int (*)())-1)); 1322 } 1323 1324 PLAN * 1325 c_closeparen() 1326 { 1327 return (palloc(N_CLOSEPAREN, (int (*)())-1)); 1328 } 1329 1330 /* 1331 * ! expression functions -- 1332 * 1333 * Negation of a primary; the unary NOT operator. 1334 */ 1335 int 1336 f_not(plan, entry) 1337 PLAN *plan; 1338 FTSENT *entry; 1339 { 1340 register PLAN *p; 1341 register int state; 1342 1343 state = 0; 1344 for (p = plan->p_data[0]; 1345 p && (state = (p->eval)(p, entry)); p = p->next); 1346 return (!state); 1347 } 1348 1349 PLAN * 1350 c_not() 1351 { 1352 return (palloc(N_NOT, f_not)); 1353 } 1354 1355 /* 1356 * expression -o expression functions -- 1357 * 1358 * Alternation of primaries; the OR operator. The second expression is 1359 * not evaluated if the first expression is true. 1360 */ 1361 int 1362 f_or(plan, entry) 1363 PLAN *plan; 1364 FTSENT *entry; 1365 { 1366 register PLAN *p; 1367 register int state; 1368 1369 state = 0; 1370 for (p = plan->p_data[0]; 1371 p && (state = (p->eval)(p, entry)); p = p->next); 1372 1373 if (state) 1374 return (1); 1375 1376 for (p = plan->p_data[1]; 1377 p && (state = (p->eval)(p, entry)); p = p->next); 1378 return (state); 1379 } 1380 1381 PLAN * 1382 c_or() 1383 { 1384 return (palloc(N_OR, f_or)); 1385 } 1386 1387 static PLAN * 1388 palloc(t, f) 1389 enum ntype t; 1390 int (*f) __P((PLAN *, FTSENT *)); 1391 { 1392 PLAN *new; 1393 1394 if ((new = malloc(sizeof(PLAN))) == NULL) 1395 err(1, NULL); 1396 new->type = t; 1397 new->eval = f; 1398 new->flags = 0; 1399 new->next = NULL; 1400 return (new); 1401 } 1402