xref: /illumos-gate/usr/src/lib/libc/port/gen/nftw.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1988 AT&T	*/
28 /*	  All Rights Reserved	*/
29 
30 /*
31  *	nftw - new file tree walk
32  *
33  *	int nftw(char *path, int (*fn)(), int depth, int flags);
34  *
35  *	Derived from System V ftw() by David Korn
36  *
37  *	nftw visits each file and directory in the tree starting at
38  *	path. It uses the generic directory reading library so it works
39  *	for any file system type.  The flags field is used to specify:
40  *		FTW_PHYS  Physical walk, does not follow symbolic links
41  *			  Otherwise, nftw will follow links but will not
42  *			  walk down any path the crosses itself.
43  *		FTW_MOUNT The walk will not cross a mount point.
44  *		FTW_DEPTH All subdirectories will be visited before the
45  *			  directory itself.
46  *		FTW_CHDIR The walk will change to each directory before
47  *			  reading it.  This is faster but core dumps
48  *			  may not get generated.
49  *
50  *	The following flags are private, and are used by the find
51  *	utility:
52  *		FTW_ANYERR Call the callback function and return
53  *			   FTW_NS on any stat failure, not just
54  *			   lack of permission.
55  *		FTW_HOPTION Use stat the first time the walk
56  *			    function is called, regardless of
57  *			    whether or not FTW_PHYS is specified.
58  *		FTW_NOLOOP Allow find utility to detect infinite loops created
59  *			   by both symbolic and hard linked directories.
60  *
61  *	fn is called with four arguments at each file and directory.
62  *	The first argument is the pathname of the object, the second
63  *	is a pointer to the stat buffer and the third is an integer
64  *	giving additional information as follows:
65  *
66  *		FTW_F	The object is a file.
67  *		FTW_D	The object is a directory.
68  *		FTW_DP	The object is a directory and subdirectories
69  *			have been visited.
70  *		FTW_SL	The object is a symbolic link.
71  *		FTW_SLN The object is a symbolic link pointing at a
72  *		        non-existing file.
73  *		FTW_DNR	The object is a directory that cannot be read.
74  *			fn will not be called for any of its descendants.
75  *		FTW_NS	Stat failed on the object because of lack of
76  *			appropriate permission. The stat buffer passed to fn
77  *			is undefined.  Stat failure for any reason is
78  *			considered an error and nftw will return -1.
79  *	The following value is private, and is used by the find utility:
80  *		FTW_DL	An infinite loop has been detected.
81  *	The fourth argument is a struct FTW* which contains the depth
82  *	and the offset into pathname to the base name.
83  *	If fn returns nonzero, nftw returns this value to its caller.
84  *
85  *	depth limits the number of open directories that ftw uses
86  *	before it starts recycling file descriptors.  In general,
87  *	a file descriptor is used for each level.  When FTW_CHDIR isn't set,
88  *	in order to descend to arbitrary depths, nftw requires 2 file
89  *	descriptors to be open during the call to openat(), therefore if
90  *	the depth argument is less than 2 nftw will not use openat(), and
91  *	it will fail with ENAMETOOLONG if it descends to a directory that
92  *	exceeds PATH_MAX.
93  *
94  */
95 
96 #include "lint.h"
97 #include <mtlib.h>
98 #include <sys/types.h>
99 #include <sys/stat.h>
100 #include <dirent.h>
101 #include <errno.h>
102 #include <limits.h>
103 #include <ftw.h>
104 #include <stdlib.h>
105 #include <string.h>
106 #include <unistd.h>
107 #include <thread.h>
108 #include <synch.h>
109 #include <stdio.h>
110 #include <strings.h>
111 #include <fcntl.h>
112 
113 #if !defined(_LP64) && _FILE_OFFSET_BITS == 64
114 #define	nftw	nftw64
115 #define	stat	stat64
116 #define	fstat	fstat64
117 #define	fstatat	fstatat64
118 #pragma weak _nftw64 = nftw64
119 #else
120 #pragma weak _nftw = nftw
121 #endif /* !_LP64 && _FILE_OFFSET_BITS == 64 */
122 
123 #ifndef PATH_MAX
124 #define	PATH_MAX	1023
125 #endif
126 
127 /*
128  * Local variables (used to be static local).
129  * Putting them into a structure that is passed
130  * around makes nftw() MT-safe with no locking required.
131  */
132 struct Save {
133 	struct Save *last;
134 	DIR	*fd;
135 	char	*comp;
136 	long	here;
137 	dev_t	dev;
138 	ino_t	inode;
139 };
140 
141 struct Var {
142 	char	*home;
143 	size_t	len;
144 	char	*fullpath;
145 	char	*tmppath;
146 	int	curflags;
147 	dev_t	cur_mount;
148 	struct FTW state;
149 	int	walklevel;
150 	int	(*statf)(const char *, struct stat *, struct Save *, int flags);
151 	int	(*savedstatf)(const char *, struct stat *, struct Save *,
152 	    int flags);
153 	DIR	*(*opendirf)(const char *);
154 };
155 
156 static int oldclose(struct Save *);
157 static int cdlstat(const char *, struct stat *, struct Save *, int flags);
158 static int cdstat(const char *, struct stat *, struct Save *, int flags);
159 static int nocdlstat(const char *, struct stat *, struct Save *, int flags);
160 static int nocdstat(const char *, struct stat *, struct Save *, int flags);
161 static DIR *cdopendir(const char *);
162 static DIR *nocdopendir(const char *);
163 static const char *get_unrooted(const char *);
164 
165 /*
166  * This is the recursive walker.
167  */
168 static int
169 walk(char *component,
170     int (*fn)(const char *, const struct stat *, int, struct FTW *),
171     int depth, struct Save *last, struct Var *vp)
172 {
173 	struct stat statb;
174 	char *p, *tmp;
175 	int type;
176 	char *comp;
177 	struct dirent *dir;
178 	char *q;
179 	int rc = 0;
180 	int val = -1;
181 	int cdval = -1;
182 	int oldbase;
183 	int skip;
184 	struct Save this;
185 	size_t base_comp, base_component, base_this_comp, base_last_comp;
186 	size_t base_fullpath, base_tmppath;
187 
188 	this.last = last;
189 	this.fd = 0;
190 	if ((vp->curflags & FTW_CHDIR) && last)
191 		comp = last->comp;
192 	else
193 		comp = vp->tmppath;
194 
195 	if (vp->savedstatf == NULL)
196 		vp->savedstatf = vp->statf;
197 
198 	if ((vp->walklevel++ == 0) && (vp->curflags & FTW_HOPTION)) {
199 		if (((vp->curflags & FTW_CHDIR) == 0) && (depth >= 2)) {
200 			vp->statf = nocdstat;
201 		} else {
202 			vp->statf = cdstat;
203 		}
204 	} else {
205 		vp->statf = vp->savedstatf;
206 	}
207 
208 	/*
209 	 * Determine the type of the component.
210 	 *
211 	 * Note that if the component is a trigger mount, this
212 	 * will cause it to load.
213 	 */
214 	if ((*vp->statf)(comp, &statb, last, _AT_TRIGGER) >= 0) {
215 		if ((statb.st_mode & S_IFMT) == S_IFDIR) {
216 			type = FTW_D;
217 			if (depth <= 1)
218 				(void) oldclose(last);
219 			if ((this.fd = (*vp->opendirf)(comp)) == 0) {
220 				if (errno == EMFILE && oldclose(last) &&
221 				    (this.fd = (*vp->opendirf)(comp)) != 0) {
222 					/*
223 					 * If opendirf fails because there
224 					 * are OPEN_MAX fd in the calling
225 					 * process, and we close the oldest
226 					 * fd, and another opendirf doesn't
227 					 * fail, depth is set to 1.
228 					 */
229 					depth = 1;
230 				} else {
231 					type = FTW_DNR;
232 					goto fail;
233 				}
234 			}
235 		} else if ((statb.st_mode & S_IFMT) == S_IFLNK) {
236 			type = FTW_SL;
237 		} else {
238 			type = FTW_F;
239 		}
240 	} else if ((vp->curflags & FTW_ANYERR) && errno != ENOENT) {
241 		/*
242 		 * If FTW_ANYERR is specified, then a stat error
243 		 * other than ENOENT automatically results in
244 		 * failure.  This allows the callback function
245 		 * to properly handle ENAMETOOLONG and ELOOP and
246 		 * things of that nature, that would be masked
247 		 * by calling lstat before failing.
248 		 */
249 		type = FTW_NS;
250 		goto fail;
251 	} else {
252 		/*
253 		 * Statf has failed. If stat was used instead of lstat,
254 		 * try using lstat. If lstat doesn't fail, "comp"
255 		 * must be a symbolic link pointing to a non-existent
256 		 * file. Such a symbolic link should be ignored.
257 		 * Also check the file type, if possible, for symbolic
258 		 * link.
259 		 */
260 		if (((vp->statf == cdstat) &&
261 		    (cdlstat(comp, &statb, last, 0) >= 0) &&
262 		    ((statb.st_mode & S_IFMT) == S_IFLNK)) ||
263 		    ((vp->statf == nocdstat) &&
264 		    (nocdlstat(comp, &statb, last, 0) >= 0) &&
265 		    ((statb.st_mode & S_IFMT) == S_IFLNK))) {
266 
267 			/*
268 			 * Ignore bad symbolic link, let "fn"
269 			 * report it.
270 			 */
271 
272 			errno = ENOENT;
273 			type = FTW_SLN;
274 		} else {
275 			type = FTW_NS;
276 	fail:
277 			/*
278 			 * if FTW_ANYERR is set in flags, we call
279 			 * the user function with FTW_NS set, regardless
280 			 * of the reason stat failed.
281 			 */
282 			if (!(vp->curflags & FTW_ANYERR))
283 				if (errno != EACCES)
284 					return (-1);
285 		}
286 	}
287 
288 	/*
289 	 * If the walk is not supposed to cross a mount point,
290 	 * and it did, get ready to return.
291 	 */
292 	if ((vp->curflags & FTW_MOUNT) && type != FTW_NS &&
293 	    statb.st_dev != vp->cur_mount)
294 		goto quit;
295 	vp->state.quit = 0;
296 
297 	/*
298 	 * If current component is not a directory, call user
299 	 * specified function and get ready to return.
300 	 */
301 	if (type != FTW_D || (vp->curflags & FTW_DEPTH) == 0)
302 		rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
303 	if (rc > 0)
304 		val = rc;
305 	skip = (vp->state.quit & FTW_SKD);
306 	if (rc != 0 || type != FTW_D || (vp->state.quit & FTW_PRUNE))
307 		goto quit;
308 
309 	if (vp->tmppath[0] != '\0' && component[-1] != '/')
310 		*component++ = '/';
311 	*component = 0;
312 	if (vp->curflags & FTW_CHDIR) {
313 		struct stat statb2;
314 
315 		/*
316 		 * Security check (there is a window between
317 		 * (*vp->statf)() and opendir() above).
318 		 */
319 		if ((vp->curflags & FTW_PHYS) &&
320 		    (fstat(this.fd->dd_fd, &statb2) < 0 ||
321 		    statb2.st_ino != statb.st_ino ||
322 		    statb2.st_dev != statb.st_dev)) {
323 			errno = EAGAIN;
324 			rc = -1;
325 			goto quit;
326 		}
327 
328 		if ((cdval = fchdir(this.fd->dd_fd)) >= 0) {
329 			this.comp = component;
330 		} else {
331 			type = FTW_DNR;
332 			rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
333 			goto quit;
334 		}
335 	}
336 
337 	/*
338 	 * If the walk has followed a symbolic link (FTW_PHYS is not set),
339 	 * traverse the walk back to make sure there is not a loop.
340 	 * The find utility (FTW_NOLOOP is set) detects infinite loops
341 	 * in both symbolic and hard linked directories.
342 	 */
343 	if ((vp->curflags & FTW_NOLOOP) ||
344 	    ((vp->curflags & FTW_PHYS) == 0)) {
345 		struct Save *sp = last;
346 		while (sp) {
347 			/*
348 			 * If the same node has already been visited, there
349 			 * is a loop. Get ready to return.
350 			 */
351 			if (sp->dev == statb.st_dev &&
352 			    sp->inode == statb.st_ino) {
353 				if (vp->curflags & FTW_NOLOOP) {
354 					/* private interface for find util */
355 					type = FTW_DL;
356 					goto fail;
357 				}
358 				goto quit;
359 			}
360 			sp = sp->last;
361 		}
362 	}
363 	this.dev = statb.st_dev;
364 	this.inode = statb.st_ino;
365 	oldbase = vp->state.base;
366 	vp->state.base = (int)(component - vp->tmppath);
367 	while ((dir = readdir(this.fd)) != NULL) {
368 		if (dir->d_ino == 0)
369 			continue;
370 		q = dir->d_name;
371 		if (*q == '.') {
372 			if (q[1] == 0)
373 				continue;
374 			else if (q[1] == '.' && q[2] == 0)
375 				continue;
376 		}
377 		if (last != NULL && last->comp != NULL) {
378 			base_last_comp = last->comp - vp->home;
379 		}
380 		base_comp = comp - vp->home;
381 		base_component = component - vp->home;
382 		if ((strlen(q) + strlen(vp->home) + 1) > vp->len) {
383 			/*
384 			 * When the space needed for vp->home has
385 			 * exceeded the amount of space that has
386 			 * been allocated, realloc() more space
387 			 * and adjust pointers to point to the
388 			 * (possibly moved) new block for vp->home
389 			 */
390 			base_this_comp = this.comp - vp->home;
391 			base_fullpath = vp->fullpath - vp->home;
392 			base_tmppath = vp->tmppath - vp->home;
393 			vp->len *= 2;
394 			tmp = (char *)realloc(vp->home, vp->len);
395 			if (tmp == NULL) {
396 				rc = -1;
397 				goto quit;
398 			}
399 			vp->home = tmp;
400 			comp = vp->home + base_comp;
401 			component = vp->home + base_component;
402 			this.comp = vp->home + base_this_comp;
403 			vp->fullpath = vp->home + base_fullpath;
404 			vp->tmppath = vp->home + base_tmppath;
405 			if (last != NULL && last->comp != NULL) {
406 				last->comp = vp->home + base_last_comp;
407 			}
408 		}
409 		p = component;
410 		while (*q != '\0')
411 			*p++ = *q++;
412 		*p = '\0';
413 		vp->state.level++;
414 
415 		/* Call walk() recursively.  */
416 		rc = walk(p, fn, depth-1, &this, vp);
417 		if (last != NULL && last->comp != NULL) {
418 			last->comp = vp->home + base_last_comp;
419 		}
420 		comp = vp->home + base_comp;
421 		component = vp->home + base_component;
422 		vp->state.level--;
423 		if (this.fd == 0) {
424 			*component = 0;
425 			if (vp->curflags & FTW_CHDIR) {
426 				this.fd = opendir(".");
427 			} else {
428 				this.fd = (*vp->opendirf)(comp);
429 			}
430 			if (this.fd == 0) {
431 				rc = -1;
432 				goto quit;
433 			}
434 			seekdir(this.fd, this.here);
435 		}
436 		if (rc != 0) {
437 			if (errno == ENOENT) {
438 				(void) fprintf(stderr, "cannot open %s: %s\n",
439 				    vp->tmppath, strerror(errno));
440 				val = rc;
441 				continue;
442 			}
443 			goto quit;	/* this seems extreme */
444 		}
445 	}
446 	vp->state.base = oldbase;
447 	*--component = 0;
448 	type = FTW_DP;
449 	if ((vp->tmppath[0] != '\0') && (vp->curflags & FTW_DEPTH) && !skip)
450 		rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
451 quit:
452 	if (cdval >= 0 && last) {
453 		/* try to change back to previous directory */
454 		if (last->fd != NULL) {
455 			if (fchdir(last->fd->dd_fd) < 0) {
456 				rc = -1;
457 			}
458 		} else {
459 			if ((cdval = chdir("..")) >= 0) {
460 				if ((*vp->statf)(".", &statb, last, 0) < 0 ||
461 				    statb.st_ino != last->inode ||
462 				    statb.st_dev != last->dev)
463 					cdval = -1;
464 			}
465 			*comp = 0;
466 			if (cdval < 0) {
467 				if (chdir(vp->fullpath) < 0) {
468 					rc = -1;
469 				} else {
470 					/* Security check */
471 					if ((vp->curflags & FTW_PHYS) &&
472 					    ((*vp->statf)(".", &statb,
473 					    last, 0) < 0 ||
474 					    statb.st_ino != last->inode ||
475 					    statb.st_dev != last->dev)) {
476 						errno = EAGAIN;
477 						rc = -1;
478 					}
479 				}
480 			}
481 		}
482 	}
483 
484 	if (this.fd)
485 		(void) closedir(this.fd);
486 	if (val > rc)
487 		return (val);
488 	else
489 		return (rc);
490 }
491 
492 int
493 nftw(const char *path,
494     int (*fn)(const char *, const struct stat *, int, struct FTW *),
495     int depth, int flags)
496 {
497 	struct Var var;
498 	struct stat statb;
499 	int rc = -1;
500 	char *dp;
501 	char *base;
502 	char *endhome;
503 	const char *savepath = path;
504 	int save_errno;
505 
506 	var.walklevel = 0;
507 	var.len = 2*(PATH_MAX+1);
508 	var.home = (char *)malloc(var.len);
509 	if (var.home == NULL)
510 		return (-1);
511 
512 	var.home[0] = 0;
513 
514 	/*
515 	 * If the walk is going to change directory before
516 	 * reading it, save current working directory.
517 	 */
518 	if (flags & FTW_CHDIR) {
519 		if (getcwd(var.home, PATH_MAX+1) == 0) {
520 			free(var.home);
521 			return (-1);
522 		}
523 	}
524 	endhome = dp = var.home + strlen(var.home);
525 	if (*path == '/')
526 		var.fullpath = dp;
527 	else {
528 		*dp++ = '/';
529 		var.fullpath = var.home;
530 	}
531 	var.tmppath =  dp;
532 	base = dp-1;
533 	while (*path) {
534 		*dp = *path;
535 		if (*dp == '/')
536 			base = dp;
537 		dp++, path++;
538 	}
539 	*dp = 0;
540 	var.state.base = (int)(base + 1 - var.tmppath);
541 	if (*path) {
542 		free(var.home);
543 		errno = ENAMETOOLONG;
544 		return (-1);
545 	}
546 	var.curflags = flags;
547 
548 	/*
549 	 * If doing chdir()'s, set var.opendirf to cdopendir.
550 	 * If not doing chdir()'s and if nftw()'s depth arg >= 2,
551 	 * set var.opendirf to nocdopendir.  In order to
552 	 * descend to arbitrary depths without doing chdir()'s, nftw()
553 	 * requires a depth arg >= 2 so that nocdopendir() can use openat()
554 	 * to traverse the directories.  So when not doing
555 	 * chdir()'s if nftw()'s depth arg <= 1, set var.opendirf to
556 	 * cdopendir.
557 	 * If doing a physical walk (not following symbolic link), set
558 	 * var.statf to cdlstat() or nocdlstat(). Otherwise, set var.statf
559 	 * to cdstat() or nocdstat().
560 	 */
561 	if (((flags & FTW_CHDIR) == 0) && (depth >= 2)) {
562 		var.opendirf = nocdopendir;
563 		if (flags & FTW_PHYS)
564 			var.statf = nocdlstat;
565 		else
566 			var.statf = nocdstat;
567 	} else {
568 		var.opendirf = cdopendir;
569 		if (flags & FTW_PHYS)
570 			var.statf = cdlstat;
571 		else
572 			var.statf = cdstat;
573 	}
574 
575 	/*
576 	 * If walk is not going to cross a mount point,
577 	 * save the current mount point.
578 	 */
579 	if (flags & FTW_MOUNT) {
580 		if ((*var.statf)(savepath, &statb, NULL, 0) >= 0)
581 			var.cur_mount = statb.st_dev;
582 		else
583 			goto done;
584 	}
585 	var.state.level = 0;
586 
587 	/*
588 	 * Call walk() which does most of the work.
589 	 * walk() uses errno in a rather obtuse way
590 	 * so we shield any incoming errno.
591 	 */
592 	save_errno = errno;
593 	errno = 0;
594 	var.savedstatf = NULL;
595 	rc = walk(dp, fn, depth, (struct Save *)0, &var);
596 	if (errno == 0)
597 		errno = save_errno;
598 done:
599 	*endhome = 0;
600 	if (flags & FTW_CHDIR)
601 		(void) chdir(var.home);
602 	free(var.home);
603 	return (rc);
604 }
605 
606 /*
607  * Get stat info on path when FTW_CHDIR is set.
608  */
609 static int
610 cdstat(const char *path, struct stat *statp, struct Save *lp __unused,
611     int flags)
612 {
613 	return (fstatat(AT_FDCWD, path, statp, flags));
614 }
615 
616 /*
617  * Get lstat info on path when FTW_CHDIR is set.
618  */
619 static int
620 cdlstat(const char *path, struct stat *statp, struct Save *lp __unused,
621     int flags)
622 {
623 	return (fstatat(AT_FDCWD, path, statp,
624 	    flags | AT_SYMLINK_NOFOLLOW));
625 }
626 
627 /*
628  * Get stat info on path when FTW_CHDIR is not set.
629  */
630 static int
631 nocdstat(const char *path, struct stat *statp, struct Save *lp, int flags)
632 {
633 	int		fd;
634 	const char	*basepath;
635 
636 	if (lp && lp->fd) {
637 		/* get basename of path */
638 		basepath = get_unrooted(path);
639 
640 		fd = lp->fd->dd_fd;
641 	} else {
642 		basepath = path;
643 
644 		fd = AT_FDCWD;
645 	}
646 
647 	return (fstatat(fd, basepath, statp, flags));
648 }
649 
650 /*
651  * Get lstat info on path when FTW_CHDIR is not set.
652  */
653 static int
654 nocdlstat(const char *path, struct stat *statp, struct Save *lp, int flags)
655 {
656 	int		fd;
657 	const char	*basepath;
658 
659 	if (lp && lp->fd) {
660 		/* get basename of path */
661 		basepath = get_unrooted(path);
662 
663 		fd = lp->fd->dd_fd;
664 	} else {
665 		basepath = path;
666 
667 		fd = AT_FDCWD;
668 	}
669 
670 	return (fstatat(fd, basepath, statp, flags | AT_SYMLINK_NOFOLLOW));
671 }
672 
673 /*
674  * Open path directory when FTW_CHDIR is set.
675  *
676  */
677 static DIR *
678 cdopendir(const char *path)
679 {
680 	return (opendir(path));
681 }
682 
683 /*
684  * Open path directory when FTW_CHDIR is not set.
685  */
686 static DIR *
687 nocdopendir(const char *path)
688 {
689 	int fd, cfd;
690 	DIR *fdd;
691 	char *dirp, *token, *ptr;
692 
693 	if (((fdd = opendir(path)) == NULL) && (errno == ENAMETOOLONG)) {
694 		if ((dirp = strdup(path)) == NULL) {
695 			errno = ENAMETOOLONG;
696 			return (NULL);
697 		}
698 		if ((token = strtok_r(dirp, "/", &ptr)) != NULL) {
699 			if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) {
700 				(void) free(dirp);
701 				errno = ENAMETOOLONG;
702 				return (NULL);
703 			}
704 			while ((token = strtok_r(NULL, "/", &ptr)) != NULL) {
705 				if ((cfd = openat(fd, token, O_RDONLY)) < 0) {
706 					(void) close(fd);
707 					(void) free(dirp);
708 					errno = ENAMETOOLONG;
709 					return (NULL);
710 				}
711 				(void) close(fd);
712 				fd = cfd;
713 			}
714 			(void) free(dirp);
715 			return (fdopendir(fd));
716 		}
717 		(void) free(dirp);
718 		errno = ENAMETOOLONG;
719 	}
720 	return (fdd);
721 }
722 
723 /*
724  * return pointer basename of path, which may contain trailing slashes
725  *
726  * We do this when we do not chdir() on the input.
727  */
728 static const char *
729 get_unrooted(const char *path)
730 {
731 	const char *ptr;
732 
733 	if (!path || !*path)
734 		return (NULL);
735 
736 	ptr = path + strlen(path);
737 	/* find last char in path before any trailing slashes */
738 	while (ptr != path && *--ptr == '/')
739 		;
740 
741 	if (ptr == path)	/* all slashes */
742 		return (ptr);
743 
744 	while (ptr != path)
745 		if (*--ptr == '/')
746 			return (++ptr);
747 
748 	return (ptr);
749 }
750 
751 /*
752  * close the oldest directory.  It saves the seek offset.
753  * return value is 0 unless it was unable to close any descriptor
754  */
755 
756 static int
757 oldclose(struct Save *sp)
758 {
759 	struct Save *spnext;
760 	while (sp) {
761 		spnext = sp->last;
762 		if (spnext == 0 || spnext->fd == 0)
763 			break;
764 		sp = spnext;
765 	}
766 	if (sp == 0 || sp->fd == 0)
767 		return (0);
768 	sp->here = telldir(sp->fd);
769 	(void) closedir(sp->fd);
770 	sp->fd = 0;
771 	return (1);
772 }
773