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