xref: /csrg-svn/lib/libc/gen/fts.c (revision 42273)
1 /*-
2  * Copyright (c) 1990 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #if defined(LIBC_SCCS) && !defined(lint)
9 static char sccsid[] = "@(#)fts.c	5.4 (Berkeley) 05/22/90";
10 #endif /* LIBC_SCCS and not lint */
11 
12 #include <sys/param.h>
13 #include <sys/stat.h>
14 #include <dirent.h>
15 #include <errno.h>
16 #include <fts.h>
17 #include <string.h>
18 
19 extern int errno;
20 
21 FTSENT *fts_alloc(), *fts_build(), *fts_cycle(), *fts_sort(), *fts_root();
22 short fts_stat();
23 char *malloc(), *realloc();
24 
25 /*
26  * Special case a root of "/" so that slashes aren't appended causing
27  * paths to be written as "//foo".
28  */
29 #define	NAPPEND(p) \
30 	(p->fts_level == ROOTLEVEL && p->fts_pathlen == 1 && \
31 	    p->fts_path[0] == '/' ? 0 : p->fts_pathlen)
32 
33 #define	CHDIR(sp, path)	(!(sp->fts_options & FTS_NOCHDIR) && chdir(path))
34 #define	FCHDIR(sp, fd)	(!(sp->fts_options & FTS_NOCHDIR) && fchdir(fd))
35 
36 #define	ROOTLEVEL	0
37 #define	ROOTPARENTLEVEL	-1
38 
39 static FTS *stream;			/* current stream pointer */
40 
41 FTS *
42 ftsopen(argv, options, compar)
43 	char *argv[];
44 	register int options;
45 	int (*compar)();
46 {
47 	register FTS *sp;
48 	register FTSENT *p, *root;
49 	register int nitems, maxlen;
50 	FTSENT *parent, *tmp;
51 	char wd[MAXPATHLEN], *getwd(), *strdup();
52 
53 	/* allocate/initialize the stream */
54 	if (!(stream = sp = (FTS *)malloc((u_int)sizeof(FTS))))
55 		return(NULL);
56 	bzero(sp, sizeof(FTS));
57 	sp->fts_compar = compar;
58 	sp->fts_options = options;
59 
60 	/* allocate/initialize root's parent */
61 	if (!(parent = fts_alloc("", 0)))
62 		goto mem1;
63 	parent->fts_level = ROOTPARENTLEVEL;
64 
65 	/* allocate/initialize root(s) */
66 	if (options & FTS_MULTIPLE) {
67 		maxlen = -1;
68 		for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) {
69 			if (!(p = fts_root(*argv)))
70 				goto mem2;
71 			if (maxlen < p->fts_namelen)
72 				maxlen = p->fts_namelen;
73 			/*
74 			 * if comparison routine supplied, traverse in sorted
75 			 * order; otherwise traverse in the order specified.
76 			 */
77 			if (compar) {
78 				p->fts_link = root;
79 				root = p;
80 				p->fts_accpath = p->fts_name;
81 				p->fts_info = fts_stat(p, 0);
82 			} else {
83 				p->fts_link = NULL;
84 				if (!root)
85 					tmp = root = p;
86 				else {
87 					tmp->fts_link = p;
88 					tmp = p;
89 				}
90 			}
91 			p->fts_level = ROOTLEVEL;
92 			p->fts_parent = parent;
93 		}
94 		if (compar && nitems > 1)
95 			root = fts_sort(root, nitems);
96 	} else {
97 		if (!(root = fts_root((char *)argv)))
98 			goto mem2;
99 		maxlen = root->fts_namelen;
100 		root->fts_link = NULL;
101 		root->fts_level = ROOTLEVEL;
102 		root->fts_parent = parent;
103 	}
104 
105 	/* start out with at least 1K+ of path space */
106 	if (!fts_path(MAX(maxlen, MAXPATHLEN)))
107 		goto mem2;
108 
109 	/*
110 	 * some minor trickiness.  Set the pointers so that ftsread thinks
111 	 * we've just finished the node before the root(s); set p->fts_info
112 	 * to FTS_NS so that everything about the "current" node is ignored.
113 	 * Rather than allocate a dummy node use the root's parent's link
114 	 * pointer.  This is handled specially in ftsclose() as well.
115 	 */
116 	sp->fts_cur = parent;
117 	parent->fts_link = root;
118 	parent->fts_info = FTS_NS;
119 
120 	/*
121 	 * if using chdir(2), do a getwd() to insure that we can get back
122 	 * here; this could be avoided for some paths, but probably not
123 	 * worth the effort.  Slashes, symbolic links, and ".." are all
124 	 * fairly nasty problems.  Note, if we can't get the current
125 	 * working directory we run anyway, just more slowly.
126 	 */
127 	if (!(options & FTS_NOCHDIR) &&
128 	    (!getwd(wd) || !(sp->fts_wd = strdup(wd))))
129 		sp->fts_options |= FTS_NOCHDIR;
130 
131 	return(sp);
132 
133 mem2:	fts_lfree(root);
134 	(void)free((char *)parent);
135 mem1:	(void)free((char *)sp);
136 	return(NULL);
137 }
138 
139 static
140 fts_load(p)
141 	register FTSENT *p;
142 {
143 	register int len;
144 	register char *cp;
145 
146 	/*
147 	 * load the stream structure for the next traversal; set the
148 	 * accpath field specially so the chdir gets done to the right
149 	 * place and the user can access the first node.
150 	 */
151 	len = p->fts_pathlen = p->fts_namelen;
152 	bcopy(p->fts_name, stream->fts_path, len + 1);
153 	if ((cp = rindex(p->fts_name, '/')) && (cp != p->fts_name || cp[1])) {
154 		len = strlen(++cp);
155 		bcopy(cp, p->fts_name, len + 1);
156 		p->fts_namelen = len;
157 	}
158 	p->fts_accpath = p->fts_path = stream->fts_path;
159 }
160 
161 ftsclose(sp)
162 	FTS *sp;
163 {
164 	register FTSENT *freep, *p;
165 	int saved_errno;
166 
167 	if (sp->fts_cur)
168 		/* check for never having read anything */
169 		if (sp->fts_cur->fts_level == ROOTPARENTLEVEL)
170 			fts_lfree(sp->fts_cur);
171 		else {
172 			for (p = sp->fts_cur; p->fts_level > ROOTPARENTLEVEL;) {
173 				freep = p;
174 				p = p->fts_link ? p->fts_link : p->fts_parent;
175 				(void)free((char *)freep);
176 			}
177 			(void)free((char *)p);
178 		}
179 
180 	/* free up child linked list, sort array, path buffer */
181 	if (sp->fts_child)
182 		fts_lfree(sp->fts_child);
183 	if (sp->fts_array)
184 		(void)free((char *)sp->fts_array);
185 	(void)free((char *)sp->fts_path);
186 
187 	/*
188 	 * return to original directory, save errno if necessary;
189 	 * free up the directory buffer
190 	 */
191 	if (!(sp->fts_options & FTS_NOCHDIR)) {
192 		saved_errno = chdir(sp->fts_wd) ? errno : 0;
193 		(void)free(sp->fts_wd);
194 	}
195 
196 	/* free up the stream pointer */
197 	(void)free((char *)sp);
198 
199 	/* set errno and return */
200 	if (!(sp->fts_options & FTS_NOCHDIR) && saved_errno) {
201 		errno = saved_errno;
202 		return(-1);
203 	}
204 	return(0);
205 }
206 
207 FTSENT *
208 ftsread(sp)
209 	register FTS *sp;
210 {
211 	register FTSENT *p;
212 	register int instr;
213 	static int cd;
214 	FTSENT *tmp;
215 	char *cp;
216 
217 	/* if finished or unrecoverable error, return NULL */
218 	if (!sp->fts_cur || sp->fts_options & FTS__STOP)
219 		return(NULL);
220 
221 	/* set global stream pointer, and current node pointer */
222 	stream = sp;
223 	p = sp->fts_cur;
224 
225 	/* save and zero out user instructions */
226 	instr = p->fts_instr;
227 	p->fts_instr = 0;
228 
229 	/* if used link pointer for cycle detection, restore it */
230 	if (sp->fts_savelink) {
231 		p->fts_link = sp->fts_savelink;
232 		sp->fts_savelink = NULL;
233 	}
234 
235 	/* any type of file may be re-visited; re-stat and return */
236 	if (instr == FTS_AGAIN) {
237 		p->fts_info = fts_stat(p, 0);
238 		return(p);
239 	}
240 
241 	if (p->fts_info == FTS_D)
242 		if (instr == FTS_SKIP) {
243 			if (sp->fts_child) {
244 				fts_lfree(sp->fts_child);
245 				sp->fts_child = NULL;
246 			}
247 		} else {
248 			if (!sp->fts_child &&
249 			    (!(sp->fts_child = fts_build(sp, 1))))
250 				return(p);
251 			p = sp->fts_child;
252 			sp->fts_child = NULL;
253 			return(sp->fts_cur = p);
254 		}
255 	else if (p->fts_info == FTS_SL && instr == FTS_FOLLOW) {
256 		p->fts_info = fts_stat(p, 1);
257 		return(p);
258 	}
259 
260 	/*
261 	 * user may have called ftsset on pointer returned by
262 	 * ftschildren; handle it here.
263 	 */
264 	for (p = p->fts_link; p;) {
265 		instr = p->fts_instr;
266 		if (instr == FTS_FOLLOW) {
267 			p->fts_info = fts_stat(p, 1);
268 			p->fts_instr = 0;
269 			break;
270 		}
271 		if (instr == FTS_SKIP) {
272 			tmp = p;
273 			p = p->fts_link;
274 			(void)free((char *)tmp);
275 			continue;
276 		}
277 		p->fts_instr = 0;
278 		break;
279 	}
280 
281 	/* go to next node on this level */
282 	if (p) {
283 		/*
284 		 * if root level node, set up paths and return.  If not the
285 		 * first time, and it's not an absolute pathname, get back
286 		 * to wherever we started from.
287 		 */
288 		if (p->fts_level == ROOTLEVEL) {
289 			fts_load(p);
290 			if (cd) {
291 				(void)free((char *)sp->fts_cur);
292 				if (p->fts_path[0] != '/' &&
293 				    CHDIR(sp, sp->fts_wd)) {
294 					/* return target path for error msg */
295 					p->fts_path = sp->fts_wd;
296 					p->fts_info = FTS_ERR;
297 					sp->fts_options |= FTS__STOP;
298 					return(sp->fts_cur = p);
299 				}
300 			} else
301 				cd = 1;
302 			p->fts_info = fts_stat(p, 0);
303 		} else {
304 			(void)free((char *)sp->fts_cur);
305 			cp = sp->fts_path + NAPPEND(p->fts_parent);
306 			*cp++ = '/';
307 			bcopy(p->fts_name, cp, p->fts_namelen + 1);
308 			if (p->fts_info == FTS_D && (tmp = fts_cycle(p))) {
309 				sp->fts_savelink = p->fts_link;
310 				p->fts_link = tmp;
311 			}
312 		}
313 		return(sp->fts_cur = p);
314 	}
315 
316 	/* go to parent */
317 	p = sp->fts_cur->fts_parent;
318 	(void)free((char *)sp->fts_cur);
319 	if (p->fts_level == ROOTPARENTLEVEL) {
320 		/*
321 		 * done; free everything up and set errno to 0 so the user
322 		 * can distinguish between error and EOF.
323 		 */
324 		(void)free((char *)p);
325 		errno = 0;
326 		return(sp->fts_cur = NULL);
327 	}
328 
329 	sp->fts_path[p->fts_pathlen] = '\0';
330 	if (CHDIR(sp, "..")) {
331 		sp->fts_options |= FTS__STOP;
332 		p->fts_info = FTS_ERR;
333 	} else
334 		p->fts_info = FTS_DP;
335 	return(sp->fts_cur = p);
336 }
337 
338 /*
339  * ftsset takes the stream as an argument although it's not used in this
340  * implementation; it would be necessary if anyone wanted to add global
341  * semantics to fts using ftsset.  A possible error return is allowed for
342  * similar reasons.
343  */
344 /* ARGSUSED */
345 ftsset(sp, p, instr)
346 	FTS *sp;
347 	FTSENT *p;
348 	int instr;
349 {
350 	p->fts_instr = instr;
351 	return(0);
352 }
353 
354 FTSENT *
355 ftschildren(sp)
356 	register FTS *sp;
357 {
358 	/*
359 	 * set errno to 0 so that user can tell the difference between an
360 	 * error and a directory without entries.
361 	 */
362 	errno = 0;
363 	if (sp->fts_cur->fts_info != FTS_D || sp->fts_options & FTS__STOP)
364 		return(NULL);
365 	if (sp->fts_child)
366 		fts_lfree(sp->fts_child);
367 	return(sp->fts_child = fts_build(sp, 0));
368 }
369 
370 #define	ISDOT(a)	(a[0] == '.' && (!a[1] || a[1] == '.' && !a[2]))
371 
372 static FTSENT *
373 fts_build(sp, set_node)
374 	register FTS *sp;
375 	int set_node;
376 {
377 	register struct dirent *dp;
378 	register FTSENT *p, *head;
379 	register int nitems;
380 	DIR *dirp;
381 	int descend, len, level, maxlen, nlinks, saved_errno;
382 	char *cp;
383 
384 	p = sp->fts_cur;
385 	if (!(dirp = opendir(p->fts_accpath))) {
386 		if (set_node) {
387 			errno = 0;
388 			p->fts_info = FTS_DNR;
389 		}
390 		return(NULL);
391 	}
392 
393 	/*
394 	 * the real slowdown in walking the tree is the stat calls.  If
395 	 * FTS_NOSTAT is set and it's a physical walk (so that symbolic
396 	 * links can't be directories), fts assumes that the number of
397 	 * subdirectories in a node is equal to the number of links to
398 	 * the parent.  This allows stat calls to be skipped in any leaf
399 	 * directories and for any nodes after the directories in the
400 	 * parent node have been found.  This empirically cuts the stat
401 	 * calls by about 2/3.
402 	 */
403 	nlinks =
404 	    sp->fts_options & FTS_NOSTAT && sp->fts_options & FTS_PHYSICAL ?
405 	    p->fts_statb.st_nlink - (sp->fts_options & FTS_SEEDOT ? 0 : 2) : -1;
406 
407 	if (nlinks || set_node) {
408 		if (FCHDIR(sp, dirfd(dirp))) {
409 			if (set_node) {
410 				errno = 0;
411 				p->fts_info = FTS_DNX;
412 			}
413 			return(NULL);
414 		}
415 		descend = 1;
416 	} else
417 		descend = 0;
418 
419 	/* get max file name length that can be stored in current path */
420 	maxlen = sp->fts_pathlen - p->fts_pathlen - 1;
421 
422 	cp = sp->fts_path + (len = NAPPEND(p));
423 	*cp++ = '/';
424 
425 	level = p->fts_level + 1;
426 
427 	/* read the directory, attching each new entry to the `link' pointer */
428 	for (head = NULL, nitems = 0; dp = readdir(dirp);) {
429 		if (ISDOT(dp->d_name) && !(sp->fts_options & FTS_SEEDOT))
430 			continue;
431 
432 		if (!(p = fts_alloc(dp->d_name, dp->d_namlen))) {
433 			saved_errno = errno;
434 			goto mem1;
435 		}
436 		if (dp->d_namlen > maxlen) {
437 			if (!fts_path((int)dp->d_namlen)) {
438 				/* quit: this stream no longer has a path */
439 				sp->fts_options |= FTS__STOP;
440 				saved_errno = errno;
441 				(void)free((char *)p);
442 mem1:				fts_lfree(head);
443 				if (set_node)
444 					p->fts_info = FTS_ERR;
445 				if (descend && CHDIR(sp, "..")) {
446 					saved_errno = errno;
447 					sp->fts_options |= FTS__STOP;
448 				}
449 				errno = saved_errno;
450 				return(NULL);
451 			}
452 			maxlen = sp->fts_pathlen - sp->fts_cur->fts_pathlen - 1;
453 		}
454 
455 		p->fts_pathlen = len + dp->d_namlen + 1;
456 		p->fts_accpath =
457 		    sp->fts_options & FTS_NOCHDIR ? p->fts_path : p->fts_name;
458 
459 		p->fts_parent = sp->fts_cur;
460 		p->fts_level = level;
461 
462 		if (nlinks) {
463 			/* make sure fts_stat has a filename to stat */
464 			if (sp->fts_options & FTS_NOCHDIR)
465 				bcopy(p->fts_name, cp, p->fts_namelen + 1);
466 			p->fts_info = fts_stat(p, 0);
467 			if (nlinks > 0 && (p->fts_info == FTS_D ||
468 			    p->fts_info == FTS_DNR || p->fts_info == FTS_DNX))
469 				--nlinks;
470 		} else
471 			p->fts_info = FTS_NS;
472 
473 		p->fts_link = head;
474 		head = p;
475 		++nitems;
476 	}
477 	(void)closedir(dirp);
478 
479 	if (descend && (!nitems || !set_node) && CHDIR(sp, "..")) {
480 		sp->fts_options |= FTS__STOP;
481 		p->fts_info = FTS_ERR;
482 		*--cp = '\0';
483 		return(NULL);
484 	}
485 
486 	if (!nitems) {
487 		if (set_node)
488 			p->fts_info = FTS_DP;
489 		*--cp = '\0';
490 		return(NULL);
491 	}
492 
493 	if (sp->fts_compar && nitems > 1)
494 		head = fts_sort(head, nitems);
495 
496 	if (set_node)
497 		bcopy(head->fts_name, cp, head->fts_namelen + 1);
498 	else
499 		*--cp = '\0';
500 	return(head);
501 }
502 
503 static short
504 fts_stat(p, symflag)
505 	register FTSENT *p;
506 	int symflag;
507 {
508 	/*
509 	 * detection of symbolic links w/o targets.  If FTS_FOLLOW is set,
510 	 * the symlink structure is overwritten with the stat structure of
511 	 * the target.
512 	 */
513 	if (stream->fts_options & FTS_LOGICAL || symflag) {
514 		if (stat(p->fts_accpath, &p->fts_statb))
515 			return(symflag || !lstat(p->fts_accpath,
516 			    &p->fts_statb) ? FTS_SLNONE : FTS_ERR);
517 	} else if (lstat(p->fts_accpath, &p->fts_statb))
518 		return(FTS_ERR);
519 
520 	switch(p->fts_statb.st_mode&S_IFMT) {
521 	case S_IFDIR:
522 		return(FTS_D);
523 	case S_IFLNK:
524 		return(FTS_SL);
525 	case S_IFREG:
526 		return(FTS_F);
527 	}
528 	return(FTS_DEFAULT);
529 }
530 
531 static FTSENT *
532 fts_cycle(p)
533 	register FTSENT *p;
534 {
535 	register dev_t dev;
536 	register ino_t ino;
537 
538 	/*
539 	 * cycle detection is brute force; if the tree gets deep enough or
540 	 * the number of symbolic links to directories is really high
541 	 * something faster might be worthwhile.
542 	 */
543 	dev = p->fts_statb.st_dev;
544 	ino = p->fts_statb.st_ino;
545 	for (p = p->fts_parent; p->fts_level > ROOTLEVEL; p = p->fts_parent)
546 		if (ino == p->fts_statb.st_ino && dev == p->fts_statb.st_dev)
547 			return(p);
548 	return(NULL);
549 }
550 
551 #define	R(type, nelem, ptr) \
552 	(type *)realloc((char *)ptr, (u_int)((nelem) * sizeof(type)))
553 
554 static FTSENT *
555 fts_sort(head, nitems)
556 	FTSENT *head;
557 	register int nitems;
558 {
559 	register FTSENT **ap, *p;
560 
561 	/*
562 	 * construct an array of pointers to the structures and call qsort(3).
563 	 * Reassemble the array in the order returned by qsort.  If unable to
564 	 * sort for memory reasons, return the directory entries in their
565 	 * current order.  Allocate enough space for the current needs plus
566 	 * 40 so we don't realloc one entry at a time.
567 	 */
568 	if (nitems > stream->fts_nitems) {
569 		stream->fts_nitems = nitems + 40;
570 		if (!(stream->fts_array =
571 		    R(FTSENT *, stream->fts_nitems, stream->fts_array))) {
572 			stream->fts_nitems = 0;
573 			return(head);
574 		}
575 	}
576 	for (ap = stream->fts_array, p = head; p; p = p->fts_link)
577 		*ap++ = p;
578 	qsort((char *)stream->fts_array, nitems, sizeof(FTSENT *),
579 	    stream->fts_compar);
580 	for (head = *(ap = stream->fts_array); --nitems; ++ap)
581 		ap[0]->fts_link = ap[1];
582 	ap[0]->fts_link = NULL;
583 	return(head);
584 }
585 
586 static FTSENT *
587 fts_alloc(name, len)
588 	char *name;
589 	register int len;
590 {
591 	register FTSENT *p;
592 
593 	/*
594 	 * variable sized structures; the name is the last element so
595 	 * allocate enough extra space after the structure to hold it.
596 	 */
597 	if (!(p = (FTSENT *)malloc((u_int)(sizeof(FTSENT) + len))))
598 		return(NULL);
599 	bcopy(name, p->fts_name, len + 1);
600 	p->fts_namelen = len;
601 	p->fts_path = stream->fts_path;
602 	p->fts_instr = 0;
603 	p->fts_local.number = 0;
604 	p->fts_local.pointer = NULL;
605 	return(p);
606 }
607 
608 static
609 fts_lfree(head)
610 	register FTSENT *head;
611 {
612 	register FTSENT *p;
613 
614 	while (p = head) {
615 		head = head->fts_link;
616 		(void)free((char *)p);
617 	}
618 }
619 
620 /*
621  * allow essentially unlimited paths; certain programs (find, remove, ls)
622  * need to work on any tree.  Most systems will allow creation of paths
623  * much longer than MAXPATHLEN, even though the kernel won't resolve them.
624  * Add an extra 128 bytes to the requested size so that we don't realloc
625  * the path 2 bytes at a time.
626  */
627 static
628 fts_path(size)
629 	int size;
630 {
631 	stream->fts_pathlen += size + 128;
632 	return((int)(stream->fts_path =
633 	    R(char, stream->fts_pathlen, stream->fts_path)));
634 }
635 
636 static FTSENT *
637 fts_root(name)
638 	register char *name;
639 {
640 	register char *cp;
641 
642 	/*
643 	 * rip trailing slashes; it's somewhat unclear in POSIX 1003.1 what
644 	 * /a/b/ really is, they don't talk about what a null path component
645 	 * resolves to.  This hopefully does what the user intended.  Don't
646 	 * allow null pathnames.
647 	 */
648 	for (cp = name; *cp; ++cp);
649 	if (cp == name) {
650 		errno = ENOENT;
651 		return(NULL);
652 	}
653 	while (--cp > name && *cp == '/');
654 	*++cp = '\0';
655 	return(fts_alloc(name, cp - name));
656 }
657