2 * Copyright (c) 1990, 1993
3 * The Regents of the University of California. All rights reserved.
5 * This code is derived from software contributed to Berkeley by
6 * Cimarron D. Taylor of the University of California, Berkeley.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 static char sccsid[] = "@(#)function.c 8.10 (Berkeley) 5/4/95";
41 #include <sys/param.h>
42 #include <sys/ucred.h>
45 #include <sys/mount.h>
60 #define COMPARE(a, b) { \
61 switch (plan->flags) { \
73 static PLAN *palloc __P((enum ntype, int (*) __P((PLAN *, FTSENT *))));
77 * Parse a string of the form [+-]# and return the value.
80 find_parsenum(plan, option, vp, endch)
82 char *option, *vp, *endch;
85 char *endchar, *str; /* Pointer to character ending conversion. */
87 /* Determine comparison from leading + or -. */
92 plan->flags = F_GREATER;
96 plan->flags = F_LESSTHAN;
99 plan->flags = F_EQUAL;
104 * Convert the string with strtoq(). Note, if strtoq() returns zero
105 * and endchar points to the beginning of the string we know we have
108 value = strtoq(str, &endchar, 10);
109 if (value == 0 && endchar == str)
110 errx(1, "%s: %s: illegal numeric value", option, vp);
111 if (endchar[0] && (endch == NULL || endchar[0] != *endch))
112 errx(1, "%s: %s: illegal trailing character", option, vp);
119 * The value of n for the inode times (atime, ctime, and mtime) is a range,
120 * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with
121 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
122 * user wanted. Correct so that -1 is "less than 1".
124 #define TIME_CORRECT(p, ttype) \
125 if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \
129 * -amin n functions --
131 * True if the difference between the file access time and the
132 * current time is n min periods.
141 COMPARE((now - entry->fts_statp->st_atime +
142 60 - 1) / 60, plan->t_data);
151 ftsoptions &= ~FTS_NOSTAT;
153 new = palloc(N_AMIN, f_amin);
154 new->t_data = find_parsenum(new, "-amin", arg, NULL);
155 TIME_CORRECT(new, N_AMIN);
161 * -atime n functions --
163 * True if the difference between the file access time and the
164 * current time is n 24 hour periods.
173 COMPARE((now - entry->fts_statp->st_atime +
174 86400 - 1) / 86400, plan->t_data);
183 ftsoptions &= ~FTS_NOSTAT;
185 new = palloc(N_ATIME, f_atime);
186 new->t_data = find_parsenum(new, "-atime", arg, NULL);
187 TIME_CORRECT(new, N_ATIME);
193 * -cmin n functions --
195 * True if the difference between the last change of file
196 * status information and the current time is n min periods.
205 COMPARE((now - entry->fts_statp->st_ctime +
206 60 - 1) / 60, plan->t_data);
215 ftsoptions &= ~FTS_NOSTAT;
217 new = palloc(N_CMIN, f_cmin);
218 new->t_data = find_parsenum(new, "-cmin", arg, NULL);
219 TIME_CORRECT(new, N_CMIN);
224 * -ctime n functions --
226 * True if the difference between the last change of file
227 * status information and the current time is n 24 hour periods.
236 COMPARE((now - entry->fts_statp->st_ctime +
237 86400 - 1) / 86400, plan->t_data);
246 ftsoptions &= ~FTS_NOSTAT;
248 new = palloc(N_CTIME, f_ctime);
249 new->t_data = find_parsenum(new, "-ctime", arg, NULL);
250 TIME_CORRECT(new, N_CTIME);
256 * -depth functions --
258 * Always true, causes descent of the directory hierarchy to be done
259 * so that all entries in a directory are acted on before the directory
263 f_always_true(plan, entry)
275 return (palloc(N_DEPTH, f_always_true));
279 * [-exec | -ok] utility [arg ... ] ; functions --
281 * True if the executed utility returns a zero value as exit status.
282 * The end of the primary expression is delimited by a semicolon. If
283 * "{}" occurs anywhere, it gets replaced by the current pathname.
284 * The current directory for the execution of utility is the same as
285 * the current directory when the find utility was started.
287 * The primary -ok is different in that it requests affirmation of the
288 * user before executing the utility.
300 for (cnt = 0; plan->e_argv[cnt]; ++cnt)
301 if (plan->e_len[cnt])
302 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
303 entry->fts_path, plan->e_len[cnt]);
305 if (plan->flags == F_NEEDOK && !queryuser(plan->e_argv))
308 /* make sure find output is interspersed correctly with subprocesses */
311 switch (pid = fork()) {
320 execvp(plan->e_argv[0], plan->e_argv);
321 warn("%s", plan->e_argv[0]);
324 pid = waitpid(pid, &status, 0);
325 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
330 * build three parallel arrays, one with pointers to the strings passed
331 * on the command line, one with (possibly duplicated) pointers to the
332 * argv array, and one with integer values that are lengths of the
333 * strings, but also flags meaning that the string has to be massaged.
340 PLAN *new; /* node returned */
342 register char **argv, **ap, *p;
346 new = palloc(N_EXEC, f_exec);
348 new->flags = F_NEEDOK;
350 for (ap = argv = *argvp;; ++ap) {
353 "%s: no terminating \";\"", isok ? "-ok" : "-exec");
358 cnt = ap - *argvp + 1;
359 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
360 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
361 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
363 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
364 new->e_orig[cnt] = *argv;
365 for (p = *argv; *p; ++p)
366 if (p[0] == '{' && p[1] == '}') {
367 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN);
368 new->e_len[cnt] = MAXPATHLEN;
372 new->e_argv[cnt] = *argv;
376 new->e_argv[cnt] = new->e_orig[cnt] = NULL;
383 * -execdir utility [arg ... ] ; functions --
385 * True if the executed utility returns a zero value as exit status.
386 * The end of the primary expression is delimited by a semicolon. If
387 * "{}" occurs anywhere, it gets replaced by the unqualified pathname.
388 * The current directory for the execution of utility is the same as
389 * the directory where the file lives.
392 f_execdir(plan, entry)
402 /* XXX - if file/dir ends in '/' this will not work -- can it? */
403 if ((file = strrchr(entry->fts_path, '/')))
406 file = entry->fts_path;
408 for (cnt = 0; plan->e_argv[cnt]; ++cnt)
409 if (plan->e_len[cnt])
410 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
411 file, plan->e_len[cnt]);
413 /* don't mix output of command with find output */
417 switch (pid = fork()) {
422 execvp(plan->e_argv[0], plan->e_argv);
423 warn("%s", plan->e_argv[0]);
426 pid = waitpid(pid, &status, 0);
427 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
432 * build three parallel arrays, one with pointers to the strings passed
433 * on the command line, one with (possibly duplicated) pointers to the
434 * argv array, and one with integer values that are lengths of the
435 * strings, but also flags meaning that the string has to be massaged.
441 PLAN *new; /* node returned */
443 register char **argv, **ap, *p;
445 ftsoptions &= ~FTS_NOSTAT;
448 new = palloc(N_EXECDIR, f_execdir);
450 for (ap = argv = *argvp;; ++ap) {
453 "-execdir: no terminating \";\"");
458 cnt = ap - *argvp + 1;
459 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
460 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
461 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
463 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
464 new->e_orig[cnt] = *argv;
465 for (p = *argv; *p; ++p)
466 if (p[0] == '{' && p[1] == '}') {
467 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN);
468 new->e_len[cnt] = MAXPATHLEN;
472 new->e_argv[cnt] = *argv;
476 new->e_argv[cnt] = new->e_orig[cnt] = NULL;
483 * -follow functions --
485 * Always true, causes symbolic links to be followed on a global
491 ftsoptions &= ~FTS_PHYSICAL;
492 ftsoptions |= FTS_LOGICAL;
494 return (palloc(N_FOLLOW, f_always_true));
498 * -fstype functions --
500 * True if the file is of a certain type.
503 f_fstype(plan, entry)
507 static dev_t curdev; /* need a guaranteed illegal dev value */
508 static int first = 1;
510 static int val_type, val_flags;
513 /* Only check when we cross mount point. */
514 if (first || curdev != entry->fts_statp->st_dev) {
515 curdev = entry->fts_statp->st_dev;
518 * Statfs follows symlinks; find wants the link's file system,
519 * not where it points.
521 if (entry->fts_info == FTS_SL ||
522 entry->fts_info == FTS_SLNONE) {
523 if ((p = strrchr(entry->fts_accpath, '/')) != NULL)
526 p = entry->fts_accpath;
535 if (statfs(entry->fts_accpath, &sb))
536 err(1, "%s", entry->fts_accpath);
546 * Further tests may need both of these values, so
547 * always copy both of them.
549 val_flags = sb.f_flags;
550 val_type = sb.f_type;
552 switch (plan->flags) {
554 return (val_flags & plan->mt_data) != 0;
556 return (val_type == plan->mt_data);
562 #if !defined(__NetBSD__)
570 ftsoptions &= ~FTS_NOSTAT;
572 new = palloc(N_FSTYPE, f_fstype);
575 * Check first for a filesystem name.
577 if (getvfsbyname(arg, &vfc) == 0) {
578 new->flags = F_MTTYPE;
579 new->mt_data = vfc.vfc_typenum;
585 if (!strcmp(arg, "local")) {
586 new->flags = F_MTFLAG;
587 new->mt_data = MNT_LOCAL;
592 if (!strcmp(arg, "rdonly")) {
593 new->flags = F_MTFLAG;
594 new->mt_data = MNT_RDONLY;
599 errx(1, "%s: unknown file type", arg);
605 * -group gname functions --
607 * True if the file belongs to the group gname. If gname is numeric and
608 * an equivalent of the getgrnam() function does not return a valid group
609 * name, gname is taken as a group ID.
616 return (entry->fts_statp->st_gid == plan->g_data);
627 ftsoptions &= ~FTS_NOSTAT;
632 if (gid == 0 && gname[0] != '0')
633 errx(1, "-group: %s: no such group", gname);
637 new = palloc(N_GROUP, f_group);
643 * -inum n functions --
645 * True if the file has inode # n.
652 COMPARE(entry->fts_statp->st_ino, plan->i_data);
661 ftsoptions &= ~FTS_NOSTAT;
663 new = palloc(N_INUM, f_inum);
664 new->i_data = find_parsenum(new, "-inum", arg, NULL);
669 * -links n functions --
671 * True if the file has n links.
678 COMPARE(entry->fts_statp->st_nlink, plan->l_data);
687 ftsoptions &= ~FTS_NOSTAT;
689 new = palloc(N_LINKS, f_links);
690 new->l_data = (nlink_t)find_parsenum(new, "-links", arg, NULL);
697 * Always true - prints the current entry to stdout in "ls" format.
704 printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
711 ftsoptions &= ~FTS_NOSTAT;
714 return (palloc(N_LS, f_ls));
718 * -mtime n functions --
720 * True if the difference between the file modification time and the
721 * current time is n 24 hour periods.
730 COMPARE((now - entry->fts_statp->st_mtime + 86400 - 1) /
731 86400, plan->t_data);
740 ftsoptions &= ~FTS_NOSTAT;
742 new = palloc(N_MTIME, f_mtime);
743 new->t_data = find_parsenum(new, "-mtime", arg, NULL);
744 TIME_CORRECT(new, N_MTIME);
749 * -mmin n functions --
751 * True if the difference between the file modification time and the
752 * current time is n min periods.
761 COMPARE((now - entry->fts_statp->st_mtime + 60 - 1) /
771 ftsoptions &= ~FTS_NOSTAT;
773 new = palloc(N_MMIN, f_mmin);
774 new->t_data = find_parsenum(new, "-mmin", arg, NULL);
775 TIME_CORRECT(new, N_MMIN);
783 * True if the basename of the filename being examined
784 * matches pattern using Pattern Matching Notation S3.14
791 return (!fnmatch(plan->c_data, entry->fts_name, 0));
800 new = palloc(N_NAME, f_name);
801 new->c_data = pattern;
806 * -newer file functions --
808 * True if the current file has been modified more recently
809 * then the modification time of the file named by the pathname
817 return (entry->fts_statp->st_mtime > plan->t_data);
827 ftsoptions &= ~FTS_NOSTAT;
829 if (stat(filename, &sb))
830 err(1, "%s", filename);
831 new = palloc(N_NEWER, f_newer);
832 new->t_data = sb.st_mtime;
837 * -nogroup functions --
839 * True if file belongs to a user ID for which the equivalent
840 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
843 f_nogroup(plan, entry)
847 char *group_from_gid();
849 return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1);
855 ftsoptions &= ~FTS_NOSTAT;
857 return (palloc(N_NOGROUP, f_nogroup));
861 * -nouser functions --
863 * True if file belongs to a user ID for which the equivalent
864 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
867 f_nouser(plan, entry)
871 char *user_from_uid();
873 return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1);
879 ftsoptions &= ~FTS_NOSTAT;
881 return (palloc(N_NOUSER, f_nouser));
887 * True if the path of the filename being examined
888 * matches pattern using Pattern Matching Notation S3.14
895 return (!fnmatch(plan->c_data, entry->fts_path, 0));
904 new = palloc(N_NAME, f_path);
905 new->c_data = pattern;
912 * The mode argument is used to represent file mode bits. If it starts
913 * with a leading digit, it's treated as an octal mode, otherwise as a
923 mode = entry->fts_statp->st_mode &
924 (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
925 if (plan->flags == F_ATLEAST)
926 return ((plan->m_data | mode) == mode);
928 return (mode == plan->m_data);
939 ftsoptions &= ~FTS_NOSTAT;
941 new = palloc(N_PERM, f_perm);
944 new->flags = F_ATLEAST;
948 if ((set = setmode(perm)) == NULL)
949 err(1, "-perm: %s: illegal mode string", perm);
951 new->m_data = getmode(set, 0);
957 * -print functions --
959 * Always true, causes the current pathame to be written to
967 (void)puts(entry->fts_path);
976 return (palloc(N_PRINT, f_print));
980 * -print0 functions --
982 * Always true, causes the current pathame to be written to
983 * standard output followed by a NUL character
986 f_print0(plan, entry)
990 fputs(entry->fts_path, stdout);
1000 return (palloc(N_PRINT0, f_print0));
1004 * -prune functions --
1006 * Prune a portion of the hierarchy.
1009 f_prune(plan, entry)
1015 if (fts_set(tree, entry, FTS_SKIP))
1016 err(1, "%s", entry->fts_path);
1023 return (palloc(N_PRUNE, f_prune));
1027 * -size n[c] functions --
1029 * True if the file size in bytes, divided by an implementation defined
1030 * value and rounded up to the next integer, is n. If n is followed by
1031 * a c, the size is in bytes.
1033 #define FIND_SIZE 512
1034 static int divsize = 1;
1043 size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
1044 FIND_SIZE : entry->fts_statp->st_size;
1045 COMPARE(size, plan->o_data);
1055 ftsoptions &= ~FTS_NOSTAT;
1057 new = palloc(N_SIZE, f_size);
1059 new->o_data = find_parsenum(new, "-size", arg, &endch);
1066 * -type c functions --
1068 * True if the type of the file is c, where c is b, c, d, p, f or w
1069 * for block special file, character special file, directory, FIFO,
1070 * regular file or whiteout respectively.
1077 return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data);
1087 ftsoptions &= ~FTS_NOSTAT;
1089 switch (typestring[0]) {
1114 ftsoptions |= FTS_WHITEOUT;
1116 #endif /* FTS_WHITEOUT */
1118 errx(1, "-type: %s: unknown type", typestring);
1121 new = palloc(N_TYPE, f_type);
1127 * -delete functions --
1129 * True always. Makes it's best shot and continues on regardless.
1132 f_delete(plan, entry)
1136 /* ignore these from fts */
1137 if (strcmp(entry->fts_accpath, ".") == 0 ||
1138 strcmp(entry->fts_accpath, "..") == 0)
1142 if (isdepth == 0 || /* depth off */
1143 (ftsoptions & FTS_NOSTAT) || /* not stat()ing */
1144 !(ftsoptions & FTS_PHYSICAL) || /* physical off */
1145 (ftsoptions & FTS_LOGICAL)) /* or finally, logical on */
1146 errx(1, "-delete: insecure options got turned on");
1148 /* Potentially unsafe - do not accept relative paths whatsoever */
1149 if (strchr(entry->fts_accpath, '/') != NULL)
1150 errx(1, "-delete: %s: relative path potentially not safe",
1151 entry->fts_accpath);
1153 /* Turn off user immutable bits if running as root */
1154 if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
1155 !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
1157 chflags(entry->fts_accpath,
1158 entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
1160 /* rmdir directories, unlink everything else */
1161 if (S_ISDIR(entry->fts_statp->st_mode)) {
1162 if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY)
1163 warn("-delete: rmdir(%s)", entry->fts_path);
1165 if (unlink(entry->fts_accpath) < 0)
1166 warn("-delete: unlink(%s)", entry->fts_path);
1177 ftsoptions &= ~FTS_NOSTAT; /* no optimise */
1178 ftsoptions |= FTS_PHYSICAL; /* disable -follow */
1179 ftsoptions &= ~FTS_LOGICAL; /* disable -follow */
1180 isoutput = 1; /* possible output */
1181 isdepth = 1; /* -depth implied */
1183 return (palloc(N_DELETE, f_delete));
1187 * -user uname functions --
1189 * True if the file belongs to the user uname. If uname is numeric and
1190 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1191 * return a valid user name, uname is taken as a user ID.
1198 return (entry->fts_statp->st_uid == plan->u_data);
1209 ftsoptions &= ~FTS_NOSTAT;
1211 p = getpwnam(username);
1213 uid = atoi(username);
1214 if (uid == 0 && username[0] != '0')
1215 errx(1, "-user: %s: no such user", username);
1219 new = palloc(N_USER, f_user);
1225 * -xdev functions --
1227 * Always true, causes find not to decend past directories that have a
1228 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1233 ftsoptions |= FTS_XDEV;
1235 return (palloc(N_XDEV, f_always_true));
1239 * ( expression ) functions --
1241 * True if expression is true.
1251 for (p = plan->p_data[0];
1252 p && (state = (p->eval)(p, entry)); p = p->next);
1257 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are
1258 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1259 * to a N_EXPR node containing the expression and the ')' node is discarded.
1264 return (palloc(N_OPENPAREN, (int (*)())-1));
1270 return (palloc(N_CLOSEPAREN, (int (*)())-1));
1274 * ! expression functions --
1276 * Negation of a primary; the unary NOT operator.
1286 for (p = plan->p_data[0];
1287 p && (state = (p->eval)(p, entry)); p = p->next);
1294 return (palloc(N_NOT, f_not));
1298 * expression -o expression functions --
1300 * Alternation of primaries; the OR operator. The second expression is
1301 * not evaluated if the first expression is true.
1311 for (p = plan->p_data[0];
1312 p && (state = (p->eval)(p, entry)); p = p->next);
1317 for (p = plan->p_data[1];
1318 p && (state = (p->eval)(p, entry)); p = p->next);
1325 return (palloc(N_OR, f_or));
1331 int (*f) __P((PLAN *, FTSENT *));
1335 if ((new = malloc(sizeof(PLAN))) == NULL)