xref: /onnv-gate/usr/src/lib/libc/port/gen/_xftw.c (revision 6812:febeba71273d)
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  */
21*6812Sraf 
220Sstevel@tonic-gate /*
23*6812Sraf  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
240Sstevel@tonic-gate  * Use is subject to license terms.
250Sstevel@tonic-gate  */
260Sstevel@tonic-gate 
270Sstevel@tonic-gate /*	Copyright (c) 1988 AT&T	*/
280Sstevel@tonic-gate /*	  All Rights Reserved  	*/
290Sstevel@tonic-gate 
30*6812Sraf #pragma ident	"%Z%%M%	%I%	%E% SMI"
310Sstevel@tonic-gate 
320Sstevel@tonic-gate /*
330Sstevel@tonic-gate  *	_xftw - file tree walk the uses expanded stat structure
340Sstevel@tonic-gate  *
350Sstevel@tonic-gate  *	int _xftw(path, fn, depth)  char *path; int (*fn)(); int depth;
360Sstevel@tonic-gate  *
370Sstevel@tonic-gate  *	Given a path name, _xftw starts from the file given by that path
380Sstevel@tonic-gate  *	name and visits each file and directory in the tree beneath
390Sstevel@tonic-gate  *	that file.  If a single file has multiple links within the
400Sstevel@tonic-gate  *	structure, it will be visited once for each such link.
410Sstevel@tonic-gate  *	For each object visited, fn is called with three arguments.
420Sstevel@tonic-gate  *		(*fn) (pathname, statp, ftwflag)
430Sstevel@tonic-gate  *	The first contains the path name of the object, the second
440Sstevel@tonic-gate  *	contains a pointer to a stat buffer which will usually hold
450Sstevel@tonic-gate  *	appropriate information for the object and the third will
460Sstevel@tonic-gate  *	contain an integer value giving additional information about
470Sstevel@tonic-gate  *
480Sstevel@tonic-gate  *		FTW_F	The object is a file for which stat was
490Sstevel@tonic-gate  *			successful.  It does not guarantee that the
500Sstevel@tonic-gate  *			file can actually be read.
510Sstevel@tonic-gate  *
520Sstevel@tonic-gate  *		FTW_D	The object is a directory for which stat and
530Sstevel@tonic-gate  *			open for read were both successful.
540Sstevel@tonic-gate  *
550Sstevel@tonic-gate  *		FTW_DNR	The object is a directory for which stat
560Sstevel@tonic-gate  *			succeeded, but which cannot be read.  Because
570Sstevel@tonic-gate  *			the directory cannot be read, fn will not be
580Sstevel@tonic-gate  *			called for any descendants of this directory.
590Sstevel@tonic-gate  *
600Sstevel@tonic-gate  *		FTW_NS	Stat failed on the object because of lack of
610Sstevel@tonic-gate  *			appropriate permission.  This indication will
620Sstevel@tonic-gate  *			be given for example for each file in a
630Sstevel@tonic-gate  *			directory with read but no execute permission.
640Sstevel@tonic-gate  *			Because stat failed, it is not possible to
650Sstevel@tonic-gate  *			determine whether this object is a file or a
660Sstevel@tonic-gate  *			directory.  The stat buffer passed to fn will
670Sstevel@tonic-gate  *			contain garbage.  Stat failure for any reason
680Sstevel@tonic-gate  *			other than lack of permission will be
690Sstevel@tonic-gate  *			considered an error and will cause _xftw to stop
700Sstevel@tonic-gate  *			and return -1 to its caller.
710Sstevel@tonic-gate  *
720Sstevel@tonic-gate  *	If fn returns nonzero, _xftw stops and returns the same value
730Sstevel@tonic-gate  *	to its caller.  If _xftw gets into other trouble along the way,
740Sstevel@tonic-gate  *	it returns -1 and leaves an indication of the cause in errno.
750Sstevel@tonic-gate  *
760Sstevel@tonic-gate  *	The third argument to _xftw does not limit the depth to which
770Sstevel@tonic-gate  *	_xftw will go.  Rather, it limits the depth to which _xftw will
780Sstevel@tonic-gate  *	go before it starts recycling file descriptors.  In general,
790Sstevel@tonic-gate  *	it is necessary to use a file descriptor for each level of the
800Sstevel@tonic-gate  *	tree, but they can be recycled for deep trees by saving the
813523Scf46844  *	position, closing, re-opening, and seeking.  In order to descend
823523Scf46844  *	to arbitrary depths, _xftw requires 2 file descriptors to be open
833523Scf46844  *	during the call to openat(), therefore if the depth argument
843523Scf46844  *	is less than 2 _xftw will not use openat(), and it will fail with
853523Scf46844  *	ENAMETOOLONG if it descends to a directory that exceeds PATH_MAX.
860Sstevel@tonic-gate  */
870Sstevel@tonic-gate 
880Sstevel@tonic-gate /*
890Sstevel@tonic-gate  * this interface uses the expanded stat structure and therefore
900Sstevel@tonic-gate  * must have EFT enabled.
910Sstevel@tonic-gate  */
920Sstevel@tonic-gate #ifdef _STYPES
930Sstevel@tonic-gate #undef _STYPES
940Sstevel@tonic-gate #endif
950Sstevel@tonic-gate 
960Sstevel@tonic-gate #include "lint.h"
970Sstevel@tonic-gate #include <sys/types.h>
980Sstevel@tonic-gate #include <sys/stat.h>
993523Scf46844 #include <fcntl.h>
1000Sstevel@tonic-gate #include <sys/param.h>
1010Sstevel@tonic-gate #include <dirent.h>
1020Sstevel@tonic-gate #include <errno.h>
1030Sstevel@tonic-gate #include <ftw.h>
1040Sstevel@tonic-gate #include <string.h>
1050Sstevel@tonic-gate #include <stdlib.h>
1063523Scf46844 #include <unistd.h>
1073523Scf46844 
1083523Scf46844 struct Var {
1093523Scf46844 	int level;
1103523Scf46844 	int odepth;
1113523Scf46844 };
1120Sstevel@tonic-gate 
1133523Scf46844 static DIR *nocdopendir(const char *, struct Var *);
1143523Scf46844 static int nocdstat(const char *, struct stat *, struct Var *, int);
1153523Scf46844 static const char *get_unrooted(const char *);
1163523Scf46844 static int fwalk(const char *, int (*)(const char *, const struct stat *, int),
1173523Scf46844 	int, struct Var *);
1183523Scf46844 
1193523Scf46844 /*ARGSUSED*/
1200Sstevel@tonic-gate int
_xftw(int ver,const char * path,int (* fn)(const char *,const struct stat *,int),int depth)1210Sstevel@tonic-gate _xftw(int ver, const char *path,
1220Sstevel@tonic-gate 	int (*fn)(const char *, const struct stat *, int), int depth)
1230Sstevel@tonic-gate {
1243523Scf46844 	struct Var var;
1253523Scf46844 	int rc;
1263523Scf46844 
1273523Scf46844 	var.level = 0;
1283523Scf46844 	var.odepth = depth;
1293523Scf46844 	rc = fwalk(path, fn, depth, &var);
1303523Scf46844 	return (rc);
1313523Scf46844 }
1323523Scf46844 
1333523Scf46844 /*
1343523Scf46844  * This is the recursive walker.
1353523Scf46844  */
1363523Scf46844 static int
fwalk(const char * path,int (* fn)(const char *,const struct stat *,int),int depth,struct Var * vp)1373523Scf46844 fwalk(const char *path, int (*fn)(const char *, const struct stat *, int),
1383523Scf46844 	int depth, struct Var *vp)
1393523Scf46844 {
1400Sstevel@tonic-gate 	size_t	n;
1410Sstevel@tonic-gate 	int rc;
1420Sstevel@tonic-gate 	int save_errno;
1430Sstevel@tonic-gate 	DIR *dirp;
1440Sstevel@tonic-gate 	char *subpath;
1450Sstevel@tonic-gate 	struct stat sb;
1460Sstevel@tonic-gate 	struct dirent *direntp;
1470Sstevel@tonic-gate 
1483523Scf46844 	vp->level++;
1490Sstevel@tonic-gate 
1500Sstevel@tonic-gate 	/*
1510Sstevel@tonic-gate 	 * Try to get file status.
1520Sstevel@tonic-gate 	 * If unsuccessful, errno will say why.
1530Sstevel@tonic-gate 	 * It's ok to have a symbolic link that points to
1540Sstevel@tonic-gate 	 * non-existing file. In this case, pass FTW_NS
1553523Scf46844 	 * to a function instead of aborting fwalk() right away.
1560Sstevel@tonic-gate 	 */
1573523Scf46844 	if (nocdstat(path, &sb, vp, 0) < 0) {
1580Sstevel@tonic-gate #ifdef S_IFLNK
1590Sstevel@tonic-gate 		save_errno = errno;
1603523Scf46844 		if ((nocdstat(path, &sb, vp, AT_SYMLINK_NOFOLLOW) != -1) &&
1613523Scf46844 		    ((sb.st_mode & S_IFMT) == S_IFLNK)) {
1620Sstevel@tonic-gate 			errno = save_errno;
1630Sstevel@tonic-gate 			return (*fn)(path, &sb, FTW_NS);
1640Sstevel@tonic-gate 		} else  {
1650Sstevel@tonic-gate 			errno = save_errno;
1660Sstevel@tonic-gate 		}
1670Sstevel@tonic-gate #endif
1680Sstevel@tonic-gate 		return (errno == EACCES? (*fn)(path, &sb, FTW_NS): -1);
1690Sstevel@tonic-gate 	}
1700Sstevel@tonic-gate 
1710Sstevel@tonic-gate 	/*
1720Sstevel@tonic-gate 	 *	The stat succeeded, so we know the object exists.
1730Sstevel@tonic-gate 	 *	If not a directory, call the user function and return.
1740Sstevel@tonic-gate 	 */
1750Sstevel@tonic-gate 	if ((sb.st_mode & S_IFMT) != S_IFDIR)
1760Sstevel@tonic-gate 		return ((*fn)(path, &sb, FTW_F));
1770Sstevel@tonic-gate 
1780Sstevel@tonic-gate 	/*
1790Sstevel@tonic-gate 	 *	The object was a directory.
1800Sstevel@tonic-gate 	 *
1810Sstevel@tonic-gate 	 *	Open a file to read the directory
1820Sstevel@tonic-gate 	 */
1833523Scf46844 	dirp = nocdopendir(path, vp);
1840Sstevel@tonic-gate 
1850Sstevel@tonic-gate 	/*
1860Sstevel@tonic-gate 	 *	Call the user function, telling it whether
1870Sstevel@tonic-gate 	 *	the directory can be read.  If it can't be read
1880Sstevel@tonic-gate 	 *	call the user function or indicate an error,
1890Sstevel@tonic-gate 	 *	depending on the reason it couldn't be read.
1900Sstevel@tonic-gate 	 */
1910Sstevel@tonic-gate 	if (dirp == NULL)
1920Sstevel@tonic-gate 		return (errno == EACCES? (*fn)(path, &sb, FTW_DNR): -1);
1930Sstevel@tonic-gate 
1940Sstevel@tonic-gate 	/* We could read the directory.  Call user function. */
1950Sstevel@tonic-gate 	rc = (*fn)(path, &sb, FTW_D);
1960Sstevel@tonic-gate 	if (rc != 0) {
1970Sstevel@tonic-gate 		(void) closedir(dirp);
1980Sstevel@tonic-gate 		return (rc);
1990Sstevel@tonic-gate 	}
2000Sstevel@tonic-gate 
2010Sstevel@tonic-gate 	/*
2020Sstevel@tonic-gate 	 *	Read the directory one component at a time.
2030Sstevel@tonic-gate 	 *	We must ignore "." and "..", but other than that,
2040Sstevel@tonic-gate 	 *	just create a path name and call self to check it out.
2050Sstevel@tonic-gate 	 */
2060Sstevel@tonic-gate 	while (direntp = readdir(dirp)) {
2070Sstevel@tonic-gate 		long here;
2080Sstevel@tonic-gate 
2090Sstevel@tonic-gate 		if (strcmp(direntp->d_name, ".") == 0 ||
2100Sstevel@tonic-gate 		    strcmp(direntp->d_name, "..") == 0)
2110Sstevel@tonic-gate 			continue;
2120Sstevel@tonic-gate 
2133523Scf46844 		/* Create a prefix to which we will append component names */
2143523Scf46844 		n = strlen(path);
2153523Scf46844 		subpath = malloc(n + strlen(direntp->d_name) + 2);
2163523Scf46844 		if (subpath == 0) {
2173523Scf46844 			(void) closedir(dirp);
2183523Scf46844 			errno = ENOMEM;
2193523Scf46844 			return (-1);
2203523Scf46844 		}
2213523Scf46844 		(void) strcpy(subpath, path);
2223523Scf46844 		if (subpath[0] != '\0' && subpath[n-1] != '/')
2233523Scf46844 			subpath[n++] = '/';
2243523Scf46844 
2250Sstevel@tonic-gate 		/* Append component name to the working path */
2260Sstevel@tonic-gate 		(void) strlcpy(&subpath[n], direntp->d_name, MAXNAMELEN);
2270Sstevel@tonic-gate 
2280Sstevel@tonic-gate 		/*
2290Sstevel@tonic-gate 		 *	If we are about to exceed our depth,
2300Sstevel@tonic-gate 		 *	remember where we are and close a file.
2310Sstevel@tonic-gate 		 */
2320Sstevel@tonic-gate 		if (depth <= 1) {
2330Sstevel@tonic-gate 			here = telldir(dirp);
2343523Scf46844 			if (closedir(dirp) < 0) {
2353523Scf46844 				free(subpath);
2360Sstevel@tonic-gate 				return (-1);
2373523Scf46844 			}
2380Sstevel@tonic-gate 		}
2390Sstevel@tonic-gate 
2400Sstevel@tonic-gate 		/*
2410Sstevel@tonic-gate 		 *	Do a recursive call to process the file.
2420Sstevel@tonic-gate 		 *	(watch this, sports fans)
2430Sstevel@tonic-gate 		 */
2443523Scf46844 		rc = fwalk(subpath, fn, depth-1, vp);
2450Sstevel@tonic-gate 		if (rc != 0) {
2463523Scf46844 			free(subpath);
2470Sstevel@tonic-gate 			if (depth > 1)
2480Sstevel@tonic-gate 				(void) closedir(dirp);
2490Sstevel@tonic-gate 			return (rc);
2500Sstevel@tonic-gate 		}
2510Sstevel@tonic-gate 
2520Sstevel@tonic-gate 		/*
2530Sstevel@tonic-gate 		 *	If we closed the file, try to reopen it.
2540Sstevel@tonic-gate 		 */
2550Sstevel@tonic-gate 		if (depth <= 1) {
2563523Scf46844 			dirp = nocdopendir(path, vp);
2573523Scf46844 			if (dirp == NULL) {
2583523Scf46844 				free(subpath);
2590Sstevel@tonic-gate 				return (-1);
2603523Scf46844 			}
2610Sstevel@tonic-gate 			seekdir(dirp, here);
2620Sstevel@tonic-gate 		}
2633523Scf46844 		free(subpath);
2640Sstevel@tonic-gate 	}
2650Sstevel@tonic-gate 	(void) closedir(dirp);
2660Sstevel@tonic-gate 	return (0);
2670Sstevel@tonic-gate }
2683523Scf46844 
2693523Scf46844 /*
2703523Scf46844  * Open a directory with an arbitrarily long path name.  If the original
2713523Scf46844  * depth arg >= 2, use openat() to make sure that it doesn't fail with
2723523Scf46844  * ENAMETOOLONG.
2733523Scf46844  */
2743523Scf46844 static DIR *
nocdopendir(const char * path,struct Var * vp)2753523Scf46844 nocdopendir(const char *path, struct Var *vp)
2763523Scf46844 {
2773523Scf46844 	int fd, cfd;
2783523Scf46844 	DIR *fdd;
2793523Scf46844 	char *dirp, *token, *ptr;
2803523Scf46844 
2813523Scf46844 	fdd = opendir(path);
2823523Scf46844 	if ((vp->odepth > 1) && (fdd == NULL) && (errno == ENAMETOOLONG)) {
2833523Scf46844 		/*
2843523Scf46844 		 * Traverse the path using openat() to get the fd for
2853523Scf46844 		 * fdopendir().
2863523Scf46844 		 */
2873523Scf46844 		if ((dirp = strdup(path)) == NULL) {
2883523Scf46844 			errno = ENAMETOOLONG;
2893523Scf46844 			return (NULL);
2903523Scf46844 		}
2913523Scf46844 		if ((token = strtok_r(dirp, "/", &ptr)) != NULL) {
2923523Scf46844 		    if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) {
2933523Scf46844 			(void) free(dirp);
2943523Scf46844 			errno = ENAMETOOLONG;
2953523Scf46844 			return (NULL);
2963523Scf46844 		    }
2973523Scf46844 		    while ((token = strtok_r(NULL, "/", &ptr)) != NULL) {
2983523Scf46844 			if ((cfd = openat(fd, token, O_RDONLY)) < 0) {
2993523Scf46844 			    (void) close(fd);
3003523Scf46844 			    (void) free(dirp);
3013523Scf46844 			    errno = ENAMETOOLONG;
3023523Scf46844 			    return (NULL);
3033523Scf46844 			}
3043523Scf46844 			(void) close(fd);
3053523Scf46844 			fd = cfd;
3063523Scf46844 		    }
3073523Scf46844 		    (void) free(dirp);
3083523Scf46844 		    return (fdopendir(fd));
3093523Scf46844 		}
3103523Scf46844 		(void) free(dirp);
3113523Scf46844 		errno = ENAMETOOLONG;
3123523Scf46844 	}
3133523Scf46844 	return (fdd);
3143523Scf46844 }
3153523Scf46844 
3163523Scf46844 /*
3173523Scf46844  * Stat a file with an arbitrarily long path name. If we aren't doing a
3183523Scf46844  * stat on the arg passed to _xftw() and if the original depth arg >= 2,
3193523Scf46844  * use openat() to make sure that it doesn't fail with ENAMETOOLONG.
3203523Scf46844  */
3213523Scf46844 static int
nocdstat(const char * path,struct stat * statp,struct Var * vp,int sym)3223523Scf46844 nocdstat(const char *path, struct stat *statp, struct Var *vp, int sym)
3233523Scf46844 {
3243523Scf46844 	int fd, cfd;
3253523Scf46844 	char *dirp, *token, *ptr;
3263523Scf46844 	int rc;
3273523Scf46844 	const char *unrootp;
3283523Scf46844 	int save_err;
3293523Scf46844 
3303523Scf46844 	rc = fstatat(AT_FDCWD, path, statp, sym);
3313523Scf46844 	if ((vp->level > 1) && (vp->odepth >= 2) && (rc < 0) &&
3323523Scf46844 	    (errno == ENAMETOOLONG)) {
3333523Scf46844 		/* Traverse path using openat() to get fd for fstatat(). */
3343523Scf46844 		if ((dirp = strdup(path)) == NULL) {
3353523Scf46844 			errno = ENAMETOOLONG;
3363523Scf46844 			return (-1);
3373523Scf46844 		}
3383523Scf46844 		if ((token = strtok_r(dirp, "/", &ptr)) != NULL) {
3393523Scf46844 		    if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) {
3403523Scf46844 			(void) free(dirp);
3413523Scf46844 			errno = ENAMETOOLONG;
3423523Scf46844 			return (-1);
3433523Scf46844 		    }
3443523Scf46844 		    unrootp = get_unrooted(path);
3453523Scf46844 		    while (((token = strtok_r(NULL, "/", &ptr)) != NULL) &&
3463523Scf46844 			(strcmp(token, unrootp) != 0)) {
3473523Scf46844 			    if ((cfd = openat(fd, token, O_RDONLY)) < 0) {
3483523Scf46844 				(void) close(fd);
3493523Scf46844 				(void) free(dirp);
3503523Scf46844 				errno = ENAMETOOLONG;
3513523Scf46844 				return (NULL);
3523523Scf46844 			    }
3533523Scf46844 			    (void) close(fd);
3543523Scf46844 			    fd = cfd;
3553523Scf46844 		    }
3563523Scf46844 		    (void) free(dirp);
3573523Scf46844 		    rc = fstatat(fd, unrootp, statp, sym);
3583523Scf46844 		    save_err = errno;
3593523Scf46844 		    (void) close(fd);
3603523Scf46844 		    errno = save_err;
3613523Scf46844 		    return (rc);
3623523Scf46844 		}
3633523Scf46844 		(void) free(dirp);
3643523Scf46844 		errno = ENAMETOOLONG;
3653523Scf46844 	}
3663523Scf46844 	return (rc);
3673523Scf46844 }
3683523Scf46844 
3693523Scf46844 /*
3703523Scf46844  * Return pointer basename of path.  This routine doesn't remove
3713523Scf46844  * trailing slashes, but there won't be any.
3723523Scf46844  */
3733523Scf46844 static const char *
get_unrooted(const char * path)3743523Scf46844 get_unrooted(const char *path)
3753523Scf46844 {
3763523Scf46844 	const char *ptr;
3773523Scf46844 
3783523Scf46844 	if (!path || !*path)
3793523Scf46844 		return (NULL);
3803523Scf46844 
3813523Scf46844 	ptr = path + strlen(path);
3823523Scf46844 	/* find last char in path before any trailing slashes */
3833523Scf46844 	while (ptr != path && *--ptr == '/')
3843523Scf46844 		;
3853523Scf46844 
3863523Scf46844 	if (ptr == path)	/* all slashes */
3873523Scf46844 		return (ptr);
3883523Scf46844 
3893523Scf46844 	while (ptr != path)
3903523Scf46844 		if (*--ptr == '/')
3913523Scf46844 			return (++ptr);
3923523Scf46844 
3933523Scf46844 	return (ptr);
3943523Scf46844 }
395