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