1 /* $NetBSD: function.c,v 1.72 2013/05/04 06:29:32 uebayasi Exp $ */
4 * Copyright (c) 1990, 1993
5 * The Regents of the University of California. All rights reserved.
7 * This code is derived from software contributed to Berkeley by
8 * Cimarron D. Taylor of the University of California, Berkeley.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 #include <sys/cdefs.h>
38 static char sccsid
[] = "from: @(#)function.c 8.10 (Berkeley) 5/4/95";
40 __RCSID("$NetBSD: function.c,v 1.72 2013/05/04 06:29:32 uebayasi Exp $");
44 #include <sys/param.h>
47 #include <sys/mount.h>
68 #define COMPARE(a, b) { \
69 switch (plan->flags) { \
81 static int64_t find_parsenum(PLAN
*, const char *, const char *, char *);
82 static void run_f_exec(PLAN
*);
83 int f_always_true(PLAN
*, FTSENT
*);
84 int f_amin(PLAN
*, FTSENT
*);
85 int f_anewer(PLAN
*, FTSENT
*);
86 int f_atime(PLAN
*, FTSENT
*);
87 int f_cmin(PLAN
*, FTSENT
*);
88 int f_cnewer(PLAN
*, FTSENT
*);
89 int f_ctime(PLAN
*, FTSENT
*);
90 int f_delete(PLAN
*, FTSENT
*);
91 int f_empty(PLAN
*, FTSENT
*);
92 int f_exec(PLAN
*, FTSENT
*);
93 int f_execdir(PLAN
*, FTSENT
*);
94 int f_false(PLAN
*, FTSENT
*);
95 int f_flags(PLAN
*, FTSENT
*);
96 int f_fprint(PLAN
*, FTSENT
*);
97 int f_fstype(PLAN
*, FTSENT
*);
98 int f_group(PLAN
*, FTSENT
*);
99 int f_iname(PLAN
*, FTSENT
*);
100 int f_inum(PLAN
*, FTSENT
*);
101 int f_links(PLAN
*, FTSENT
*);
102 int f_ls(PLAN
*, FTSENT
*);
103 int f_mindepth(PLAN
*, FTSENT
*);
104 int f_maxdepth(PLAN
*, FTSENT
*);
105 int f_mmin(PLAN
*, FTSENT
*);
106 int f_mtime(PLAN
*, FTSENT
*);
107 int f_name(PLAN
*, FTSENT
*);
108 int f_newer(PLAN
*, FTSENT
*);
109 int f_nogroup(PLAN
*, FTSENT
*);
110 int f_nouser(PLAN
*, FTSENT
*);
111 int f_path(PLAN
*, FTSENT
*);
112 int f_perm(PLAN
*, FTSENT
*);
113 int f_print(PLAN
*, FTSENT
*);
114 int f_print0(PLAN
*, FTSENT
*);
115 int f_printx(PLAN
*, FTSENT
*);
116 int f_prune(PLAN
*, FTSENT
*);
117 int f_regex(PLAN
*, FTSENT
*);
118 int f_size(PLAN
*, FTSENT
*);
119 int f_type(PLAN
*, FTSENT
*);
120 int f_user(PLAN
*, FTSENT
*);
121 int f_not(PLAN
*, FTSENT
*);
122 int f_or(PLAN
*, FTSENT
*);
123 static PLAN
*c_regex_common(char ***, int, enum ntype
, bool);
124 static PLAN
*palloc(enum ntype
, int (*)(PLAN
*, FTSENT
*));
132 * Parse a string of the form [+-]# and return the value.
135 find_parsenum(PLAN
*plan
, const char *option
, const char *vp
, char *endch
)
139 char *endchar
; /* Pointer to character ending conversion. */
141 /* Determine comparison from leading + or -. */
146 plan
->flags
= F_GREATER
;
150 plan
->flags
= F_LESSTHAN
;
153 plan
->flags
= F_EQUAL
;
158 * Convert the string with strtol(). Note, if strtol() returns zero
159 * and endchar points to the beginning of the string we know we have
162 value
= strtoq(str
, &endchar
, 10);
163 if (value
== 0 && endchar
== str
)
164 errx(1, "%s: %s: illegal numeric value", option
, vp
);
165 if (endchar
[0] && (endch
== NULL
|| endchar
[0] != *endch
))
166 errx(1, "%s: %s: illegal trailing character", option
, vp
);
173 * The value of n for the inode times (atime, ctime, and mtime) is a range,
174 * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with
175 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
176 * user wanted. Correct so that -1 is "less than 1".
178 #define TIME_CORRECT(p, ttype) \
179 if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \
183 * -amin n functions --
185 * True if the difference between the file access time and the
186 * current time is n 1 minute periods.
189 f_amin(PLAN
*plan
, FTSENT
*entry
)
191 COMPARE((now
- entry
->fts_statp
->st_atime
+
192 SECSPERMIN
- 1) / SECSPERMIN
, plan
->t_data
);
196 c_amin(char ***argvp
, int isok
)
202 ftsoptions
&= ~FTS_NOSTAT
;
204 new = palloc(N_AMIN
, f_amin
);
205 new->t_data
= find_parsenum(new, "-amin", arg
, NULL
);
206 TIME_CORRECT(new, N_AMIN
);
211 * -anewer file functions --
213 * True if the current file has been accessed more recently
214 * than the access time of the file named by the pathname
218 f_anewer(PLAN
*plan
, FTSENT
*entry
)
221 return timespeccmp(&entry
->fts_statp
->st_atim
, &plan
->ts_data
, >);
225 c_anewer(char ***argvp
, int isok
)
227 char *filename
= **argvp
;
232 ftsoptions
&= ~FTS_NOSTAT
;
234 if (stat(filename
, &sb
))
235 err(1, "%s", filename
);
236 new = palloc(N_ANEWER
, f_anewer
);
237 new->ts_data
= sb
.st_atim
;
242 * -atime n functions --
244 * True if the difference between the file access time and the
245 * current time is n 24 hour periods.
248 f_atime(PLAN
*plan
, FTSENT
*entry
)
250 COMPARE((now
- entry
->fts_statp
->st_atime
+
251 SECSPERDAY
- 1) / SECSPERDAY
, plan
->t_data
);
255 c_atime(char ***argvp
, int isok
)
261 ftsoptions
&= ~FTS_NOSTAT
;
263 new = palloc(N_ATIME
, f_atime
);
264 new->t_data
= find_parsenum(new, "-atime", arg
, NULL
);
265 TIME_CORRECT(new, N_ATIME
);
270 * -cmin n functions --
272 * True if the difference between the last change of file
273 * status information and the current time is n 24 hour periods.
276 f_cmin(PLAN
*plan
, FTSENT
*entry
)
278 COMPARE((now
- entry
->fts_statp
->st_ctime
+
279 SECSPERMIN
- 1) / SECSPERMIN
, plan
->t_data
);
283 c_cmin(char ***argvp
, int isok
)
289 ftsoptions
&= ~FTS_NOSTAT
;
291 new = palloc(N_CMIN
, f_cmin
);
292 new->t_data
= find_parsenum(new, "-cmin", arg
, NULL
);
293 TIME_CORRECT(new, N_CMIN
);
298 * -cnewer file functions --
300 * True if the current file has been changed more recently
301 * than the changed time of the file named by the pathname
305 f_cnewer(PLAN
*plan
, FTSENT
*entry
)
308 return timespeccmp(&entry
->fts_statp
->st_ctim
, &plan
->ts_data
, >);
312 c_cnewer(char ***argvp
, int isok
)
314 char *filename
= **argvp
;
319 ftsoptions
&= ~FTS_NOSTAT
;
321 if (stat(filename
, &sb
))
322 err(1, "%s", filename
);
323 new = palloc(N_CNEWER
, f_cnewer
);
324 new->ts_data
= sb
.st_ctim
;
329 * -ctime n functions --
331 * True if the difference between the last change of file
332 * status information and the current time is n 24 hour periods.
335 f_ctime(PLAN
*plan
, FTSENT
*entry
)
337 COMPARE((now
- entry
->fts_statp
->st_ctime
+
338 SECSPERDAY
- 1) / SECSPERDAY
, plan
->t_data
);
342 c_ctime(char ***argvp
, int isok
)
348 ftsoptions
&= ~FTS_NOSTAT
;
350 new = palloc(N_CTIME
, f_ctime
);
351 new->t_data
= find_parsenum(new, "-ctime", arg
, NULL
);
352 TIME_CORRECT(new, N_CTIME
);
357 * -delete functions --
359 * Always true. Makes its best shot and continues on regardless.
362 f_delete(PLAN
*plan __unused
, FTSENT
*entry
)
364 /* ignore these from fts */
365 if (strcmp(entry
->fts_accpath
, ".") == 0 ||
366 strcmp(entry
->fts_accpath
, "..") == 0)
370 if (isdepth
== 0 || /* depth off */
371 (ftsoptions
& FTS_NOSTAT
) || /* not stat()ing */
372 !(ftsoptions
& FTS_PHYSICAL
) || /* physical off */
373 (ftsoptions
& FTS_LOGICAL
)) /* or finally, logical on */
374 errx(1, "-delete: insecure options got turned on");
376 /* Potentially unsafe - do not accept relative paths whatsoever */
377 if (strchr(entry
->fts_accpath
, '/') != NULL
)
378 errx(1, "-delete: %s: relative path potentially not safe",
381 #if !defined(__minix)
382 /* Turn off user immutable bits if running as root */
383 if ((entry
->fts_statp
->st_flags
& (UF_APPEND
|UF_IMMUTABLE
)) &&
384 !(entry
->fts_statp
->st_flags
& (SF_APPEND
|SF_IMMUTABLE
)) &&
386 chflags(entry
->fts_accpath
,
387 entry
->fts_statp
->st_flags
&= ~(UF_APPEND
|UF_IMMUTABLE
));
388 #endif /* !defined(__minix) */
390 /* rmdir directories, unlink everything else */
391 if (S_ISDIR(entry
->fts_statp
->st_mode
)) {
392 if (rmdir(entry
->fts_accpath
) < 0 && errno
!= ENOTEMPTY
)
393 warn("-delete: rmdir(%s)", entry
->fts_path
);
395 if (unlink(entry
->fts_accpath
) < 0)
396 warn("-delete: unlink(%s)", entry
->fts_path
);
404 c_delete(char ***argvp __unused
, int isok
)
407 ftsoptions
&= ~FTS_NOSTAT
; /* no optimize */
408 ftsoptions
|= FTS_PHYSICAL
; /* disable -follow */
409 ftsoptions
&= ~FTS_LOGICAL
; /* disable -follow */
410 isoutput
= 1; /* possible output */
411 isdepth
= 1; /* -depth implied */
413 return palloc(N_DELETE
, f_delete
);
417 * -depth functions --
419 * Always true, causes descent of the directory hierarchy to be done
420 * so that all entries in a directory are acted on before the directory
424 f_always_true(PLAN
*plan
, FTSENT
*entry
)
431 c_depth(char ***argvp
, int isok
)
435 return (palloc(N_DEPTH
, f_always_true
));
439 * -empty functions --
441 * True if the file or directory is empty
444 f_empty(PLAN
*plan
, FTSENT
*entry
)
446 if (S_ISREG(entry
->fts_statp
->st_mode
) &&
447 entry
->fts_statp
->st_size
== 0)
449 if (S_ISDIR(entry
->fts_statp
->st_mode
)) {
455 dir
= opendir(entry
->fts_accpath
);
458 for (dp
= readdir(dir
); dp
; dp
= readdir(dir
))
459 if (dp
->d_name
[0] != '.' ||
460 (dp
->d_name
[1] != '\0' &&
461 (dp
->d_name
[1] != '.' || dp
->d_name
[2] != '\0'))) {
472 c_empty(char ***argvp
, int isok
)
474 ftsoptions
&= ~FTS_NOSTAT
;
476 return (palloc(N_EMPTY
, f_empty
));
480 * [-exec | -ok] utility [arg ... ] ; functions --
481 * [-exec | -ok] utility [arg ... ] {} + functions --
483 * If the end of the primary expression is delimited by a
484 * semicolon: true if the executed utility returns a zero value
485 * as exit status. If "{}" occurs anywhere, it gets replaced by
486 * the current pathname.
488 * If the end of the primary expression is delimited by a plus
489 * sign: always true. Pathnames for which the primary is
490 * evaluated shall be aggregated into sets. The utility will be
491 * executed once per set, with "{}" replaced by the entire set of
492 * pathnames (as if xargs). "{}" must appear last.
494 * The current directory for the execution of utility is the same
495 * as the current directory when the find utility was started.
497 * The primary -ok is different in that it requests affirmation
498 * of the user before executing the utility.
501 f_exec(PLAN
*plan
, FTSENT
*entry
)
508 if (plan
->flags
& F_PLUSSET
) {
510 * Confirm sufficient buffer space, then copy the path
513 l
= strlen(entry
->fts_path
);
514 if (plan
->ep_p
+ l
< plan
->ep_ebp
) {
515 plan
->ep_bxp
[plan
->ep_narg
++] =
516 strcpy(plan
->ep_p
, entry
->fts_path
);
519 if (plan
->ep_narg
== plan
->ep_maxargs
)
523 * Without sufficient space to copy in the next
524 * argument, run the command to empty out the
525 * buffer before re-attepting the copy.
528 if ((plan
->ep_p
+ l
< plan
->ep_ebp
)) {
529 plan
->ep_bxp
[plan
->ep_narg
++]
530 = strcpy(plan
->ep_p
, entry
->fts_path
);
533 errx(1, "insufficient space for argument");
537 for (cnt
= 0; plan
->e_argv
[cnt
]; ++cnt
)
538 if (plan
->e_len
[cnt
])
539 brace_subst(plan
->e_orig
[cnt
],
543 if (plan
->flags
& F_NEEDOK
&& !queryuser(plan
->e_argv
))
546 /* Don't mix output of command with find output. */
550 switch (pid
= vfork()) {
559 execvp(plan
->e_argv
[0], plan
->e_argv
);
560 warn("%s", plan
->e_argv
[0]);
563 pid
= waitpid(pid
, &status
, 0);
564 return (pid
!= -1 && WIFEXITED(status
)
565 && !WEXITSTATUS(status
));
570 run_f_exec(PLAN
*plan
)
575 /* Ensure arg list is null terminated. */
576 plan
->ep_bxp
[plan
->ep_narg
] = NULL
;
578 /* Don't mix output of command with find output. */
582 switch (pid
= vfork()) {
591 execvp(plan
->e_argv
[0], plan
->e_argv
);
592 warn("%s", plan
->e_argv
[0]);
596 /* Clear out the argument list. */
598 plan
->ep_bxp
[plan
->ep_narg
] = NULL
;
599 /* As well as the argument buffer. */
600 plan
->ep_p
= plan
->ep_bbp
;
603 pid
= waitpid(pid
, &status
, 0);
604 if (WIFEXITED(status
))
605 rval
= WEXITSTATUS(status
);
610 * If we have a non-zero exit status, preserve it so find(1) can
611 * later exit with it.
614 plan
->ep_rval
= rval
;
619 * build three parallel arrays, one with pointers to the strings passed
620 * on the command line, one with (possibly duplicated) pointers to the
621 * argv array, and one with integer values that are lengths of the
622 * strings, but also flags meaning that the string has to be massaged.
624 * If -exec ... {} +, use only the first array, but make it large
625 * enough to hold 5000 args (cf. src/usr.bin/xargs/xargs.c for a
626 * discussion), and then allocate ARG_MAX - 4K of space for args.
629 c_exec(char ***argvp
, int isok
)
631 PLAN
*new; /* node returned */
633 int brace
, lastbrace
;
634 char **argv
, **ap
, *p
;
638 new = palloc(N_EXEC
, f_exec
);
640 new->flags
|= F_NEEDOK
;
643 * Terminate if we encounter an arg exactly equal to ";", or an
644 * arg exactly equal to "+" following an arg exactly equal to
647 for (ap
= argv
= *argvp
, brace
= 0;; ++ap
) {
649 errx(1, "%s: no terminating \";\" or \"+\"",
650 isok
? "-ok" : "-exec");
653 if (strcmp(*ap
, "{}") == 0)
655 if (strcmp(*ap
, ";") == 0)
657 if (strcmp(*ap
, "+") == 0 && lastbrace
) {
658 new->flags
|= F_PLUSSET
;
664 * POSIX says -ok ... {} + "need not be supported," and it does
665 * not make much sense anyway.
667 if (new->flags
& F_NEEDOK
&& new->flags
& F_PLUSSET
)
668 errx(1, "-ok: terminating \"+\" not permitted.");
670 if (new->flags
& F_PLUSSET
) {
673 cnt
= ap
- *argvp
- 1; /* units are words */
674 new->ep_maxargs
= 5000;
675 new->e_argv
= emalloc((cnt
+ new->ep_maxargs
)
676 * sizeof(*new->e_argv
));
678 /* We start stuffing arguments after the user's last one. */
679 new->ep_bxp
= &new->e_argv
[cnt
];
683 * Count up the space of the user's arguments, and
684 * subtract that from what we allocate.
686 #define MAXARG (ARG_MAX - 4 * 1024)
687 for (argv
= *argvp
, c
= 0, cnt
= 0;
690 c
+= strlen(*argv
) + 1;
692 errx(1, "Arguments too long");
693 new->e_argv
[cnt
] = *argv
;
695 bufsize
= MAXARG
- c
;
698 * Allocate, and then initialize current, base, and
701 new->ep_p
= new->ep_bbp
= emalloc(bufsize
+ 1);
702 new->ep_ebp
= new->ep_bbp
+ bufsize
- 1;
704 } else { /* !F_PLUSSET */
705 cnt
= ap
- *argvp
+ 1;
706 new->e_argv
= emalloc(cnt
* sizeof(*new->e_argv
));
707 new->e_orig
= emalloc(cnt
* sizeof(*new->e_orig
));
708 new->e_len
= emalloc(cnt
* sizeof(*new->e_len
));
710 for (argv
= *argvp
, cnt
= 0; argv
< ap
; ++argv
, ++cnt
) {
711 new->e_orig
[cnt
] = *argv
;
712 for (p
= *argv
; *p
; ++p
)
713 if (p
[0] == '{' && p
[1] == '}') {
716 new->e_len
[cnt
] = MAXPATHLEN
;
720 new->e_argv
[cnt
] = *argv
;
724 new->e_orig
[cnt
] = NULL
;
727 new->e_argv
[cnt
] = NULL
;
733 * -execdir utility [arg ... ] ; functions --
735 * True if the executed utility returns a zero value as exit status.
736 * The end of the primary expression is delimited by a semicolon. If
737 * "{}" occurs anywhere, it gets replaced by the unqualified pathname.
738 * The current directory for the execution of utility is the same as
739 * the directory where the file lives.
742 f_execdir(PLAN
*plan
, FTSENT
*entry
)
749 /* XXX - if file/dir ends in '/' this will not work -- can it? */
750 if ((file
= strrchr(entry
->fts_path
, '/')))
753 file
= entry
->fts_path
;
755 for (cnt
= 0; plan
->e_argv
[cnt
]; ++cnt
)
756 if (plan
->e_len
[cnt
])
757 brace_subst(plan
->e_orig
[cnt
], &plan
->e_argv
[cnt
],
758 file
, &plan
->e_len
[cnt
]);
760 /* don't mix output of command with find output */
764 switch (pid
= vfork()) {
769 execvp(plan
->e_argv
[0], plan
->e_argv
);
770 warn("%s", plan
->e_argv
[0]);
773 pid
= waitpid(pid
, &status
, 0);
774 return (pid
!= -1 && WIFEXITED(status
) && !WEXITSTATUS(status
));
779 * build three parallel arrays, one with pointers to the strings passed
780 * on the command line, one with (possibly duplicated) pointers to the
781 * argv array, and one with integer values that are lengths of the
782 * strings, but also flags meaning that the string has to be massaged.
785 c_execdir(char ***argvp
, int isok
)
787 PLAN
*new; /* node returned */
789 char **argv
, **ap
, *p
;
791 ftsoptions
&= ~FTS_NOSTAT
;
794 new = palloc(N_EXECDIR
, f_execdir
);
796 for (ap
= argv
= *argvp
;; ++ap
) {
799 "-execdir: no terminating \";\"");
804 cnt
= ap
- *argvp
+ 1;
805 new->e_argv
= emalloc(cnt
* sizeof(*new->e_argv
));
806 new->e_orig
= emalloc(cnt
* sizeof(*new->e_orig
));
807 new->e_len
= emalloc(cnt
* sizeof(*new->e_len
));
809 for (argv
= *argvp
, cnt
= 0; argv
< ap
; ++argv
, ++cnt
) {
810 new->e_orig
[cnt
] = *argv
;
811 for (p
= *argv
; *p
; ++p
)
812 if (p
[0] == '{' && p
[1] == '}') {
813 new->e_argv
[cnt
] = emalloc(MAXPATHLEN
);
814 new->e_len
[cnt
] = MAXPATHLEN
;
818 new->e_argv
[cnt
] = *argv
;
822 new->e_argv
[cnt
] = new->e_orig
[cnt
] = NULL
;
829 c_exit(char ***argvp
, int isok
)
834 /* not technically true, but otherwise '-print' is implied */
837 new = palloc(N_EXIT
, f_always_true
);
841 new->exit_val
= find_parsenum(new, "-exit", arg
, NULL
);
853 f_false(PLAN
*plan
, FTSENT
*entry
)
860 c_false(char ***argvp
, int isok
)
862 return (palloc(N_FALSE
, f_false
));
867 * -flags [-]flags functions --
870 f_flags(PLAN
*plan
, FTSENT
*entry
)
874 flags
= entry
->fts_statp
->st_flags
;
875 if (plan
->flags
== F_ATLEAST
)
876 return ((plan
->f_data
| flags
) == flags
);
878 return (flags
== plan
->f_data
);
883 c_flags(char ***argvp
, int isok
)
885 char *flags
= **argvp
;
890 ftsoptions
&= ~FTS_NOSTAT
;
892 new = palloc(N_FLAGS
, f_flags
);
895 new->flags
= F_ATLEAST
;
900 if ((strcmp(flags
, "none") != 0) &&
901 (string_to_flags(&flags
, &flagset
, NULL
) != 0))
902 errx(1, "-flags: %s: illegal flags string", flags
);
903 new->f_data
= flagset
;
908 * -follow functions --
910 * Always true, causes symbolic links to be followed on a global
914 c_follow(char ***argvp
, int isok
)
916 ftsoptions
&= ~FTS_PHYSICAL
;
917 ftsoptions
|= FTS_LOGICAL
;
919 return (palloc(N_FOLLOW
, f_always_true
));
922 /* -fprint functions --
924 * Causes the current pathame to be written to the defined output file.
927 f_fprint(PLAN
*plan
, FTSENT
*entry
)
930 if (-1 == fprintf(plan
->fprint_file
, "%s\n", entry
->fts_path
))
935 /* no descriptors are closed; they will be closed by
936 operating system when this find command exits. */
940 c_fprint(char ***argvp
, int isok
)
944 isoutput
= 1; /* do not assume -print */
946 new = palloc(N_FPRINT
, f_fprint
);
948 if (NULL
== (new->fprint_file
= fopen(**argvp
, "w")))
949 err(1, "-fprint: %s: cannot create file", **argvp
);
956 * -fstype functions --
958 * True if the file is of a certain type.
961 f_fstype(PLAN
*plan
, FTSENT
*entry
)
963 static dev_t curdev
; /* need a guaranteed illegal dev value */
964 static int first
= 1;
967 static char fstype
[sizeof(sb
.f_fstypename
)];
970 memset(&save
, 0, sizeof save
); /* XXX gcc */
972 /* Only check when we cross mount point. */
973 if (first
|| curdev
!= entry
->fts_statp
->st_dev
) {
974 curdev
= entry
->fts_statp
->st_dev
;
977 * Statfs follows symlinks; find wants the link's file system,
978 * not where it points.
980 if (entry
->fts_info
== FTS_SL
||
981 entry
->fts_info
== FTS_SLNONE
) {
982 if ((p
= strrchr(entry
->fts_accpath
, '/')) != NULL
)
985 p
= entry
->fts_accpath
;
994 if (statvfs(entry
->fts_accpath
, &sb
))
995 err(1, "%s", entry
->fts_accpath
);
1005 * Further tests may need both of these values, so
1006 * always copy both of them.
1009 strlcpy(fstype
, sb
.f_fstypename
, sizeof(fstype
));
1011 switch (plan
->flags
) {
1013 return (val
& plan
->mt_data
);
1015 return (strncmp(fstype
, plan
->c_data
, sizeof(fstype
)) == 0);
1022 c_fstype(char ***argvp
, int isok
)
1024 char *arg
= **argvp
;
1028 ftsoptions
&= ~FTS_NOSTAT
;
1030 new = palloc(N_FSTYPE
, f_fstype
);
1034 if (!strcmp(arg
, "local")) {
1035 new->flags
= F_MTFLAG
;
1036 new->mt_data
= MNT_LOCAL
;
1041 if (!strcmp(arg
, "rdonly")) {
1042 new->flags
= F_MTFLAG
;
1043 new->mt_data
= MNT_RDONLY
;
1049 new->flags
= F_MTTYPE
;
1055 * -group gname functions --
1057 * True if the file belongs to the group gname. If gname is numeric and
1058 * an equivalent of the getgrnam() function does not return a valid group
1059 * name, gname is taken as a group ID.
1062 f_group(PLAN
*plan
, FTSENT
*entry
)
1065 return (entry
->fts_statp
->st_gid
== plan
->g_data
);
1069 c_group(char ***argvp
, int isok
)
1071 char *gname
= **argvp
;
1077 ftsoptions
&= ~FTS_NOSTAT
;
1079 g
= getgrnam(gname
);
1082 if (gid
== 0 && gname
[0] != '0')
1083 errx(1, "-group: %s: no such group", gname
);
1087 new = palloc(N_GROUP
, f_group
);
1093 * -inum n functions --
1095 * True if the file has inode # n.
1098 f_inum(PLAN
*plan
, FTSENT
*entry
)
1101 COMPARE(entry
->fts_statp
->st_ino
, plan
->i_data
);
1105 c_inum(char ***argvp
, int isok
)
1107 char *arg
= **argvp
;
1111 ftsoptions
&= ~FTS_NOSTAT
;
1113 new = palloc(N_INUM
, f_inum
);
1114 new->i_data
= find_parsenum(new, "-inum", arg
, NULL
);
1119 * -links n functions --
1121 * True if the file has n links.
1124 f_links(PLAN
*plan
, FTSENT
*entry
)
1127 COMPARE(entry
->fts_statp
->st_nlink
, plan
->l_data
);
1131 c_links(char ***argvp
, int isok
)
1133 char *arg
= **argvp
;
1137 ftsoptions
&= ~FTS_NOSTAT
;
1139 new = palloc(N_LINKS
, f_links
);
1140 new->l_data
= (nlink_t
)find_parsenum(new, "-links", arg
, NULL
);
1147 * Always true - prints the current entry to stdout in "ls" format.
1150 f_ls(PLAN
*plan
, FTSENT
*entry
)
1153 printlong(entry
->fts_path
, entry
->fts_accpath
, entry
->fts_statp
);
1158 c_ls(char ***argvp
, int isok
)
1161 ftsoptions
&= ~FTS_NOSTAT
;
1164 return (palloc(N_LS
, f_ls
));
1168 * - maxdepth n functions --
1170 * True if the current search depth is less than or equal to the
1171 * maximum depth specified
1174 f_maxdepth(PLAN
*plan
, FTSENT
*entry
)
1178 if (entry
->fts_level
>= plan
->max_data
)
1179 fts_set(tree
, entry
, FTS_SKIP
);
1180 return (entry
->fts_level
<= plan
->max_data
);
1184 c_maxdepth(char ***argvp
, int isok
)
1186 char *arg
= **argvp
;
1190 new = palloc(N_MAXDEPTH
, f_maxdepth
);
1191 new->max_data
= atoi(arg
);
1196 * - mindepth n functions --
1198 * True if the current search depth is greater than or equal to the
1199 * minimum depth specified
1202 f_mindepth(PLAN
*plan
, FTSENT
*entry
)
1204 return (entry
->fts_level
>= plan
->min_data
);
1208 c_mindepth(char ***argvp
, int isok
)
1210 char *arg
= **argvp
;
1214 new = palloc(N_MINDEPTH
, f_mindepth
);
1215 new->min_data
= atoi(arg
);
1220 * -mmin n functions --
1222 * True if the difference between the file modification time and the
1223 * current time is n 24 hour periods.
1226 f_mmin(PLAN
*plan
, FTSENT
*entry
)
1228 COMPARE((now
- entry
->fts_statp
->st_mtime
+ SECSPERMIN
- 1) /
1229 SECSPERMIN
, plan
->t_data
);
1233 c_mmin(char ***argvp
, int isok
)
1235 char *arg
= **argvp
;
1239 ftsoptions
&= ~FTS_NOSTAT
;
1241 new = palloc(N_MMIN
, f_mmin
);
1242 new->t_data
= find_parsenum(new, "-mmin", arg
, NULL
);
1243 TIME_CORRECT(new, N_MMIN
);
1248 * -mtime n functions --
1250 * True if the difference between the file modification time and the
1251 * current time is n 24 hour periods.
1254 f_mtime(PLAN
*plan
, FTSENT
*entry
)
1256 COMPARE((now
- entry
->fts_statp
->st_mtime
+ SECSPERDAY
- 1) /
1257 SECSPERDAY
, plan
->t_data
);
1261 c_mtime(char ***argvp
, int isok
)
1263 char *arg
= **argvp
;
1267 ftsoptions
&= ~FTS_NOSTAT
;
1269 new = palloc(N_MTIME
, f_mtime
);
1270 new->t_data
= find_parsenum(new, "-mtime", arg
, NULL
);
1271 TIME_CORRECT(new, N_MTIME
);
1276 * -name functions --
1278 * True if the basename of the filename being examined
1279 * matches pattern using Pattern Matching Notation S3.14
1282 f_name(PLAN
*plan
, FTSENT
*entry
)
1285 return (!fnmatch(plan
->c_data
, entry
->fts_name
, 0));
1289 c_name(char ***argvp
, int isok
)
1291 char *pattern
= **argvp
;
1295 new = palloc(N_NAME
, f_name
);
1296 new->c_data
= pattern
;
1301 * -iname functions --
1303 * Similar to -name, but does case insensitive matching
1307 f_iname(PLAN
*plan
, FTSENT
*entry
)
1309 return (!fnmatch(plan
->c_data
, entry
->fts_name
, FNM_CASEFOLD
));
1313 c_iname(char ***argvp
, int isok
)
1315 char *pattern
= **argvp
;
1319 new = palloc(N_INAME
, f_iname
);
1320 new->c_data
= pattern
;
1325 * -newer file functions --
1327 * True if the current file has been modified more recently
1328 * than the modification time of the file named by the pathname
1332 f_newer(PLAN
*plan
, FTSENT
*entry
)
1335 return timespeccmp(&entry
->fts_statp
->st_mtim
, &plan
->ts_data
, >);
1339 c_newer(char ***argvp
, int isok
)
1341 char *filename
= **argvp
;
1346 ftsoptions
&= ~FTS_NOSTAT
;
1348 if (stat(filename
, &sb
))
1349 err(1, "%s", filename
);
1350 new = palloc(N_NEWER
, f_newer
);
1351 new->ts_data
= sb
.st_mtim
;
1356 * -nogroup functions --
1358 * True if file belongs to a user ID for which the equivalent
1359 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
1362 f_nogroup(PLAN
*plan
, FTSENT
*entry
)
1365 return (group_from_gid(entry
->fts_statp
->st_gid
, 1) ? 0 : 1);
1369 c_nogroup(char ***argvp
, int isok
)
1371 ftsoptions
&= ~FTS_NOSTAT
;
1373 return (palloc(N_NOGROUP
, f_nogroup
));
1377 * -nouser functions --
1379 * True if file belongs to a user ID for which the equivalent
1380 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
1383 f_nouser(PLAN
*plan
, FTSENT
*entry
)
1386 return (user_from_uid(entry
->fts_statp
->st_uid
, 1) ? 0 : 1);
1390 c_nouser(char ***argvp
, int isok
)
1392 ftsoptions
&= ~FTS_NOSTAT
;
1394 return (palloc(N_NOUSER
, f_nouser
));
1398 * -path functions --
1400 * True if the path of the filename being examined
1401 * matches pattern using Pattern Matching Notation S3.14
1404 f_path(PLAN
*plan
, FTSENT
*entry
)
1407 return (!fnmatch(plan
->c_data
, entry
->fts_path
, 0));
1411 c_path(char ***argvp
, int isok
)
1413 char *pattern
= **argvp
;
1417 new = palloc(N_NAME
, f_path
);
1418 new->c_data
= pattern
;
1423 * -perm functions --
1425 * The mode argument is used to represent file mode bits. If it starts
1426 * with a leading digit, it's treated as an octal mode, otherwise as a
1430 f_perm(PLAN
*plan
, FTSENT
*entry
)
1434 mode
= entry
->fts_statp
->st_mode
&
1435 (S_ISUID
|S_ISGID
|S_ISTXT
|S_IRWXU
|S_IRWXG
|S_IRWXO
);
1436 if (plan
->flags
== F_ATLEAST
)
1437 return ((plan
->m_data
| mode
) == mode
);
1439 return (mode
== plan
->m_data
);
1444 c_perm(char ***argvp
, int isok
)
1446 char *perm
= **argvp
;
1451 ftsoptions
&= ~FTS_NOSTAT
;
1453 new = palloc(N_PERM
, f_perm
);
1456 new->flags
= F_ATLEAST
;
1460 if ((set
= setmode(perm
)) == NULL
)
1461 err(1, "-perm: Cannot set file mode `%s'", perm
);
1463 new->m_data
= getmode(set
, 0);
1469 * -print functions --
1471 * Always true, causes the current pathame to be written to
1475 f_print(PLAN
*plan
, FTSENT
*entry
)
1478 (void)printf("%s\n", entry
->fts_path
);
1483 f_print0(PLAN
*plan
, FTSENT
*entry
)
1486 (void)fputs(entry
->fts_path
, stdout
);
1487 (void)fputc('\0', stdout
);
1492 f_printx(PLAN
*plan
, FTSENT
*entry
)
1496 for (cp
= entry
->fts_path
; *cp
; cp
++) {
1497 if (*cp
== '\'' || *cp
== '\"' || *cp
== ' ' ||
1498 *cp
== '$' || *cp
== '`' ||
1499 *cp
== '\t' || *cp
== '\n' || *cp
== '\\')
1500 fputc('\\', stdout
);
1505 fputc('\n', stdout
);
1510 c_print(char ***argvp
, int isok
)
1515 return (palloc(N_PRINT
, f_print
));
1519 c_print0(char ***argvp
, int isok
)
1524 return (palloc(N_PRINT0
, f_print0
));
1528 c_printx(char ***argvp
, int isok
)
1533 return (palloc(N_PRINTX
, f_printx
));
1537 * -prune functions --
1539 * Prune a portion of the hierarchy.
1542 f_prune(PLAN
*plan
, FTSENT
*entry
)
1544 if (fts_set(tree
, entry
, FTS_SKIP
))
1545 err(1, "%s", entry
->fts_path
);
1550 c_prune(char ***argvp
, int isok
)
1553 return (palloc(N_PRUNE
, f_prune
));
1557 * -regex regexp (and related) functions --
1559 * True if the complete file path matches the regular expression regexp.
1560 * For -regex, regexp is a case-sensitive (basic) regular expression.
1561 * For -iregex, regexp is a case-insensitive (basic) regular expression.
1564 f_regex(PLAN
*plan
, FTSENT
*entry
)
1567 return (regexec(&plan
->regexp_data
, entry
->fts_path
, 0, NULL
, 0) == 0);
1571 c_regex_common(char ***argvp
, int isok
, enum ntype type
, bool icase
)
1573 char errbuf
[LINE_MAX
];
1575 char *regexp
= **argvp
;
1583 len
= strlen(regexp
) + 1 + 6;
1584 lineregexp
= malloc(len
); /* max needed */
1585 if (lineregexp
== NULL
)
1587 snprintf(lineregexp
, len
, "^%s(%s%s)$",
1588 (regcomp_flags
& REG_EXTENDED
) ? "" : "\\", regexp
,
1589 (regcomp_flags
& REG_EXTENDED
) ? "" : "\\");
1590 rv
= regcomp(®
, lineregexp
, REG_NOSUB
|regcomp_flags
|
1591 (icase
? REG_ICASE
: 0));
1594 regerror(rv
, ®
, errbuf
, sizeof errbuf
);
1595 errx(1, "regexp %s: %s", regexp
, errbuf
);
1598 new = palloc(type
, f_regex
);
1599 new->regexp_data
= reg
;
1604 c_regex(char ***argvp
, int isok
)
1607 return (c_regex_common(argvp
, isok
, N_REGEX
, false));
1611 c_iregex(char ***argvp
, int isok
)
1614 return (c_regex_common(argvp
, isok
, N_IREGEX
, true));
1618 * -size n[c] functions --
1620 * True if the file size in bytes, divided by an implementation defined
1621 * value and rounded up to the next integer, is n. If n is followed by
1622 * a c, the size is in bytes.
1624 #define FIND_SIZE 512
1625 static int divsize
= 1;
1628 f_size(PLAN
*plan
, FTSENT
*entry
)
1632 size
= divsize
? (entry
->fts_statp
->st_size
+ FIND_SIZE
- 1) /
1633 FIND_SIZE
: entry
->fts_statp
->st_size
;
1634 COMPARE(size
, plan
->o_data
);
1638 c_size(char ***argvp
, int isok
)
1640 char *arg
= **argvp
;
1645 ftsoptions
&= ~FTS_NOSTAT
;
1647 new = palloc(N_SIZE
, f_size
);
1649 new->o_data
= find_parsenum(new, "-size", arg
, &endch
);
1656 * -type c functions --
1658 * True if the type of the file is c, where c is b, c, d, p, f or w
1659 * for block special file, character special file, directory, FIFO,
1660 * regular file or whiteout respectively.
1663 f_type(PLAN
*plan
, FTSENT
*entry
)
1666 return ((entry
->fts_statp
->st_mode
& S_IFMT
) == plan
->m_data
);
1670 c_type(char ***argvp
, int isok
)
1672 char *typestring
= **argvp
;
1674 mode_t mask
= (mode_t
)0;
1677 ftsoptions
&= ~FTS_NOSTAT
;
1679 switch (typestring
[0]) {
1706 ftsoptions
|= FTS_WHITEOUT
;
1709 #endif /* S_IFWHT */
1711 errx(1, "-type: %s: unknown type", typestring
);
1714 new = palloc(N_TYPE
, f_type
);
1720 * -user uname functions --
1722 * True if the file belongs to the user uname. If uname is numeric and
1723 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1724 * return a valid user name, uname is taken as a user ID.
1727 f_user(PLAN
*plan
, FTSENT
*entry
)
1730 COMPARE(entry
->fts_statp
->st_uid
, plan
->u_data
);
1734 c_user(char ***argvp
, int isok
)
1736 char *username
= **argvp
;
1742 ftsoptions
&= ~FTS_NOSTAT
;
1744 new = palloc(N_USER
, f_user
);
1745 p
= getpwnam(username
);
1747 if (atoi(username
) == 0 && username
[0] != '0' &&
1748 strcmp(username
, "+0") && strcmp(username
, "-0"))
1749 errx(1, "-user: %s: no such user", username
);
1750 uid
= find_parsenum(new, "-user", username
, NULL
);
1753 new->flags
= F_EQUAL
;
1762 * -xdev functions --
1764 * Always true, causes find not to descend past directories that have a
1765 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1768 c_xdev(char ***argvp
, int isok
)
1770 ftsoptions
|= FTS_XDEV
;
1772 return (palloc(N_XDEV
, f_always_true
));
1776 * ( expression ) functions --
1778 * True if expression is true.
1781 f_expr(PLAN
*plan
, FTSENT
*entry
)
1787 for (p
= plan
->p_data
[0];
1788 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1793 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are
1794 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1795 * to a N_EXPR node containing the expression and the ')' node is discarded.
1798 c_openparen(char ***argvp
, int isok
)
1801 return (palloc(N_OPENPAREN
, (int (*)(PLAN
*, FTSENT
*))-1));
1805 c_closeparen(char ***argvp
, int isok
)
1808 return (palloc(N_CLOSEPAREN
, (int (*)(PLAN
*, FTSENT
*))-1));
1812 * ! expression functions --
1814 * Negation of a primary; the unary NOT operator.
1817 f_not(PLAN
*plan
, FTSENT
*entry
)
1823 for (p
= plan
->p_data
[0];
1824 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1829 c_not(char ***argvp
, int isok
)
1832 return (palloc(N_NOT
, f_not
));
1836 * expression -o expression functions --
1838 * Alternation of primaries; the OR operator. The second expression is
1839 * not evaluated if the first expression is true.
1842 f_or(PLAN
*plan
, FTSENT
*entry
)
1848 for (p
= plan
->p_data
[0];
1849 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1854 for (p
= plan
->p_data
[1];
1855 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1860 c_or(char ***argvp
, int isok
)
1863 return (palloc(N_OR
, f_or
));
1867 c_null(char ***argvp
, int isok
)
1876 * Check and see if the specified plan has any residual state,
1877 * and if so, clean it up as appropriate.
1879 * At the moment, only N_EXEC has state. Two kinds: 1)
1880 * lists of files to feed to subprocesses 2) State on exit
1881 * statusses of past subprocesses.
1885 plan_cleanup(PLAN
*plan
, void *arg
)
1887 if (plan
->type
==N_EXEC
&& plan
->ep_narg
)
1890 return plan
->ep_rval
; /* Passed save exit-status up chain */
1894 palloc(enum ntype t
, int (*f
)(PLAN
*, FTSENT
*))
1898 if ((new = malloc(sizeof(PLAN
))) == NULL
)
1900 memset(new, 0, sizeof(PLAN
));