1 /* $NetBSD: function.c,v 1.64 2007/07/19 07:49:30 daniel 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>
37 #include <sys/param.h>
40 #include <sys/mount.h>
66 #define COMPARE(a, b) { \
67 switch (plan->flags) { \
79 static int32_t find_parsenum(PLAN
*, const char *, const char *, char *);
80 static void run_f_exec(PLAN
*);
81 int f_always_true(PLAN
*, FTSENT
*);
82 int f_amin(PLAN
*, FTSENT
*);
83 int f_anewer(PLAN
*, FTSENT
*);
84 int f_atime(PLAN
*, FTSENT
*);
85 int f_cmin(PLAN
*, FTSENT
*);
86 int f_cnewer(PLAN
*, FTSENT
*);
87 int f_ctime(PLAN
*, FTSENT
*);
88 int f_delete(PLAN
*, FTSENT
*);
89 int f_empty(PLAN
*, FTSENT
*);
90 int f_exec(PLAN
*, FTSENT
*);
91 int f_execdir(PLAN
*, FTSENT
*);
92 int f_false(PLAN
*, FTSENT
*);
93 int f_flags(PLAN
*, FTSENT
*);
94 int f_fprint(PLAN
*, FTSENT
*);
95 int f_fstype(PLAN
*, FTSENT
*);
96 int f_group(PLAN
*, FTSENT
*);
97 int f_iname(PLAN
*, FTSENT
*);
98 int f_inum(PLAN
*, FTSENT
*);
99 int f_links(PLAN
*, FTSENT
*);
100 int f_ls(PLAN
*, FTSENT
*);
101 int f_mindepth(PLAN
*, FTSENT
*);
102 int f_maxdepth(PLAN
*, FTSENT
*);
103 int f_mmin(PLAN
*, FTSENT
*);
104 int f_mtime(PLAN
*, FTSENT
*);
105 int f_name(PLAN
*, FTSENT
*);
106 int f_newer(PLAN
*, FTSENT
*);
107 int f_nogroup(PLAN
*, FTSENT
*);
108 int f_nouser(PLAN
*, FTSENT
*);
109 int f_path(PLAN
*, FTSENT
*);
110 int f_perm(PLAN
*, FTSENT
*);
111 int f_print(PLAN
*, FTSENT
*);
112 int f_print0(PLAN
*, FTSENT
*);
113 int f_printx(PLAN
*, FTSENT
*);
114 int f_prune(PLAN
*, FTSENT
*);
115 int f_regex(PLAN
*, FTSENT
*);
116 int f_size(PLAN
*, FTSENT
*);
117 int f_type(PLAN
*, FTSENT
*);
118 int f_user(PLAN
*, FTSENT
*);
119 int f_not(PLAN
*, FTSENT
*);
120 int f_or(PLAN
*, FTSENT
*);
121 static PLAN
*c_regex_common(char ***, int, enum ntype
, bool);
122 static PLAN
*palloc(enum ntype
, int (*)(PLAN
*, FTSENT
*));
130 * Parse a string of the form [+-]# and return the value.
133 find_parsenum(PLAN
*plan
, const char *option
, const char *vp
, char *endch
)
137 char *endchar
; /* Pointer to character ending conversion. */
139 /* Determine comparison from leading + or -. */
144 plan
->flags
= F_GREATER
;
148 plan
->flags
= F_LESSTHAN
;
151 plan
->flags
= F_EQUAL
;
156 * Convert the string with strtol(). Note, if strtol() returns zero
157 * and endchar points to the beginning of the string we know we have
160 value
= strtol(str
, &endchar
, 10);
161 if (value
== 0 && endchar
== str
)
162 errx(1, "%s: %s: illegal numeric value", option
, vp
);
163 if (endchar
[0] && (endch
== NULL
|| endchar
[0] != *endch
))
164 errx(1, "%s: %s: illegal trailing character", option
, vp
);
171 * The value of n for the inode times (atime, ctime, and mtime) is a range,
172 * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with
173 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
174 * user wanted. Correct so that -1 is "less than 1".
176 #define TIME_CORRECT(p, ttype) \
177 if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \
181 * -amin n functions --
183 * True if the difference between the file access time and the
184 * current time is n 1 minute periods.
187 f_amin(PLAN
*plan
, FTSENT
*entry
)
189 COMPARE((now
- entry
->fts_statp
->st_atime
+
190 SECSPERMIN
- 1) / SECSPERMIN
, plan
->t_data
);
194 c_amin(char ***argvp
, int isok
)
200 ftsoptions
&= ~FTS_NOSTAT
;
202 new = palloc(N_AMIN
, f_amin
);
203 new->t_data
= find_parsenum(new, "-amin", arg
, NULL
);
204 TIME_CORRECT(new, N_AMIN
);
209 * -anewer file functions --
211 * True if the current file has been accessed more recently
212 * than the access time of the file named by the pathname
216 f_anewer(plan
, entry
)
221 return (entry
->fts_statp
->st_atime
> plan
->t_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->t_data
= sb
.st_atime
;
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
);
269 * -cmin n functions --
271 * True if the difference between the last change of file
272 * status information and the current time is n 24 hour periods.
275 f_cmin(PLAN
*plan
, FTSENT
*entry
)
277 COMPARE((now
- entry
->fts_statp
->st_ctime
+
278 SECSPERMIN
- 1) / SECSPERMIN
, plan
->t_data
);
282 c_cmin(char ***argvp
, int isok
)
288 ftsoptions
&= ~FTS_NOSTAT
;
290 new = palloc(N_CMIN
, f_cmin
);
291 new->t_data
= find_parsenum(new, "-cmin", arg
, NULL
);
292 TIME_CORRECT(new, N_CMIN
);
297 * -cnewer file functions --
299 * True if the current file has been changed more recently
300 * than the changed time of the file named by the pathname
304 f_cnewer(PLAN
*plan
, FTSENT
*entry
)
307 return (entry
->fts_statp
->st_ctime
> plan
->t_data
);
311 c_cnewer(char ***argvp
, int isok
)
313 char *filename
= **argvp
;
318 ftsoptions
&= ~FTS_NOSTAT
;
320 if (stat(filename
, &sb
))
321 err(1, "%s", filename
);
322 new = palloc(N_CNEWER
, f_cnewer
);
323 new->t_data
= sb
.st_ctime
;
328 * -ctime n functions --
330 * True if the difference between the last change of file
331 * status information and the current time is n 24 hour periods.
334 f_ctime(PLAN
*plan
, FTSENT
*entry
)
336 COMPARE((now
- entry
->fts_statp
->st_ctime
+
337 SECSPERDAY
- 1) / SECSPERDAY
, plan
->t_data
);
341 c_ctime(char ***argvp
, int isok
)
347 ftsoptions
&= ~FTS_NOSTAT
;
349 new = palloc(N_CTIME
, f_ctime
);
350 new->t_data
= find_parsenum(new, "-ctime", arg
, NULL
);
351 TIME_CORRECT(new, N_CTIME
);
356 * -delete functions --
358 * True always. Makes its best shot and continues on regardless.
361 f_delete(PLAN
*plan __unused
, FTSENT
*entry
)
363 /* ignore these from fts */
364 if (strcmp(entry
->fts_accpath
, ".") == 0 ||
365 strcmp(entry
->fts_accpath
, "..") == 0)
369 if (isdepth
== 0 || /* depth off */
370 (ftsoptions
& FTS_NOSTAT
) || /* not stat()ing */
371 !(ftsoptions
& FTS_PHYSICAL
) || /* physical off */
372 (ftsoptions
& FTS_LOGICAL
)) /* or finally, logical on */
373 errx(1, "-delete: insecure options got turned on");
375 /* Potentially unsafe - do not accept relative paths whatsoever */
376 if (strchr(entry
->fts_accpath
, '/') != NULL
)
377 errx(1, "-delete: %s: relative path potentially not safe",
381 /* Turn off user immutable bits if running as root */
382 if ((entry
->fts_statp
->st_flags
& (UF_APPEND
|UF_IMMUTABLE
)) &&
383 !(entry
->fts_statp
->st_flags
& (SF_APPEND
|SF_IMMUTABLE
)) &&
385 chflags(entry
->fts_accpath
,
386 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
= fork()) {
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
= fork()) {
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
= fork()) {
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
));
862 * -flags [-]flags functions --
865 f_flags(PLAN
*plan
, FTSENT
*entry
)
869 flags
= entry
->fts_statp
->st_flags
;
870 if (plan
->flags
== F_ATLEAST
)
871 return ((plan
->f_data
| flags
) == flags
);
873 return (flags
== plan
->f_data
);
874 /* MINIX has no file flags. */
880 c_flags(char ***argvp
, int isok
)
882 char *flags
= **argvp
;
887 ftsoptions
&= ~FTS_NOSTAT
;
889 new = palloc(N_FLAGS
, f_flags
);
892 new->flags
= F_ATLEAST
;
897 if ((strcmp(flags
, "none") != 0) &&
898 (string_to_flags(&flags
, &flagset
, NULL
) != 0))
899 errx(1, "-flags: %s: illegal flags string", flags
);
900 new->f_data
= flagset
;
906 * -follow functions --
908 * Always true, causes symbolic links to be followed on a global
912 c_follow(char ***argvp
, int isok
)
914 ftsoptions
&= ~FTS_PHYSICAL
;
915 ftsoptions
|= FTS_LOGICAL
;
917 return (palloc(N_FOLLOW
, f_always_true
));
920 /* -fprint functions --
922 * Causes the current pathame to be written to the defined output file.
925 f_fprint(PLAN
*plan
, FTSENT
*entry
)
928 if (-1 == fprintf(plan
->fprint_file
, "%s\n", entry
->fts_path
))
933 /* no descriptors are closed; they will be closed by
934 operating system when this find command exits. */
938 c_fprint(char ***argvp
, int isok
)
942 isoutput
= 1; /* do not assume -print */
944 new = palloc(N_FPRINT
, f_fprint
);
946 if (NULL
== (new->fprint_file
= fopen(**argvp
, "w")))
947 err(1, "-fprint: %s: cannot create file", **argvp
);
954 * -fstype functions --
956 * True if the file is of a certain type.
960 f_fstype(PLAN
*plan
, FTSENT
*entry
)
962 static dev_t curdev
; /* need a guaranteed illegal dev value */
963 static int first
= 1;
966 static char fstype
[sizeof(sb
.f_fstypename
)];
969 memset(&save
, 0, sizeof save
); /* XXX gcc */
971 /* Only check when we cross mount point. */
972 if (first
|| curdev
!= entry
->fts_statp
->st_dev
) {
973 curdev
= entry
->fts_statp
->st_dev
;
976 * Statfs follows symlinks; find wants the link's file system,
977 * not where it points.
979 if (entry
->fts_info
== FTS_SL
||
980 entry
->fts_info
== FTS_SLNONE
) {
981 if ((p
= strrchr(entry
->fts_accpath
, '/')) != NULL
)
984 p
= entry
->fts_accpath
;
993 if (statvfs(entry
->fts_accpath
, &sb
))
994 err(1, "%s", entry
->fts_accpath
);
1004 * Further tests may need both of these values, so
1005 * always copy both of them.
1008 strlcpy(fstype
, sb
.f_fstypename
, sizeof(fstype
));
1010 switch (plan
->flags
) {
1012 return (val
& plan
->mt_data
);
1014 return (strncmp(fstype
, plan
->c_data
, sizeof(fstype
)) == 0);
1021 c_fstype(char ***argvp
, int isok
)
1023 char *arg
= **argvp
;
1027 ftsoptions
&= ~FTS_NOSTAT
;
1029 new = palloc(N_FSTYPE
, f_fstype
);
1033 if (!strcmp(arg
, "local")) {
1034 new->flags
= F_MTFLAG
;
1035 new->mt_data
= MNT_LOCAL
;
1040 if (!strcmp(arg
, "rdonly")) {
1041 new->flags
= F_MTFLAG
;
1042 new->mt_data
= MNT_RDONLY
;
1048 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
);
1219 * -mmin n functions --
1221 * True if the difference between the file modification time and the
1222 * current time is n 24 hour periods.
1225 f_mmin(PLAN
*plan
, FTSENT
*entry
)
1227 COMPARE((now
- entry
->fts_statp
->st_mtime
+ SECSPERMIN
- 1) /
1228 SECSPERMIN
, plan
->t_data
);
1232 c_mmin(char ***argvp
, int isok
)
1234 char *arg
= **argvp
;
1238 ftsoptions
&= ~FTS_NOSTAT
;
1240 new = palloc(N_MMIN
, f_mmin
);
1241 new->t_data
= find_parsenum(new, "-mmin", arg
, NULL
);
1242 TIME_CORRECT(new, N_MMIN
);
1246 * -mtime n functions --
1248 * True if the difference between the file modification time and the
1249 * current time is n 24 hour periods.
1252 f_mtime(PLAN
*plan
, FTSENT
*entry
)
1254 COMPARE((now
- entry
->fts_statp
->st_mtime
+ SECSPERDAY
- 1) /
1255 SECSPERDAY
, plan
->t_data
);
1259 c_mtime(char ***argvp
, int isok
)
1261 char *arg
= **argvp
;
1265 ftsoptions
&= ~FTS_NOSTAT
;
1267 new = palloc(N_MTIME
, f_mtime
);
1268 new->t_data
= find_parsenum(new, "-mtime", arg
, NULL
);
1269 TIME_CORRECT(new, N_MTIME
);
1274 * -name functions --
1276 * True if the basename of the filename being examined
1277 * matches pattern using Pattern Matching Notation S3.14
1280 f_name(PLAN
*plan
, FTSENT
*entry
)
1283 return (!fnmatch(plan
->c_data
, entry
->fts_name
, 0));
1287 c_name(char ***argvp
, int isok
)
1289 char *pattern
= **argvp
;
1293 new = palloc(N_NAME
, f_name
);
1294 new->c_data
= pattern
;
1299 * -iname functions --
1301 * Similar to -name, but does case insensitive matching
1305 f_iname(PLAN
*plan
, FTSENT
*entry
)
1307 return (!fnmatch(plan
->c_data
, entry
->fts_name
, FNM_CASEFOLD
));
1311 c_iname(char ***argvp
, int isok
)
1313 char *pattern
= **argvp
;
1317 new = palloc(N_INAME
, f_iname
);
1318 new->c_data
= pattern
;
1323 * -newer file functions --
1325 * True if the current file has been modified more recently
1326 * than the modification time of the file named by the pathname
1330 f_newer(PLAN
*plan
, FTSENT
*entry
)
1333 return (entry
->fts_statp
->st_mtime
> plan
->t_data
);
1337 c_newer(char ***argvp
, int isok
)
1339 char *filename
= **argvp
;
1344 ftsoptions
&= ~FTS_NOSTAT
;
1346 if (stat(filename
, &sb
))
1347 err(1, "%s", filename
);
1348 new = palloc(N_NEWER
, f_newer
);
1349 new->t_data
= sb
.st_mtime
;
1354 * -nogroup functions --
1356 * True if file belongs to a user ID for which the equivalent
1357 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
1360 f_nogroup(PLAN
*plan
, FTSENT
*entry
)
1363 return (group_from_gid(entry
->fts_statp
->st_gid
, 1) ? 0 : 1);
1367 c_nogroup(char ***argvp
, int isok
)
1369 ftsoptions
&= ~FTS_NOSTAT
;
1371 return (palloc(N_NOGROUP
, f_nogroup
));
1375 * -nouser functions --
1377 * True if file belongs to a user ID for which the equivalent
1378 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
1381 f_nouser(PLAN
*plan
, FTSENT
*entry
)
1384 return (user_from_uid(entry
->fts_statp
->st_uid
, 1) ? 0 : 1);
1388 c_nouser(char ***argvp
, int isok
)
1390 ftsoptions
&= ~FTS_NOSTAT
;
1392 return (palloc(N_NOUSER
, f_nouser
));
1396 * -path functions --
1398 * True if the path of the filename being examined
1399 * matches pattern using Pattern Matching Notation S3.14
1402 f_path(PLAN
*plan
, FTSENT
*entry
)
1405 return (!fnmatch(plan
->c_data
, entry
->fts_path
, 0));
1409 c_path(char ***argvp
, int isok
)
1411 char *pattern
= **argvp
;
1415 new = palloc(N_NAME
, f_path
);
1416 new->c_data
= pattern
;
1421 * -perm functions --
1423 * The mode argument is used to represent file mode bits. If it starts
1424 * with a leading digit, it's treated as an octal mode, otherwise as a
1428 f_perm(PLAN
*plan
, FTSENT
*entry
)
1432 mode
= entry
->fts_statp
->st_mode
&
1437 |S_IRWXU
|S_IRWXG
|S_IRWXO
);
1438 if (plan
->flags
== F_ATLEAST
)
1439 return ((plan
->m_data
| mode
) == mode
);
1440 else if (plan
->flags
== F_ANY
)
1441 return ((plan
->m_data
& mode
) != 0);
1443 return (mode
== plan
->m_data
);
1448 c_perm(char ***argvp
, int isok
)
1450 char *perm
= **argvp
;
1455 ftsoptions
&= ~FTS_NOSTAT
;
1457 new = palloc(N_PERM
, f_perm
);
1460 new->flags
= F_ATLEAST
;
1462 } else if (*perm
== '+') {
1467 if ((set
= setmode(perm
)) == NULL
)
1468 err(1, "-perm: Cannot set file mode `%s'", perm
);
1470 new->m_data
= getmode(set
, 0);
1476 * -print functions --
1478 * Always true, causes the current pathame to be written to
1482 f_print(PLAN
*plan
, FTSENT
*entry
)
1485 (void)printf("%s\n", entry
->fts_path
);
1490 f_print0(PLAN
*plan
, FTSENT
*entry
)
1493 (void)fputs(entry
->fts_path
, stdout
);
1494 (void)fputc('\0', stdout
);
1499 f_printx(PLAN
*plan
, FTSENT
*entry
)
1503 for (cp
= entry
->fts_path
; *cp
; cp
++) {
1504 if (*cp
== '\'' || *cp
== '\"' || *cp
== ' ' ||
1505 *cp
== '$' || *cp
== '`' ||
1506 *cp
== '\t' || *cp
== '\n' || *cp
== '\\')
1507 fputc('\\', stdout
);
1512 fputc('\n', stdout
);
1517 c_print(char ***argvp
, int isok
)
1522 return (palloc(N_PRINT
, f_print
));
1526 c_print0(char ***argvp
, int isok
)
1531 return (palloc(N_PRINT0
, f_print0
));
1535 c_printx(char ***argvp
, int isok
)
1540 return (palloc(N_PRINTX
, f_printx
));
1544 * -prune functions --
1546 * Prune a portion of the hierarchy.
1549 f_prune(PLAN
*plan
, FTSENT
*entry
)
1551 if (fts_set(tree
, entry
, FTS_SKIP
))
1552 err(1, "%s", entry
->fts_path
);
1557 c_prune(char ***argvp
, int isok
)
1560 return (palloc(N_PRUNE
, f_prune
));
1564 * -regex regexp (and related) functions --
1566 * True if the complete file path matches the regular expression regexp.
1567 * For -regex, regexp is a case-sensitive (basic) regular expression.
1568 * For -iregex, regexp is a case-insensitive (basic) regular expression.
1571 f_regex(PLAN
*plan
, FTSENT
*entry
)
1574 return (regexec(&plan
->regexp_data
, entry
->fts_path
, 0, NULL
, 0) == 0);
1578 c_regex_common(char ***argvp
, int isok
, enum ntype type
, bool icase
)
1582 char *regexp
= **argvp
;
1590 len
= strlen(regexp
) + 1 + 6;
1591 lineregexp
= malloc(len
); /* max needed */
1592 if (lineregexp
== NULL
)
1594 snprintf(lineregexp
, len
, "^%s(%s%s)$",
1595 (regcomp_flags
& REG_EXTENDED
) ? "" : "\\", regexp
,
1596 (regcomp_flags
& REG_EXTENDED
) ? "" : "\\");
1597 rv
= regcomp(®
, lineregexp
, REG_NOSUB
|regcomp_flags
|
1598 (icase
? REG_ICASE
: 0));
1601 regerror(rv
, ®
, errbuf
, sizeof errbuf
);
1602 errx(1, "regexp %s: %s", regexp
, errbuf
);
1605 new = palloc(type
, f_regex
);
1606 new->regexp_data
= reg
;
1611 c_regex(char ***argvp
, int isok
)
1614 return (c_regex_common(argvp
, isok
, N_REGEX
, false));
1618 c_iregex(char ***argvp
, int isok
)
1621 return (c_regex_common(argvp
, isok
, N_IREGEX
, true));
1625 * -size n[c] functions --
1627 * True if the file size in bytes, divided by an implementation defined
1628 * value and rounded up to the next integer, is n. If n is followed by
1629 * a c, the size is in bytes.
1631 #define FIND_SIZE 512
1632 static int divsize
= 1;
1635 f_size(PLAN
*plan
, FTSENT
*entry
)
1639 size
= divsize
? (entry
->fts_statp
->st_size
+ FIND_SIZE
- 1) /
1640 FIND_SIZE
: entry
->fts_statp
->st_size
;
1641 COMPARE(size
, plan
->o_data
);
1645 c_size(char ***argvp
, int isok
)
1647 char *arg
= **argvp
;
1652 ftsoptions
&= ~FTS_NOSTAT
;
1654 new = palloc(N_SIZE
, f_size
);
1656 new->o_data
= find_parsenum(new, "-size", arg
, &endch
);
1663 * -type c functions --
1665 * True if the type of the file is c, where c is b, c, d, p, f or w
1666 * for block special file, character special file, directory, FIFO,
1667 * regular file or whiteout respectively.
1670 f_type(PLAN
*plan
, FTSENT
*entry
)
1673 return ((entry
->fts_statp
->st_mode
& S_IFMT
) == plan
->m_data
);
1677 c_type(char ***argvp
, int isok
)
1679 char *typestring
= **argvp
;
1681 mode_t mask
= (mode_t
)0;
1684 ftsoptions
&= ~FTS_NOSTAT
;
1686 switch (typestring
[0]) {
1715 ftsoptions
|= FTS_WHITEOUT
;
1718 #endif /* S_IFWHT */
1720 errx(1, "-type: %s: unknown type", typestring
);
1723 new = palloc(N_TYPE
, f_type
);
1729 * -user uname functions --
1731 * True if the file belongs to the user uname. If uname is numeric and
1732 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1733 * return a valid user name, uname is taken as a user ID.
1736 f_user(PLAN
*plan
, FTSENT
*entry
)
1739 COMPARE(entry
->fts_statp
->st_uid
, plan
->u_data
);
1743 c_user(char ***argvp
, int isok
)
1745 char *username
= **argvp
;
1751 ftsoptions
&= ~FTS_NOSTAT
;
1753 new = palloc(N_USER
, f_user
);
1754 p
= getpwnam(username
);
1756 if (atoi(username
) == 0 && username
[0] != '0' &&
1757 strcmp(username
, "+0") && strcmp(username
, "-0"))
1758 errx(1, "-user: %s: no such user", username
);
1759 uid
= find_parsenum(new, "-user", username
, NULL
);
1762 new->flags
= F_EQUAL
;
1771 * -xdev functions --
1773 * Always true, causes find not to descend past directories that have a
1774 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1777 c_xdev(char ***argvp
, int isok
)
1779 ftsoptions
|= FTS_XDEV
;
1781 return (palloc(N_XDEV
, f_always_true
));
1785 * ( expression ) functions --
1787 * True if expression is true.
1790 f_expr(PLAN
*plan
, FTSENT
*entry
)
1796 for (p
= plan
->p_data
[0];
1797 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1802 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are
1803 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1804 * to a N_EXPR node containing the expression and the ')' node is discarded.
1807 c_openparen(char ***argvp
, int isok
)
1810 return (palloc(N_OPENPAREN
, (int (*)(PLAN
*, FTSENT
*))-1));
1814 c_closeparen(char ***argvp
, int isok
)
1817 return (palloc(N_CLOSEPAREN
, (int (*)(PLAN
*, FTSENT
*))-1));
1821 * ! expression functions --
1823 * Negation of a primary; the unary NOT operator.
1826 f_not(PLAN
*plan
, FTSENT
*entry
)
1832 for (p
= plan
->p_data
[0];
1833 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1838 c_not(char ***argvp
, int isok
)
1841 return (palloc(N_NOT
, f_not
));
1845 * expression -o expression functions --
1847 * Alternation of primaries; the OR operator. The second expression is
1848 * not evaluated if the first expression is true.
1851 f_or(PLAN
*plan
, FTSENT
*entry
)
1857 for (p
= plan
->p_data
[0];
1858 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1863 for (p
= plan
->p_data
[1];
1864 p
&& (state
= (p
->eval
)(p
, entry
)); p
= p
->next
);
1869 c_or(char ***argvp
, int isok
)
1872 return (palloc(N_OR
, f_or
));
1876 c_null(char ***argvp
, int isok
)
1885 * Check and see if the specified plan has any residual state,
1886 * and if so, clean it up as appropriate.
1888 * At the moment, only N_EXEC has state. Two kinds: 1)
1889 * lists of files to feed to subprocesses 2) State on exit
1890 * statusses of past subprocesses.
1894 plan_cleanup(PLAN
*plan
, void *arg
)
1896 if (plan
->type
==N_EXEC
&& plan
->ep_narg
)
1899 return plan
->ep_rval
; /* Passed save exit-status up chain */
1903 palloc(enum ntype t
, int (*f
)(PLAN
*, FTSENT
*))
1907 if ((new = malloc(sizeof(PLAN
))) == NULL
)
1909 memset(new, 0, sizeof(PLAN
));