#include #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) { char *p; 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, "%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, 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); } 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, 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); } 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), "UGMamnpqsxDT"); if(i != s_len(stfmt)) sysfatal("bad stfmt: %s", s_to_c(stfmt)); break; default: usage(); }ARGEND; 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, 1); } else for(i=0; iqid.type & QTDIR)){ if(fflag && !seen(d) && mindepth < 1) dofile(argv[i], d, 1); } 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; }