1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <String.h>
5
6 int Cflag = 0;
7 int uflag = 0;
8 String *stfmt;
9
10 /* should turn these flags into a mask */
11 int dflag = 1;
12 int fflag = 1;
13 int tflag = 0;
14 int xflag = 0;
15 long maxdepth = ~(1<<31);
16 long mindepth = 0;
17
18 char *dotpath = ".";
19 Dir *dotdir = nil;
20
21 Biobuf *bout;
22
23 int seen(Dir*);
24
25 void
warn(char * fmt,...)26 warn(char *fmt, ...)
27 {
28 va_list arg;
29 char buf[1024]; /* arbitrary */
30 int n;
31
32 if((n = snprint(buf, sizeof(buf), "%s: ", argv0)) < 0)
33 sysfatal("snprint: %r");
34 va_start(arg, fmt);
35 vseprint(buf+n, buf+sizeof(buf), fmt, arg);
36 va_end(arg);
37
38 Bflush(bout);
39 fprint(2, "%s\n", buf);
40 }
41
42 void
dofile(char * path,Dir * f,int pathonly)43 dofile(char *path, Dir *f, int pathonly)
44 {
45 char *p;
46
47 if(
48 (f == dotdir)
49 || (tflag && ! (f->qid.type & QTTMP))
50 || (xflag && ! (f->mode & DMEXEC))
51 )
52 return;
53
54 for(p = s_to_c(stfmt); *p != '\0'; p++){
55 switch(*p){
56 case 'U': Bwrite(bout, f->uid, strlen(f->uid)); break;
57 case 'G': Bwrite(bout, f->gid, strlen(f->gid)); break;
58 case 'M': Bwrite(bout, f->muid, strlen(f->muid)); break;
59 case 'a': Bprint(bout, "%uld", f->atime); break;
60 case 'm': Bprint(bout, "%uld", f->mtime); break;
61 case 'n': Bwrite(bout, f->name, strlen(f->name)); break;
62 case 'p':
63 if(path != dotpath)
64 Bwrite(bout, path, strlen(path));
65 if(! (f->qid.type & QTDIR) && !pathonly){
66 if(path != dotpath)
67 Bputc(bout, '/');
68 Bwrite(bout, f->name, strlen(f->name));
69 }
70 break;
71 case 'q': Bprint(bout, "%ullx.%uld.%.2uhhx", f->qid.path, f->qid.vers, f->qid.type); break;
72 case 's': Bprint(bout, "%lld", f->length); break;
73 case 'x': Bprint(bout, "%ulo", f->mode); break;
74 default:
75 abort();
76 }
77
78 if(*(p+1) != '\0')
79 Bputc(bout, ' ');
80 }
81
82 Bputc(bout, '\n');
83
84 if(uflag)
85 Bflush(bout);
86 }
87
88 void
walk(char * path,Dir * cf,long depth)89 walk(char *path, Dir *cf, long depth)
90 {
91 String *file;
92 Dir *dirs, *f, *fe;
93 int fd;
94 long n;
95
96 if(cf == nil){
97 warn("path: %s: %r", path);
98 return;
99 }
100
101 if(depth >= maxdepth)
102 goto nodescend;
103
104 if((fd = open(path, OREAD)) < 0){
105 warn("couldn't open %s: %r", path);
106 return;
107 }
108
109 while((n = dirread(fd, &dirs)) > 0){
110 fe = dirs+n;
111 for(f = dirs; f < fe; f++){
112 if(seen(f))
113 continue;
114 if(! (f->qid.type & QTDIR)){
115 if(fflag && depth >= mindepth)
116 dofile(path, f, 0);
117 } else if(strcmp(f->name, ".") == 0 || strcmp(f->name, "..") == 0){
118 warn(". or .. named file: %s/%s", path, f->name);
119 } else{
120 if(depth+1 > maxdepth){
121 dofile(path, f, 0);
122 continue;
123 } else if(path == dotpath){
124 if((file = s_new()) == nil)
125 sysfatal("s_new: %r");
126 } else{
127 if((file = s_copy(path)) == nil)
128 sysfatal("s_copy: %r");
129 if(s_len(file) != 1 || *s_to_c(file) != '/')
130 s_putc(file, '/');
131 }
132 s_append(file, f->name);
133
134 walk(s_to_c(file), f, depth+1);
135 s_free(file);
136 }
137 }
138 free(dirs);
139 }
140 close(fd);
141 if(n < 0)
142 warn("%s: %r", path);
143
144 nodescend:
145 depth--;
146 if(dflag && depth >= mindepth)
147 dofile(path, cf, 0);
148 }
149
150 char*
slashslash(char * s)151 slashslash(char *s)
152 {
153 char *p, *q;
154
155 for(p=q=s; *q; q++){
156 if(q>s && *q=='/' && *(q-1)=='/')
157 continue;
158 if(p != q)
159 *p = *q;
160 p++;
161 }
162 do{
163 *p-- = '\0';
164 } while(p>s && *p=='/');
165
166 return s;
167 }
168
169 long
estrtol(char * as,char ** aas,int base)170 estrtol(char *as, char **aas, int base)
171 {
172 long n;
173 char *p;
174
175 n = strtol(as, &p, base);
176 if(p == as || *p != '\0')
177 sysfatal("estrtol: bad input '%s'", as);
178 else if(aas != nil)
179 *aas = p;
180
181 return n;
182 }
183
184 void
elimdepth(char * p)185 elimdepth(char *p){
186 char *q;
187
188 if(strlen(p) == 0)
189 sysfatal("empty depth argument");
190
191 if(q = strchr(p, ',')){
192 *q = '\0';
193 if(p != q)
194 mindepth = estrtol(p, nil, 0);
195 p = q+1;
196 if(*p == '\0')
197 return;
198 }
199
200 maxdepth = estrtol(p, nil, 0);
201 }
202
203 void
usage(void)204 usage(void)
205 {
206 fprint(2, "usage: %s [-udftx] [-n mind,maxd] [-e statfmt] [file ...]\n", argv0);
207 exits("usage");
208 }
209
210 /*
211 Last I checked (commit 3dd6a31881535615389c24ab9a139af2798c462c),
212 libString calls sysfatal when things go wrong; in my local
213 copy of libString, failed calls return nil and errstr is set.
214
215 There are various nil checks in this code when calling libString
216 functions, but since they are a no-op and libString needs
217 a rework, I left them in - BurnZeZ
218 */
219
220 Biobuf *
Bfdopen(int fd,int flag)221 Bfdopen(int fd, int flag)
222 {
223 Biobuf *b;
224
225 b = malloc(sizeof(Biobuf));
226 if(b && Binit(b, fd, flag) == 0)
227 return b;
228 free(b);
229 return nil;
230 }
231
232
233 void
main(int argc,char ** argv)234 main(int argc, char **argv)
235 {
236 long i;
237 Dir *d;
238
239 stfmt = nil;
240 ARGBEGIN{
241 case 'C': Cflag++; break; /* undocumented; do not cleanname() the args */
242 case 'u': uflag++; break; /* unbuffered output */
243
244 case 'd': dflag++; fflag = 0; break; /* only dirs */
245 case 'f': fflag++; dflag = 0; break; /* only non-dirs */
246 case 't': tflag++; break; /* only tmp files */
247 case 'x': xflag++; break; /* only executable permission */
248
249 case 'n': elimdepth(EARGF(usage())); break;
250 case 'e':
251 if((stfmt = s_reset(stfmt)) == nil)
252 sysfatal("s_reset: %r");
253 s_append(stfmt, EARGF(usage()));
254 i = strspn(s_to_c(stfmt), "UGMamnpqsx");
255 if(i != s_len(stfmt))
256 sysfatal("bad stfmt: %s\n", s_to_c(stfmt));
257 break;
258 default:
259 usage();
260 }ARGEND;
261
262 if((bout = Bfdopen(1, OWRITE)) == nil)
263 sysfatal("Bfdopen: %r");
264 if(stfmt == nil){
265 if((stfmt = s_new()) == nil)
266 sysfatal("s_new: %r");
267 s_putc(stfmt, 'p');
268 s_terminate(stfmt);
269 }
270 if(maxdepth != ~(1<<31))
271 maxdepth++;
272 if(argc == 0){
273 dotdir = dirstat(".");
274 walk(dotpath, dotdir, 1);
275 } else for(i=0; i<argc; i++){
276 if(strncmp(argv[i], "#/", 2) == 0)
277 slashslash(argv[i]+2);
278 else{
279 if(!Cflag)
280 cleanname(argv[i]);
281 slashslash(argv[i]);
282 }
283 if((d = dirstat(argv[i])) != nil && ! (d->qid.type & QTDIR)){
284 if(fflag && !seen(d) && mindepth < 1)
285 dofile(argv[i], d, 1);
286 } else
287 walk(argv[i], d, 1);
288 free(d);
289 }
290 Bterm(bout);
291
292 exits(nil);
293 }
294
295 /* below pilfered from /sys/src/cmd/du.c
296 * NOTE: I did not check for bugs */
297
298 #define NCACHE 256 /* must be power of two */
299
300 typedef struct
301 {
302 Dir* cache;
303 int n;
304 int max;
305 } Cache;
306 Cache cache[NCACHE];
307
308 int
seen(Dir * dir)309 seen(Dir *dir)
310 {
311 Dir *dp;
312 int i;
313 Cache *c;
314
315 c = &cache[dir->qid.path&(NCACHE-1)];
316 dp = c->cache;
317 for(i=0; i<c->n; i++, dp++)
318 if(dir->qid.path == dp->qid.path &&
319 dir->type == dp->type &&
320 dir->dev == dp->dev)
321 return 1;
322 if(c->n == c->max){
323 if (c->max == 0)
324 c->max = 8;
325 else
326 c->max += c->max/2;
327 c->cache = realloc(c->cache, c->max*sizeof(Dir));
328 if(c->cache == nil)
329 sysfatal("realloc: %r");
330 }
331 c->cache[c->n++] = *dir;
332 return 0;
333 }
334