xref: /illumos-gate/usr/src/lib/libc/port/gen/_xftw.c (revision d362b749)
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  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*	Copyright (c) 1988 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
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 <sys/feature_tests.h>
97 
98 #if !defined(_LP64) && _FILE_OFFSET_BITS == 64
99 #define	fstatat64	_fstatat64
100 #define	lstat64		_lstat64
101 #define	openat64	_openat64
102 #define	readdir64	_readdir64
103 #define	stat64		_stat64
104 #else
105 #define	fstatat		_fstatat
106 #define	lstat		_lstat
107 #define	openat		_openat
108 #define	readdir		_readdir
109 #define	stat		_stat
110 #endif /* !_LP64 && _FILE_OFFSET_BITS == 64 */
111 
112 #define	close		_close
113 #define	closedir	_closedir
114 #define	fdopendir	_fdopendir
115 #define	opendir		_opendir
116 #define	seekdir		_seekdir
117 #define	strdup		_strdup
118 #define	strtok_r	_strtok_r
119 #define	telldir		_telldir
120 
121 #include "lint.h"
122 #include <sys/types.h>
123 #include <sys/stat.h>
124 #include <fcntl.h>
125 #include <sys/param.h>
126 #include <dirent.h>
127 #include <errno.h>
128 #include <ftw.h>
129 #include <string.h>
130 #include <stdlib.h>
131 #include <unistd.h>
132 
133 struct Var {
134 	int level;
135 	int odepth;
136 };
137 
138 static DIR *nocdopendir(const char *, struct Var *);
139 static int nocdstat(const char *, struct stat *, struct Var *, int);
140 static const char *get_unrooted(const char *);
141 static int fwalk(const char *, int (*)(const char *, const struct stat *, int),
142 	int, struct Var *);
143 
144 /*ARGSUSED*/
145 int
146 _xftw(int ver, const char *path,
147 	int (*fn)(const char *, const struct stat *, int), int depth)
148 {
149 	struct Var var;
150 	int rc;
151 
152 	var.level = 0;
153 	var.odepth = depth;
154 	rc = fwalk(path, fn, depth, &var);
155 	return (rc);
156 }
157 
158 /*
159  * This is the recursive walker.
160  */
161 static int
162 fwalk(const char *path, int (*fn)(const char *, const struct stat *, int),
163 	int depth, struct Var *vp)
164 {
165 	size_t	n;
166 	int rc;
167 	int save_errno;
168 	DIR *dirp;
169 	char *subpath;
170 	struct stat sb;
171 	struct dirent *direntp;
172 
173 	vp->level++;
174 
175 	/*
176 	 * Try to get file status.
177 	 * If unsuccessful, errno will say why.
178 	 * It's ok to have a symbolic link that points to
179 	 * non-existing file. In this case, pass FTW_NS
180 	 * to a function instead of aborting fwalk() right away.
181 	 */
182 	if (nocdstat(path, &sb, vp, 0) < 0) {
183 #ifdef S_IFLNK
184 		save_errno = errno;
185 		if ((nocdstat(path, &sb, vp, AT_SYMLINK_NOFOLLOW) != -1) &&
186 		    ((sb.st_mode & S_IFMT) == S_IFLNK)) {
187 			errno = save_errno;
188 			return (*fn)(path, &sb, FTW_NS);
189 		} else  {
190 			errno = save_errno;
191 		}
192 #endif
193 		return (errno == EACCES? (*fn)(path, &sb, FTW_NS): -1);
194 	}
195 
196 	/*
197 	 *	The stat succeeded, so we know the object exists.
198 	 *	If not a directory, call the user function and return.
199 	 */
200 	if ((sb.st_mode & S_IFMT) != S_IFDIR)
201 		return ((*fn)(path, &sb, FTW_F));
202 
203 	/*
204 	 *	The object was a directory.
205 	 *
206 	 *	Open a file to read the directory
207 	 */
208 	dirp = nocdopendir(path, vp);
209 
210 	/*
211 	 *	Call the user function, telling it whether
212 	 *	the directory can be read.  If it can't be read
213 	 *	call the user function or indicate an error,
214 	 *	depending on the reason it couldn't be read.
215 	 */
216 	if (dirp == NULL)
217 		return (errno == EACCES? (*fn)(path, &sb, FTW_DNR): -1);
218 
219 	/* We could read the directory.  Call user function. */
220 	rc = (*fn)(path, &sb, FTW_D);
221 	if (rc != 0) {
222 		(void) closedir(dirp);
223 		return (rc);
224 	}
225 
226 	/*
227 	 *	Read the directory one component at a time.
228 	 *	We must ignore "." and "..", but other than that,
229 	 *	just create a path name and call self to check it out.
230 	 */
231 	while (direntp = readdir(dirp)) {
232 		long here;
233 
234 		if (strcmp(direntp->d_name, ".") == 0 ||
235 		    strcmp(direntp->d_name, "..") == 0)
236 			continue;
237 
238 		/* Create a prefix to which we will append component names */
239 		n = strlen(path);
240 		subpath = malloc(n + strlen(direntp->d_name) + 2);
241 		if (subpath == 0) {
242 			(void) closedir(dirp);
243 			errno = ENOMEM;
244 			return (-1);
245 		}
246 		(void) strcpy(subpath, path);
247 		if (subpath[0] != '\0' && subpath[n-1] != '/')
248 			subpath[n++] = '/';
249 
250 		/* Append component name to the working path */
251 		(void) strlcpy(&subpath[n], direntp->d_name, MAXNAMELEN);
252 
253 		/*
254 		 *	If we are about to exceed our depth,
255 		 *	remember where we are and close a file.
256 		 */
257 		if (depth <= 1) {
258 			here = telldir(dirp);
259 			if (closedir(dirp) < 0) {
260 				free(subpath);
261 				return (-1);
262 			}
263 		}
264 
265 		/*
266 		 *	Do a recursive call to process the file.
267 		 *	(watch this, sports fans)
268 		 */
269 		rc = fwalk(subpath, fn, depth-1, vp);
270 		if (rc != 0) {
271 			free(subpath);
272 			if (depth > 1)
273 				(void) closedir(dirp);
274 			return (rc);
275 		}
276 
277 		/*
278 		 *	If we closed the file, try to reopen it.
279 		 */
280 		if (depth <= 1) {
281 			dirp = nocdopendir(path, vp);
282 			if (dirp == NULL) {
283 				free(subpath);
284 				return (-1);
285 			}
286 			seekdir(dirp, here);
287 		}
288 		free(subpath);
289 	}
290 	(void) closedir(dirp);
291 	return (0);
292 }
293 
294 /*
295  * Open a directory with an arbitrarily long path name.  If the original
296  * depth arg >= 2, use openat() to make sure that it doesn't fail with
297  * ENAMETOOLONG.
298  */
299 static DIR *
300 nocdopendir(const char *path, struct Var *vp)
301 {
302 	int fd, cfd;
303 	DIR *fdd;
304 	char *dirp, *token, *ptr;
305 
306 	fdd = opendir(path);
307 	if ((vp->odepth > 1) && (fdd == NULL) && (errno == ENAMETOOLONG)) {
308 		/*
309 		 * Traverse the path using openat() to get the fd for
310 		 * fdopendir().
311 		 */
312 		if ((dirp = strdup(path)) == NULL) {
313 			errno = ENAMETOOLONG;
314 			return (NULL);
315 		}
316 		if ((token = strtok_r(dirp, "/", &ptr)) != NULL) {
317 		    if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) {
318 			(void) free(dirp);
319 			errno = ENAMETOOLONG;
320 			return (NULL);
321 		    }
322 		    while ((token = strtok_r(NULL, "/", &ptr)) != NULL) {
323 			if ((cfd = openat(fd, token, O_RDONLY)) < 0) {
324 			    (void) close(fd);
325 			    (void) free(dirp);
326 			    errno = ENAMETOOLONG;
327 			    return (NULL);
328 			}
329 			(void) close(fd);
330 			fd = cfd;
331 		    }
332 		    (void) free(dirp);
333 		    return (fdopendir(fd));
334 		}
335 		(void) free(dirp);
336 		errno = ENAMETOOLONG;
337 	}
338 	return (fdd);
339 }
340 
341 /*
342  * Stat a file with an arbitrarily long path name. If we aren't doing a
343  * stat on the arg passed to _xftw() and if the original depth arg >= 2,
344  * use openat() to make sure that it doesn't fail with ENAMETOOLONG.
345  */
346 static int
347 nocdstat(const char *path, struct stat *statp, struct Var *vp, int sym)
348 {
349 	int fd, cfd;
350 	char *dirp, *token, *ptr;
351 	int rc;
352 	const char *unrootp;
353 	int save_err;
354 
355 	rc = fstatat(AT_FDCWD, path, statp, sym);
356 	if ((vp->level > 1) && (vp->odepth >= 2) && (rc < 0) &&
357 	    (errno == ENAMETOOLONG)) {
358 		/* Traverse path using openat() to get fd for fstatat(). */
359 		if ((dirp = strdup(path)) == NULL) {
360 			errno = ENAMETOOLONG;
361 			return (-1);
362 		}
363 		if ((token = strtok_r(dirp, "/", &ptr)) != NULL) {
364 		    if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) {
365 			(void) free(dirp);
366 			errno = ENAMETOOLONG;
367 			return (-1);
368 		    }
369 		    unrootp = get_unrooted(path);
370 		    while (((token = strtok_r(NULL, "/", &ptr)) != NULL) &&
371 			(strcmp(token, unrootp) != 0)) {
372 			    if ((cfd = openat(fd, token, O_RDONLY)) < 0) {
373 				(void) close(fd);
374 				(void) free(dirp);
375 				errno = ENAMETOOLONG;
376 				return (NULL);
377 			    }
378 			    (void) close(fd);
379 			    fd = cfd;
380 		    }
381 		    (void) free(dirp);
382 		    rc = fstatat(fd, unrootp, statp, sym);
383 		    save_err = errno;
384 		    (void) close(fd);
385 		    errno = save_err;
386 		    return (rc);
387 		}
388 		(void) free(dirp);
389 		errno = ENAMETOOLONG;
390 	}
391 	return (rc);
392 }
393 
394 /*
395  * Return pointer basename of path.  This routine doesn't remove
396  * trailing slashes, but there won't be any.
397  */
398 static const char *
399 get_unrooted(const char *path)
400 {
401 	const char *ptr;
402 
403 	if (!path || !*path)
404 		return (NULL);
405 
406 	ptr = path + strlen(path);
407 	/* find last char in path before any trailing slashes */
408 	while (ptr != path && *--ptr == '/')
409 		;
410 
411 	if (ptr == path)	/* all slashes */
412 		return (ptr);
413 
414 	while (ptr != path)
415 		if (*--ptr == '/')
416 			return (++ptr);
417 
418 	return (ptr);
419 }
420