ref: b95ec908375919c15d5c3e10b3c08dda1a501235
dir: /sys/src/cmd/walk.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <String.h> #include <fcall.h> typedef struct Seen Seen; struct Seen { Seen *up; Dir *d; }; int Cflag = 0; int uflag = 0; String *stfmt; /* should turn these flags into a mask */ int printdirs = 1; int printfiles = 1; int temponly = 0; int execonly = 0; long maxdepth = ~(1<<31); long mindepth = 0; char *dotpath = "."; Dir *dotdir = nil; Biobuf *bout; 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); } int seen(Seen *s, Dir *d) { for(; s != nil; s = s->up) if(d->qid.path == s->d->qid.path && d->qid.type == s->d->qid.type && d->dev == s->d->dev) return 1; return 0; } void dofile(char *path, Dir *f, int pathonly) { char *p; if( (f == dotdir) || (temponly && ! (f->qid.type & QTTMP)) || (execonly && ! (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, "%M", f->mode); break; /* These two are slightly different, as they tell us about the fileserver instead of the file */ case 'D': Bprint(bout, "%ud", f->dev); break; case 'T': Bprint(bout, "%C", f->type); break; default: abort(); } if(*(p+1) != '\0') Bputc(bout, ' '); } Bputc(bout, '\n'); if(uflag) Bflush(bout); } void walk(char *path, Dir *cf, Seen *up, long depth) { String *file; Dir *dirs, *f, *fe; Seen s; long n, fseen; int fd; if(cf == nil){ warn("path: %s: %r", path); return; } fseen = seen(up, cf); if(depth >= maxdepth || fseen) goto nodescend; if((fd = open(path, OREAD)) < 0){ warn("couldn't open %s: %r", path); return; } s.d = cf; s.up = up; while((n = dirread(fd, &dirs)) > 0){ fe = dirs+n; for(f = dirs; f < fe; f++){ if(!(f->qid.type & QTDIR)){ if(printfiles && depth >= mindepth) dofile(path, f, 0); }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); 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, &s, depth+1); s_free(file); } } free(dirs); } close(fd); if(n < 0) warn("%s: %r", path); nodescend: depth--; if(printdirs && (depth >= mindepth || fseen)) dofile(path, cf, 0); } 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': printfiles = 0; break; /* only dirs */ case 'f': printdirs = 0; break; /* only non-dirs */ case 't': temponly = 1; break; /* only tmp files */ case 'x': execonly = 1; 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), "UGMamnpqsxDT"); if(i != s_len(stfmt)) sysfatal("bad stfmt: %s", s_to_c(stfmt)); break; default: usage(); }ARGEND; if(!printfiles && !printdirs) sysfatal("mutually exclusive flags: -f and -d"); fmtinstall('M', dirmodefmt); 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, nil, 1); }else for(i=0; i<argc; i++){ if(strncmp(argv[i], "#/", 2) == 0) slashslash(argv[i]+2); else{ if(!Cflag) cleanname(argv[i]); slashslash(argv[i]); } if((d = dirstat(argv[i])) == nil) continue; if(d->qid.type & QTDIR){ walk(argv[i], d, nil, 1); }else{ if(printfiles && mindepth < 1) dofile(argv[i], d, 1); } free(d); } Bterm(bout); exits(nil); }