1/*-
2 * Copyright (c) 1990, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Cimarron D. Taylor of the University of California, Berkeley.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#ifndef lint
38static char sccsid[] = "@(#)function.c 8.10 (Berkeley) 5/4/95";
39static char rcsid[] = "$FreeBSD: head/usr.bin/find/function.c 61573 2000-06-12 10:36:52Z roberto $";
40#endif /* not lint */
41
42#include <sys/param.h>
43#include <sys/ucred.h>
44#include <sys/stat.h>
45#include <sys/wait.h>
46#include <sys/mount.h>
47
48#include <err.h>
49#include <errno.h>
50#include <fnmatch.h>
51#include <fts.h>
52#include <grp.h>
53#include <pwd.h>
54#include <stdio.h>
55#include <stdlib.h>
56#include <string.h>
57#include <unistd.h>
58
59#include "find.h"
60
61#define COMPARE(a, b) { \
62 switch (plan->flags) { \
63 case F_EQUAL: \
64 return (a == b); \
65 case F_LESSTHAN: \
66 return (a < b); \
67 case F_GREATER: \
68 return (a > b); \
69 default: \
70 abort(); \
71 } \
72}
73
74u_long setflags __P((char **, u_long *, u_long *));
75
76static PLAN *palloc __P((enum ntype, int (*) __P((PLAN *, FTSENT *))));
77
78/*
79 * find_parsenum --
80 * Parse a string of the form [+-]# and return the value.
81 */
82static long long
83find_parsenum(plan, option, vp, endch)
84 PLAN *plan;
85 char *option, *vp, *endch;
86{
87 long long value;
88 char *endchar, *str; /* Pointer to character ending conversion. */
89
90 /* Determine comparison from leading + or -. */
91 str = vp;
92 switch (*str) {
93 case '+':
94 ++str;
95 plan->flags = F_GREATER;
96 break;
97 case '-':
98 ++str;
99 plan->flags = F_LESSTHAN;
100 break;
101 default:
102 plan->flags = F_EQUAL;
103 break;
104 }
105
106 /*
107 * Convert the string with strtoq(). Note, if strtoq() returns zero
108 * and endchar points to the beginning of the string we know we have
109 * a syntax error.
110 */
111 value = strtoq(str, &endchar, 10);
112 if (value == 0 && endchar == str)
113 errx(1, "%s: %s: illegal numeric value", option, vp);
114 if (endchar[0] && (endch == NULL || endchar[0] != *endch))
115 errx(1, "%s: %s: illegal trailing character", option, vp);
116 if (endch)
117 *endch = endchar[0];
118 return (value);
119}
120
121/*
122 * The value of n for the inode times (atime, ctime, and mtime) is a range,
123 * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with
124 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
125 * user wanted. Correct so that -1 is "less than 1".
126 */
127#define TIME_CORRECT(p, ttype) \
128 if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \
129 ++((p)->t_data);
130
131/*
132 * -amin n functions --
133 *
134 * True if the difference between the file access time and the
135 * current time is n min periods.
136 */
137int
138f_amin(plan, entry)
139 PLAN *plan;
140 FTSENT *entry;
141{
142 extern time_t now;
143
144 COMPARE((now - entry->fts_statp->st_atime +
145 60 - 1) / 60, plan->t_data);
146}
147
148PLAN *
149c_amin(arg)
150 char *arg;
151{
152 PLAN *new;
153
154 ftsoptions &= ~FTS_NOSTAT;
155
156 new = palloc(N_AMIN, f_amin);
157 new->t_data = find_parsenum(new, "-amin", arg, NULL);
158 TIME_CORRECT(new, N_AMIN);
159 return (new);
160}
161
162
163/*
164 * -atime n functions --
165 *
166 * True if the difference between the file access time and the
167 * current time is n 24 hour periods.
168 */
169int
170f_atime(plan, entry)
171 PLAN *plan;
172 FTSENT *entry;
173{
174 extern time_t now;
175
176 COMPARE((now - entry->fts_statp->st_atime +
177 86400 - 1) / 86400, plan->t_data);
178}
179
180PLAN *
181c_atime(arg)
182 char *arg;
183{
184 PLAN *new;
185
186 ftsoptions &= ~FTS_NOSTAT;
187
188 new = palloc(N_ATIME, f_atime);
189 new->t_data = find_parsenum(new, "-atime", arg, NULL);
190 TIME_CORRECT(new, N_ATIME);
191 return (new);
192}
193
194
195/*
196 * -cmin n functions --
197 *
198 * True if the difference between the last change of file
199 * status information and the current time is n min periods.
200 */
201int
202f_cmin(plan, entry)
203 PLAN *plan;
204 FTSENT *entry;
205{
206 extern time_t now;
207
208 COMPARE((now - entry->fts_statp->st_ctime +
209 60 - 1) / 60, plan->t_data);
210}
211
212PLAN *
213c_cmin(arg)
214 char *arg;
215{
216 PLAN *new;
217
218 ftsoptions &= ~FTS_NOSTAT;
219
220 new = palloc(N_CMIN, f_cmin);
221 new->t_data = find_parsenum(new, "-cmin", arg, NULL);
222 TIME_CORRECT(new, N_CMIN);
223 return (new);
224}
225
226/*
227 * -ctime n functions --
228 *
229 * True if the difference between the last change of file
230 * status information and the current time is n 24 hour periods.
231 */
232int
233f_ctime(plan, entry)
234 PLAN *plan;
235 FTSENT *entry;
236{
237 extern time_t now;
238
239 COMPARE((now - entry->fts_statp->st_ctime +
240 86400 - 1) / 86400, plan->t_data);
241}
242
243PLAN *
244c_ctime(arg)
245 char *arg;
246{
247 PLAN *new;
248
249 ftsoptions &= ~FTS_NOSTAT;
250
251 new = palloc(N_CTIME, f_ctime);
252 new->t_data = find_parsenum(new, "-ctime", arg, NULL);
253 TIME_CORRECT(new, N_CTIME);
254 return (new);
255}
256
257
258/*
259 * -depth functions --
260 *
261 * Always true, causes descent of the directory hierarchy to be done
262 * so that all entries in a directory are acted on before the directory
263 * itself.
264 */
265int
266f_always_true(plan, entry)
267 PLAN *plan;
268 FTSENT *entry;
269{
270 return (1);
271}
272
273PLAN *
274c_depth()
275{
276 isdepth = 1;
277
278 return (palloc(N_DEPTH, f_always_true));
279}
280
281/*
282 * [-exec | -ok] utility [arg ... ] ; functions --
283 *
284 * True if the executed utility returns a zero value as exit status.
285 * The end of the primary expression is delimited by a semicolon. If
286 * "{}" occurs anywhere, it gets replaced by the current pathname.
287 * The current directory for the execution of utility is the same as
288 * the current directory when the find utility was started.
289 *
290 * The primary -ok is different in that it requests affirmation of the
291 * user before executing the utility.
292 */
293int
294f_exec(plan, entry)
295 register PLAN *plan;
296 FTSENT *entry;
297{
298 extern int dotfd;
299 register int cnt;
300 pid_t pid;
301 int status;
302
303 for (cnt = 0; plan->e_argv[cnt]; ++cnt)
304 if (plan->e_len[cnt])
305 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
306 entry->fts_path, plan->e_len[cnt]);
307
308 if (plan->flags == F_NEEDOK && !queryuser(plan->e_argv))
309 return (0);
310
311 /* make sure find output is interspersed correctly with subprocesses */
312 fflush(stdout);
313
314 switch (pid = fork()) {
315 case -1:
316 err(1, "fork");
317 /* NOTREACHED */
318 case 0:
319 if (fchdir(dotfd)) {
320 warn("chdir");
321 _exit(1);
322 }
323 execvp(plan->e_argv[0], plan->e_argv);
324 warn("%s", plan->e_argv[0]);
325 _exit(1);
326 }
327 pid = waitpid(pid, &status, 0);
328 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
329}
330
331/*
332 * c_exec --
333 * build three parallel arrays, one with pointers to the strings passed
334 * on the command line, one with (possibly duplicated) pointers to the
335 * argv array, and one with integer values that are lengths of the
336 * strings, but also flags meaning that the string has to be massaged.
337 */
338PLAN *
339c_exec(argvp, isok)
340 char ***argvp;
341 int isok;
342{
343 PLAN *new; /* node returned */
344 register int cnt;
345 register char **argv, **ap, *p;
346
347 isoutput = 1;
348
349 new = palloc(N_EXEC, f_exec);
350 if (isok)
351 new->flags = F_NEEDOK;
352
353 for (ap = argv = *argvp;; ++ap) {
354 if (!*ap)
355 errx(1,
356 "%s: no terminating \";\"", isok ? "-ok" : "-exec");
357 if (**ap == ';')
358 break;
359 }
360
361 cnt = ap - *argvp + 1;
362 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
363 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
364 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
365
366 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
367 new->e_orig[cnt] = *argv;
368 for (p = *argv; *p; ++p)
369 if (p[0] == '{' && p[1] == '}') {
370 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN);
371 new->e_len[cnt] = MAXPATHLEN;
372 break;
373 }
374 if (!*p) {
375 new->e_argv[cnt] = *argv;
376 new->e_len[cnt] = 0;
377 }
378 }
379 new->e_argv[cnt] = new->e_orig[cnt] = NULL;
380
381 *argvp = argv + 1;
382 return (new);
383}
384
385/*
386 * -execdir utility [arg ... ] ; functions --
387 *
388 * True if the executed utility returns a zero value as exit status.
389 * The end of the primary expression is delimited by a semicolon. If
390 * "{}" occurs anywhere, it gets replaced by the unqualified pathname.
391 * The current directory for the execution of utility is the same as
392 * the directory where the file lives.
393 */
394int
395f_execdir(plan, entry)
396 register PLAN *plan;
397 FTSENT *entry;
398{
399 register int cnt;
400 pid_t pid;
401 int status;
402 char *file;
403
404 /* XXX - if file/dir ends in '/' this will not work -- can it? */
405 if ((file = strrchr(entry->fts_path, '/')))
406 file++;
407 else
408 file = entry->fts_path;
409
410 for (cnt = 0; plan->e_argv[cnt]; ++cnt)
411 if (plan->e_len[cnt])
412 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
413 file, plan->e_len[cnt]);
414
415 /* don't mix output of command with find output */
416 fflush(stdout);
417 fflush(stderr);
418
419 switch (pid = fork()) {
420 case -1:
421 err(1, "fork");
422 /* NOTREACHED */
423 case 0:
424 execvp(plan->e_argv[0], plan->e_argv);
425 warn("%s", plan->e_argv[0]);
426 _exit(1);
427 }
428 pid = waitpid(pid, &status, 0);
429 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
430}
431
432/*
433 * c_execdir --
434 * build three parallel arrays, one with pointers to the strings passed
435 * on the command line, one with (possibly duplicated) pointers to the
436 * argv array, and one with integer values that are lengths of the
437 * strings, but also flags meaning that the string has to be massaged.
438 */
439PLAN *
440c_execdir(argvp)
441 char ***argvp;
442{
443 PLAN *new; /* node returned */
444 register int cnt;
445 register char **argv, **ap, *p;
446
447 ftsoptions &= ~FTS_NOSTAT;
448 isoutput = 1;
449
450 new = palloc(N_EXECDIR, f_execdir);
451
452 for (ap = argv = *argvp;; ++ap) {
453 if (!*ap)
454 errx(1,
455 "-execdir: no terminating \";\"");
456 if (**ap == ';')
457 break;
458 }
459
460 cnt = ap - *argvp + 1;
461 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
462 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
463 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
464
465 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
466 new->e_orig[cnt] = *argv;
467 for (p = *argv; *p; ++p)
468 if (p[0] == '{' && p[1] == '}') {
469 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN);
470 new->e_len[cnt] = MAXPATHLEN;
471 break;
472 }
473 if (!*p) {
474 new->e_argv[cnt] = *argv;
475 new->e_len[cnt] = 0;
476 }
477 }
478 new->e_argv[cnt] = new->e_orig[cnt] = NULL;
479
480 *argvp = argv + 1;
481 return (new);
482}
483
484/*
485 * -follow functions --
486 *
487 * Always true, causes symbolic links to be followed on a global
488 * basis.
489 */
490PLAN *
491c_follow()
492{
493 ftsoptions &= ~FTS_PHYSICAL;
494 ftsoptions |= FTS_LOGICAL;
495
496 return (palloc(N_FOLLOW, f_always_true));
497}
498
499/*
500 * -fstype functions --
501 *
502 * True if the file is of a certain type.
503 */
504int
505f_fstype(plan, entry)
506 PLAN *plan;
507 FTSENT *entry;
508{
509 static dev_t curdev; /* need a guaranteed illegal dev value */
510 static int first = 1;
511 struct statfs sb;
512 static int val_type, val_flags;
513 char *p, save[2];
514
515 /* Only check when we cross mount point. */
516 if (first || curdev != entry->fts_statp->st_dev) {
517 curdev = entry->fts_statp->st_dev;
518
519 /*
520 * Statfs follows symlinks; find wants the link's file system,
521 * not where it points.
522 */
523 if (entry->fts_info == FTS_SL ||
524 entry->fts_info == FTS_SLNONE) {
525 if ((p = strrchr(entry->fts_accpath, '/')) != NULL)
526 ++p;
527 else
528 p = entry->fts_accpath;
529 save[0] = p[0];
530 p[0] = '.';
531 save[1] = p[1];
532 p[1] = '\0';
533
534 } else
535 p = NULL;
536
537 if (statfs(entry->fts_accpath, &sb))
538 err(1, "%s", entry->fts_accpath);
539
540 if (p) {
541 p[0] = save[0];
542 p[1] = save[1];
543 }
544
545 first = 0;
546
547 /*
548 * Further tests may need both of these values, so
549 * always copy both of them.
550 */
551 val_flags = sb.f_flags;
552 val_type = sb.f_type;
553 }
554 switch (plan->flags) {
555 case F_MTFLAG:
556 return (val_flags & plan->mt_data) != 0;
557 case F_MTTYPE:
558 return (val_type == plan->mt_data);
559 default:
560 abort();
561 }
562}
563
564#if !defined(__NetBSD__)
565PLAN *
566c_fstype(arg)
567 char *arg;
568{
569 register PLAN *new;
570 struct vfsconf vfc;
571
572 ftsoptions &= ~FTS_NOSTAT;
573
574 new = palloc(N_FSTYPE, f_fstype);
575
576 /*
577 * Check first for a filesystem name.
578 */
579 if (getvfsbyname(arg, &vfc) == 0) {
580 new->flags = F_MTTYPE;
581 new->mt_data = vfc.vfc_typenum;
582 return (new);
583 }
584
585 switch (*arg) {
586 case 'l':
587 if (!strcmp(arg, "local")) {
588 new->flags = F_MTFLAG;
589 new->mt_data = MNT_LOCAL;
590 return (new);
591 }
592 break;
593 case 'r':
594 if (!strcmp(arg, "rdonly")) {
595 new->flags = F_MTFLAG;
596 new->mt_data = MNT_RDONLY;
597 return (new);
598 }
599 break;
600 }
601 errx(1, "%s: unknown file type", arg);
602 /* NOTREACHED */
603}
604#endif
605
606/*
607 * -group gname functions --
608 *
609 * True if the file belongs to the group gname. If gname is numeric and
610 * an equivalent of the getgrnam() function does not return a valid group
611 * name, gname is taken as a group ID.
612 */
613int
614f_group(plan, entry)
615 PLAN *plan;
616 FTSENT *entry;
617{
618 return (entry->fts_statp->st_gid == plan->g_data);
619}
620
621PLAN *
622c_group(gname)
623 char *gname;
624{
625 PLAN *new;
626 struct group *g;
627 gid_t gid;
628
629 ftsoptions &= ~FTS_NOSTAT;
630
631 g = getgrnam(gname);
632 if (g == NULL) {
633 gid = atoi(gname);
634 if (gid == 0 && gname[0] != '0')
635 errx(1, "-group: %s: no such group", gname);
636 } else
637 gid = g->gr_gid;
638
639 new = palloc(N_GROUP, f_group);
640 new->g_data = gid;
641 return (new);
642}
643
644/*
645 * -inum n functions --
646 *
647 * True if the file has inode # n.
648 */
649int
650f_inum(plan, entry)
651 PLAN *plan;
652 FTSENT *entry;
653{
654 COMPARE(entry->fts_statp->st_ino, plan->i_data);
655}
656
657PLAN *
658c_inum(arg)
659 char *arg;
660{
661 PLAN *new;
662
663 ftsoptions &= ~FTS_NOSTAT;
664
665 new = palloc(N_INUM, f_inum);
666 new->i_data = find_parsenum(new, "-inum", arg, NULL);
667 return (new);
668}
669
670/*
671 * -links n functions --
672 *
673 * True if the file has n links.
674 */
675int
676f_links(plan, entry)
677 PLAN *plan;
678 FTSENT *entry;
679{
680 COMPARE(entry->fts_statp->st_nlink, plan->l_data);
681}
682
683PLAN *
684c_links(arg)
685 char *arg;
686{
687 PLAN *new;
688
689 ftsoptions &= ~FTS_NOSTAT;
690
691 new = palloc(N_LINKS, f_links);
692 new->l_data = (nlink_t)find_parsenum(new, "-links", arg, NULL);
693 return (new);
694}
695
696/*
697 * -ls functions --
698 *
699 * Always true - prints the current entry to stdout in "ls" format.
700 */
701int
702f_ls(plan, entry)
703 PLAN *plan;
704 FTSENT *entry;
705{
706 printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
707 return (1);
708}
709
710PLAN *
711c_ls()
712{
713 ftsoptions &= ~FTS_NOSTAT;
714 isoutput = 1;
715
716 return (palloc(N_LS, f_ls));
717}
718
719/*
720 * -mtime n functions --
721 *
722 * True if the difference between the file modification time and the
723 * current time is n 24 hour periods.
724 */
725int
726f_mtime(plan, entry)
727 PLAN *plan;
728 FTSENT *entry;
729{
730 extern time_t now;
731
732 COMPARE((now - entry->fts_statp->st_mtime + 86400 - 1) /
733 86400, plan->t_data);
734}
735
736PLAN *
737c_mtime(arg)
738 char *arg;
739{
740 PLAN *new;
741
742 ftsoptions &= ~FTS_NOSTAT;
743
744 new = palloc(N_MTIME, f_mtime);
745 new->t_data = find_parsenum(new, "-mtime", arg, NULL);
746 TIME_CORRECT(new, N_MTIME);
747 return (new);
748}
749
750/*
751 * -mmin n functions --
752 *
753 * True if the difference between the file modification time and the
754 * current time is n min periods.
755 */
756int
757f_mmin(plan, entry)
758 PLAN *plan;
759 FTSENT *entry;
760{
761 extern time_t now;
762
763 COMPARE((now - entry->fts_statp->st_mtime + 60 - 1) /
764 60, plan->t_data);
765}
766
767PLAN *
768c_mmin(arg)
769 char *arg;
770{
771 PLAN *new;
772
773 ftsoptions &= ~FTS_NOSTAT;
774
775 new = palloc(N_MMIN, f_mmin);
776 new->t_data = find_parsenum(new, "-mmin", arg, NULL);
777 TIME_CORRECT(new, N_MMIN);
778 return (new);
779}
780
781
782/*
783 * -name functions --
784 *
785 * True if the basename of the filename being examined
786 * matches pattern using Pattern Matching Notation S3.14
787 */
788int
789f_name(plan, entry)
790 PLAN *plan;
791 FTSENT *entry;
792{
793 return (!fnmatch(plan->c_data, entry->fts_name, 0));
794}
795
796PLAN *
797c_name(pattern)
798 char *pattern;
799{
800 PLAN *new;
801
802 new = palloc(N_NAME, f_name);
803 new->c_data = pattern;
804 return (new);
805}
806
807/*
808 * -newer file functions --
809 *
810 * True if the current file has been modified more recently
811 * then the modification time of the file named by the pathname
812 * file.
813 */
814int
815f_newer(plan, entry)
816 PLAN *plan;
817 FTSENT *entry;
818{
819 return (entry->fts_statp->st_mtime > plan->t_data);
820}
821
822PLAN *
823c_newer(filename)
824 char *filename;
825{
826 PLAN *new;
827 struct stat sb;
828
829 ftsoptions &= ~FTS_NOSTAT;
830
831 if (stat(filename, &sb))
832 err(1, "%s", filename);
833 new = palloc(N_NEWER, f_newer);
834 new->t_data = sb.st_mtime;
835 return (new);
836}
837
838/*
839 * -nogroup functions --
840 *
841 * True if file belongs to a user ID for which the equivalent
842 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
843 */
844int
845f_nogroup(plan, entry)
846 PLAN *plan;
847 FTSENT *entry;
848{
849 char *group_from_gid();
850
851 return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1);
852}
853
854PLAN *
855c_nogroup()
856{
857 ftsoptions &= ~FTS_NOSTAT;
858
859 return (palloc(N_NOGROUP, f_nogroup));
860}
861
862/*
863 * -nouser functions --
864 *
865 * True if file belongs to a user ID for which the equivalent
866 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
867 */
868int
869f_nouser(plan, entry)
870 PLAN *plan;
871 FTSENT *entry;
872{
873 char *user_from_uid();
874
875 return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1);
876}
877
878PLAN *
879c_nouser()
880{
881 ftsoptions &= ~FTS_NOSTAT;
882
883 return (palloc(N_NOUSER, f_nouser));
884}
885
886/*
887 * -path functions --
888 *
889 * True if the path of the filename being examined
890 * matches pattern using Pattern Matching Notation S3.14
891 */
892int
893f_path(plan, entry)
894 PLAN *plan;
895 FTSENT *entry;
896{
897 return (!fnmatch(plan->c_data, entry->fts_path, 0));
898}
899
900PLAN *
901c_path(pattern)
902 char *pattern;
903{
904 PLAN *new;
905
906 new = palloc(N_NAME, f_path);
907 new->c_data = pattern;
908 return (new);
909}
910
911/*
912 * -perm functions --
913 *
914 * The mode argument is used to represent file mode bits. If it starts
915 * with a leading digit, it's treated as an octal mode, otherwise as a
916 * symbolic mode.
917 */
918int
919f_perm(plan, entry)
920 PLAN *plan;
921 FTSENT *entry;
922{
923 mode_t mode;
924
925 mode = entry->fts_statp->st_mode &
926 (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
927 if (plan->flags == F_ATLEAST)
928 return ((plan->m_data | mode) == mode);
929 else if (plan->flags == F_ANY )
930 return (plan->m_data & mode);
931 else
932 return (mode == plan->m_data);
933 /* NOTREACHED */
934}
935
936PLAN *
937c_perm(perm)
938 char *perm;
939{
940 PLAN *new;
941 mode_t *set;
942
943 ftsoptions &= ~FTS_NOSTAT;
944
945 new = palloc(N_PERM, f_perm);
946
947 if (*perm == '-') {
948 new->flags = F_ATLEAST;
949 ++perm;
950 } else if (*perm == '+') {
951 new->flags = F_ANY;
952 ++perm;
953 }
954
955 if ((set = setmode(perm)) == NULL)
956 errx(1, "-perm: %s: illegal mode string", perm);
957
958 new->m_data = getmode(set, 0);
959 free(set);
960 return (new);
961}
962
963/*
964 * -flags functions --
965 *
966 * The flags argument is used to represent file flags bits.
967 */
968int
969f_flags(plan, entry)
970 PLAN *plan;
971 FTSENT *entry;
972{
973 u_long flags;
974
975 flags = entry->fts_statp->st_flags &
976 (UF_NODUMP | UF_IMMUTABLE | UF_APPEND | UF_OPAQUE |
977 SF_ARCHIVED | SF_IMMUTABLE | SF_APPEND);
978 if (plan->flags == F_ATLEAST)
979 /* note that plan->fl_flags always is a subset of
980 plan->fl_mask */
981 return (flags & plan->fl_mask) == plan->fl_flags;
982 else
983 return flags == plan->fl_flags;
984 /* NOTREACHED */
985}
986
987PLAN *
988c_flags(flags_str)
989 char *flags_str;
990{
991 PLAN *new;
992 u_long flags, notflags;
993
994 ftsoptions &= ~FTS_NOSTAT;
995
996 new = palloc(N_FLAGS, f_flags);
997
998 if (*flags_str == '-') {
999 new->flags = F_ATLEAST;
1000 flags_str++;
1001 }
1002 if (setflags(&flags_str, &flags, ¬flags) == 1)
1003 errx(1, "-flags: %s: illegal flags string", flags_str);
1004
1005 new->fl_flags = flags;
1006 new->fl_mask = flags | notflags;
1007#if 0
1008 printf("flags = %08x, mask = %08x (%08x, %08x)\n",
1009 new->fl_flags, new->fl_mask, flags, notflags);
1010#endif
1011 return new;
1012}
1013
1014 /*
1015
1016
1017/*
1018 * -print functions --
1019 *
1020 * Always true, causes the current pathame to be written to
1021 * standard output.
1022 */
1023int
1024f_print(plan, entry)
1025 PLAN *plan;
1026 FTSENT *entry;
1027{
1028 (void)puts(entry->fts_path);
1029 return (1);
1030}
1031
1032PLAN *
1033c_print()
1034{
1035 isoutput = 1;
1036
1037 return (palloc(N_PRINT, f_print));
1038}
1039
1040/*
1041 * -print0 functions --
1042 *
1043 * Always true, causes the current pathame to be written to
1044 * standard output followed by a NUL character
1045 */
1046int
1047f_print0(plan, entry)
1048 PLAN *plan;
1049 FTSENT *entry;
1050{
1051 fputs(entry->fts_path, stdout);
1052 fputc('\0', stdout);
1053 return (1);
1054}
1055
1056PLAN *
1057c_print0()
1058{
1059 isoutput = 1;
1060
1061 return (palloc(N_PRINT0, f_print0));
1062}
1063
1064/*
1065 * -prune functions --
1066 *
1067 * Prune a portion of the hierarchy.
1068 */
1069int
1070f_prune(plan, entry)
1071 PLAN *plan;
1072 FTSENT *entry;
1073{
1074 extern FTS *tree;
1075
1076 if (fts_set(tree, entry, FTS_SKIP))
1077 err(1, "%s", entry->fts_path);
1078 return (1);
1079}
1080
1081PLAN *
1082c_prune()
1083{
1084 return (palloc(N_PRUNE, f_prune));
1085}
1086
1087/*
1088 * -size n[c] functions --
1089 *
1090 * True if the file size in bytes, divided by an implementation defined
1091 * value and rounded up to the next integer, is n. If n is followed by
1092 * a c, the size is in bytes.
1093 */
1094#define FIND_SIZE 512
1095static int divsize = 1;
1096
1097int
1098f_size(plan, entry)
1099 PLAN *plan;
1100 FTSENT *entry;
1101{
1102 off_t size;
1103
1104 size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
1105 FIND_SIZE : entry->fts_statp->st_size;
1106 COMPARE(size, plan->o_data);
1107}
1108
1109PLAN *
1110c_size(arg)
1111 char *arg;
1112{
1113 PLAN *new;
1114 char endch;
1115
1116 ftsoptions &= ~FTS_NOSTAT;
1117
1118 new = palloc(N_SIZE, f_size);
1119 endch = 'c';
1120 new->o_data = find_parsenum(new, "-size", arg, &endch);
1121 if (endch == 'c')
1122 divsize = 0;
1123 return (new);
1124}
1125
1126/*
1127 * -type c functions --
1128 *
1129 * True if the type of the file is c, where c is b, c, d, p, f or w
1130 * for block special file, character special file, directory, FIFO,
1131 * regular file or whiteout respectively.
1132 */
1133int
1134f_type(plan, entry)
1135 PLAN *plan;
1136 FTSENT *entry;
1137{
1138 return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data);
1139}
1140
1141PLAN *
1142c_type(typestring)
1143 char *typestring;
1144{
1145 PLAN *new;
1146 mode_t mask;
1147
1148 ftsoptions &= ~FTS_NOSTAT;
1149
1150 switch (typestring[0]) {
1151 case 'b':
1152 mask = S_IFBLK;
1153 break;
1154 case 'c':
1155 mask = S_IFCHR;
1156 break;
1157 case 'd':
1158 mask = S_IFDIR;
1159 break;
1160 case 'f':
1161 mask = S_IFREG;
1162 break;
1163 case 'l':
1164 mask = S_IFLNK;
1165 break;
1166 case 'p':
1167 mask = S_IFIFO;
1168 break;
1169 case 's':
1170 mask = S_IFSOCK;
1171 break;
1172#ifdef FTS_WHITEOUT
1173 case 'w':
1174 mask = S_IFWHT;
1175 ftsoptions |= FTS_WHITEOUT;
1176 break;
1177#endif /* FTS_WHITEOUT */
1178 default:
1179 errx(1, "-type: %s: unknown type", typestring);
1180 }
1181
1182 new = palloc(N_TYPE, f_type);
1183 new->m_data = mask;
1184 return (new);
1185}
1186
1187/*
1188 * -delete functions --
1189 *
1190 * True always. Makes it's best shot and continues on regardless.
1191 */
1192int
1193f_delete(plan, entry)
1194 PLAN *plan;
1195 FTSENT *entry;
1196{
1197 /* ignore these from fts */
1198 if (strcmp(entry->fts_accpath, ".") == 0 ||
1199 strcmp(entry->fts_accpath, "..") == 0)
1200 return (1);
1201
1202 /* sanity check */
1203 if (isdepth == 0 || /* depth off */
1204 (ftsoptions & FTS_NOSTAT) || /* not stat()ing */
1205 !(ftsoptions & FTS_PHYSICAL) || /* physical off */
1206 (ftsoptions & FTS_LOGICAL)) /* or finally, logical on */
1207 errx(1, "-delete: insecure options got turned on");
1208
1209 /* Potentially unsafe - do not accept relative paths whatsoever */
1210 if (strchr(entry->fts_accpath, '/') != NULL)
1211 errx(1, "-delete: %s: relative path potentially not safe",
1212 entry->fts_accpath);
1213
1214 /* Turn off user immutable bits if running as root */
1215 if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
1216 !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
1217 geteuid() == 0)
1218 chflags(entry->fts_accpath,
1219 entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
1220
1221 /* rmdir directories, unlink everything else */
1222 if (S_ISDIR(entry->fts_statp->st_mode)) {
1223 if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY)
1224 warn("-delete: rmdir(%s)", entry->fts_path);
1225 } else {
1226 if (unlink(entry->fts_accpath) < 0)
1227 warn("-delete: unlink(%s)", entry->fts_path);
1228 }
1229
1230 /* "succeed" */
1231 return (1);
1232}
1233
1234PLAN *
1235c_delete()
1236{
1237
1238 ftsoptions &= ~FTS_NOSTAT; /* no optimise */
1239 ftsoptions |= FTS_PHYSICAL; /* disable -follow */
1240 ftsoptions &= ~FTS_LOGICAL; /* disable -follow */
1241 isoutput = 1; /* possible output */
1242 isdepth = 1; /* -depth implied */
1243
1244 return (palloc(N_DELETE, f_delete));
1245}
1246
1247/*
1248 * -user uname functions --
1249 *
1250 * True if the file belongs to the user uname. If uname is numeric and
1251 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1252 * return a valid user name, uname is taken as a user ID.
1253 */
1254int
1255f_user(plan, entry)
1256 PLAN *plan;
1257 FTSENT *entry;
1258{
1259 return (entry->fts_statp->st_uid == plan->u_data);
1260}
1261
1262PLAN *
1263c_user(username)
1264 char *username;
1265{
1266 PLAN *new;
1267 struct passwd *p;
1268 uid_t uid;
1269
1270 ftsoptions &= ~FTS_NOSTAT;
1271
1272 p = getpwnam(username);
1273 if (p == NULL) {
1274 uid = atoi(username);
1275 if (uid == 0 && username[0] != '0')
1276 errx(1, "-user: %s: no such user", username);
1277 } else
1278 uid = p->pw_uid;
1279
1280 new = palloc(N_USER, f_user);
1281 new->u_data = uid;
1282 return (new);
1283}
1284
1285/*
1286 * -xdev functions --
1287 *
1288 * Always true, causes find not to decend past directories that have a
1289 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1290 */
1291PLAN *
1292c_xdev()
1293{
1294 ftsoptions |= FTS_XDEV;
1295
1296 return (palloc(N_XDEV, f_always_true));
1297}
1298
1299/*
1300 * ( expression ) functions --
1301 *
1302 * True if expression is true.
1303 */
1304int
1305f_expr(plan, entry)
1306 PLAN *plan;
1307 FTSENT *entry;
1308{
1309 register PLAN *p;
1310 register int state;
1311
1312 state = 0;
1313 for (p = plan->p_data[0];
1314 p && (state = (p->eval)(p, entry)); p = p->next);
1315 return (state);
1316}
1317
1318/*
1319 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are
1320 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1321 * to a N_EXPR node containing the expression and the ')' node is discarded.
1322 */
1323PLAN *
1324c_openparen()
1325{
1326 return (palloc(N_OPENPAREN, (int (*)())-1));
1327}
1328
1329PLAN *
1330c_closeparen()
1331{
1332 return (palloc(N_CLOSEPAREN, (int (*)())-1));
1333}
1334
1335/*
1336 * ! expression functions --
1337 *
1338 * Negation of a primary; the unary NOT operator.
1339 */
1340int
1341f_not(plan, entry)
1342 PLAN *plan;
1343 FTSENT *entry;
1344{
1345 register PLAN *p;
1346 register int state;
1347
1348 state = 0;
1349 for (p = plan->p_data[0];
1350 p && (state = (p->eval)(p, entry)); p = p->next);
1351 return (!state);
1352}
1353
1354PLAN *
1355c_not()
1356{
1357 return (palloc(N_NOT, f_not));
1358}
1359
1360/*
1361 * expression -o expression functions --
1362 *
1363 * Alternation of primaries; the OR operator. The second expression is
1364 * not evaluated if the first expression is true.
1365 */
1366int
1367f_or(plan, entry)
1368 PLAN *plan;
1369 FTSENT *entry;
1370{
1371 register PLAN *p;
1372 register int state;
1373
1374 state = 0;
1375 for (p = plan->p_data[0];
1376 p && (state = (p->eval)(p, entry)); p = p->next);
1377
1378 if (state)
1379 return (1);
1380
1381 for (p = plan->p_data[1];
1382 p && (state = (p->eval)(p, entry)); p = p->next);
1383 return (state);
1384}
1385
1386PLAN *
1387c_or()
1388{
1389 return (palloc(N_OR, f_or));
1390}
1391
1392static PLAN *
1393palloc(t, f)
1394 enum ntype t;
1395 int (*f) __P((PLAN *, FTSENT *));
1396{
1397 PLAN *new;
1398
1399 if ((new = malloc(sizeof(PLAN))) == NULL)
1400 err(1, NULL);
1401 new->type = t;
1402 new->eval = f;
1403 new->flags = 0;
1404 new->next = NULL;
1405 return (new);
1406}
2 * Copyright (c) 1990, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Cimarron D. Taylor of the University of California, Berkeley.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#ifndef lint
38static char sccsid[] = "@(#)function.c 8.10 (Berkeley) 5/4/95";
39static char rcsid[] = "$FreeBSD: head/usr.bin/find/function.c 61573 2000-06-12 10:36:52Z roberto $";
40#endif /* not lint */
41
42#include <sys/param.h>
43#include <sys/ucred.h>
44#include <sys/stat.h>
45#include <sys/wait.h>
46#include <sys/mount.h>
47
48#include <err.h>
49#include <errno.h>
50#include <fnmatch.h>
51#include <fts.h>
52#include <grp.h>
53#include <pwd.h>
54#include <stdio.h>
55#include <stdlib.h>
56#include <string.h>
57#include <unistd.h>
58
59#include "find.h"
60
61#define COMPARE(a, b) { \
62 switch (plan->flags) { \
63 case F_EQUAL: \
64 return (a == b); \
65 case F_LESSTHAN: \
66 return (a < b); \
67 case F_GREATER: \
68 return (a > b); \
69 default: \
70 abort(); \
71 } \
72}
73
74u_long setflags __P((char **, u_long *, u_long *));
75
76static PLAN *palloc __P((enum ntype, int (*) __P((PLAN *, FTSENT *))));
77
78/*
79 * find_parsenum --
80 * Parse a string of the form [+-]# and return the value.
81 */
82static long long
83find_parsenum(plan, option, vp, endch)
84 PLAN *plan;
85 char *option, *vp, *endch;
86{
87 long long value;
88 char *endchar, *str; /* Pointer to character ending conversion. */
89
90 /* Determine comparison from leading + or -. */
91 str = vp;
92 switch (*str) {
93 case '+':
94 ++str;
95 plan->flags = F_GREATER;
96 break;
97 case '-':
98 ++str;
99 plan->flags = F_LESSTHAN;
100 break;
101 default:
102 plan->flags = F_EQUAL;
103 break;
104 }
105
106 /*
107 * Convert the string with strtoq(). Note, if strtoq() returns zero
108 * and endchar points to the beginning of the string we know we have
109 * a syntax error.
110 */
111 value = strtoq(str, &endchar, 10);
112 if (value == 0 && endchar == str)
113 errx(1, "%s: %s: illegal numeric value", option, vp);
114 if (endchar[0] && (endch == NULL || endchar[0] != *endch))
115 errx(1, "%s: %s: illegal trailing character", option, vp);
116 if (endch)
117 *endch = endchar[0];
118 return (value);
119}
120
121/*
122 * The value of n for the inode times (atime, ctime, and mtime) is a range,
123 * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with
124 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
125 * user wanted. Correct so that -1 is "less than 1".
126 */
127#define TIME_CORRECT(p, ttype) \
128 if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \
129 ++((p)->t_data);
130
131/*
132 * -amin n functions --
133 *
134 * True if the difference between the file access time and the
135 * current time is n min periods.
136 */
137int
138f_amin(plan, entry)
139 PLAN *plan;
140 FTSENT *entry;
141{
142 extern time_t now;
143
144 COMPARE((now - entry->fts_statp->st_atime +
145 60 - 1) / 60, plan->t_data);
146}
147
148PLAN *
149c_amin(arg)
150 char *arg;
151{
152 PLAN *new;
153
154 ftsoptions &= ~FTS_NOSTAT;
155
156 new = palloc(N_AMIN, f_amin);
157 new->t_data = find_parsenum(new, "-amin", arg, NULL);
158 TIME_CORRECT(new, N_AMIN);
159 return (new);
160}
161
162
163/*
164 * -atime n functions --
165 *
166 * True if the difference between the file access time and the
167 * current time is n 24 hour periods.
168 */
169int
170f_atime(plan, entry)
171 PLAN *plan;
172 FTSENT *entry;
173{
174 extern time_t now;
175
176 COMPARE((now - entry->fts_statp->st_atime +
177 86400 - 1) / 86400, plan->t_data);
178}
179
180PLAN *
181c_atime(arg)
182 char *arg;
183{
184 PLAN *new;
185
186 ftsoptions &= ~FTS_NOSTAT;
187
188 new = palloc(N_ATIME, f_atime);
189 new->t_data = find_parsenum(new, "-atime", arg, NULL);
190 TIME_CORRECT(new, N_ATIME);
191 return (new);
192}
193
194
195/*
196 * -cmin n functions --
197 *
198 * True if the difference between the last change of file
199 * status information and the current time is n min periods.
200 */
201int
202f_cmin(plan, entry)
203 PLAN *plan;
204 FTSENT *entry;
205{
206 extern time_t now;
207
208 COMPARE((now - entry->fts_statp->st_ctime +
209 60 - 1) / 60, plan->t_data);
210}
211
212PLAN *
213c_cmin(arg)
214 char *arg;
215{
216 PLAN *new;
217
218 ftsoptions &= ~FTS_NOSTAT;
219
220 new = palloc(N_CMIN, f_cmin);
221 new->t_data = find_parsenum(new, "-cmin", arg, NULL);
222 TIME_CORRECT(new, N_CMIN);
223 return (new);
224}
225
226/*
227 * -ctime n functions --
228 *
229 * True if the difference between the last change of file
230 * status information and the current time is n 24 hour periods.
231 */
232int
233f_ctime(plan, entry)
234 PLAN *plan;
235 FTSENT *entry;
236{
237 extern time_t now;
238
239 COMPARE((now - entry->fts_statp->st_ctime +
240 86400 - 1) / 86400, plan->t_data);
241}
242
243PLAN *
244c_ctime(arg)
245 char *arg;
246{
247 PLAN *new;
248
249 ftsoptions &= ~FTS_NOSTAT;
250
251 new = palloc(N_CTIME, f_ctime);
252 new->t_data = find_parsenum(new, "-ctime", arg, NULL);
253 TIME_CORRECT(new, N_CTIME);
254 return (new);
255}
256
257
258/*
259 * -depth functions --
260 *
261 * Always true, causes descent of the directory hierarchy to be done
262 * so that all entries in a directory are acted on before the directory
263 * itself.
264 */
265int
266f_always_true(plan, entry)
267 PLAN *plan;
268 FTSENT *entry;
269{
270 return (1);
271}
272
273PLAN *
274c_depth()
275{
276 isdepth = 1;
277
278 return (palloc(N_DEPTH, f_always_true));
279}
280
281/*
282 * [-exec | -ok] utility [arg ... ] ; functions --
283 *
284 * True if the executed utility returns a zero value as exit status.
285 * The end of the primary expression is delimited by a semicolon. If
286 * "{}" occurs anywhere, it gets replaced by the current pathname.
287 * The current directory for the execution of utility is the same as
288 * the current directory when the find utility was started.
289 *
290 * The primary -ok is different in that it requests affirmation of the
291 * user before executing the utility.
292 */
293int
294f_exec(plan, entry)
295 register PLAN *plan;
296 FTSENT *entry;
297{
298 extern int dotfd;
299 register int cnt;
300 pid_t pid;
301 int status;
302
303 for (cnt = 0; plan->e_argv[cnt]; ++cnt)
304 if (plan->e_len[cnt])
305 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
306 entry->fts_path, plan->e_len[cnt]);
307
308 if (plan->flags == F_NEEDOK && !queryuser(plan->e_argv))
309 return (0);
310
311 /* make sure find output is interspersed correctly with subprocesses */
312 fflush(stdout);
313
314 switch (pid = fork()) {
315 case -1:
316 err(1, "fork");
317 /* NOTREACHED */
318 case 0:
319 if (fchdir(dotfd)) {
320 warn("chdir");
321 _exit(1);
322 }
323 execvp(plan->e_argv[0], plan->e_argv);
324 warn("%s", plan->e_argv[0]);
325 _exit(1);
326 }
327 pid = waitpid(pid, &status, 0);
328 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
329}
330
331/*
332 * c_exec --
333 * build three parallel arrays, one with pointers to the strings passed
334 * on the command line, one with (possibly duplicated) pointers to the
335 * argv array, and one with integer values that are lengths of the
336 * strings, but also flags meaning that the string has to be massaged.
337 */
338PLAN *
339c_exec(argvp, isok)
340 char ***argvp;
341 int isok;
342{
343 PLAN *new; /* node returned */
344 register int cnt;
345 register char **argv, **ap, *p;
346
347 isoutput = 1;
348
349 new = palloc(N_EXEC, f_exec);
350 if (isok)
351 new->flags = F_NEEDOK;
352
353 for (ap = argv = *argvp;; ++ap) {
354 if (!*ap)
355 errx(1,
356 "%s: no terminating \";\"", isok ? "-ok" : "-exec");
357 if (**ap == ';')
358 break;
359 }
360
361 cnt = ap - *argvp + 1;
362 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
363 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
364 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
365
366 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
367 new->e_orig[cnt] = *argv;
368 for (p = *argv; *p; ++p)
369 if (p[0] == '{' && p[1] == '}') {
370 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN);
371 new->e_len[cnt] = MAXPATHLEN;
372 break;
373 }
374 if (!*p) {
375 new->e_argv[cnt] = *argv;
376 new->e_len[cnt] = 0;
377 }
378 }
379 new->e_argv[cnt] = new->e_orig[cnt] = NULL;
380
381 *argvp = argv + 1;
382 return (new);
383}
384
385/*
386 * -execdir utility [arg ... ] ; functions --
387 *
388 * True if the executed utility returns a zero value as exit status.
389 * The end of the primary expression is delimited by a semicolon. If
390 * "{}" occurs anywhere, it gets replaced by the unqualified pathname.
391 * The current directory for the execution of utility is the same as
392 * the directory where the file lives.
393 */
394int
395f_execdir(plan, entry)
396 register PLAN *plan;
397 FTSENT *entry;
398{
399 register int cnt;
400 pid_t pid;
401 int status;
402 char *file;
403
404 /* XXX - if file/dir ends in '/' this will not work -- can it? */
405 if ((file = strrchr(entry->fts_path, '/')))
406 file++;
407 else
408 file = entry->fts_path;
409
410 for (cnt = 0; plan->e_argv[cnt]; ++cnt)
411 if (plan->e_len[cnt])
412 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
413 file, plan->e_len[cnt]);
414
415 /* don't mix output of command with find output */
416 fflush(stdout);
417 fflush(stderr);
418
419 switch (pid = fork()) {
420 case -1:
421 err(1, "fork");
422 /* NOTREACHED */
423 case 0:
424 execvp(plan->e_argv[0], plan->e_argv);
425 warn("%s", plan->e_argv[0]);
426 _exit(1);
427 }
428 pid = waitpid(pid, &status, 0);
429 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
430}
431
432/*
433 * c_execdir --
434 * build three parallel arrays, one with pointers to the strings passed
435 * on the command line, one with (possibly duplicated) pointers to the
436 * argv array, and one with integer values that are lengths of the
437 * strings, but also flags meaning that the string has to be massaged.
438 */
439PLAN *
440c_execdir(argvp)
441 char ***argvp;
442{
443 PLAN *new; /* node returned */
444 register int cnt;
445 register char **argv, **ap, *p;
446
447 ftsoptions &= ~FTS_NOSTAT;
448 isoutput = 1;
449
450 new = palloc(N_EXECDIR, f_execdir);
451
452 for (ap = argv = *argvp;; ++ap) {
453 if (!*ap)
454 errx(1,
455 "-execdir: no terminating \";\"");
456 if (**ap == ';')
457 break;
458 }
459
460 cnt = ap - *argvp + 1;
461 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
462 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
463 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
464
465 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
466 new->e_orig[cnt] = *argv;
467 for (p = *argv; *p; ++p)
468 if (p[0] == '{' && p[1] == '}') {
469 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN);
470 new->e_len[cnt] = MAXPATHLEN;
471 break;
472 }
473 if (!*p) {
474 new->e_argv[cnt] = *argv;
475 new->e_len[cnt] = 0;
476 }
477 }
478 new->e_argv[cnt] = new->e_orig[cnt] = NULL;
479
480 *argvp = argv + 1;
481 return (new);
482}
483
484/*
485 * -follow functions --
486 *
487 * Always true, causes symbolic links to be followed on a global
488 * basis.
489 */
490PLAN *
491c_follow()
492{
493 ftsoptions &= ~FTS_PHYSICAL;
494 ftsoptions |= FTS_LOGICAL;
495
496 return (palloc(N_FOLLOW, f_always_true));
497}
498
499/*
500 * -fstype functions --
501 *
502 * True if the file is of a certain type.
503 */
504int
505f_fstype(plan, entry)
506 PLAN *plan;
507 FTSENT *entry;
508{
509 static dev_t curdev; /* need a guaranteed illegal dev value */
510 static int first = 1;
511 struct statfs sb;
512 static int val_type, val_flags;
513 char *p, save[2];
514
515 /* Only check when we cross mount point. */
516 if (first || curdev != entry->fts_statp->st_dev) {
517 curdev = entry->fts_statp->st_dev;
518
519 /*
520 * Statfs follows symlinks; find wants the link's file system,
521 * not where it points.
522 */
523 if (entry->fts_info == FTS_SL ||
524 entry->fts_info == FTS_SLNONE) {
525 if ((p = strrchr(entry->fts_accpath, '/')) != NULL)
526 ++p;
527 else
528 p = entry->fts_accpath;
529 save[0] = p[0];
530 p[0] = '.';
531 save[1] = p[1];
532 p[1] = '\0';
533
534 } else
535 p = NULL;
536
537 if (statfs(entry->fts_accpath, &sb))
538 err(1, "%s", entry->fts_accpath);
539
540 if (p) {
541 p[0] = save[0];
542 p[1] = save[1];
543 }
544
545 first = 0;
546
547 /*
548 * Further tests may need both of these values, so
549 * always copy both of them.
550 */
551 val_flags = sb.f_flags;
552 val_type = sb.f_type;
553 }
554 switch (plan->flags) {
555 case F_MTFLAG:
556 return (val_flags & plan->mt_data) != 0;
557 case F_MTTYPE:
558 return (val_type == plan->mt_data);
559 default:
560 abort();
561 }
562}
563
564#if !defined(__NetBSD__)
565PLAN *
566c_fstype(arg)
567 char *arg;
568{
569 register PLAN *new;
570 struct vfsconf vfc;
571
572 ftsoptions &= ~FTS_NOSTAT;
573
574 new = palloc(N_FSTYPE, f_fstype);
575
576 /*
577 * Check first for a filesystem name.
578 */
579 if (getvfsbyname(arg, &vfc) == 0) {
580 new->flags = F_MTTYPE;
581 new->mt_data = vfc.vfc_typenum;
582 return (new);
583 }
584
585 switch (*arg) {
586 case 'l':
587 if (!strcmp(arg, "local")) {
588 new->flags = F_MTFLAG;
589 new->mt_data = MNT_LOCAL;
590 return (new);
591 }
592 break;
593 case 'r':
594 if (!strcmp(arg, "rdonly")) {
595 new->flags = F_MTFLAG;
596 new->mt_data = MNT_RDONLY;
597 return (new);
598 }
599 break;
600 }
601 errx(1, "%s: unknown file type", arg);
602 /* NOTREACHED */
603}
604#endif
605
606/*
607 * -group gname functions --
608 *
609 * True if the file belongs to the group gname. If gname is numeric and
610 * an equivalent of the getgrnam() function does not return a valid group
611 * name, gname is taken as a group ID.
612 */
613int
614f_group(plan, entry)
615 PLAN *plan;
616 FTSENT *entry;
617{
618 return (entry->fts_statp->st_gid == plan->g_data);
619}
620
621PLAN *
622c_group(gname)
623 char *gname;
624{
625 PLAN *new;
626 struct group *g;
627 gid_t gid;
628
629 ftsoptions &= ~FTS_NOSTAT;
630
631 g = getgrnam(gname);
632 if (g == NULL) {
633 gid = atoi(gname);
634 if (gid == 0 && gname[0] != '0')
635 errx(1, "-group: %s: no such group", gname);
636 } else
637 gid = g->gr_gid;
638
639 new = palloc(N_GROUP, f_group);
640 new->g_data = gid;
641 return (new);
642}
643
644/*
645 * -inum n functions --
646 *
647 * True if the file has inode # n.
648 */
649int
650f_inum(plan, entry)
651 PLAN *plan;
652 FTSENT *entry;
653{
654 COMPARE(entry->fts_statp->st_ino, plan->i_data);
655}
656
657PLAN *
658c_inum(arg)
659 char *arg;
660{
661 PLAN *new;
662
663 ftsoptions &= ~FTS_NOSTAT;
664
665 new = palloc(N_INUM, f_inum);
666 new->i_data = find_parsenum(new, "-inum", arg, NULL);
667 return (new);
668}
669
670/*
671 * -links n functions --
672 *
673 * True if the file has n links.
674 */
675int
676f_links(plan, entry)
677 PLAN *plan;
678 FTSENT *entry;
679{
680 COMPARE(entry->fts_statp->st_nlink, plan->l_data);
681}
682
683PLAN *
684c_links(arg)
685 char *arg;
686{
687 PLAN *new;
688
689 ftsoptions &= ~FTS_NOSTAT;
690
691 new = palloc(N_LINKS, f_links);
692 new->l_data = (nlink_t)find_parsenum(new, "-links", arg, NULL);
693 return (new);
694}
695
696/*
697 * -ls functions --
698 *
699 * Always true - prints the current entry to stdout in "ls" format.
700 */
701int
702f_ls(plan, entry)
703 PLAN *plan;
704 FTSENT *entry;
705{
706 printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
707 return (1);
708}
709
710PLAN *
711c_ls()
712{
713 ftsoptions &= ~FTS_NOSTAT;
714 isoutput = 1;
715
716 return (palloc(N_LS, f_ls));
717}
718
719/*
720 * -mtime n functions --
721 *
722 * True if the difference between the file modification time and the
723 * current time is n 24 hour periods.
724 */
725int
726f_mtime(plan, entry)
727 PLAN *plan;
728 FTSENT *entry;
729{
730 extern time_t now;
731
732 COMPARE((now - entry->fts_statp->st_mtime + 86400 - 1) /
733 86400, plan->t_data);
734}
735
736PLAN *
737c_mtime(arg)
738 char *arg;
739{
740 PLAN *new;
741
742 ftsoptions &= ~FTS_NOSTAT;
743
744 new = palloc(N_MTIME, f_mtime);
745 new->t_data = find_parsenum(new, "-mtime", arg, NULL);
746 TIME_CORRECT(new, N_MTIME);
747 return (new);
748}
749
750/*
751 * -mmin n functions --
752 *
753 * True if the difference between the file modification time and the
754 * current time is n min periods.
755 */
756int
757f_mmin(plan, entry)
758 PLAN *plan;
759 FTSENT *entry;
760{
761 extern time_t now;
762
763 COMPARE((now - entry->fts_statp->st_mtime + 60 - 1) /
764 60, plan->t_data);
765}
766
767PLAN *
768c_mmin(arg)
769 char *arg;
770{
771 PLAN *new;
772
773 ftsoptions &= ~FTS_NOSTAT;
774
775 new = palloc(N_MMIN, f_mmin);
776 new->t_data = find_parsenum(new, "-mmin", arg, NULL);
777 TIME_CORRECT(new, N_MMIN);
778 return (new);
779}
780
781
782/*
783 * -name functions --
784 *
785 * True if the basename of the filename being examined
786 * matches pattern using Pattern Matching Notation S3.14
787 */
788int
789f_name(plan, entry)
790 PLAN *plan;
791 FTSENT *entry;
792{
793 return (!fnmatch(plan->c_data, entry->fts_name, 0));
794}
795
796PLAN *
797c_name(pattern)
798 char *pattern;
799{
800 PLAN *new;
801
802 new = palloc(N_NAME, f_name);
803 new->c_data = pattern;
804 return (new);
805}
806
807/*
808 * -newer file functions --
809 *
810 * True if the current file has been modified more recently
811 * then the modification time of the file named by the pathname
812 * file.
813 */
814int
815f_newer(plan, entry)
816 PLAN *plan;
817 FTSENT *entry;
818{
819 return (entry->fts_statp->st_mtime > plan->t_data);
820}
821
822PLAN *
823c_newer(filename)
824 char *filename;
825{
826 PLAN *new;
827 struct stat sb;
828
829 ftsoptions &= ~FTS_NOSTAT;
830
831 if (stat(filename, &sb))
832 err(1, "%s", filename);
833 new = palloc(N_NEWER, f_newer);
834 new->t_data = sb.st_mtime;
835 return (new);
836}
837
838/*
839 * -nogroup functions --
840 *
841 * True if file belongs to a user ID for which the equivalent
842 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
843 */
844int
845f_nogroup(plan, entry)
846 PLAN *plan;
847 FTSENT *entry;
848{
849 char *group_from_gid();
850
851 return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1);
852}
853
854PLAN *
855c_nogroup()
856{
857 ftsoptions &= ~FTS_NOSTAT;
858
859 return (palloc(N_NOGROUP, f_nogroup));
860}
861
862/*
863 * -nouser functions --
864 *
865 * True if file belongs to a user ID for which the equivalent
866 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
867 */
868int
869f_nouser(plan, entry)
870 PLAN *plan;
871 FTSENT *entry;
872{
873 char *user_from_uid();
874
875 return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1);
876}
877
878PLAN *
879c_nouser()
880{
881 ftsoptions &= ~FTS_NOSTAT;
882
883 return (palloc(N_NOUSER, f_nouser));
884}
885
886/*
887 * -path functions --
888 *
889 * True if the path of the filename being examined
890 * matches pattern using Pattern Matching Notation S3.14
891 */
892int
893f_path(plan, entry)
894 PLAN *plan;
895 FTSENT *entry;
896{
897 return (!fnmatch(plan->c_data, entry->fts_path, 0));
898}
899
900PLAN *
901c_path(pattern)
902 char *pattern;
903{
904 PLAN *new;
905
906 new = palloc(N_NAME, f_path);
907 new->c_data = pattern;
908 return (new);
909}
910
911/*
912 * -perm functions --
913 *
914 * The mode argument is used to represent file mode bits. If it starts
915 * with a leading digit, it's treated as an octal mode, otherwise as a
916 * symbolic mode.
917 */
918int
919f_perm(plan, entry)
920 PLAN *plan;
921 FTSENT *entry;
922{
923 mode_t mode;
924
925 mode = entry->fts_statp->st_mode &
926 (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
927 if (plan->flags == F_ATLEAST)
928 return ((plan->m_data | mode) == mode);
929 else if (plan->flags == F_ANY )
930 return (plan->m_data & mode);
931 else
932 return (mode == plan->m_data);
933 /* NOTREACHED */
934}
935
936PLAN *
937c_perm(perm)
938 char *perm;
939{
940 PLAN *new;
941 mode_t *set;
942
943 ftsoptions &= ~FTS_NOSTAT;
944
945 new = palloc(N_PERM, f_perm);
946
947 if (*perm == '-') {
948 new->flags = F_ATLEAST;
949 ++perm;
950 } else if (*perm == '+') {
951 new->flags = F_ANY;
952 ++perm;
953 }
954
955 if ((set = setmode(perm)) == NULL)
956 errx(1, "-perm: %s: illegal mode string", perm);
957
958 new->m_data = getmode(set, 0);
959 free(set);
960 return (new);
961}
962
963/*
964 * -flags functions --
965 *
966 * The flags argument is used to represent file flags bits.
967 */
968int
969f_flags(plan, entry)
970 PLAN *plan;
971 FTSENT *entry;
972{
973 u_long flags;
974
975 flags = entry->fts_statp->st_flags &
976 (UF_NODUMP | UF_IMMUTABLE | UF_APPEND | UF_OPAQUE |
977 SF_ARCHIVED | SF_IMMUTABLE | SF_APPEND);
978 if (plan->flags == F_ATLEAST)
979 /* note that plan->fl_flags always is a subset of
980 plan->fl_mask */
981 return (flags & plan->fl_mask) == plan->fl_flags;
982 else
983 return flags == plan->fl_flags;
984 /* NOTREACHED */
985}
986
987PLAN *
988c_flags(flags_str)
989 char *flags_str;
990{
991 PLAN *new;
992 u_long flags, notflags;
993
994 ftsoptions &= ~FTS_NOSTAT;
995
996 new = palloc(N_FLAGS, f_flags);
997
998 if (*flags_str == '-') {
999 new->flags = F_ATLEAST;
1000 flags_str++;
1001 }
1002 if (setflags(&flags_str, &flags, ¬flags) == 1)
1003 errx(1, "-flags: %s: illegal flags string", flags_str);
1004
1005 new->fl_flags = flags;
1006 new->fl_mask = flags | notflags;
1007#if 0
1008 printf("flags = %08x, mask = %08x (%08x, %08x)\n",
1009 new->fl_flags, new->fl_mask, flags, notflags);
1010#endif
1011 return new;
1012}
1013
1014 /*
1015
1016
1017/*
1018 * -print functions --
1019 *
1020 * Always true, causes the current pathame to be written to
1021 * standard output.
1022 */
1023int
1024f_print(plan, entry)
1025 PLAN *plan;
1026 FTSENT *entry;
1027{
1028 (void)puts(entry->fts_path);
1029 return (1);
1030}
1031
1032PLAN *
1033c_print()
1034{
1035 isoutput = 1;
1036
1037 return (palloc(N_PRINT, f_print));
1038}
1039
1040/*
1041 * -print0 functions --
1042 *
1043 * Always true, causes the current pathame to be written to
1044 * standard output followed by a NUL character
1045 */
1046int
1047f_print0(plan, entry)
1048 PLAN *plan;
1049 FTSENT *entry;
1050{
1051 fputs(entry->fts_path, stdout);
1052 fputc('\0', stdout);
1053 return (1);
1054}
1055
1056PLAN *
1057c_print0()
1058{
1059 isoutput = 1;
1060
1061 return (palloc(N_PRINT0, f_print0));
1062}
1063
1064/*
1065 * -prune functions --
1066 *
1067 * Prune a portion of the hierarchy.
1068 */
1069int
1070f_prune(plan, entry)
1071 PLAN *plan;
1072 FTSENT *entry;
1073{
1074 extern FTS *tree;
1075
1076 if (fts_set(tree, entry, FTS_SKIP))
1077 err(1, "%s", entry->fts_path);
1078 return (1);
1079}
1080
1081PLAN *
1082c_prune()
1083{
1084 return (palloc(N_PRUNE, f_prune));
1085}
1086
1087/*
1088 * -size n[c] functions --
1089 *
1090 * True if the file size in bytes, divided by an implementation defined
1091 * value and rounded up to the next integer, is n. If n is followed by
1092 * a c, the size is in bytes.
1093 */
1094#define FIND_SIZE 512
1095static int divsize = 1;
1096
1097int
1098f_size(plan, entry)
1099 PLAN *plan;
1100 FTSENT *entry;
1101{
1102 off_t size;
1103
1104 size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
1105 FIND_SIZE : entry->fts_statp->st_size;
1106 COMPARE(size, plan->o_data);
1107}
1108
1109PLAN *
1110c_size(arg)
1111 char *arg;
1112{
1113 PLAN *new;
1114 char endch;
1115
1116 ftsoptions &= ~FTS_NOSTAT;
1117
1118 new = palloc(N_SIZE, f_size);
1119 endch = 'c';
1120 new->o_data = find_parsenum(new, "-size", arg, &endch);
1121 if (endch == 'c')
1122 divsize = 0;
1123 return (new);
1124}
1125
1126/*
1127 * -type c functions --
1128 *
1129 * True if the type of the file is c, where c is b, c, d, p, f or w
1130 * for block special file, character special file, directory, FIFO,
1131 * regular file or whiteout respectively.
1132 */
1133int
1134f_type(plan, entry)
1135 PLAN *plan;
1136 FTSENT *entry;
1137{
1138 return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data);
1139}
1140
1141PLAN *
1142c_type(typestring)
1143 char *typestring;
1144{
1145 PLAN *new;
1146 mode_t mask;
1147
1148 ftsoptions &= ~FTS_NOSTAT;
1149
1150 switch (typestring[0]) {
1151 case 'b':
1152 mask = S_IFBLK;
1153 break;
1154 case 'c':
1155 mask = S_IFCHR;
1156 break;
1157 case 'd':
1158 mask = S_IFDIR;
1159 break;
1160 case 'f':
1161 mask = S_IFREG;
1162 break;
1163 case 'l':
1164 mask = S_IFLNK;
1165 break;
1166 case 'p':
1167 mask = S_IFIFO;
1168 break;
1169 case 's':
1170 mask = S_IFSOCK;
1171 break;
1172#ifdef FTS_WHITEOUT
1173 case 'w':
1174 mask = S_IFWHT;
1175 ftsoptions |= FTS_WHITEOUT;
1176 break;
1177#endif /* FTS_WHITEOUT */
1178 default:
1179 errx(1, "-type: %s: unknown type", typestring);
1180 }
1181
1182 new = palloc(N_TYPE, f_type);
1183 new->m_data = mask;
1184 return (new);
1185}
1186
1187/*
1188 * -delete functions --
1189 *
1190 * True always. Makes it's best shot and continues on regardless.
1191 */
1192int
1193f_delete(plan, entry)
1194 PLAN *plan;
1195 FTSENT *entry;
1196{
1197 /* ignore these from fts */
1198 if (strcmp(entry->fts_accpath, ".") == 0 ||
1199 strcmp(entry->fts_accpath, "..") == 0)
1200 return (1);
1201
1202 /* sanity check */
1203 if (isdepth == 0 || /* depth off */
1204 (ftsoptions & FTS_NOSTAT) || /* not stat()ing */
1205 !(ftsoptions & FTS_PHYSICAL) || /* physical off */
1206 (ftsoptions & FTS_LOGICAL)) /* or finally, logical on */
1207 errx(1, "-delete: insecure options got turned on");
1208
1209 /* Potentially unsafe - do not accept relative paths whatsoever */
1210 if (strchr(entry->fts_accpath, '/') != NULL)
1211 errx(1, "-delete: %s: relative path potentially not safe",
1212 entry->fts_accpath);
1213
1214 /* Turn off user immutable bits if running as root */
1215 if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
1216 !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
1217 geteuid() == 0)
1218 chflags(entry->fts_accpath,
1219 entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
1220
1221 /* rmdir directories, unlink everything else */
1222 if (S_ISDIR(entry->fts_statp->st_mode)) {
1223 if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY)
1224 warn("-delete: rmdir(%s)", entry->fts_path);
1225 } else {
1226 if (unlink(entry->fts_accpath) < 0)
1227 warn("-delete: unlink(%s)", entry->fts_path);
1228 }
1229
1230 /* "succeed" */
1231 return (1);
1232}
1233
1234PLAN *
1235c_delete()
1236{
1237
1238 ftsoptions &= ~FTS_NOSTAT; /* no optimise */
1239 ftsoptions |= FTS_PHYSICAL; /* disable -follow */
1240 ftsoptions &= ~FTS_LOGICAL; /* disable -follow */
1241 isoutput = 1; /* possible output */
1242 isdepth = 1; /* -depth implied */
1243
1244 return (palloc(N_DELETE, f_delete));
1245}
1246
1247/*
1248 * -user uname functions --
1249 *
1250 * True if the file belongs to the user uname. If uname is numeric and
1251 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1252 * return a valid user name, uname is taken as a user ID.
1253 */
1254int
1255f_user(plan, entry)
1256 PLAN *plan;
1257 FTSENT *entry;
1258{
1259 return (entry->fts_statp->st_uid == plan->u_data);
1260}
1261
1262PLAN *
1263c_user(username)
1264 char *username;
1265{
1266 PLAN *new;
1267 struct passwd *p;
1268 uid_t uid;
1269
1270 ftsoptions &= ~FTS_NOSTAT;
1271
1272 p = getpwnam(username);
1273 if (p == NULL) {
1274 uid = atoi(username);
1275 if (uid == 0 && username[0] != '0')
1276 errx(1, "-user: %s: no such user", username);
1277 } else
1278 uid = p->pw_uid;
1279
1280 new = palloc(N_USER, f_user);
1281 new->u_data = uid;
1282 return (new);
1283}
1284
1285/*
1286 * -xdev functions --
1287 *
1288 * Always true, causes find not to decend past directories that have a
1289 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1290 */
1291PLAN *
1292c_xdev()
1293{
1294 ftsoptions |= FTS_XDEV;
1295
1296 return (palloc(N_XDEV, f_always_true));
1297}
1298
1299/*
1300 * ( expression ) functions --
1301 *
1302 * True if expression is true.
1303 */
1304int
1305f_expr(plan, entry)
1306 PLAN *plan;
1307 FTSENT *entry;
1308{
1309 register PLAN *p;
1310 register int state;
1311
1312 state = 0;
1313 for (p = plan->p_data[0];
1314 p && (state = (p->eval)(p, entry)); p = p->next);
1315 return (state);
1316}
1317
1318/*
1319 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are
1320 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1321 * to a N_EXPR node containing the expression and the ')' node is discarded.
1322 */
1323PLAN *
1324c_openparen()
1325{
1326 return (palloc(N_OPENPAREN, (int (*)())-1));
1327}
1328
1329PLAN *
1330c_closeparen()
1331{
1332 return (palloc(N_CLOSEPAREN, (int (*)())-1));
1333}
1334
1335/*
1336 * ! expression functions --
1337 *
1338 * Negation of a primary; the unary NOT operator.
1339 */
1340int
1341f_not(plan, entry)
1342 PLAN *plan;
1343 FTSENT *entry;
1344{
1345 register PLAN *p;
1346 register int state;
1347
1348 state = 0;
1349 for (p = plan->p_data[0];
1350 p && (state = (p->eval)(p, entry)); p = p->next);
1351 return (!state);
1352}
1353
1354PLAN *
1355c_not()
1356{
1357 return (palloc(N_NOT, f_not));
1358}
1359
1360/*
1361 * expression -o expression functions --
1362 *
1363 * Alternation of primaries; the OR operator. The second expression is
1364 * not evaluated if the first expression is true.
1365 */
1366int
1367f_or(plan, entry)
1368 PLAN *plan;
1369 FTSENT *entry;
1370{
1371 register PLAN *p;
1372 register int state;
1373
1374 state = 0;
1375 for (p = plan->p_data[0];
1376 p && (state = (p->eval)(p, entry)); p = p->next);
1377
1378 if (state)
1379 return (1);
1380
1381 for (p = plan->p_data[1];
1382 p && (state = (p->eval)(p, entry)); p = p->next);
1383 return (state);
1384}
1385
1386PLAN *
1387c_or()
1388{
1389 return (palloc(N_OR, f_or));
1390}
1391
1392static PLAN *
1393palloc(t, f)
1394 enum ntype t;
1395 int (*f) __P((PLAN *, FTSENT *));
1396{
1397 PLAN *new;
1398
1399 if ((new = malloc(sizeof(PLAN))) == NULL)
1400 err(1, NULL);
1401 new->type = t;
1402 new->eval = f;
1403 new->flags = 0;
1404 new->next = NULL;
1405 return (new);
1406}