tw.c revision 3f54fd611f536639ec30dd53c48e5ec1897cc7d9
/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1989-2012 AT&T Intellectual Property *
* and is licensed under the *
* Eclipse Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* Glenn Fowler <gsf@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
/*
* Glenn Fowler
* AT&T Research
*
* tw -- tree walk
*
* print [execute cmd on] path names in tree rooted at . [dir]
* or on path names listed on stdin [-]
*/
#define SNAPSHOT_ID "snapshot"
#define SNAPSHOT_PATH "%(url)s"
#define SNAPSHOT_EASY "%(ctime)..64u,%(perm)..64u,%(size)..64u,%(uid)..64u,%(gid)..64u"
#define SNAPSHOT_HARD "%(md5sum)s"
#define SNAPSHOT_DELIM "|"
static const char usage[] =
"[-?\n@(#)$Id: tw (AT&T Research) 2012-04-11 $\n]"
"[+NAME?tw - file tree walk]"
"[+DESCRIPTION?\btw\b recursively descends the file tree rooted at the "
"current directory and lists the pathname of each file found. If \acmd "
"arg ...\a is specified then the pathnames are collected and appended to "
"the end of the \aarg\alist and \acmd\a is executed by the equivalent of "
"\bexecvp\b(2). \acmd\a will be executed 0 or more times, depending the "
"number of generated pathname arguments.]"
"[+?If the last option is \b-\b and \b--fast\b was not specified then "
"the pathnames are read, one per line, from the standard input, the "
"\b--directory\b options are ignored, and the directory tree is not "
"traversed.]"
"[+?\bgetconf PATH_RESOLVE\b determines how symbolic links are handled. "
"This can be explicitly overridden by the \b--logical\b, "
"\b--metaphysical\b, and \b--physical\b options below. \bPATH_RESOLVE\b "
"can be one of:]"
"{"
"[+logical?Follow all symbolic links.]"
"[+metaphysical?Follow command argument symbolic links, "
"otherwise don't follow.]"
"[+physical?Don't follow symbolic links.]"
"}"
"[a:arg-list?The first \aarg\a named \astring\a is replaced by the "
"current pathname list before \acmd\a is executed.]:[string]"
"[c:args|arg-count?\acmd\a is executed after \acount\a arguments are "
"collected.]#[count]"
"[d:directory?The file tree traversal is rooted at \adir\a. Multiple "
"\b--directory\b directories are traversed in order from left to right. "
"If the last option was \b-\b then all \b--directory\b are "
"ignored.]:[dir]"
"[e:expr?\aexpr\a defines expression functions that control tree "
"traversal. Multiple \b--expr\b expressions are parsed in order from "
"left to right. See EXPRESSIONS below for details.]:[expr]"
"[f:fast?Searches the \bfind\b(1) or \blocate\b(1) database for paths "
"matching the \bksh\b(1) \apattern\a. See \bupdatedb\b(1) for details on "
"this database. Any \b--expr\b expressions are applied to the matching "
"paths.]:[pattern]"
"[i:ignore-errors?Ignore inaccessible files and directories.]"
"[I:ignore-case?Ignore case in pathname comparisons.]"
"[l:local?Do not descend into non-local filesystem directories.]"
"[m:intermediate?Before visiting a selected file select and visit "
"intermediate directories leading to the file that have not already been "
"selected.]"
"[n:notraverse?Evaluate the \bbegin\b, \bselect\b and \bend\b "
"expressions but eliminate the tree traversal.]"
"[p:post?Visit each directory after its files have been processed. By "
"default directories are visited pre-order.]"
"[q:query?Emit an interactive query for each visited path. An "
"affirmative response accepts the path, a negative response rejects the "
"path, and a quit response exits \atw\a.]"
"[r:recursive?Visit directories listed on the standard input.]"
"[S:separator?The input file list separator is set to the first "
"character of \astring\a.]:[string]"
"[s:size|max-chars?Use at most \achars\a characters per command. The "
"default is as large as possible.]#[chars]"
"[t:trace|verbose?Print the command line on the standard error before "
"executing it.]"
"[x:error-exit?Exit \btw\b with the exit code of the first \acmd\a that "
"returns an exit code greater than or equal to \acode\a. By default "
"\acmd\a exit codes are ignored (mostly because of \bgrep\b(1).)]#[code]"
"[z:snapshot?Write a snapshot of the selected files to the standard "
"output. For the first snapshot the standard input must either be empty "
"or a single line containing delimiter separated output format fields, "
"with the delimiter appearing as both the first and last character. The "
"format is in the same style as \bls\b(1) and \bps\b(1) formats: "
"%(\aidentifier\a)\aprintf-format\a, where \aidentifier\a is one of the "
"file status identifiers described below, and \aprintf-format\a is a "
"\bprintf\b(3) format specification. A default delimiter (\""
SNAPSHOT_DELIM "\") and field values are assumed for an empty "
"snapshot input file. The format fields are:]"
"{"
"[+easy-format?The \aeasy\a part of the snapshot file state, default \""
SNAPSHOT_EASY "\". This part is recomputed for each file.]"
"[+hard-format?The \ahard\a part of the snapshot file state, default \""
SNAPSHOT_HARD "\". This part is computed only if the easy part "
"has changed.]"
"[+change-status?This field, with both a leading and trailing delimiter, "
"is ignored on input and set to one of the following values for "
"files that have changed since the last snapshot:]"
"{"
"[+C?The file changed.]"
"[+D?The file was deleted.]"
"[+N?The file is new.]"
"}"
"}"
"[C:chop?Chop leading \b./\b from printed pathnames. This is implied by "
"\b--logical\b.]"
"[E:file?Compile \b--expr\b expressions from \afile\a. Multiple "
"\b--file\b options may be specified. See EXPRESSIONS below for "
"details.]:[file]"
"[F:codes?Set the \blocate\b(1) fast find codes database "
"\apath\a.]:[path]"
"[G:generate?Generate a \aformat\a \blocate\b(1) database of the visited "
"files and directories. Exit status 1 means some files were not "
"accessible but the database was properly generated; exit status 2 means "
"that database was not generated. Format may be:]:[format]"
"{"
"[+dir?machine independent with directory trailing /.]"
"[+old?old fast find]"
"[+gnu?gnu \blocate\b(1)]"
"[+type?machine independent with directory and mime types]"
"}"
"[L:logical|follow?Follow symbolic links. The default is determined by "
"\bgetconf PATH_RESOLVE\b.]"
"[H:metaphysical?Follow command argument symbolic links, otherwise don't "
"follow. The default is determined by \bgetconf PATH_RESOLVE\b.]"
"[P:physical?Don't follow symbolic links. The default is determined by "
"\bgetconf PATH_RESOLVE\b.]"
"[X:xdev|mount?Do not descend into directories in different filesystems "
"than their parents.]"
"[D:debug?Set the debug trace \alevel\a; higher levels produce more "
"output.]# [level]"
"\n"
"\n[ cmd [ arg ... ] ]\n"
"\n"
"[+EXPRESSIONS?Expressions are C style and operate on elements of the "
"\bstat\b(2) struct with the leading \bst_\b omitted. A function "
"expression is defined by one of:]"
"{"
"[+?function-name : statement-list]"
"[+?type function-name() { statement-list }]"
"}"
"[+?where \afunction-name\a is one of:]"
"{"
"[+begin?Evaluated before the traversal starts. The return value "
"is ignored. The default is a no-op.]"
"[+select?Evaluated as each file is visited. A 0 return value "
"skips \baction\b for the file; otherwise \baction\b is "
"evaluated. All files are selected by default. \bselect\b is "
"assumed when \afunction-name\a: is omitted.]"
"[+action?Evaluated for each select file. The return value is "
"ignored. The default \baction\b list the file path name, with "
"leading \b./\b stripped, one per line on the standard output.]"
"[+end?Evaluated after the traversal completes. The return value "
"is ignored.]"
"[+sort?A pseudo-function: the statement list is a , separated "
"list of identifiers used to sort the entries of each directory. "
"If any identifier is preceded by \b!\b then the sort order is "
"reversed. If any identifier is preceded by \b~\b then case is "
"ignored.]"
"}"
"[+?\astatement-list\a is a C style \bexpr\b(3) expression that "
"supports: \bint\b \avar\a, ...; and \bfloat\b \avar\a, ...; "
"declarations, \b(int)\b and \b(float)\b casts, \bif\b-\belse\b "
"conditionals, \bfor\b and \bwhile\b loops, and \b{...}\b blocks. The "
"trailing \b;\b in any expression list is optional. The expression value "
"is the value of the last evaluated expression in \astatement-list\a. "
"Numbers and comments follow C syntax. String operands must be quoted "
"with either \b\"...\"\b or \b'...'\b. String comparisons \b==\b and "
"\b!=\b treat the right hand operand as a \bksh\b(1) file match "
"pattern.]"
"[+?The expressions operate on the current pathname file status that is "
"provided by the following field identifiers, most of which are "
"described under \bst_\b\afield\a in \bstat\b(2). In general, if a "
"status identifier appears on the left hand side of a binary operator "
"then the right hand side may be a string that is converted to an "
"integral constant according to the identifier semantics.]"
"{"
"\bdate\b(1) expressions]"
"[+blocks?number of 1k blocks]"
"[+checksum?equivalent to \bsum(\"tw\")]"
"[+ctime?status change time]"
"[+dev?file system device]"
"[+fstype?file system type name; \bufs\b if it can't be "
"determined]"
"[+gid?owner group id; \agid\a strings are interpreted as group "
"names]"
"[+gidok?1 if \agid\a is a valid group id in the system "
"database, 0 otherwise.]"
"[+level?the depth of the file relative to the traversal root]"
"[+local?an integer valued field associated with each active "
"object in the traversal; This field may be assigned. The "
"initial value is 0. Multiple \alocal\a elements may be declared "
"by \bint local.\b\aelement1\a...;. In this case the \blocal\b "
"field itself is not accessible.]"
"[+md5sum?equivalent to \bsum(\"md5\")]"
"[+mime?the file contents \afile\a(1) \b--mime\b type]"
"[+mode?type and permission bits; the \bFMT\b constant may be "
"used to mask mask the file type and permission bits; \bmode\b "
"strings are interpreted as \bchmod\b(1) expressions]"
"[+mtime?modify time]"
"[+name?file name with directory prefix stripped]"
"[+nlink?hard link count]"
"[+path?full path name relative to the current active "
"\b--directory\b]"
"[+perm?the permission bits of \bmode\b]"
"[+rdev?the major.minor device number if the file is a device]"
"[+size?size in bytes]"
"[+status?the \bfts\b(3) \bFTS_\b* or \bftwalk\b(3) \bFTW_\b* "
"status. This field may be assigned:]"
"{"
"[+AGAIN?visit the file again]"
"[+FOLLOW?if the file is a symbolic link then follow it]"
"[+NOPOST?cancel any post order visit to this file]"
"[+SKIP?do not consider this file or any subdirectories "
"if it is a directory]"
"}"
"[+sum(\"\amethod\a\")?file contents checksum using \amethod\a; "
"see \bsum\b(1) \b--method\b for details.]"
"[+symlink?the symbolic link text if the file is a symbolic "
"link]"
"[+type?the type bits of \bmode\b:]"
"{"
"[+BLK?block special]"
"[+CHR?block special]"
"[+DIR?directory]"
"[+DOOR?door]"
"[+FIFO?fifo]"
"[+LNK?symbolic link]"
"[+REG?regular]"
"[+SOCK?unix domain socket]"
"}"
"[+uid?owner user id; \auid\a strings are interpreted as user "
"names]"
"[+uidok?1 if \auid\a is a valid user id in the system database, "
"0 otherwise.]"
"[+url?unprintable chars n path converted to %XX hex]"
"[+visit?an integer variable associated with each unique object "
"visited; Objects are identified using the \bdev\b and \bino\b "
"status identifiers. This field may be assigned. The initial "
"value is 0. Multiple \bvisit\b elements may be declared by "
"\bint visit.\b \aelement\a...;. In this case the \bvisit\b "
"field itself is not accessible.]"
"}"
"[+?Status identifiers may be prefixed by 1 or more \bparent.\b "
"references, to access ancestor directory information. The parent status "
"information of a top level object is the same as the object except that "
"\bname\b and \bpath\b are undefined. If a status identifier is "
"immediately preceded by \b\"string\"\b. then string is a file pathname "
"from which the status is taken.]"
"[+?The following \bexpr\b(3) functions are supported:]"
"{"
"[+exit(expr)?causes \atw\a to exit with the exit code \aexpr\a "
"which defaults to 0 if omitted]"
"[+printf(format[,arg...]])?print the arguments on the standard "
"output using the \bprintf\b(3) specification \aformat\a.]"
"[+eprintf(format[,arg...]])?print the arguments on the standard "
"error using the \bprintf\b(3) specification \aformat\a.]"
"[+query(format[,arg...]])?prompt with the \bprintf\b(3) message "
"on the standard error an read an interactive response. An "
"affirmative response returns 1, \bq\b or \bEOF\b causes \atw\a "
"to to exit immediately, and any other input returns 0.]"
"}"
"[+EXAMPLES]"
"{"
"[+tw?Lists the current directory tree.]"
"[+tw chmod go-w?Turns off the group and other write permissions "
"for all files in the current directory tree using a minimal "
"amount of \bchmod\b(1) command execs.]"
"[+tw -e \"uid != 'bozo' || (mode & 'go=w')\"?Lists all files in "
"the current directory that don't belong to the user \bbozo\b or "
"have group or other write permission.]"
"[+tw -m -d / -e \"fstype == '/'.fstype && mtime > '/etc/backup.time'.mtime\"?"
"Lists all files and intermediate directories on the same file "
"system type as \b/\b that are newer than the file "
"\b/etc/backup.time\b.]"
"[+tw - chmod +x < commands?Executes \bchmod +x\b on the "
"pathnames listed in the file \bcommands\b.]"
"[+tw -e \"int count;?\baction: count++; printf('name=%s "
"inode=%08ld\\\\n', name, ino); end: printf('%d file%s\\\\n', "
"count, count==1 ? '' : 's');\"\b Lists the name and inode "
"number of each file and also the total number of files.]"
"[+tw -pP -e \"?\baction: if (visit++ == 0) { parent.local += "
"local + blocks; if (type == DIR) printf('%d\\\\t%s\\\\n', local "
"+ blocks, path); }\"\b Exercise for the reader.]"
"}"
"[+EXIT STATUS]"
"{"
"[+0?All invocations of \acmd\a returned exit status 0.]"
"[+1-125?A command line meeting the specified requirements could "
"not be assembled, one or more of the invocations of \acmd\a "
"returned non-0 exit status, or some other error occurred.]"
"[+126?\acmd\a was found but could not be executed.]"
"[+127?\acmd\a was not found.]"
"}"
"[+ENVIRONMENT]"
"{"
"[+FINDCODES?Path name of the \blocate\b(1) database.]"
"[+LOCATE_PATH?Alternate path name of \blocate\b(1) database.]"
"}"
"[+FILES]"
"{"
"[+lib/find/find.codes?Default \blocate\b(1) database.]"
"}"
"[+NOTES?In order to access the \bslocate\b(1) database the \btw\b "
"executable must be setgid to the \bslocate\b group.]"
"[+SEE ALSO?\bfind\b(1), \bgetconf\b(1), \blocate\b(1), \bslocate\b(1), "
"\bsum\b(1), \bupdatedb\b(1), \bxargs\b(1)]"
;
#include "tw.h"
#include <ctype.h>
#include <proc.h>
#include <wait.h>
typedef struct Dir /* directory list */
{
char* name; /* dir name */
} Dir_t;
static void intermediate(Ftw_t*, char*);
static int
urlcmp(register const char* p, register const char* s, int d)
{
int pc;
int sc;
for (;;)
{
if ((pc = *p++) == d)
{
if (*s != d)
return -1;
break;
}
if ((sc = *s++) == d)
return 1;
if (pc == '%')
{
p += 2;
}
if (sc == '%')
{
s += 2;
}
return -1;
return 1;
}
return 0;
}
#define SNAPSHOT_changed 'C'
#define SNAPSHOT_deleted 'D'
#define SNAPSHOT_new 'N'
/*
* do the action
*/
static void
{
char* s;
int i;
int j;
int k;
int n;
int r;
switch (op)
{
case ACT_CMDARG:
exit(i);
break;
case ACT_CODE:
break;
case ACT_CODETYPE:
if (findwrite(state.find, ftw->path, ftw->pathlen, magictype(state.magic, fp, PATH(ftw), &ftw->statb)))
if (fp)
break;
case ACT_EVAL:
break;
case ACT_INTERMEDIATE:
break;
case ACT_LIST:
break;
case ACT_SNAPSHOT:
r = SNAPSHOT_new;
k = 1;
else
{
do
{
{
r = SNAPSHOT_changed;
if (!(k = memcmp(state.snapshot.prev + i, s + i, j - i) || state.snapshot.prev[j] != state.snapshot.format.delim))
{
{
}
else
}
}
else if (k > 0)
break;
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))
{
sfwrite(sfstdout, state.snapshot.prev, n - (state.snapshot.prev[n-2] == state.snapshot.format.delim ? 4 : 1));
}
break;
} while (k < 0);
}
if (k)
{
{
}
}
else
break;
}
}
/*
* generate intermediate (missing) directories for terminal elements
*/
static void
{
register char* s;
register char* t;
register int c;
{
c = *s;
*s = 0;
*s = c;
}
}
/*
* tw a single file
*/
static int
{
{
case FTW_NS:
{
return 0;
}
break;
case FTW_DC:
{
return 0;
}
break;
case FTW_DNR:
{
}
break;
case FTW_DNX:
{
}
break;
case FTW_DP:
goto pop;
break;
case FTW_D:
ftw->ignorecase = (state.icase || (!ftw->level || !ftw->parent->ignorecase) && strchr(astconf("PATH_ATTRIBUTES", ftw->name, NiL), 'c')) ? STR_ICASE : 0;
break;
default:
ftw->ignorecase = ftw->level ? ftw->parent->ignorecase : (state.icase || strchr(astconf("PATH_ATTRIBUTES", ftw->name, NiL), 'c')) ? STR_ICASE : 0;
break;
}
else
{
pop:
{
}
}
return 0;
}
/*
* order child entries
*/
static int
{
register Exnode_t* x;
register Exnode_t* y;
register int v;
long n1;
long n2;
int icase;
int reverse;
v = 0;
y = 0;
for (;;)
{
switch (x->op)
{
case '~':
continue;
case '!':
continue;
case ',':
continue;
case S2B:
case X2I:
continue;
case ID:
else
{
v = -1;
v = 1;
else
v = 0;
}
if (v)
{
if (reverse)
v = -v;
break;
}
if (!(x = y))
break;
y = 0;
continue;
}
break;
}
return v;
}
int
{
register int n;
register char* s;
char* args;
char* codes;
char** av;
char** ap;
int i;
int count;
int len;
int traverse;
int size;
Exnode_t* x;
Exnode_t* y;
args = 0;
codes = 0;
count = 0;
size = 0;
traverse = 1;
for (;;)
{
{
case 'a':
continue;
case 'c':
continue;
case 'd':
continue;
case 'e':
continue;
case 'f':
continue;
case 'i':
continue;
case 'l':
continue;
case 'm':
continue;
case 'n':
traverse = 0;
continue;
case 'p':
continue;
case 'q':
continue;
case 'r':
continue;
case 's':
continue;
case 't':
continue;
case 'x':
continue;
case 'z':
{
if (!(s = strdup(s)))
if (!(s = strchr(s, n)))
{
}
*s++ = 0;
goto osnap;
if (!(s = strchr(s, n)))
goto osnap;
*s++ = 0;
if (!(s = strchr(s, n)))
goto osnap;
*s++ = 0;
{
if (!(s = strchr(s, n)))
goto osnap;
*s = 0;
}
else
}
else
{
}
compile("sort:name;", 0);
continue;
case 'C':
continue;
case 'D':
continue;
case 'E':
continue;
case 'F':
continue;
case 'G':
{
return 0;
}
else if (!streq(opt_info.arg, "-") && !streq(opt_info.arg, "default") && !streq(opt_info.arg, "dir"))
error(3, "%s: invalid find codes format -- { default|dir type old gnu|locate } expected", opt_info.arg);
continue;
case 'H':
continue;
case 'I':
continue;
case 'L':
continue;
case 'P':
continue;
case 'S':
continue;
case 'X':
continue;
case '?':
continue;
case ':':
continue;
}
break;
}
if (error_info.errors)
/*
* do it
*/
if ((x = exexpr(state.program, "select", NiL, INTEGER)) || (x = exexpr(state.program, NiL, NiL, INTEGER)))
if (traverse)
{
{
y = 0;
for (;;)
{
switch (x->op)
{
case ',':
/*FALLTHROUGH*/
case '!':
case '~':
case S2B:
case X2I:
continue;
case ID:
if (!(x = y))
break;
y = 0;
continue;
default:
break;
}
break;
}
}
{
argv++;
argc--;
}
{
}
else
{
char* p;
exit(2);
{
compile("_tw_init:mime;", 0);
}
else
compile("1", 0);
if (!(state.sortkey = newof(0, Exnode_t, 1, 0)) || !(state.sortkey->data.variable.symbol = (Exid_t*)dtmatch(state.program->symbols, "name")))
s = p = 0;
{
else
}
}
if (state.intermediate)
{
}
{
*ap = 0;
exit(1);
{
switch (n)
{
case ACT_CMDARG:
exit(i);
break;
case ACT_LIST:
break;
default:
break;
}
}
}
{
for (;;)
{
{
continue;
}
else
break;
if (!n)
exit(i);
}
}
else
{
*ap = 0;
}
exit(i);
exit(2);
}
}