xref: /onnv-gate/usr/src/lib/libc/port/gen/nftw.c (revision 5302:eec6aeacde6e)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
53523Scf46844  * Common Development and Distribution License (the "License").
63523Scf46844  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
211219Sraf 
220Sstevel@tonic-gate /*
233523Scf46844  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
240Sstevel@tonic-gate  * Use is subject to license terms.
250Sstevel@tonic-gate  */
260Sstevel@tonic-gate 
270Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
280Sstevel@tonic-gate 
290Sstevel@tonic-gate /*	Copyright (c) 1988 AT&T	*/
300Sstevel@tonic-gate /*	  All Rights Reserved  	*/
310Sstevel@tonic-gate 
320Sstevel@tonic-gate 
330Sstevel@tonic-gate /*
340Sstevel@tonic-gate  *	nftw - new file tree walk
350Sstevel@tonic-gate  *
360Sstevel@tonic-gate  *	int nftw(char *path, int (*fn)(), int depth, int flags);
370Sstevel@tonic-gate  *
380Sstevel@tonic-gate  *	Derived from System V ftw() by David Korn
390Sstevel@tonic-gate  *
400Sstevel@tonic-gate  *	nftw visits each file and directory in the tree starting at
410Sstevel@tonic-gate  *	path. It uses the generic directory reading library so it works
420Sstevel@tonic-gate  *	for any file system type.  The flags field is used to specify:
433523Scf46844  *		FTW_PHYS  Physical walk, does not follow symbolic links
440Sstevel@tonic-gate  *			  Otherwise, nftw will follow links but will not
450Sstevel@tonic-gate  *			  walk down any path the crosses itself.
460Sstevel@tonic-gate  *		FTW_MOUNT The walk will not cross a mount point.
470Sstevel@tonic-gate  *		FTW_DEPTH All subdirectories will be visited before the
480Sstevel@tonic-gate  *			  directory itself.
490Sstevel@tonic-gate  *		FTW_CHDIR The walk will change to each directory before
500Sstevel@tonic-gate  *			  reading it.  This is faster but core dumps
510Sstevel@tonic-gate  *			  may not get generated.
520Sstevel@tonic-gate  *
530Sstevel@tonic-gate  *	The following flags are private, and are used by the find
540Sstevel@tonic-gate  *	utility:
550Sstevel@tonic-gate  *		FTW_ANYERR Call the callback function and return
560Sstevel@tonic-gate  *			   FTW_NS on any stat failure, not just
570Sstevel@tonic-gate  *			   lack of permission.
580Sstevel@tonic-gate  *		FTW_HOPTION Use stat the first time the walk
590Sstevel@tonic-gate  *			    function is called, regardless of
600Sstevel@tonic-gate  *			    whether or not FTW_PHYS is specified.
613523Scf46844  *		FTW_NOLOOP Allow find utility to detect infinite loops created
623523Scf46844  *			   by both symbolic and hard linked directories.
630Sstevel@tonic-gate  *
640Sstevel@tonic-gate  *	fn is called with four arguments at each file and directory.
650Sstevel@tonic-gate  *	The first argument is the pathname of the object, the second
660Sstevel@tonic-gate  *	is a pointer to the stat buffer and the third is an integer
670Sstevel@tonic-gate  *	giving additional information as follows:
680Sstevel@tonic-gate  *
690Sstevel@tonic-gate  *		FTW_F	The object is a file.
700Sstevel@tonic-gate  *		FTW_D	The object is a directory.
710Sstevel@tonic-gate  *		FTW_DP	The object is a directory and subdirectories
720Sstevel@tonic-gate  *			have been visited.
730Sstevel@tonic-gate  *		FTW_SL	The object is a symbolic link.
740Sstevel@tonic-gate  *		FTW_SLN The object is a symbolic link pointing at a
750Sstevel@tonic-gate  *		        non-existing file.
760Sstevel@tonic-gate  *		FTW_DNR	The object is a directory that cannot be read.
770Sstevel@tonic-gate  *			fn will not be called for any of its descendants.
780Sstevel@tonic-gate  *		FTW_NS	Stat failed on the object because of lack of
790Sstevel@tonic-gate  *			appropriate permission. The stat buffer passed to fn
800Sstevel@tonic-gate  *			is undefined.  Stat failure for any reason is
810Sstevel@tonic-gate  *			considered an error and nftw will return -1.
823523Scf46844  *	The following value is private, and is used by the find utility:
833523Scf46844  *		FTW_DL	An infinite loop has been detected.
840Sstevel@tonic-gate  *	The fourth argument is a struct FTW* which contains the depth
850Sstevel@tonic-gate  *	and the offset into pathname to the base name.
860Sstevel@tonic-gate  *	If fn returns nonzero, nftw returns this value to its caller.
870Sstevel@tonic-gate  *
880Sstevel@tonic-gate  *	depth limits the number of open directories that ftw uses
890Sstevel@tonic-gate  *	before it starts recycling file descriptors.  In general,
903523Scf46844  *	a file descriptor is used for each level.  When FTW_CHDIR isn't set,
913523Scf46844  *	in order to descend to arbitrary depths, nftw requires 2 file
923523Scf46844  *	descriptors to be open during the call to openat(), therefore if
933523Scf46844  *	the depth argument is less than 2 nftw will not use openat(), and
943523Scf46844  *	it will fail with ENAMETOOLONG if it descends to a directory that
953523Scf46844  *	exceeds PATH_MAX.
960Sstevel@tonic-gate  *
970Sstevel@tonic-gate  */
980Sstevel@tonic-gate 
990Sstevel@tonic-gate #include <sys/feature_tests.h>
1000Sstevel@tonic-gate 
1010Sstevel@tonic-gate #if !defined(_LP64) && _FILE_OFFSET_BITS == 64
1020Sstevel@tonic-gate #pragma weak nftw64 = _nftw64
1030Sstevel@tonic-gate #define	_nftw		_nftw64
1040Sstevel@tonic-gate #define	fstat64		_fstat64
1053523Scf46844 #define	fstatat64	_fstatat64
1060Sstevel@tonic-gate #define	lstat64		_lstat64
1073523Scf46844 #define	openat64	_openat64
1080Sstevel@tonic-gate #define	readdir64	_readdir64
1090Sstevel@tonic-gate #define	stat64		_stat64
1100Sstevel@tonic-gate #else
1110Sstevel@tonic-gate #pragma weak nftw = _nftw
1120Sstevel@tonic-gate #define	fstat		_fstat
1133523Scf46844 #define	fstatat		_fstatat
1140Sstevel@tonic-gate #define	lstat		_lstat
1153523Scf46844 #define	openat		_openat
1160Sstevel@tonic-gate #define	readdir		_readdir
1170Sstevel@tonic-gate #define	stat		_stat
1180Sstevel@tonic-gate #endif /* !_LP64 && _FILE_OFFSET_BITS == 64 */
1190Sstevel@tonic-gate 
1200Sstevel@tonic-gate #define	chdir		_chdir
1213523Scf46844 #define	close		_close
1220Sstevel@tonic-gate #define	closedir	_closedir
1230Sstevel@tonic-gate #define	fchdir		_fchdir
1243523Scf46844 #define	fdopendir	_fdopendir
1250Sstevel@tonic-gate #define	fprintf		_fprintf
1260Sstevel@tonic-gate #define	getcwd		_getcwd
1270Sstevel@tonic-gate #define	opendir		_opendir
1280Sstevel@tonic-gate #define	seekdir		_seekdir
1293523Scf46844 #define	strdup		_strdup
1301219Sraf #define	strerror	_strerror
1313523Scf46844 #define	strtok_r	_strtok_r
1320Sstevel@tonic-gate #define	telldir		_telldir
1330Sstevel@tonic-gate 
1340Sstevel@tonic-gate #include "lint.h"
1350Sstevel@tonic-gate #include <mtlib.h>
1360Sstevel@tonic-gate #include <sys/types.h>
1370Sstevel@tonic-gate #include <sys/stat.h>
1380Sstevel@tonic-gate #include <dirent.h>
1390Sstevel@tonic-gate #include <errno.h>
1400Sstevel@tonic-gate #include <limits.h>
1410Sstevel@tonic-gate #include <ftw.h>
1420Sstevel@tonic-gate #include <stdlib.h>
1430Sstevel@tonic-gate #include <string.h>
1440Sstevel@tonic-gate #include <unistd.h>
1450Sstevel@tonic-gate #include <thread.h>
1460Sstevel@tonic-gate #include <synch.h>
1470Sstevel@tonic-gate #include <stdio.h>
1483523Scf46844 #include <strings.h>
1493523Scf46844 #include <fcntl.h>
1500Sstevel@tonic-gate 
1510Sstevel@tonic-gate #ifndef PATH_MAX
1520Sstevel@tonic-gate #define	PATH_MAX	1023
1530Sstevel@tonic-gate #endif
1540Sstevel@tonic-gate 
1550Sstevel@tonic-gate /*
1560Sstevel@tonic-gate  * Local variables (used to be static local).
1570Sstevel@tonic-gate  * Putting them into a structure that is passed
1580Sstevel@tonic-gate  * around makes nftw() MT-safe with no locking required.
1590Sstevel@tonic-gate  */
1600Sstevel@tonic-gate struct Save {
1610Sstevel@tonic-gate 	struct Save *last;
1620Sstevel@tonic-gate 	DIR	*fd;
1630Sstevel@tonic-gate 	char	*comp;
1640Sstevel@tonic-gate 	long	here;
1650Sstevel@tonic-gate 	dev_t	dev;
1660Sstevel@tonic-gate 	ino_t	inode;
1670Sstevel@tonic-gate };
1680Sstevel@tonic-gate 
1693523Scf46844 struct Var {
1703523Scf46844 	char	*home;
1713523Scf46844 	size_t	len;
1723523Scf46844 	char	*fullpath;
1733523Scf46844 	char	*tmppath;
1743523Scf46844 	int	curflags;
1753523Scf46844 	dev_t	cur_mount;
1763523Scf46844 	struct FTW state;
1773523Scf46844 	int	walklevel;
178*5302Sth199096 	int	(*statf)(const char *, struct stat *, struct Save *, int flags);
179*5302Sth199096 	int	(*savedstatf)(const char *, struct stat *, struct Save *,
180*5302Sth199096 	    int flags);
1813523Scf46844 	DIR	*(*opendirf)(const char *);
1823523Scf46844 };
1833523Scf46844 
1840Sstevel@tonic-gate static int oldclose(struct Save *);
185*5302Sth199096 static int cdlstat(const char *, struct stat *, struct Save *, int flags);
186*5302Sth199096 static int cdstat(const char *, struct stat *, struct Save *, int flags);
187*5302Sth199096 static int nocdlstat(const char *, struct stat *, struct Save *, int flags);
188*5302Sth199096 static int nocdstat(const char *, struct stat *, struct Save *, int flags);
1893523Scf46844 static DIR *cdopendir(const char *);
1903523Scf46844 static DIR *nocdopendir(const char *);
1913523Scf46844 static const char *get_unrooted(const char *);
1920Sstevel@tonic-gate 
1930Sstevel@tonic-gate /*
1940Sstevel@tonic-gate  * This is the recursive walker.
1950Sstevel@tonic-gate  */
1960Sstevel@tonic-gate static int
1970Sstevel@tonic-gate walk(char *component,
1980Sstevel@tonic-gate     int (*fn)(const char *, const struct stat *, int, struct FTW *),
1990Sstevel@tonic-gate     int depth, struct Save *last, struct Var *vp)
2000Sstevel@tonic-gate {
2010Sstevel@tonic-gate 	struct stat statb;
2023523Scf46844 	char *p, *tmp;
2030Sstevel@tonic-gate 	int type;
2040Sstevel@tonic-gate 	char *comp;
2050Sstevel@tonic-gate 	struct dirent *dir;
2060Sstevel@tonic-gate 	char *q;
2070Sstevel@tonic-gate 	int rc = 0;
2080Sstevel@tonic-gate 	int val = -1;
2090Sstevel@tonic-gate 	int cdval = -1;
2100Sstevel@tonic-gate 	int oldbase;
2110Sstevel@tonic-gate 	int skip;
2120Sstevel@tonic-gate 	struct Save this;
2133523Scf46844 	size_t base_comp, base_component, base_this_comp, base_last_comp;
2143523Scf46844 	size_t base_fullpath, base_tmppath;
2150Sstevel@tonic-gate 
2160Sstevel@tonic-gate 	this.last = last;
2170Sstevel@tonic-gate 	this.fd = 0;
2180Sstevel@tonic-gate 	if ((vp->curflags & FTW_CHDIR) && last)
2190Sstevel@tonic-gate 		comp = last->comp;
2200Sstevel@tonic-gate 	else
2210Sstevel@tonic-gate 		comp = vp->tmppath;
2220Sstevel@tonic-gate 
2230Sstevel@tonic-gate 	if (vp->savedstatf == NULL)
2240Sstevel@tonic-gate 		vp->savedstatf = vp->statf;
2250Sstevel@tonic-gate 
2263523Scf46844 	if ((vp->walklevel++ == 0) && (vp->curflags & FTW_HOPTION)) {
2273523Scf46844 		if (((vp->curflags & FTW_CHDIR) == 0) && (depth >= 2)) {
2283523Scf46844 			vp->statf = nocdstat;
2293523Scf46844 		} else {
2303523Scf46844 			vp->statf = cdstat;
2313523Scf46844 		}
2323523Scf46844 	} else {
2330Sstevel@tonic-gate 		vp->statf = vp->savedstatf;
2343523Scf46844 	}
2350Sstevel@tonic-gate 
2360Sstevel@tonic-gate 	/*
2370Sstevel@tonic-gate 	 * Determine the type of the component.
238*5302Sth199096 	 *
239*5302Sth199096 	 * Note that if the component is a trigger mount, this
240*5302Sth199096 	 * will cause it to load.
2410Sstevel@tonic-gate 	 */
242*5302Sth199096 	if ((*vp->statf)(comp, &statb, last, _AT_TRIGGER) >= 0) {
2430Sstevel@tonic-gate 		if ((statb.st_mode & S_IFMT) == S_IFDIR) {
2440Sstevel@tonic-gate 			type = FTW_D;
2450Sstevel@tonic-gate 			if (depth <= 1)
2460Sstevel@tonic-gate 				(void) oldclose(last);
2473523Scf46844 			if ((this.fd = (*vp->opendirf)(comp)) == 0) {
2480Sstevel@tonic-gate 				if (errno == EMFILE && oldclose(last) &&
2493523Scf46844 				    (this.fd = (*vp->opendirf)(comp)) != 0) {
2503523Scf46844 					/*
2513523Scf46844 					 * If opendirf fails because there
2523523Scf46844 					 * are OPEN_MAX fd in the calling
2533523Scf46844 					 * process, and we close the oldest
2543523Scf46844 					 * fd, and another opendirf doesn't
2553523Scf46844 					 * fail, depth is set to 1.
2563523Scf46844 					 */
2570Sstevel@tonic-gate 					depth = 1;
2580Sstevel@tonic-gate 				} else {
2590Sstevel@tonic-gate 					type = FTW_DNR;
2600Sstevel@tonic-gate 					goto fail;
2610Sstevel@tonic-gate 				}
2620Sstevel@tonic-gate 			}
2630Sstevel@tonic-gate 		} else if ((statb.st_mode & S_IFMT) == S_IFLNK) {
2640Sstevel@tonic-gate 			type = FTW_SL;
2650Sstevel@tonic-gate 		} else {
2660Sstevel@tonic-gate 			type = FTW_F;
2670Sstevel@tonic-gate 		}
2680Sstevel@tonic-gate 	} else if ((vp->curflags & FTW_ANYERR) && errno != ENOENT) {
2690Sstevel@tonic-gate 		/*
2700Sstevel@tonic-gate 		 * If FTW_ANYERR is specified, then a stat error
2710Sstevel@tonic-gate 		 * other than ENOENT automatically results in
2720Sstevel@tonic-gate 		 * failure.  This allows the callback function
2730Sstevel@tonic-gate 		 * to properly handle ENAMETOOLONG and ELOOP and
2740Sstevel@tonic-gate 		 * things of that nature, that would be masked
2750Sstevel@tonic-gate 		 * by calling lstat before failing.
2760Sstevel@tonic-gate 		 */
2770Sstevel@tonic-gate 		type = FTW_NS;
2780Sstevel@tonic-gate 		goto fail;
2790Sstevel@tonic-gate 	} else {
2800Sstevel@tonic-gate 		/*
2810Sstevel@tonic-gate 		 * Statf has failed. If stat was used instead of lstat,
2820Sstevel@tonic-gate 		 * try using lstat. If lstat doesn't fail, "comp"
2830Sstevel@tonic-gate 		 * must be a symbolic link pointing to a non-existent
2840Sstevel@tonic-gate 		 * file. Such a symbolic link should be ignored.
2850Sstevel@tonic-gate 		 * Also check the file type, if possible, for symbolic
2860Sstevel@tonic-gate 		 * link.
2870Sstevel@tonic-gate 		 */
2883523Scf46844 		if (((vp->statf == cdstat) &&
289*5302Sth199096 		    (cdlstat(comp, &statb, last, 0) >= 0) &&
2903523Scf46844 		    ((statb.st_mode & S_IFMT) == S_IFLNK)) ||
2913523Scf46844 		    ((vp->statf == nocdstat) &&
292*5302Sth199096 		    (nocdlstat(comp, &statb, last, 0) >= 0) &&
2933523Scf46844 		    ((statb.st_mode & S_IFMT) == S_IFLNK))) {
2940Sstevel@tonic-gate 
2950Sstevel@tonic-gate 			/*
2960Sstevel@tonic-gate 			 * Ignore bad symbolic link, let "fn"
2970Sstevel@tonic-gate 			 * report it.
2980Sstevel@tonic-gate 			 */
2990Sstevel@tonic-gate 
3000Sstevel@tonic-gate 			errno = ENOENT;
3010Sstevel@tonic-gate 			type = FTW_SLN;
3020Sstevel@tonic-gate 		} else {
3030Sstevel@tonic-gate 			type = FTW_NS;
3040Sstevel@tonic-gate 	fail:
3050Sstevel@tonic-gate 			/*
3060Sstevel@tonic-gate 			 * if FTW_ANYERR is set in flags, we call
3070Sstevel@tonic-gate 			 * the user function with FTW_NS set, regardless
3080Sstevel@tonic-gate 			 * of the reason stat failed.
3090Sstevel@tonic-gate 			 */
3100Sstevel@tonic-gate 			if (!(vp->curflags & FTW_ANYERR))
3110Sstevel@tonic-gate 				if (errno != EACCES)
3120Sstevel@tonic-gate 					return (-1);
3130Sstevel@tonic-gate 		}
3140Sstevel@tonic-gate 	}
3150Sstevel@tonic-gate 
3160Sstevel@tonic-gate 	/*
3170Sstevel@tonic-gate 	 * If the walk is not supposed to cross a mount point,
3180Sstevel@tonic-gate 	 * and it did, get ready to return.
3190Sstevel@tonic-gate 	 */
3200Sstevel@tonic-gate 	if ((vp->curflags & FTW_MOUNT) && type != FTW_NS &&
3210Sstevel@tonic-gate 	    statb.st_dev != vp->cur_mount)
3220Sstevel@tonic-gate 		goto quit;
3230Sstevel@tonic-gate 	vp->state.quit = 0;
3240Sstevel@tonic-gate 
3250Sstevel@tonic-gate 	/*
3260Sstevel@tonic-gate 	 * If current component is not a directory, call user
3270Sstevel@tonic-gate 	 * specified function and get ready to return.
3280Sstevel@tonic-gate 	 */
3290Sstevel@tonic-gate 	if (type != FTW_D || (vp->curflags & FTW_DEPTH) == 0)
3300Sstevel@tonic-gate 		rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
3310Sstevel@tonic-gate 	if (rc > 0)
3320Sstevel@tonic-gate 		val = rc;
3330Sstevel@tonic-gate 	skip = (vp->state.quit & FTW_SKD);
3340Sstevel@tonic-gate 	if (rc != 0 || type != FTW_D || (vp->state.quit & FTW_PRUNE))
3350Sstevel@tonic-gate 		goto quit;
3360Sstevel@tonic-gate 
3370Sstevel@tonic-gate 	if (vp->tmppath[0] != '\0' && component[-1] != '/')
3380Sstevel@tonic-gate 		*component++ = '/';
3393523Scf46844 	*component = 0;
3400Sstevel@tonic-gate 	if (vp->curflags & FTW_CHDIR) {
3410Sstevel@tonic-gate 		struct stat statb2;
3420Sstevel@tonic-gate 
3430Sstevel@tonic-gate 		/*
3440Sstevel@tonic-gate 		 * Security check (there is a window between
3450Sstevel@tonic-gate 		 * (*vp->statf)() and opendir() above).
3460Sstevel@tonic-gate 		 */
3470Sstevel@tonic-gate 		if ((vp->curflags & FTW_PHYS) &&
3480Sstevel@tonic-gate 		    (fstat(this.fd->dd_fd, &statb2) < 0 ||
3490Sstevel@tonic-gate 		    statb2.st_ino != statb.st_ino ||
3500Sstevel@tonic-gate 		    statb2.st_dev != statb.st_dev)) {
3510Sstevel@tonic-gate 			errno = EAGAIN;
3520Sstevel@tonic-gate 			rc = -1;
3530Sstevel@tonic-gate 			goto quit;
3540Sstevel@tonic-gate 		}
3550Sstevel@tonic-gate 
3560Sstevel@tonic-gate 		if ((cdval = fchdir(this.fd->dd_fd)) >= 0) {
3570Sstevel@tonic-gate 			this.comp = component;
3580Sstevel@tonic-gate 		} else {
3590Sstevel@tonic-gate 			type = FTW_DNR;
3600Sstevel@tonic-gate 			rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
3610Sstevel@tonic-gate 			goto quit;
3620Sstevel@tonic-gate 		}
3630Sstevel@tonic-gate 	}
3640Sstevel@tonic-gate 
3650Sstevel@tonic-gate 	/*
3663523Scf46844 	 * If the walk has followed a symbolic link (FTW_PHYS is not set),
3673523Scf46844 	 * traverse the walk back to make sure there is not a loop.
3683523Scf46844 	 * The find utility (FTW_NOLOOP is set) detects infinite loops
3693523Scf46844 	 * in both symbolic and hard linked directories.
3700Sstevel@tonic-gate 	 */
3713523Scf46844 	if ((vp->curflags & FTW_NOLOOP) ||
3723523Scf46844 	    ((vp->curflags & FTW_PHYS) == 0)) {
3730Sstevel@tonic-gate 		struct Save *sp = last;
3740Sstevel@tonic-gate 		while (sp) {
3750Sstevel@tonic-gate 			/*
3760Sstevel@tonic-gate 			 * If the same node has already been visited, there
3770Sstevel@tonic-gate 			 * is a loop. Get ready to return.
3780Sstevel@tonic-gate 			 */
3790Sstevel@tonic-gate 			if (sp->dev == statb.st_dev &&
3803523Scf46844 			    sp->inode == statb.st_ino) {
3813523Scf46844 				if (vp->curflags & FTW_NOLOOP) {
3823523Scf46844 					/* private interface for find util */
3833523Scf46844 					type = FTW_DL;
3843523Scf46844 					goto fail;
3853523Scf46844 				}
3860Sstevel@tonic-gate 				goto quit;
3873523Scf46844 			}
3880Sstevel@tonic-gate 			sp = sp->last;
3890Sstevel@tonic-gate 		}
3900Sstevel@tonic-gate 	}
3910Sstevel@tonic-gate 	this.dev = statb.st_dev;
3920Sstevel@tonic-gate 	this.inode = statb.st_ino;
3930Sstevel@tonic-gate 	oldbase = vp->state.base;
3940Sstevel@tonic-gate 	vp->state.base = (int)(component - vp->tmppath);
3950Sstevel@tonic-gate 	while (dir = readdir(this.fd)) {
3960Sstevel@tonic-gate 		if (dir->d_ino == 0)
3970Sstevel@tonic-gate 			continue;
3980Sstevel@tonic-gate 		q = dir->d_name;
3990Sstevel@tonic-gate 		if (*q == '.') {
4000Sstevel@tonic-gate 			if (q[1] == 0)
4010Sstevel@tonic-gate 				continue;
4020Sstevel@tonic-gate 			else if (q[1] == '.' && q[2] == 0)
4030Sstevel@tonic-gate 				continue;
4040Sstevel@tonic-gate 		}
4053523Scf46844 		if (last != NULL && last->comp != NULL) {
4063523Scf46844 			base_last_comp = last->comp - vp->home;
4073523Scf46844 		}
4083523Scf46844 		base_comp = comp - vp->home;
4093523Scf46844 		base_component = component - vp->home;
4103523Scf46844 		if ((strlen(q) + strlen(vp->home) + 1) > vp->len) {
4113523Scf46844 			/*
4123523Scf46844 			 * When the space needed for vp->home has
4133523Scf46844 			 * exceeded the amount of space that has
4143523Scf46844 			 * been allocated, realloc() more space
4153523Scf46844 			 * and adjust pointers to point to the
4163523Scf46844 			 * (possibly moved) new block for vp->home
4173523Scf46844 			 */
4183523Scf46844 			base_this_comp = this.comp - vp->home;
4193523Scf46844 			base_fullpath = vp->fullpath - vp->home;
4203523Scf46844 			base_tmppath = vp->tmppath - vp->home;
4213523Scf46844 			vp->len *= 2;
4223523Scf46844 			tmp = (char *)realloc(vp->home, vp->len);
4233523Scf46844 			if (tmp == NULL) {
4243523Scf46844 				rc = -1;
4253523Scf46844 				goto quit;
4263523Scf46844 			}
4273523Scf46844 			vp->home = tmp;
4283523Scf46844 			comp = vp->home + base_comp;
4293523Scf46844 			component = vp->home + base_component;
4303523Scf46844 			this.comp = vp->home + base_this_comp;
4313523Scf46844 			vp->fullpath = vp->home + base_fullpath;
4323523Scf46844 			vp->tmppath = vp->home + base_tmppath;
4333523Scf46844 			if (last != NULL && last->comp != NULL) {
4343523Scf46844 				last->comp = vp->home + base_last_comp;
4353523Scf46844 			}
4363523Scf46844 		}
4370Sstevel@tonic-gate 		p = component;
4383523Scf46844 		while (*q != '\0')
4390Sstevel@tonic-gate 			*p++ = *q++;
4400Sstevel@tonic-gate 		*p = '\0';
4410Sstevel@tonic-gate 		vp->state.level++;
4420Sstevel@tonic-gate 
4430Sstevel@tonic-gate 		/* Call walk() recursively.  */
4440Sstevel@tonic-gate 		rc = walk(p, fn, depth-1, &this, vp);
4453523Scf46844 		if (last != NULL && last->comp != NULL) {
4463523Scf46844 			last->comp = vp->home + base_last_comp;
4473523Scf46844 		}
4483523Scf46844 		comp = vp->home + base_comp;
4493523Scf46844 		component = vp->home + base_component;
4500Sstevel@tonic-gate 		vp->state.level--;
4510Sstevel@tonic-gate 		if (this.fd == 0) {
4520Sstevel@tonic-gate 			*component = 0;
4530Sstevel@tonic-gate 			if (vp->curflags & FTW_CHDIR) {
4540Sstevel@tonic-gate 				this.fd = opendir(".");
4550Sstevel@tonic-gate 			} else {
4563523Scf46844 				this.fd = (*vp->opendirf)(comp);
4570Sstevel@tonic-gate 			}
4580Sstevel@tonic-gate 			if (this.fd == 0) {
4590Sstevel@tonic-gate 				rc = -1;
4600Sstevel@tonic-gate 				goto quit;
4610Sstevel@tonic-gate 			}
4620Sstevel@tonic-gate 			seekdir(this.fd, this.here);
4630Sstevel@tonic-gate 		}
4640Sstevel@tonic-gate 		if (rc != 0) {
4650Sstevel@tonic-gate 			if (errno == ENOENT) {
4660Sstevel@tonic-gate 				(void) fprintf(stderr, "cannot open %s: %s\n",
4670Sstevel@tonic-gate 				    vp->tmppath, strerror(errno));
4680Sstevel@tonic-gate 				val = rc;
4690Sstevel@tonic-gate 				continue;
4700Sstevel@tonic-gate 			}
4710Sstevel@tonic-gate 			goto quit;	/* this seems extreme */
4720Sstevel@tonic-gate 		}
4730Sstevel@tonic-gate 	}
4740Sstevel@tonic-gate 	vp->state.base = oldbase;
4750Sstevel@tonic-gate 	*--component = 0;
4760Sstevel@tonic-gate 	type = FTW_DP;
4770Sstevel@tonic-gate 	if ((vp->tmppath[0] != '\0') && (vp->curflags & FTW_DEPTH) && !skip)
4780Sstevel@tonic-gate 		rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
4790Sstevel@tonic-gate quit:
4800Sstevel@tonic-gate 	if (cdval >= 0 && last) {
4810Sstevel@tonic-gate 		/* try to change back to previous directory */
4820Sstevel@tonic-gate 		if (last->fd != NULL) {
4830Sstevel@tonic-gate 			if (fchdir(last->fd->dd_fd) < 0) {
4840Sstevel@tonic-gate 				rc = -1;
4850Sstevel@tonic-gate 			}
4860Sstevel@tonic-gate 		} else {
4870Sstevel@tonic-gate 			if ((cdval = chdir("..")) >= 0) {
488*5302Sth199096 				if ((*vp->statf)(".", &statb, last, 0) < 0 ||
4890Sstevel@tonic-gate 				    statb.st_ino != last->inode ||
4900Sstevel@tonic-gate 				    statb.st_dev != last->dev)
4910Sstevel@tonic-gate 					cdval = -1;
4920Sstevel@tonic-gate 			}
4930Sstevel@tonic-gate 			*comp = 0;
4940Sstevel@tonic-gate 			if (cdval < 0) {
4950Sstevel@tonic-gate 				if (chdir(vp->fullpath) < 0) {
4960Sstevel@tonic-gate 					rc = -1;
4970Sstevel@tonic-gate 				} else {
498*5302Sth199096 					/* Security check */
499*5302Sth199096 					if ((vp->curflags & FTW_PHYS) &&
500*5302Sth199096 					    ((*vp->statf)(".", &statb,
501*5302Sth199096 					    last, 0) < 0 ||
502*5302Sth199096 					    statb.st_ino != last->inode ||
503*5302Sth199096 					    statb.st_dev != last->dev)) {
504*5302Sth199096 						errno = EAGAIN;
505*5302Sth199096 						rc = -1;
506*5302Sth199096 					}
5070Sstevel@tonic-gate 				}
5080Sstevel@tonic-gate 			}
5090Sstevel@tonic-gate 		}
5100Sstevel@tonic-gate 	}
511*5302Sth199096 
5120Sstevel@tonic-gate 	if (this.fd)
5130Sstevel@tonic-gate 		(void) closedir(this.fd);
5140Sstevel@tonic-gate 	if (val > rc)
5150Sstevel@tonic-gate 		return (val);
5160Sstevel@tonic-gate 	else
5170Sstevel@tonic-gate 		return (rc);
5180Sstevel@tonic-gate }
5190Sstevel@tonic-gate 
5200Sstevel@tonic-gate int
5210Sstevel@tonic-gate _nftw(const char *path,
5220Sstevel@tonic-gate     int (*fn)(const char *, const struct stat *, int, struct FTW *),
5230Sstevel@tonic-gate     int depth, int flags)
5240Sstevel@tonic-gate {
5250Sstevel@tonic-gate 	struct Var var;
5260Sstevel@tonic-gate 	struct stat statb;
5270Sstevel@tonic-gate 	int rc = -1;
5280Sstevel@tonic-gate 	char *dp;
5290Sstevel@tonic-gate 	char *base;
5300Sstevel@tonic-gate 	char *endhome;
5310Sstevel@tonic-gate 	const char *savepath = path;
5320Sstevel@tonic-gate 	int save_errno;
5330Sstevel@tonic-gate 
5343523Scf46844 	var.walklevel = 0;
5353523Scf46844 	var.len = 2*(PATH_MAX+1);
5363523Scf46844 	var.home = (char *)malloc(var.len);
5373523Scf46844 	if (var.home == NULL)
5383523Scf46844 		return (-1);
5393523Scf46844 
5403523Scf46844 	var.home[0] = 0;
5410Sstevel@tonic-gate 
5420Sstevel@tonic-gate 	/*
5430Sstevel@tonic-gate 	 * If the walk is going to change directory before
5443523Scf46844 	 * reading it, save current working directory.
5450Sstevel@tonic-gate 	 */
5460Sstevel@tonic-gate 	if (flags & FTW_CHDIR) {
5473523Scf46844 		if (getcwd(var.home, PATH_MAX+1) == 0) {
5483523Scf46844 			free(var.home);
5490Sstevel@tonic-gate 			return (-1);
5503523Scf46844 		}
5510Sstevel@tonic-gate 	}
5523523Scf46844 	endhome = dp = var.home + strlen(var.home);
5530Sstevel@tonic-gate 	if (*path == '/')
5540Sstevel@tonic-gate 		var.fullpath = dp;
5550Sstevel@tonic-gate 	else {
5560Sstevel@tonic-gate 		*dp++ = '/';
5573523Scf46844 		var.fullpath = var.home;
5580Sstevel@tonic-gate 	}
5590Sstevel@tonic-gate 	var.tmppath =  dp;
5600Sstevel@tonic-gate 	base = dp-1;
5613523Scf46844 	while (*path) {
5620Sstevel@tonic-gate 		*dp = *path;
5630Sstevel@tonic-gate 		if (*dp == '/')
5640Sstevel@tonic-gate 			base = dp;
5650Sstevel@tonic-gate 		dp++, path++;
5660Sstevel@tonic-gate 	}
5670Sstevel@tonic-gate 	*dp = 0;
5680Sstevel@tonic-gate 	var.state.base = (int)(base + 1 - var.tmppath);
5690Sstevel@tonic-gate 	if (*path) {
5703523Scf46844 		free(var.home);
5710Sstevel@tonic-gate 		errno = ENAMETOOLONG;
5720Sstevel@tonic-gate 		return (-1);
5730Sstevel@tonic-gate 	}
5740Sstevel@tonic-gate 	var.curflags = flags;
5750Sstevel@tonic-gate 
5760Sstevel@tonic-gate 	/*
5773523Scf46844 	 * If doing chdir()'s, set var.opendirf to cdopendir.
5783523Scf46844 	 * If not doing chdir()'s and if nftw()'s depth arg >= 2,
5793523Scf46844 	 * set var.opendirf to nocdopendir.  In order to
5803523Scf46844 	 * descend to arbitrary depths without doing chdir()'s, nftw()
5813523Scf46844 	 * requires a depth arg >= 2 so that nocdopendir() can use openat()
5823523Scf46844 	 * to traverse the directories.  So when not doing
5833523Scf46844 	 * chdir()'s if nftw()'s depth arg <= 1, set var.opendirf to
5843523Scf46844 	 * cdopendir.
5850Sstevel@tonic-gate 	 * If doing a physical walk (not following symbolic link), set
5863523Scf46844 	 * var.statf to cdlstat() or nocdlstat(). Otherwise, set var.statf
5873523Scf46844 	 * to cdstat() or nocdstat().
5880Sstevel@tonic-gate 	 */
5893523Scf46844 	if (((flags & FTW_CHDIR) == 0) && (depth >= 2)) {
5903523Scf46844 		var.opendirf = nocdopendir;
5913523Scf46844 		if (flags & FTW_PHYS)
5923523Scf46844 			var.statf = nocdlstat;
5933523Scf46844 		else
5943523Scf46844 			var.statf = nocdstat;
5953523Scf46844 	} else {
5963523Scf46844 		var.opendirf = cdopendir;
5973523Scf46844 		if (flags & FTW_PHYS)
5983523Scf46844 			var.statf = cdlstat;
5993523Scf46844 		else
6003523Scf46844 			var.statf = cdstat;
6013523Scf46844 	}
6020Sstevel@tonic-gate 
6030Sstevel@tonic-gate 	/*
6040Sstevel@tonic-gate 	 * If walk is not going to cross a mount point,
6050Sstevel@tonic-gate 	 * save the current mount point.
6060Sstevel@tonic-gate 	 */
6070Sstevel@tonic-gate 	if (flags & FTW_MOUNT) {
608*5302Sth199096 		if ((*var.statf)(savepath, &statb, NULL, 0) >= 0)
6090Sstevel@tonic-gate 			var.cur_mount = statb.st_dev;
6100Sstevel@tonic-gate 		else
6110Sstevel@tonic-gate 			goto done;
6120Sstevel@tonic-gate 	}
6130Sstevel@tonic-gate 	var.state.level = 0;
6140Sstevel@tonic-gate 
6150Sstevel@tonic-gate 	/*
6160Sstevel@tonic-gate 	 * Call walk() which does most of the work.
6170Sstevel@tonic-gate 	 * walk() uses errno in a rather obtuse way
6180Sstevel@tonic-gate 	 * so we shield any incoming errno.
6190Sstevel@tonic-gate 	 */
6200Sstevel@tonic-gate 	save_errno = errno;
6210Sstevel@tonic-gate 	errno = 0;
6220Sstevel@tonic-gate 	var.savedstatf = NULL;
6230Sstevel@tonic-gate 	rc = walk(dp, fn, depth, (struct Save *)0, &var);
6240Sstevel@tonic-gate 	if (errno == 0)
6250Sstevel@tonic-gate 		errno = save_errno;
6260Sstevel@tonic-gate done:
6270Sstevel@tonic-gate 	*endhome = 0;
6280Sstevel@tonic-gate 	if (flags & FTW_CHDIR)
6293523Scf46844 		(void) chdir(var.home);
6303523Scf46844 	free(var.home);
6310Sstevel@tonic-gate 	return (rc);
6320Sstevel@tonic-gate }
6330Sstevel@tonic-gate 
6340Sstevel@tonic-gate /*
6353523Scf46844  * Get stat info on path when FTW_CHDIR is set.
6363523Scf46844  */
6373523Scf46844 /*ARGSUSED1*/
6383523Scf46844 static int
639*5302Sth199096 cdstat(const char *path, struct stat *statp, struct Save *lp, int flags)
640*5302Sth199096 {
641*5302Sth199096 	return (fstatat(AT_FDCWD, path, statp, flags));
6423523Scf46844 }
6433523Scf46844 
6443523Scf46844 /*
6453523Scf46844  * Get lstat info on path when FTW_CHDIR is set.
6463523Scf46844  */
6473523Scf46844 /*ARGSUSED1*/
6483523Scf46844 static int
649*5302Sth199096 cdlstat(const char *path, struct stat *statp, struct Save *lp, int flags)
6503523Scf46844 {
651*5302Sth199096 	return (fstatat(AT_FDCWD, path, statp,
652*5302Sth199096 	    flags | AT_SYMLINK_NOFOLLOW));
6533523Scf46844 }
6543523Scf46844 
6553523Scf46844 /*
6563523Scf46844  * Get stat info on path when FTW_CHDIR is not set.
6573523Scf46844  */
6583523Scf46844 static int
659*5302Sth199096 nocdstat(const char *path, struct stat *statp, struct Save *lp, int flags)
6603523Scf46844 {
661*5302Sth199096 	int		fd;
662*5302Sth199096 	const char	*basepath;
6633523Scf46844 
6643523Scf46844 	if (lp && lp->fd) {
6653523Scf46844 		/* get basename of path */
666*5302Sth199096 		basepath = get_unrooted(path);
667*5302Sth199096 
668*5302Sth199096 		fd = lp->fd->dd_fd;
6693523Scf46844 	} else {
670*5302Sth199096 		basepath = path;
671*5302Sth199096 
672*5302Sth199096 		fd = AT_FDCWD;
6733523Scf46844 	}
674*5302Sth199096 
675*5302Sth199096 	return (fstatat(fd, basepath, statp, flags));
6763523Scf46844 }
6773523Scf46844 
6783523Scf46844 /*
6793523Scf46844  * Get lstat info on path when FTW_CHDIR is not set.
6803523Scf46844  */
6813523Scf46844 static int
682*5302Sth199096 nocdlstat(const char *path, struct stat *statp, struct Save *lp, int flags)
6833523Scf46844 {
684*5302Sth199096 	int		fd;
685*5302Sth199096 	const char	*basepath;
6863523Scf46844 
6873523Scf46844 	if (lp && lp->fd) {
6883523Scf46844 		/* get basename of path */
689*5302Sth199096 		basepath = get_unrooted(path);
690*5302Sth199096 
691*5302Sth199096 		fd = lp->fd->dd_fd;
6923523Scf46844 	} else {
693*5302Sth199096 		basepath = path;
694*5302Sth199096 
695*5302Sth199096 		fd = AT_FDCWD;
6963523Scf46844 	}
697*5302Sth199096 
698*5302Sth199096 	return (fstatat(fd, basepath, statp, flags | AT_SYMLINK_NOFOLLOW));
6993523Scf46844 }
7003523Scf46844 
7013523Scf46844 /*
7023523Scf46844  * Open path directory when FTW_CHDIR is set.
7033523Scf46844  *
7043523Scf46844  */
7053523Scf46844 static DIR *
7063523Scf46844 cdopendir(const char *path)
7073523Scf46844 {
7083523Scf46844 	return (opendir(path));
7093523Scf46844 }
7103523Scf46844 
7113523Scf46844 /*
7123523Scf46844  * Open path directory when FTW_CHDIR is not set.
7133523Scf46844  */
7143523Scf46844 static DIR *
7153523Scf46844 nocdopendir(const char *path)
7163523Scf46844 {
7173523Scf46844 	int fd, cfd;
7183523Scf46844 	DIR *fdd;
7193523Scf46844 	char *dirp, *token, *ptr;
7203523Scf46844 
7213523Scf46844 	if (((fdd = opendir(path)) == NULL) && (errno == ENAMETOOLONG)) {
7223523Scf46844 		if ((dirp = strdup(path)) == NULL) {
7233523Scf46844 			errno = ENAMETOOLONG;
7243523Scf46844 			return (NULL);
7253523Scf46844 		}
7263523Scf46844 		if ((token = strtok_r(dirp, "/", &ptr)) != NULL) {
727*5302Sth199096 			if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) {
728*5302Sth199096 				(void) free(dirp);
729*5302Sth199096 				errno = ENAMETOOLONG;
730*5302Sth199096 				return (NULL);
731*5302Sth199096 			}
732*5302Sth199096 			while ((token = strtok_r(NULL, "/", &ptr)) != NULL) {
733*5302Sth199096 				if ((cfd = openat(fd, token, O_RDONLY)) < 0) {
734*5302Sth199096 					(void) close(fd);
735*5302Sth199096 					(void) free(dirp);
736*5302Sth199096 					errno = ENAMETOOLONG;
737*5302Sth199096 					return (NULL);
738*5302Sth199096 				}
739*5302Sth199096 				(void) close(fd);
740*5302Sth199096 				fd = cfd;
741*5302Sth199096 			}
7423523Scf46844 			(void) free(dirp);
743*5302Sth199096 			return (fdopendir(fd));
7443523Scf46844 		}
7453523Scf46844 		(void) free(dirp);
7463523Scf46844 		errno = ENAMETOOLONG;
7473523Scf46844 	}
7483523Scf46844 	return (fdd);
7493523Scf46844 }
7503523Scf46844 
751*5302Sth199096 /*
752*5302Sth199096  * return pointer basename of path, which may contain trailing slashes
753*5302Sth199096  *
754*5302Sth199096  * We do this when we do not chdir() on the input.
755*5302Sth199096  */
7563523Scf46844 static const char *
7573523Scf46844 get_unrooted(const char *path)
7583523Scf46844 {
7593523Scf46844 	const char *ptr;
7603523Scf46844 
7613523Scf46844 	if (!path || !*path)
7623523Scf46844 		return (NULL);
7633523Scf46844 
7643523Scf46844 	ptr = path + strlen(path);
7653523Scf46844 	/* find last char in path before any trailing slashes */
7663523Scf46844 	while (ptr != path && *--ptr == '/')
7673523Scf46844 		;
7683523Scf46844 
7693523Scf46844 	if (ptr == path)	/* all slashes */
7703523Scf46844 		return (ptr);
7713523Scf46844 
7723523Scf46844 	while (ptr != path)
7733523Scf46844 		if (*--ptr == '/')
7743523Scf46844 			return (++ptr);
7753523Scf46844 
7763523Scf46844 	return (ptr);
7773523Scf46844 }
7783523Scf46844 
7793523Scf46844 /*
7800Sstevel@tonic-gate  * close the oldest directory.  It saves the seek offset.
7810Sstevel@tonic-gate  * return value is 0 unless it was unable to close any descriptor
7820Sstevel@tonic-gate  */
7830Sstevel@tonic-gate 
7840Sstevel@tonic-gate static int
7850Sstevel@tonic-gate oldclose(struct Save *sp)
7860Sstevel@tonic-gate {
7870Sstevel@tonic-gate 	struct Save *spnext;
7880Sstevel@tonic-gate 	while (sp) {
7890Sstevel@tonic-gate 		spnext = sp->last;
7900Sstevel@tonic-gate 		if (spnext == 0 || spnext->fd == 0)
7910Sstevel@tonic-gate 			break;
7920Sstevel@tonic-gate 		sp = spnext;
7930Sstevel@tonic-gate 	}
7940Sstevel@tonic-gate 	if (sp == 0 || sp->fd == 0)
7950Sstevel@tonic-gate 		return (0);
7960Sstevel@tonic-gate 	sp->here = telldir(sp->fd);
7970Sstevel@tonic-gate 	(void) closedir(sp->fd);
7980Sstevel@tonic-gate 	sp->fd = 0;
7990Sstevel@tonic-gate 	return (1);
8000Sstevel@tonic-gate }
801