function.c revision 72945
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 39static const char sccsid[] = "@(#)function.c 8.10 (Berkeley) 5/4/95"; 40#else 41static const char rcsid[] = 42 "$FreeBSD: head/usr.bin/find/function.c 72945 2001-02-23 16:20:55Z knu $"; 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 80static int do_f_regex __P((PLAN *, FTSENT *, int)); 81static PLAN *do_c_regex __P((char *, int)); 82static 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 */ 88static long long 89find_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 */ 143int 144f_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 154PLAN * 155c_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 */ 175int 176f_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 186PLAN * 187c_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 */ 207int 208f_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 218PLAN * 219c_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 */ 238int 239f_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 249PLAN * 250c_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 */ 271int 272f_always_true(plan, entry) 273 PLAN *plan; 274 FTSENT *entry; 275{ 276 return (1); 277} 278 279PLAN * 280c_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 */ 299int 300f_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 */ 344PLAN * 345c_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 */ 396int 397f_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 425PLAN * 426c_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 */ 442int 443f_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 */ 487PLAN * 488c_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 */ 538PLAN * 539c_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 */ 552int 553f_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__) 613int 614f_always_false(plan, entry) 615 PLAN *plan; 616 FTSENT *entry; 617{ 618 return (0); 619} 620 621PLAN * 622c_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 */ 675int 676f_group(plan, entry) 677 PLAN *plan; 678 FTSENT *entry; 679{ 680 return (entry->fts_statp->st_gid == plan->g_data); 681} 682 683PLAN * 684c_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 */ 711int 712f_inum(plan, entry) 713 PLAN *plan; 714 FTSENT *entry; 715{ 716 COMPARE(entry->fts_statp->st_ino, plan->i_data); 717} 718 719PLAN * 720c_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 */ 737int 738f_links(plan, entry) 739 PLAN *plan; 740 FTSENT *entry; 741{ 742 COMPARE(entry->fts_statp->st_nlink, plan->l_data); 743} 744 745PLAN * 746c_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 */ 763int 764f_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 772PLAN * 773c_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 */ 790int 791f_maxdepth(plan, entry) 792 PLAN *plan; 793 FTSENT *entry; 794{ 795 return (1); 796} 797 798PLAN * 799c_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 */ 819int 820f_mindepth(plan, entry) 821 PLAN *plan; 822 FTSENT *entry; 823{ 824 return (1); 825} 826 827PLAN * 828c_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 */ 848int 849f_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 859PLAN * 860c_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 */ 879int 880f_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 890PLAN * 891c_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 */ 911int 912f_name(plan, entry) 913 PLAN *plan; 914 FTSENT *entry; 915{ 916 return (!fnmatch(plan->c_data, entry->fts_name, 0)); 917} 918 919PLAN * 920c_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 */ 936int 937f_iname(plan, entry) 938 PLAN *plan; 939 FTSENT *entry; 940{ 941 return (!fnmatch(plan->c_data, entry->fts_name, FNM_CASEFOLD)); 942} 943 944PLAN * 945c_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 */ 962int 963f_regex(plan, entry) 964 PLAN *plan; 965 FTSENT *entry; 966{ 967 return (do_f_regex(plan, entry, 0)); 968} 969 970PLAN * 971c_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 */ 982int 983f_iregex(plan, entry) 984 PLAN *plan; 985 FTSENT *entry; 986{ 987 return (do_f_regex(plan, entry, REG_ICASE)); 988} 989 990PLAN * 991c_iregex(pattern) 992 char *pattern; 993{ 994 return (do_c_regex(pattern, REG_ICASE)); 995} 996 997static int 998do_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 1033PLAN * 1034do_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 */ 1064int 1065f_newer(plan, entry) 1066 PLAN *plan; 1067 FTSENT *entry; 1068{ 1069 return (entry->fts_statp->st_mtime > plan->t_data); 1070} 1071 1072PLAN * 1073c_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 */ 1094int 1095f_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 1102PLAN * 1103c_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 */ 1116int 1117f_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 1124PLAN * 1125c_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 */ 1138int 1139f_path(plan, entry) 1140 PLAN *plan; 1141 FTSENT *entry; 1142{ 1143 return (!fnmatch(plan->c_data, entry->fts_path, 0)); 1144} 1145 1146PLAN * 1147c_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 */ 1162int 1163f_ipath(plan, entry) 1164 PLAN *plan; 1165 FTSENT *entry; 1166{ 1167 return (!fnmatch(plan->c_data, entry->fts_path, FNM_CASEFOLD)); 1168} 1169 1170PLAN * 1171c_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 */ 1188int 1189f_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 1206PLAN * 1207c_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 */ 1238int 1239f_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 1257PLAN * 1258c_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 */ 1290int 1291f_print(plan, entry) 1292 PLAN *plan; 1293 FTSENT *entry; 1294{ 1295 (void)puts(entry->fts_path); 1296 return (1); 1297} 1298 1299PLAN * 1300c_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 */ 1313int 1314f_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 1323PLAN * 1324c_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 */ 1336int 1337f_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 1348PLAN * 1349c_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 1362static int divsize = 1; 1363 1364int 1365f_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 1376PLAN * 1377c_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 */ 1400int 1401f_type(plan, entry) 1402 PLAN *plan; 1403 FTSENT *entry; 1404{ 1405 return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data); 1406} 1407 1408PLAN * 1409c_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 */ 1459int 1460f_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 1501PLAN * 1502c_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 */ 1521int 1522f_user(plan, entry) 1523 PLAN *plan; 1524 FTSENT *entry; 1525{ 1526 return (entry->fts_statp->st_uid == plan->u_data); 1527} 1528 1529PLAN * 1530c_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 */ 1558PLAN * 1559c_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 */ 1571int 1572f_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 */ 1590PLAN * 1591c_openparen() 1592{ 1593 return (palloc(N_OPENPAREN, (int (*)())-1)); 1594} 1595 1596PLAN * 1597c_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 */ 1607int 1608f_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 1621PLAN * 1622c_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 */ 1633int 1634f_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 1653PLAN * 1654c_or() 1655{ 1656 return (palloc(N_OR, f_or)); 1657} 1658 1659static PLAN * 1660palloc(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