xref: /illumos-gate/usr/src/lib/libc/port/gen/nftw.c (revision 141040e8a310da49386b596573e5dde5580572ec)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 #pragma ident	"%Z%%M%	%I%	%E% SMI"
29 
30 /*	Copyright (c) 1988 AT&T	*/
31 /*	  All Rights Reserved  	*/
32 
33 
34 /*
35  *	nftw - new file tree walk
36  *
37  *	int nftw(char *path, int (*fn)(), int depth, int flags);
38  *
39  *	Derived from System V ftw() by David Korn
40  *
41  *	nftw visits each file and directory in the tree starting at
42  *	path. It uses the generic directory reading library so it works
43  *	for any file system type.  The flags field is used to specify:
44  *		FTW_PHYS  Physical walk, does not follow symblolic links
45  *			  Otherwise, nftw will follow links but will not
46  *			  walk down any path the crosses itself.
47  *		FTW_MOUNT The walk will not cross a mount point.
48  *		FTW_DEPTH All subdirectories will be visited before the
49  *			  directory itself.
50  *		FTW_CHDIR The walk will change to each directory before
51  *			  reading it.  This is faster but core dumps
52  *			  may not get generated.
53  *
54  *	The following flags are private, and are used by the find
55  *	utility:
56  *		FTW_ANYERR Call the callback function and return
57  *			   FTW_NS on any stat failure, not just
58  *			   lack of permission.
59  *		FTW_HOPTION Use stat the first time the walk
60  *			    function is called, regardless of
61  *			    whether or not FTW_PHYS is specified.
62  *
63  *	fn is called with four arguments at each file and directory.
64  *	The first argument is the pathname of the object, the second
65  *	is a pointer to the stat buffer and the third is an integer
66  *	giving additional information as follows:
67  *
68  *		FTW_F	The object is a file.
69  *		FTW_D	The object is a directory.
70  *		FTW_DP	The object is a directory and subdirectories
71  *			have been visited.
72  *		FTW_SL	The object is a symbolic link.
73  *		FTW_SLN The object is a symbolic link pointing at a
74  *		        non-existing file.
75  *		FTW_DNR	The object is a directory that cannot be read.
76  *			fn will not be called for any of its descendants.
77  *		FTW_NS	Stat failed on the object because of lack of
78  *			appropriate permission. The stat buffer passed to fn
79  *			is undefined.  Stat failure for any reason is
80  *			considered an error and nftw will return -1.
81  *	The fourth argument is a struct FTW* which contains the depth
82  *	and the offset into pathname to the base name.
83  *	If fn returns nonzero, nftw returns this value to its caller.
84  *
85  *	depth limits the number of open directories that ftw uses
86  *	before it starts recycling file descriptors.  In general,
87  *	a file descriptor is used for each level.
88  *
89  */
90 
91 #include <sys/feature_tests.h>
92 
93 #if !defined(_LP64) && _FILE_OFFSET_BITS == 64
94 #pragma weak nftw64 = _nftw64
95 #define	_nftw		_nftw64
96 #define	fstat64		_fstat64
97 #define	lstat64		_lstat64
98 #define	readdir64	_readdir64
99 #define	stat64		_stat64
100 #else
101 #pragma weak nftw = _nftw
102 #define	fstat		_fstat
103 #define	lstat		_lstat
104 #define	readdir		_readdir
105 #define	stat		_stat
106 #endif /* !_LP64 && _FILE_OFFSET_BITS == 64 */
107 
108 #define	chdir		_chdir
109 #define	closedir	_closedir
110 #define	fchdir		_fchdir
111 #define	fprintf		_fprintf
112 #define	getcwd		_getcwd
113 #define	opendir		_opendir
114 #define	seekdir		_seekdir
115 #define	strerror	_strerror
116 #define	telldir		_telldir
117 
118 #include "lint.h"
119 #include <mtlib.h>
120 #include <sys/types.h>
121 #include <sys/stat.h>
122 #include <dirent.h>
123 #include <errno.h>
124 #include <limits.h>
125 #include <ftw.h>
126 #include <stdlib.h>
127 #include <string.h>
128 #include <unistd.h>
129 #include <thread.h>
130 #include <synch.h>
131 #include <stdio.h>
132 
133 #ifndef PATH_MAX
134 #define	PATH_MAX	1023
135 #endif
136 
137 /*
138  * Local variables (used to be static local).
139  * Putting them into a structure that is passed
140  * around makes nftw() MT-safe with no locking required.
141  */
142 struct Var {
143 	char	*fullpath;
144 	char	*tmppath;
145 	int	curflags;
146 	dev_t	cur_mount;
147 	struct FTW state;
148 	int	walklevel;
149 	int	(*statf)(const char *, struct stat *);
150 	int	(*savedstatf)(const char *, struct stat *);
151 };
152 
153 struct Save {
154 	struct Save *last;
155 	DIR	*fd;
156 	char	*comp;
157 	long	here;
158 	dev_t	dev;
159 	ino_t	inode;
160 };
161 
162 static int oldclose(struct Save *);
163 
164 /*
165  * This is the recursive walker.
166  */
167 static int
168 walk(char *component,
169     int (*fn)(const char *, const struct stat *, int, struct FTW *),
170     int depth, struct Save *last, struct Var *vp)
171 {
172 	struct stat statb;
173 	char *p;
174 	int type;
175 	char *comp;
176 	struct dirent *dir;
177 	char *q;
178 	int rc = 0;
179 	int val = -1;
180 	int cdval = -1;
181 	int oldbase;
182 	int skip;
183 	struct Save this;
184 
185 	this.last = last;
186 	this.fd = 0;
187 	if ((vp->curflags & FTW_CHDIR) && last)
188 		comp = last->comp;
189 	else
190 		comp = vp->tmppath;
191 
192 	if (vp->savedstatf == NULL)
193 		vp->savedstatf = vp->statf;
194 
195 	if ((vp->walklevel++ == 0) && (vp->curflags & FTW_HOPTION))
196 		vp->statf = stat;
197 	else
198 		vp->statf = vp->savedstatf;
199 
200 	/*
201 	 * Determine the type of the component.
202 	 */
203 	if ((*vp->statf)(comp, &statb) >= 0) {
204 		if ((statb.st_mode & S_IFMT) == S_IFDIR) {
205 			type = FTW_D;
206 			if (depth <= 1)
207 				(void) oldclose(last);
208 			if ((this.fd = opendir(comp)) == 0) {
209 				if (errno == EMFILE && oldclose(last) &&
210 				    (this.fd = opendir(comp)) != 0) {
211 					depth = 1;
212 				} else {
213 					type = FTW_DNR;
214 					goto fail;
215 				}
216 			}
217 			if (statb.st_fstype[0] == 'a' &&
218 			    strcmp(statb.st_fstype, "autofs") == 0) {
219 				/*
220 				 * this dir is on autofs
221 				 */
222 				if (fstat(this.fd->dd_fd, &statb) < 0) {
223 					(void) closedir(this.fd);
224 					type = FTW_NS;
225 					goto fail;
226 				}
227 			}
228 		} else if ((statb.st_mode & S_IFMT) == S_IFLNK) {
229 			type = FTW_SL;
230 		} else {
231 			type = FTW_F;
232 		}
233 	} else if ((vp->curflags & FTW_ANYERR) && errno != ENOENT) {
234 		/*
235 		 * If FTW_ANYERR is specified, then a stat error
236 		 * other than ENOENT automatically results in
237 		 * failure.  This allows the callback function
238 		 * to properly handle ENAMETOOLONG and ELOOP and
239 		 * things of that nature, that would be masked
240 		 * by calling lstat before failing.
241 		 */
242 		type = FTW_NS;
243 		goto fail;
244 	} else {
245 		/*
246 		 * Statf has failed. If stat was used instead of lstat,
247 		 * try using lstat. If lstat doesn't fail, "comp"
248 		 * must be a symbolic link pointing to a non-existent
249 		 * file. Such a symbolic link should be ignored.
250 		 * Also check the file type, if possible, for symbolic
251 		 * link.
252 		 */
253 		if ((vp->statf == stat) && (lstat(comp, &statb) >= 0) &&
254 		    ((statb.st_mode & S_IFMT) == S_IFLNK)) {
255 
256 			/*
257 			 * Ignore bad symbolic link, let "fn"
258 			 * report it.
259 			 */
260 
261 			errno = ENOENT;
262 			type = FTW_SLN;
263 		} else {
264 			type = FTW_NS;
265 	fail:
266 			/*
267 			 * if FTW_ANYERR is set in flags, we call
268 			 * the user function with FTW_NS set, regardless
269 			 * of the reason stat failed.
270 			 */
271 			if (!(vp->curflags & FTW_ANYERR))
272 				if (errno != EACCES)
273 					return (-1);
274 		}
275 	}
276 
277 	/*
278 	 * If the walk is not supposed to cross a mount point,
279 	 * and it did, get ready to return.
280 	 */
281 	if ((vp->curflags & FTW_MOUNT) && type != FTW_NS &&
282 	    statb.st_dev != vp->cur_mount)
283 		goto quit;
284 	vp->state.quit = 0;
285 
286 	/*
287 	 * If current component is not a directory, call user
288 	 * specified function and get ready to return.
289 	 */
290 	if (type != FTW_D || (vp->curflags & FTW_DEPTH) == 0)
291 		rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
292 	if (rc > 0)
293 		val = rc;
294 	skip = (vp->state.quit & FTW_SKD);
295 	if (rc != 0 || type != FTW_D || (vp->state.quit & FTW_PRUNE))
296 		goto quit;
297 
298 	if (vp->tmppath[0] != '\0' && component[-1] != '/')
299 		*component++ = '/';
300 	if (vp->curflags & FTW_CHDIR) {
301 		struct stat statb2;
302 
303 		*component = 0;
304 		/*
305 		 * Security check (there is a window between
306 		 * (*vp->statf)() and opendir() above).
307 		 */
308 		if ((vp->curflags & FTW_PHYS) &&
309 		    (fstat(this.fd->dd_fd, &statb2) < 0 ||
310 		    statb2.st_ino != statb.st_ino ||
311 		    statb2.st_dev != statb.st_dev)) {
312 			errno = EAGAIN;
313 			rc = -1;
314 			goto quit;
315 		}
316 
317 		if ((cdval = fchdir(this.fd->dd_fd)) >= 0) {
318 			this.comp = component;
319 		} else {
320 			type = FTW_DNR;
321 			rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
322 			goto quit;
323 		}
324 	}
325 
326 	/*
327 	 * If the walk has followed a symbolic link, traverse
328 	 * the walk back to make sure there is not a loop.
329 	 *
330 	 * XXX - may need to look at this
331 	 * There's code to check for cycles, but only for FTW_PHYS
332 	 * (find -L flag).  However, all directories should be
333 	 * checked, even if not following links because of hardlinks
334 	 * to directories (not recommended, but can exist).
335 	 *
336 	 * We might have added AVL tree routines here to store and search
337 	 * the inodes and devices, as is done for du/ls/chgrp/chown,
338 	 * but libcmdutils is for for internal use only, so we can't
339 	 * add it to a public libc function (nftw()).
340 	 */
341 	if ((vp->curflags & FTW_PHYS) == 0) {
342 		struct Save *sp = last;
343 		while (sp) {
344 			/*
345 			 * If the same node has already been visited, there
346 			 * is a loop. Get ready to return.
347 			 */
348 			if (sp->dev == statb.st_dev &&
349 			    sp->inode == statb.st_ino)
350 				goto quit;
351 			sp = sp->last;
352 		}
353 	}
354 	this.dev = statb.st_dev;
355 	this.inode = statb.st_ino;
356 	oldbase = vp->state.base;
357 	vp->state.base = (int)(component - vp->tmppath);
358 	while (dir = readdir(this.fd)) {
359 		if (dir->d_ino == 0)
360 			continue;
361 		q = dir->d_name;
362 		if (*q == '.') {
363 			if (q[1] == 0)
364 				continue;
365 			else if (q[1] == '.' && q[2] == 0)
366 				continue;
367 		}
368 		p = component;
369 		while (p < &vp->tmppath[PATH_MAX] && *q != '\0')
370 			*p++ = *q++;
371 		*p = '\0';
372 		vp->state.level++;
373 
374 		/* Call walk() recursively.  */
375 		rc = walk(p, fn, depth-1, &this, vp);
376 		vp->state.level--;
377 		if (this.fd == 0) {
378 			*component = 0;
379 			if (vp->curflags & FTW_CHDIR) {
380 				this.fd = opendir(".");
381 			} else {
382 				this.fd = opendir(comp);
383 			}
384 			if (this.fd == 0) {
385 				rc = -1;
386 				goto quit;
387 			}
388 			seekdir(this.fd, this.here);
389 		}
390 		if (rc != 0) {
391 			if (errno == ENOENT) {
392 				(void) fprintf(stderr, "cannot open %s: %s\n",
393 				    vp->tmppath, strerror(errno));
394 				val = rc;
395 				continue;
396 			}
397 			goto quit;	/* this seems extreme */
398 		}
399 	}
400 	vp->state.base = oldbase;
401 	*--component = 0;
402 	type = FTW_DP;
403 	if ((vp->tmppath[0] != '\0') && (vp->curflags & FTW_DEPTH) && !skip)
404 		rc = (*fn)(vp->tmppath, &statb, type, &vp->state);
405 quit:
406 	if (cdval >= 0 && last) {
407 		/* try to change back to previous directory */
408 		if (last->fd != NULL) {
409 			if (fchdir(last->fd->dd_fd) < 0) {
410 				rc = -1;
411 			}
412 		} else {
413 			if ((cdval = chdir("..")) >= 0) {
414 				if ((*vp->statf)(".", &statb) < 0 ||
415 				    statb.st_ino != last->inode ||
416 				    statb.st_dev != last->dev)
417 					cdval = -1;
418 			}
419 			*comp = 0;
420 			if (cdval < 0) {
421 				if (chdir(vp->fullpath) < 0) {
422 					rc = -1;
423 				} else {
424 					/* Security check */
425 					if ((vp->curflags & FTW_PHYS) &&
426 					    ((*vp->statf)(".", &statb) < 0 ||
427 					    statb.st_ino != last->inode ||
428 					    statb.st_dev != last->dev)) {
429 						errno = EAGAIN;
430 						rc = -1;
431 					}
432 				}
433 			}
434 		}
435 	}
436 	if (this.fd)
437 		(void) closedir(this.fd);
438 	if (val > rc)
439 		return (val);
440 	else
441 		return (rc);
442 }
443 
444 int
445 _nftw(const char *path,
446     int (*fn)(const char *, const struct stat *, int, struct FTW *),
447     int depth, int flags)
448 {
449 	struct Var var;
450 	struct stat statb;
451 	char home[2*(PATH_MAX+1)];
452 	int rc = -1;
453 	char *dp;
454 	char *base;
455 	char *endhome;
456 	const char *savepath = path;
457 	int save_errno;
458 
459 	home[0] = 0;
460 
461 	/*
462 	 * If the walk is going to change directory before
463 	 * reading it, save current woring directory.
464 	 */
465 	if (flags & FTW_CHDIR) {
466 		if (getcwd(home, PATH_MAX+1) == 0)
467 			return (-1);
468 	}
469 	endhome = dp = home + strlen(home);
470 	if (*path == '/')
471 		var.fullpath = dp;
472 	else {
473 		*dp++ = '/';
474 		var.fullpath = home;
475 	}
476 	var.tmppath =  dp;
477 	base = dp-1;
478 	while (*path && dp < &var.tmppath[PATH_MAX]) {
479 		*dp = *path;
480 		if (*dp == '/')
481 			base = dp;
482 		dp++, path++;
483 	}
484 	*dp = 0;
485 	var.state.base = (int)(base + 1 - var.tmppath);
486 	if (*path) {
487 		errno = ENAMETOOLONG;
488 		return (-1);
489 	}
490 	var.curflags = flags;
491 
492 	/*
493 	 * If doing a physical walk (not following symbolic link), set
494 	 * var.statf to lstat(). Otherwise, set var.statf to stat().
495 	 */
496 	if ((flags & FTW_PHYS) == 0)
497 		var.statf = stat;
498 	else
499 		var.statf = lstat;
500 
501 	/*
502 	 * If walk is not going to cross a mount point,
503 	 * save the current mount point.
504 	 */
505 	if (flags & FTW_MOUNT) {
506 		if ((*var.statf)(savepath, &statb) >= 0)
507 			var.cur_mount = statb.st_dev;
508 		else
509 			goto done;
510 	}
511 	var.state.level = 0;
512 
513 	/*
514 	 * Call walk() which does most of the work.
515 	 * walk() uses errno in a rather obtuse way
516 	 * so we shield any incoming errno.
517 	 */
518 	save_errno = errno;
519 	errno = 0;
520 	var.savedstatf = NULL;
521 	var.walklevel = 0;
522 	rc = walk(dp, fn, depth, (struct Save *)0, &var);
523 	if (errno == 0)
524 		errno = save_errno;
525 done:
526 	*endhome = 0;
527 	if (flags & FTW_CHDIR)
528 		(void) chdir(home);
529 	return (rc);
530 }
531 
532 /*
533  * close the oldest directory.  It saves the seek offset.
534  * return value is 0 unless it was unable to close any descriptor
535  */
536 
537 static int
538 oldclose(struct Save *sp)
539 {
540 	struct Save *spnext;
541 	while (sp) {
542 		spnext = sp->last;
543 		if (spnext == 0 || spnext->fd == 0)
544 			break;
545 		sp = spnext;
546 	}
547 	if (sp == 0 || sp->fd == 0)
548 		return (0);
549 	sp->here = telldir(sp->fd);
550 	(void) closedir(sp->fd);
551 	sp->fd = 0;
552 	return (1);
553 }
554