xref: /illumos-gate/usr/src/lib/libc/port/gen/_xftw.c (revision efd4c9b63ad77503c101fc6c2ed8ba96c9d52964)
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