Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/cmd/tw/find.c
1808 views
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 <[email protected]> *
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*
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
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
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*
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
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
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
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
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
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
1875