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