1 /* $NetBSD: function.c,v 1.81 2024/02/05 21:46:07 andvar 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 #include <sys/cdefs.h> 36 #ifndef lint 37 #if 0 38 static char sccsid[] = "from: @(#)function.c 8.10 (Berkeley) 5/4/95"; 39 #else 40 __RCSID("$NetBSD: function.c,v 1.81 2024/02/05 21:46:07 andvar Exp $"); 41 #endif 42 #endif /* not lint */ 43 44 #include <sys/param.h> 45 #include <sys/stat.h> 46 #include <sys/wait.h> 47 #include <sys/mount.h> 48 49 #include <dirent.h> 50 #include <err.h> 51 #include <errno.h> 52 #include <fnmatch.h> 53 #include <fts.h> 54 #include <grp.h> 55 #include <inttypes.h> 56 #include <limits.h> 57 #include <pwd.h> 58 #include <stdbool.h> 59 #include <stdio.h> 60 #include <stdlib.h> 61 #include <string.h> 62 #include <tzfile.h> 63 #include <unistd.h> 64 #include <util.h> 65 66 #include "find.h" 67 68 #define COMPARE(a, b) { \ 69 switch (plan->flags) { \ 70 case F_EQUAL: \ 71 return (a == b); \ 72 case F_LESSTHAN: \ 73 return (a < b); \ 74 case F_GREATER: \ 75 return (a > b); \ 76 default: \ 77 abort(); \ 78 } \ 79 } 80 81 static int64_t find_parsenum(PLAN *, const char *, const char *, char *); 82 static void run_f_exec(PLAN *); 83 int f_always_true(PLAN *, FTSENT *); 84 int f_amin(PLAN *, FTSENT *); 85 int f_anewer(PLAN *, FTSENT *); 86 int f_asince(PLAN *, FTSENT *); 87 int f_atime(PLAN *, FTSENT *); 88 int f_cmin(PLAN *, FTSENT *); 89 int f_cnewer(PLAN *, FTSENT *); 90 int f_csince(PLAN *, FTSENT *); 91 int f_ctime(PLAN *, FTSENT *); 92 int f_delete(PLAN *, FTSENT *); 93 int f_empty(PLAN *, FTSENT *); 94 int f_exec(PLAN *, FTSENT *); 95 int f_execdir(PLAN *, FTSENT *); 96 int f_false(PLAN *, FTSENT *); 97 int f_flags(PLAN *, FTSENT *); 98 int f_fprint(PLAN *, FTSENT *); 99 int f_fstype(PLAN *, FTSENT *); 100 int f_group(PLAN *, FTSENT *); 101 int f_iname(PLAN *, FTSENT *); 102 int f_inum(PLAN *, FTSENT *); 103 int f_links(PLAN *, FTSENT *); 104 int f_ls(PLAN *, FTSENT *); 105 int f_mindepth(PLAN *, FTSENT *); 106 int f_maxdepth(PLAN *, FTSENT *); 107 int f_mmin(PLAN *, FTSENT *); 108 int f_mtime(PLAN *, FTSENT *); 109 int f_name(PLAN *, FTSENT *); 110 int f_newer(PLAN *, FTSENT *); 111 /* 112 * Unimplemented Gnu findutils options 113 * 114 int f_newerBB(PLAN *, FTSENT *); 115 int f_newerBa(PLAN *, FTSENT *); 116 int f_newerBc(PLAN *, FTSENT *); 117 int f_newerBm(PLAN *, FTSENT *); 118 int f_newerBt(PLAN *, FTSENT *); 119 int f_neweraB(PLAN *, FTSENT *); 120 int f_newerac(PLAN *, FTSENT *); 121 int f_neweram(PLAN *, FTSENT *); 122 int f_newerca(PLAN *, FTSENT *); 123 int f_newercm(PLAN *, FTSENT *); 124 int f_newercB(PLAN *, FTSENT *); 125 int f_newermB(PLAN *, FTSENT *); 126 int f_newerma(PLAN *, FTSENT *); 127 int f_newermc(PLAN *, FTSENT *); 128 * 129 */ 130 int f_nogroup(PLAN *, FTSENT *); 131 int f_nouser(PLAN *, FTSENT *); 132 int f_path(PLAN *, FTSENT *); 133 int f_perm(PLAN *, FTSENT *); 134 int f_print(PLAN *, FTSENT *); 135 int f_print0(PLAN *, FTSENT *); 136 int f_printx(PLAN *, FTSENT *); 137 int f_prune(PLAN *, FTSENT *); 138 int f_regex(PLAN *, FTSENT *); 139 int f_since(PLAN *, FTSENT *); 140 int f_size(PLAN *, FTSENT *); 141 int f_type(PLAN *, FTSENT *); 142 int f_user(PLAN *, FTSENT *); 143 int f_not(PLAN *, FTSENT *); 144 int f_or(PLAN *, FTSENT *); 145 static PLAN *c_regex_common(char ***, int, enum ntype, bool); 146 static PLAN *palloc(enum ntype, int (*)(PLAN *, FTSENT *)); 147 148 extern int dotfd; 149 extern FTS *tree; 150 extern time_t now; 151 152 /* 153 * find_parsenum -- 154 * Parse a string of the form [+-]# and return the value. 155 */ 156 static int64_t 157 find_parsenum(PLAN *plan, const char *option, const char *vp, char *endch) 158 { 159 int64_t value; 160 const char *str; 161 char *endchar; /* Pointer to character ending conversion. */ 162 163 /* Determine comparison from leading + or -. */ 164 str = vp; 165 switch (*str) { 166 case '+': 167 ++str; 168 plan->flags = F_GREATER; 169 break; 170 case '-': 171 ++str; 172 plan->flags = F_LESSTHAN; 173 break; 174 default: 175 plan->flags = F_EQUAL; 176 break; 177 } 178 179 /* 180 * Convert the string with strtol(). Note, if strtol() returns zero 181 * and endchar points to the beginning of the string we know we have 182 * a syntax error. 183 */ 184 value = strtoll(str, &endchar, 10); 185 if (value == 0 && endchar == str) 186 errx(1, "%s: %s: illegal numeric value", option, vp); 187 if (endchar[0] && (endch == NULL || endchar[0] != *endch)) 188 errx(1, "%s: %s: illegal trailing character", option, vp); 189 if (endch) 190 *endch = endchar[0]; 191 return (value); 192 } 193 194 /* 195 * find_parsedate -- 196 * 197 * Validate the timestamp argument or report an error 198 */ 199 static time_t 200 find_parsedate(PLAN *plan, const char *option, const char *vp) 201 { 202 time_t timestamp; 203 204 errno = 0; 205 timestamp = parsedate(vp, NULL, NULL); 206 if (timestamp == -1 && errno != 0) 207 errx(1, "%s: %s: invalid timestamp value", option, vp); 208 return timestamp; 209 } 210 211 /* 212 * The value of n for the inode times (atime, ctime, and mtime) is a range, 213 * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with 214 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the 215 * user wanted. Correct so that -1 is "less than 1". 216 */ 217 #define TIME_CORRECT(p, ttype) \ 218 if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \ 219 ++((p)->t_data); 220 221 /* 222 * -amin n functions -- 223 * 224 * True if the difference between the file access time and the 225 * current time is n 1 minute periods. 226 */ 227 int 228 f_amin(PLAN *plan, FTSENT *entry) 229 { 230 COMPARE((now - entry->fts_statp->st_atime + 231 SECSPERMIN - 1) / SECSPERMIN, plan->t_data); 232 } 233 234 PLAN * 235 c_amin(char ***argvp, int isok, char *opt) 236 { 237 char *arg = **argvp; 238 PLAN *new; 239 240 (*argvp)++; 241 ftsoptions &= ~FTS_NOSTAT; 242 243 new = palloc(N_AMIN, f_amin); 244 new->t_data = find_parsenum(new, opt, arg, NULL); 245 TIME_CORRECT(new, N_AMIN); 246 return (new); 247 } 248 249 /* 250 * -anewer file functions -- 251 * 252 * True if the current file has been accessed more recently 253 * than the access time of the file named by the pathname 254 * file. 255 */ 256 int 257 f_anewer(PLAN *plan, FTSENT *entry) 258 { 259 260 return timespeccmp(&entry->fts_statp->st_atim, &plan->ts_data, >); 261 } 262 263 PLAN * 264 c_anewer(char ***argvp, int isok, char *opt) 265 { 266 char *filename = **argvp; 267 PLAN *new; 268 struct stat sb; 269 270 (*argvp)++; 271 ftsoptions &= ~FTS_NOSTAT; 272 273 if (stat(filename, &sb)) 274 err(1, "%s: %s", opt, filename); 275 new = palloc(N_ANEWER, f_anewer); 276 new->ts_data = sb.st_atim; 277 return (new); 278 } 279 280 /* 281 * -asince "timestamp" functions -- 282 * 283 * True if the file access time is greater than the timestamp value 284 */ 285 int 286 f_asince(PLAN *plan, FTSENT *entry) 287 { 288 COMPARE(entry->fts_statp->st_atime, plan->t_data); 289 } 290 291 PLAN * 292 c_asince(char ***argvp, int isok, char *opt) 293 { 294 char *arg = **argvp; 295 PLAN *new; 296 297 (*argvp)++; 298 ftsoptions &= ~FTS_NOSTAT; 299 300 new = palloc(N_ASINCE, f_asince); 301 new->t_data = find_parsedate(new, opt, arg); 302 new->flags = F_GREATER; 303 return (new); 304 } 305 306 /* 307 * -atime n functions -- 308 * 309 * True if the difference between the file access time and the 310 * current time is n 24 hour periods. 311 */ 312 int 313 f_atime(PLAN *plan, FTSENT *entry) 314 { 315 COMPARE((now - entry->fts_statp->st_atime + 316 SECSPERDAY - 1) / SECSPERDAY, plan->t_data); 317 } 318 319 PLAN * 320 c_atime(char ***argvp, int isok, char *opt) 321 { 322 char *arg = **argvp; 323 PLAN *new; 324 325 (*argvp)++; 326 ftsoptions &= ~FTS_NOSTAT; 327 328 new = palloc(N_ATIME, f_atime); 329 new->t_data = find_parsenum(new, opt, arg, NULL); 330 TIME_CORRECT(new, N_ATIME); 331 return (new); 332 } 333 334 /* 335 * -cmin n functions -- 336 * 337 * True if the difference between the last change of file 338 * status information and the current time is n 24 hour periods. 339 */ 340 int 341 f_cmin(PLAN *plan, FTSENT *entry) 342 { 343 COMPARE((now - entry->fts_statp->st_ctime + 344 SECSPERMIN - 1) / SECSPERMIN, plan->t_data); 345 } 346 347 PLAN * 348 c_cmin(char ***argvp, int isok, char *opt) 349 { 350 char *arg = **argvp; 351 PLAN *new; 352 353 (*argvp)++; 354 ftsoptions &= ~FTS_NOSTAT; 355 356 new = palloc(N_CMIN, f_cmin); 357 new->t_data = find_parsenum(new, opt, arg, NULL); 358 TIME_CORRECT(new, N_CMIN); 359 return (new); 360 } 361 362 /* 363 * -cnewer file functions -- 364 * 365 * True if the current file has been changed more recently 366 * than the changed time of the file named by the pathname 367 * file. 368 */ 369 int 370 f_cnewer(PLAN *plan, FTSENT *entry) 371 { 372 373 return timespeccmp(&entry->fts_statp->st_ctim, &plan->ts_data, >); 374 } 375 376 PLAN * 377 c_cnewer(char ***argvp, int isok, char *opt) 378 { 379 char *filename = **argvp; 380 PLAN *new; 381 struct stat sb; 382 383 (*argvp)++; 384 ftsoptions &= ~FTS_NOSTAT; 385 386 if (stat(filename, &sb)) 387 err(1, "%s: %s ", opt, filename); 388 new = palloc(N_CNEWER, f_cnewer); 389 new->ts_data = sb.st_ctim; 390 return (new); 391 } 392 393 /* 394 * -csince "timestamp" functions -- 395 * 396 * True if the file status change time is greater than the timestamp value 397 */ 398 int 399 f_csince(PLAN *plan, FTSENT *entry) 400 { 401 COMPARE(entry->fts_statp->st_ctime, plan->t_data); 402 } 403 404 PLAN * 405 c_csince(char ***argvp, int isok, char *opt) 406 { 407 char *arg = **argvp; 408 PLAN *new; 409 410 (*argvp)++; 411 ftsoptions &= ~FTS_NOSTAT; 412 413 new = palloc(N_CSINCE, f_csince); 414 new->t_data = find_parsedate(new, opt, arg); 415 new->flags = F_GREATER; 416 return (new); 417 } 418 419 /* 420 * -ctime n functions -- 421 * 422 * True if the difference between the last change of file 423 * status information and the current time is n 24 hour periods. 424 */ 425 int 426 f_ctime(PLAN *plan, FTSENT *entry) 427 { 428 COMPARE((now - entry->fts_statp->st_ctime + 429 SECSPERDAY - 1) / SECSPERDAY, plan->t_data); 430 } 431 432 PLAN * 433 c_ctime(char ***argvp, int isok, char *opt) 434 { 435 char *arg = **argvp; 436 PLAN *new; 437 438 (*argvp)++; 439 ftsoptions &= ~FTS_NOSTAT; 440 441 new = palloc(N_CTIME, f_ctime); 442 new->t_data = find_parsenum(new, opt, arg, NULL); 443 TIME_CORRECT(new, N_CTIME); 444 return (new); 445 } 446 447 /* 448 * -delete functions -- 449 * 450 * Always true. Makes its best shot and continues on regardless. 451 */ 452 int 453 f_delete(PLAN *plan __unused, FTSENT *entry) 454 { 455 /* ignore these from fts */ 456 if (strcmp(entry->fts_accpath, ".") == 0 || 457 strcmp(entry->fts_accpath, "..") == 0) 458 return 1; 459 460 /* sanity check */ 461 if (isdepth == 0 || /* depth off */ 462 (ftsoptions & FTS_NOSTAT) || /* not stat()ing */ 463 !(ftsoptions & FTS_PHYSICAL) || /* physical off */ 464 (ftsoptions & FTS_LOGICAL)) /* or finally, logical on */ 465 errx(1, "-delete: insecure options got turned on"); 466 467 /* Potentially unsafe - do not accept relative paths whatsoever */ 468 if (entry->fts_level > 0 && strchr(entry->fts_accpath, '/') != NULL) 469 errx(1, "-delete: %s: relative path potentially not safe", 470 entry->fts_accpath); 471 472 /* Turn off user immutable bits if running as root */ 473 if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) && 474 !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && 475 geteuid() == 0) 476 chflags(entry->fts_accpath, 477 entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)); 478 479 /* rmdir directories, unlink everything else */ 480 if (S_ISDIR(entry->fts_statp->st_mode)) { 481 if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY) 482 warn("-delete: rmdir(%s)", entry->fts_path); 483 } else { 484 if (unlink(entry->fts_accpath) < 0) 485 warn("-delete: unlink(%s)", entry->fts_path); 486 } 487 488 /* "succeed" */ 489 return 1; 490 } 491 492 PLAN * 493 c_delete(char ***argvp __unused, int isok, char *opt) 494 { 495 496 ftsoptions &= ~FTS_NOSTAT; /* no optimize */ 497 ftsoptions |= FTS_PHYSICAL; /* disable -follow */ 498 ftsoptions &= ~FTS_LOGICAL; /* disable -follow */ 499 isoutput = 1; /* possible output */ 500 isdepth = 1; /* -depth implied */ 501 502 return palloc(N_DELETE, f_delete); 503 } 504 505 /* 506 * -depth functions -- 507 * 508 * Always true, causes descent of the directory hierarchy to be done 509 * so that all entries in a directory are acted on before the directory 510 * itself. 511 */ 512 int 513 f_always_true(PLAN *plan, FTSENT *entry) 514 { 515 516 return (1); 517 } 518 519 PLAN * 520 c_depth(char ***argvp, int isok, char *opt) 521 { 522 isdepth = 1; 523 524 return (palloc(N_DEPTH, f_always_true)); 525 } 526 527 /* 528 * -empty functions -- 529 * 530 * True if the file or directory is empty 531 */ 532 int 533 f_empty(PLAN *plan, FTSENT *entry) 534 { 535 if (S_ISREG(entry->fts_statp->st_mode) && 536 entry->fts_statp->st_size == 0) 537 return (1); 538 if (S_ISDIR(entry->fts_statp->st_mode)) { 539 struct dirent *dp; 540 int empty; 541 DIR *dir; 542 543 empty = 1; 544 dir = opendir(entry->fts_accpath); 545 if (dir == NULL) 546 return (0); 547 for (dp = readdir(dir); dp; dp = readdir(dir)) 548 if (dp->d_name[0] != '.' || 549 (dp->d_name[1] != '\0' && 550 (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) { 551 empty = 0; 552 break; 553 } 554 closedir(dir); 555 return (empty); 556 } 557 return (0); 558 } 559 560 PLAN * 561 c_empty(char ***argvp, int isok, char *opt) 562 { 563 ftsoptions &= ~FTS_NOSTAT; 564 565 return (palloc(N_EMPTY, f_empty)); 566 } 567 568 /* 569 * [-exec | -ok] utility [arg ... ] ; functions -- 570 * [-exec | -ok] utility [arg ... ] {} + functions -- 571 * 572 * If the end of the primary expression is delimited by a 573 * semicolon: true if the executed utility returns a zero value 574 * as exit status. If "{}" occurs anywhere, it gets replaced by 575 * the current pathname. 576 * 577 * If the end of the primary expression is delimited by a plus 578 * sign: always true. Pathnames for which the primary is 579 * evaluated shall be aggregated into sets. The utility will be 580 * executed once per set, with "{}" replaced by the entire set of 581 * pathnames (as if xargs). "{}" must appear last. 582 * 583 * The current directory for the execution of utility is the same 584 * as the current directory when the find utility was started. 585 * 586 * The primary -ok is different in that it requests affirmation 587 * of the user before executing the utility. 588 */ 589 int 590 f_exec(PLAN *plan, FTSENT *entry) 591 { 592 size_t cnt; 593 int l; 594 pid_t pid; 595 int status; 596 597 if (plan->flags & F_PLUSSET) { 598 /* 599 * Confirm sufficient buffer space, then copy the path 600 * to the buffer. 601 */ 602 l = strlen(entry->fts_path); 603 if (plan->ep_p + l < plan->ep_ebp) { 604 plan->ep_bxp[plan->ep_narg++] = 605 strcpy(plan->ep_p, entry->fts_path); 606 plan->ep_p += l + 1; 607 608 if (plan->ep_narg == plan->ep_maxargs) 609 run_f_exec(plan); 610 } else { 611 /* 612 * Without sufficient space to copy in the next 613 * argument, run the command to empty out the 614 * buffer before re-attepting the copy. 615 */ 616 run_f_exec(plan); 617 if ((plan->ep_p + l < plan->ep_ebp)) { 618 plan->ep_bxp[plan->ep_narg++] 619 = strcpy(plan->ep_p, entry->fts_path); 620 plan->ep_p += l + 1; 621 } else 622 errx(1, "insufficient space for argument"); 623 } 624 return (1); 625 } else { 626 for (cnt = 0; plan->e_argv[cnt]; ++cnt) 627 if (plan->e_len[cnt]) 628 brace_subst(plan->e_orig[cnt], 629 &plan->e_argv[cnt], 630 entry->fts_path, 631 &plan->e_len[cnt]); 632 if (plan->flags & F_NEEDOK && !queryuser(plan->e_argv)) 633 return (0); 634 635 /* Don't mix output of command with find output. */ 636 fflush(stdout); 637 fflush(stderr); 638 639 switch (pid = vfork()) { 640 case -1: 641 err(1, "vfork"); 642 /* NOTREACHED */ 643 case 0: 644 /* change dir back from where we started */ 645 if (!(ftsoptions & FTS_NOCHDIR) && fchdir(dotfd)) { 646 warn("chdir"); 647 _exit(1); 648 } 649 execvp(plan->e_argv[0], plan->e_argv); 650 warn("%s", plan->e_argv[0]); 651 _exit(1); 652 } 653 pid = waitpid(pid, &status, 0); 654 return (pid != -1 && WIFEXITED(status) 655 && !WEXITSTATUS(status)); 656 } 657 } 658 659 static void 660 run_f_exec(PLAN *plan) 661 { 662 pid_t pid; 663 int rval, status; 664 665 /* Ensure arg list is null terminated. */ 666 plan->ep_bxp[plan->ep_narg] = NULL; 667 668 /* Don't mix output of command with find output. */ 669 fflush(stdout); 670 fflush(stderr); 671 672 switch (pid = vfork()) { 673 case -1: 674 err(1, "vfork"); 675 /* NOTREACHED */ 676 case 0: 677 /* change dir back from where we started */ 678 if (!(ftsoptions & FTS_NOCHDIR) && fchdir(dotfd)) { 679 warn("chdir"); 680 _exit(1); 681 } 682 execvp(plan->e_argv[0], plan->e_argv); 683 warn("%s", plan->e_argv[0]); 684 _exit(1); 685 } 686 687 /* Clear out the argument list. */ 688 plan->ep_narg = 0; 689 plan->ep_bxp[plan->ep_narg] = NULL; 690 /* As well as the argument buffer. */ 691 plan->ep_p = plan->ep_bbp; 692 *plan->ep_p = '\0'; 693 694 pid = waitpid(pid, &status, 0); 695 if (WIFEXITED(status)) 696 rval = WEXITSTATUS(status); 697 else 698 rval = -1; 699 700 /* 701 * If we have a non-zero exit status, preserve it so find(1) can 702 * later exit with it. 703 */ 704 if (rval) 705 plan->ep_rval = rval; 706 } 707 708 /* 709 * c_exec -- 710 * build three parallel arrays, one with pointers to the strings passed 711 * on the command line, one with (possibly duplicated) pointers to the 712 * argv array, and one with integer values that are lengths of the 713 * strings, but also flags meaning that the string has to be massaged. 714 * 715 * If -exec ... {} +, use only the first array, but make it large 716 * enough to hold 5000 args (cf. src/usr.bin/xargs/xargs.c for a 717 * discussion), and then allocate ARG_MAX - 4K of space for args. 718 */ 719 PLAN * 720 c_exec(char ***argvp, int isok, char *opt) 721 { 722 PLAN *new; /* node returned */ 723 size_t cnt; 724 int brace, lastbrace; 725 char **argv, **ap, *p; 726 727 isoutput = 1; 728 729 new = palloc(N_EXEC, f_exec); 730 if (isok) 731 new->flags |= F_NEEDOK; 732 733 /* 734 * Terminate if we encounter an arg exactly equal to ";", or an 735 * arg exactly equal to "+" following an arg exactly equal to 736 * "{}". 737 */ 738 for (ap = argv = *argvp, brace = 0;; ++ap) { 739 if (!*ap) 740 errx(1, "%s: no terminating \";\" or \"+\"", opt); 741 lastbrace = brace; 742 brace = 0; 743 if (strcmp(*ap, "{}") == 0) 744 brace = 1; 745 if (strcmp(*ap, ";") == 0) 746 break; 747 if (strcmp(*ap, "+") == 0 && lastbrace) { 748 new->flags |= F_PLUSSET; 749 break; 750 } 751 } 752 753 /* 754 * POSIX says -ok ... {} + "need not be supported," and it does 755 * not make much sense anyway. 756 */ 757 if (new->flags & F_NEEDOK && new->flags & F_PLUSSET) 758 errx(1, "%s: terminating \"+\" not permitted.", opt); 759 760 if (new->flags & F_PLUSSET) { 761 size_t c, bufsize; 762 763 cnt = ap - *argvp - 1; /* units are words */ 764 new->ep_maxargs = ARG_MAX / (sizeof (char *) + 16); 765 if (new->ep_maxargs > 5000) 766 new->ep_maxargs = 5000; 767 new->e_argv = emalloc((cnt + new->ep_maxargs) 768 * sizeof(*new->e_argv)); 769 770 /* We start stuffing arguments after the user's last one. */ 771 new->ep_bxp = &new->e_argv[cnt]; 772 new->ep_narg = 0; 773 774 /* 775 * Count up the space of the user's arguments, and 776 * subtract that from what we allocate. 777 */ 778 #define MAXARG (ARG_MAX - 4 * 1024) 779 for (argv = *argvp, c = 0, cnt = 0; 780 argv < ap; 781 ++argv, ++cnt) { 782 c += strlen(*argv) + 1; 783 if (c >= MAXARG) 784 errx(1, "Arguments too long"); 785 new->e_argv[cnt] = *argv; 786 } 787 if (c + new->ep_maxargs * sizeof (char *) >= MAXARG) 788 errx(1, "Arguments too long"); 789 bufsize = MAXARG - c - new->ep_maxargs * sizeof (char *); 790 791 /* 792 * Allocate, and then initialize current, base, and 793 * end pointers. 794 */ 795 new->ep_p = new->ep_bbp = emalloc(bufsize + 1); 796 new->ep_ebp = new->ep_bbp + bufsize - 1; 797 new->ep_rval = 0; 798 } else { /* !F_PLUSSET */ 799 cnt = ap - *argvp + 1; 800 new->e_argv = emalloc(cnt * sizeof(*new->e_argv)); 801 new->e_orig = emalloc(cnt * sizeof(*new->e_orig)); 802 new->e_len = emalloc(cnt * sizeof(*new->e_len)); 803 804 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) { 805 new->e_orig[cnt] = *argv; 806 for (p = *argv; *p; ++p) 807 if (p[0] == '{' && p[1] == '}') { 808 new->e_argv[cnt] = 809 emalloc(MAXPATHLEN); 810 new->e_len[cnt] = MAXPATHLEN; 811 break; 812 } 813 if (!*p) { 814 new->e_argv[cnt] = *argv; 815 new->e_len[cnt] = 0; 816 } 817 } 818 new->e_orig[cnt] = NULL; 819 } 820 821 new->e_argv[cnt] = NULL; 822 *argvp = argv + 1; 823 return (new); 824 } 825 826 /* 827 * -execdir utility [arg ... ] ; functions -- 828 * 829 * True if the executed utility returns a zero value as exit status. 830 * The end of the primary expression is delimited by a semicolon. If 831 * "{}" occurs anywhere, it gets replaced by the unqualified pathname. 832 * The current directory for the execution of utility is the same as 833 * the directory where the file lives. 834 */ 835 int 836 f_execdir(PLAN *plan, FTSENT *entry) 837 { 838 size_t cnt; 839 pid_t pid; 840 int status; 841 char *file; 842 843 /* XXX - if file/dir ends in '/' this will not work -- can it? */ 844 if ((file = strrchr(entry->fts_path, '/'))) 845 file++; 846 else 847 file = entry->fts_path; 848 849 for (cnt = 0; plan->e_argv[cnt]; ++cnt) 850 if (plan->e_len[cnt]) 851 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt], 852 file, &plan->e_len[cnt]); 853 854 /* don't mix output of command with find output */ 855 fflush(stdout); 856 fflush(stderr); 857 858 switch (pid = vfork()) { 859 case -1: 860 err(1, "fork"); 861 /* NOTREACHED */ 862 case 0: 863 execvp(plan->e_argv[0], plan->e_argv); 864 warn("%s", plan->e_argv[0]); 865 _exit(1); 866 } 867 pid = waitpid(pid, &status, 0); 868 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status)); 869 } 870 871 /* 872 * c_execdir -- 873 * build three parallel arrays, one with pointers to the strings passed 874 * on the command line, one with (possibly duplicated) pointers to the 875 * argv array, and one with integer values that are lengths of the 876 * strings, but also flags meaning that the string has to be massaged. 877 */ 878 PLAN * 879 c_execdir(char ***argvp, int isok, char *opt) 880 { 881 PLAN *new; /* node returned */ 882 size_t cnt; 883 char **argv, **ap, *p; 884 885 ftsoptions &= ~FTS_NOSTAT; 886 isoutput = 1; 887 888 new = palloc(N_EXECDIR, f_execdir); 889 890 for (ap = argv = *argvp;; ++ap) { 891 if (!*ap) 892 errx(1, "%s: no terminating \";\"", opt); 893 if (**ap == ';') 894 break; 895 } 896 897 cnt = ap - *argvp + 1; 898 new->e_argv = emalloc(cnt * sizeof(*new->e_argv)); 899 new->e_orig = emalloc(cnt * sizeof(*new->e_orig)); 900 new->e_len = emalloc(cnt * sizeof(*new->e_len)); 901 902 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) { 903 new->e_orig[cnt] = *argv; 904 for (p = *argv; *p; ++p) 905 if (p[0] == '{' && p[1] == '}') { 906 new->e_argv[cnt] = emalloc(MAXPATHLEN); 907 new->e_len[cnt] = MAXPATHLEN; 908 break; 909 } 910 if (!*p) { 911 new->e_argv[cnt] = *argv; 912 new->e_len[cnt] = 0; 913 } 914 } 915 new->e_argv[cnt] = new->e_orig[cnt] = NULL; 916 917 *argvp = argv + 1; 918 return (new); 919 } 920 921 PLAN * 922 c_exit(char ***argvp, int isok, char *opt) 923 { 924 char *arg = **argvp; 925 PLAN *new; 926 927 /* not technically true, but otherwise '-print' is implied */ 928 isoutput = 1; 929 930 new = palloc(N_EXIT, f_always_true); 931 932 if (arg) { 933 (*argvp)++; 934 new->exit_val = find_parsenum(new, opt, arg, NULL); 935 } else 936 new->exit_val = 0; 937 938 return (new); 939 } 940 941 942 /* 943 * -false function 944 */ 945 int 946 f_false(PLAN *plan, FTSENT *entry) 947 { 948 949 return (0); 950 } 951 952 PLAN * 953 c_false(char ***argvp, int isok, char *opt) 954 { 955 return (palloc(N_FALSE, f_false)); 956 } 957 958 959 /* 960 * -flags [-]flags functions -- 961 */ 962 int 963 f_flags(PLAN *plan, FTSENT *entry) 964 { 965 uint32_t flags; 966 967 flags = entry->fts_statp->st_flags; 968 if (plan->flags == F_ATLEAST) 969 return ((plan->f_data | flags) == flags); 970 else 971 return (flags == plan->f_data); 972 /* NOTREACHED */ 973 } 974 975 PLAN * 976 c_flags(char ***argvp, int isok, char *opt) 977 { 978 char *flags = **argvp; 979 PLAN *new; 980 u_long flagset; 981 982 (*argvp)++; 983 ftsoptions &= ~FTS_NOSTAT; 984 985 new = palloc(N_FLAGS, f_flags); 986 987 if (*flags == '-') { 988 new->flags = F_ATLEAST; 989 ++flags; 990 } 991 992 flagset = 0; 993 if ((strcmp(flags, "none") != 0) && 994 (string_to_flags(&flags, &flagset, NULL) != 0)) 995 errx(1, "%s: %s: illegal flags string", opt, flags); 996 new->f_data = flagset; 997 return (new); 998 } 999 1000 /* 1001 * -follow functions -- 1002 * 1003 * Always true, causes symbolic links to be followed on a global 1004 * basis. 1005 */ 1006 PLAN * 1007 c_follow(char ***argvp, int isok, char *opt) 1008 { 1009 ftsoptions &= ~FTS_PHYSICAL; 1010 ftsoptions |= FTS_LOGICAL; 1011 1012 return (palloc(N_FOLLOW, f_always_true)); 1013 } 1014 1015 /* -fprint functions -- 1016 * 1017 * Causes the current pathname to be written to the defined output file. 1018 */ 1019 int 1020 f_fprint(PLAN *plan, FTSENT *entry) 1021 { 1022 1023 if (-1 == fprintf(plan->fprint_file, "%s\n", entry->fts_path)) 1024 warn("fprintf"); 1025 1026 return(1); 1027 1028 /* no descriptors are closed; they will be closed by 1029 operating system when this find command exits. */ 1030 } 1031 1032 PLAN * 1033 c_fprint(char ***argvp, int isok, char *opt) 1034 { 1035 PLAN *new; 1036 1037 isoutput = 1; /* do not assume -print */ 1038 1039 new = palloc(N_FPRINT, f_fprint); 1040 1041 if (NULL == (new->fprint_file = fopen(**argvp, "w"))) 1042 err(1, "%s: %s: cannot create file", opt, **argvp); 1043 1044 (*argvp)++; 1045 return (new); 1046 } 1047 1048 /* 1049 * -fstype functions -- 1050 * 1051 * True if the file is of a certain type. 1052 */ 1053 int 1054 f_fstype(PLAN *plan, FTSENT *entry) 1055 { 1056 static dev_t curdev; /* need a guaranteed illegal dev value */ 1057 static int first = 1; 1058 struct statvfs sb; 1059 static short val; 1060 static char fstype[sizeof(sb.f_fstypename)]; 1061 char *p, save[2]; 1062 1063 memset(&save, 0, sizeof save); /* XXX gcc */ 1064 1065 /* Only check when we cross mount point. */ 1066 if (first || curdev != entry->fts_statp->st_dev) { 1067 curdev = entry->fts_statp->st_dev; 1068 1069 /* 1070 * Statfs follows symlinks; find wants the link's file system, 1071 * not where it points. 1072 */ 1073 if (entry->fts_info == FTS_SL || 1074 entry->fts_info == FTS_SLNONE) { 1075 if ((p = strrchr(entry->fts_accpath, '/')) != NULL) 1076 ++p; 1077 else 1078 p = entry->fts_accpath; 1079 save[0] = p[0]; 1080 p[0] = '.'; 1081 save[1] = p[1]; 1082 p[1] = '\0'; 1083 1084 } else 1085 p = NULL; 1086 1087 if (statvfs(entry->fts_accpath, &sb)) 1088 err(1, "%s", entry->fts_accpath); 1089 1090 if (p) { 1091 p[0] = save[0]; 1092 p[1] = save[1]; 1093 } 1094 1095 first = 0; 1096 1097 /* 1098 * Further tests may need both of these values, so 1099 * always copy both of them. 1100 */ 1101 val = sb.f_flag; 1102 strlcpy(fstype, sb.f_fstypename, sizeof(fstype)); 1103 } 1104 switch (plan->flags) { 1105 case F_MTFLAG: 1106 return (val & plan->mt_data); 1107 case F_MTTYPE: 1108 return (strncmp(fstype, plan->c_data, sizeof(fstype)) == 0); 1109 default: 1110 abort(); 1111 } 1112 } 1113 1114 PLAN * 1115 c_fstype(char ***argvp, int isok, char *opt) 1116 { 1117 char *arg = **argvp; 1118 PLAN *new; 1119 1120 (*argvp)++; 1121 ftsoptions &= ~FTS_NOSTAT; 1122 1123 new = palloc(N_FSTYPE, f_fstype); 1124 1125 switch (*arg) { 1126 case 'l': 1127 if (!strcmp(arg, "local")) { 1128 new->flags = F_MTFLAG; 1129 new->mt_data = MNT_LOCAL; 1130 return (new); 1131 } 1132 break; 1133 case 'r': 1134 if (!strcmp(arg, "rdonly")) { 1135 new->flags = F_MTFLAG; 1136 new->mt_data = MNT_RDONLY; 1137 return (new); 1138 } 1139 break; 1140 } 1141 1142 new->flags = F_MTTYPE; 1143 new->c_data = arg; 1144 return (new); 1145 } 1146 1147 /* 1148 * -group gname functions -- 1149 * 1150 * True if the file belongs to the group gname. If gname is numeric and 1151 * an equivalent of the getgrnam() function does not return a valid group 1152 * name, gname is taken as a group ID. 1153 */ 1154 int 1155 f_group(PLAN *plan, FTSENT *entry) 1156 { 1157 1158 COMPARE(entry->fts_statp->st_gid, plan->g_data); 1159 } 1160 1161 PLAN * 1162 c_group(char ***argvp, int isok, char *opt) 1163 { 1164 char *gname = **argvp; 1165 PLAN *new; 1166 struct group *g; 1167 gid_t gid; 1168 1169 (*argvp)++; 1170 ftsoptions &= ~FTS_NOSTAT; 1171 1172 new = palloc(N_GROUP, f_group); 1173 g = getgrnam(gname); 1174 if (g == NULL) { 1175 if (atoi(gname) == 0 && gname[0] != '0' && 1176 strcmp(gname, "+0") && strcmp(gname, "-0")) 1177 errx(1, "%s: %s: no such group", opt, gname); 1178 gid = find_parsenum(new, "-group", gname, NULL); 1179 1180 } else { 1181 new->flags = F_EQUAL; 1182 gid = g->gr_gid; 1183 } 1184 1185 new->g_data = gid; 1186 return (new); 1187 } 1188 1189 /* 1190 * -inum n functions -- 1191 * 1192 * True if the file has inode # n. 1193 */ 1194 int 1195 f_inum(PLAN *plan, FTSENT *entry) 1196 { 1197 1198 COMPARE(entry->fts_statp->st_ino, plan->i_data); 1199 } 1200 1201 PLAN * 1202 c_inum(char ***argvp, int isok, char *opt) 1203 { 1204 char *arg = **argvp; 1205 PLAN *new; 1206 1207 (*argvp)++; 1208 ftsoptions &= ~FTS_NOSTAT; 1209 1210 new = palloc(N_INUM, f_inum); 1211 new->i_data = find_parsenum(new, opt, arg, NULL); 1212 return (new); 1213 } 1214 1215 /* 1216 * -links n functions -- 1217 * 1218 * True if the file has n links. 1219 */ 1220 int 1221 f_links(PLAN *plan, FTSENT *entry) 1222 { 1223 1224 COMPARE(entry->fts_statp->st_nlink, plan->l_data); 1225 } 1226 1227 PLAN * 1228 c_links(char ***argvp, int isok, char *opt) 1229 { 1230 char *arg = **argvp; 1231 PLAN *new; 1232 1233 (*argvp)++; 1234 ftsoptions &= ~FTS_NOSTAT; 1235 1236 new = palloc(N_LINKS, f_links); 1237 new->l_data = (nlink_t)find_parsenum(new, opt, arg, NULL); 1238 return (new); 1239 } 1240 1241 /* 1242 * -ls functions -- 1243 * 1244 * Always true - prints the current entry to stdout in "ls" format. 1245 */ 1246 int 1247 f_ls(PLAN *plan, FTSENT *entry) 1248 { 1249 1250 printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp); 1251 return (1); 1252 } 1253 1254 PLAN * 1255 c_ls(char ***argvp, int isok, char *opt) 1256 { 1257 1258 ftsoptions &= ~FTS_NOSTAT; 1259 isoutput = 1; 1260 1261 return (palloc(N_LS, f_ls)); 1262 } 1263 1264 /* 1265 * - maxdepth n functions -- 1266 * 1267 * True if the current search depth is less than or equal to the 1268 * maximum depth specified 1269 */ 1270 int 1271 f_maxdepth(PLAN *plan, FTSENT *entry) 1272 { 1273 extern FTS *tree; 1274 1275 if (entry->fts_level >= plan->max_data) 1276 fts_set(tree, entry, FTS_SKIP); 1277 return (entry->fts_level <= plan->max_data); 1278 } 1279 1280 PLAN * 1281 c_maxdepth(char ***argvp, int isok, char *opt) 1282 { 1283 char *arg = **argvp; 1284 PLAN *new; 1285 1286 (*argvp)++; 1287 new = palloc(N_MAXDEPTH, f_maxdepth); 1288 new->max_data = atoi(arg); 1289 return (new); 1290 } 1291 1292 /* 1293 * - mindepth n functions -- 1294 * 1295 * True if the current search depth is greater than or equal to the 1296 * minimum depth specified 1297 */ 1298 int 1299 f_mindepth(PLAN *plan, FTSENT *entry) 1300 { 1301 return (entry->fts_level >= plan->min_data); 1302 } 1303 1304 PLAN * 1305 c_mindepth(char ***argvp, int isok, char *opt) 1306 { 1307 char *arg = **argvp; 1308 PLAN *new; 1309 1310 (*argvp)++; 1311 new = palloc(N_MINDEPTH, f_mindepth); 1312 new->min_data = atoi(arg); 1313 return (new); 1314 } 1315 1316 /* 1317 * -mmin n functions -- 1318 * 1319 * True if the difference between the file modification time and the 1320 * current time is n 24 hour periods. 1321 */ 1322 int 1323 f_mmin(PLAN *plan, FTSENT *entry) 1324 { 1325 COMPARE((now - entry->fts_statp->st_mtime + SECSPERMIN - 1) / 1326 SECSPERMIN, plan->t_data); 1327 } 1328 1329 PLAN * 1330 c_mmin(char ***argvp, int isok, char *opt) 1331 { 1332 char *arg = **argvp; 1333 PLAN *new; 1334 1335 (*argvp)++; 1336 ftsoptions &= ~FTS_NOSTAT; 1337 1338 new = palloc(N_MMIN, f_mmin); 1339 new->t_data = find_parsenum(new, opt, arg, NULL); 1340 TIME_CORRECT(new, N_MMIN); 1341 return (new); 1342 } 1343 1344 /* 1345 * -mtime n functions -- 1346 * 1347 * True if the difference between the file modification time and the 1348 * current time is n 24 hour periods. 1349 */ 1350 int 1351 f_mtime(PLAN *plan, FTSENT *entry) 1352 { 1353 COMPARE((now - entry->fts_statp->st_mtime + SECSPERDAY - 1) / 1354 SECSPERDAY, plan->t_data); 1355 } 1356 1357 PLAN * 1358 c_mtime(char ***argvp, int isok, char *opt) 1359 { 1360 char *arg = **argvp; 1361 PLAN *new; 1362 1363 (*argvp)++; 1364 ftsoptions &= ~FTS_NOSTAT; 1365 1366 new = palloc(N_MTIME, f_mtime); 1367 new->t_data = find_parsenum(new, opt, arg, NULL); 1368 TIME_CORRECT(new, N_MTIME); 1369 return (new); 1370 } 1371 1372 /* 1373 * -name functions -- 1374 * 1375 * True if the basename of the filename being examined 1376 * matches pattern using Pattern Matching Notation S3.14 1377 */ 1378 int 1379 f_name(PLAN *plan, FTSENT *entry) 1380 { 1381 1382 return (!fnmatch(plan->c_data, entry->fts_name, 0)); 1383 } 1384 1385 PLAN * 1386 c_name(char ***argvp, int isok, char *opt) 1387 { 1388 char *pattern = **argvp; 1389 PLAN *new; 1390 1391 (*argvp)++; 1392 new = palloc(N_NAME, f_name); 1393 new->c_data = pattern; 1394 return (new); 1395 } 1396 1397 /* 1398 * -iname functions -- 1399 * 1400 * Similar to -name, but does case insensitive matching 1401 * 1402 */ 1403 int 1404 f_iname(PLAN *plan, FTSENT *entry) 1405 { 1406 return (!fnmatch(plan->c_data, entry->fts_name, FNM_CASEFOLD)); 1407 } 1408 1409 PLAN * 1410 c_iname(char ***argvp, int isok, char *opt) 1411 { 1412 char *pattern = **argvp; 1413 PLAN *new; 1414 1415 (*argvp)++; 1416 new = palloc(N_INAME, f_iname); 1417 new->c_data = pattern; 1418 return (new); 1419 } 1420 1421 /* 1422 * -newer file functions -- 1423 * 1424 * True if the current file has been modified more recently 1425 * than the modification time of the file named by the pathname 1426 * file. 1427 */ 1428 int 1429 f_newer(PLAN *plan, FTSENT *entry) 1430 { 1431 1432 return timespeccmp(&entry->fts_statp->st_mtim, &plan->ts_data, >); 1433 } 1434 1435 PLAN * 1436 c_newer(char ***argvp, int isok, char *opt) 1437 { 1438 char *filename = **argvp; 1439 PLAN *new; 1440 struct stat sb; 1441 1442 (*argvp)++; 1443 ftsoptions &= ~FTS_NOSTAT; 1444 1445 if (stat(filename, &sb)) 1446 err(1, "%s: %s", opt, filename); 1447 new = palloc(N_NEWER, f_newer); 1448 new->ts_data = sb.st_mtim; 1449 return (new); 1450 } 1451 1452 /* 1453 * -nogroup functions -- 1454 * 1455 * True if file belongs to a user ID for which the equivalent 1456 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL. 1457 */ 1458 int 1459 f_nogroup(PLAN *plan, FTSENT *entry) 1460 { 1461 1462 return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1); 1463 } 1464 1465 PLAN * 1466 c_nogroup(char ***argvp, int isok, char *opt) 1467 { 1468 ftsoptions &= ~FTS_NOSTAT; 1469 1470 return (palloc(N_NOGROUP, f_nogroup)); 1471 } 1472 1473 /* 1474 * -nouser functions -- 1475 * 1476 * True if file belongs to a user ID for which the equivalent 1477 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL. 1478 */ 1479 int 1480 f_nouser(PLAN *plan, FTSENT *entry) 1481 { 1482 1483 return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1); 1484 } 1485 1486 PLAN * 1487 c_nouser(char ***argvp, int isok, char *opt) 1488 { 1489 ftsoptions &= ~FTS_NOSTAT; 1490 1491 return (palloc(N_NOUSER, f_nouser)); 1492 } 1493 1494 /* 1495 * -path functions -- 1496 * 1497 * True if the path of the filename being examined 1498 * matches pattern using Pattern Matching Notation S3.14 1499 */ 1500 int 1501 f_path(PLAN *plan, FTSENT *entry) 1502 { 1503 1504 return (!fnmatch(plan->c_data, entry->fts_path, 0)); 1505 } 1506 1507 PLAN * 1508 c_path(char ***argvp, int isok, char *opt) 1509 { 1510 char *pattern = **argvp; 1511 PLAN *new; 1512 1513 (*argvp)++; 1514 new = palloc(N_NAME, f_path); 1515 new->c_data = pattern; 1516 return (new); 1517 } 1518 1519 /* 1520 * -perm functions -- 1521 * 1522 * The mode argument is used to represent file mode bits. If it starts 1523 * with a leading digit, it's treated as an octal mode, otherwise as a 1524 * symbolic mode. 1525 */ 1526 int 1527 f_perm(PLAN *plan, FTSENT *entry) 1528 { 1529 mode_t mode; 1530 1531 mode = entry->fts_statp->st_mode & 1532 (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO); 1533 if (plan->flags == F_ATLEAST) 1534 return ((plan->m_data | mode) == mode); 1535 else 1536 return (mode == plan->m_data); 1537 /* NOTREACHED */ 1538 } 1539 1540 PLAN * 1541 c_perm(char ***argvp, int isok, char *opt) 1542 { 1543 char *perm = **argvp; 1544 PLAN *new; 1545 mode_t *set; 1546 1547 (*argvp)++; 1548 ftsoptions &= ~FTS_NOSTAT; 1549 1550 new = palloc(N_PERM, f_perm); 1551 1552 if (*perm == '-') { 1553 new->flags = F_ATLEAST; 1554 ++perm; 1555 } 1556 1557 if ((set = setmode(perm)) == NULL) 1558 err(1, "%s: Cannot set file mode `%s'", opt, perm); 1559 1560 new->m_data = getmode(set, 0); 1561 free(set); 1562 return (new); 1563 } 1564 1565 /* 1566 * -print functions -- 1567 * 1568 * Always true, causes the current pathname to be written to 1569 * standard output. 1570 */ 1571 int 1572 f_print(PLAN *plan, FTSENT *entry) 1573 { 1574 1575 (void)printf("%s\n", entry->fts_path); 1576 return (1); 1577 } 1578 1579 int 1580 f_print0(PLAN *plan, FTSENT *entry) 1581 { 1582 1583 (void)fputs(entry->fts_path, stdout); 1584 (void)fputc('\0', stdout); 1585 return (1); 1586 } 1587 1588 int 1589 f_printx(PLAN *plan, FTSENT *entry) 1590 { 1591 char *cp; 1592 1593 for (cp = entry->fts_path; *cp; cp++) { 1594 if (*cp == '\'' || *cp == '\"' || *cp == ' ' || 1595 *cp == '$' || *cp == '`' || 1596 *cp == '\t' || *cp == '\n' || *cp == '\\') 1597 fputc('\\', stdout); 1598 1599 fputc(*cp, stdout); 1600 } 1601 1602 fputc('\n', stdout); 1603 return (1); 1604 } 1605 1606 PLAN * 1607 c_print(char ***argvp, int isok, char *opt) 1608 { 1609 1610 isoutput = 1; 1611 1612 return (palloc(N_PRINT, f_print)); 1613 } 1614 1615 PLAN * 1616 c_print0(char ***argvp, int isok, char *opt) 1617 { 1618 1619 isoutput = 1; 1620 1621 return (palloc(N_PRINT0, f_print0)); 1622 } 1623 1624 PLAN * 1625 c_printx(char ***argvp, int isok, char *opt) 1626 { 1627 1628 isoutput = 1; 1629 1630 return (palloc(N_PRINTX, f_printx)); 1631 } 1632 1633 /* 1634 * -prune functions -- 1635 * 1636 * Prune a portion of the hierarchy. 1637 */ 1638 int 1639 f_prune(PLAN *plan, FTSENT *entry) 1640 { 1641 if (fts_set(tree, entry, FTS_SKIP)) 1642 err(1, "%s", entry->fts_path); 1643 return (1); 1644 } 1645 1646 PLAN * 1647 c_prune(char ***argvp, int isok, char *opt) 1648 { 1649 1650 return (palloc(N_PRUNE, f_prune)); 1651 } 1652 1653 /* 1654 * -regex regexp (and related) functions -- 1655 * 1656 * True if the complete file path matches the regular expression regexp. 1657 * For -regex, regexp is a case-sensitive (basic) regular expression. 1658 * For -iregex, regexp is a case-insensitive (basic) regular expression. 1659 */ 1660 int 1661 f_regex(PLAN *plan, FTSENT *entry) 1662 { 1663 1664 return (regexec(&plan->regexp_data, entry->fts_path, 0, NULL, 0) == 0); 1665 } 1666 1667 static PLAN * 1668 c_regex_common(char ***argvp, int isok, enum ntype type, bool icase) 1669 { 1670 char errbuf[LINE_MAX]; 1671 regex_t reg; 1672 char *regexp = **argvp; 1673 char *lineregexp; 1674 PLAN *new; 1675 int rv; 1676 size_t len; 1677 1678 (*argvp)++; 1679 1680 len = strlen(regexp) + 1 + 6; 1681 lineregexp = malloc(len); /* max needed */ 1682 if (lineregexp == NULL) 1683 err(1, NULL); 1684 snprintf(lineregexp, len, "^%s(%s%s)$", 1685 (regcomp_flags & REG_EXTENDED) ? "" : "\\", regexp, 1686 (regcomp_flags & REG_EXTENDED) ? "" : "\\"); 1687 rv = regcomp(®, lineregexp, REG_NOSUB|regcomp_flags| 1688 (icase ? REG_ICASE : 0)); 1689 free(lineregexp); 1690 if (rv != 0) { 1691 regerror(rv, ®, errbuf, sizeof errbuf); 1692 errx(1, "regexp %s: %s", regexp, errbuf); 1693 } 1694 1695 new = palloc(type, f_regex); 1696 new->regexp_data = reg; 1697 return (new); 1698 } 1699 1700 PLAN * 1701 c_regex(char ***argvp, int isok, char *opt) 1702 { 1703 1704 return (c_regex_common(argvp, isok, N_REGEX, false)); 1705 } 1706 1707 PLAN * 1708 c_iregex(char ***argvp, int isok, char *opt) 1709 { 1710 1711 return (c_regex_common(argvp, isok, N_IREGEX, true)); 1712 } 1713 1714 /* 1715 * -since "timestamp" functions -- 1716 * 1717 * True if the file modification time is greater than the timestamp value 1718 */ 1719 int 1720 f_since(PLAN *plan, FTSENT *entry) 1721 { 1722 COMPARE(entry->fts_statp->st_mtime, plan->t_data); 1723 } 1724 1725 PLAN * 1726 c_since(char ***argvp, int isok, char *opt) 1727 { 1728 char *arg = **argvp; 1729 PLAN *new; 1730 1731 (*argvp)++; 1732 ftsoptions &= ~FTS_NOSTAT; 1733 1734 new = palloc(N_SINCE, f_since); 1735 new->t_data = find_parsedate(new, opt, arg); 1736 new->flags = F_GREATER; 1737 return (new); 1738 } 1739 1740 /* 1741 * -size n[c] functions -- 1742 * 1743 * True if the file size in bytes, divided by an implementation defined 1744 * value and rounded up to the next integer, is n. If n is followed by 1745 * a c, the size is in bytes. 1746 */ 1747 #define FIND_SIZE 512 1748 static int divsize = 1; 1749 1750 int 1751 f_size(PLAN *plan, FTSENT *entry) 1752 { 1753 off_t size; 1754 1755 size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) / 1756 FIND_SIZE : entry->fts_statp->st_size; 1757 COMPARE(size, plan->o_data); 1758 } 1759 1760 PLAN * 1761 c_size(char ***argvp, int isok, char *opt) 1762 { 1763 char *arg = **argvp; 1764 PLAN *new; 1765 char endch; 1766 1767 (*argvp)++; 1768 ftsoptions &= ~FTS_NOSTAT; 1769 1770 new = palloc(N_SIZE, f_size); 1771 endch = 'c'; 1772 new->o_data = find_parsenum(new, opt, arg, &endch); 1773 if (endch == 'c') 1774 divsize = 0; 1775 return (new); 1776 } 1777 1778 /* 1779 * -type c functions -- 1780 * 1781 * True if the type of the file is c, where c is b, c, d, p, f or w 1782 * for block special file, character special file, directory, FIFO, 1783 * regular file or whiteout respectively. 1784 */ 1785 int 1786 f_type(PLAN *plan, FTSENT *entry) 1787 { 1788 1789 return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data); 1790 } 1791 1792 PLAN * 1793 c_type(char ***argvp, int isok, char *opt) 1794 { 1795 char *typestring = **argvp; 1796 PLAN *new; 1797 mode_t mask = (mode_t)0; 1798 1799 (*argvp)++; 1800 ftsoptions &= ~FTS_NOSTAT; 1801 1802 switch (typestring[0]) { 1803 case 'b': 1804 mask = S_IFBLK; 1805 break; 1806 case 'c': 1807 mask = S_IFCHR; 1808 break; 1809 case 'd': 1810 mask = S_IFDIR; 1811 break; 1812 case 'f': 1813 mask = S_IFREG; 1814 break; 1815 case 'l': 1816 mask = S_IFLNK; 1817 break; 1818 case 'p': 1819 mask = S_IFIFO; 1820 break; 1821 case 's': 1822 mask = S_IFSOCK; 1823 break; 1824 #ifdef S_IFWHT 1825 case 'W': 1826 case 'w': 1827 mask = S_IFWHT; 1828 #ifdef FTS_WHITEOUT 1829 ftsoptions |= FTS_WHITEOUT; 1830 #endif 1831 break; 1832 #endif /* S_IFWHT */ 1833 default: 1834 errx(1, "%s: %s: unknown type", opt, typestring); 1835 } 1836 1837 new = palloc(N_TYPE, f_type); 1838 new->m_data = mask; 1839 return (new); 1840 } 1841 1842 /* 1843 * -user uname functions -- 1844 * 1845 * True if the file belongs to the user uname. If uname is numeric and 1846 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not 1847 * return a valid user name, uname is taken as a user ID. 1848 */ 1849 int 1850 f_user(PLAN *plan, FTSENT *entry) 1851 { 1852 1853 COMPARE(entry->fts_statp->st_uid, plan->u_data); 1854 } 1855 1856 PLAN * 1857 c_user(char ***argvp, int isok, char *opt) 1858 { 1859 char *username = **argvp; 1860 PLAN *new; 1861 struct passwd *p; 1862 uid_t uid; 1863 1864 (*argvp)++; 1865 ftsoptions &= ~FTS_NOSTAT; 1866 1867 new = palloc(N_USER, f_user); 1868 p = getpwnam(username); 1869 if (p == NULL) { 1870 if (atoi(username) == 0 && username[0] != '0' && 1871 strcmp(username, "+0") && strcmp(username, "-0")) 1872 errx(1, "%s: %s: no such user", opt, username); 1873 uid = find_parsenum(new, opt, username, NULL); 1874 1875 } else { 1876 new->flags = F_EQUAL; 1877 uid = p->pw_uid; 1878 } 1879 1880 new->u_data = uid; 1881 return (new); 1882 } 1883 1884 /* 1885 * -xdev functions -- 1886 * 1887 * Always true, causes find not to descend past directories that have a 1888 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1]) 1889 */ 1890 PLAN * 1891 c_xdev(char ***argvp, int isok, char *opt) 1892 { 1893 ftsoptions |= FTS_XDEV; 1894 1895 return (palloc(N_XDEV, f_always_true)); 1896 } 1897 1898 /* 1899 * ( expression ) functions -- 1900 * 1901 * True if expression is true. 1902 */ 1903 int 1904 f_expr(PLAN *plan, FTSENT *entry) 1905 { 1906 PLAN *p; 1907 int state; 1908 1909 state = 0; 1910 for (p = plan->p_data[0]; 1911 p && (state = (p->eval)(p, entry)); p = p->next); 1912 return (state); 1913 } 1914 1915 /* 1916 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are 1917 * eliminated during phase 2 of find_formplan() --- the '(' node is converted 1918 * to a N_EXPR node containing the expression and the ')' node is discarded. 1919 */ 1920 PLAN * 1921 c_openparen(char ***argvp, int isok, char *opt) 1922 { 1923 1924 return (palloc(N_OPENPAREN, (int (*)(PLAN *, FTSENT *))-1)); 1925 } 1926 1927 PLAN * 1928 c_closeparen(char ***argvp, int isok, char *opt) 1929 { 1930 1931 return (palloc(N_CLOSEPAREN, (int (*)(PLAN *, FTSENT *))-1)); 1932 } 1933 1934 /* 1935 * ! expression functions -- 1936 * 1937 * Negation of a primary; the unary NOT operator. 1938 */ 1939 int 1940 f_not(PLAN *plan, FTSENT *entry) 1941 { 1942 PLAN *p; 1943 int state; 1944 1945 state = 0; 1946 for (p = plan->p_data[0]; 1947 p && (state = (p->eval)(p, entry)); p = p->next); 1948 return (!state); 1949 } 1950 1951 PLAN * 1952 c_not(char ***argvp, int isok, char *opt) 1953 { 1954 1955 return (palloc(N_NOT, f_not)); 1956 } 1957 1958 /* 1959 * expression -o expression functions -- 1960 * 1961 * Alternation of primaries; the OR operator. The second expression is 1962 * not evaluated if the first expression is true. 1963 */ 1964 int 1965 f_or(PLAN *plan, FTSENT *entry) 1966 { 1967 PLAN *p; 1968 int state; 1969 1970 state = 0; 1971 for (p = plan->p_data[0]; 1972 p && (state = (p->eval)(p, entry)); p = p->next); 1973 1974 if (state) 1975 return (1); 1976 1977 for (p = plan->p_data[1]; 1978 p && (state = (p->eval)(p, entry)); p = p->next); 1979 return (state); 1980 } 1981 1982 PLAN * 1983 c_or(char ***argvp, int isok, char *opt) 1984 { 1985 1986 return (palloc(N_OR, f_or)); 1987 } 1988 1989 PLAN * 1990 c_null(char ***argvp, int isok, char *opt) 1991 { 1992 1993 return (NULL); 1994 } 1995 1996 1997 /* 1998 * plan_cleanup -- 1999 * Check and see if the specified plan has any residual state, 2000 * and if so, clean it up as appropriate. 2001 * 2002 * At the moment, only N_EXEC has state. Two kinds: 1) 2003 * lists of files to feed to subprocesses 2) State on exit 2004 * status of past subprocesses. 2005 */ 2006 /* ARGSUSED1 */ 2007 int 2008 plan_cleanup(PLAN *plan, void *arg) 2009 { 2010 if (plan->type==N_EXEC && plan->ep_narg) 2011 run_f_exec(plan); 2012 2013 return plan->ep_rval; /* Passed save exit-status up chain */ 2014 } 2015 2016 static PLAN * 2017 palloc(enum ntype t, int (*f)(PLAN *, FTSENT *)) 2018 { 2019 PLAN *new; 2020 2021 if ((new = malloc(sizeof(PLAN))) == NULL) 2022 err(1, NULL); 2023 memset(new, 0, sizeof(PLAN)); 2024 new->type = t; 2025 new->eval = f; 2026 return (new); 2027 } 2028