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