xref: /plan9-contrib/sys/src/cmd/walk.c (revision 6126a792bb0dbd81f004453253c57091c32d95b6)
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