1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1989-2012 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                                                                      *
19 ***********************************************************************/
20 #pragma prototyped
21 /*
22  * David Korn
23  * Glenn Fowler
24  * AT&T Research
25  *
26  * rewrite of find program to use fts*() and optget()
27  * this implementation should have all your favorite
28  * find options plus these extensions:
29  *
30  *	-fast pattern
31  *		traverses the fast find database (updatedb(1))
32  *		under the dirs specified on the command line
33  *		for paths that contain pattern; all other
34  *		expression operators apply to matching paths
35  *	-magic pattern
36  *		matches pattern agains the file(1) magic description
37  *	-mime type/subtype
38  *		matches type/subtype against the file mime type
39  *	-ignorecase
40  *		case ingnored in path pattern matching
41  *	-xargs command ... \;
42  *		like -exec except that command will be invoked
43  *		with as many file arguments as the system
44  *		allows per command
45  *	-test now
46  *		set the current time to now for testing
47  */
48 
49 static const char usage1[] =
50 "[-1p1?@(#)$Id: find (AT&T Research) 2012-04-11 $\n]"
51 USAGE_LICENSE
52 "[+NAME?find - find files]"
53 "[+DESCRIPTION?\bfind\b recursively descends the directory hierarchy for each"
54 "	\apath\a and applies an \aoption\a expression to each file in the"
55 "	hierarchy. \b-print\b is implied if there is no action that"
56 "	generates output. The expression starts with the first argument"
57 "	that matches [(-!]]. Option expressions may occur before and/or"
58 "	after the \apath\a arguments. For numeric arguments \an\a, \a+n\a"
59 "	means \a>n\a, \a-n\a means \a<n\a, and \an\a means exactly \an\a.]"
60 ;
61 
62 static const char usage2[] =
63 "\n"
64 "[ path ... ] [ options ]\n"
65 "\n"
66 "[+EXIT STATUS?If no commands were executed (\b-exec\b, \b-xargs\b) the exit"
67 "	status is 1 if errors were detected in the directory traversal and"
68 "	0 if no errors were ecountered. Otherwise the status is:]{"
69 "	[+0?All \acommand\a executions returned exit status 0.]"
70 "	[+1-125?A command line meeting the specified requirements could not"
71 "		be assembled, one or more of the invocations of \acommand\a"
72 "		returned  non-0 exit status, or some other error occurred.]"
73 "	[+126?\acommand\a was found but could not be executed.]"
74 "	[+127?\acommand\a was not found.]"
75 "}"
76 "[+ENVIRONMENT]{"
77 "	[+FINDCODES?Path name of the \blocate\b(1) database.]"
78 "	[+LOCATE_PATH?Alternate path name of \blocate\b(1) database.]"
79 "}"
80 "[+FILES]{"
81 "	[+lib/find/codes?Default \blocate\b(1) database.]"
82 "}"
83 "[+NOTES?In order to access the \bslocate\b(1) database the \bfind\b executable"
84 "	must be setgid to the \bslocate\b group.]"
85 "[+SEE ALSO?\bcpio\b(1), \bfile\b(1), \blocate\b(1), \bls\b(1), \bsh\b(1),"
86 "	\bslocate\b(1), \btest\b(1), \btw\b(1), \bupdatedb\b(1),"
87 "	\bxargs\b(1), \bstat\b(2)]"
88 ;
89 
90 #include <ast.h>
91 #include <ls.h>
92 #include <modex.h>
93 #include <find.h>
94 #include <fts.h>
95 #include <dirent.h>
96 #include <error.h>
97 #include <proc.h>
98 #include <tm.h>
99 #include <ctype.h>
100 #include <magic.h>
101 #include <mime.h>
102 #include <regex.h>
103 #include <vmalloc.h>
104 
105 #include "cmdarg.h"
106 
107 #define ignorecase	fts_number
108 
109 #define PATH(f)		((f)->fts_path)
110 
111 #define DAY		(unsigned long)(24*60*60)
112 
113 /*
114  * this is the list of operations
115  * retain the order
116  */
117 
118 #undef	NOGROUP
119 #undef	NOUSER
120 
121 enum Command
122 {
123 	CFALSE, CTRUE,
124 
125 	PRINT, PRINT0, PRINTF, PRINTX, FPRINT, FPRINT0, FPRINTF, FPRINTX,
126 	LS, FLS,
127 	ATIME, AMIN, ANEWER, CTIME, CMIN, CNEWER, MTIME, MMIN, NEWER,
128 	POST, LOCAL, XDEV, PHYS,
129 	NAME, USER, GROUP, INUM, SIZE, LINKS, PERM, EXEC, OK, CPIO, NCPIO,
130 	TYPE, AND, OR, NOT, COMMA, LPAREN, RPAREN, LOGIC, PRUNE,
131 	CHECK, SILENT, IGNORE, SORT, REVERSE, FSTYPE, META,
132 	NOGROUP, NOUSER, FAST, ICASE, MAGIC, MIME, XARGS,
133 	DAYSTART, MAXDEPTH, MINDEPTH, NOLEAF, EMPTY,
134 	ILNAME, INAME, IPATH,
135 	IREGEX, LNAME, PATH, REGEX, USED, XTYPE, CHOP,
136 	LEVEL, TEST, CODES
137 };
138 
139 #define Unary		(1<<0)
140 #define Num		(1<<1)
141 #define Str		(1<<2)
142 #define Exec		(1<<3)
143 #define Op		(1<<4)
144 #define File		(1<<5)
145 #define Re		(1<<6)
146 #define Stat		(1<<7)
147 #define Unit		(1<<8)
148 
149 typedef int (*Sort_f)(FTSENT* const*, FTSENT* const*);
150 
151 struct Node_s;
152 typedef struct Node_s Node_t;
153 
154 struct State_s;
155 typedef struct State_s State_t;
156 
157 typedef struct Args_s
158 {
159 	const char*	name;
160 	enum Command	action;
161 	int		type;
162 	int		primary;
163 	const char*	arg;
164 	const char*	values;
165 	const char*	help;
166 } Args_t;
167 
168 typedef union Value_u
169 {
170 	char**		p;
171 	char*		s;
172 	unsigned long	u;
173 	long		l;
174 	int		i;
175 	short		h;
176 	char		c;
177 } Value_t;
178 
179 typedef struct Fmt_s
180 {
181 	Sffmt_t		fmt;
182 	State_t*	state;
183 	FTSENT*		ent;
184 	char		tmp[PATH_MAX];
185 } Fmt_t;
186 
187 typedef union Item_u
188 {
189 	Node_t*		np;
190 	char*		cp;
191 	Cmdarg_t*	xp;
192 	regex_t*	re;
193 	Sfio_t*		fp;
194 	unsigned long	u;
195 	long		l;
196 	int		i;
197 } Item_t;
198 
199 struct Node_s
200 {
201 	Node_t*		next;
202 	const char*	name;
203 	enum Command	action;
204 	Item_t		first;
205 	Item_t		second;
206 	Item_t		third;
207 };
208 
209 struct State_s
210 {
211 	unsigned int	minlevel;
212 	unsigned int	maxlevel;
213 	int		walkflags;
214 	char		buf[LS_W_LONG+LS_W_INUMBER+LS_W_BLOCKS+2*PATH_MAX+1];
215 	char		txt[PATH_MAX+1];
216 	char*		usage;
217 	Node_t*		cmd;
218 	Proc_t*		proc;
219 	Node_t*		topnode;
220 	Node_t*		lastnode;
221 	Node_t*		nextnode;
222 	unsigned long	now;
223 	unsigned long	day;
224 	Sfio_t*		output;
225 	char*		codes;
226 	char*		fast;
227 	int		icase;
228 	int		primary;
229 	int		reverse;
230 	int		silent;
231 	enum Command	sortkey;
232 	Magic_t*	magic;
233 	Magicdisc_t	magicdisc;
234 	regdisc_t	redisc;
235 	Fmt_t		fmt;
236 	Sfio_t*		str;
237 	Sfio_t*		tmp;
238 	Vmalloc_t*	vm;
239 };
240 
241 static const char* const	cpio[] = { "cpio", "-o", 0 };
242 static const char* const	ncpio[] = { "cpio", "-ocB", 0 };
243 
244 /*
245  * Except for pathnames, these are the only legal arguments
246  */
247 
248 static const Args_t	commands[] =
249 {
250 "begin",	LPAREN,		Unary,		0,	0,	0,
251 	"Equivalent to \\(. Begin nested expression.",
252 "end",		RPAREN,		Unary,		0,	0,	0,
253 	"Equivalent to \\). End nested expression.",
254 "a|and",	AND,		Op,		0,	0,	0,
255 	"Equivalent to `\\&'. \aexpr1\a \b-and\b \aexpr2\a:"
256 	" Both \aexpr1\a and \aexpr2\a must evaluate \btrue\b."
257 	" This is the default operator for two expression in sequence.",
258 "amin",		AMIN,		Num|Stat,	0,	"minutes",	0,
259 	"File was last accessed \aminutes\a minutes ago.",
260 "anewer",	ANEWER,		Str|Stat,	0,	"file",	0,
261 	"File was last accessed more recently than \afile\a was modified.",
262 "atime",	ATIME,		Num|Stat,	0,	"days",	0,
263 	"File was last accessed \adays\a days ago.",
264 "check",	CHECK,		Unary,		0,	0,	0,
265 	"Turns off \b-silent\b; enables inaccessible file and directory"
266 	" warnings. This is the default.",
267 "chop",		CHOP,		Unary,		0,	0,	0,
268 	"Chop leading \b./\b from printed pathnames.",
269 "cmin",		CMIN,		Num|Stat,	0,	"minutes",	0,
270 	"File status changed \aminutes\a minutes ago.",
271 "cnewer",	CNEWER,		Str|Stat,	0,	"file",	0,
272 	"File status changed more recently than \afile\a was modified.",
273 "codes",	CODES,		Str,		0,	"path",	0,
274 	"Sets the \bfind\b or \blocate\b(1) database \apath\a."
275 	" See \bupdatedb\b(1) for a description of this database.",
276 "comma",	COMMA,		Op,		0,	0,	0,
277 	"Equivalent to `,'. Joins two expressions; the status of the first"
278 	" is ignored.",
279 "cpio",		CPIO,		Unary|Stat,	1,	0,	0,
280 	"File is written as a binary format \bcpio\b(1) file entry.",
281 "ctime",	CTIME,		Num|Stat,	0,	"days",	0,
282 	"File status changed \adays\a days ago.",
283 "daystart",	AMIN,		Unary|Stat,	0,	0,	0,
284 	"Measure times (-amin -atime -cmin -ctime -mmin -mtime) from the"
285 	" beginning of today. The default is 24 hours ago.",
286 "depth",	POST,		Unary,		0,	0,	0,
287 	"Process directory contents before the directory itself.",
288 "empty",	EMPTY,		Unary|Stat,	0,	0,	0,
289 	"A directory with size 0 or with no entries other than . or .., or a"
290 	" regular file with size 0.",
291 "exec",		EXEC,		Exec,		1,	"command ... ; | command ... {} +", 0,
292 	"Execute \acommand ...\a; true if 0 exit status is returned."
293 	" Arguments up to \b;\b are taken as arguments to \acommand\a."
294 	" The string `{}' in \acommand ...\a is globally replaced by "
295 	" the current filename. The command is executed in the directory"
296 	" from which \bfind\b was executed. The second form gathers"
297 	" pathnames until \bARG_MAX\b is reached, replaces {} preceding"
298 	" \b+\b with the list of pathnames, one pathname per argument,"
299 	" and executes \acommand\a ... \apathname\a ..., possibly multiple"
300 	" times, until all pathnames are exhausted.",
301 "false",	CFALSE,		Unary,		0,	0,	0,
302 	"Always false.",
303 "fast",		FAST,		Str,		0,	"pattern",	0,
304 	"Searches the \bfind\b or \blocate\b(1) database for paths"
305 	" matching the \bksh\b(1) \apattern\a. See \bupdatedb\b(1) for"
306 	" details on this database. The command line arguments limit"
307 	" the search and the expression, but all depth options are ignored."
308 	" The remainder of the expression is applied to the matching paths.",
309 "fls",		FLS,		File|Stat,	1,	"file",	0,
310 	"Like -ls except the output is written to \afile\a.",
311 "fprint",	FPRINT,		File|Stat,	1,	"file",	0,
312 	"Like -print except the output is written to \afile\a.",
313 "fprint0",	FPRINT0,	File|Stat,	1,	"file",	0,
314 	"Like -print0 except the output is written to \afile\a.",
315 "fprintf",	FPRINTF,	File|Stat,	1,	"file format",	0,
316 	"Like -printf except the output is written to \afile\a.",
317 "fprintx",	FPRINTX,	File|Stat,	1,	"file",	0,
318 	"Like -printx except the output is written to \afile\a.",
319 "fstype",	FSTYPE,		Str|Stat,	0,	"type",	0,
320 	"File is on a filesystem \atype\a. See \bdf\b(1) or"
321 	" \b-printf %F\b for local filesystem type names.",
322 "group|gid",	GROUP,		Str|Stat,	0,	"id",	0,
323 	"File group id name or number matches \aid\a.",
324 "ignorecase",	ICASE,		Unary,		0,	0,	0,
325 	"Ignore case in all pattern match expressions.",
326 "ilname",	ILNAME,		Str,		0,	"pattern",	0,
327 	"A case-insensitive version of \b-lname\b \apattern\a.",
328 "iname",	INAME,		Str,		0,	"pattern",	0,
329 	"A case-insensitive version of \b-name\b \apattern\a.",
330 "inum",		INUM,		Num|Stat,	0,	"number",	0,
331 	"File has inode number \anumber\a.",
332 "ipath",	IPATH,		Str,		0,	"pattern",	0,
333 	"A case-insensitive version of \b-path\b \apattern\a.",
334 "iregex",	IREGEX,		Re,		0,	"pattern",	0,
335 	"A case-insensitive version of \b-regex\b \apattern\a.",
336 "level",	LEVEL,		Num,		0,	"level",	0,
337 	"Current level (depth) is \alevel\a.",
338 "links",	LINKS,		Num|Stat,	0,	"number",	0,
339 	"File has \anumber\a links.",
340 "lname",	LNAME,		Str,		0,	"pattern",	0,
341 	"File is a symbolic link with text that matches \apattern\a.",
342 "local",	LOCAL,		Unary|Stat,	0,	0,	0,
343 	"File is on a local filesystem.",
344 "logical|follow|L",LOGIC,	Unary,		0,	0,	0,
345 	"Follow symbolic links.",
346 "ls",		LS,		Unary|Stat,	1,	0,	0,
347 	"List the current file in `ls -dils' format to the standard output.",
348 "magic",	MAGIC,		Str,		0,	"pattern",	0,
349 	"File magic number matches the \bfile\b(1) and \bmagic\b(3)"
350 	" description \apattern\a.",
351 "maxdepth",	MAXDEPTH,	Num,		0,	"level",	0,
352 	"Descend at most \alevel\a directory levels below the command"
353 	" line arguments. \b-maxdepth 0\b limits the search to the command"
354 	" line arguments.",
355 "metaphysical|H",META,		Unary,		0,	0,	0,
356 	"\b-logical\b for command line arguments, \b-physical\b otherwise.",
357 "mime",		MIME,		Str,		0,	"type/subtype",	0,
358 	"File mime type matches the pattern \atype/subtype\a.",
359 "mindepth",	MINDEPTH,	Num,		0,	"level",	0,
360 	"Do not apply tests or actions a levels less than \alevel\a."
361 	" \b-mindepth 1\b processes all but the command line arguments.",
362 "mmin",		MMIN,		Num|Stat,	0,	"minutes",	0,
363 	"File was modified \aminutes\a minutes ago.",
364 "mount|x|xdev|X",XDEV,		Unary|Stat,	0,	0,	0,
365 	"Do not descend into directories in different filesystems than"
366 	" their parents.",
367 "mtime",	MTIME,		Num|Stat,	0,	"days",	0,
368 	"File was modified \adays\a days ago.",
369 "name",		NAME,		Str,		0,	"pattern",	0,
370 	"File base name (no directory components) matches \apattern\a.",
371 "ncpio",	NCPIO,		Unary|Stat,	1,	0,		0,
372 	"File is written as a character format \bcpio\b(1) file entry.",
373 "newer",	NEWER,		Str|Stat,	0,	"file",	0,
374 	"File was modified more recently than \afile\a.",
375 "nogroup",	NOGROUP,	Unary|Stat,	0,	0,	0,
376 	"There is no group name matching the file group id.",
377 "noleaf",	NOLEAF,		Unary|Stat,	0,	0,	0,
378 	"Disable \b-physical\b leaf file \bstat\b(2) optimizations."
379 	" Only required on filesystems with . and .. as the first entries"
380 	" and link count not equal to 2 plus the number of subdirectories.",
381 "not",		NOT,		Op,		0,	0,	0,
382 	"\b-not\b \aexpr\a: inverts the truth value of \aexpr\a.",
383 "nouser",	NOUSER,		Unary|Stat,	0,	0,	0,
384 	"There is no user name matching the file user id.",
385 "ok",		OK,		Exec,		1,	"command ... \\;", 0,
386 	"Like \b-exec\b except a prompt is written to the terminal."
387 	" If the response does not match `[yY]].*' then the command"
388 	" is not run and false is returned.",
389 "o|or",		OR,		Op,		0,	0,	0,
390 	"Equivalent to `\\|'. \aexpr1\a \b-or\b \aexpr2\a:"
391 	" \aexpr2\a is not"
392 	" evaluated if \aexpr1\a is true.",
393 "path",		PATH,		Str,		0,	"pattern",	0,
394 	"File path name (with directory components) matches \apattern\a.",
395 "perm",		PERM,		Num|Stat,	0,	"mode",	0,
396 	"File permission bits tests; \amode\a may be octal or symbolic as"
397 	" in \bchmod\b(1). \amode\a: exactly \amode\a; \a-mode\a: all"
398 	" \amode\a bits are set; \a+mode\a: at least one of \amode\a"
399 	" bits are set.",
400 "physical|phys|P",PHYS,		Unary,		0,	0,	0,
401 	"Do not follow symbolic links. This is the default.",
402 "post",		POST,		Unary,		0,	0,	0,
403 	"Process directories before and and after the contents are processed.",
404 "print",	PRINT,		Unary,		1,	0,	0,
405 	"Print the path name (including directory components) to the"
406 	" standard output, followed by a newline.",
407 "print0",	PRINT0,		Unary,		1,	0,	0,
408 	"Like \b-print\b, except that the path is followed by a NUL character.",
409 "printf",	PRINTF,		Str|Stat,	1,	"format",
410 	"[+----- escape sequences -----?]"
411 		"[+\\a?alert]"
412 		"[+\\b?backspace]"
413 		"[+\\f?form feed]"
414 		"[+\\n?newline]"
415 		"[+\\t?horizontal tab]"
416 		"[+\\v?vertical tab]"
417 		"[+\\xnn?hexadecimal character \ann\a]"
418 		"[+\\nnn?octal character \annn\a]"
419 	"[+----- format directives -----?]"
420 		"[+%%?literal %]"
421 		"[+%a?access time in \bctime\b(3) format]"
422 		"[+%Ac?access time is \bstrftime\b(3) %\ac\a format]"
423 		"[+%b?file size in 512 byte blocks]"
424 		"[+%c?status change time in \bctime\b(3) format]"
425 		"[+%Cc?status change time is \bstrftime\b(3) %\ac\a format]"
426 		"[+%d?directory tree depth; 0 means command line argument]"
427 		"[+%f?file base name (no directory components)]"
428 		"[+%F?filesystem type name; use this for \b-fstype\b]"
429 		"[+%g?group name, or numeric group id if no name found]"
430 		"[+%G?numeric group id]"
431 		"[+%h?file directory name (no base component)]"
432 		"[+%H?command line argument under which file is found]"
433 		"[+%i?file inode number]"
434 		"[+%k?file size in kilobytes]"
435 		"[+%l?symbolic link text, empty if not symbolic link]"
436 		"[+%m?permission bits in octal]"
437 		"[+%n?number of hard links]"
438 		"[+%p?full path name]"
439 		"[+%P?file path with command line argument prefix deleted]"
440 		"[+%s?file size in bytes]"
441 		"[+%t?modify time in \bctime\b(3) format]"
442 		"[+%Tc?modify time is \bstrftime\b(3) %\ac\a format]"
443 		"[+%u?user name, or numeric user id if no name found]"
444 		"[+%U?numeric user id]"
445 		"[+%x?%p quoted for \bxargs\b(1)]"
446 		"[+%X?%P quoted for \bxargs\b(1)]",
447 	"Print format \aformat\a on the standard output, interpreting"
448 	" `\\' escapes and `%' directives. \bprintf\b(3) field width"
449 	" and precision are interpreted as usual, but the directive"
450 	" characters have special interpretation.",
451 "printx",	PRINTX,		Unary,		1,	0,	0,
452 	"Print the path name (including directory components) to the"
453 	" standard output, with \bxargs\b(1) special characters preceded"
454 	" by \b\\\b, followed by a newline.",
455 "prune",	PRUNE,		Unary,		0,	0,	0,
456 	"Ignored if \b-depth\b is given, otherwise do not descend the"
457 	" current directory.",
458 "regex",	REGEX,		Re,		0,	"pattern",	0,
459 	"Path name matches the anchored regular expression \apattern\a,"
460 	" i.e., leading ^ and traling $ are implied.",
461 "reverse",	REVERSE,	Unary,		0,	0,	0,
462 	"Reverse the \b-sort\b sense.",
463 "silent",	SILENT,		Unary,		0,	0,	0,
464 	"Do not warn about inaccessible directories or symbolic link loops.",
465 "size",		SIZE,		Num|Stat|Unit,	0,	"number[bcgkm]]", 0,
466 	"File size is \anumber\a units (b: 512 byte blocks, c: characters"
467 	" g: 1024*1024*1024 blocks, k: 1024 blocks, m: 1024*1024 blocks.)"
468 	" Sizes are rounded to the next unit.",
469 "sort",		SORT,		Str,		0,	"option",	0,
470 	"Search each directory in \a-option\a sort order, e.g., \b-name\b"
471 	" sorts by name, \b-size\b sorts by size.",
472 "test",		TEST,		Num,		0,	"seconds",	0,
473 	"Set the current time to \aseconds\a since the epoch. Other"
474 	" implementation defined test modes may also be enabled.",
475 "true",		CTRUE,		Unary,		0,	0,	0,
476 	"Always true.",
477 "type",		TYPE,		Str|Stat,	0,	"type",
478 		"[+b?block special]"
479 		"[+c?character special]"
480 		"[+d?directory]"
481 		"[+f?regular file]"
482 		"[+l?symbolic link]"
483 		"[+p?named pipe (FIFO)]"
484 		"[+s?socket]"
485 		"[+C?contiguous]"
486 		"[+D?door]",
487 	"File type matches \atype\a:",
488 "used",		USED,		Num|Stat,	0,	"days",	0,
489 	"File was accessed \adays\a days after its status changed.",
490 "user|uid",	USER,		Str|Stat,0,	"id",	0,
491 	"File user id matches the name or number \aid\a.",
492 "xargs",	XARGS,		Exec,		1,	"command ... \\;", 0,
493 	"Like \b-exec\b except as many file args as permitted are"
494 	" appended to \acommand ...\a which may be executed"
495 	" 0 or more times depending on the number of files found and"
496 	" local system \bexec\b(2) argument limits.",
497 "xtype",	XTYPE,		Str|Stat,	0,	"type",	0,
498 	"Like \b-type\b, except if symbolic links are followed, the test"
499 	" is applied to the symbolic link itself, otherwise the test is applied"
500 	" to the pointed to file. Equivalent to \b-type\b if no symbolic"
501 	" links are involved.",
502 	0,
503 };
504 
505 /*
506  *  Table lookup routine
507  */
508 
509 static Args_t*
lookup(register char * word)510 lookup(register char* word)
511 {
512 	register Args_t*	argp;
513 	register int		second;
514 
515 	while (*word == '-')
516 		word++;
517 	if (*word)
518 	{
519 		second = word[1];
520 		for (argp = (Args_t*)commands; argp->name; argp++)
521 			if (second == argp->name[1] && streq(word, argp->name))
522 				return argp;
523 	}
524 	return 0;
525 }
526 
527 /*
528  * quote path component to sp for xargs(1)
529  */
530 
531 static void
quotex(register Sfio_t * sp,register const char * s,int term)532 quotex(register Sfio_t* sp, register const char* s, int term)
533 {
534 	register int	c;
535 
536 	while (c = *s++)
537 	{
538 		if (isspace(c) || c == '\\' || c == '\'' || c == '"')
539 			sfputc(sp, '\\');
540 		sfputc(sp, c);
541 	}
542 	if (term >= 0)
543 		sfputc(sp, term);
544 }
545 
546 /*
547  * printf %! extension function
548  */
549 
550 static int
print(Sfio_t * sp,void * vp,Sffmt_t * dp)551 print(Sfio_t* sp, void* vp, Sffmt_t* dp)
552 {
553 	register Fmt_t*		fp = (Fmt_t*)dp;
554 	register FTSENT*	ent = fp->ent;
555 	register State_t*	state = fp->state;
556 	Value_t*		value = (Value_t*)vp;
557 
558 	char*			s;
559 
560 	if (dp->n_str > 0)
561 		sfsprintf(s = fp->tmp, sizeof(fp->tmp), "%.*s", dp->n_str, dp->t_str);
562 	else
563 		s = 0;
564 	switch (dp->fmt)
565 	{
566 	case 'A':
567 		dp->fmt = 's';
568 		dp->size = -1;
569 		value->s = fmttime(s, ent->fts_statp->st_atime);
570 		break;
571 	case 'b':
572 		dp->fmt = 'u';
573 		value->u = iblocks(ent->fts_statp);
574 		break;
575 	case 'C':
576 		dp->fmt = 's';
577 		dp->size = -1;
578 		value->s = fmttime(s, ent->fts_statp->st_ctime);
579 		break;
580 	case 'd':
581 		dp->fmt = 'u';
582 		value->u = ent->fts_level;
583 		break;
584 	case 'H':
585 		while (ent->fts_level > 0)
586 			ent = ent->fts_parent;
587 		/*FALLTHROUGH*/
588 	case 'f':
589 		dp->fmt = 's';
590 		dp->size = ent->fts_namelen;
591 		value->s = ent->fts_name;
592 		break;
593 	case 'F':
594 		dp->fmt = 's';
595 		dp->size = -1;
596 		value->s = fmtfs(ent->fts_statp);
597 		break;
598 	case 'g':
599 		dp->fmt = 's';
600 		dp->size = -1;
601 		value->s = fmtgid(ent->fts_statp->st_gid);
602 		break;
603 	case 'G':
604 		dp->fmt = 'd';
605 		value->i = ent->fts_statp->st_gid;
606 		break;
607 	case 'i':
608 		dp->fmt = 'u';
609 		value->u = ent->fts_statp->st_ino;
610 		break;
611 	case 'k':
612 		dp->fmt = 'u';
613 		value->u = iblocks(ent->fts_statp);
614 		break;
615 	case 'm':
616 		dp->fmt = 'o';
617 		value->i = ent->fts_statp->st_mode;
618 		break;
619 	case 'n':
620 		dp->fmt = 'u';
621 		value->u = ent->fts_statp->st_nlink;
622 		break;
623 	case 'p':
624 		dp->fmt = 's';
625 		dp->size = ent->fts_pathlen;
626 		value->s = ent->fts_path;
627 		break;
628 	case 'P':
629 		dp->fmt = 's';
630 		dp->size = -1;
631 		s = ent->fts_path;
632 		while (ent->fts_level > 0)
633 			ent = ent->fts_parent;
634 		s += ent->fts_pathlen;
635 		if (*s == '/')
636 			s++;
637 		value->s = s;
638 		break;
639 	case 's':
640 		dp->fmt = 'u';
641 		value->u = ent->fts_statp->st_size;
642 		break;
643 	case 'T':
644 		dp->fmt = 's';
645 		dp->size = -1;
646 		value->s = fmttime(s, ent->fts_statp->st_mtime);
647 		break;
648 	case 'u':
649 		dp->fmt = 's';
650 		dp->size = -1;
651 		value->s = fmtuid(ent->fts_statp->st_uid);
652 		break;
653 	case 'U':
654 		dp->fmt = 'd';
655 		value->i = ent->fts_statp->st_uid;
656 		break;
657 	case 'x':
658 		dp->fmt = 's';
659 		quotex(state->tmp, ent->fts_path, -1);
660 		dp->size = sfstrtell(state->tmp);
661 		if (!(value->s = sfstruse(state->tmp)))
662 		{
663 			error(ERROR_SYSTEM|2, "out of space");
664 			return -1;
665 		}
666 		break;
667 	case 'X':
668 		dp->fmt = 's';
669 		s = ent->fts_path;
670 		while (ent->fts_level > 0)
671 			ent = ent->fts_parent;
672 		s += ent->fts_pathlen;
673 		if (*s == '/')
674 			s++;
675 		quotex(state->tmp, s, -1);
676 		dp->size = sfstrtell(state->tmp);
677 		if (!(value->s = sfstruse(state->tmp)))
678 		{
679 			error(ERROR_SYSTEM|2, "out of space");
680 			return -1;
681 		}
682 		break;
683 	case 'Y':
684 		if (s)
685 		{
686 			switch (*s)
687 			{
688 			case 'H':
689 				dp->fmt = 's';
690 				dp->size = -1;
691 				value->s = "ERROR";
692 				break;
693 			case 'h':
694 				dp->fmt = 's';
695 				if (s = strrchr(ent->fts_path, '/'))
696 				{
697 					value->s = ent->fts_path;
698 					dp->size = s - ent->fts_path;
699 				}
700 				else
701 				{
702 					value->s = ".";
703 					dp->size = 1;
704 				}
705 				break;
706 			case 'l':
707 				dp->fmt = 's';
708 				dp->size = -1;
709 				value->s = S_ISLNK(ent->fts_statp->st_mode) && pathgetlink(PATH(ent), fp->tmp, sizeof(fp->tmp)) > 0 ? fp->tmp : "";
710 				break;
711 			default:
712 				error(2, "%%(%s)Y: invalid %%Y argument", s);
713 				return -1;
714 			}
715 			break;
716 		}
717 		/*FALLTHROUGH*/
718 	default:
719 		error(2, "internal error: %%%c: unknown format", dp->fmt);
720 		return -1;
721 	case 'Z':
722 		dp->fmt = 'c';
723 		value->i = 0;
724 		break;
725 	}
726 	dp->flags |= SFFMT_VALUE;
727 	return 0;
728 }
729 
730 /*
731  * convert the gnu-style-find printf format string for sfio extension
732  */
733 
734 static char*
format(State_t * state,register char * s)735 format(State_t* state, register char* s)
736 {
737 	register char*	t;
738 	register int	c;
739 	char*		b;
740 
741 	stresc(s);
742 	c = strlen(s);
743 	if (!(t = vmnewof(state->vm, 0, char, c * 2, 0)))
744 	{
745 		error(ERROR_SYSTEM|2, "out of space");
746 		return 0;
747 	}
748 	b = t;
749 	while (c = *s++)
750 	{
751 		if (c == '%')
752 		{
753 			if (*s == '%')
754 			{
755 				*t++ = c;
756 				*t++ = *s++;
757 			}
758 			else
759 			{
760 				do
761 				{
762 					*t++ = c;
763 				} while ((c = *s++) && !isalpha(c));
764 				if (!c)
765 					break;
766 				switch (c)
767 				{
768 				case 'A':
769 				case 'C':
770 				case 'T':
771 					*t++ = '(';
772 					*t++ = '%';
773 					switch (*t++ = *s++)
774 					{
775 					case '@':
776 						*(t - 1) = '#';
777 						break;
778 					default:
779 						if (isalpha(*(t - 1)))
780 							break;
781 						*(t - 1) = 'K';
782 						s--;
783 						break;
784 					}
785 					*t++ = ')';
786 					break;
787 				case 'a':
788 				case 'c':
789 				case 't':
790 					c = toupper(c);
791 					break;
792 				case 'H':
793 				case 'h':
794 				case 'l':
795 					*t++ = '(';
796 					*t++ = c;
797 					*t++ = ')';
798 					c = 'Y';
799 					break;
800 				case 'b':
801 				case 'd':
802 				case 'f':
803 				case 'F':
804 				case 'g':
805 				case 'G':
806 				case 'i':
807 				case 'k':
808 				case 'm':
809 				case 'n':
810 				case 'p':
811 				case 'P':
812 				case 's':
813 				case 'u':
814 				case 'U':
815 				case 'x':
816 				case 'X':
817 				case 'Z':
818 					break;
819 				default:
820 					error(2, "%%%c: unknown format", c);
821 					return 0;
822 				}
823 			}
824 		}
825 		*t++ = c;
826 	}
827 	*t = 0;
828 	return b;
829 }
830 
831 /*
832  * compile the arguments
833  */
834 
835 static int
compile(State_t * state,char ** argv,register Node_t * np,int nested)836 compile(State_t* state, char** argv, register Node_t* np, int nested)
837 {
838 	register char*		b;
839 	register Node_t*	oldnp = 0;
840 	register const Args_t*	argp;
841 	Node_t*			tp;
842 	char*			e;
843 	char**			com;
844 	regdisc_t*		redisc;
845 	int			index = opt_info.index;
846 	int			i;
847 	int			k;
848 	Cmddisc_t		disc;
849 	enum Command		oldop = PRINT;
850 
851 	for (;;)
852 	{
853 		if ((i = optget(argv, state->usage)) > 0)
854 		{
855 			k = argv[opt_info.index-1][0];
856 			if (i == '?')
857 				error(ERROR_USAGE|4, "%s", opt_info.arg);
858 			if (i == ':')
859 				error(2, "%s", opt_info.arg);
860 			continue;
861 		}
862 		else if (i == 0)
863 		{
864 			if (e = argv[opt_info.index])
865 			{
866 				k = e[0];
867 				if (!e[1] || e[1] == k && !e[2])
868 					switch (k)
869 					{
870 					case '(':
871 						argv[opt_info.index] = "-begin";
872 						continue;
873 					case ')':
874 						argv[opt_info.index] = "-end";
875 						continue;
876 					case '!':
877 						argv[opt_info.index] = "-not";
878 						continue;
879 					case '&':
880 						argv[opt_info.index] = "-and";
881 						continue;
882 					case '|':
883 						argv[opt_info.index] = "-or";
884 						continue;
885 					}
886 			}
887 			oldop = PRINT;
888 			break;
889 		}
890 		argp = commands - (i + 10);
891 		state->primary |= argp->primary;
892 		np->next = 0;
893 		np->name = argp->name;
894 		np->action = argp->action;
895 		np->second.i = 0;
896 		np->third.u = 0;
897 		if (argp->type & Stat)
898 			state->walkflags &= ~FTS_NOSTAT;
899 		if (argp->type & Op)
900 		{
901 			if (oldop == NOT || np->action != NOT && (oldop != PRINT || !oldnp))
902 			{
903 				error(2, "%s: operator syntax error", np->name);
904 				return -1;
905 			}
906 			oldop = argp->action;
907 		}
908 		else
909 		{
910 			oldop = PRINT;
911 			if (!(argp->type & Unary))
912 			{
913 				b = opt_info.arg;
914 				switch (argp->type & ~(Stat|Unit))
915 				{
916 				case File:
917 					if (streq(b, "/dev/stdout") || streq(b, "/dev/fd/1"))
918 						np->first.fp = state->output;
919 					else if (!(np->first.fp = sfopen(NiL, b, "w")))
920 					{
921 						error(ERROR_SYSTEM|2, "%s: cannot write", b);
922 						return -1;
923 					}
924 					break;
925 				case Num:
926 					if (*b == '+' || *b == '-')
927 					{
928 						np->second.i = *b;
929 						b++;
930 					}
931 					np->first.u = strtoul(b, &e, 0);
932 					if (argp->type & Unit)
933 						switch (*e++)
934 						{
935 						default:
936 							e--;
937 							/*FALLTHROUGH*/
938 						case 'b':
939 							np->third.u = 512;
940 							break;
941 						case 'c':
942 							break;
943 						case 'g':
944 							np->third.u = 1024 * 1024 * 1024;
945 							break;
946 						case 'k':
947 							np->third.u = 1024;
948 							break;
949 						case 'm':
950 							np->third.u = 1024 * 1024;
951 							break;
952 						case 'w':
953 							np->third.u = 2;
954 							break;
955 						}
956 					if (*e)
957 						error(1, "%s: invalid character%s after number", e, *(e + 1) ? "s" : "");
958 					break;
959 				default:
960 					np->first.cp = b;
961 					break;
962 				}
963 			}
964 		}
965 		switch (argp->action)
966 		{
967 		case AND:
968 			continue;
969 		case OR:
970 		case COMMA:
971 			np->first.np = state->topnode;
972 			state->topnode = np;
973 			oldnp->next = 0;
974 			break;
975 		case LPAREN:
976 			tp = state->topnode;
977 			state->topnode = np + 1;
978 			if ((i = compile(state, argv, state->topnode, 1)) < 0)
979 				return i;
980 			if (!streq(argv[opt_info.index-1], "-end"))
981 			{
982 				error(2, "(...) imbalance -- closing ) expected", np->name);
983 				return -1;
984 			}
985 			np->first.np = state->topnode;
986 			state->topnode = tp;
987 			oldnp = np;
988 			np->next = np + i;
989 			np += i;
990 			continue;
991 		case RPAREN:
992 			if (!oldnp || !nested)
993 			{
994 				error(2, "(...) imbalance -- opening ( omitted", np->name);
995 				return -1;
996 			}
997 			oldnp->next = 0;
998 			return opt_info.index - index;
999 		case LOGIC:
1000 			state->walkflags &= ~(FTS_META|FTS_PHYSICAL);
1001 		ignore:
1002 			np->action = IGNORE;
1003 			continue;
1004 		case META:
1005 			state->walkflags |= FTS_META|FTS_PHYSICAL;
1006 			goto ignore;
1007 		case PHYS:
1008 			state->walkflags &= ~FTS_META;
1009 			state->walkflags |= FTS_PHYSICAL;
1010 			goto ignore;
1011 		case XDEV:
1012 			state->walkflags |= FTS_XDEV;
1013 			goto ignore;
1014 		case POST:
1015 			state->walkflags &= ~FTS_NOPOSTORDER;
1016 			goto ignore;
1017 		case CHECK:
1018 			state->silent = 0;
1019 			goto ignore;
1020 		case NOLEAF:
1021 			goto ignore;
1022 		case REVERSE:
1023 			state->reverse = 1;
1024 			goto ignore;
1025 		case SILENT:
1026 			state->silent = 1;
1027 			goto ignore;
1028 		case CODES:
1029 			state->codes = b;
1030 			goto ignore;
1031 		case FAST:
1032 			state->fast = b;
1033 			goto ignore;
1034 		case ICASE:
1035 			state->icase = 1;
1036 			goto ignore;
1037 		case LOCAL:
1038 			np->first.l = 0;
1039 			np->second.i = '-';
1040 			break;
1041 		case ATIME:
1042 		case CTIME:
1043 		case MTIME:
1044 			switch (np->second.i)
1045 			{
1046 			case '+':
1047 				np->second.u = state->day - (np->first.u + 1) * DAY - 1;
1048 				np->first.u = 0;
1049 				break;
1050 			case '-':
1051 				np->second.u = ~0;
1052 				np->first.u = state->day - np->first.u * DAY + 1;
1053 				break;
1054 			default:
1055 				np->second.u = state->day - np->first.u * DAY - 1;
1056 				np->first.u = state->day - (np->first.u + 1) * DAY;
1057 				break;
1058 			}
1059 			break;
1060 		case AMIN:
1061 		case CMIN:
1062 		case MMIN:
1063 			np->action--;
1064 			switch (np->second.i)
1065 			{
1066 			case '+':
1067 				np->second.u = state->now - np->first.u * 60;
1068 				np->first.u = 0;
1069 				break;
1070 			case '-':
1071 				np->first.u = state->now - np->first.u * 60;
1072 				np->second.u = ~0;
1073 				break;
1074 			default:
1075 				np->second.u = state->now - np->first.u * 60;
1076 				np->first.u = np->second.u - 60;
1077 				break;
1078 			}
1079 			break;
1080 		case USER:
1081 			if ((np->first.l = struid(b)) < 0)
1082 			{
1083 				error(2, "%s: invalid user name", np->name);
1084 				return -1;
1085 			}
1086 			break;
1087 		case GROUP:
1088 			if ((np->first.l = strgid(b)) < 0)
1089 			{
1090 				error(2, "%s: invalid group name", np->name);
1091 				return -1;
1092 			}
1093 			break;
1094 		case EXEC:
1095 		case OK:
1096 		case XARGS:
1097 			state->walkflags |= FTS_NOCHDIR;
1098 			com = argv + opt_info.index - 1;
1099 			i = np->action == XARGS ? 0 : 1;
1100 			k = np->action == OK ? CMD_QUERY : 0;
1101 			for (;;)
1102 			{
1103 				if (!(b = argv[opt_info.index++]))
1104 				{
1105 					error(2, "incomplete statement");
1106 					return -1;
1107 				}
1108 				if (streq(b, ";"))
1109 					break;
1110 				if (strmatch(b, "*{}*"))
1111 				{
1112 					if (!(k & CMD_INSERT) && streq(b, "{}") && (b = argv[opt_info.index]) && (streq(b, ";") || streq(b, "+") && !(i = 0)))
1113 					{
1114 						argv[opt_info.index - 1] = 0;
1115 						opt_info.index++;
1116 						break;
1117 					}
1118 					k |= CMD_INSERT;
1119 				}
1120 			}
1121 			argv[opt_info.index - 1] = 0;
1122 			if (k & CMD_INSERT)
1123 				i = 1;
1124 			CMDDISC(&disc, k|CMD_EXIT|CMD_IGNORE, errorf);
1125 			if (!(np->first.xp = cmdopen(com, i, 0, "{}", &disc)))
1126 			{
1127 				error(ERROR_SYSTEM|2, "out of space");
1128 				return -1;
1129 			}
1130 			np->second.np = state->cmd;
1131 			state->cmd = np;
1132 			break;
1133 		case MAGIC:
1134 		case MIME:
1135 			if (!state->magic)
1136 			{
1137 				state->magicdisc.version = MAGIC_VERSION;
1138 				state->magicdisc.flags = 0;
1139 				state->magicdisc.errorf = errorf;
1140 				if (!(state->magic = magicopen(&state->magicdisc)) || magicload(state->magic, NiL, 0))
1141 				{
1142 					error(2, "%s: cannot load magic file", MAGIC_FILE);
1143 					return -1;
1144 				}
1145 			}
1146 			break;
1147 		case IREGEX:
1148 		case REGEX:
1149 			if (!(np->second.re = vmnewof(state->vm, 0, regex_t, 1, sizeof(regdisc_t))))
1150 			{
1151 				error(ERROR_SYSTEM|2, "out of space");
1152 				return -1;
1153 			}
1154 			redisc = (regdisc_t*)(np->second.re + 1);
1155 			redisc->re_version = REG_VERSION;
1156 			redisc->re_flags = REG_NOFREE;
1157 			redisc->re_errorf = (regerror_t)errorf;
1158 			redisc->re_resizef = (regresize_t)vmgetmem;
1159 			redisc->re_resizehandle = (void*)state->vm;
1160 			np->second.re->re_disc = redisc;
1161 			i = REG_EXTENDED|REG_LENIENT|REG_NOSUB|REG_NULL|REG_LEFT|REG_RIGHT|REG_DISCIPLINE;
1162 			if (argp->action == IREGEX)
1163 			{
1164 				i |= REG_ICASE;
1165 				np->action = REGEX;
1166 			}
1167 			if (i = regcomp(np->second.re, b, i))
1168 			{
1169 				regfatal(np->second.re, 2, i);
1170 				return -1;
1171 			}
1172 			break;
1173 		case PERM:
1174 			if (*b == '-' || *b == '+')
1175 				np->second.l = *b++;
1176 			np->first.l = strperm(b, &e, -1);
1177 			if (*e)
1178 			{
1179 				error(2, "%s: invalid permission expression", e);
1180 				return -1;
1181 			}
1182 			break;
1183 		case SORT:
1184 			if (!(argp = lookup(b)))
1185 			{
1186 				error(2, "%s: invalid sort key", b);
1187 				return -1;
1188 			}
1189 			state->sortkey = argp->action;
1190 			goto ignore;
1191 		case TYPE:
1192 		case XTYPE:
1193 			np->first.l = *b;
1194 			break;
1195 		case CPIO:
1196 			com = (char**)cpio;
1197 			goto common;
1198 		case NCPIO:
1199 			{
1200 				long	ops[2];
1201 				int	fd;
1202 
1203 				com = (char**)ncpio;
1204 			common:
1205 				/*
1206 				 * set up cpio
1207 				 */
1208 
1209 				if ((fd = open(b, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) < 0)
1210 				{
1211 					error(ERROR_SYSTEM|2, "%s: cannot create", b);
1212 					return -1;
1213 				}
1214 				ops[0] = PROC_FD_DUP(fd, 1, PROC_FD_PARENT|PROC_FD_CHILD);
1215 				ops[1] = 0;
1216 				if (!(state->proc = procopen("cpio", com, NiL, ops, PROC_WRITE)))
1217 				{
1218 					error(ERROR_SYSTEM|2, "cpio: cannot exec");
1219 					return -1;
1220 				}
1221 				if (!(state->output = sfnew(NiL, NiL, -1, state->proc->wfd, SF_WRITE)))
1222 				{
1223 					error(ERROR_SYSTEM|2, "cpio: cannot write");
1224 					return -1;
1225 				}
1226 				state->walkflags &= ~FTS_NOPOSTORDER;
1227 				np->action = PRINT;
1228 			}
1229 			/*FALLTHROUGH*/
1230 		case PRINT:
1231 			np->first.fp = state->output;
1232 			np->second.i = '\n';
1233 			break;
1234 		case PRINT0:
1235 			np->first.fp = state->output;
1236 			np->second.i = 0;
1237 			np->action = PRINT;
1238 			break;
1239 		case PRINTF:
1240 			np->second.cp = format(state, np->first.cp);
1241 			np->first.fp = state->output;
1242 			break;
1243 		case PRINTX:
1244 			np->first.fp = state->output;
1245 			np->second.i = '\n';
1246 			break;
1247 		case FPRINT:
1248 			np->second.i = '\n';
1249 			np->action = PRINT;
1250 			break;
1251 		case FPRINT0:
1252 			np->second.i = 0;
1253 			np->action = PRINT;
1254 			break;
1255 		case FPRINTF:
1256 			if (!(b = argv[opt_info.index++]))
1257 			{
1258 				error(2, "incomplete statement");
1259 				return -1;
1260 			}
1261 			np->second.cp = format(state, b);
1262 			break;
1263 		case FPRINTX:
1264 			np->second.i = '\n';
1265 			np->action = PRINTX;
1266 			break;
1267 		case LS:
1268 			np->first.fp = state->output;
1269 			if (state->sortkey == IGNORE)
1270 				state->sortkey = NAME;
1271 			break;
1272 		case FLS:
1273 			if (state->sortkey == IGNORE)
1274 				state->sortkey = NAME;
1275 			np->action = LS;
1276 			break;
1277 		case NEWER:
1278 		case ANEWER:
1279 		case CNEWER:
1280 			{
1281 				struct stat	st;
1282 
1283 				if (stat(b, &st))
1284 				{
1285 					error(2, "%s: not found", b);
1286 					return -1;
1287 				}
1288 				np->first.l = st.st_mtime;
1289 				np->second.i = '+';
1290 			}
1291 			break;
1292 		case CHOP:
1293 			state->walkflags |= FTS_NOSEEDOTDIR;
1294 			goto ignore;
1295 		case DAYSTART:
1296 			{
1297 				Tm_t*	tm;
1298 				time_t	t;
1299 
1300 				t = state->now;
1301 				tm = tmmake(&t);
1302 				tm->tm_hour = 0;
1303 				tm->tm_min = 0;
1304 				tm->tm_sec = 0;
1305 				state->day = tmtime(tm, TM_LOCALZONE);
1306 			}
1307 			goto ignore;
1308 		case MINDEPTH:
1309 			state->minlevel = np->first.l;
1310 			goto ignore;
1311 		case MAXDEPTH:
1312 			state->maxlevel = np->first.l;
1313 			goto ignore;
1314 		case TEST:
1315 			state->day = np->first.u;
1316 			goto ignore;
1317 		}
1318 		oldnp = np;
1319 		oldnp->next = ++np;
1320 	}
1321 	if (oldop != PRINT)
1322 	{
1323 		error(2, "%s: invalid argument", argv[opt_info.index - 1]);
1324 		return -1;
1325 	}
1326 	if (error_info.errors)
1327 		error(ERROR_USAGE|4, "%s", optusage(NiL));
1328 	state->nextnode = np;
1329 	if (state->lastnode = oldnp)
1330 		oldnp->next = 0;
1331 	return opt_info.index - index;
1332 }
1333 
1334 /*
1335  * This is the function that gets executed at each node
1336  */
1337 
1338 static int
execute(State_t * state,FTSENT * ent)1339 execute(State_t* state, FTSENT* ent)
1340 {
1341 	register Node_t*	np = state->topnode;
1342 	register int		val = 0;
1343 	register unsigned long	u;
1344 	unsigned long		m;
1345 	int			not = 0;
1346 	char*			bp;
1347 	Sfio_t*			fp;
1348 	Node_t*			tp;
1349 	struct stat		st;
1350 	DIR*			dir;
1351 	struct dirent*		dnt;
1352 
1353 	if (ent->fts_level > state->maxlevel)
1354 	{
1355 		fts_set(NiL, ent, FTS_SKIP);
1356 		return 0;
1357 	}
1358 	switch (ent->fts_info)
1359 	{
1360 	case FTS_DP:
1361 		if ((state->walkflags & FTS_NOCHDIR) && stat(PATH(ent), ent->fts_statp))
1362 			return 0;
1363 		break;
1364 	case FTS_NS:
1365 		if (!state->silent)
1366 			error(2, "%s: not found", ent->fts_path);
1367 		return 0;
1368 	case FTS_DC:
1369 		if (!state->silent)
1370 			error(2, "%s: directory causes cycle", ent->fts_path);
1371 		return 0;
1372 	case FTS_DNR:
1373 		if (!state->silent)
1374 			error(2, "%s: cannot read directory", ent->fts_path);
1375 		break;
1376 	case FTS_DNX:
1377 		if (!state->silent)
1378 			error(2, "%s: cannot search directory", ent->fts_path);
1379 		fts_set(NiL, ent, FTS_SKIP);
1380 		break;
1381 	case FTS_D:
1382 		if (!(state->walkflags & FTS_NOPOSTORDER))
1383 			return 0;
1384 		ent->ignorecase = (state->icase || (!ent->fts_level || !ent->fts_parent->ignorecase) && strchr(astconf("PATH_ATTRIBUTES", ent->fts_name, NiL), 'c')) ? STR_ICASE : 0;
1385 		break;
1386 	default:
1387 		ent->ignorecase = ent->fts_level ? ent->fts_parent->ignorecase : (state->icase || strchr(astconf("PATH_ATTRIBUTES", ent->fts_name, NiL), 'c')) ? STR_ICASE : 0;
1388 		break;
1389 	}
1390 	if (ent->fts_level < state->minlevel)
1391 		return 0;
1392 	while (np)
1393 	{
1394 		switch (np->action)
1395 		{
1396 		case NOT:
1397 			not = !not;
1398 			np = np->next;
1399 			continue;
1400 		case COMMA:
1401 		case LPAREN:
1402 		case OR:
1403 			tp = state->topnode;
1404 			state->topnode = np->first.np;
1405 			if ((val = execute(state, ent)) < 0)
1406 				return val;
1407 			state->topnode = tp;
1408 			switch (np->action)
1409 			{
1410 			case COMMA:
1411 				val = 1;
1412 				break;
1413 			case OR:
1414 				if (val)
1415 					return 1;
1416 				val = 1;
1417 				break;
1418 			}
1419 			break;
1420 		case LOCAL:
1421 			u = fts_local(ent);
1422 			goto num;
1423 		case XTYPE:
1424 			val = ((state->walkflags & FTS_PHYSICAL) ? stat(PATH(ent), &st) : lstat(PATH(ent), &st)) ? 0 : st.st_mode;
1425 			goto type;
1426 		case TYPE:
1427 			val = ent->fts_statp->st_mode;
1428 		type:
1429 			switch (np->first.l)
1430 			{
1431 			case 'b':
1432 				val = S_ISBLK(val);
1433 				break;
1434 			case 'c':
1435 				val = S_ISCHR(val);
1436 				break;
1437 			case 'd':
1438 				val = S_ISDIR(val);
1439 				break;
1440 			case 'f':
1441 				val = S_ISREG(val);
1442 				break;
1443 			case 'l':
1444 				val = S_ISLNK(val);
1445 				break;
1446 			case 'p':
1447 				val = S_ISFIFO(val);
1448 				break;
1449 #ifdef S_ISSOCK
1450 			case 's':
1451 				val = S_ISSOCK(val);
1452 				break;
1453 #endif
1454 #ifdef S_ISCTG
1455 			case 'C':
1456 				val = S_ISCTG(val);
1457 				break;
1458 #endif
1459 #ifdef S_ISDOOR
1460 			case 'D':
1461 				val = S_ISDOOR(val);
1462 				break;
1463 #endif
1464 			default:
1465 				val = 0;
1466 				break;
1467 			}
1468 			break;
1469 		case PERM:
1470 			u = modex(ent->fts_statp->st_mode) & 07777;
1471 			switch (np->second.i)
1472 			{
1473 			case '-':
1474 				val = (u & np->first.u) == np->first.u;
1475 				break;
1476 			case '+':
1477 				val = (u & np->first.u) != 0;
1478 				break;
1479 			default:
1480 				val = u == np->first.u;
1481 				break;
1482 			}
1483 			break;
1484 		case INUM:
1485 			u = ent->fts_statp->st_ino;
1486 			goto num;
1487 		case ATIME:
1488 			u = ent->fts_statp->st_atime;
1489 			goto tim;
1490 		case CTIME:
1491 			u = ent->fts_statp->st_ctime;
1492 			goto tim;
1493 		case MTIME:
1494 			u = ent->fts_statp->st_mtime;
1495 		tim:
1496 			val = u >= np->first.u && u <= np->second.u;
1497 			break;
1498 		case NEWER:
1499 			val = (unsigned long)ent->fts_statp->st_mtime > (unsigned long)np->first.u;
1500 			break;
1501 		case ANEWER:
1502 			val = (unsigned long)ent->fts_statp->st_atime > (unsigned long)np->first.u;
1503 			break;
1504 		case CNEWER:
1505 			val = (unsigned long)ent->fts_statp->st_ctime > (unsigned long)np->first.u;
1506 			break;
1507 		case SIZE:
1508 			u = ent->fts_statp->st_size;
1509 			goto num;
1510 		case USER:
1511 			u = ent->fts_statp->st_uid;
1512 			goto num;
1513 		case NOUSER:
1514 			val = *fmtuid(ent->fts_statp->st_uid);
1515 			val = isdigit(val);
1516 			break;
1517 		case GROUP:
1518 			u = ent->fts_statp->st_gid;
1519 			goto num;
1520 		case NOGROUP:
1521 			val = *fmtgid(ent->fts_statp->st_gid);
1522 			val = isdigit(val);
1523 			break;
1524 		case LINKS:
1525 			u = ent->fts_statp->st_nlink;
1526 		num:
1527 			if (m = np->third.u)
1528 				u = (u + m - 1) / m;
1529 			switch (np->second.i)
1530 			{
1531 			case '+':
1532 				val = (u > np->first.u);
1533 				break;
1534 			case '-':
1535 				val = (u < np->first.u);
1536 				break;
1537 			default:
1538 				val = (u == np->first.u);
1539 				break;
1540 			}
1541 			break;
1542 		case EXEC:
1543 		case OK:
1544 		case XARGS:
1545 			val = !cmdarg(np->first.xp, ent->fts_path, ent->fts_pathlen);
1546 			break;
1547 		case NAME:
1548 		case INAME:
1549 			if (bp = ent->fts_level ? (char*)0 : strchr(ent->fts_name, '/'))
1550 				*bp = 0;
1551 			val = strgrpmatch(ent->fts_name, np->first.cp, NiL, 0, STR_MAXIMAL|STR_LEFT|STR_RIGHT|(np->action == INAME ? STR_ICASE : ent->ignorecase)) != 0;
1552 			if (bp)
1553 				*bp = '/';
1554 			break;
1555 		case LNAME:
1556 			val = S_ISLNK(ent->fts_statp->st_mode) && pathgetlink(PATH(ent), state->txt, sizeof(state->txt)) > 0 && strgrpmatch(state->txt, np->first.cp, NiL, 0, STR_MAXIMAL|STR_LEFT|STR_RIGHT|ent->ignorecase);
1557 			break;
1558 		case ILNAME:
1559 			val = S_ISLNK(ent->fts_statp->st_mode) && pathgetlink(PATH(ent), state->txt, sizeof(state->txt)) > 0 && strgrpmatch(state->txt, np->first.cp, NiL, 0, STR_MAXIMAL|STR_LEFT|STR_RIGHT|STR_ICASE);
1560 			break;
1561 		case PATH:
1562 			val = strgrpmatch(ent->fts_path, np->first.cp, NiL, 0, STR_MAXIMAL|STR_LEFT|STR_RIGHT|ent->ignorecase) != 0;
1563 			break;
1564 		case IPATH:
1565 			val = strgrpmatch(ent->fts_path, np->first.cp, NiL, 0, STR_MAXIMAL|STR_LEFT|STR_RIGHT|STR_ICASE) != 0;
1566 			break;
1567 		case MAGIC:
1568 			fp = sfopen(NiL, PATH(ent), "r");
1569 			val = strmatch(magictype(state->magic, fp, PATH(ent), ent->fts_statp), np->first.cp) != 0;
1570 			if (fp)
1571 				sfclose(fp);
1572 			break;
1573 		case MIME:
1574 			fp = sfopen(NiL, PATH(ent), "r");
1575 			state->magicdisc.flags |= MAGIC_MIME;
1576 			val = strmatch(magictype(state->magic, fp, PATH(ent), ent->fts_statp), np->first.cp) != 0;
1577 			state->magicdisc.flags &= ~MAGIC_MIME;
1578 			if (fp)
1579 				sfclose(fp);
1580 			break;
1581 		case REGEX:
1582 			if (!(val = regnexec(np->second.re, ent->fts_path, ent->fts_pathlen, NiL, 0, 0)))
1583 				val = 1;
1584 			else if (val == REG_NOMATCH)
1585 				val = 0;
1586 			else
1587 			{
1588 				regfatal(np->second.re, 4, val);
1589 				return -1;
1590 			}
1591 			break;
1592 		case PRINT:
1593 			sfputr(np->first.fp, ent->fts_path, np->second.i);
1594 			val = 1;
1595 			break;
1596 		case PRINTF:
1597 			state->fmt.fmt.version = SFIO_VERSION;
1598 			state->fmt.fmt.extf = print;
1599 			state->fmt.fmt.form = np->second.cp;
1600 			state->fmt.ent = ent;
1601 			sfprintf(np->first.fp, "%!", &state->fmt);
1602 			val = 1;
1603 			break;
1604 		case PRINTX:
1605 			quotex(np->first.fp, ent->fts_path, np->second.i);
1606 			val = 1;
1607 			break;
1608 		case PRUNE:
1609 			fts_set(NiL, ent, FTS_SKIP);
1610 			val = 1;
1611 			break;
1612 		case FSTYPE:
1613 			val = strcmp(fmtfs(ent->fts_statp), np->first.cp) == 0;
1614 			break;
1615 		case LS:
1616 			fmtls(state->buf, ent->fts_path, ent->fts_statp, NiL, S_ISLNK(ent->fts_statp->st_mode) && pathgetlink(PATH(ent), state->txt, sizeof(state->txt)) > 0 ? state->txt : NiL, LS_LONG|LS_INUMBER|LS_BLOCKS);
1617 			sfputr(np->first.fp, state->buf, '\n');
1618 			val = 1;
1619 			break;
1620 		case EMPTY:
1621 			if (S_ISREG(ent->fts_statp->st_mode))
1622 				val = !ent->fts_statp->st_size;
1623 			else if (!S_ISDIR(ent->fts_statp->st_mode))
1624 				val = 0;
1625 			else if (!ent->fts_statp->st_size)
1626 				val = 1;
1627 			else if (!(dir = opendir(ent->fts_path)))
1628 			{
1629 				if (!state->silent)
1630 					error(2, "%s: cannot read directory", ent->fts_path);
1631 				val = 0;
1632 			}
1633 			else
1634 			{
1635 				while ((dnt = readdir(dir)) && (dnt->d_name[0] == '.' && (!dnt->d_name[1] || dnt->d_name[1] == '.' && !dnt->d_name[2])));
1636 				val = !dnt;
1637 				closedir(dir);
1638 			}
1639 			break;
1640 		case CFALSE:
1641 			val = 0;
1642 			break;
1643 		case CTRUE:
1644 			val = 1;
1645 			break;
1646 		case LEVEL:
1647 			u = ent->fts_level;
1648 			goto num;
1649 		default:
1650 			error(2, "internal error: %s: action not implemented", np->name);
1651 			return -1;
1652 		}
1653 		if (!(val ^= not))
1654 			break;
1655 		not = 0;
1656 		np = np->next;
1657 	}
1658 	return val;
1659 }
1660 
1661 /*
1662  * order child entries
1663  */
1664 
1665 static int
order(FTSENT * const * p1,FTSENT * const * p2)1666 order(FTSENT* const* p1, FTSENT* const* p2)
1667 {
1668 	register const FTSENT*	f1 = *p1;
1669 	register const FTSENT*	f2 = *p2;
1670 	register State_t*	state = f1->fts->fts_handle;
1671 	register long		n1;
1672 	register long		n2;
1673 	int			n;
1674 
1675 	switch (state->sortkey)
1676 	{
1677 	case ATIME:
1678 		n2 = f1->fts_statp->st_atime;
1679 		n1 = f2->fts_statp->st_atime;
1680 		break;
1681 	case CTIME:
1682 		n2 = f1->fts_statp->st_ctime;
1683 		n1 = f2->fts_statp->st_ctime;
1684 		break;
1685 	case MTIME:
1686 		n2 = f1->fts_statp->st_mtime;
1687 		n1 = f2->fts_statp->st_mtime;
1688 		break;
1689 	case SIZE:
1690 		n2 = f1->fts_statp->st_size;
1691 		n1 = f2->fts_statp->st_size;
1692 		break;
1693 	default:
1694 		error(1, "invalid sort key -- name assumed");
1695 		state->sortkey = NAME;
1696 		/*FALLTHROUGH*/
1697 	case NAME:
1698 		n = state->icase ? strcasecmp(f1->fts_name, f2->fts_name) : strcoll(f1->fts_name, f2->fts_name);
1699 		goto done;
1700 	}
1701 	if (n1 < n2)
1702 		n = -1;
1703 	else if (n1 > n2)
1704 		n = 1;
1705 	else
1706 		n = 0;
1707  done:
1708 	if (state->reverse)
1709 		n = -n;
1710 	return n;
1711 }
1712 
1713 static int
find(State_t * state,char ** paths,int flags,Sort_f sort)1714 find(State_t* state, char** paths, int flags, Sort_f sort)
1715 {
1716 	FTS*	fts;
1717 	FTSENT*	ent;
1718 	int	r;
1719 
1720 	r = 0;
1721 	if (fts = fts_open(paths, flags, sort))
1722 	{
1723 		fts->fts_handle = state;
1724 		while (ent = fts_read(fts))
1725 			if (execute(state, ent) < 0)
1726 			{
1727 				r = 1;
1728 				break;
1729 			}
1730 		fts_close(fts);
1731 	}
1732 	return r;
1733 }
1734 
1735 int
main(int argc,char ** argv)1736 main(int argc, char** argv)
1737 {
1738 	register char*			cp;
1739 	register char**			op;
1740 	register Find_t*		fp;
1741 	register const Args_t*		ap;
1742 	int				r;
1743 	Sort_f				sort;
1744 	Finddisc_t			disc;
1745 	State_t				state;
1746 
1747 	static const char* const	defpath[] = { ".", 0 };
1748 
1749 	setlocale(LC_ALL, "");
1750 	error_info.id = "find";
1751 	memset(&state, 0, sizeof(state));
1752 	if (!(state.vm = vmopen(Vmdcheap, Vmbest, 0)) || !(state.str = sfstropen()) || !(state.tmp = sfstropen()))
1753 	{
1754 		error(ERROR_SYSTEM|2, "out of space");
1755 		goto done;
1756 	}
1757 	state.maxlevel = ~0;
1758 	state.walkflags = FTS_PHYSICAL|FTS_NOSTAT|FTS_NOPOSTORDER|FTS_SEEDOTDIR;
1759 	state.sortkey = IGNORE;
1760 	sort = 0;
1761 	fp = 0;
1762 	sfputr(state.str, usage1, -1);
1763 	for (ap = commands; ap->name; ap++)
1764 	{
1765 		sfprintf(state.str, "[%d:%s?%s]", ap - commands + 10, ap->name, ap->help);
1766 		if (ap->arg)
1767 			sfprintf(state.str, "%c[%s]", (ap->type & Num) ? '#' : ':', ap->arg);
1768 		if (ap->values)
1769 			sfprintf(state.str, "{%s}", ap->values);
1770 		sfputc(state.str, '\n');
1771 	}
1772 	sfputr(state.str, usage2, -1);
1773 	if (!(state.usage = sfstruse(state.str)))
1774 	{
1775 		error(ERROR_SYSTEM|2, "out of space");
1776 		goto done;
1777 	}
1778 	state.day = state.now = (unsigned long)time(NiL);
1779 	state.output = sfstdout;
1780 	if (!(state.topnode = vmnewof(state.vm, 0, Node_t, argc + 3, 0)))
1781 	{
1782 		error(2, "not enough space for expressions");
1783 		goto done;
1784 	}
1785 	if (compile(&state, argv, state.topnode, 0) < 0)
1786 		goto done;
1787 	op = argv + opt_info.index;
1788 	while (cp = argv[opt_info.index])
1789 	{
1790 		if (*cp == '-' || (*cp == '!' || *cp == '(' || *cp == ')' || *cp == ',') && *(cp + 1) == 0)
1791 		{
1792 			r = opt_info.index;
1793 			if (compile(&state, argv, state.topnode, 0) < 0)
1794 				goto done;
1795 			argv[r] = 0;
1796 			if (cp = argv[opt_info.index])
1797 			{
1798 				error(2, "%s: invalid argument", cp);
1799 				goto done;
1800 			}
1801 			break;
1802 		}
1803 		opt_info.index++;
1804 	}
1805 	if (!*op)
1806 		op = (char**)defpath;
1807 	while (state.topnode && state.topnode->action == IGNORE)
1808 		state.topnode = state.topnode->next;
1809 	if (!(state.walkflags & FTS_PHYSICAL))
1810 		state.walkflags &= ~FTS_NOSTAT;
1811 	if (state.fast)
1812 	{
1813 		if (state.sortkey != IGNORE)
1814 			error(1, "-sort ignored for -fast");
1815 		memset(&disc, 0, sizeof(disc));
1816 		disc.version = FIND_VERSION;
1817 		disc.flags = state.icase ? FIND_ICASE : 0;
1818 		disc.errorf = errorf;
1819 		disc.dirs = op;
1820 		state.walkflags |= FTS_TOP;
1821 		if (fp = findopen(state.codes, state.fast, NiL, &disc))
1822 			while (cp = findread(fp))
1823 			{
1824 				if (!state.topnode)
1825 					sfputr(sfstdout, cp, '\n');
1826 				else if (find(&state, (char**)cp, FTS_ONEPATH|state.walkflags, NiL))
1827 					goto done;
1828 			}
1829 	}
1830 	else
1831 	{
1832 		if (!state.primary)
1833 		{
1834 			if (!state.topnode)
1835 				state.topnode = state.nextnode;
1836 			else if (state.topnode != state.nextnode)
1837 			{
1838 				state.nextnode->action = LPAREN;
1839 				state.nextnode->first.np = state.topnode;
1840 				state.nextnode->next = state.nextnode + 1;
1841 				state.topnode = state.nextnode++;
1842 			}
1843 			state.nextnode->action = PRINT;
1844 			state.nextnode->first.fp = state.output;
1845 			state.nextnode->second.i = '\n';
1846 			state.nextnode->next = 0;
1847 		}
1848 		fp = 0;
1849 		if (state.sortkey != IGNORE)
1850 			sort = order;
1851 		find(&state, op, state.walkflags, sort);
1852 	}
1853  done:
1854 	while (state.cmd)
1855 	{
1856 		cmdflush(state.cmd->first.xp);
1857 		cmdclose(state.cmd->first.xp);
1858 		state.cmd = state.cmd->second.np;
1859 	}
1860 	if (state.vm)
1861 		vmclose(state.vm);
1862 	if (state.str)
1863 		sfstrclose(state.str);
1864 	if (state.tmp)
1865 		sfstrclose(state.tmp);
1866 	if (fp && findclose(fp))
1867 		error(ERROR_SYSTEM|2, "fast find error");
1868 	if (state.proc && (r = procclose(state.proc)))
1869 		error(ERROR_SYSTEM|2, "subprocess exit code %d", r);
1870 	if (sfsync(sfstdout))
1871 		error(ERROR_SYSTEM|2, "write error");
1872 	return error_info.errors != 0;
1873 }
1874