From 6da89d6266511cfbe6f308799e8232a7920a5cc3 Mon Sep 17 00:00:00 2001 From: BurnZeZ Date: Mon, 25 Mar 2019 23:10:02 +0000 Subject: [PATCH] added walk(1) --- sys/man/1/walk | 122 +++++++++++++++++ sys/src/cmd/walk.c | 323 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 445 insertions(+) create mode 100644 sys/man/1/walk create mode 100644 sys/src/cmd/walk.c diff --git a/sys/man/1/walk b/sys/man/1/walk new file mode 100644 index 000000000..74d24b371 --- /dev/null +++ b/sys/man/1/walk @@ -0,0 +1,122 @@ +.TH WALK 1 +.SH NAME +walk \- walk a path +.SH SYNOPSIS +.B walk +[ +.B -dftxu +] [ +.B -n +.I mind,maxd +] [ +.B -e +.I ststr +] [ +.I name ... +] +.SH DESCRIPTION +.I Walk +recursively descends any directory arguments, +printing the name of each file on a separate line. +When no arguments are given, the current directory +is assumed. +Non-directory arguments are checked for existence, +then printed, if so. +.PP +Options are: +.TP +.B -d +Print only directories. +.TP +.B -f +Print only non-directories. +.TP +.B -t +Print a file only if it has the temporary flag set. +.TP +.B -x +Print a file only if it has any executable bits set. +.TP +.B -u +Unbuffered output. +.TP +.B -n min,max +Set the inclusive range of depths for filtering in results. +Both +.I min +and +.I max +are optional. +.TP +.B -e statfmt +Setting the statfmt string allows specifying the data +.B walk +should print. +It takes a string of characters, each corresponding +to some piece of information about the file being +traversed, and prints them separated by spaces. +.PP +The statfmt characters are as follows: +.TF . +.TP +.B U +owner name (uid) +.TP +.B G +group name (gid) +.TP +.B M +name of last user to modify (muid) +.TP +.B a +last access time (atime) +.TP +.B m +last modification time (mtime) +.TP +.B n +final path element (name) +.TP +.B p +path +.TP +.B q +qid path.version.type (see +.IR stat (2)) +.TP +.B s +size in bytes +.TP +.B x +permissions +.PD +.PP +The default statfmt is simply, +.IR p . +.SH EXAMPLES +List files in a directory, sorted by modification time. +.IP +.EX +walk -femp catpics | sort -n | sed 's/^[^ ]+ //' +.EE +.PP +Print the size and path of files (excluding dirs) +in the working directory. +.IP +.EX +walk -fn1 -esp +.EE +.PD +.SH SOURCE +.B /sys/src/cmd/walk.c +.SH SEE ALSO +.IR ls (1), +.IR du (1) +.SH BUGS +Statfmt character `x' displays permissions as an integer. +.PP +Manipulating ifs is a nuisance. +.PP +File names are assumed to not contain newlines. +.PP +Correct invocation requires too much thought. diff --git a/sys/src/cmd/walk.c b/sys/src/cmd/walk.c new file mode 100644 index 000000000..69a0f154f --- /dev/null +++ b/sys/src/cmd/walk.c @@ -0,0 +1,323 @@ +#include +#include +#include +#include + +int Cflag = 0; +int uflag = 0; +String *stfmt; + +/* should turn these flags into a mask */ +int dflag = 1; +int fflag = 1; +int tflag = 0; +int xflag = 0; +long maxdepth = ~(1<<31); +long mindepth = 0; + +char *dotpath = "."; +Dir *dotdir = nil; + +Biobuf *bout; + +int seen(Dir*); + +void +warn(char *fmt, ...) +{ + va_list arg; + char buf[1024]; /* arbitrary */ + int n; + + if((n = snprint(buf, sizeof(buf), "%s: ", argv0)) < 0) + sysfatal("snprint: %r"); + va_start(arg, fmt); + vseprint(buf+n, buf+sizeof(buf), fmt, arg); + va_end(arg); + + Bflush(bout); + fprint(2, "%s\n", buf); +} + +void +dofile(char *path, Dir *f, int pathonly, long depth) +{ + char *p; + + USED(depth); + + if( + (f == dotdir) + || (tflag && ! (f->qid.type & QTTMP)) + || (xflag && ! (f->mode & DMEXEC)) + ) + return; + + for(p = s_to_c(stfmt); *p != '\0'; p++){ + switch(*p){ + case 'U': Bwrite(bout, f->uid, strlen(f->uid)); break; + case 'G': Bwrite(bout, f->gid, strlen(f->gid)); break; + case 'M': Bwrite(bout, f->muid, strlen(f->muid)); break; + case 'a': Bprint(bout, "%uld", f->atime); break; + case 'm': Bprint(bout, "%uld", f->mtime); break; + case 'n': Bwrite(bout, f->name, strlen(f->name)); break; + case 'p': + if(path != dotpath) + Bwrite(bout, path, strlen(path)); + if(! (f->qid.type & QTDIR) && !pathonly){ + if(path != dotpath) + Bputc(bout, '/'); + Bwrite(bout, f->name, strlen(f->name)); + } + break; + case 'q': Bprint(bout, "%ullx.%uld.%.2uhhx", f->qid.path, f->qid.vers, f->qid.type); break; + case 's': Bprint(bout, "%lld", f->length); break; + case 'x': Bprint(bout, "%ulo", f->mode); break; + default: + abort(); + } + + if(*(p+1) != '\0') + Bputc(bout, ' '); + } + + Bputc(bout, '\n'); + + if(uflag) + Bflush(bout); +} + +void +walk(char *path, Dir *cf, long depth) +{ + String *file; + Dir *dirs, *f, *fe; + int fd; + long n; + + if(cf == nil){ + warn("path: %s: %r", path); + return; + } + + if(depth >= maxdepth) + goto nodescend; + + if((fd = open(path, OREAD)) < 0){ + warn("couldn't open %s: %r", path); + return; + } + + while((n = dirread(fd, &dirs)) > 0){ + fe = dirs+n; + for(f = dirs; f < fe; f++){ + if(seen(f)) + continue; + if(! (f->qid.type & QTDIR)){ + if(fflag && depth >= mindepth) + dofile(path, f, 0, depth); + } else if(strcmp(f->name, ".") == 0 || strcmp(f->name, "..") == 0){ + warn(". or .. named file: %s/%s", path, f->name); + } else{ + if(depth+1 > maxdepth){ + dofile(path, f, 0, depth); + continue; + } else if(path == dotpath){ + if((file = s_new()) == nil) + sysfatal("s_new: %r"); + } else{ + if((file = s_copy(path)) == nil) + sysfatal("s_copy: %r"); + if(s_len(file) != 1 || *s_to_c(file) != '/') + s_putc(file, '/'); + } + s_append(file, f->name); + + walk(s_to_c(file), f, depth+1); + s_free(file); + } + } + free(dirs); + } + close(fd); + if(n < 0) + warn("%s: %r", path); + +nodescend: + depth--; + if(dflag && depth >= mindepth) + dofile(path, cf, 0, depth); +} + +char* +slashslash(char *s) +{ + char *p, *q; + + for(p=q=s; *q; q++){ + if(q>s && *q=='/' && *(q-1)=='/') + continue; + if(p != q) + *p = *q; + p++; + } + do{ + *p-- = '\0'; + } while(p>s && *p=='/'); + + return s; +} + +long +estrtol(char *as, char **aas, int base) +{ + long n; + char *p; + + n = strtol(as, &p, base); + if(p == as || *p != '\0') + sysfatal("estrtol: bad input '%s'", as); + else if(aas != nil) + *aas = p; + + return n; +} + +void +elimdepth(char *p){ + char *q; + + if(strlen(p) == 0) + sysfatal("empty depth argument"); + + if(q = strchr(p, ',')){ + *q = '\0'; + if(p != q) + mindepth = estrtol(p, nil, 0); + p = q+1; + if(*p == '\0') + return; + } + + maxdepth = estrtol(p, nil, 0); +} + +void +usage(void) +{ + fprint(2, "usage: %s [-udftx] [-n mind,maxd] [-e statfmt] [file ...]\n", argv0); + exits("usage"); +} + +/* + Last I checked (commit 3dd6a31881535615389c24ab9a139af2798c462c), + libString calls sysfatal when things go wrong; in my local + copy of libString, failed calls return nil and errstr is set. + + There are various nil checks in this code when calling libString + functions, but since they are a no-op and libString needs + a rework, I left them in - BurnZeZ +*/ + +void +main(int argc, char **argv) +{ + long i; + Dir *d; + + stfmt = nil; + ARGBEGIN{ + case 'C': Cflag++; break; /* undocumented; do not cleanname() the args */ + case 'u': uflag++; break; /* unbuffered output */ + + case 'd': dflag++; fflag = 0; break; /* only dirs */ + case 'f': fflag++; dflag = 0; break; /* only non-dirs */ + case 't': tflag++; break; /* only tmp files */ + case 'x': xflag++; break; /* only executable permission */ + + case 'n': elimdepth(EARGF(usage())); break; + case 'e': + if((stfmt = s_reset(stfmt)) == nil) + sysfatal("s_reset: %r"); + s_append(stfmt, EARGF(usage())); + i = strspn(s_to_c(stfmt), "UGMamnpqsx"); + if(i != s_len(stfmt)) + sysfatal("bad stfmt: %s\n", s_to_c(stfmt)); + break; + default: + usage(); + }ARGEND; + + if((bout = Bfdopen(1, OWRITE)) == nil) + sysfatal("Bfdopen: %r"); + Blethal(bout, nil); + if(stfmt == nil){ + if((stfmt = s_new()) == nil) + sysfatal("s_new: %r"); + s_putc(stfmt, 'p'); + s_terminate(stfmt); + } + if(maxdepth != ~(1<<31)) + maxdepth++; + if(argc == 0){ + dotdir = dirstat("."); + walk(dotpath, dotdir, 1); + } else for(i=0; iqid.type & QTDIR)){ + if(fflag && !seen(d) && mindepth < 1) + dofile(argv[i], d, 1, 0); + } else + walk(argv[i], d, 1); + free(d); + } + Bterm(bout); + + exits(nil); +} + +/* below pilfered from /sys/src/cmd/du.c + * NOTE: I did not check for bugs */ + +#define NCACHE 256 /* must be power of two */ + +typedef struct +{ + Dir* cache; + int n; + int max; +} Cache; +Cache cache[NCACHE]; + +int +seen(Dir *dir) +{ + Dir *dp; + int i; + Cache *c; + + c = &cache[dir->qid.path&(NCACHE-1)]; + dp = c->cache; + for(i=0; in; i++, dp++) + if(dir->qid.path == dp->qid.path && + dir->type == dp->type && + dir->dev == dp->dev) + return 1; + if(c->n == c->max){ + if (c->max == 0) + c->max = 8; + else + c->max += c->max/2; + c->cache = realloc(c->cache, c->max*sizeof(Dir)); + if(c->cache == nil) + sysfatal("realloc: %r"); + } + c->cache[c->n++] = *dir; + return 0; +}