1 /* $NetBSD: function.c,v 1.63 2007/07/17 21:35:29 christos 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.63 2007/07/17 21:35:29 christos 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
, entry
)
223 return (entry
->fts_statp
->st_atime
> plan
->t_data
);
227 c_anewer(char ***argvp
, int isok
)
229 char *filename
= **argvp
;
234 ftsoptions
&= ~FTS_NOSTAT
;
236 if (stat(filename
, &sb
))
237 err(1, "%s", filename
);
238 new = palloc(N_ANEWER
, f_anewer
);
239 new->t_data
= sb
.st_atime
;
244 * -atime n functions --
246 * True if the difference between the file access time and the
247 * current time is n 24 hour periods.
250 f_atime(PLAN
*plan
, FTSENT
*entry
)
252 COMPARE((now
- entry
->fts_statp
->st_atime
+
253 SECSPERDAY
- 1) / SECSPERDAY
, plan
->t_data
);
257 c_atime(char ***argvp
, int isok
)
263 ftsoptions
&= ~FTS_NOSTAT
;
265 new = palloc(N_ATIME
, f_atime
);
266 new->t_data
= find_parsenum(new, "-atime", arg
, NULL
);
267 TIME_CORRECT(new, N_ATIME
);
271 * -cmin n functions --
273 * True if the difference between the last change of file
274 * status information and the current time is n 24 hour periods.
277 f_cmin(PLAN
*plan
, FTSENT
*entry
)
279 COMPARE((now
- entry
->fts_statp
->st_ctime
+
280 SECSPERMIN
- 1) / SECSPERMIN
, plan
->t_data
);
284 c_cmin(char ***argvp
, int isok
)
290 ftsoptions
&= ~FTS_NOSTAT
;
292 new = palloc(N_CMIN
, f_cmin
);
293 new->t_data
= find_parsenum(new, "-cmin", arg
, NULL
);
294 TIME_CORRECT(new, N_CMIN
);
299 * -cnewer file functions --
301 * True if the current file has been changed more recently
302 * than the changed time of the file named by the pathname
306 f_cnewer(PLAN
*plan
, FTSENT
*entry
)
309 return (entry
->fts_statp
->st_ctime
> plan
->t_data
);
313 c_cnewer(char ***argvp
, int isok
)
315 char *filename
= **argvp
;
320 ftsoptions
&= ~FTS_NOSTAT
;
322 if (stat(filename
, &sb
))
323 err(1, "%s", filename
);
324 new = palloc(N_CNEWER
, f_cnewer
);
325 new->t_data
= sb
.st_ctime
;
330 * -ctime n functions --
332 * True if the difference between the last change of file
333 * status information and the current time is n 24 hour periods.
336 f_ctime(PLAN
*plan
, FTSENT
*entry
)
338 COMPARE((now
- entry
->fts_statp
->st_ctime
+
339 SECSPERDAY
- 1) / SECSPERDAY
, plan
->t_data
);
343 c_ctime(char ***argvp
, int isok
)
349 ftsoptions
&= ~FTS_NOSTAT
;
351 new = palloc(N_CTIME
, f_ctime
);
352 new->t_data
= find_parsenum(new, "-ctime", arg
, NULL
);
353 TIME_CORRECT(new, N_CTIME
);
358 * -delete functions --
360 * True always. Makes its best shot and continues on regardless.
363 f_delete(PLAN
*plan __unused
, FTSENT
*entry
)
365 /* ignore these from fts */
366 if (strcmp(entry
->fts_accpath
, ".") == 0 ||
367 strcmp(entry
->fts_accpath
, "..") == 0)
371 if (isdepth
== 0 || /* depth off */
372 (ftsoptions
& FTS_NOSTAT
) || /* not stat()ing */
373 !(ftsoptions
& FTS_PHYSICAL
) || /* physical off */
374 (ftsoptions
& FTS_LOGICAL
)) /* or finally, logical on */
375 errx(1, "-delete: insecure options got turned on");
377 /* Potentially unsafe - do not accept relative paths whatsoever */
378 if (strchr(entry
->fts_accpath
, '/') != NULL
)
379 errx(1, "-delete: %s: relative path potentially not safe",
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
));
389 /* rmdir directories, unlink everything else */
390 if (S_ISDIR(entry
->fts_statp
->st_mode
)) {
391 if (rmdir(entry
->fts_accpath
) < 0 && errno
!= ENOTEMPTY
)
392 warn("-delete: rmdir(%s)", entry
->fts_path
);
394 if (unlink(entry
->fts_accpath
) < 0)
395 warn("-delete: unlink(%s)", entry
->fts_path
);
403 c_delete(char ***argvp __unused
, int isok
)
406 ftsoptions
&= ~FTS_NOSTAT
; /* no optimize */
407 ftsoptions
|= FTS_PHYSICAL
; /* disable -follow */
408 ftsoptions
&= ~FTS_LOGICAL
; /* disable -follow */
409 isoutput
= 1; /* possible output */
410 isdepth
= 1; /* -depth implied */
412 return palloc(N_DELETE
, f_delete
);
416 * -depth functions --
418 * Always true, causes descent of the directory hierarchy to be done
419 * so that all entries in a directory are acted on before the directory
423 f_always_true(PLAN
*plan
, FTSENT
*entry
)
430 c_depth(char ***argvp
, int isok
)
434 return (palloc(N_DEPTH
, f_always_true
));
438 * -empty functions --
440 * True if the file or directory is empty
443 f_empty(PLAN
*plan
, FTSENT
*entry
)
445 if (S_ISREG(entry
->fts_statp
->st_mode
) &&
446 entry
->fts_statp
->st_size
== 0)
448 if (S_ISDIR(entry
->fts_statp
->st_mode
)) {
454 dir
= opendir(entry
->fts_accpath
);
456 err(1, "%s", entry
->fts_accpath
);
457 for (dp
= readdir(dir
); dp
; dp
= readdir(dir
))
458 if (dp
->d_name
[0] != '.' ||
459 (dp
->d_name
[1] != '\0' &&
460 (dp
->d_name
[1] != '.' || dp
->d_name
[2] != '\0'))) {
471 c_empty(char ***argvp
, int isok
)
473 ftsoptions
&= ~FTS_NOSTAT
;
475 return (palloc(N_EMPTY
, f_empty
));
479 * [-exec | -ok] utility [arg ... ] ; functions --
480 * [-exec | -ok] utility [arg ... ] {} + functions --
482 * If the end of the primary expression is delimited by a
483 * semicolon: true if the executed utility returns a zero value
484 * as exit status. If "{}" occurs anywhere, it gets replaced by
485 * the current pathname.
487 * If the end of the primary expression is delimited by a plus
488 * sign: always true. Pathnames for which the primary is
489 * evaluated shall be aggregated into sets. The utility will be
490 * executed once per set, with "{}" replaced by the entire set of
491 * pathnames (as if xargs). "{}" must appear last.
493 * The current directory for the execution of utility is the same
494 * as the current directory when the find utility was started.
496 * The primary -ok is different in that it requests affirmation
497 * of the user before executing the utility.
500 f_exec(PLAN
*plan
, FTSENT
*entry
)
506 if (plan
->flags
& F_PLUSSET
) {
508 * Confirm sufficient buffer space, then copy the path
511 l
= strlen(entry
->fts_path
);
512 if (plan
->ep_p
+ l
< plan
->ep_ebp
) {
513 plan
->ep_bxp
[plan
->ep_narg
++] =
514 strcpy(plan
->ep_p
, entry
->fts_path
);
517 if (plan
->ep_narg
== plan
->ep_maxargs
)
521 * Without sufficient space to copy in the next
522 * argument, run the command to empty out the
523 * buffer before re-attepting the copy.
526 if ((plan
->ep_p
+ l
< plan
->ep_ebp
)) {
527 plan
->ep_bxp
[plan
->ep_narg
++]
528 = strcpy(plan
->ep_p
, entry
->fts_path
);
531 errx(1, "insufficient space for argument");
535 for (cnt
= 0; plan
->e_argv
[cnt
]; ++cnt
)
536 if (plan
->e_len
[cnt
])
537 brace_subst(plan
->e_orig
[cnt
],
541 if (plan
->flags
& F_NEEDOK
&& !queryuser(plan
->e_argv
))
544 /* Don't mix output of command with find output. */
548 switch (pid
= vfork()) {
557 execvp(plan
->e_argv
[0], plan
->e_argv
);
558 warn("%s", plan
->e_argv
[0]);
561 pid
= waitpid(pid
, &status
, 0);
562 return (pid
!= -1 && WIFEXITED(status
)
563 && !WEXITSTATUS(status
));
568 run_f_exec(PLAN
*plan
)
573 /* Ensure arg list is null terminated. */
574 plan
->ep_bxp
[plan
->ep_narg
] = NULL
;
576 /* Don't mix output of command with find output. */
580 switch (pid
= vfork()) {
589 execvp(plan
->e_argv
[0], plan
->e_argv
);
590 warn("%s", plan
->e_argv
[0]);
594 /* Clear out the argument list. */
596 plan
->ep_bxp
[plan
->ep_narg
] = NULL
;
597 /* As well as the argument buffer. */
598 plan
->ep_p
= plan
->ep_bbp
;
601 pid
= waitpid(pid
, &status
, 0);
602 if (WIFEXITED(status
))
603 rval
= WEXITSTATUS(status
);
608 * If we have a non-zero exit status, preserve it so find(1) can
609 * later exit with it.
612 plan
->ep_rval
= rval
;
617 * build three parallel arrays, one with pointers to the strings passed
618 * on the command line, one with (possibly duplicated) pointers to the
619 * argv array, and one with integer values that are lengths of the
620 * strings, but also flags meaning that the string has to be massaged.
622 * If -exec ... {} +, use only the first array, but make it large
623 * enough to hold 5000 args (cf. src/usr.bin/xargs/xargs.c for a
624 * discussion), and then allocate ARG_MAX - 4K of space for args.
627 c_exec(char ***argvp
, int isok
)
629 PLAN
*new; /* node returned */
630 int cnt
, brace
, lastbrace
;
631 char **argv
, **ap
, *p
;
635 new = palloc(N_EXEC
, f_exec
);
637 new->flags
|= F_NEEDOK
;
640 * Terminate if we encounter an arg exacty equal to ";", or an
641 * arg exacty equal to "+" following an arg exacty equal to
644 for (ap
= argv
= *argvp
, brace
= 0;; ++ap
) {
646 errx(1, "%s: no terminating \";\" or \"+\"",
647 isok
? "-ok" : "-exec");
649 if (strcmp(*ap
, "{}") == 0)
651 if (strcmp(*ap
, ";") == 0)
653 if (strcmp(*ap
, "+") == 0 && lastbrace
) {
654 new->flags
|= F_PLUSSET
;
660 * POSIX says -ok ... {} + "need not be supported," and it does
661 * not make much sense anyway.
663 if (new->flags
& F_NEEDOK
&& new->flags
& F_PLUSSET
)
664 errx(1, "-ok: terminating \"+\" not permitted.");
666 if (new->flags
& F_PLUSSET
) {
669 cnt
= ap
- *argvp
- 1; /* units are words */
670 new->ep_maxargs
= 5000;
671 new->e_argv
= (char **)emalloc((u_int
)(cnt
+ new->ep_maxargs
)
674 /* We start stuffing arguments after the user's last one. */
675 new->ep_bxp
= &new->e_argv
[cnt
];
679 * Count up the space of the user's arguments, and
680 * subtract that from what we allocate.
682 for (argv
= *argvp
, c
= 0, cnt
= 0;
685 c
+= strlen(*argv
) + 1;
686 new->e_argv
[cnt
] = *argv
;
688 bufsize
= ARG_MAX
- 4 * 1024 - c
;
692 * Allocate, and then initialize current, base, and
695 new->ep_p
= new->ep_bbp
= malloc(bufsize
+ 1);
696 new->ep_ebp
= new->ep_bbp
+ bufsize
- 1;
698 } else { /* !F_PLUSSET */
699 cnt
= ap
- *argvp
+ 1;
700 new->e_argv
= (char **)emalloc((u_int
)cnt
* sizeof(char *));
701 new->e_orig
= (char **)emalloc((u_int
)cnt
* sizeof(char *));
702 new->e_len
= (int *)emalloc((u_int
)cnt
* sizeof(int));
704 for (argv
= *argvp
, cnt
= 0; argv
< ap
; ++argv
, ++cnt
) {
705 new->e_orig
[cnt
] = *argv
;
706 for (p
= *argv
; *p
; ++p
)
707 if (p
[0] == '{' && p
[1] == '}') {
709 emalloc((u_int
)MAXPATHLEN
);
710 new->e_len
[cnt
] = MAXPATHLEN
;
714 new->e_argv
[cnt
] = *argv
;
718 new->e_orig
[cnt
] = NULL
;
721 new->e_argv
[cnt
] = NULL
;
727 * -execdir utility [arg ... ] ; functions --
729 * True if the executed utility returns a zero value as exit status.
730 * The end of the primary expression is delimited by a semicolon. If
731 * "{}" occurs anywhere, it gets replaced by the unqualified pathname.
732 * The current directory for the execution of utility is the same as
733 * the directory where the file lives.
736 f_execdir(PLAN
*plan
, FTSENT
*entry
)
743 /* XXX - if file/dir ends in '/' this will not work -- can it? */
744 if ((file
= strrchr(entry
->fts_path
, '/')))
747 file
= entry
->fts_path
;
749 for (cnt
= 0; plan
->e_argv
[cnt
]; ++cnt
)
750 if (plan
->e_len
[cnt
])
751 brace_subst(plan
->e_orig
[cnt
], &plan
->e_argv
[cnt
],
752 file
, &plan
->e_len
[cnt
]);
754 /* don't mix output of command with find output */
758 switch (pid
= vfork()) {
763 execvp(plan
->e_argv
[0], plan
->e_argv
);
764 warn("%s", plan
->e_argv
[0]);
767 pid
= waitpid(pid
, &status
, 0);
768 return (pid
!= -1 && WIFEXITED(status
) && !WEXITSTATUS(status
));
773 * build three parallel arrays, one with pointers to the strings passed
774 * on the command line, one with (possibly duplicated) pointers to the
775 * argv array, and one with integer values that are lengths of the
776 * strings, but also flags meaning that the string has to be massaged.
779 c_execdir(char ***argvp
, int isok
)
781 PLAN
*new; /* node returned */
783 char **argv
, **ap
, *p
;
785 ftsoptions
&= ~FTS_NOSTAT
;
788 new = palloc(N_EXECDIR
, f_execdir
);
790 for (ap
= argv
= *argvp
;; ++ap
) {
793 "-execdir: no terminating \";\"");
798 cnt
= ap
- *argvp
+ 1;
799 new->e_argv
= (char **)emalloc((u_int
)cnt
* sizeof(char *));
800 new->e_orig
= (char **)emalloc((u_int
)cnt
* sizeof(char *));
801 new->e_len
= (int *)emalloc((u_int
)cnt
* sizeof(int));
803 for (argv
= *argvp
, cnt
= 0; argv
< ap
; ++argv
, ++cnt
) {
804 new->e_orig
[cnt
] = *argv
;
805 for (p
= *argv
; *p
; ++p
)
806 if (p
[0] == '{' && p
[1] == '}') {
807 new->e_argv
[cnt
] = emalloc((u_int
)MAXPATHLEN
);
808 new->e_len
[cnt
] = MAXPATHLEN
;
812 new->e_argv
[cnt
] = *argv
;
816 new->e_argv
[cnt
] = new->e_orig
[cnt
] = NULL
;
823 c_exit(char ***argvp
, int isok
)
828 /* not technically true, but otherwise '-print' is implied */
831 new = palloc(N_EXIT
, f_always_true
);
835 new->exit_val
= find_parsenum(new, "-exit", arg
, NULL
);
847 f_false(PLAN
*plan
, FTSENT
*entry
)
854 c_false(char ***argvp
, int isok
)
856 return (palloc(N_FALSE
, f_false
));
861 * -flags [-]flags functions --
864 f_flags(PLAN
*plan
, FTSENT
*entry
)
868 flags
= entry
->fts_statp
->st_flags
;
869 if (plan
->flags
== F_ATLEAST
)
870 return ((plan
->f_data
| flags
) == flags
);
872 return (flags
== plan
->f_data
);
877 c_flags(char ***argvp
, int isok
)
879 char *flags
= **argvp
;
884 ftsoptions
&= ~FTS_NOSTAT
;
886 new = palloc(N_FLAGS
, f_flags
);
889 new->flags
= F_ATLEAST
;
894 if ((strcmp(flags
, "none") != 0) &&
895 (string_to_flags(&flags
, &flagset
, NULL
) != 0))
896 errx(1, "-flags: %s: illegal flags string", flags
);
897 new->f_data
= flagset
;
902 * -follow functions --
904 * Always true, causes symbolic links to be followed on a global
908 c_follow(char ***argvp
, int isok
)
910 ftsoptions
&= ~FTS_PHYSICAL
;
911 ftsoptions
|= FTS_LOGICAL
;
913 return (palloc(N_FOLLOW
, f_always_true
));
916 /* -fprint functions --
918 * Causes the current pathame to be written to the defined output file.
921 f_fprint(PLAN
*plan
, FTSENT
*entry
)
924 if (-1 == fprintf(plan
->fprint_file
, "%s\n", entry
->fts_path
))
929 /* no descriptors are closed; they will be closed by
930 operating system when this find command exits. */
934 c_fprint(char ***argvp
, int isok
)
938 isoutput
= 1; /* do not assume -print */
940 new = palloc(N_FPRINT
, f_fprint
);
942 if (NULL
== (new->fprint_file
= fopen(**argvp
, "w")))
943 err(1, "-fprint: %s: cannot create file", **argvp
);
950 * -fstype functions --
952 * True if the file is of a certain type.
955 f_fstype(PLAN
*plan
, FTSENT
*entry
)
957 static dev_t curdev
; /* need a guaranteed illegal dev value */
958 static int first
= 1;
961 static char fstype
[sizeof(sb
.f_fstypename
)];
964 memset(&save
, 0, sizeof save
); /* XXX gcc */
966 /* Only check when we cross mount point. */
967 if (first
|| curdev
!= entry
->fts_statp
->st_dev
) {
968 curdev
= entry
->fts_statp
->st_dev
;
971 * Statfs follows symlinks; find wants the link's file system,
972 * not where it points.
974 if (entry
->fts_info
== FTS_SL
||
975 entry
->fts_info
== FTS_SLNONE
) {
976 if ((p
= strrchr(entry
->fts_accpath
, '/')) != NULL
)
979 p
= entry
->fts_accpath
;
988 if (statvfs(entry
->fts_accpath
, &sb
))
989 err(1, "%s", entry
->fts_accpath
);
999 * Further tests may need both of these values, so
1000 * always copy both of them.
1003 strlcpy(fstype
, sb
.f_fstypename
, sizeof(fstype
));
1005 switch (plan
->flags
) {
1007 return (val
& plan
->mt_data
);
1009 return (strncmp(fstype
, plan
->c_data
, sizeof(fstype
)) == 0);
1016 c_fstype(char ***argvp
, int isok
)
1018 char *arg
= **argvp
;
1022 ftsoptions
&= ~FTS_NOSTAT
;
1024 new = palloc(N_FSTYPE
, f_fstype
);
1028 if (!strcmp(arg
, "local")) {
1029 new->flags
= F_MTFLAG
;
1030 new->mt_data
= MNT_LOCAL
;
1035 if (!strcmp(arg
, "rdonly")) {
1036 new->flags
= F_MTFLAG
;
1037 new->mt_data
= MNT_RDONLY
;
1043 new->flags
= F_MTTYPE
;
1049 * -group gname functions --
1051 * True if the file belongs to the group gname. If gname is numeric and
1052 * an equivalent of the getgrnam() function does not return a valid group
1053 * name, gname is taken as a group ID.
1056 f_group(PLAN
*plan
, FTSENT
*entry
)
1059 return (entry
->fts_statp
->st_gid
== plan
->g_data
);
1063 c_group(char ***argvp
, int isok
)
1065 char *gname
= **argvp
;
1071 ftsoptions
&= ~FTS_NOSTAT
;
1073 g
= getgrnam(gname
);
1076 if (gid
== 0 && gname
[0] != '0')
1077 errx(1, "-group: %s: no such group", gname
);
1081 new = palloc(N_GROUP
, f_group
);
1087 * -inum n functions --
1089 * True if the file has inode # n.
1092 f_inum(PLAN
*plan
, FTSENT
*entry
)
1095 COMPARE(entry
->fts_statp
->st_ino
, plan
->i_data
);
1099 c_inum(char ***argvp
, int isok
)
1101 char *arg
= **argvp
;
1105 ftsoptions
&= ~FTS_NOSTAT
;
1107 new = palloc(N_INUM
, f_inum
);
1108 new->i_data
= find_parsenum(new, "-inum", arg
, NULL
);
1113 * -links n functions --
1115 * True if the file has n links.
1118 f_links(PLAN
*plan
, FTSENT
*entry
)
1121 COMPARE(entry
->fts_statp
->st_nlink
, plan
->l_data
);
1125 c_links(char ***argvp
, int isok
)
1127 char *arg
= **argvp
;
1131 ftsoptions
&= ~FTS_NOSTAT
;
1133 new = palloc(N_LINKS
, f_links
);
1134 new->l_data
= (nlink_t
)find_parsenum(new, "-links", arg
, NULL
);
1141 * Always true - prints the current entry to stdout in "ls" format.
1144 f_ls(PLAN
*plan
, FTSENT
*entry
)
1147 printlong(entry
->fts_path
, entry
->fts_accpath
, entry
->fts_statp
);
1152 c_ls(char ***argvp
, int isok
)
1155 ftsoptions
&= ~FTS_NOSTAT
;
1158 return (palloc(N_LS
, f_ls
));
1162 * - maxdepth n functions --
1164 * True if the current search depth is less than or equal to the
1165 * maximum depth specified
1168 f_maxdepth(PLAN
*plan
, FTSENT
*entry
)
1172 if (entry
->fts_level
>= plan
->max_data
)
1173 fts_set(tree
, entry
, FTS_SKIP
);
1174 return (entry
->fts_level
<= plan
->max_data
);
1178 c_maxdepth(char ***argvp
, int isok
)
1180 char *arg
= **argvp
;
1184 new = palloc(N_MAXDEPTH
, f_maxdepth
);
1185 new->max_data
= atoi(arg
);
1190 * - mindepth n functions --
1192 * True if the current search depth is greater than or equal to the
1193 * minimum depth specified
1196 f_mindepth(PLAN
*plan
, FTSENT
*entry
)
1198 return (entry
->fts_level
>= plan
->min_data
);
1202 c_mindepth(char ***argvp
, int isok
)
1204 char *arg
= **argvp
;
1208 new = palloc(N_MINDEPTH
, f_mindepth
);
1209 new->min_data
= atoi(arg
);
1213 * -mmin n functions --
1215 * True if the difference between the file modification time and the
1216 * current time is n 24 hour periods.
1219 f_mmin(PLAN
*plan
, FTSENT
*entry
)
1221 COMPARE((now
- entry
->fts_statp
->st_mtime
+ SECSPERMIN
- 1) /
1222 SECSPERMIN
, plan
->t_data
);
1226 c_mmin(char ***argvp
, int isok
)
1228 char *arg
= **argvp
;
1232 ftsoptions
&= ~FTS_NOSTAT
;
1234 new = palloc(N_MMIN
, f_mmin
);
1235 new->t_data
= find_parsenum(new, "-mmin", arg
, NULL
);
1236 TIME_CORRECT(new, N_MMIN
);
1240 * -mtime n functions --
1242 * True if the difference between the file modification time and the
1243 * current time is n 24 hour periods.
1246 f_mtime(PLAN
*plan
, FTSENT
*entry
)
1248 COMPARE((now
- entry
->fts_statp
->st_mtime
+ SECSPERDAY
- 1) /
1249 SECSPERDAY
, plan
->t_data
);
1253 c_mtime(char ***argvp
, int isok
)
1255 char *arg
= **argvp
;
1259 ftsoptions
&= ~FTS_NOSTAT
;
1261 new = palloc(N_MTIME
, f_mtime
);
1262 new->t_data
= find_parsenum(new, "-mtime", arg
, NULL
);
1263 TIME_CORRECT(new, N_MTIME
);
1268 * -name functions --
1270 * True if the basename of the filename being examined
1271 * matches pattern using Pattern Matching Notation S3.14
1274 f_name(PLAN
*plan
, FTSENT
*entry
)
1277 return (!fnmatch(plan
->c_data
, entry
->fts_name
, 0));
1281 c_name(char ***argvp
, int isok
)
1283 char *pattern
= **argvp
;
1287 new = palloc(N_NAME
, f_name
);
1288 new->c_data
= pattern
;
1293 * -iname functions --
1295 * Similar to -name, but does case insensitive matching
1299 f_iname(PLAN
*plan
, FTSENT
*entry
)
1301 return (!fnmatch(plan
->c_data
, entry
->fts_name
, FNM_CASEFOLD
));
1305 c_iname(char ***argvp
, int isok
)
1307 char *pattern
= **argvp
;
1311 new = palloc(N_INAME
, f_iname
);
1312 new->c_data
= pattern
;
1317 * -newer file functions --
1319 * True if the current file has been modified more recently
1320 * than the modification time of the file named by the pathname
1324 f_newer(PLAN
*plan
, FTSENT
*entry
)
1327 return (entry
->fts_statp
->st_mtime
> plan
->t_data
);
1331 c_newer(char ***argvp
, int isok
)
1333 char *filename
= **argvp
;
1338 ftsoptions
&= ~FTS_NOSTAT
;
1340 if (stat(filename
, &sb
))
1341 err(1, "%s", filename
);
1342 new = palloc(N_NEWER
, f_newer
);
1343 new->t_data
= sb
.st_mtime
;
1348 * -nogroup functions --
1350 * True if file belongs to a user ID for which the equivalent
1351 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
1354 f_nogroup(PLAN
*plan
, FTSENT
*entry
)
1357 return (group_from_gid(entry
->fts_statp
->st_gid
, 1) ? 0 : 1);
1361 c_nogroup(char ***argvp
, int isok
)
1363 ftsoptions
&= ~FTS_NOSTAT
;
1365 return (palloc(N_NOGROUP
, f_nogroup
));
1369 * -nouser functions --
1371 * True if file belongs to a user ID for which the equivalent
1372 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
1375 f_nouser(PLAN
*plan
, FTSENT
*entry
)
1378 return (user_from_uid(entry
->fts_statp
->st_uid
, 1) ? 0 : 1);
1382 c_nouser(char ***argvp
, int isok
)
1384 ftsoptions
&= ~FTS_NOSTAT
;
1386 return (palloc(N_NOUSER
, f_nouser
));
1390 * -path functions --
1392 * True if the path of the filename being examined
1393 * matches pattern using Pattern Matching Notation S3.14
1396 f_path(PLAN
*plan
, FTSENT
*entry
)
1399 return (!fnmatch(plan
->c_data
, entry
->fts_path
, 0));
1403 c_path(char ***argvp
, int isok
)
1405 char *pattern
= **argvp
;
1409 new = palloc(N_NAME
, f_path
);
1410 new->c_data
= pattern
;
1415 * -perm functions --
1417 * The mode argument is used to represent file mode bits. If it starts
1418 * with a leading digit, it's treated as an octal mode, otherwise as a
1422 f_perm(PLAN
*plan
, FTSENT
*entry
)
1426 mode
= entry
->fts_statp
->st_mode
&
1427 (S_ISUID
|S_ISGID
|S_ISTXT
|S_IRWXU
|S_IRWXG
|S_IRWXO
);
1428 if (plan
->flags
== F_ATLEAST
)
1429 return ((plan
->m_data
| mode
) == mode
);
1431 return (mode
== plan
->m_data
);
1436 c_perm(char ***argvp
, int isok
)
1438 char *perm
= **argvp
;
1443 ftsoptions
&= ~FTS_NOSTAT
;
1445 new = palloc(N_PERM
, f_perm
);
1448 new->flags
= F_ATLEAST
;
1452 if ((set
= setmode(perm
)) == NULL
)
1453 err(1, "-perm: Cannot set file mode `%s'", perm
);
1455 new->m_data
= getmode(set
, 0);
1461 * -print functions --
1463 * Always true, causes the current pathame to be written to
1467 f_print(PLAN
*plan
, FTSENT
*entry
)
1470 (void)printf("%s\n", entry
->fts_path
);
1475 f_print0(PLAN
*plan
, FTSENT
*entry
)
1478 (void)fputs(entry
->fts_path
, stdout
);
1479 (void)fputc('\0', stdout
);
1484 f_printx(PLAN
*plan
, FTSENT
*entry
)
1488 for (cp
= entry
->fts_path
; *cp
; cp
++) {
1489 if (*cp
== '\'' || *cp
== '\"' || *cp
== ' ' ||
1490 *cp
== '$' || *cp
== '`' ||
1491 *cp
== '\t' || *cp
== '\n' || *cp
== '\\')
1492 fputc('\\', stdout
);
1497 fputc('\n', stdout
);
1502 c_print(char ***argvp
, int isok
)
1507 return (palloc(N_PRINT
, f_print
));
1511 c_print0(char ***argvp
, int isok
)
1516 return (palloc(N_PRINT0
, f_print0
));
1520 c_printx(char ***argvp
, int isok
)
1525 return (palloc(N_PRINTX
, f_printx
));
1529 * -prune functions --
1531 * Prune a portion of the hierarchy.
1534 f_prune(PLAN
*plan
, FTSENT
*entry
)
1536 if (fts_set(tree
, entry
, FTS_SKIP
))
1537 err(1, "%s", entry
->fts_path
);
1542 c_prune(char ***argvp
, int isok
)
1545 return (palloc(N_PRUNE
, f_prune
));
1549 * -regex regexp (and related) functions --
1551 * True if the complete file path matches the regular expression regexp.
1552 * For -regex, regexp is a case-sensitive (basic) regular expression.
1553 * For -iregex, regexp is a case-insensitive (basic) regular expression.
1556 f_regex(PLAN
*plan
, FTSENT
*entry
)
1559 return (regexec(&plan
->regexp_data
, entry
->fts_path
, 0, NULL
, 0) == 0);
1563 c_regex_common(char ***argvp
, int isok
, enum ntype type
, bool icase
)
1565 char errbuf
[LINE_MAX
];
1567 char *regexp
= **argvp
;
1575 len
= strlen(regexp
) + 1 + 6;
1576 lineregexp
= malloc(len
); /* max needed */
1577 if (lineregexp
== NULL
)
1579 snprintf(lineregexp
, len
, "^%s(%s%s)$",
1580 (regcomp_flags
& REG_EXTENDED
) ? "" : "\\", regexp
,
1581 (regcomp_flags
& REG_EXTENDED
) ? "" : "\\");
1582 rv
= regcomp(®
, lineregexp
, REG_NOSUB
|regcomp_flags
|
1583 (icase
? REG_ICASE
: 0));
1586 regerror(rv
, ®
, errbuf
, sizeof errbuf
);
1587 errx(1, "regexp %s: %s", regexp
, errbuf
);
1590 new = palloc(type
, f_regex
);
1591 new->regexp_data
= reg
;
1596 c_regex(char ***argvp
, int isok
)
1599 return (c_regex_common(argvp
, isok
, N_REGEX
, false));
1603 c_iregex(char ***argvp
, int isok
)
1606 return (c_regex_common(argvp
, isok
, N_IREGEX
, true));
1610 * -size n[c] functions --
1612 * True if the file size in bytes, divided by an implementation defined
1613 * value and rounded up to the next integer, is n. If n is followed by
1614 * a c, the size is in bytes.
1616 #define FIND_SIZE 512
1617 static int divsize
= 1;
1620 f_size(PLAN
*plan
, FTSENT
*entry
)
1624 size
= divsize
? (entry
->fts_statp
->st_size
+ FIND_SIZE
- 1) /
1625 FIND_SIZE
: entry
->fts_statp
->st_size
;
1626 COMPARE(size
, plan
->o_data
);
1630 c_size(char ***argvp
, int isok
)
1632 char *arg
= **argvp
;
1637 ftsoptions
&= ~FTS_NOSTAT
;
1639 new = palloc(N_SIZE
, f_size
);
1641 new->o_data
= find_parsenum(new, "-size", arg
, &endch
);
1648 * -type c functions --
1650 * True if the type of the file is c, where c is b, c, d, p, f or w
1651 * for block special file, character special file, directory, FIFO,
1652 * regular file or whiteout respectively.
1655 f_type(PLAN
*plan
, FTSENT
*entry
)
1658 return ((entry
->fts_statp
->st_mode
& S_IFMT
) == plan
->m_data
);
1662 c_type(char ***argvp
, int isok
)
1664 char *typestring
= **argvp
;
1666 mode_t mask
= (mode_t
)0;
1669 ftsoptions
&= ~FTS_NOSTAT
;
1671 switch (typestring
[0]) {
1698 ftsoptions
|= FTS_WHITEOUT
;
1701 #endif /* S_IFWHT */
1703 errx(1, "-type: %s: unknown type", typestring
);
1706 new = palloc(N_TYPE
, f_type
);
1712 * -user uname functions --
1714 * True if the file belongs to the user uname. If uname is numeric and
1715 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1716 * return a valid user name, uname is taken as a user ID.
1719 f_user(PLAN
*plan
, FTSENT
*entry
)
1722 COMPARE(entry
->fts_statp
->st_uid
, plan
->u_data
);
1726 c_user(char ***argvp
, int isok
)
1728 char *username
= **argvp
;
1734 ftsoptions
&= ~FTS_NOSTAT
;
1736 new = palloc(N_USER
, f_user
);
1737 p
= getpwnam(username
);
1739 if (atoi(username
) == 0 && username
[0] != '0' &&
1740 strcmp(username
, "+0") && strcmp(username
, "-0"))
1741 errx(1, "-user: %s: no such user", username
);
1742 uid
= find_parsenum(new, "-user", username
, NULL
);
1745 new->flags
= F_EQUAL
;
1754 * -xdev functions --
1756 * Always true, causes find not to descend past directories that have a
1757 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1760 c_xdev(char ***argvp
, int isok
)
1762 ftsoptions
|= FTS_XDEV
;
1764 return (palloc(N_XDEV
, f_always_true
));
1768 * ( expression ) functions --
1770 * True if expression is true.
1773 f_expr(PLAN
*plan
, FTSENT
*entry
)
1779 for (p
= plan
->p_data
[0];
1780 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1785 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are
1786 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1787 * to a N_EXPR node containing the expression and the ')' node is discarded.
1790 c_openparen(char ***argvp
, int isok
)
1793 return (palloc(N_OPENPAREN
, (int (*)(PLAN
*, FTSENT
*))-1));
1797 c_closeparen(char ***argvp
, int isok
)
1800 return (palloc(N_CLOSEPAREN
, (int (*)(PLAN
*, FTSENT
*))-1));
1804 * ! expression functions --
1806 * Negation of a primary; the unary NOT operator.
1809 f_not(PLAN
*plan
, FTSENT
*entry
)
1815 for (p
= plan
->p_data
[0];
1816 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1821 c_not(char ***argvp
, int isok
)
1824 return (palloc(N_NOT
, f_not
));
1828 * expression -o expression functions --
1830 * Alternation of primaries; the OR operator. The second expression is
1831 * not evaluated if the first expression is true.
1834 f_or(PLAN
*plan
, FTSENT
*entry
)
1840 for (p
= plan
->p_data
[0];
1841 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1846 for (p
= plan
->p_data
[1];
1847 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1852 c_or(char ***argvp
, int isok
)
1855 return (palloc(N_OR
, f_or
));
1859 c_null(char ***argvp
, int isok
)
1868 * Check and see if the specified plan has any residual state,
1869 * and if so, clean it up as appropriate.
1871 * At the moment, only N_EXEC has state. Two kinds: 1)
1872 * lists of files to feed to subprocesses 2) State on exit
1873 * statusses of past subprocesses.
1877 plan_cleanup(PLAN
*plan
, void *arg
)
1879 if (plan
->type
==N_EXEC
&& plan
->ep_narg
)
1882 return plan
->ep_rval
; /* Passed save exit-status up chain */
1886 palloc(enum ntype t
, int (*f
)(PLAN
*, FTSENT
*))
1890 if ((new = malloc(sizeof(PLAN
))) == NULL
)
1892 memset(new, 0, sizeof(PLAN
));