GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/find/function.c Lines: 150 509 29.5 %
Date: 2017-11-07 Branches: 76 326 23.3 %

Line Branch Exec Source
1
/*	$OpenBSD: function.c,v 1.45 2017/01/03 21:31:16 tedu Exp $	*/
2
3
/*-
4
 * Copyright (c) 1990, 1993
5
 *	The Regents of the University of California.  All rights reserved.
6
 *
7
 * This code is derived from software contributed to Berkeley by
8
 * Cimarron D. Taylor of the University of California, Berkeley.
9
 *
10
 * Redistribution and use in source and binary forms, with or without
11
 * modification, are permitted provided that the following conditions
12
 * are met:
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.
21
 *
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
32
 * SUCH DAMAGE.
33
 */
34
35
#include <sys/stat.h>
36
#include <sys/wait.h>
37
#include <sys/mount.h>
38
39
#include <dirent.h>
40
#include <err.h>
41
#include <errno.h>
42
#include <fcntl.h>
43
#include <fnmatch.h>
44
#include <fts.h>
45
#include <grp.h>
46
#include <libgen.h>
47
#include <limits.h>
48
#include <pwd.h>
49
#include <stdio.h>
50
#include <stdlib.h>
51
#include <string.h>
52
#include <unistd.h>
53
54
#include "find.h"
55
#include "extern.h"
56
57
#define	COMPARE(a, b) {							\
58
	switch (plan->flags) {						\
59
	case F_EQUAL:							\
60
		return (a == b);					\
61
	case F_LESSTHAN:						\
62
		return (a < b);						\
63
	case F_GREATER:							\
64
		return (a > b);						\
65
	default:							\
66
		abort();						\
67
	}								\
68
}
69
70
static PLAN *palloc(enum ntype, int (*)(PLAN *, FTSENT *));
71
static long long find_parsenum(PLAN *plan, char *option, char *vp, char *endch);
72
static void run_f_exec(PLAN *plan);
73
static PLAN *palloc(enum ntype t, int (*f)(PLAN *, FTSENT *));
74
75
int	f_amin(PLAN *, FTSENT *);
76
int	f_atime(PLAN *, FTSENT *);
77
int	f_cmin(PLAN *, FTSENT *);
78
int	f_ctime(PLAN *, FTSENT *);
79
int	f_always_true(PLAN *, FTSENT *);
80
int	f_empty(PLAN *, FTSENT *);
81
int	f_exec(PLAN *, FTSENT *);
82
int	f_execdir(PLAN *, FTSENT *);
83
int	f_flags(PLAN *, FTSENT *);
84
int	f_fstype(PLAN *, FTSENT *);
85
int	f_group(PLAN *, FTSENT *);
86
int	f_inum(PLAN *, FTSENT *);
87
int	f_empty(PLAN *, FTSENT *);
88
int	f_links(PLAN *, FTSENT *);
89
int	f_ls(PLAN *, FTSENT *);
90
int	f_maxdepth(PLAN *, FTSENT *);
91
int	f_mindepth(PLAN *, FTSENT *);
92
int	f_mtime(PLAN *, FTSENT *);
93
int	f_mmin(PLAN *, FTSENT *);
94
int	f_name(PLAN *, FTSENT *);
95
int	f_iname(PLAN *, FTSENT *);
96
int	f_newer(PLAN *, FTSENT *);
97
int	f_anewer(PLAN *, FTSENT *);
98
int	f_cnewer(PLAN *, FTSENT *);
99
int	f_nogroup(PLAN *, FTSENT *);
100
int	f_nouser(PLAN *, FTSENT *);
101
int	f_path(PLAN *, FTSENT *);
102
int	f_perm(PLAN *, FTSENT *);
103
int	f_print(PLAN *, FTSENT *);
104
int	f_print0(PLAN *, FTSENT *);
105
int	f_prune(PLAN *, FTSENT *);
106
int	f_size(PLAN *, FTSENT *);
107
int	f_type(PLAN *, FTSENT *);
108
int	f_user(PLAN *, FTSENT *);
109
int	f_expr(PLAN *, FTSENT *);
110
int	f_not(PLAN *, FTSENT *);
111
int	f_or(PLAN *, FTSENT *);
112
113
extern int dotfd;
114
extern time_t now;
115
extern FTS *tree;
116
117
/*
118
 * find_parsenum --
119
 *	Parse a string of the form [+-]# and return the value.
120
 */
121
static long long
122
find_parsenum(PLAN *plan, char *option, char *vp, char *endch)
123
{
124
	long long value;
125
4
	char *endchar, *str;	/* Pointer to character ending conversion. */
126
127
	/* Determine comparison from leading + or -. */
128
	str = vp;
129
2
	switch (*str) {
130
	case '+':
131
2
		++str;
132
		plan->flags = F_GREATER;
133
2
		break;
134
	case '-':
135
		++str;
136
		plan->flags = F_LESSTHAN;
137
		break;
138
	default:
139
		plan->flags = F_EQUAL;
140
		break;
141
	}
142
143
	/*
144
	 * Convert the string with strtoll().  Note, if strtoll() returns
145
	 * zero and endchar points to the beginning of the string we know
146
	 * we have a syntax error.
147
	 */
148
2
	value = strtoll(str, &endchar, 10);
149

2
	if (value == 0 && endchar == str)
150
		errx(1, "%s: %s: illegal numeric value", option, vp);
151

2
	if (endchar[0] && (endch == NULL || endchar[0] != *endch))
152
		errx(1, "%s: %s: illegal trailing character", option, vp);
153
2
	if (endch)
154
		*endch = endchar[0];
155
2
	return (value);
156
2
}
157
158
/*
159
 * The value of n for the inode times (atime, ctime, and mtime) is a range,
160
 * i.e. n matches from (n - 1) to n 24 hour periods.  This interacts with
161
 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
162
 * user wanted.  Correct so that -1 is "less than 1".
163
 */
164
#define	TIME_CORRECT(p, ttype)						\
165
	if ((p)->type == ttype && (p)->flags == F_LESSTHAN)		\
166
		++((p)->sec_data);
167
168
/*
169
 * -amin n functions --
170
 *
171
 *     True if the difference between the file access time and the
172
 *     current time is n min periods.
173
 */
174
int
175
f_amin(PLAN *plan, FTSENT *entry)
176
{
177
	extern time_t now;
178
179
	COMPARE((now - entry->fts_statp->st_atime +
180
	    60 - 1) / 60, plan->sec_data);
181
}
182
183
PLAN *
184
c_amin(char *arg, char ***ignored, int unused)
185
{
186
	PLAN *new;
187
188
	ftsoptions &= ~FTS_NOSTAT;
189
190
	new = palloc(N_AMIN, f_amin);
191
	new->sec_data = find_parsenum(new, "-amin", arg, NULL);
192
	TIME_CORRECT(new, N_AMIN);
193
	return (new);
194
}
195
196
/*
197
 * -atime n functions --
198
 *
199
 *	True if the difference between the file access time and the
200
 *	current time is n 24 hour periods.
201
 */
202
int
203
f_atime(PLAN *plan, FTSENT *entry)
204
{
205
206

3
	COMPARE((now - entry->fts_statp->st_atime +
207
	    SECSPERDAY - 1) / SECSPERDAY, plan->sec_data);
208
1
}
209
210
PLAN *
211
c_atime(char *arg, char ***ignored, int unused)
212
{
213
	PLAN *new;
214
215
2
	ftsoptions &= ~FTS_NOSTAT;
216
217
1
	new = palloc(N_ATIME, f_atime);
218
1
	new->sec_data = find_parsenum(new, "-atime", arg, NULL);
219

2
	TIME_CORRECT(new, N_ATIME);
220
1
	return (new);
221
}
222
223
/*
224
 * -cmin n functions --
225
 *
226
 *     True if the difference between the last change of file
227
 *     status information and the current time is n min periods.
228
 */
229
int
230
f_cmin(PLAN *plan, FTSENT *entry)
231
{
232
	extern time_t now;
233
234
	COMPARE((now - entry->fts_statp->st_ctime +
235
	    60 - 1) / 60, plan->sec_data);
236
}
237
238
PLAN *
239
c_cmin(char *arg, char ***ignored, int unused)
240
{
241
	PLAN *new;
242
243
	ftsoptions &= ~FTS_NOSTAT;
244
245
	new = palloc(N_CMIN, f_cmin);
246
	new->sec_data = find_parsenum(new, "-cmin", arg, NULL);
247
	TIME_CORRECT(new, N_CMIN);
248
	return (new);
249
}
250
251
/*
252
 * -ctime n functions --
253
 *
254
 *	True if the difference between the last change of file
255
 *	status information and the current time is n 24 hour periods.
256
 */
257
int
258
f_ctime(PLAN *plan, FTSENT *entry)
259
{
260
261
	COMPARE((now - entry->fts_statp->st_ctime +
262
	    SECSPERDAY - 1) / SECSPERDAY, plan->sec_data);
263
}
264
265
PLAN *
266
c_ctime(char *arg, char ***ignored, int unused)
267
{
268
	PLAN *new;
269
270
	ftsoptions &= ~FTS_NOSTAT;
271
272
	new = palloc(N_CTIME, f_ctime);
273
	new->sec_data = find_parsenum(new, "-ctime", arg, NULL);
274
	TIME_CORRECT(new, N_CTIME);
275
	return (new);
276
}
277
278
/*
279
 * -depth functions --
280
 *
281
 *	Always true, causes descent of the directory hierarchy to be done
282
 *	so that all entries in a directory are acted on before the directory
283
 *	itself.
284
 */
285
int
286
f_always_true(PLAN *plan, FTSENT *entry)
287
{
288
13300
	return (1);
289
}
290
291
PLAN *
292
c_depth(char *ignore, char ***ignored, int unused)
293
{
294
	isdepth = 1;
295
296
	return (palloc(N_DEPTH, f_always_true));
297
}
298
299
/*
300
 * -delete functions
301
 */
302
int
303
f_delete(PLAN *plan, FTSENT *entry)
304
{
305
306
	/* can't delete these */
307
	if (strcmp(entry->fts_accpath, ".") == 0 ||
308
	    strcmp(entry->fts_accpath, "..") == 0)
309
		return 1;
310
311
	/* sanity check */
312
	if (isdepth == 0 ||                     /* depth off */
313
	    (ftsoptions & FTS_NOSTAT))          /* not stat()ing */
314
		errx(1, "-delete: insecure options got turned on");
315
	if (!(ftsoptions & FTS_PHYSICAL) ||     /* physical off */
316
	    (ftsoptions & FTS_LOGICAL))         /* or finally, logical on */
317
		errx(1, "-delete: forbidden when symlinks are followed");
318
319
	/* Potentially unsafe - do not accept relative paths whatsoever */
320
	if (entry->fts_level > FTS_ROOTLEVEL &&
321
	    strchr(entry->fts_accpath, '/') != NULL)
322
		errx(1, "-delete: %s: relative path potentially not safe",
323
		    entry->fts_accpath);
324
#if 0
325
	/* Turn off user immutable bits if running as root */
326
	if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
327
	    !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
328
	    geteuid() == 0)
329
		lchflags(entry->fts_accpath,
330
		    entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
331
#endif
332
	/* rmdir directories, unlink everything else */
333
	if (S_ISDIR(entry->fts_statp->st_mode)) {
334
		if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY)
335
			warn("-delete: rmdir(%s)", entry->fts_path);
336
	} else {
337
		if (unlink(entry->fts_accpath) < 0)
338
			warn("-delete: unlink(%s)", entry->fts_path);
339
340
	}
341
342
	return 1;
343
}
344
345
PLAN *
346
c_delete(char *ignore, char ***ignored, int unused)
347
{
348
	ftsoptions &= ~FTS_NOSTAT;
349
	isoutput = 1;
350
	isdelete = 1;
351
	isdepth = 1;
352
353
	return (palloc(N_DELETE, f_delete));
354
}
355
356
/*
357
 * -empty functions --
358
 *
359
 *	True if the file or directory is empty
360
 */
361
int
362
f_empty(PLAN *plan, FTSENT *entry)
363
{
364
	if (S_ISREG(entry->fts_statp->st_mode) && entry->fts_statp->st_size == 0)
365
		return (1);
366
	if (S_ISDIR(entry->fts_statp->st_mode)) {
367
		struct dirent *dp;
368
		int empty;
369
		DIR *dir;
370
371
		empty = 1;
372
		dir = opendir(entry->fts_accpath);
373
		if (dir == NULL)
374
			return (0);
375
		for (dp = readdir(dir); dp; dp = readdir(dir))
376
			if (dp->d_name[0] != '.' ||
377
			    (dp->d_name[1] != '\0' &&
378
			     (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) {
379
				empty = 0;
380
				break;
381
			}
382
		closedir(dir);
383
		return (empty);
384
	}
385
	return (0);
386
}
387
388
PLAN *
389
c_empty(char *ignore, char ***ignored, int unused)
390
{
391
	ftsoptions &= ~FTS_NOSTAT;
392
393
	return (palloc(N_EMPTY, f_empty));
394
}
395
396
/*
397
 * [-exec | -ok] utility [arg ... ] ; functions --
398
 * [-exec | -ok] utility [arg ... ] {} + functions --
399
 *
400
 *	If the end of the primary expression is delimited by a
401
 *	semicolon: true if the executed utility returns a zero value
402
 *	as exit status.  If "{}" occurs anywhere, it gets replaced by
403
 *	the current pathname.
404
 *
405
 *	If the end of the primary expression is delimited by a plus
406
 *	sign: always true. Pathnames for which the primary is
407
 *	evaluated shall be aggregated into sets. The utility will be
408
 *	executed once per set, with "{}" replaced by the entire set of
409
 *	pathnames (as if xargs). "{}" must appear last.
410
 *
411
 *	The current directory for the execution of utility is the same
412
 *	as the current directory when the find utility was started.
413
 *
414
 *	The primary -ok is different in that it requests affirmation
415
 *	of the user before executing the utility.
416
 */
417
int
418
f_exec(PLAN *plan, FTSENT *entry)
419
{
420
	int cnt, l;
421
	pid_t pid;
422
16338
	int status;
423
424
8169
	if (plan->flags & F_PLUSSET) {
425
		/*
426
		 * Confirm sufficient buffer space, then copy the path
427
		 * to the buffer.
428
		 */
429
		l = strlen(entry->fts_path);
430
		if (plan->ep_p + l < plan->ep_ebp) {
431
			plan->ep_bxp[plan->ep_narg++] = plan->ep_p;
432
			strlcpy(plan->ep_p, entry->fts_path, l + 1);
433
			plan->ep_p += l + 1;
434
435
			if (plan->ep_narg == plan->ep_maxargs)
436
				run_f_exec(plan);
437
		} else {
438
			/*
439
			 * Without sufficient space to copy in the next
440
			 * argument, run the command to empty out the
441
			 * buffer before re-attepting the copy.
442
			 */
443
			run_f_exec(plan);
444
			if (plan->ep_p + l < plan->ep_ebp) {
445
				plan->ep_bxp[plan->ep_narg++] = plan->ep_p;
446
				strlcpy(plan->ep_p, entry->fts_path, l + 1);
447
				plan->ep_p += l + 1;
448
			} else
449
				errx(1, "insufficient space for argument");
450
		}
451
		return (1);
452
	} else {
453
196056
		for (cnt = 0; plan->e_argv[cnt]; ++cnt)
454
89859
			if (plan->e_len[cnt])
455
32676
				brace_subst(plan->e_orig[cnt],
456
				    &plan->e_argv[cnt],
457
16338
				    entry->fts_path,
458
				    plan->e_len[cnt]);
459

8169
		if (plan->flags & F_NEEDOK && !queryuser(plan->e_argv))
460
			return (0);
461
462
		/* don't mix output of command with find output */
463
8169
		fflush(stdout);
464
8169
		fflush(stderr);
465
466
8169
		switch (pid = vfork()) {
467
		case -1:
468
			err(1, "fork");
469
			/* NOTREACHED */
470
		case 0:
471
			if (fchdir(dotfd)) {
472
				warn("chdir");
473
				_exit(1);
474
			}
475
			execvp(plan->e_argv[0], plan->e_argv);
476
			warn("%s", plan->e_argv[0]);
477
			_exit(1);
478
		}
479
8169
		pid = waitpid(pid, &status, 0);
480

32676
		return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
481
	}
482
8169
}
483
484
static void
485
run_f_exec(PLAN *plan)
486
{
487
	pid_t pid;
488
	int rval, status;
489
490
	/* Ensure arg list is null terminated. */
491
	plan->ep_bxp[plan->ep_narg] = NULL;
492
493
	/* Don't mix output of command with find output. */
494
 	fflush(stdout);
495
 	fflush(stderr);
496
497
	switch (pid = vfork()) {
498
	case -1:
499
		err(1, "vfork");
500
		/* NOTREACHED */
501
	case 0:
502
		if (fchdir(dotfd)) {
503
			warn("chdir");
504
			_exit(1);
505
		}
506
		execvp(plan->e_argv[0], plan->e_argv);
507
		warn("%s", plan->e_argv[0]);
508
		_exit(1);
509
	}
510
511
	/* Clear out the argument list. */
512
	plan->ep_narg = 0;
513
	plan->ep_bxp[plan->ep_narg] = NULL;
514
	/* As well as the argument buffer. */
515
	plan->ep_p = plan->ep_bbp;
516
	*plan->ep_p = '\0';
517
518
	pid = waitpid(pid, &status, 0);
519
	if (WIFEXITED(status))
520
		rval = WEXITSTATUS(status);
521
	else
522
		rval = -1;
523
524
	/*
525
	 * If we have a non-zero exit status, preserve it so find(1) can
526
	 * later exit with it.
527
	 */
528
	if (rval)
529
		plan->ep_rval = rval;
530
}
531
532
/*
533
 * c_exec --
534
 *	build three parallel arrays, one with pointers to the strings passed
535
 *	on the command line, one with (possibly duplicated) pointers to the
536
 *	argv array, and one with integer values that are lengths of the
537
 *	strings, but also flags meaning that the string has to be massaged.
538
 *
539
 *	If -exec ... {} +, use only the first array, but make it large
540
 *	enough to hold 5000 args (cf. src/usr.bin/xargs/xargs.c for a
541
 *	discussion), and then allocate ARG_MAX - 4K of space for args.
542
 */
543
PLAN *
544
c_exec(char *unused, char ***argvp, int isok)
545
{
546
	PLAN *new;			/* node returned */
547
	int cnt, brace, lastbrace;
548
	char **argv, **ap, *p;
549
550
	/* make sure the current directory is readable */
551
6
	if (dotfd == -1)
552
		errx(1, "%s: cannot open \".\"", isok ? "-ok" : "-exec");
553
554
3
	isoutput = 1;
555
556
3
	new = palloc(N_EXEC, f_exec);
557
3
	if (isok)
558
		new->flags |= F_NEEDOK;
559
560
	/*
561
	 * Terminate if we encounter an arg exactly equal to ";", or an
562
	 * arg exactly equal to "+" following an arg exactly equal to
563
	 * "{}".
564
	 */
565
36
	for (ap = argv = *argvp, brace = 0;; ++ap) {
566
36
		if (!*ap)
567
			errx(1, "%s: no terminating \";\" or \"+\"",
568
			    isok ? "-ok" : "-exec");
569
		lastbrace = brace;
570
		brace = 0;
571
36
		if (strcmp(*ap, "{}") == 0)
572
3
			brace = 1;
573
36
		if (strcmp(*ap, ";") == 0)
574
			break;
575
33
		if (strcmp(*ap, "+") == 0 && lastbrace) {
576
			new->flags |= F_PLUSSET;
577
			break;
578
		}
579
	}
580
581
582
	/*
583
	 * POSIX says -ok ... {} + "need not be supported," and it does
584
	 * not make much sense anyway.
585
	 */
586

3
	if (new->flags & F_NEEDOK && new->flags & F_PLUSSET)
587
		errx(1, "-ok: terminating \"+\" not permitted.");
588
589
3
	if (new->flags & F_PLUSSET) {
590
		u_int c, bufsize;
591
592
		cnt = ap - *argvp - 1;			/* units are words */
593
		new->ep_maxargs = 5000;
594
		new->e_argv = ereallocarray(NULL,
595
		    (size_t)(cnt + new->ep_maxargs), sizeof(char **));
596
597
		/* We start stuffing arguments after the user's last one. */
598
		new->ep_bxp = &new->e_argv[cnt];
599
		new->ep_narg = 0;
600
601
		/*
602
		 * Count up the space of the user's arguments, and
603
		 * subtract that from what we allocate.
604
		 */
605
		for (argv = *argvp, c = 0, cnt = 0;
606
		     argv < ap;
607
		     ++argv, ++cnt) {
608
			c += strlen(*argv) + 1;
609
 			new->e_argv[cnt] = *argv;
610
 		}
611
		bufsize = ARG_MAX - 4 * 1024 - c;
612
613
614
		/*
615
		 * Allocate, and then initialize current, base, and
616
		 * end pointers.
617
		 */
618
		new->ep_p = new->ep_bbp = malloc(bufsize + 1);
619
		new->ep_ebp = new->ep_bbp + bufsize - 1;
620
		new->ep_rval = 0;
621
	} else { /* !F_PLUSSET */
622
3
		cnt = ap - *argvp + 1;
623
3
		new->e_argv = ereallocarray(NULL, cnt, sizeof(char *));
624
3
		new->e_orig = ereallocarray(NULL, cnt, sizeof(char *));
625
3
		new->e_len = ereallocarray(NULL, cnt, sizeof(int));
626
627
72
		for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
628
33
			new->e_orig[cnt] = *argv;
629
294
			for (p = *argv; *p; ++p)
630

126
				if (p[0] == '{' && p[1] == '}') {
631
6
					new->e_argv[cnt] =
632
6
						emalloc((u_int)PATH_MAX);
633
6
					new->e_len[cnt] = PATH_MAX;
634
6
					break;
635
				}
636
33
			if (!*p) {
637
27
				new->e_argv[cnt] = *argv;
638
27
				new->e_len[cnt] = 0;
639
27
			}
640
		}
641
3
		new->e_orig[cnt] = NULL;
642
 	}
643
644
3
	new->e_argv[cnt] = NULL;
645
3
	*argvp = argv + 1;
646
3
	return (new);
647
}
648
649
/*
650
 * -execdir utility [arg ... ] ; functions --
651
 *
652
 *	True if the executed utility returns a zero value as exit status.
653
 *	The end of the primary expression is delimited by a semicolon.  If
654
 *	"{}" occurs anywhere, it gets replaced by the unqualified pathname.
655
 *	The current directory for the execution of utility is the same as
656
 *	the directory where the file lives.
657
 */
658
int
659
f_execdir(PLAN *plan, FTSENT *entry)
660
{
661
	int cnt;
662
	pid_t pid;
663
	int status, fd;
664
	char base[PATH_MAX];
665
666
	/* fts(3) does not chdir for the root level so we do it ourselves. */
667
	if (entry->fts_level == FTS_ROOTLEVEL) {
668
		if ((fd = open(".", O_RDONLY)) == -1) {
669
			warn("cannot open \".\"");
670
			return (0);
671
		}
672
		if (chdir(entry->fts_accpath)) {
673
			(void) close(fd);
674
			return (0);
675
		}
676
	}
677
678
	/* Substitute basename(path) for {} since cwd is it's parent dir */
679
	(void)strncpy(base, basename(entry->fts_path), sizeof(base) - 1);
680
	base[sizeof(base) - 1] = '\0';
681
	for (cnt = 0; plan->e_argv[cnt]; ++cnt)
682
		if (plan->e_len[cnt])
683
			brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
684
			    base, plan->e_len[cnt]);
685
686
	/* don't mix output of command with find output */
687
	fflush(stdout);
688
	fflush(stderr);
689
690
	switch (pid = vfork()) {
691
	case -1:
692
		err(1, "fork");
693
		/* NOTREACHED */
694
	case 0:
695
		execvp(plan->e_argv[0], plan->e_argv);
696
		warn("%s", plan->e_argv[0]);
697
		_exit(1);
698
	}
699
700
	/* Undo the above... */
701
	if (entry->fts_level == FTS_ROOTLEVEL) {
702
		if (fchdir(fd) == -1) {
703
			warn("unable to chdir back to starting directory");
704
			(void) close(fd);
705
			return (0);
706
		}
707
		(void) close(fd);
708
	}
709
710
	pid = waitpid(pid, &status, 0);
711
	return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
712
}
713
714
/*
715
 * c_execdir --
716
 *	build three parallel arrays, one with pointers to the strings passed
717
 *	on the command line, one with (possibly duplicated) pointers to the
718
 *	argv array, and one with integer values that are lengths of the
719
 *	strings, but also flags meaning that the string has to be massaged.
720
 */
721
PLAN *
722
c_execdir(char *ignored, char ***argvp, int unused)
723
{
724
	PLAN *new;			/* node returned */
725
	int cnt;
726
	char **argv, **ap, *p;
727
728
4
	ftsoptions &= ~FTS_NOSTAT;
729
2
	isoutput = 1;
730
731
2
	new = palloc(N_EXECDIR, f_execdir);
732
733
9
	for (ap = argv = *argvp;; ++ap) {
734
9
		if (!*ap)
735
			errx(1,
736
			    "-execdir: no terminating \";\"");
737
9
		if (**ap == ';')
738
			break;
739
	}
740
741
2
	cnt = ap - *argvp + 1;
742
2
	new->e_argv = ereallocarray(NULL, cnt, sizeof(char *));
743
2
	new->e_orig = ereallocarray(NULL, cnt, sizeof(char *));
744
2
	new->e_len = ereallocarray(NULL, cnt, sizeof(int));
745
746
18
	for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
747
7
		new->e_orig[cnt] = *argv;
748
40
		for (p = *argv; *p; ++p)
749

17
			if (p[0] == '{' && p[1] == '}') {
750
2
				new->e_argv[cnt] = emalloc((u_int)PATH_MAX);
751
2
				new->e_len[cnt] = PATH_MAX;
752
2
				break;
753
			}
754
7
		if (!*p) {
755
5
			new->e_argv[cnt] = *argv;
756
5
			new->e_len[cnt] = 0;
757
5
		}
758
	}
759
2
	new->e_argv[cnt] = new->e_orig[cnt] = NULL;
760
761
2
	*argvp = argv + 1;
762
2
	return (new);
763
}
764
765
/*
766
 * -flags functions --
767
 *
768
 *	The flags argument is used to represent file flags bits.
769
 */
770
int
771
f_flags(PLAN *plan, FTSENT *entry)
772
{
773
	u_int flags;
774
775
	flags = entry->fts_statp->st_flags &
776
	    (UF_NODUMP | UF_IMMUTABLE | UF_APPEND | UF_OPAQUE |
777
	     SF_ARCHIVED | SF_IMMUTABLE | SF_APPEND);
778
	if (plan->flags == F_ATLEAST)
779
		/* note that plan->fl_flags always is a subset of
780
		   plan->fl_mask */
781
		return ((flags & plan->fl_mask) == plan->fl_flags);
782
	else
783
		return (flags == plan->fl_flags);
784
	/* NOTREACHED */
785
}
786
787
PLAN *
788
c_flags(char *flags_str, char ***ignored, int unused)
789
{
790
	PLAN *new;
791
	u_int32_t flags, notflags;
792
793
	ftsoptions &= ~FTS_NOSTAT;
794
795
	new = palloc(N_FLAGS, f_flags);
796
797
	if (*flags_str == '-') {
798
		new->flags = F_ATLEAST;
799
		++flags_str;
800
	}
801
802
	if (strtofflags(&flags_str, &flags, &notflags) == 1)
803
		errx(1, "-flags: %s: illegal flags string", flags_str);
804
805
	new->fl_flags = flags;
806
	new->fl_mask = flags | notflags;
807
	return (new);
808
}
809
810
/*
811
 * -follow functions --
812
 *
813
 *	Always true, causes symbolic links to be followed on a global
814
 *	basis.
815
 */
816
PLAN *
817
c_follow(char *ignore, char ***ignored, int unused)
818
{
819
4
	ftsoptions &= ~FTS_PHYSICAL;
820
2
	ftsoptions |= FTS_LOGICAL;
821
822
2
	return (palloc(N_FOLLOW, f_always_true));
823
}
824
825
/*
826
 * -fstype functions --
827
 *
828
 *	True if the file is of a certain type.
829
 */
830
int
831
f_fstype(PLAN *plan, FTSENT *entry)
832
{
833
	static dev_t curdev;	/* need a guaranteed illegal dev value */
834
	static int first = 1;
835
	struct statfs sb;
836
	static short val;
837
	static char fstype[MFSNAMELEN];
838
	char *p, save[2];
839
840
	/* Only check when we cross mount point. */
841
	if (first || curdev != entry->fts_statp->st_dev) {
842
		curdev = entry->fts_statp->st_dev;
843
844
		/*
845
		 * Statfs follows symlinks; find wants the link's file system,
846
		 * not where it points.
847
		 */
848
		if (entry->fts_info == FTS_SL ||
849
		    entry->fts_info == FTS_SLNONE) {
850
			if ((p = strrchr(entry->fts_accpath, '/')))
851
				++p;
852
			else
853
				p = entry->fts_accpath;
854
			save[0] = p[0];
855
			p[0] = '.';
856
			save[1] = p[1];
857
			p[1] = '\0';
858
859
		} else
860
			p = NULL;
861
862
		if (statfs(entry->fts_accpath, &sb))
863
			err(1, "%s", entry->fts_accpath);
864
865
		if (p) {
866
			p[0] = save[0];
867
			p[1] = save[1];
868
		}
869
870
		first = 0;
871
872
		/*
873
		 * Further tests may need both of these values, so
874
		 * always copy both of them.
875
		 */
876
		val = sb.f_flags;
877
		strncpy(fstype, sb.f_fstypename, MFSNAMELEN);
878
	}
879
	switch (plan->flags) {
880
	case F_MTFLAG:
881
		return (val & plan->mt_data);
882
	case F_MTTYPE:
883
		return (strncmp(fstype, plan->c_data, MFSNAMELEN) == 0);
884
	default:
885
		abort();
886
	}
887
}
888
889
PLAN *
890
c_fstype(char *arg, char ***ignored, int unused)
891
{
892
	PLAN *new;
893
894
	ftsoptions &= ~FTS_NOSTAT;
895
896
	new = palloc(N_FSTYPE, f_fstype);
897
	switch (*arg) {
898
	case 'l':
899
		if (!strcmp(arg, "local")) {
900
			new->flags = F_MTFLAG;
901
			new->mt_data = MNT_LOCAL;
902
			return (new);
903
		}
904
		break;
905
	case 'r':
906
		if (!strcmp(arg, "rdonly")) {
907
			new->flags = F_MTFLAG;
908
			new->mt_data = MNT_RDONLY;
909
			return (new);
910
		}
911
		break;
912
	}
913
914
	new->flags = F_MTTYPE;
915
	new->c_data = arg;
916
	return (new);
917
}
918
919
/*
920
 * -group gname functions --
921
 *
922
 *	True if the file belongs to the group gname.  If gname is numeric and
923
 *	an equivalent of the getgrnam() function does not return a valid group
924
 *	name, gname is taken as a group ID.
925
 */
926
int
927
f_group(PLAN *plan, FTSENT *entry)
928
{
929
	return (entry->fts_statp->st_gid == plan->g_data);
930
}
931
932
PLAN *
933
c_group(char *gname, char ***ignored, int unused)
934
{
935
	PLAN *new;
936
	struct group *g;
937
	gid_t gid;
938
939
	ftsoptions &= ~FTS_NOSTAT;
940
941
	g = getgrnam(gname);
942
	if (g == NULL) {
943
		const char *errstr;
944
945
		gid = strtonum(gname, 0, GID_MAX, &errstr);
946
		if (errstr)
947
			errx(1, "-group: %s: no such group", gname);
948
	} else
949
		gid = g->gr_gid;
950
951
	new = palloc(N_GROUP, f_group);
952
	new->g_data = gid;
953
	return (new);
954
}
955
956
/*
957
 * -inum n functions --
958
 *
959
 *	True if the file has inode # n.
960
 */
961
int
962
f_inum(PLAN *plan, FTSENT *entry)
963
{
964
	COMPARE(entry->fts_statp->st_ino, plan->i_data);
965
}
966
967
PLAN *
968
c_inum(char *arg, char ***ignored, int unused)
969
{
970
	long long inum;
971
	PLAN *new;
972
973
	ftsoptions &= ~FTS_NOSTAT;
974
975
	new = palloc(N_INUM, f_inum);
976
	inum = find_parsenum(new, "-inum", arg, NULL);
977
	if (inum != (ino_t)inum)
978
		errx(1, "-inum: %s: number too great", arg);
979
	new->i_data = inum;
980
	return (new);
981
}
982
983
/*
984
 * -links n functions --
985
 *
986
 *	True if the file has n links.
987
 */
988
int
989
f_links(PLAN *plan, FTSENT *entry)
990
{
991
	COMPARE(entry->fts_statp->st_nlink, plan->l_data);
992
}
993
994
PLAN *
995
c_links(char *arg, char ***ignored, int unused)
996
{
997
	PLAN *new;
998
	long long nlink;
999
1000
	ftsoptions &= ~FTS_NOSTAT;
1001
1002
	new = palloc(N_LINKS, f_links);
1003
	nlink = find_parsenum(new, "-links", arg, NULL);
1004
	if (nlink != (nlink_t)nlink)
1005
		errx(1, "-links: %s: number too great", arg);
1006
	new->l_data = nlink;
1007
	return (new);
1008
}
1009
1010
/*
1011
 * -ls functions --
1012
 *
1013
 *	Always true - prints the current entry to stdout in "ls" format.
1014
 */
1015
int
1016
f_ls(PLAN *plan, FTSENT *entry)
1017
{
1018
	printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
1019
	return (1);
1020
}
1021
1022
PLAN *
1023
c_ls(char *ignore, char ***ignored, int unused)
1024
{
1025
	ftsoptions &= ~FTS_NOSTAT;
1026
	isoutput = 1;
1027
1028
	return (palloc(N_LS, f_ls));
1029
}
1030
1031
/*
1032
 * - maxdepth n functions --
1033
 *
1034
 *	True if the current search depth is less than or equal to the
1035
 *	maximum depth specified
1036
 */
1037
int
1038
f_maxdepth(PLAN *plan, FTSENT *entry)
1039
{
1040
1041
	if (entry->fts_level >= plan->max_data)
1042
		fts_set(tree, entry, FTS_SKIP);
1043
	return (entry->fts_level <= plan->max_data);
1044
}
1045
1046
PLAN *
1047
c_maxdepth(char *arg, char ***ignored, int unused)
1048
{
1049
	PLAN *new;
1050
	const char *errstr = NULL;
1051
1052
	new = palloc(N_MAXDEPTH, f_maxdepth);
1053
	new->max_data = strtonum(arg, 0, FTS_MAXLEVEL, &errstr);
1054
	if (errstr)
1055
		errx(1, "%s: maxdepth value %s", arg, errstr);
1056
	return (new);
1057
}
1058
1059
/*
1060
 * - mindepth n functions --
1061
 *
1062
 *	True if the current search depth is greater than or equal to the
1063
 *	minimum depth specified
1064
 */
1065
int
1066
f_mindepth(PLAN *plan, FTSENT *entry)
1067
{
1068
1069
	return (entry->fts_level >= plan->min_data);
1070
}
1071
1072
PLAN *
1073
c_mindepth(char *arg, char ***ignored, int unused)
1074
{
1075
	PLAN *new;
1076
	const char *errstr = NULL;
1077
1078
	new = palloc(N_MINDEPTH, f_mindepth);
1079
	new->min_data = strtonum(arg, 0, INT_MAX, &errstr);
1080
	if (errstr)
1081
		errx(1, "-mindepth: %s: value %s", arg, errstr);
1082
	return (new);
1083
}
1084
1085
/*
1086
 * -mtime n functions --
1087
 *
1088
 *	True if the difference between the file modification time and the
1089
 *	current time is n 24 hour periods.
1090
 */
1091
int
1092
f_mtime(PLAN *plan, FTSENT *entry)
1093
{
1094
1095

21
	COMPARE((now - entry->fts_statp->st_mtime + SECSPERDAY - 1) /
1096
	    SECSPERDAY, plan->sec_data);
1097
7
}
1098
1099
PLAN *
1100
c_mtime(char *arg, char ***ignored, int unused)
1101
{
1102
	PLAN *new;
1103
1104
2
	ftsoptions &= ~FTS_NOSTAT;
1105
1106
1
	new = palloc(N_MTIME, f_mtime);
1107
1
	new->sec_data = find_parsenum(new, "-mtime", arg, NULL);
1108

2
	TIME_CORRECT(new, N_MTIME);
1109
1
	return (new);
1110
}
1111
1112
/*
1113
 * -mmin n functions --
1114
 *
1115
 *     True if the difference between the file modification time and the
1116
 *     current time is n min periods.
1117
 */
1118
int
1119
f_mmin(PLAN *plan, FTSENT *entry)
1120
{
1121
	extern time_t now;
1122
1123
	COMPARE((now - entry->fts_statp->st_mtime + 60 - 1) /
1124
	    60, plan->sec_data);
1125
}
1126
1127
PLAN *
1128
c_mmin(char *arg, char ***ignored, int unused)
1129
{
1130
	PLAN *new;
1131
1132
	ftsoptions &= ~FTS_NOSTAT;
1133
1134
	new = palloc(N_MMIN, f_mmin);
1135
	new->sec_data = find_parsenum(new, "-mmin", arg, NULL);
1136
	TIME_CORRECT(new, N_MMIN);
1137
	return (new);
1138
}
1139
1140
/*
1141
 * -name functions --
1142
 *
1143
 *	True if the basename of the filename being examined
1144
 *	matches pattern using Pattern Matching Notation S3.14
1145
 */
1146
int
1147
f_name(PLAN *plan, FTSENT *entry)
1148
{
1149
5289170
	return (!fnmatch(plan->c_data, entry->fts_name, 0));
1150
}
1151
1152
PLAN *
1153
c_name(char *pattern, char ***ignored, int unused)
1154
{
1155
	PLAN *new;
1156
1157
90
	new = palloc(N_NAME, f_name);
1158
45
	new->c_data = pattern;
1159
45
	return (new);
1160
}
1161
1162
/*
1163
 * -iname functions --
1164
 *
1165
 *	Similar to -name, but does case insensitive matching
1166
 *
1167
 */
1168
int
1169
f_iname(PLAN *plan, FTSENT *entry)
1170
{
1171
	return (!fnmatch(plan->c_data, entry->fts_name, FNM_CASEFOLD));
1172
}
1173
1174
PLAN *
1175
c_iname(char *pattern, char ***ignored, int unused)
1176
{
1177
	PLAN *new;
1178
1179
	new = palloc(N_INAME, f_iname);
1180
	new->c_data = pattern;
1181
	return (new);
1182
}
1183
1184
/*
1185
 * -newer file functions --
1186
 *
1187
 *	True if the current file has been modified more recently
1188
 *	then the modification time of the file named by the pathname
1189
 *	file.
1190
 */
1191
int
1192
f_newer(PLAN *plan, FTSENT *entry)
1193
{
1194
1195
	return (entry->fts_statp->st_mtimespec.tv_sec > plan->t_data.tv_sec ||
1196
	    (entry->fts_statp->st_mtimespec.tv_sec == plan->t_data.tv_sec &&
1197
	    entry->fts_statp->st_mtimespec.tv_nsec > plan->t_data.tv_nsec));
1198
}
1199
1200
PLAN *
1201
c_newer(char *filename, char ***ignored, int unused)
1202
{
1203
	PLAN *new;
1204
	struct stat sb;
1205
1206
	ftsoptions &= ~FTS_NOSTAT;
1207
1208
	if (stat(filename, &sb))
1209
		err(1, "%s", filename);
1210
	new = palloc(N_NEWER, f_newer);
1211
	memcpy(&new->t_data, &sb.st_mtimespec, sizeof(struct timespec));
1212
	return (new);
1213
}
1214
1215
/*
1216
 * -anewer file functions --
1217
 *
1218
 *	True if the current file has been accessed more recently
1219
 *	then the access time of the file named by the pathname
1220
 *	file.
1221
 */
1222
int
1223
f_anewer(PLAN *plan, FTSENT *entry)
1224
{
1225
1226
	return (entry->fts_statp->st_atimespec.tv_sec > plan->t_data.tv_sec ||
1227
	    (entry->fts_statp->st_atimespec.tv_sec == plan->t_data.tv_sec &&
1228
	    entry->fts_statp->st_atimespec.tv_nsec > plan->t_data.tv_nsec));
1229
}
1230
1231
PLAN *
1232
c_anewer(char *filename, char ***ignored, int unused)
1233
{
1234
	PLAN *new;
1235
	struct stat sb;
1236
1237
	ftsoptions &= ~FTS_NOSTAT;
1238
1239
	if (stat(filename, &sb))
1240
		err(1, "%s", filename);
1241
	new = palloc(N_NEWER, f_anewer);
1242
	memcpy(&new->t_data, &sb.st_atimespec, sizeof(struct timespec));
1243
	return (new);
1244
}
1245
1246
/*
1247
 * -cnewer file functions --
1248
 *
1249
 *	True if the current file has been changed more recently
1250
 *	then the inode change time of the file named by the pathname
1251
 *	file.
1252
 */
1253
int
1254
f_cnewer(PLAN *plan, FTSENT *entry)
1255
{
1256
1257
	return (entry->fts_statp->st_ctimespec.tv_sec > plan->t_data.tv_sec ||
1258
	    (entry->fts_statp->st_ctimespec.tv_sec == plan->t_data.tv_sec &&
1259
	    entry->fts_statp->st_ctimespec.tv_nsec > plan->t_data.tv_nsec));
1260
}
1261
1262
PLAN *
1263
c_cnewer(char *filename, char ***ignored, int unused)
1264
{
1265
	PLAN *new;
1266
	struct stat sb;
1267
1268
	ftsoptions &= ~FTS_NOSTAT;
1269
1270
	if (stat(filename, &sb))
1271
		err(1, "%s", filename);
1272
	new = palloc(N_NEWER, f_cnewer);
1273
	memcpy(&new->t_data, &sb.st_ctimespec, sizeof(struct timespec));
1274
	return (new);
1275
}
1276
1277
/*
1278
 * -nogroup functions --
1279
 *
1280
 *	True if file belongs to a user ID for which the equivalent
1281
 *	of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
1282
 */
1283
int
1284
f_nogroup(PLAN *plan, FTSENT *entry)
1285
{
1286
	return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1);
1287
}
1288
1289
PLAN *
1290
c_nogroup(char *ignore, char ***ignored, int unused)
1291
{
1292
	ftsoptions &= ~FTS_NOSTAT;
1293
1294
	return (palloc(N_NOGROUP, f_nogroup));
1295
}
1296
1297
/*
1298
 * -nouser functions --
1299
 *
1300
 *	True if file belongs to a user ID for which the equivalent
1301
 *	of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
1302
 */
1303
int
1304
f_nouser(PLAN *plan, FTSENT *entry)
1305
{
1306
	return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1);
1307
}
1308
1309
PLAN *
1310
c_nouser(char *ignore, char ***ignored, int unused)
1311
{
1312
	ftsoptions &= ~FTS_NOSTAT;
1313
1314
	return (palloc(N_NOUSER, f_nouser));
1315
}
1316
1317
/*
1318
 * -path functions --
1319
 *
1320
 *	True if the path of the filename being examined
1321
 *	matches pattern using Pattern Matching Notation S3.14
1322
 */
1323
int
1324
f_path(PLAN *plan, FTSENT *entry)
1325
{
1326
9922
	return (!fnmatch(plan->c_data, entry->fts_path, 0));
1327
}
1328
1329
PLAN *
1330
c_path(char *pattern, char ***ignored, int unused)
1331
{
1332
	PLAN *new;
1333
1334
22
	new = palloc(N_NAME, f_path);
1335
11
	new->c_data = pattern;
1336
11
	return (new);
1337
}
1338
1339
/*
1340
 * -perm functions --
1341
 *
1342
 *	The mode argument is used to represent file mode bits.  If it starts
1343
 *	with a leading digit, it's treated as an octal mode, otherwise as a
1344
 *	symbolic mode.
1345
 */
1346
int
1347
f_perm(PLAN *plan, FTSENT *entry)
1348
{
1349
	mode_t mode;
1350
1351
	mode = entry->fts_statp->st_mode &
1352
	    (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
1353
	if (plan->flags == F_ATLEAST)
1354
		return ((plan->m_data | mode) == mode);
1355
	else
1356
		return (mode == plan->m_data);
1357
	/* NOTREACHED */
1358
}
1359
1360
PLAN *
1361
c_perm(char *perm, char ***ignored, int unused)
1362
{
1363
	PLAN *new;
1364
	void *set;
1365
1366
	ftsoptions &= ~FTS_NOSTAT;
1367
1368
	new = palloc(N_PERM, f_perm);
1369
1370
	if (*perm == '-') {
1371
		new->flags = F_ATLEAST;
1372
		++perm;
1373
	}
1374
1375
	if ((set = setmode(perm)) == NULL)
1376
		errx(1, "-perm: %s: illegal mode string", perm);
1377
1378
	new->m_data = getmode(set, 0);
1379
	free(set);
1380
	return (new);
1381
}
1382
1383
/*
1384
 * -print functions --
1385
 *
1386
 *	Always true, causes the current pathame to be written to
1387
 *	standard output.
1388
 */
1389
int
1390
f_print(PLAN *plan, FTSENT *entry)
1391
{
1392
93806
	(void)printf("%s\n", entry->fts_path);
1393
46903
	return(1);
1394
}
1395
1396
/* ARGSUSED */
1397
int
1398
f_print0(PLAN *plan, FTSENT *entry)
1399
{
1400
20096
	(void)fputs(entry->fts_path, stdout);
1401
10048
	(void)fputc('\0', stdout);
1402
10048
	return(1);
1403
}
1404
1405
PLAN *
1406
c_print(char *ignore, char ***ignored, int unused)
1407
{
1408
196
	isoutput = 1;
1409
1410
98
	return(palloc(N_PRINT, f_print));
1411
}
1412
1413
PLAN *
1414
c_print0(char *ignore, char ***ignored, int unused)
1415
{
1416
20
	isoutput = 1;
1417
1418
10
	return(palloc(N_PRINT0, f_print0));
1419
}
1420
1421
/*
1422
 * -prune functions --
1423
 *
1424
 *	Prune a portion of the hierarchy.
1425
 */
1426
int
1427
f_prune(PLAN *plan, FTSENT *entry)
1428
{
1429
1430
6
	if (fts_set(tree, entry, FTS_SKIP))
1431
		err(1, "%s", entry->fts_path);
1432
3
	return (1);
1433
}
1434
1435
PLAN *
1436
c_prune(char *ignore, char ***ignored, int unused)
1437
{
1438
2
	return (palloc(N_PRUNE, f_prune));
1439
}
1440
1441
/*
1442
 * -size n[c] functions --
1443
 *
1444
 *	True if the file size in bytes, divided by an implementation defined
1445
 *	value and rounded up to the next integer, is n.  If n is followed by
1446
 *	a c, the size is in bytes.
1447
 */
1448
#define	FIND_SIZE	512
1449
static int divsize = 1;
1450
1451
int
1452
f_size(PLAN *plan, FTSENT *entry)
1453
{
1454
	off_t size;
1455
1456
	size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
1457
	    FIND_SIZE : entry->fts_statp->st_size;
1458
	COMPARE(size, plan->o_data);
1459
}
1460
1461
PLAN *
1462
c_size(char *arg, char ***ignored, int unused)
1463
{
1464
	PLAN *new;
1465
	char endch;
1466
1467
	ftsoptions &= ~FTS_NOSTAT;
1468
1469
	new = palloc(N_SIZE, f_size);
1470
	endch = 'c';
1471
	new->o_data = find_parsenum(new, "-size", arg, &endch);
1472
	if (endch == 'c')
1473
		divsize = 0;
1474
	return (new);
1475
}
1476
1477
/*
1478
 * -type c functions --
1479
 *
1480
 *	True if the type of the file is c, where c is b, c, d, p, or f for
1481
 *	block special file, character special file, directory, FIFO, or
1482
 *	regular file, respectively.
1483
 */
1484
int
1485
f_type(PLAN *plan, FTSENT *entry)
1486
{
1487
168224
	return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data);
1488
}
1489
1490
PLAN *
1491
c_type(char *typestring, char ***ignored, int unused)
1492
{
1493
	PLAN *new;
1494
	mode_t  mask;
1495
1496
160
	ftsoptions &= ~FTS_NOSTAT;
1497
1498


80
	switch (typestring[0]) {
1499
	case 'b':
1500
		mask = S_IFBLK;
1501
		break;
1502
	case 'c':
1503
		mask = S_IFCHR;
1504
		break;
1505
	case 'd':
1506
		mask = S_IFDIR;
1507
6
		break;
1508
	case 'f':
1509
		mask = S_IFREG;
1510
72
		break;
1511
	case 'l':
1512
		mask = S_IFLNK;
1513
2
		break;
1514
	case 'p':
1515
		mask = S_IFIFO;
1516
		break;
1517
	case 's':
1518
		mask = S_IFSOCK;
1519
		break;
1520
	default:
1521
		errx(1, "-type: %s: unknown type", typestring);
1522
	}
1523
1524
80
	new = palloc(N_TYPE, f_type);
1525
80
	new->m_data = mask;
1526
80
	return (new);
1527
}
1528
1529
/*
1530
 * -user uname functions --
1531
 *
1532
 *	True if the file belongs to the user uname.  If uname is numeric and
1533
 *	an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1534
 *	return a valid user name, uname is taken as a user ID.
1535
 */
1536
int
1537
f_user(PLAN *plan, FTSENT *entry)
1538
{
1539
	return (entry->fts_statp->st_uid == plan->u_data);
1540
}
1541
1542
PLAN *
1543
c_user(char *username, char ***ignored, int unused)
1544
{
1545
	PLAN *new;
1546
	struct passwd *p;
1547
	uid_t uid;
1548
1549
	ftsoptions &= ~FTS_NOSTAT;
1550
1551
	p = getpwnam(username);
1552
	if (p == NULL) {
1553
		const char *errstr;
1554
1555
		uid = strtonum(username, 0, UID_MAX, &errstr);
1556
		if (errstr)
1557
			errx(1, "-user: %s: no such user", username);
1558
	} else
1559
		uid = p->pw_uid;
1560
1561
	new = palloc(N_USER, f_user);
1562
	new->u_data = uid;
1563
	return (new);
1564
}
1565
1566
/*
1567
 * -xdev functions --
1568
 *
1569
 *	Always true, causes find not to decend past directories that have a
1570
 *	different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1571
 */
1572
PLAN *
1573
c_xdev(char *ignore, char ***ignored, int unused)
1574
{
1575
	ftsoptions |= FTS_XDEV;
1576
1577
	return (palloc(N_XDEV, f_always_true));
1578
}
1579
1580
/*
1581
 * ( expression ) functions --
1582
 *
1583
 *	True if expression is true.
1584
 */
1585
int
1586
f_expr(PLAN *plan, FTSENT *entry)
1587
{
1588
	PLAN *p;
1589
	int state;
1590
1591
8064124
	for (p = plan->p_data[0];
1592
5504394
	    p && (state = (p->eval)(p, entry)); p = p->next);
1593
2645340
	return (state);
1594
}
1595
1596
/*
1597
 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers.  They are
1598
 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1599
 * to a N_EXPR node containing the expression and the ')' node is discarded.
1600
 */
1601
PLAN *
1602
c_openparen(char *ignore, char ***ignored, int unused)
1603
{
1604
94
	return (palloc(N_OPENPAREN, (int (*)(PLAN *, FTSENT *))-1));
1605
}
1606
1607
PLAN *
1608
c_closeparen(char *ignore, char ***ignored, int unused)
1609
{
1610
94
	return (palloc(N_CLOSEPAREN, (int (*)(PLAN *, FTSENT *))-1));
1611
}
1612
1613
/*
1614
 * ! expression functions --
1615
 *
1616
 *	Negation of a primary; the unary NOT operator.
1617
 */
1618
int
1619
f_not(PLAN *plan, FTSENT *entry)
1620
{
1621
	PLAN *p;
1622
	int state;
1623
1624
7888
	for (p = plan->p_data[0];
1625
5728
	    p && (state = (p->eval)(p, entry)); p = p->next);
1626
2512
	return (!state);
1627
}
1628
1629
PLAN *
1630
c_not(char *ignore, char ***ignored, int unused)
1631
{
1632
12
	return (palloc(N_NOT, f_not));
1633
}
1634
1635
/*
1636
 * expression -o expression functions --
1637
 *
1638
 *	Alternation of primaries; the OR operator.  The second expression is
1639
 * not evaluated if the first expression is true.
1640
 */
1641
int
1642
f_or(PLAN *plan, FTSENT *entry)
1643
{
1644
	PLAN *p;
1645
	int state;
1646
1647
21474
	for (p = plan->p_data[0];
1648
14741
	    p && (state = (p->eval)(p, entry)); p = p->next);
1649
1650
7051
	if (state)
1651
315
		return (1);
1652
1653
13754
	for (p = plan->p_data[1];
1654
14035
	    p && (state = (p->eval)(p, entry)); p = p->next);
1655
6736
	return (state);
1656
7051
}
1657
1658
PLAN *
1659
c_or(char *ignore, char ***ignored, int unused)
1660
{
1661
16
	return (palloc(N_OR, f_or));
1662
}
1663
1664
1665
/*
1666
 * plan_cleanup --
1667
 *	Check and see if the specified plan has any residual state,
1668
 *	and if so, clean it up as appropriate.
1669
 *
1670
 *	At the moment, only N_EXEC has state. Two kinds: 1)
1671
 * 	lists of files to feed to subprocesses 2) State on exit
1672
 *	statusses of past subprocesses.
1673
 */
1674
/* ARGSUSED1 */
1675
int
1676
plan_cleanup(PLAN *plan, void *arg)
1677
{
1678

609
	if (plan->type==N_EXEC && plan->ep_narg)
1679
		run_f_exec(plan);
1680
1681
303
	return plan->ep_rval;		/* Passed save exit-status up chain */
1682
}
1683
1684
1685
static PLAN *
1686
palloc(enum ntype t, int (*f)(PLAN *, FTSENT *))
1687
{
1688
	PLAN *new;
1689
1690
724
	if ((new = calloc(1, sizeof(PLAN)))) {
1691
362
		new->type = t;
1692
362
		new->eval = f;
1693
362
		return (new);
1694
	}
1695
	err(1, NULL);
1696
	/* NOTREACHED */
1697
}