Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/cmd/tw/tw.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
* Glenn Fowler
23
* AT&T Research
24
*
25
* tw -- tree walk
26
*
27
* print [execute cmd on] path names in tree rooted at . [dir]
28
* or on path names listed on stdin [-]
29
*/
30
31
#define SNAPSHOT_ID "snapshot"
32
#define SNAPSHOT_PATH "%(url)s"
33
#define SNAPSHOT_EASY "%(ctime)..64u,%(perm)..64u,%(size)..64u,%(uid)..64u,%(gid)..64u"
34
#define SNAPSHOT_HARD "%(md5sum)s"
35
#define SNAPSHOT_DELIM "|"
36
37
static const char usage[] =
38
"[-?\n@(#)$Id: tw (AT&T Research) 2012-04-11 $\n]"
39
USAGE_LICENSE
40
"[+NAME?tw - file tree walk]"
41
"[+DESCRIPTION?\btw\b recursively descends the file tree rooted at the "
42
"current directory and lists the pathname of each file found. If \acmd "
43
"arg ...\a is specified then the pathnames are collected and appended to "
44
"the end of the \aarg\alist and \acmd\a is executed by the equivalent of "
45
"\bexecvp\b(2). \acmd\a will be executed 0 or more times, depending the "
46
"number of generated pathname arguments.]"
47
"[+?If the last option is \b-\b and \b--fast\b was not specified then "
48
"the pathnames are read, one per line, from the standard input, the "
49
"\b--directory\b options are ignored, and the directory tree is not "
50
"traversed.]"
51
"[+?\bgetconf PATH_RESOLVE\b determines how symbolic links are handled. "
52
"This can be explicitly overridden by the \b--logical\b, "
53
"\b--metaphysical\b, and \b--physical\b options below. \bPATH_RESOLVE\b "
54
"can be one of:]"
55
"{"
56
"[+logical?Follow all symbolic links.]"
57
"[+metaphysical?Follow command argument symbolic links, "
58
"otherwise don't follow.]"
59
"[+physical?Don't follow symbolic links.]"
60
"}"
61
"[a:arg-list?The first \aarg\a named \astring\a is replaced by the "
62
"current pathname list before \acmd\a is executed.]:[string]"
63
"[c:args|arg-count?\acmd\a is executed after \acount\a arguments are "
64
"collected.]#[count]"
65
"[d:directory?The file tree traversal is rooted at \adir\a. Multiple "
66
"\b--directory\b directories are traversed in order from left to right. "
67
"If the last option was \b-\b then all \b--directory\b are "
68
"ignored.]:[dir]"
69
"[e:expr?\aexpr\a defines expression functions that control tree "
70
"traversal. Multiple \b--expr\b expressions are parsed in order from "
71
"left to right. See EXPRESSIONS below for details.]:[expr]"
72
"[f:fast?Searches the \bfind\b(1) or \blocate\b(1) database for paths "
73
"matching the \bksh\b(1) \apattern\a. See \bupdatedb\b(1) for details on "
74
"this database. Any \b--expr\b expressions are applied to the matching "
75
"paths.]:[pattern]"
76
"[i:ignore-errors?Ignore inaccessible files and directories.]"
77
"[I:ignore-case?Ignore case in pathname comparisons.]"
78
"[l:local?Do not descend into non-local filesystem directories.]"
79
"[m:intermediate?Before visiting a selected file select and visit "
80
"intermediate directories leading to the file that have not already been "
81
"selected.]"
82
"[n:notraverse?Evaluate the \bbegin\b, \bselect\b and \bend\b "
83
"expressions but eliminate the tree traversal.]"
84
"[p:post?Visit each directory after its files have been processed. By "
85
"default directories are visited pre-order.]"
86
"[q:query?Emit an interactive query for each visited path. An "
87
"affirmative response accepts the path, a negative response rejects the "
88
"path, and a quit response exits \atw\a.]"
89
"[r:recursive?Visit directories listed on the standard input.]"
90
"[S:separator?The input file list separator is set to the first "
91
"character of \astring\a.]:[string]"
92
"[s:size|max-chars?Use at most \achars\a characters per command. The "
93
"default is as large as possible.]#[chars]"
94
"[t:trace|verbose?Print the command line on the standard error before "
95
"executing it.]"
96
"[x:error-exit?Exit \btw\b with the exit code of the first \acmd\a that "
97
"returns an exit code greater than or equal to \acode\a. By default "
98
"\acmd\a exit codes are ignored (mostly because of \bgrep\b(1).)]#[code]"
99
"[z:snapshot?Write a snapshot of the selected files to the standard "
100
"output. For the first snapshot the standard input must either be empty "
101
"or a single line containing delimiter separated output format fields, "
102
"with the delimiter appearing as both the first and last character. The "
103
"format is in the same style as \bls\b(1) and \bps\b(1) formats: "
104
"%(\aidentifier\a)\aprintf-format\a, where \aidentifier\a is one of the "
105
"file status identifiers described below, and \aprintf-format\a is a "
106
"\bprintf\b(3) format specification. A default delimiter (\""
107
SNAPSHOT_DELIM "\") and field values are assumed for an empty "
108
"snapshot input file. The format fields are:]"
109
"{"
110
"[+" SNAPSHOT_ID "?The literal string \b" SNAPSHOT_ID "\b.]"
111
"[+path-format?The current path format, default \"" SNAPSHOT_PATH "\".]"
112
"[+easy-format?The \aeasy\a part of the snapshot file state, default \""
113
SNAPSHOT_EASY "\". This part is recomputed for each file.]"
114
"[+hard-format?The \ahard\a part of the snapshot file state, default \""
115
SNAPSHOT_HARD "\". This part is computed only if the easy part "
116
"has changed.]"
117
"[+change-status?This field, with both a leading and trailing delimiter, "
118
"is ignored on input and set to one of the following values for "
119
"files that have changed since the last snapshot:]"
120
"{"
121
"[+C?The file changed.]"
122
"[+D?The file was deleted.]"
123
"[+N?The file is new.]"
124
"}"
125
"}"
126
"[C:chop?Chop leading \b./\b from printed pathnames. This is implied by "
127
"\b--logical\b.]"
128
"[E:file?Compile \b--expr\b expressions from \afile\a. Multiple "
129
"\b--file\b options may be specified. See EXPRESSIONS below for "
130
"details.]:[file]"
131
"[F:codes?Set the \blocate\b(1) fast find codes database "
132
"\apath\a.]:[path]"
133
"[G:generate?Generate a \aformat\a \blocate\b(1) database of the visited "
134
"files and directories. Exit status 1 means some files were not "
135
"accessible but the database was properly generated; exit status 2 means "
136
"that database was not generated. Format may be:]:[format]"
137
"{"
138
"[+dir?machine independent with directory trailing /.]"
139
"[+old?old fast find]"
140
"[+gnu?gnu \blocate\b(1)]"
141
"[+type?machine independent with directory and mime types]"
142
"}"
143
"[L:logical|follow?Follow symbolic links. The default is determined by "
144
"\bgetconf PATH_RESOLVE\b.]"
145
"[H:metaphysical?Follow command argument symbolic links, otherwise don't "
146
"follow. The default is determined by \bgetconf PATH_RESOLVE\b.]"
147
"[P:physical?Don't follow symbolic links. The default is determined by "
148
"\bgetconf PATH_RESOLVE\b.]"
149
"[X:xdev|mount?Do not descend into directories in different filesystems "
150
"than their parents.]"
151
"[D:debug?Set the debug trace \alevel\a; higher levels produce more "
152
"output.]# [level]"
153
"\n"
154
"\n[ cmd [ arg ... ] ]\n"
155
"\n"
156
"[+EXPRESSIONS?Expressions are C style and operate on elements of the "
157
"\bstat\b(2) struct with the leading \bst_\b omitted. A function "
158
"expression is defined by one of:]"
159
"{"
160
"[+?function-name : statement-list]"
161
"[+?type function-name() { statement-list }]"
162
"}"
163
"[+?where \afunction-name\a is one of:]"
164
"{"
165
"[+begin?Evaluated before the traversal starts. The return value "
166
"is ignored. The default is a no-op.]"
167
"[+select?Evaluated as each file is visited. A 0 return value "
168
"skips \baction\b for the file; otherwise \baction\b is "
169
"evaluated. All files are selected by default. \bselect\b is "
170
"assumed when \afunction-name\a: is omitted.]"
171
"[+action?Evaluated for each select file. The return value is "
172
"ignored. The default \baction\b list the file path name, with "
173
"leading \b./\b stripped, one per line on the standard output.]"
174
"[+end?Evaluated after the traversal completes. The return value "
175
"is ignored.]"
176
"[+sort?A pseudo-function: the statement list is a , separated "
177
"list of identifiers used to sort the entries of each directory. "
178
"If any identifier is preceded by \b!\b then the sort order is "
179
"reversed. If any identifier is preceded by \b~\b then case is "
180
"ignored.]"
181
"}"
182
"[+?\astatement-list\a is a C style \bexpr\b(3) expression that "
183
"supports: \bint\b \avar\a, ...; and \bfloat\b \avar\a, ...; "
184
"declarations, \b(int)\b and \b(float)\b casts, \bif\b-\belse\b "
185
"conditionals, \bfor\b and \bwhile\b loops, and \b{...}\b blocks. The "
186
"trailing \b;\b in any expression list is optional. The expression value "
187
"is the value of the last evaluated expression in \astatement-list\a. "
188
"Numbers and comments follow C syntax. String operands must be quoted "
189
"with either \b\"...\"\b or \b'...'\b. String comparisons \b==\b and "
190
"\b!=\b treat the right hand operand as a \bksh\b(1) file match "
191
"pattern.]"
192
"[+?The expressions operate on the current pathname file status that is "
193
"provided by the following field identifiers, most of which are "
194
"described under \bst_\b\afield\a in \bstat\b(2). In general, if a "
195
"status identifier appears on the left hand side of a binary operator "
196
"then the right hand side may be a string that is converted to an "
197
"integral constant according to the identifier semantics.]"
198
"{"
199
"[+atime?access time; time/date strings are interpreted as "
200
"\bdate\b(1) expressions]"
201
"[+blocks?number of 1k blocks]"
202
"[+checksum?equivalent to \bsum(\"tw\")]"
203
"[+ctime?status change time]"
204
"[+dev?file system device]"
205
"[+fstype?file system type name; \bufs\b if it can't be "
206
"determined]"
207
"[+gid?owner group id; \agid\a strings are interpreted as group "
208
"names]"
209
"[+gidok?1 if \agid\a is a valid group id in the system "
210
"database, 0 otherwise.]"
211
"[+ino?inode/serial number]"
212
"[+level?the depth of the file relative to the traversal root]"
213
"[+local?an integer valued field associated with each active "
214
"object in the traversal; This field may be assigned. The "
215
"initial value is 0. Multiple \alocal\a elements may be declared "
216
"by \bint local.\b\aelement1\a...;. In this case the \blocal\b "
217
"field itself is not accessible.]"
218
"[+md5sum?equivalent to \bsum(\"md5\")]"
219
"[+mime?the file contents \afile\a(1) \b--mime\b type]"
220
"[+mode?type and permission bits; the \bFMT\b constant may be "
221
"used to mask mask the file type and permission bits; \bmode\b "
222
"strings are interpreted as \bchmod\b(1) expressions]"
223
"[+mtime?modify time]"
224
"[+name?file name with directory prefix stripped]"
225
"[+nlink?hard link count]"
226
"[+path?full path name relative to the current active "
227
"\b--directory\b]"
228
"[+perm?the permission bits of \bmode\b]"
229
"[+rdev?the major.minor device number if the file is a device]"
230
"[+size?size in bytes]"
231
"[+status?the \bfts\b(3) \bFTS_\b* or \bftwalk\b(3) \bFTW_\b* "
232
"status. This field may be assigned:]"
233
"{"
234
"[+AGAIN?visit the file again]"
235
"[+FOLLOW?if the file is a symbolic link then follow it]"
236
"[+NOPOST?cancel any post order visit to this file]"
237
"[+SKIP?do not consider this file or any subdirectories "
238
"if it is a directory]"
239
"}"
240
"[+sum(\"\amethod\a\")?file contents checksum using \amethod\a; "
241
"see \bsum\b(1) \b--method\b for details.]"
242
"[+symlink?the symbolic link text if the file is a symbolic "
243
"link]"
244
"[+type?the type bits of \bmode\b:]"
245
"{"
246
"[+BLK?block special]"
247
"[+CHR?block special]"
248
"[+DIR?directory]"
249
"[+DOOR?door]"
250
"[+FIFO?fifo]"
251
"[+LNK?symbolic link]"
252
"[+REG?regular]"
253
"[+SOCK?unix domain socket]"
254
"}"
255
"[+uid?owner user id; \auid\a strings are interpreted as user "
256
"names]"
257
"[+uidok?1 if \auid\a is a valid user id in the system database, "
258
"0 otherwise.]"
259
"[+url?unprintable chars n path converted to %XX hex]"
260
"[+visit?an integer variable associated with each unique object "
261
"visited; Objects are identified using the \bdev\b and \bino\b "
262
"status identifiers. This field may be assigned. The initial "
263
"value is 0. Multiple \bvisit\b elements may be declared by "
264
"\bint visit.\b \aelement\a...;. In this case the \bvisit\b "
265
"field itself is not accessible.]"
266
"}"
267
"[+?Status identifiers may be prefixed by 1 or more \bparent.\b "
268
"references, to access ancestor directory information. The parent status "
269
"information of a top level object is the same as the object except that "
270
"\bname\b and \bpath\b are undefined. If a status identifier is "
271
"immediately preceded by \b\"string\"\b. then string is a file pathname "
272
"from which the status is taken.]"
273
"[+?The following \bexpr\b(3) functions are supported:]"
274
"{"
275
"[+exit(expr)?causes \atw\a to exit with the exit code \aexpr\a "
276
"which defaults to 0 if omitted]"
277
"[+printf(format[,arg...]])?print the arguments on the standard "
278
"output using the \bprintf\b(3) specification \aformat\a.]"
279
"[+eprintf(format[,arg...]])?print the arguments on the standard "
280
"error using the \bprintf\b(3) specification \aformat\a.]"
281
"[+query(format[,arg...]])?prompt with the \bprintf\b(3) message "
282
"on the standard error an read an interactive response. An "
283
"affirmative response returns 1, \bq\b or \bEOF\b causes \atw\a "
284
"to to exit immediately, and any other input returns 0.]"
285
"}"
286
"[+EXAMPLES]"
287
"{"
288
"[+tw?Lists the current directory tree.]"
289
"[+tw chmod go-w?Turns off the group and other write permissions "
290
"for all files in the current directory tree using a minimal "
291
"amount of \bchmod\b(1) command execs.]"
292
"[+tw -e \"uid != 'bozo' || (mode & 'go=w')\"?Lists all files in "
293
"the current directory that don't belong to the user \bbozo\b or "
294
"have group or other write permission.]"
295
"[+tw -m -d / -e \"fstype == '/'.fstype && mtime > '/etc/backup.time'.mtime\"?"
296
"Lists all files and intermediate directories on the same file "
297
"system type as \b/\b that are newer than the file "
298
"\b/etc/backup.time\b.]"
299
"[+tw - chmod +x < commands?Executes \bchmod +x\b on the "
300
"pathnames listed in the file \bcommands\b.]"
301
"[+tw -e \"int count;?\baction: count++; printf('name=%s "
302
"inode=%08ld\\\\n', name, ino); end: printf('%d file%s\\\\n', "
303
"count, count==1 ? '' : 's');\"\b Lists the name and inode "
304
"number of each file and also the total number of files.]"
305
"[+tw -pP -e \"?\baction: if (visit++ == 0) { parent.local += "
306
"local + blocks; if (type == DIR) printf('%d\\\\t%s\\\\n', local "
307
"+ blocks, path); }\"\b Exercise for the reader.]"
308
"}"
309
"[+EXIT STATUS]"
310
"{"
311
"[+0?All invocations of \acmd\a returned exit status 0.]"
312
"[+1-125?A command line meeting the specified requirements could "
313
"not be assembled, one or more of the invocations of \acmd\a "
314
"returned non-0 exit status, or some other error occurred.]"
315
"[+126?\acmd\a was found but could not be executed.]"
316
"[+127?\acmd\a was not found.]"
317
"}"
318
"[+ENVIRONMENT]"
319
"{"
320
"[+FINDCODES?Path name of the \blocate\b(1) database.]"
321
"[+LOCATE_PATH?Alternate path name of \blocate\b(1) database.]"
322
"}"
323
"[+FILES]"
324
"{"
325
"[+lib/find/find.codes?Default \blocate\b(1) database.]"
326
"}"
327
"[+NOTES?In order to access the \bslocate\b(1) database the \btw\b "
328
"executable must be setgid to the \bslocate\b group.]"
329
"[+SEE ALSO?\bfind\b(1), \bgetconf\b(1), \blocate\b(1), \bslocate\b(1), "
330
"\bsum\b(1), \bupdatedb\b(1), \bxargs\b(1)]"
331
;
332
333
#include "tw.h"
334
335
#include <ctype.h>
336
#include <proc.h>
337
#include <wait.h>
338
339
#define ALL ((Exnode_t*)0)
340
#define LIST ((Exnode_t*)1)
341
342
#define FTW_LIST (FTW_USER<<0) /* files listed on stdin */
343
#define FTW_RECURSIVE (FTW_USER<<1) /* walk files listed on stdin */
344
345
typedef struct Dir /* directory list */
346
{
347
struct Dir* next; /* next in list */
348
char* name; /* dir name */
349
} Dir_t;
350
351
State_t state;
352
353
static void intermediate(Ftw_t*, char*);
354
355
static int
356
urlcmp(register const char* p, register const char* s, int d)
357
{
358
int pc;
359
int sc;
360
361
for (;;)
362
{
363
if ((pc = *p++) == d)
364
{
365
if (*s != d)
366
return -1;
367
break;
368
}
369
if ((sc = *s++) == d)
370
return 1;
371
if (pc == '%')
372
{
373
pc = (int)strntol(p, 2, NiL, 16);
374
p += 2;
375
}
376
if (sc == '%')
377
{
378
sc = (int)strntol(s, 2, NiL, 16);
379
s += 2;
380
}
381
if (pc < sc)
382
return -1;
383
if (pc > sc)
384
return 1;
385
}
386
return 0;
387
}
388
389
#define SNAPSHOT_changed 'C'
390
#define SNAPSHOT_deleted 'D'
391
#define SNAPSHOT_new 'N'
392
393
/*
394
* do the action
395
*/
396
397
static void
398
act(register Ftw_t* ftw, int op)
399
{
400
char* s;
401
Sfio_t* fp;
402
int i;
403
int j;
404
int k;
405
int n;
406
int r;
407
408
switch (op)
409
{
410
case ACT_CMDARG:
411
if ((i = cmdarg(state.cmd, ftw->path, ftw->pathlen)) >= state.errexit)
412
exit(i);
413
break;
414
case ACT_CODE:
415
if (findwrite(state.find, ftw->path, ftw->pathlen, (ftw->info & FTW_D) ? "system/dir" : (char*)0))
416
state.finderror = 1;
417
break;
418
case ACT_CODETYPE:
419
fp = sfopen(NiL, PATH(ftw), "r");
420
if (findwrite(state.find, ftw->path, ftw->pathlen, magictype(state.magic, fp, PATH(ftw), &ftw->statb)))
421
state.finderror = 1;
422
if (fp)
423
sfclose(fp);
424
break;
425
case ACT_EVAL:
426
eval(state.action, ftw);
427
break;
428
case ACT_INTERMEDIATE:
429
intermediate(ftw, ftw->path);
430
break;
431
case ACT_LIST:
432
sfputr(sfstdout, ftw->path, '\n');
433
break;
434
case ACT_SNAPSHOT:
435
print(state.snapshot.tmp, ftw, state.snapshot.format.path);
436
sfputc(state.snapshot.tmp, state.snapshot.format.delim);
437
i = sfstrtell(state.snapshot.tmp);
438
print(state.snapshot.tmp, ftw, state.snapshot.format.easy);
439
j = sfstrtell(state.snapshot.tmp);
440
s = sfstrbase(state.snapshot.tmp);
441
r = SNAPSHOT_new;
442
if (!state.snapshot.prev)
443
k = 1;
444
else
445
{
446
do
447
{
448
if (!(k = urlcmp(state.snapshot.prev, s, state.snapshot.format.delim)))
449
{
450
r = SNAPSHOT_changed;
451
if (!(k = memcmp(state.snapshot.prev + i, s + i, j - i) || state.snapshot.prev[j] != state.snapshot.format.delim))
452
{
453
if ((n = (int)sfvalue(sfstdin)) > 4 && state.snapshot.prev[n-2] == state.snapshot.format.delim)
454
{
455
sfwrite(sfstdout, state.snapshot.prev, n - 4);
456
sfputc(sfstdout, '\n');
457
}
458
else
459
sfwrite(sfstdout, state.snapshot.prev, n);
460
}
461
}
462
else if (k > 0)
463
break;
464
else if (k < 0 && (n = (int)sfvalue(sfstdin)) > 4 && (state.snapshot.prev[n-2] != state.snapshot.format.delim || state.snapshot.prev[n-3] != SNAPSHOT_deleted))
465
{
466
sfwrite(sfstdout, state.snapshot.prev, n - (state.snapshot.prev[n-2] == state.snapshot.format.delim ? 4 : 1));
467
sfputc(sfstdout, state.snapshot.format.delim);
468
sfputc(sfstdout, SNAPSHOT_deleted);
469
sfputc(sfstdout, state.snapshot.format.delim);
470
sfputc(sfstdout, '\n');
471
if (state.cmdflags & CMD_TRACE)
472
error(1, "%s deleted", ftw->path);
473
}
474
if (!(state.snapshot.prev = sfgetr(sfstdin, '\n', 0)))
475
break;
476
} while (k < 0);
477
}
478
if (k)
479
{
480
if (state.snapshot.format.hard && (ftw->info & FTW_F))
481
{
482
sfputc(state.snapshot.tmp, state.snapshot.format.delim);
483
print(state.snapshot.tmp, ftw, state.snapshot.format.hard);
484
}
485
sfputc(state.snapshot.tmp, state.snapshot.format.delim);
486
sfputc(state.snapshot.tmp, r);
487
sfputc(state.snapshot.tmp, state.snapshot.format.delim);
488
sfputr(sfstdout, sfstruse(state.snapshot.tmp), '\n');
489
if (state.cmdflags & CMD_TRACE)
490
error(1, "%s %s", ftw->path, r == SNAPSHOT_new ? "new" : "changed");
491
}
492
else
493
sfstrseek(state.snapshot.tmp, SEEK_SET, 0);
494
break;
495
}
496
}
497
498
/*
499
* generate intermediate (missing) directories for terminal elements
500
*/
501
502
static void
503
intermediate(register Ftw_t* ftw, register char* path)
504
{
505
register char* s;
506
register char* t;
507
register int c;
508
509
if (!(ftw->info & FTW_D) || ftw->statb.st_nlink)
510
{
511
ftw->statb.st_nlink = 0;
512
if (ftw->level > 1)
513
intermediate(ftw->parent, path);
514
s = path + ftw->pathlen;
515
c = *s;
516
*s = 0;
517
t = ftw->path;
518
ftw->path = path;
519
act(ftw, state.actII);
520
ftw->path = t;
521
*s = c;
522
}
523
}
524
525
/*
526
* tw a single file
527
*/
528
529
static int
530
tw(register Ftw_t* ftw)
531
{
532
Local_t* lp;
533
534
switch (ftw->info)
535
{
536
case FTW_NS:
537
if (!state.info)
538
{
539
if (!state.pattern && !state.ignore)
540
error(2, "%s: not found", ftw->path);
541
return 0;
542
}
543
break;
544
case FTW_DC:
545
if (!state.info)
546
{
547
if (!state.ignore)
548
error(2, "%s: directory causes cycle", ftw->path);
549
return 0;
550
}
551
break;
552
case FTW_DNR:
553
if (!state.info)
554
{
555
if (!state.ignore)
556
error(2, "%s: cannot read directory", ftw->path);
557
}
558
break;
559
case FTW_DNX:
560
if (!state.info)
561
{
562
if (!state.ignore)
563
error(2, "%s: cannot search directory", ftw->path);
564
ftw->status = FTW_SKIP;
565
}
566
break;
567
case FTW_DP:
568
if (!(state.ftwflags & FTW_TWICE) || (state.ftwflags & FTW_DOT) && stat(PATH(ftw), &ftw->statb))
569
goto pop;
570
break;
571
case FTW_D:
572
ftw->ignorecase = (state.icase || (!ftw->level || !ftw->parent->ignorecase) && strchr(astconf("PATH_ATTRIBUTES", ftw->name, NiL), 'c')) ? STR_ICASE : 0;
573
break;
574
default:
575
ftw->ignorecase = ftw->level ? ftw->parent->ignorecase : (state.icase || strchr(astconf("PATH_ATTRIBUTES", ftw->name, NiL), 'c')) ? STR_ICASE : 0;
576
break;
577
}
578
if (state.localfs && (ftw->info & FTW_D) && !ftwlocal(ftw))
579
ftw->status = FTW_SKIP;
580
else
581
{
582
if (state.select == ALL || eval(state.select, ftw) && ftw->status != FTW_SKIP)
583
act(ftw, state.act);
584
pop:
585
if (state.localmem && (lp = (Local_t*)ftw->local.pointer))
586
{
587
lp->next = state.local;
588
state.local = lp;
589
}
590
if ((state.ftwflags & (FTW_LIST|FTW_RECURSIVE)) == FTW_LIST)
591
ftw->status = FTW_SKIP;
592
}
593
return 0;
594
}
595
596
/*
597
* order child entries
598
*/
599
600
static int
601
order(register Ftw_t* f1, register Ftw_t* f2)
602
{
603
register Exnode_t* x;
604
register Exnode_t* y;
605
register int v;
606
long n1;
607
long n2;
608
int icase;
609
int reverse;
610
611
v = 0;
612
icase = state.icase;
613
reverse = state.reverse;
614
x = state.sortkey;
615
y = 0;
616
for (;;)
617
{
618
switch (x->op)
619
{
620
case '~':
621
icase = !icase;
622
x = x->data.operand.left;
623
continue;
624
case '!':
625
reverse = !reverse;
626
x = x->data.operand.left;
627
continue;
628
case ',':
629
y = x->data.operand.right;
630
x = x->data.operand.left;
631
continue;
632
case S2B:
633
case X2I:
634
x = x->data.operand.left;
635
continue;
636
case ID:
637
x->data.variable.symbol;
638
if (x->data.variable.symbol->index == F_name || x->data.variable.symbol->index == F_path)
639
v = icase ? strcasecmp(f1->name, f2->name) : strcoll(f1->name, f2->name);
640
else
641
{
642
n1 = getnum(x->data.variable.symbol, f1);
643
n2 = getnum(x->data.variable.symbol, f2);
644
if (n1 < n2)
645
v = -1;
646
else if (n1 > n2)
647
v = 1;
648
else
649
v = 0;
650
}
651
if (v)
652
{
653
if (reverse)
654
v = -v;
655
break;
656
}
657
if (!(x = y))
658
break;
659
y = 0;
660
icase = state.icase;
661
reverse = state.reverse;
662
continue;
663
}
664
break;
665
}
666
message((-2, "order(%s,%s) = %d [n1=%ld n2=%ld]", f1->name, f2->name, v, n1, n2));
667
return v;
668
}
669
670
int
671
main(int argc, register char** argv)
672
{
673
register int n;
674
register char* s;
675
char* args;
676
char* codes;
677
char** av;
678
char** ap;
679
int i;
680
int count;
681
int len;
682
int traverse;
683
int size;
684
Dir_t* firstdir;
685
Dir_t* lastdir;
686
Exnode_t* x;
687
Exnode_t* y;
688
Ftw_t ftw;
689
Finddisc_t disc;
690
691
setlocale(LC_ALL, "");
692
error_info.id = "tw";
693
av = argv + 1;
694
args = 0;
695
codes = 0;
696
count = 0;
697
size = 0;
698
traverse = 1;
699
firstdir = lastdir = newof(0, Dir_t, 1, 0);
700
firstdir->name = ".";
701
state.action = LIST;
702
state.cmdflags = CMD_EXIT|CMD_IGNORE|CMD_IMPLICIT|CMD_NEWLINE;
703
state.errexit = EXIT_QUIT;
704
state.ftwflags = ftwflags()|FTW_DELAY;
705
state.select = ALL;
706
state.separator = '\n';
707
memset(&disc, 0, sizeof(disc));
708
for (;;)
709
{
710
switch (optget(argv, usage))
711
{
712
case 'a':
713
args = opt_info.arg;
714
state.cmdflags |= CMD_POST;
715
continue;
716
case 'c':
717
if ((count = opt_info.num) < 0)
718
error(3, "argument count must be >= 0");
719
continue;
720
case 'd':
721
lastdir = lastdir->next = newof(0, Dir_t, 1, 0);
722
lastdir->name = opt_info.arg;
723
continue;
724
case 'e':
725
compile(opt_info.arg, 0);
726
continue;
727
case 'f':
728
state.pattern = opt_info.arg;
729
continue;
730
case 'i':
731
state.ignore = 1;
732
continue;
733
case 'l':
734
state.localfs = 1;
735
continue;
736
case 'm':
737
state.intermediate = 1;
738
continue;
739
case 'n':
740
traverse = 0;
741
continue;
742
case 'p':
743
state.ftwflags |= FTW_TWICE;
744
continue;
745
case 'q':
746
state.cmdflags |= CMD_QUERY;
747
continue;
748
case 'r':
749
state.ftwflags |= FTW_RECURSIVE;
750
continue;
751
case 's':
752
if ((size = opt_info.num) < 0)
753
error(3, "command size must be >= 0");
754
continue;
755
case 't':
756
state.cmdflags |= CMD_TRACE;
757
continue;
758
case 'x':
759
state.errexit = opt_info.arg ? opt_info.num : EXIT_QUIT;
760
continue;
761
case 'z':
762
if (s = sfgetr(sfstdin, '\n', 1))
763
{
764
if (!(s = strdup(s)))
765
error(ERROR_SYSTEM|3, "out of space");
766
n = state.snapshot.format.delim = *s++;
767
state.snapshot.format.path = s;
768
if (!(s = strchr(s, n)))
769
{
770
osnap:
771
error(3, "invalid snapshot on standard input");
772
}
773
*s++ = 0;
774
if (!streq(state.snapshot.format.path, SNAPSHOT_ID))
775
goto osnap;
776
state.snapshot.format.path = s;
777
if (!(s = strchr(s, n)))
778
goto osnap;
779
*s++ = 0;
780
state.snapshot.format.easy = s;
781
if (!(s = strchr(s, n)))
782
goto osnap;
783
*s++ = 0;
784
if (*(state.snapshot.format.hard = s))
785
{
786
if (!(s = strchr(s, n)))
787
goto osnap;
788
*s = 0;
789
}
790
else
791
state.snapshot.format.hard = 0;
792
state.snapshot.sp = sfstdin;
793
state.snapshot.prev = sfgetr(sfstdin, '\n', 0);
794
}
795
else
796
{
797
state.snapshot.format.path = SNAPSHOT_PATH;
798
state.snapshot.format.easy = SNAPSHOT_EASY;
799
state.snapshot.format.hard = SNAPSHOT_HARD;
800
state.snapshot.format.delim = SNAPSHOT_DELIM[0];
801
}
802
if (!(state.snapshot.tmp = sfstropen()))
803
error(ERROR_SYSTEM|3, "out of space");
804
compile("sort:name;", 0);
805
continue;
806
case 'C':
807
state.ftwflags |= FTW_NOSEEDOTDIR;
808
continue;
809
case 'D':
810
error_info.trace = -opt_info.num;
811
continue;
812
case 'E':
813
compile(opt_info.arg, 1);
814
continue;
815
case 'F':
816
codes = opt_info.arg;
817
continue;
818
case 'G':
819
disc.flags |= FIND_GENERATE;
820
if (streq(opt_info.arg, "old"))
821
disc.flags |= FIND_OLD;
822
else if (streq(opt_info.arg, "gnu") || streq(opt_info.arg, "locate"))
823
disc.flags |= FIND_GNU;
824
else if (streq(opt_info.arg, "type"))
825
disc.flags |= FIND_TYPE;
826
else if (streq(opt_info.arg, "?"))
827
{
828
error(2, "formats are { default|dir type old gnu|locate }");
829
return 0;
830
}
831
else if (!streq(opt_info.arg, "-") && !streq(opt_info.arg, "default") && !streq(opt_info.arg, "dir"))
832
error(3, "%s: invalid find codes format -- { default|dir type old gnu|locate } expected", opt_info.arg);
833
continue;
834
case 'H':
835
state.ftwflags |= FTW_META|FTW_PHYSICAL;
836
continue;
837
case 'I':
838
state.icase = 1;
839
continue;
840
case 'L':
841
state.ftwflags &= ~(FTW_META|FTW_PHYSICAL|FTW_SEEDOTDIR);
842
continue;
843
case 'P':
844
state.ftwflags &= ~FTW_META;
845
state.ftwflags |= FTW_PHYSICAL;
846
continue;
847
case 'S':
848
state.separator = *opt_info.arg;
849
continue;
850
case 'X':
851
state.ftwflags |= FTW_MOUNT;
852
continue;
853
case '?':
854
error(ERROR_USAGE|4, "%s", opt_info.arg);
855
continue;
856
case ':':
857
error(2, "%s", opt_info.arg);
858
continue;
859
}
860
break;
861
}
862
argv += opt_info.index;
863
argc -= opt_info.index;
864
if (error_info.errors)
865
error(ERROR_USAGE|4, "%s", optusage(NiL));
866
867
/*
868
* do it
869
*/
870
871
if (state.snapshot.tmp)
872
sfprintf(sfstdout, "%c%s%c%s%c%s%c%s%c\n",
873
state.snapshot.format.delim, SNAPSHOT_ID,
874
state.snapshot.format.delim, state.snapshot.format.path,
875
state.snapshot.format.delim, state.snapshot.format.easy,
876
state.snapshot.format.delim, state.snapshot.format.hard ? state.snapshot.format.hard : "",
877
state.snapshot.format.delim);
878
if (x = exexpr(state.program, "begin", NiL, 0))
879
eval(x, NiL);
880
if ((x = exexpr(state.program, "select", NiL, INTEGER)) || (x = exexpr(state.program, NiL, NiL, INTEGER)))
881
state.select = x;
882
if (!(state.ftwflags & FTW_PHYSICAL))
883
state.ftwflags &= ~FTW_DELAY;
884
memset(&ftw, 0, sizeof(ftw));
885
ftw.path = ftw.name = "";
886
if (traverse)
887
{
888
if (x = exexpr(state.program, "action", NiL, 0))
889
state.action = x;
890
if (x = exexpr(state.program, "sort", NiL, 0))
891
{
892
state.sortkey = x;
893
y = 0;
894
for (;;)
895
{
896
switch (x->op)
897
{
898
case ',':
899
y = x->data.operand.right;
900
/*FALLTHROUGH*/
901
case '!':
902
case '~':
903
case S2B:
904
case X2I:
905
x = x->data.operand.left;
906
continue;
907
case ID:
908
if (!(x = y))
909
break;
910
y = 0;
911
continue;
912
default:
913
error(3, "invalid sort identifier (op 0x%02x)", x->op);
914
break;
915
}
916
break;
917
}
918
state.sort = order;
919
}
920
if (*argv && (*argv)[0] == '-' && (*argv)[1] == 0)
921
{
922
state.ftwflags |= FTW_LIST;
923
argv++;
924
argc--;
925
}
926
if (*argv || args || count || !(state.cmdflags & CMD_IMPLICIT))
927
{
928
Cmddisc_t disc;
929
930
CMDDISC(&disc, state.cmdflags, errorf);
931
state.cmd = cmdopen(argv, count, size, args, &disc);
932
state.ftwflags |= FTW_DOT;
933
}
934
else
935
state.cmdflags &= ~CMD_IMPLICIT;
936
if (codes && (disc.flags & FIND_GENERATE))
937
{
938
char* p;
939
Dir_t* dp;
940
char pwd[PATH_MAX];
941
char tmp[PATH_MAX];
942
943
disc.version = FIND_VERSION;
944
if (state.cmdflags & CMD_TRACE)
945
disc.flags |= FIND_TYPE;
946
if (state.cmdflags & CMD_QUERY)
947
disc.flags |= FIND_OLD;
948
disc.errorf = errorf;
949
if (!(state.find = findopen(codes, NiL, NiL, &disc)))
950
exit(2);
951
if (disc.flags & FIND_TYPE)
952
{
953
state.act = ACT_CODETYPE;
954
compile("_tw_init:mime;", 0);
955
state.magicdisc.flags |= MAGIC_MIME;
956
}
957
else
958
state.act = ACT_CODE;
959
state.icase = 1;
960
state.pattern = 0;
961
state.sort = order;
962
if (!state.program)
963
compile("1", 0);
964
if (!(state.sortkey = newof(0, Exnode_t, 1, 0)) || !(state.sortkey->data.variable.symbol = (Exid_t*)dtmatch(state.program->symbols, "name")))
965
error(ERROR_SYSTEM|3, "out of space");
966
state.sortkey->op = ID;
967
s = p = 0;
968
for (dp = (firstdir == lastdir) ? firstdir : firstdir->next; dp; dp = dp->next)
969
{
970
if (*(s = dp->name) == '/')
971
sfsprintf(tmp, sizeof(tmp), "%s", s);
972
else if (!p && !(p = getcwd(pwd, sizeof(pwd))))
973
error(ERROR_SYSTEM|3, "cannot determine pwd path");
974
else
975
sfsprintf(tmp, sizeof(tmp), "%s/%s", p, s);
976
pathcanon(tmp, sizeof(tmp), PATH_PHYSICAL);
977
if (!(dp->name = strdup(tmp)))
978
error(ERROR_SYSTEM|3, "out of space [PATH_PHYSICAL]");
979
}
980
}
981
else if (state.snapshot.tmp)
982
state.act = ACT_SNAPSHOT;
983
else if (state.cmdflags & CMD_IMPLICIT)
984
state.act = ACT_CMDARG;
985
else if (state.action == LIST)
986
state.act = ACT_LIST;
987
else if (state.action)
988
state.act = ACT_EVAL;
989
if (state.intermediate)
990
{
991
state.actII = state.act;
992
state.act = ACT_INTERMEDIATE;
993
}
994
if (state.pattern)
995
{
996
disc.version = FIND_VERSION;
997
if (state.icase)
998
disc.flags |= FIND_ICASE;
999
disc.errorf = errorf;
1000
disc.dirs = ap = av;
1001
if (firstdir != lastdir)
1002
firstdir = firstdir->next;
1003
do {*ap++ = firstdir->name;} while (firstdir = firstdir->next);
1004
*ap = 0;
1005
if (!(state.find = findopen(codes, state.pattern, NiL, &disc)))
1006
exit(1);
1007
state.ftwflags |= FTW_TOP;
1008
n = state.select == ALL ? state.act : ACT_EVAL;
1009
while (s = findread(state.find))
1010
{
1011
switch (n)
1012
{
1013
case ACT_CMDARG:
1014
if ((i = cmdarg(state.cmd, s, strlen(s))) >= state.errexit)
1015
exit(i);
1016
break;
1017
case ACT_LIST:
1018
sfputr(sfstdout, s, '\n');
1019
break;
1020
default:
1021
ftwalk(s, tw, state.ftwflags, NiL);
1022
break;
1023
}
1024
}
1025
}
1026
else if (state.ftwflags & FTW_LIST)
1027
{
1028
sfopen(sfstdin, NiL, "rt");
1029
n = state.select == ALL && state.act == ACT_CMDARG;
1030
for (;;)
1031
{
1032
if (s = sfgetr(sfstdin, state.separator, 1))
1033
len = sfvalue(sfstdin) - 1;
1034
else if (state.separator != '\n')
1035
{
1036
state.separator = '\n';
1037
continue;
1038
}
1039
else if (s = sfgetr(sfstdin, state.separator, -1))
1040
len = sfvalue(sfstdin);
1041
else
1042
break;
1043
if (!n)
1044
ftwalk(s, tw, state.ftwflags, NiL);
1045
else if ((i = cmdarg(state.cmd, s, len)) >= state.errexit)
1046
exit(i);
1047
}
1048
if (sferror(sfstdin))
1049
error(ERROR_SYSTEM|2, "input read error");
1050
}
1051
else if (firstdir == lastdir)
1052
ftwalk(firstdir->name, tw, state.ftwflags, state.sort);
1053
else
1054
{
1055
ap = av;
1056
while (firstdir = firstdir->next)
1057
*ap++ = firstdir->name;
1058
*ap = 0;
1059
ftwalk((char*)av, tw, state.ftwflags|FTW_MULTIPLE, state.sort);
1060
}
1061
if (state.cmd && (i = cmdflush(state.cmd)) >= state.errexit)
1062
exit(i);
1063
if (state.find && (findclose(state.find) || state.finderror))
1064
exit(2);
1065
}
1066
else if (state.select)
1067
error_info.errors = eval(state.select, &ftw) == 0;
1068
if (x = exexpr(state.program, "end", NiL, 0))
1069
eval(x, &ftw);
1070
if (sfsync(sfstdout))
1071
error(ERROR_SYSTEM|2, "write error");
1072
exit(error_info.errors != 0);
1073
}
1074
1075