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