2019-03-25 23:10:02 +00:00
|
|
|
#include <u.h>
|
|
|
|
#include <libc.h>
|
|
|
|
#include <bio.h>
|
|
|
|
#include <String.h>
|
2021-04-06 12:43:38 +00:00
|
|
|
#include <fcall.h>
|
2019-03-25 23:10:02 +00:00
|
|
|
|
|
|
|
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
|
2019-03-30 15:10:36 +00:00
|
|
|
dofile(char *path, Dir *f, int pathonly)
|
2019-03-25 23:10:02 +00:00
|
|
|
{
|
|
|
|
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;
|
2021-04-06 12:43:38 +00:00
|
|
|
case 'x': Bprint(bout, "%M", f->mode); break;
|
2020-01-28 01:27:41 +00:00
|
|
|
|
|
|
|
/* 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;
|
2019-03-25 23:10:02 +00:00
|
|
|
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)
|
2019-03-30 15:10:36 +00:00
|
|
|
dofile(path, f, 0);
|
2019-03-25 23:10:02 +00:00
|
|
|
} else if(strcmp(f->name, ".") == 0 || strcmp(f->name, "..") == 0){
|
|
|
|
warn(". or .. named file: %s/%s", path, f->name);
|
|
|
|
} else{
|
|
|
|
if(depth+1 > maxdepth){
|
2019-03-30 15:10:36 +00:00
|
|
|
dofile(path, f, 0);
|
2019-03-25 23:10:02 +00:00
|
|
|
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)
|
2019-03-30 15:10:36 +00:00
|
|
|
dofile(path, cf, 0);
|
2019-03-25 23:10:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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()));
|
2020-01-28 01:27:41 +00:00
|
|
|
i = strspn(s_to_c(stfmt), "UGMamnpqsxDT");
|
2019-03-25 23:10:02 +00:00
|
|
|
if(i != s_len(stfmt))
|
2020-01-28 00:44:44 +00:00
|
|
|
sysfatal("bad stfmt: %s", s_to_c(stfmt));
|
2019-03-25 23:10:02 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
usage();
|
|
|
|
}ARGEND;
|
|
|
|
|
2021-04-06 12:43:38 +00:00
|
|
|
fmtinstall('M', dirmodefmt);
|
|
|
|
|
2019-03-25 23:10:02 +00:00
|
|
|
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; 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 && ! (d->qid.type & QTDIR)){
|
|
|
|
if(fflag && !seen(d) && mindepth < 1)
|
2019-03-30 15:10:36 +00:00
|
|
|
dofile(argv[i], d, 1);
|
2019-03-25 23:10:02 +00:00
|
|
|
} 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; i<c->n; 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;
|
|
|
|
}
|