function.c revision 1.77 1 /* $NetBSD: function.c,v 1.77 2018/09/04 15:16:15 kre 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.77 2018/09/04 15:16:15 kre 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 = strtoq(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 if (fchdir(dotfd)) {
645 warn("chdir");
646 _exit(1);
647 }
648 execvp(plan->e_argv[0], plan->e_argv);
649 warn("%s", plan->e_argv[0]);
650 _exit(1);
651 }
652 pid = waitpid(pid, &status, 0);
653 return (pid != -1 && WIFEXITED(status)
654 && !WEXITSTATUS(status));
655 }
656 }
657
658 static void
659 run_f_exec(PLAN *plan)
660 {
661 pid_t pid;
662 int rval, status;
663
664 /* Ensure arg list is null terminated. */
665 plan->ep_bxp[plan->ep_narg] = NULL;
666
667 /* Don't mix output of command with find output. */
668 fflush(stdout);
669 fflush(stderr);
670
671 switch (pid = vfork()) {
672 case -1:
673 err(1, "vfork");
674 /* NOTREACHED */
675 case 0:
676 if (fchdir(dotfd)) {
677 warn("chdir");
678 _exit(1);
679 }
680 execvp(plan->e_argv[0], plan->e_argv);
681 warn("%s", plan->e_argv[0]);
682 _exit(1);
683 }
684
685 /* Clear out the argument list. */
686 plan->ep_narg = 0;
687 plan->ep_bxp[plan->ep_narg] = NULL;
688 /* As well as the argument buffer. */
689 plan->ep_p = plan->ep_bbp;
690 *plan->ep_p = '\0';
691
692 pid = waitpid(pid, &status, 0);
693 if (WIFEXITED(status))
694 rval = WEXITSTATUS(status);
695 else
696 rval = -1;
697
698 /*
699 * If we have a non-zero exit status, preserve it so find(1) can
700 * later exit with it.
701 */
702 if (rval)
703 plan->ep_rval = rval;
704 }
705
706 /*
707 * c_exec --
708 * build three parallel arrays, one with pointers to the strings passed
709 * on the command line, one with (possibly duplicated) pointers to the
710 * argv array, and one with integer values that are lengths of the
711 * strings, but also flags meaning that the string has to be massaged.
712 *
713 * If -exec ... {} +, use only the first array, but make it large
714 * enough to hold 5000 args (cf. src/usr.bin/xargs/xargs.c for a
715 * discussion), and then allocate ARG_MAX - 4K of space for args.
716 */
717 PLAN *
718 c_exec(char ***argvp, int isok, char *opt)
719 {
720 PLAN *new; /* node returned */
721 size_t cnt;
722 int brace, lastbrace;
723 char **argv, **ap, *p;
724
725 isoutput = 1;
726
727 new = palloc(N_EXEC, f_exec);
728 if (isok)
729 new->flags |= F_NEEDOK;
730
731 /*
732 * Terminate if we encounter an arg exactly equal to ";", or an
733 * arg exactly equal to "+" following an arg exactly equal to
734 * "{}".
735 */
736 for (ap = argv = *argvp, brace = 0;; ++ap) {
737 if (!*ap)
738 errx(1, "%s: no terminating \";\" or \"+\"", opt);
739 lastbrace = brace;
740 brace = 0;
741 if (strcmp(*ap, "{}") == 0)
742 brace = 1;
743 if (strcmp(*ap, ";") == 0)
744 break;
745 if (strcmp(*ap, "+") == 0 && lastbrace) {
746 new->flags |= F_PLUSSET;
747 break;
748 }
749 }
750
751 /*
752 * POSIX says -ok ... {} + "need not be supported," and it does
753 * not make much sense anyway.
754 */
755 if (new->flags & F_NEEDOK && new->flags & F_PLUSSET)
756 errx(1, "%s: terminating \"+\" not permitted.", opt);
757
758 if (new->flags & F_PLUSSET) {
759 size_t c, bufsize;
760
761 cnt = ap - *argvp - 1; /* units are words */
762 new->ep_maxargs = ARG_MAX / (sizeof (char *) + 16);
763 if (new->ep_maxargs > 5000)
764 new->ep_maxargs = 5000;
765 new->e_argv = emalloc((cnt + new->ep_maxargs)
766 * sizeof(*new->e_argv));
767
768 /* We start stuffing arguments after the user's last one. */
769 new->ep_bxp = &new->e_argv[cnt];
770 new->ep_narg = 0;
771
772 /*
773 * Count up the space of the user's arguments, and
774 * subtract that from what we allocate.
775 */
776 #define MAXARG (ARG_MAX - 4 * 1024)
777 for (argv = *argvp, c = 0, cnt = 0;
778 argv < ap;
779 ++argv, ++cnt) {
780 c += strlen(*argv) + 1;
781 if (c >= MAXARG)
782 errx(1, "Arguments too long");
783 new->e_argv[cnt] = *argv;
784 }
785 if (c + new->ep_maxargs * sizeof (char *) >= MAXARG)
786 errx(1, "Arguments too long");
787 bufsize = MAXARG - c - new->ep_maxargs * sizeof (char *);
788
789 /*
790 * Allocate, and then initialize current, base, and
791 * end pointers.
792 */
793 new->ep_p = new->ep_bbp = emalloc(bufsize + 1);
794 new->ep_ebp = new->ep_bbp + bufsize - 1;
795 new->ep_rval = 0;
796 } else { /* !F_PLUSSET */
797 cnt = ap - *argvp + 1;
798 new->e_argv = emalloc(cnt * sizeof(*new->e_argv));
799 new->e_orig = emalloc(cnt * sizeof(*new->e_orig));
800 new->e_len = emalloc(cnt * sizeof(*new->e_len));
801
802 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
803 new->e_orig[cnt] = *argv;
804 for (p = *argv; *p; ++p)
805 if (p[0] == '{' && p[1] == '}') {
806 new->e_argv[cnt] =
807 emalloc(MAXPATHLEN);
808 new->e_len[cnt] = MAXPATHLEN;
809 break;
810 }
811 if (!*p) {
812 new->e_argv[cnt] = *argv;
813 new->e_len[cnt] = 0;
814 }
815 }
816 new->e_orig[cnt] = NULL;
817 }
818
819 new->e_argv[cnt] = NULL;
820 *argvp = argv + 1;
821 return (new);
822 }
823
824 /*
825 * -execdir utility [arg ... ] ; functions --
826 *
827 * True if the executed utility returns a zero value as exit status.
828 * The end of the primary expression is delimited by a semicolon. If
829 * "{}" occurs anywhere, it gets replaced by the unqualified pathname.
830 * The current directory for the execution of utility is the same as
831 * the directory where the file lives.
832 */
833 int
834 f_execdir(PLAN *plan, FTSENT *entry)
835 {
836 size_t cnt;
837 pid_t pid;
838 int status;
839 char *file;
840
841 /* XXX - if file/dir ends in '/' this will not work -- can it? */
842 if ((file = strrchr(entry->fts_path, '/')))
843 file++;
844 else
845 file = entry->fts_path;
846
847 for (cnt = 0; plan->e_argv[cnt]; ++cnt)
848 if (plan->e_len[cnt])
849 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
850 file, &plan->e_len[cnt]);
851
852 /* don't mix output of command with find output */
853 fflush(stdout);
854 fflush(stderr);
855
856 switch (pid = vfork()) {
857 case -1:
858 err(1, "fork");
859 /* NOTREACHED */
860 case 0:
861 execvp(plan->e_argv[0], plan->e_argv);
862 warn("%s", plan->e_argv[0]);
863 _exit(1);
864 }
865 pid = waitpid(pid, &status, 0);
866 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
867 }
868
869 /*
870 * c_execdir --
871 * build three parallel arrays, one with pointers to the strings passed
872 * on the command line, one with (possibly duplicated) pointers to the
873 * argv array, and one with integer values that are lengths of the
874 * strings, but also flags meaning that the string has to be massaged.
875 */
876 PLAN *
877 c_execdir(char ***argvp, int isok, char *opt)
878 {
879 PLAN *new; /* node returned */
880 size_t cnt;
881 char **argv, **ap, *p;
882
883 ftsoptions &= ~FTS_NOSTAT;
884 isoutput = 1;
885
886 new = palloc(N_EXECDIR, f_execdir);
887
888 for (ap = argv = *argvp;; ++ap) {
889 if (!*ap)
890 errx(1, "%s: no terminating \";\"", opt);
891 if (**ap == ';')
892 break;
893 }
894
895 cnt = ap - *argvp + 1;
896 new->e_argv = emalloc(cnt * sizeof(*new->e_argv));
897 new->e_orig = emalloc(cnt * sizeof(*new->e_orig));
898 new->e_len = emalloc(cnt * sizeof(*new->e_len));
899
900 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
901 new->e_orig[cnt] = *argv;
902 for (p = *argv; *p; ++p)
903 if (p[0] == '{' && p[1] == '}') {
904 new->e_argv[cnt] = emalloc(MAXPATHLEN);
905 new->e_len[cnt] = MAXPATHLEN;
906 break;
907 }
908 if (!*p) {
909 new->e_argv[cnt] = *argv;
910 new->e_len[cnt] = 0;
911 }
912 }
913 new->e_argv[cnt] = new->e_orig[cnt] = NULL;
914
915 *argvp = argv + 1;
916 return (new);
917 }
918
919 PLAN *
920 c_exit(char ***argvp, int isok, char *opt)
921 {
922 char *arg = **argvp;
923 PLAN *new;
924
925 /* not technically true, but otherwise '-print' is implied */
926 isoutput = 1;
927
928 new = palloc(N_EXIT, f_always_true);
929
930 if (arg) {
931 (*argvp)++;
932 new->exit_val = find_parsenum(new, opt, arg, NULL);
933 } else
934 new->exit_val = 0;
935
936 return (new);
937 }
938
939
940 /*
941 * -false function
942 */
943 int
944 f_false(PLAN *plan, FTSENT *entry)
945 {
946
947 return (0);
948 }
949
950 PLAN *
951 c_false(char ***argvp, int isok, char *opt)
952 {
953 return (palloc(N_FALSE, f_false));
954 }
955
956
957 /*
958 * -flags [-]flags functions --
959 */
960 int
961 f_flags(PLAN *plan, FTSENT *entry)
962 {
963 u_int32_t flags;
964
965 flags = entry->fts_statp->st_flags;
966 if (plan->flags == F_ATLEAST)
967 return ((plan->f_data | flags) == flags);
968 else
969 return (flags == plan->f_data);
970 /* NOTREACHED */
971 }
972
973 PLAN *
974 c_flags(char ***argvp, int isok, char *opt)
975 {
976 char *flags = **argvp;
977 PLAN *new;
978 u_long flagset;
979
980 (*argvp)++;
981 ftsoptions &= ~FTS_NOSTAT;
982
983 new = palloc(N_FLAGS, f_flags);
984
985 if (*flags == '-') {
986 new->flags = F_ATLEAST;
987 ++flags;
988 }
989
990 flagset = 0;
991 if ((strcmp(flags, "none") != 0) &&
992 (string_to_flags(&flags, &flagset, NULL) != 0))
993 errx(1, "%s: %s: illegal flags string", opt, flags);
994 new->f_data = flagset;
995 return (new);
996 }
997
998 /*
999 * -follow functions --
1000 *
1001 * Always true, causes symbolic links to be followed on a global
1002 * basis.
1003 */
1004 PLAN *
1005 c_follow(char ***argvp, int isok, char *opt)
1006 {
1007 ftsoptions &= ~FTS_PHYSICAL;
1008 ftsoptions |= FTS_LOGICAL;
1009
1010 return (palloc(N_FOLLOW, f_always_true));
1011 }
1012
1013 /* -fprint functions --
1014 *
1015 * Causes the current pathame to be written to the defined output file.
1016 */
1017 int
1018 f_fprint(PLAN *plan, FTSENT *entry)
1019 {
1020
1021 if (-1 == fprintf(plan->fprint_file, "%s\n", entry->fts_path))
1022 warn("fprintf");
1023
1024 return(1);
1025
1026 /* no descriptors are closed; they will be closed by
1027 operating system when this find command exits. */
1028 }
1029
1030 PLAN *
1031 c_fprint(char ***argvp, int isok, char *opt)
1032 {
1033 PLAN *new;
1034
1035 isoutput = 1; /* do not assume -print */
1036
1037 new = palloc(N_FPRINT, f_fprint);
1038
1039 if (NULL == (new->fprint_file = fopen(**argvp, "w")))
1040 err(1, "%s: %s: cannot create file", opt, **argvp);
1041
1042 (*argvp)++;
1043 return (new);
1044 }
1045
1046 /*
1047 * -fstype functions --
1048 *
1049 * True if the file is of a certain type.
1050 */
1051 int
1052 f_fstype(PLAN *plan, FTSENT *entry)
1053 {
1054 static dev_t curdev; /* need a guaranteed illegal dev value */
1055 static int first = 1;
1056 struct statvfs sb;
1057 static short val;
1058 static char fstype[sizeof(sb.f_fstypename)];
1059 char *p, save[2];
1060
1061 memset(&save, 0, sizeof save); /* XXX gcc */
1062
1063 /* Only check when we cross mount point. */
1064 if (first || curdev != entry->fts_statp->st_dev) {
1065 curdev = entry->fts_statp->st_dev;
1066
1067 /*
1068 * Statfs follows symlinks; find wants the link's file system,
1069 * not where it points.
1070 */
1071 if (entry->fts_info == FTS_SL ||
1072 entry->fts_info == FTS_SLNONE) {
1073 if ((p = strrchr(entry->fts_accpath, '/')) != NULL)
1074 ++p;
1075 else
1076 p = entry->fts_accpath;
1077 save[0] = p[0];
1078 p[0] = '.';
1079 save[1] = p[1];
1080 p[1] = '\0';
1081
1082 } else
1083 p = NULL;
1084
1085 if (statvfs(entry->fts_accpath, &sb))
1086 err(1, "%s", entry->fts_accpath);
1087
1088 if (p) {
1089 p[0] = save[0];
1090 p[1] = save[1];
1091 }
1092
1093 first = 0;
1094
1095 /*
1096 * Further tests may need both of these values, so
1097 * always copy both of them.
1098 */
1099 val = sb.f_flag;
1100 strlcpy(fstype, sb.f_fstypename, sizeof(fstype));
1101 }
1102 switch (plan->flags) {
1103 case F_MTFLAG:
1104 return (val & plan->mt_data);
1105 case F_MTTYPE:
1106 return (strncmp(fstype, plan->c_data, sizeof(fstype)) == 0);
1107 default:
1108 abort();
1109 }
1110 }
1111
1112 PLAN *
1113 c_fstype(char ***argvp, int isok, char *opt)
1114 {
1115 char *arg = **argvp;
1116 PLAN *new;
1117
1118 (*argvp)++;
1119 ftsoptions &= ~FTS_NOSTAT;
1120
1121 new = palloc(N_FSTYPE, f_fstype);
1122
1123 switch (*arg) {
1124 case 'l':
1125 if (!strcmp(arg, "local")) {
1126 new->flags = F_MTFLAG;
1127 new->mt_data = MNT_LOCAL;
1128 return (new);
1129 }
1130 break;
1131 case 'r':
1132 if (!strcmp(arg, "rdonly")) {
1133 new->flags = F_MTFLAG;
1134 new->mt_data = MNT_RDONLY;
1135 return (new);
1136 }
1137 break;
1138 }
1139
1140 new->flags = F_MTTYPE;
1141 new->c_data = arg;
1142 return (new);
1143 }
1144
1145 /*
1146 * -group gname functions --
1147 *
1148 * True if the file belongs to the group gname. If gname is numeric and
1149 * an equivalent of the getgrnam() function does not return a valid group
1150 * name, gname is taken as a group ID.
1151 */
1152 int
1153 f_group(PLAN *plan, FTSENT *entry)
1154 {
1155
1156 COMPARE(entry->fts_statp->st_gid, plan->g_data);
1157 }
1158
1159 PLAN *
1160 c_group(char ***argvp, int isok, char *opt)
1161 {
1162 char *gname = **argvp;
1163 PLAN *new;
1164 struct group *g;
1165 gid_t gid;
1166
1167 (*argvp)++;
1168 ftsoptions &= ~FTS_NOSTAT;
1169
1170 new = palloc(N_GROUP, f_group);
1171 g = getgrnam(gname);
1172 if (g == NULL) {
1173 if (atoi(gname) == 0 && gname[0] != '0' &&
1174 strcmp(gname, "+0") && strcmp(gname, "-0"))
1175 errx(1, "%s: %s: no such group", opt, gname);
1176 gid = find_parsenum(new, "-group", gname, NULL);
1177
1178 } else {
1179 new->flags = F_EQUAL;
1180 gid = g->gr_gid;
1181 }
1182
1183 new->g_data = gid;
1184 return (new);
1185 }
1186
1187 /*
1188 * -inum n functions --
1189 *
1190 * True if the file has inode # n.
1191 */
1192 int
1193 f_inum(PLAN *plan, FTSENT *entry)
1194 {
1195
1196 COMPARE(entry->fts_statp->st_ino, plan->i_data);
1197 }
1198
1199 PLAN *
1200 c_inum(char ***argvp, int isok, char *opt)
1201 {
1202 char *arg = **argvp;
1203 PLAN *new;
1204
1205 (*argvp)++;
1206 ftsoptions &= ~FTS_NOSTAT;
1207
1208 new = palloc(N_INUM, f_inum);
1209 new->i_data = find_parsenum(new, opt, arg, NULL);
1210 return (new);
1211 }
1212
1213 /*
1214 * -links n functions --
1215 *
1216 * True if the file has n links.
1217 */
1218 int
1219 f_links(PLAN *plan, FTSENT *entry)
1220 {
1221
1222 COMPARE(entry->fts_statp->st_nlink, plan->l_data);
1223 }
1224
1225 PLAN *
1226 c_links(char ***argvp, int isok, char *opt)
1227 {
1228 char *arg = **argvp;
1229 PLAN *new;
1230
1231 (*argvp)++;
1232 ftsoptions &= ~FTS_NOSTAT;
1233
1234 new = palloc(N_LINKS, f_links);
1235 new->l_data = (nlink_t)find_parsenum(new, opt, arg, NULL);
1236 return (new);
1237 }
1238
1239 /*
1240 * -ls functions --
1241 *
1242 * Always true - prints the current entry to stdout in "ls" format.
1243 */
1244 int
1245 f_ls(PLAN *plan, FTSENT *entry)
1246 {
1247
1248 printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
1249 return (1);
1250 }
1251
1252 PLAN *
1253 c_ls(char ***argvp, int isok, char *opt)
1254 {
1255
1256 ftsoptions &= ~FTS_NOSTAT;
1257 isoutput = 1;
1258
1259 return (palloc(N_LS, f_ls));
1260 }
1261
1262 /*
1263 * - maxdepth n functions --
1264 *
1265 * True if the current search depth is less than or equal to the
1266 * maximum depth specified
1267 */
1268 int
1269 f_maxdepth(PLAN *plan, FTSENT *entry)
1270 {
1271 extern FTS *tree;
1272
1273 if (entry->fts_level >= plan->max_data)
1274 fts_set(tree, entry, FTS_SKIP);
1275 return (entry->fts_level <= plan->max_data);
1276 }
1277
1278 PLAN *
1279 c_maxdepth(char ***argvp, int isok, char *opt)
1280 {
1281 char *arg = **argvp;
1282 PLAN *new;
1283
1284 (*argvp)++;
1285 new = palloc(N_MAXDEPTH, f_maxdepth);
1286 new->max_data = atoi(arg);
1287 return (new);
1288 }
1289
1290 /*
1291 * - mindepth n functions --
1292 *
1293 * True if the current search depth is greater than or equal to the
1294 * minimum depth specified
1295 */
1296 int
1297 f_mindepth(PLAN *plan, FTSENT *entry)
1298 {
1299 return (entry->fts_level >= plan->min_data);
1300 }
1301
1302 PLAN *
1303 c_mindepth(char ***argvp, int isok, char *opt)
1304 {
1305 char *arg = **argvp;
1306 PLAN *new;
1307
1308 (*argvp)++;
1309 new = palloc(N_MINDEPTH, f_mindepth);
1310 new->min_data = atoi(arg);
1311 return (new);
1312 }
1313
1314 /*
1315 * -mmin n functions --
1316 *
1317 * True if the difference between the file modification time and the
1318 * current time is n 24 hour periods.
1319 */
1320 int
1321 f_mmin(PLAN *plan, FTSENT *entry)
1322 {
1323 COMPARE((now - entry->fts_statp->st_mtime + SECSPERMIN - 1) /
1324 SECSPERMIN, plan->t_data);
1325 }
1326
1327 PLAN *
1328 c_mmin(char ***argvp, int isok, char *opt)
1329 {
1330 char *arg = **argvp;
1331 PLAN *new;
1332
1333 (*argvp)++;
1334 ftsoptions &= ~FTS_NOSTAT;
1335
1336 new = palloc(N_MMIN, f_mmin);
1337 new->t_data = find_parsenum(new, opt, arg, NULL);
1338 TIME_CORRECT(new, N_MMIN);
1339 return (new);
1340 }
1341
1342 /*
1343 * -mtime n functions --
1344 *
1345 * True if the difference between the file modification time and the
1346 * current time is n 24 hour periods.
1347 */
1348 int
1349 f_mtime(PLAN *plan, FTSENT *entry)
1350 {
1351 COMPARE((now - entry->fts_statp->st_mtime + SECSPERDAY - 1) /
1352 SECSPERDAY, plan->t_data);
1353 }
1354
1355 PLAN *
1356 c_mtime(char ***argvp, int isok, char *opt)
1357 {
1358 char *arg = **argvp;
1359 PLAN *new;
1360
1361 (*argvp)++;
1362 ftsoptions &= ~FTS_NOSTAT;
1363
1364 new = palloc(N_MTIME, f_mtime);
1365 new->t_data = find_parsenum(new, opt, arg, NULL);
1366 TIME_CORRECT(new, N_MTIME);
1367 return (new);
1368 }
1369
1370 /*
1371 * -name functions --
1372 *
1373 * True if the basename of the filename being examined
1374 * matches pattern using Pattern Matching Notation S3.14
1375 */
1376 int
1377 f_name(PLAN *plan, FTSENT *entry)
1378 {
1379
1380 return (!fnmatch(plan->c_data, entry->fts_name, 0));
1381 }
1382
1383 PLAN *
1384 c_name(char ***argvp, int isok, char *opt)
1385 {
1386 char *pattern = **argvp;
1387 PLAN *new;
1388
1389 (*argvp)++;
1390 new = palloc(N_NAME, f_name);
1391 new->c_data = pattern;
1392 return (new);
1393 }
1394
1395 /*
1396 * -iname functions --
1397 *
1398 * Similar to -name, but does case insensitive matching
1399 *
1400 */
1401 int
1402 f_iname(PLAN *plan, FTSENT *entry)
1403 {
1404 return (!fnmatch(plan->c_data, entry->fts_name, FNM_CASEFOLD));
1405 }
1406
1407 PLAN *
1408 c_iname(char ***argvp, int isok, char *opt)
1409 {
1410 char *pattern = **argvp;
1411 PLAN *new;
1412
1413 (*argvp)++;
1414 new = palloc(N_INAME, f_iname);
1415 new->c_data = pattern;
1416 return (new);
1417 }
1418
1419 /*
1420 * -newer file functions --
1421 *
1422 * True if the current file has been modified more recently
1423 * than the modification time of the file named by the pathname
1424 * file.
1425 */
1426 int
1427 f_newer(PLAN *plan, FTSENT *entry)
1428 {
1429
1430 return timespeccmp(&entry->fts_statp->st_mtim, &plan->ts_data, >);
1431 }
1432
1433 PLAN *
1434 c_newer(char ***argvp, int isok, char *opt)
1435 {
1436 char *filename = **argvp;
1437 PLAN *new;
1438 struct stat sb;
1439
1440 (*argvp)++;
1441 ftsoptions &= ~FTS_NOSTAT;
1442
1443 if (stat(filename, &sb))
1444 err(1, "%s: %s", opt, filename);
1445 new = palloc(N_NEWER, f_newer);
1446 new->ts_data = sb.st_mtim;
1447 return (new);
1448 }
1449
1450 /*
1451 * -nogroup functions --
1452 *
1453 * True if file belongs to a user ID for which the equivalent
1454 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
1455 */
1456 int
1457 f_nogroup(PLAN *plan, FTSENT *entry)
1458 {
1459
1460 return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1);
1461 }
1462
1463 PLAN *
1464 c_nogroup(char ***argvp, int isok, char *opt)
1465 {
1466 ftsoptions &= ~FTS_NOSTAT;
1467
1468 return (palloc(N_NOGROUP, f_nogroup));
1469 }
1470
1471 /*
1472 * -nouser functions --
1473 *
1474 * True if file belongs to a user ID for which the equivalent
1475 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
1476 */
1477 int
1478 f_nouser(PLAN *plan, FTSENT *entry)
1479 {
1480
1481 return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1);
1482 }
1483
1484 PLAN *
1485 c_nouser(char ***argvp, int isok, char *opt)
1486 {
1487 ftsoptions &= ~FTS_NOSTAT;
1488
1489 return (palloc(N_NOUSER, f_nouser));
1490 }
1491
1492 /*
1493 * -path functions --
1494 *
1495 * True if the path of the filename being examined
1496 * matches pattern using Pattern Matching Notation S3.14
1497 */
1498 int
1499 f_path(PLAN *plan, FTSENT *entry)
1500 {
1501
1502 return (!fnmatch(plan->c_data, entry->fts_path, 0));
1503 }
1504
1505 PLAN *
1506 c_path(char ***argvp, int isok, char *opt)
1507 {
1508 char *pattern = **argvp;
1509 PLAN *new;
1510
1511 (*argvp)++;
1512 new = palloc(N_NAME, f_path);
1513 new->c_data = pattern;
1514 return (new);
1515 }
1516
1517 /*
1518 * -perm functions --
1519 *
1520 * The mode argument is used to represent file mode bits. If it starts
1521 * with a leading digit, it's treated as an octal mode, otherwise as a
1522 * symbolic mode.
1523 */
1524 int
1525 f_perm(PLAN *plan, FTSENT *entry)
1526 {
1527 mode_t mode;
1528
1529 mode = entry->fts_statp->st_mode &
1530 (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
1531 if (plan->flags == F_ATLEAST)
1532 return ((plan->m_data | mode) == mode);
1533 else
1534 return (mode == plan->m_data);
1535 /* NOTREACHED */
1536 }
1537
1538 PLAN *
1539 c_perm(char ***argvp, int isok, char *opt)
1540 {
1541 char *perm = **argvp;
1542 PLAN *new;
1543 mode_t *set;
1544
1545 (*argvp)++;
1546 ftsoptions &= ~FTS_NOSTAT;
1547
1548 new = palloc(N_PERM, f_perm);
1549
1550 if (*perm == '-') {
1551 new->flags = F_ATLEAST;
1552 ++perm;
1553 }
1554
1555 if ((set = setmode(perm)) == NULL)
1556 err(1, "%s: Cannot set file mode `%s'", opt, perm);
1557
1558 new->m_data = getmode(set, 0);
1559 free(set);
1560 return (new);
1561 }
1562
1563 /*
1564 * -print functions --
1565 *
1566 * Always true, causes the current pathame to be written to
1567 * standard output.
1568 */
1569 int
1570 f_print(PLAN *plan, FTSENT *entry)
1571 {
1572
1573 (void)printf("%s\n", entry->fts_path);
1574 return (1);
1575 }
1576
1577 int
1578 f_print0(PLAN *plan, FTSENT *entry)
1579 {
1580
1581 (void)fputs(entry->fts_path, stdout);
1582 (void)fputc('\0', stdout);
1583 return (1);
1584 }
1585
1586 int
1587 f_printx(PLAN *plan, FTSENT *entry)
1588 {
1589 char *cp;
1590
1591 for (cp = entry->fts_path; *cp; cp++) {
1592 if (*cp == '\'' || *cp == '\"' || *cp == ' ' ||
1593 *cp == '$' || *cp == '`' ||
1594 *cp == '\t' || *cp == '\n' || *cp == '\\')
1595 fputc('\\', stdout);
1596
1597 fputc(*cp, stdout);
1598 }
1599
1600 fputc('\n', stdout);
1601 return (1);
1602 }
1603
1604 PLAN *
1605 c_print(char ***argvp, int isok, char *opt)
1606 {
1607
1608 isoutput = 1;
1609
1610 return (palloc(N_PRINT, f_print));
1611 }
1612
1613 PLAN *
1614 c_print0(char ***argvp, int isok, char *opt)
1615 {
1616
1617 isoutput = 1;
1618
1619 return (palloc(N_PRINT0, f_print0));
1620 }
1621
1622 PLAN *
1623 c_printx(char ***argvp, int isok, char *opt)
1624 {
1625
1626 isoutput = 1;
1627
1628 return (palloc(N_PRINTX, f_printx));
1629 }
1630
1631 /*
1632 * -prune functions --
1633 *
1634 * Prune a portion of the hierarchy.
1635 */
1636 int
1637 f_prune(PLAN *plan, FTSENT *entry)
1638 {
1639 if (fts_set(tree, entry, FTS_SKIP))
1640 err(1, "%s", entry->fts_path);
1641 return (1);
1642 }
1643
1644 PLAN *
1645 c_prune(char ***argvp, int isok, char *opt)
1646 {
1647
1648 return (palloc(N_PRUNE, f_prune));
1649 }
1650
1651 /*
1652 * -regex regexp (and related) functions --
1653 *
1654 * True if the complete file path matches the regular expression regexp.
1655 * For -regex, regexp is a case-sensitive (basic) regular expression.
1656 * For -iregex, regexp is a case-insensitive (basic) regular expression.
1657 */
1658 int
1659 f_regex(PLAN *plan, FTSENT *entry)
1660 {
1661
1662 return (regexec(&plan->regexp_data, entry->fts_path, 0, NULL, 0) == 0);
1663 }
1664
1665 static PLAN *
1666 c_regex_common(char ***argvp, int isok, enum ntype type, bool icase)
1667 {
1668 char errbuf[LINE_MAX];
1669 regex_t reg;
1670 char *regexp = **argvp;
1671 char *lineregexp;
1672 PLAN *new;
1673 int rv;
1674 size_t len;
1675
1676 (*argvp)++;
1677
1678 len = strlen(regexp) + 1 + 6;
1679 lineregexp = malloc(len); /* max needed */
1680 if (lineregexp == NULL)
1681 err(1, NULL);
1682 snprintf(lineregexp, len, "^%s(%s%s)$",
1683 (regcomp_flags & REG_EXTENDED) ? "" : "\\", regexp,
1684 (regcomp_flags & REG_EXTENDED) ? "" : "\\");
1685 rv = regcomp(®, lineregexp, REG_NOSUB|regcomp_flags|
1686 (icase ? REG_ICASE : 0));
1687 free(lineregexp);
1688 if (rv != 0) {
1689 regerror(rv, ®, errbuf, sizeof errbuf);
1690 errx(1, "regexp %s: %s", regexp, errbuf);
1691 }
1692
1693 new = palloc(type, f_regex);
1694 new->regexp_data = reg;
1695 return (new);
1696 }
1697
1698 PLAN *
1699 c_regex(char ***argvp, int isok, char *opt)
1700 {
1701
1702 return (c_regex_common(argvp, isok, N_REGEX, false));
1703 }
1704
1705 PLAN *
1706 c_iregex(char ***argvp, int isok, char *opt)
1707 {
1708
1709 return (c_regex_common(argvp, isok, N_IREGEX, true));
1710 }
1711
1712 /*
1713 * -since "timestamp" functions --
1714 *
1715 * True if the file modification time is greater than the timestamp value
1716 */
1717 int
1718 f_since(PLAN *plan, FTSENT *entry)
1719 {
1720 COMPARE(entry->fts_statp->st_mtime, plan->t_data);
1721 }
1722
1723 PLAN *
1724 c_since(char ***argvp, int isok, char *opt)
1725 {
1726 char *arg = **argvp;
1727 PLAN *new;
1728
1729 (*argvp)++;
1730 ftsoptions &= ~FTS_NOSTAT;
1731
1732 new = palloc(N_SINCE, f_since);
1733 new->t_data = find_parsedate(new, opt, arg);
1734 new->flags = F_GREATER;
1735 return (new);
1736 }
1737
1738 /*
1739 * -size n[c] functions --
1740 *
1741 * True if the file size in bytes, divided by an implementation defined
1742 * value and rounded up to the next integer, is n. If n is followed by
1743 * a c, the size is in bytes.
1744 */
1745 #define FIND_SIZE 512
1746 static int divsize = 1;
1747
1748 int
1749 f_size(PLAN *plan, FTSENT *entry)
1750 {
1751 off_t size;
1752
1753 size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
1754 FIND_SIZE : entry->fts_statp->st_size;
1755 COMPARE(size, plan->o_data);
1756 }
1757
1758 PLAN *
1759 c_size(char ***argvp, int isok, char *opt)
1760 {
1761 char *arg = **argvp;
1762 PLAN *new;
1763 char endch;
1764
1765 (*argvp)++;
1766 ftsoptions &= ~FTS_NOSTAT;
1767
1768 new = palloc(N_SIZE, f_size);
1769 endch = 'c';
1770 new->o_data = find_parsenum(new, opt, arg, &endch);
1771 if (endch == 'c')
1772 divsize = 0;
1773 return (new);
1774 }
1775
1776 /*
1777 * -type c functions --
1778 *
1779 * True if the type of the file is c, where c is b, c, d, p, f or w
1780 * for block special file, character special file, directory, FIFO,
1781 * regular file or whiteout respectively.
1782 */
1783 int
1784 f_type(PLAN *plan, FTSENT *entry)
1785 {
1786
1787 return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data);
1788 }
1789
1790 PLAN *
1791 c_type(char ***argvp, int isok, char *opt)
1792 {
1793 char *typestring = **argvp;
1794 PLAN *new;
1795 mode_t mask = (mode_t)0;
1796
1797 (*argvp)++;
1798 ftsoptions &= ~FTS_NOSTAT;
1799
1800 switch (typestring[0]) {
1801 case 'b':
1802 mask = S_IFBLK;
1803 break;
1804 case 'c':
1805 mask = S_IFCHR;
1806 break;
1807 case 'd':
1808 mask = S_IFDIR;
1809 break;
1810 case 'f':
1811 mask = S_IFREG;
1812 break;
1813 case 'l':
1814 mask = S_IFLNK;
1815 break;
1816 case 'p':
1817 mask = S_IFIFO;
1818 break;
1819 case 's':
1820 mask = S_IFSOCK;
1821 break;
1822 #ifdef S_IFWHT
1823 case 'W':
1824 case 'w':
1825 mask = S_IFWHT;
1826 #ifdef FTS_WHITEOUT
1827 ftsoptions |= FTS_WHITEOUT;
1828 #endif
1829 break;
1830 #endif /* S_IFWHT */
1831 default:
1832 errx(1, "%s: %s: unknown type", opt, typestring);
1833 }
1834
1835 new = palloc(N_TYPE, f_type);
1836 new->m_data = mask;
1837 return (new);
1838 }
1839
1840 /*
1841 * -user uname functions --
1842 *
1843 * True if the file belongs to the user uname. If uname is numeric and
1844 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1845 * return a valid user name, uname is taken as a user ID.
1846 */
1847 int
1848 f_user(PLAN *plan, FTSENT *entry)
1849 {
1850
1851 COMPARE(entry->fts_statp->st_uid, plan->u_data);
1852 }
1853
1854 PLAN *
1855 c_user(char ***argvp, int isok, char *opt)
1856 {
1857 char *username = **argvp;
1858 PLAN *new;
1859 struct passwd *p;
1860 uid_t uid;
1861
1862 (*argvp)++;
1863 ftsoptions &= ~FTS_NOSTAT;
1864
1865 new = palloc(N_USER, f_user);
1866 p = getpwnam(username);
1867 if (p == NULL) {
1868 if (atoi(username) == 0 && username[0] != '0' &&
1869 strcmp(username, "+0") && strcmp(username, "-0"))
1870 errx(1, "%s: %s: no such user", opt, username);
1871 uid = find_parsenum(new, opt, username, NULL);
1872
1873 } else {
1874 new->flags = F_EQUAL;
1875 uid = p->pw_uid;
1876 }
1877
1878 new->u_data = uid;
1879 return (new);
1880 }
1881
1882 /*
1883 * -xdev functions --
1884 *
1885 * Always true, causes find not to descend past directories that have a
1886 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1887 */
1888 PLAN *
1889 c_xdev(char ***argvp, int isok, char *opt)
1890 {
1891 ftsoptions |= FTS_XDEV;
1892
1893 return (palloc(N_XDEV, f_always_true));
1894 }
1895
1896 /*
1897 * ( expression ) functions --
1898 *
1899 * True if expression is true.
1900 */
1901 int
1902 f_expr(PLAN *plan, FTSENT *entry)
1903 {
1904 PLAN *p;
1905 int state;
1906
1907 state = 0;
1908 for (p = plan->p_data[0];
1909 p && (state = (p->eval)(p, entry)); p = p->next);
1910 return (state);
1911 }
1912
1913 /*
1914 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are
1915 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1916 * to a N_EXPR node containing the expression and the ')' node is discarded.
1917 */
1918 PLAN *
1919 c_openparen(char ***argvp, int isok, char *opt)
1920 {
1921
1922 return (palloc(N_OPENPAREN, (int (*)(PLAN *, FTSENT *))-1));
1923 }
1924
1925 PLAN *
1926 c_closeparen(char ***argvp, int isok, char *opt)
1927 {
1928
1929 return (palloc(N_CLOSEPAREN, (int (*)(PLAN *, FTSENT *))-1));
1930 }
1931
1932 /*
1933 * ! expression functions --
1934 *
1935 * Negation of a primary; the unary NOT operator.
1936 */
1937 int
1938 f_not(PLAN *plan, FTSENT *entry)
1939 {
1940 PLAN *p;
1941 int state;
1942
1943 state = 0;
1944 for (p = plan->p_data[0];
1945 p && (state = (p->eval)(p, entry)); p = p->next);
1946 return (!state);
1947 }
1948
1949 PLAN *
1950 c_not(char ***argvp, int isok, char *opt)
1951 {
1952
1953 return (palloc(N_NOT, f_not));
1954 }
1955
1956 /*
1957 * expression -o expression functions --
1958 *
1959 * Alternation of primaries; the OR operator. The second expression is
1960 * not evaluated if the first expression is true.
1961 */
1962 int
1963 f_or(PLAN *plan, FTSENT *entry)
1964 {
1965 PLAN *p;
1966 int state;
1967
1968 state = 0;
1969 for (p = plan->p_data[0];
1970 p && (state = (p->eval)(p, entry)); p = p->next);
1971
1972 if (state)
1973 return (1);
1974
1975 for (p = plan->p_data[1];
1976 p && (state = (p->eval)(p, entry)); p = p->next);
1977 return (state);
1978 }
1979
1980 PLAN *
1981 c_or(char ***argvp, int isok, char *opt)
1982 {
1983
1984 return (palloc(N_OR, f_or));
1985 }
1986
1987 PLAN *
1988 c_null(char ***argvp, int isok, char *opt)
1989 {
1990
1991 return (NULL);
1992 }
1993
1994
1995 /*
1996 * plan_cleanup --
1997 * Check and see if the specified plan has any residual state,
1998 * and if so, clean it up as appropriate.
1999 *
2000 * At the moment, only N_EXEC has state. Two kinds: 1)
2001 * lists of files to feed to subprocesses 2) State on exit
2002 * statusses of past subprocesses.
2003 */
2004 /* ARGSUSED1 */
2005 int
2006 plan_cleanup(PLAN *plan, void *arg)
2007 {
2008 if (plan->type==N_EXEC && plan->ep_narg)
2009 run_f_exec(plan);
2010
2011 return plan->ep_rval; /* Passed save exit-status up chain */
2012 }
2013
2014 static PLAN *
2015 palloc(enum ntype t, int (*f)(PLAN *, FTSENT *))
2016 {
2017 PLAN *new;
2018
2019 if ((new = malloc(sizeof(PLAN))) == NULL)
2020 err(1, NULL);
2021 memset(new, 0, sizeof(PLAN));
2022 new->type = t;
2023 new->eval = f;
2024 return (new);
2025 }
2026