Home | History | Annotate | Download | only in gen
      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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     31 
     32 /*
     33  *	_xftw - file tree walk the uses expanded stat structure
     34  *
     35  *	int _xftw(path, fn, depth)  char *path; int (*fn)(); int depth;
     36  *
     37  *	Given a path name, _xftw starts from the file given by that path
     38  *	name and visits each file and directory in the tree beneath
     39  *	that file.  If a single file has multiple links within the
     40  *	structure, it will be visited once for each such link.
     41  *	For each object visited, fn is called with three arguments.
     42  *		(*fn) (pathname, statp, ftwflag)
     43  *	The first contains the path name of the object, the second
     44  *	contains a pointer to a stat buffer which will usually hold
     45  *	appropriate information for the object and the third will
     46  *	contain an integer value giving additional information about
     47  *
     48  *		FTW_F	The object is a file for which stat was
     49  *			successful.  It does not guarantee that the
     50  *			file can actually be read.
     51  *
     52  *		FTW_D	The object is a directory for which stat and
     53  *			open for read were both successful.
     54  *
     55  *		FTW_DNR	The object is a directory for which stat
     56  *			succeeded, but which cannot be read.  Because
     57  *			the directory cannot be read, fn will not be
     58  *			called for any descendants of this directory.
     59  *
     60  *		FTW_NS	Stat failed on the object because of lack of
     61  *			appropriate permission.  This indication will
     62  *			be given for example for each file in a
     63  *			directory with read but no execute permission.
     64  *			Because stat failed, it is not possible to
     65  *			determine whether this object is a file or a
     66  *			directory.  The stat buffer passed to fn will
     67  *			contain garbage.  Stat failure for any reason
     68  *			other than lack of permission will be
     69  *			considered an error and will cause _xftw to stop
     70  *			and return -1 to its caller.
     71  *
     72  *	If fn returns nonzero, _xftw stops and returns the same value
     73  *	to its caller.  If _xftw gets into other trouble along the way,
     74  *	it returns -1 and leaves an indication of the cause in errno.
     75  *
     76  *	The third argument to _xftw does not limit the depth to which
     77  *	_xftw will go.  Rather, it limits the depth to which _xftw will
     78  *	go before it starts recycling file descriptors.  In general,
     79  *	it is necessary to use a file descriptor for each level of the
     80  *	tree, but they can be recycled for deep trees by saving the
     81  *	position, closing, re-opening, and seeking.  In order to descend
     82  *	to arbitrary depths, _xftw requires 2 file descriptors to be open
     83  *	during the call to openat(), therefore if the depth argument
     84  *	is less than 2 _xftw will not use openat(), and it will fail with
     85  *	ENAMETOOLONG if it descends to a directory that exceeds PATH_MAX.
     86  */
     87 
     88 /*
     89  * this interface uses the expanded stat structure and therefore
     90  * must have EFT enabled.
     91  */
     92 #ifdef _STYPES
     93 #undef _STYPES
     94 #endif
     95 
     96 #include "lint.h"
     97 #include <sys/types.h>
     98 #include <sys/stat.h>
     99 #include <fcntl.h>
    100 #include <sys/param.h>
    101 #include <dirent.h>
    102 #include <errno.h>
    103 #include <ftw.h>
    104 #include <string.h>
    105 #include <stdlib.h>
    106 #include <unistd.h>
    107 
    108 struct Var {
    109 	int level;
    110 	int odepth;
    111 };
    112 
    113 static DIR *nocdopendir(const char *, struct Var *);
    114 static int nocdstat(const char *, struct stat *, struct Var *, int);
    115 static const char *get_unrooted(const char *);
    116 static int fwalk(const char *, int (*)(const char *, const struct stat *, int),
    117 	int, struct Var *);
    118 
    119 /*ARGSUSED*/
    120 int
    121 _xftw(int ver, const char *path,
    122 	int (*fn)(const char *, const struct stat *, int), int depth)
    123 {
    124 	struct Var var;
    125 	int rc;
    126 
    127 	var.level = 0;
    128 	var.odepth = depth;
    129 	rc = fwalk(path, fn, depth, &var);
    130 	return (rc);
    131 }
    132 
    133 /*
    134  * This is the recursive walker.
    135  */
    136 static int
    137 fwalk(const char *path, int (*fn)(const char *, const struct stat *, int),
    138 	int depth, struct Var *vp)
    139 {
    140 	size_t	n;
    141 	int rc;
    142 	int save_errno;
    143 	DIR *dirp;
    144 	char *subpath;
    145 	struct stat sb;
    146 	struct dirent *direntp;
    147 
    148 	vp->level++;
    149 
    150 	/*
    151 	 * Try to get file status.
    152 	 * If unsuccessful, errno will say why.
    153 	 * It's ok to have a symbolic link that points to
    154 	 * non-existing file. In this case, pass FTW_NS
    155 	 * to a function instead of aborting fwalk() right away.
    156 	 */
    157 	if (nocdstat(path, &sb, vp, 0) < 0) {
    158 #ifdef S_IFLNK
    159 		save_errno = errno;
    160 		if ((nocdstat(path, &sb, vp, AT_SYMLINK_NOFOLLOW) != -1) &&
    161 		    ((sb.st_mode & S_IFMT) == S_IFLNK)) {
    162 			errno = save_errno;
    163 			return (*fn)(path, &sb, FTW_NS);
    164 		} else  {
    165 			errno = save_errno;
    166 		}
    167 #endif
    168 		return (errno == EACCES? (*fn)(path, &sb, FTW_NS): -1);
    169 	}
    170 
    171 	/*
    172 	 *	The stat succeeded, so we know the object exists.
    173 	 *	If not a directory, call the user function and return.
    174 	 */
    175 	if ((sb.st_mode & S_IFMT) != S_IFDIR)
    176 		return ((*fn)(path, &sb, FTW_F));
    177 
    178 	/*
    179 	 *	The object was a directory.
    180 	 *
    181 	 *	Open a file to read the directory
    182 	 */
    183 	dirp = nocdopendir(path, vp);
    184 
    185 	/*
    186 	 *	Call the user function, telling it whether
    187 	 *	the directory can be read.  If it can't be read
    188 	 *	call the user function or indicate an error,
    189 	 *	depending on the reason it couldn't be read.
    190 	 */
    191 	if (dirp == NULL)
    192 		return (errno == EACCES? (*fn)(path, &sb, FTW_DNR): -1);
    193 
    194 	/* We could read the directory.  Call user function. */
    195 	rc = (*fn)(path, &sb, FTW_D);
    196 	if (rc != 0) {
    197 		(void) closedir(dirp);
    198 		return (rc);
    199 	}
    200 
    201 	/*
    202 	 *	Read the directory one component at a time.
    203 	 *	We must ignore "." and "..", but other than that,
    204 	 *	just create a path name and call self to check it out.
    205 	 */
    206 	while (direntp = readdir(dirp)) {
    207 		long here;
    208 
    209 		if (strcmp(direntp->d_name, ".") == 0 ||
    210 		    strcmp(direntp->d_name, "..") == 0)
    211 			continue;
    212 
    213 		/* Create a prefix to which we will append component names */
    214 		n = strlen(path);
    215 		subpath = malloc(n + strlen(direntp->d_name) + 2);
    216 		if (subpath == 0) {
    217 			(void) closedir(dirp);
    218 			errno = ENOMEM;
    219 			return (-1);
    220 		}
    221 		(void) strcpy(subpath, path);
    222 		if (subpath[0] != '\0' && subpath[n-1] != '/')
    223 			subpath[n++] = '/';
    224 
    225 		/* Append component name to the working path */
    226 		(void) strlcpy(&subpath[n], direntp->d_name, MAXNAMELEN);
    227 
    228 		/*
    229 		 *	If we are about to exceed our depth,
    230 		 *	remember where we are and close a file.
    231 		 */
    232 		if (depth <= 1) {
    233 			here = telldir(dirp);
    234 			if (closedir(dirp) < 0) {
    235 				free(subpath);
    236 				return (-1);
    237 			}
    238 		}
    239 
    240 		/*
    241 		 *	Do a recursive call to process the file.
    242 		 *	(watch this, sports fans)
    243 		 */
    244 		rc = fwalk(subpath, fn, depth-1, vp);
    245 		if (rc != 0) {
    246 			free(subpath);
    247 			if (depth > 1)
    248 				(void) closedir(dirp);
    249 			return (rc);
    250 		}
    251 
    252 		/*
    253 		 *	If we closed the file, try to reopen it.
    254 		 */
    255 		if (depth <= 1) {
    256 			dirp = nocdopendir(path, vp);
    257 			if (dirp == NULL) {
    258 				free(subpath);
    259 				return (-1);
    260 			}
    261 			seekdir(dirp, here);
    262 		}
    263 		free(subpath);
    264 	}
    265 	(void) closedir(dirp);
    266 	return (0);
    267 }
    268 
    269 /*
    270  * Open a directory with an arbitrarily long path name.  If the original
    271  * depth arg >= 2, use openat() to make sure that it doesn't fail with
    272  * ENAMETOOLONG.
    273  */
    274 static DIR *
    275 nocdopendir(const char *path, struct Var *vp)
    276 {
    277 	int fd, cfd;
    278 	DIR *fdd;
    279 	char *dirp, *token, *ptr;
    280 
    281 	fdd = opendir(path);
    282 	if ((vp->odepth > 1) && (fdd == NULL) && (errno == ENAMETOOLONG)) {
    283 		/*
    284 		 * Traverse the path using openat() to get the fd for
    285 		 * fdopendir().
    286 		 */
    287 		if ((dirp = strdup(path)) == NULL) {
    288 			errno = ENAMETOOLONG;
    289 			return (NULL);
    290 		}
    291 		if ((token = strtok_r(dirp, "/", &ptr)) != NULL) {
    292 		    if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) {
    293 			(void) free(dirp);
    294 			errno = ENAMETOOLONG;
    295 			return (NULL);
    296 		    }
    297 		    while ((token = strtok_r(NULL, "/", &ptr)) != NULL) {
    298 			if ((cfd = openat(fd, token, O_RDONLY)) < 0) {
    299 			    (void) close(fd);
    300 			    (void) free(dirp);
    301 			    errno = ENAMETOOLONG;
    302 			    return (NULL);
    303 			}
    304 			(void) close(fd);
    305 			fd = cfd;
    306 		    }
    307 		    (void) free(dirp);
    308 		    return (fdopendir(fd));
    309 		}
    310 		(void) free(dirp);
    311 		errno = ENAMETOOLONG;
    312 	}
    313 	return (fdd);
    314 }
    315 
    316 /*
    317  * Stat a file with an arbitrarily long path name. If we aren't doing a
    318  * stat on the arg passed to _xftw() and if the original depth arg >= 2,
    319  * use openat() to make sure that it doesn't fail with ENAMETOOLONG.
    320  */
    321 static int
    322 nocdstat(const char *path, struct stat *statp, struct Var *vp, int sym)
    323 {
    324 	int fd, cfd;
    325 	char *dirp, *token, *ptr;
    326 	int rc;
    327 	const char *unrootp;
    328 	int save_err;
    329 
    330 	rc = fstatat(AT_FDCWD, path, statp, sym);
    331 	if ((vp->level > 1) && (vp->odepth >= 2) && (rc < 0) &&
    332 	    (errno == ENAMETOOLONG)) {
    333 		/* Traverse path using openat() to get fd for fstatat(). */
    334 		if ((dirp = strdup(path)) == NULL) {
    335 			errno = ENAMETOOLONG;
    336 			return (-1);
    337 		}
    338 		if ((token = strtok_r(dirp, "/", &ptr)) != NULL) {
    339 		    if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) {
    340 			(void) free(dirp);
    341 			errno = ENAMETOOLONG;
    342 			return (-1);
    343 		    }
    344 		    unrootp = get_unrooted(path);
    345 		    while (((token = strtok_r(NULL, "/", &ptr)) != NULL) &&
    346 			(strcmp(token, unrootp) != 0)) {
    347 			    if ((cfd = openat(fd, token, O_RDONLY)) < 0) {
    348 				(void) close(fd);
    349 				(void) free(dirp);
    350 				errno = ENAMETOOLONG;
    351 				return (NULL);
    352 			    }
    353 			    (void) close(fd);
    354 			    fd = cfd;
    355 		    }
    356 		    (void) free(dirp);
    357 		    rc = fstatat(fd, unrootp, statp, sym);
    358 		    save_err = errno;
    359 		    (void) close(fd);
    360 		    errno = save_err;
    361 		    return (rc);
    362 		}
    363 		(void) free(dirp);
    364 		errno = ENAMETOOLONG;
    365 	}
    366 	return (rc);
    367 }
    368 
    369 /*
    370  * Return pointer basename of path.  This routine doesn't remove
    371  * trailing slashes, but there won't be any.
    372  */
    373 static const char *
    374 get_unrooted(const char *path)
    375 {
    376 	const char *ptr;
    377 
    378 	if (!path || !*path)
    379 		return (NULL);
    380 
    381 	ptr = path + strlen(path);
    382 	/* find last char in path before any trailing slashes */
    383 	while (ptr != path && *--ptr == '/')
    384 		;
    385 
    386 	if (ptr == path)	/* all slashes */
    387 		return (ptr);
    388 
    389 	while (ptr != path)
    390 		if (*--ptr == '/')
    391 			return (++ptr);
    392 
    393 	return (ptr);
    394 }
    395