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