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