xref: /illumos-gate/usr/src/cmd/ndmpd/tlm/tlm_traverse.c (revision 09a48d4ca0ddda4ad26cc885769745870d989baf)
1 /*
2  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
3  */
4 
5 /*
6  * BSD 3 Clause License
7  *
8  * Copyright (c) 2007, The Storage Networking Industry Association.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 	- Redistributions of source code must retain the above copyright
14  *	  notice, this list of conditions and the following disclaimer.
15  *
16  * 	- Redistributions in binary form must reproduce the above copyright
17  *	  notice, this list of conditions and the following disclaimer in
18  *	  the documentation and/or other materials provided with the
19  *	  distribution.
20  *
21  *	- Neither the name of The Storage Networking Industry Association (SNIA)
22  *	  nor the names of its contributors may be used to endorse or promote
23  *	  products derived from this software without specific prior written
24  *	  permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
30  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 /*
39  * This file implemets the post-order, pre-order and level-order
40  * traversing of the file system.  The related macros and constants
41  * are defined in traverse.h.
42  */
43 
44 #include <sys/stat.h>
45 #include <sys/types.h>
46 #include <sys/param.h>
47 #include <assert.h>
48 #include <cstack.h>
49 #include <dirent.h>
50 #include <errno.h>
51 #include <traverse.h>
52 #include <limits.h>
53 #include <stdarg.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <syslog.h>
58 #include <fcntl.h>
59 #include <unistd.h>
60 #include <tlm.h>
61 #include "tlm_proto.h"
62 
63 /*
64  * Check if it's "." or ".."
65  */
66 boolean_t
67 rootfs_dot_or_dotdot(char *name)
68 {
69 	if (*name != '.')
70 		return (FALSE);
71 
72 	if ((name[1] == 0) || (name[1] == '.' && name[2] == 0))
73 		return (TRUE);
74 
75 	return (FALSE);
76 }
77 
78 /*
79  * Macros on fs_traverse flags.
80  */
81 #define	STOP_ONERR(f)	((f)->ft_flags & FST_STOP_ONERR)
82 #define	STOP_ONLONG(f)	((f)->ft_flags & FST_STOP_ONLONG)
83 #define	VERBOSE(f)	((f)->ft_flags & FST_VERBOSE)
84 
85 #define	CALLBACK(pp, ep)	\
86 	(*(ftp)->ft_callbk)((ftp)->ft_arg, pp, ep)
87 
88 #define	NEGATE(rv)	((rv) =	-(rv))
89 
90 /*
91  * The traversing state that is pushed onto the stack.
92  * This include:
93  * 	- The end of the path of the current directory.
94  *	- The position of the last component on it.
95  *	- The read position in the directory.
96  *	- The file handle of the directory.
97  *	- The stat of the directory.
98  */
99 typedef struct traverse_state {
100 	char *ts_end;
101 	char *ts_ent;
102 	long ts_dpos; /* position in the directory when reading its entries */
103 	fs_fhandle_t ts_fh;
104 	struct stat64 ts_st;
105 } traverse_state_t;
106 
107 /*
108  * Statistics gathering structure.
109  */
110 typedef struct traverse_statistics {
111 	ulong_t fss_newdirs;
112 	ulong_t fss_readdir_err;
113 	ulong_t fss_longpath_err;
114 	ulong_t fss_lookup_err;
115 	ulong_t fss_nondir_calls;
116 	ulong_t fss_dir_calls;
117 	ulong_t fss_nondir_skipped;
118 	ulong_t fss_dir_skipped;
119 	ulong_t fss_pushes;
120 	ulong_t fss_pops;
121 	ulong_t fss_stack_residue;
122 } traverse_statistics_t;
123 
124 /*
125  * Global instance of statistics variable.
126  */
127 traverse_statistics_t traverse_stats;
128 
129 #define	MAX_DENT_BUF_SIZE	(8 * 1024)
130 
131 typedef struct {
132 	struct stat64 fd_attr;
133 	fs_fhandle_t fd_fh;
134 	short fd_len;
135 	char fd_name[1];
136 } fs_dent_info_t;
137 
138 typedef struct dent_arg {
139 	char *da_buf;
140 	int da_end;
141 	int da_size;
142 } dent_arg_t;
143 
144 static int traverse_level_nondir(struct fs_traverse *ftp,
145     traverse_state_t *tsp, struct fst_node *pnp,
146     dent_arg_t *darg);
147 
148 /*
149  * Gather some directory entry information and return them
150  */
151 static int
152 fs_populate_dents(void *arg, int namelen,
153     char *name, long *countp, struct stat64 *attr,
154     fs_fhandle_t *fh)
155 {
156 	dent_arg_t *darg = (dent_arg_t *)arg;
157 	int reclen = sizeof (fs_dent_info_t) + namelen;
158 	fs_dent_info_t *dent;
159 
160 	if ((darg->da_end + reclen) > darg->da_size)
161 		return (-1);
162 
163 	/* LINTED improper alignment */
164 	dent = (fs_dent_info_t *)(darg->da_buf + darg->da_end);
165 
166 	dent->fd_attr = *attr;
167 	dent->fd_fh = *fh;
168 	(void) strcpy(dent->fd_name, name);
169 
170 	dent->fd_len = reclen;
171 	darg->da_end += reclen;
172 
173 	if (countp)
174 		(*countp)++;
175 
176 	return (0);
177 }
178 
179 /*
180  * Creates a new traversing state based on the path passed to it.
181  */
182 static traverse_state_t *
183 new_tsp(char *path)
184 {
185 	traverse_state_t *tsp;
186 	tsp = ndmp_malloc(sizeof (traverse_state_t));
187 	if (!tsp)
188 		return (NULL);
189 
190 	tsp->ts_end = strchr(path, '\0');
191 	if (*(tsp->ts_end-1) == '/')
192 		*--tsp->ts_end = '\0';
193 	tsp->ts_ent = NULL;
194 	tsp->ts_dpos = 0;
195 
196 	return (tsp);
197 }
198 
199 /*
200  * Create a file handle and get stats for the given path
201  */
202 int
203 fs_getstat(char *path, fs_fhandle_t *fh, struct stat64 *st)
204 {
205 	if (lstat64(path, st) == -1)
206 		return (errno);
207 
208 	fh->fh_fid = st->st_ino;
209 
210 	if (!S_ISDIR(st->st_mode))
211 		fh->fh_fpath = NULL;
212 	else
213 		fh->fh_fpath = strdup(path);
214 	return (0);
215 }
216 
217 /*
218  * Get directory entries info and return in the buffer. Cookie
219  * will keep the state of each call
220  */
221 static int
222 fs_getdents(int fildes, struct dirent *buf, size_t *nbyte,
223     char *pn_path, long *dpos, longlong_t *cookie,
224     long *n_entries, dent_arg_t *darg)
225 {
226 	struct dirent *ptr;
227 	char file_path[PATH_MAX + 1];
228 	fs_fhandle_t fh;
229 	struct stat64 st;
230 	char *p;
231 	int len;
232 	int rv;
233 
234 	if (*nbyte == 0) {
235 		(void) memset((char *)buf, 0, MAX_DENT_BUF_SIZE);
236 		*nbyte = rv = getdents(fildes, buf, darg->da_size);
237 		*cookie = 0LL;
238 
239 		if (rv <= 0)
240 			return (rv);
241 	}
242 
243 	p = (char *)buf + *cookie;
244 	len = *nbyte;
245 	do {
246 		/* LINTED improper alignment */
247 		ptr = (struct dirent *)p;
248 		*dpos =  ptr->d_off;
249 
250 		if (rootfs_dot_or_dotdot(ptr->d_name))
251 			goto skip_entry;
252 
253 		(void) snprintf(file_path, PATH_MAX, "%s/", pn_path);
254 		(void) strlcat(file_path, ptr->d_name, PATH_MAX + 1);
255 		(void) memset(&fh, 0, sizeof (fs_fhandle_t));
256 
257 		if (lstat64(file_path, &st) != 0) {
258 			rv = -1;
259 			break;
260 		}
261 
262 		fh.fh_fid = st.st_ino;
263 
264 		if (S_ISDIR(st.st_mode))
265 			goto skip_entry;
266 
267 		if (fs_populate_dents(darg, strlen(ptr->d_name),
268 		    (char *)ptr->d_name, n_entries, &st, &fh) != 0)
269 			break;
270 
271 skip_entry:
272 		p = p + ptr->d_reclen;
273 		len -= ptr->d_reclen;
274 	} while (len);
275 
276 	*cookie = (longlong_t)(p - (char *)buf);
277 	*nbyte = len;
278 	return (rv);
279 }
280 
281 /*
282  * Read the directory entries and return the information about
283  * each entry
284  */
285 int
286 fs_readdir(fs_fhandle_t *ts_fh, char *path, long *dpos,
287     char *nm, int *el, fs_fhandle_t *efh, struct stat64 *est)
288 {
289 	struct dirent *dp;
290 	char  file_path[PATH_MAX + 1];
291 	DIR *dirp;
292 	int rv;
293 
294 	if ((dirp = opendir(ts_fh->fh_fpath)) == NULL)
295 		return (errno);
296 
297 	seekdir(dirp, *dpos);
298 	if ((dp = readdir(dirp)) == NULL) {
299 		rv = 0;  /* skip this dir */
300 		*el = 0;
301 	} else {
302 		(void) snprintf(file_path, PATH_MAX, "%s/", path);
303 		(void) strlcat(file_path, dp->d_name, PATH_MAX + 1);
304 
305 		rv = fs_getstat(file_path, efh, est);
306 		if (rv == 0) {
307 			*dpos = telldir(dirp);
308 			(void) strlcpy(nm, dp->d_name, NAME_MAX + 1);
309 			*el = strlen(dp->d_name);
310 		} else {
311 			*el = 0;
312 		}
313 	}
314 	(void) closedir(dirp);
315 	return (rv);
316 }
317 
318 /*
319  * Traverse the file system in the post-order way.  The description
320  * and example is in the header file.
321  *
322  * The callback function should return 0, on success and non-zero on
323  * failure.  If the callback function returns non-zero return value,
324  * the traversing stops.
325  */
326 int
327 traverse_post(struct fs_traverse *ftp)
328 {
329 	char path[PATH_MAX + 1]; /* full path name of the current dir */
330 	char nm[NAME_MAX + 1]; /* directory entry name */
331 	char *lp; /* last position on the path */
332 	int next_dir, rv;
333 	int pl, el; /* path and directory entry length */
334 	cstack_t *sp;
335 	fs_fhandle_t pfh, efh;
336 	struct stat64 pst, est;
337 	traverse_state_t *tsp;
338 	struct fst_node pn, en; /* parent and entry nodes */
339 
340 	if (!ftp || !ftp->ft_path || !*ftp->ft_path || !ftp->ft_callbk) {
341 		NDMP_LOG(LOG_DEBUG, "Invalid argument");
342 		errno = EINVAL;
343 		return (-1);
344 	}
345 
346 	/* set the default log function if it's not already set */
347 	if (!ftp->ft_logfp) {
348 		ftp->ft_logfp = (ft_log_t)syslog;
349 		NDMP_LOG(LOG_DEBUG, "Log to system log \"%s\"", ftp->ft_path);
350 	}
351 
352 	/* set the logical path to physical path if it's not already set */
353 	if (!ftp->ft_lpath) {
354 		NDMP_LOG(LOG_DEBUG,
355 		    "report the same paths: \"%s\"", ftp->ft_path);
356 		ftp->ft_lpath = ftp->ft_path;
357 	}
358 
359 	pl = strlen(ftp->ft_lpath);
360 	if (pl + 1 > PATH_MAX) { /* +1 for the '/' */
361 		NDMP_LOG(LOG_DEBUG, "lpath too long \"%s\"", ftp->ft_path);
362 		errno = ENAMETOOLONG;
363 		return (-1);
364 	}
365 	(void) strcpy(path, ftp->ft_lpath);
366 	(void) memset(&pfh, 0, sizeof (pfh));
367 	rv = fs_getstat(ftp->ft_lpath, &pfh, &pst);
368 
369 	if (rv != 0) {
370 		NDMP_LOG(LOG_DEBUG,
371 		    "Error %d on fs_getstat(%s)", rv, ftp->ft_path);
372 		return (rv);
373 	}
374 
375 	if (!S_ISDIR(pst.st_mode)) {
376 		pn.tn_path = ftp->ft_lpath;
377 		pn.tn_fh = &pfh;
378 		pn.tn_st = &pst;
379 		en.tn_path = NULL;
380 		en.tn_fh = NULL;
381 		en.tn_st = NULL;
382 		rv = CALLBACK(&pn, &en);
383 		if (VERBOSE(ftp))
384 			NDMP_LOG(LOG_DEBUG, "CALLBACK(%s): %d", pn.tn_path, rv);
385 		free(pfh.fh_fpath);
386 		return (rv);
387 	}
388 
389 	sp = cstack_new();
390 	if (!sp) {
391 		errno = ENOMEM;
392 		free(pfh.fh_fpath);
393 		return (-1);
394 	}
395 	tsp = new_tsp(path);
396 	if (!tsp) {
397 		cstack_delete(sp);
398 		errno = ENOMEM;
399 		free(pfh.fh_fpath);
400 		return (-1);
401 	}
402 	tsp->ts_ent = tsp->ts_end;
403 	tsp->ts_fh = pfh;
404 	tsp->ts_st = pst;
405 	pn.tn_path = path;
406 	pn.tn_fh = &tsp->ts_fh;
407 	pn.tn_st = &tsp->ts_st;
408 
409 	rv = 0;
410 	next_dir = 1;
411 	do {
412 		if (next_dir) {
413 			traverse_stats.fss_newdirs++;
414 
415 			*tsp->ts_end = '\0';
416 			if (VERBOSE(ftp))
417 				NDMP_LOG(LOG_DEBUG, "pl %d \"%s\"", pl, path);
418 		}
419 
420 		next_dir = 0;
421 		do {
422 			el = NAME_MAX;
423 			rv = fs_readdir(&tsp->ts_fh, pn.tn_path,
424 			    &tsp->ts_dpos, nm, &el,
425 			    &efh, &est);
426 
427 			if (rv != 0) {
428 				free(efh.fh_fpath);
429 				traverse_stats.fss_readdir_err++;
430 
431 				NDMP_LOG(LOG_DEBUG,
432 				    "Error %d on readdir(%s) pos %d",
433 				    rv, path, tsp->ts_dpos);
434 				if (STOP_ONERR(ftp))
435 					break;
436 				rv = SKIP_ENTRY;
437 
438 				continue;
439 			}
440 
441 			/* done with this directory */
442 			if (el == 0) {
443 				if (VERBOSE(ftp))
444 					NDMP_LOG(LOG_DEBUG,
445 					    "Done(%s)", pn.tn_path);
446 				break;
447 			}
448 			nm[el] = '\0';
449 
450 			if (rootfs_dot_or_dotdot(nm)) {
451 				free(efh.fh_fpath);
452 				continue;
453 			}
454 
455 			if (VERBOSE(ftp))
456 				NDMP_LOG(LOG_DEBUG, "%u dname: \"%s\"",
457 				    tsp->ts_dpos, nm);
458 
459 			if (pl + 1 + el > PATH_MAX) {
460 				traverse_stats.fss_longpath_err++;
461 
462 				NDMP_LOG(LOG_ERR, "Path %s/%s is too long.",
463 				    path, nm);
464 				if (STOP_ONLONG(ftp))
465 					rv = ENAMETOOLONG;
466 				free(efh.fh_fpath);
467 				continue;
468 			}
469 
470 			/*
471 			 * Push the current directory on to the stack and
472 			 * dive into the entry found.
473 			 */
474 			if (S_ISDIR(est.st_mode)) {
475 
476 				assert(tsp != NULL);
477 				if (cstack_push(sp, tsp, 0)) {
478 					rv = ENOMEM;
479 					free(efh.fh_fpath);
480 					break;
481 				}
482 				traverse_stats.fss_pushes++;
483 
484 				/*
485 				 * Concatenate the current entry with the
486 				 * current path.  This will be the path of
487 				 * the new directory to be scanned.
488 				 *
489 				 * Note:
490 				 * sprintf(tsp->ts_end, "/%s", de->d_name);
491 				 * could be used here, but concatenating
492 				 * strings like this might be faster.
493 				 * The length of the new path has been
494 				 * checked above.  So strcpy() can be
495 				 * safe and should not lead to a buffer
496 				 * over-run.
497 				 */
498 				lp = tsp->ts_end;
499 				*tsp->ts_end = '/';
500 				(void) strcpy(tsp->ts_end + 1, nm);
501 
502 				tsp = new_tsp(path);
503 				if (!tsp) {
504 					free(efh.fh_fpath);
505 					rv = ENOMEM;
506 				} else {
507 					next_dir = 1;
508 					pl += el;
509 					tsp->ts_fh = efh;
510 					tsp->ts_st = est;
511 					tsp->ts_ent = lp;
512 					pn.tn_fh = &tsp->ts_fh;
513 					pn.tn_st = &tsp->ts_st;
514 				}
515 				break;
516 			} else {
517 				/*
518 				 * The entry is not a directory so the
519 				 * callback function must be called.
520 				 */
521 				traverse_stats.fss_nondir_calls++;
522 
523 				en.tn_path = nm;
524 				en.tn_fh = &efh;
525 				en.tn_st = &est;
526 				rv = CALLBACK(&pn, &en);
527 				free(efh.fh_fpath);
528 				if (VERBOSE(ftp))
529 					NDMP_LOG(LOG_DEBUG,
530 					    "CALLBACK(%s/%s): %d",
531 					    pn.tn_path, en.tn_path, rv);
532 
533 				if (rv != 0)
534 					break;
535 			}
536 		} while (rv == 0);
537 
538 		/*
539 		 * A new directory must be processed, go to the start of
540 		 * the loop, open it and process it.
541 		 */
542 		if (next_dir)
543 			continue;
544 
545 		if (rv == SKIP_ENTRY)
546 			rv = 0; /* We should skip the current directory */
547 
548 		if (rv == 0) {
549 			/*
550 			 * Remove the ent from the end of path and send it
551 			 * as an entry of the path.
552 			 */
553 			lp = tsp->ts_ent;
554 			*lp = '\0';
555 			efh = tsp->ts_fh;
556 			est = tsp->ts_st;
557 			free(tsp);
558 			if (cstack_pop(sp, (void **)&tsp, (int *)NULL))
559 				break;
560 
561 			assert(tsp != NULL);
562 			pl = tsp->ts_end - path;
563 
564 			if (VERBOSE(ftp))
565 				NDMP_LOG(LOG_DEBUG, "poped pl %d 0x%p \"%s\"",
566 				    pl, tsp, path);
567 
568 			traverse_stats.fss_pops++;
569 			traverse_stats.fss_dir_calls++;
570 
571 			pn.tn_fh = &tsp->ts_fh;
572 			pn.tn_st = &tsp->ts_st;
573 			en.tn_path = lp + 1;
574 			en.tn_fh = &efh;
575 			en.tn_st = &est;
576 
577 			rv = CALLBACK(&pn, &en);
578 			free(efh.fh_fpath);
579 			if (VERBOSE(ftp))
580 				NDMP_LOG(LOG_DEBUG, "CALLBACK(%s/%s): %d",
581 				    pn.tn_path, en.tn_path, rv);
582 			/*
583 			 * Does not need to free tsp here.  It will be released
584 			 * later.
585 			 */
586 		}
587 
588 		if (rv != 0 && tsp) {
589 			free(tsp->ts_fh.fh_fpath);
590 			free(tsp);
591 		}
592 
593 	} while (rv == 0);
594 
595 	/*
596 	 * For the 'ftp->ft_path' directory itself.
597 	 */
598 	if (rv == 0) {
599 		traverse_stats.fss_dir_calls++;
600 
601 		pn.tn_fh = &efh;
602 		pn.tn_st = &est;
603 		en.tn_path = NULL;
604 		en.tn_fh = NULL;
605 		en.tn_st = NULL;
606 		rv = CALLBACK(&pn, &en);
607 		if (VERBOSE(ftp))
608 			NDMP_LOG(LOG_DEBUG, "CALLBACK(%s): %d", pn.tn_path, rv);
609 	}
610 
611 	/*
612 	 * Pop and free all the remaining entries on the stack.
613 	 */
614 	while (!cstack_pop(sp, (void **)&tsp, (int *)NULL)) {
615 		traverse_stats.fss_stack_residue++;
616 
617 		free(tsp->ts_fh.fh_fpath);
618 		free(tsp);
619 	}
620 
621 	cstack_delete(sp);
622 	return (rv);
623 }
624 
625 /*
626  * In one pass, read all the directory entries of the specified
627  * directory and call the callback function for non-directory
628  * entries.
629  *
630  * On return:
631  *    0: Lets the directory to be scanned for directory entries.
632  *    < 0: Completely stops traversing.
633  *    FST_SKIP: stops further scanning of the directory.  Traversing
634  *        will continue with the next directory in the hierarchy.
635  *    SKIP_ENTRY: Failed to get the directory entries, so the caller
636  *	  should skip this entry.
637  */
638 static int
639 traverse_level_nondir(struct fs_traverse *ftp,
640     traverse_state_t *tsp, struct fst_node *pnp, dent_arg_t *darg)
641 {
642 	int pl; /* path length */
643 	int rv;
644 	struct fst_node en; /* entry node */
645 	longlong_t cookie_verf;
646 	fs_dent_info_t *dent;
647 	struct dirent *buf;
648 	size_t len = 0;
649 	int fd;
650 
651 	rv = 0;
652 	pl = strlen(pnp->tn_path);
653 
654 	buf = ndmp_malloc(MAX_DENT_BUF_SIZE);
655 	if (buf == NULL)
656 		return (errno);
657 
658 	fd = open(tsp->ts_fh.fh_fpath, O_RDONLY);
659 	if (fd == -1) {
660 		free(buf);
661 		return (errno);
662 	}
663 
664 	while (rv == 0) {
665 		long i, n_entries;
666 
667 		darg->da_end = 0;
668 		n_entries = 0;
669 		rv = fs_getdents(fd, buf, &len, pnp->tn_path, &tsp->ts_dpos,
670 		    &cookie_verf, &n_entries, darg);
671 		if (rv < 0) {
672 			traverse_stats.fss_readdir_err++;
673 
674 			NDMP_LOG(LOG_DEBUG, "Error %d on readdir(%s) pos %d",
675 			    rv, pnp->tn_path, tsp->ts_dpos);
676 			if (STOP_ONERR(ftp))
677 				break;
678 			/*
679 			 * We cannot read the directory entry, we should
680 			 * skip to the next directory.
681 			 */
682 			rv = SKIP_ENTRY;
683 			continue;
684 		} else {
685 			/* Break at the end of directory */
686 			if (rv > 0)
687 				rv = 0;
688 			else
689 				break;
690 		}
691 
692 		/* LINTED imporper alignment */
693 		dent = (fs_dent_info_t *)darg->da_buf;
694 		/* LINTED imporper alignment */
695 		for (i = 0; i < n_entries; i++, dent = (fs_dent_info_t *)
696 		    ((char *)dent + dent->fd_len)) {
697 
698 			if (VERBOSE(ftp))
699 				NDMP_LOG(LOG_DEBUG, "i %u dname: \"%s\"",
700 				    dent->fd_fh.fh_fid, dent->fd_name);
701 
702 			if ((pl + strlen(dent->fd_name)) > PATH_MAX) {
703 				traverse_stats.fss_longpath_err++;
704 
705 				NDMP_LOG(LOG_ERR, "Path %s/%s is too long.",
706 				    pnp->tn_path, dent->fd_name);
707 				if (STOP_ONLONG(ftp))
708 					rv = -ENAMETOOLONG;
709 				free(dent->fd_fh.fh_fpath);
710 				continue;
711 			}
712 
713 			/*
714 			 * The entry is not a directory so the callback
715 			 * function must be called.
716 			 */
717 			if (!S_ISDIR(dent->fd_attr.st_mode)) {
718 				traverse_stats.fss_nondir_calls++;
719 
720 				en.tn_path = dent->fd_name;
721 				en.tn_fh = &dent->fd_fh;
722 				en.tn_st = &dent->fd_attr;
723 				rv = CALLBACK(pnp, &en);
724 				dent->fd_fh.fh_fpath = NULL;
725 				if (rv < 0)
726 					break;
727 				if (rv == FST_SKIP) {
728 					traverse_stats.fss_nondir_skipped++;
729 					break;
730 				}
731 			}
732 		}
733 	}
734 
735 	free(buf);
736 	(void) close(fd);
737 	return (rv);
738 }
739 
740 /*
741  * Traverse the file system in the level-order way.  The description
742  * and example is in the header file.
743  */
744 int
745 traverse_level(struct fs_traverse *ftp)
746 {
747 	char path[PATH_MAX + 1];	/* full path name of the current dir */
748 	char nm[NAME_MAX + 1];	/* directory entry name */
749 	char *lp;		/* last position on the path */
750 	int next_dir, rv;
751 	int pl, el;		/* path and directory entry length */
752 
753 	cstack_t *sp;
754 	fs_fhandle_t pfh, efh;
755 	struct stat64 pst, est;
756 	traverse_state_t *tsp;
757 	struct fst_node pn, en;  /* parent and entry nodes */
758 	dent_arg_t darg;
759 
760 	if (!ftp || !ftp->ft_path || !*ftp->ft_path || !ftp->ft_callbk) {
761 		NDMP_LOG(LOG_DEBUG, "Invalid argument");
762 		errno = EINVAL;
763 		return (-1);
764 	}
765 	/* set the default log function if it's not already set */
766 	if (!ftp->ft_logfp) {
767 		ftp->ft_logfp = (ft_log_t)syslog;
768 		NDMP_LOG(LOG_DEBUG, "Log to system log \"%s\"", ftp->ft_path);
769 	}
770 	if (!ftp->ft_lpath) {
771 		NDMP_LOG(LOG_DEBUG,
772 		    "report the same paths \"%s\"", ftp->ft_path);
773 		ftp->ft_lpath = ftp->ft_path;
774 	}
775 
776 	pl = strlen(ftp->ft_lpath);
777 	if (pl + 1 > PATH_MAX) { /* +1 for the '/' */
778 		NDMP_LOG(LOG_DEBUG, "lpath too long \"%s\"", ftp->ft_path);
779 		errno = ENAMETOOLONG;
780 		return (-1);
781 	}
782 	(void) strcpy(path, ftp->ft_lpath);
783 	(void) memset(&pfh, 0, sizeof (pfh));
784 	rv = fs_getstat(ftp->ft_lpath, &pfh, &pst);
785 	if (rv != 0) {
786 		NDMP_LOG(LOG_DEBUG,
787 		    "Error %d on fs_getstat(%s)", rv, ftp->ft_path);
788 		return (-1);
789 	}
790 
791 	en.tn_path = NULL;
792 	en.tn_fh = NULL;
793 	en.tn_st = NULL;
794 	if (!S_ISDIR(pst.st_mode)) {
795 		pn.tn_path = ftp->ft_lpath;
796 		pn.tn_fh = &pfh;
797 		pn.tn_st = &pst;
798 		rv = CALLBACK(&pn, &en);
799 		if (VERBOSE(ftp))
800 			NDMP_LOG(LOG_DEBUG, "CALLBACK(%s): %d", pn.tn_path, rv);
801 
802 		free(pfh.fh_fpath);
803 		return (rv);
804 	}
805 
806 	sp = cstack_new();
807 	if (!sp) {
808 		free(pfh.fh_fpath);
809 		errno = ENOMEM;
810 		return (-1);
811 	}
812 	tsp = new_tsp(path);
813 	if (!tsp) {
814 		cstack_delete(sp);
815 		free(pfh.fh_fpath);
816 		errno = ENOMEM;
817 		return (-1);
818 	}
819 
820 	darg.da_buf = ndmp_malloc(MAX_DENT_BUF_SIZE);
821 	if (!darg.da_buf) {
822 		cstack_delete(sp);
823 		free(pfh.fh_fpath);
824 		free(tsp);
825 		errno = ENOMEM;
826 		return (-1);
827 	}
828 	darg.da_size = MAX_DENT_BUF_SIZE;
829 
830 	tsp->ts_ent = tsp->ts_end;
831 	tsp->ts_fh = pfh;
832 	tsp->ts_st = pst;
833 	pn.tn_path = path;
834 	pn.tn_fh = &tsp->ts_fh;
835 	pn.tn_st = &tsp->ts_st;
836 
837 	/* call the callback function on the path itself */
838 	traverse_stats.fss_dir_calls++;
839 	rv = CALLBACK(&pn, &en);
840 	if (rv < 0) {
841 		free(tsp);
842 		goto end;
843 	}
844 	if (rv == FST_SKIP) {
845 		traverse_stats.fss_dir_skipped++;
846 		free(tsp);
847 		rv = 0;
848 		goto end;
849 	}
850 
851 	rv = 0;
852 	next_dir = 1;
853 	do {
854 		if (next_dir) {
855 			traverse_stats.fss_newdirs++;
856 
857 			*tsp->ts_end = '\0';
858 			if (VERBOSE(ftp))
859 				NDMP_LOG(LOG_DEBUG, "pl %d \"%s\"", pl, path);
860 
861 			rv = traverse_level_nondir(ftp, tsp, &pn, &darg);
862 			if (rv < 0) {
863 				NEGATE(rv);
864 				free(tsp->ts_fh.fh_fpath);
865 				free(tsp);
866 				break;
867 			}
868 			/*
869 			 * If skipped by the callback function or
870 			 * error happened reading the information
871 			 */
872 			if (rv == FST_SKIP || rv == SKIP_ENTRY) {
873 				/*
874 				 * N.B. next_dir should be set to 0 as
875 				 * well. This prevents the infinite loop.
876 				 * If it's not set the same directory will
877 				 * be poped from the stack and will be
878 				 * scanned again.
879 				 */
880 				next_dir = 0;
881 				rv = 0;
882 				goto skip_dir;
883 			}
884 
885 			/* re-start reading entries of the directory */
886 			tsp->ts_dpos = 0;
887 		}
888 
889 		next_dir = 0;
890 		do {
891 			el = NAME_MAX;
892 			rv = fs_readdir(&tsp->ts_fh, pn.tn_path,
893 			    &tsp->ts_dpos, nm, &el, &efh,
894 			    &est);
895 			if (rv != 0) {
896 				traverse_stats.fss_readdir_err++;
897 
898 				NDMP_LOG(LOG_DEBUG,
899 				    "Error %d on readdir(%s) pos %d",
900 				    rv, path, tsp->ts_dpos);
901 				if (STOP_ONERR(ftp))
902 					break;
903 				rv = SKIP_ENTRY;
904 				continue;
905 			}
906 
907 			/* done with this directory */
908 			if (el == 0)
909 				break;
910 
911 			nm[el] = '\0';
912 
913 			if (rootfs_dot_or_dotdot(nm)) {
914 				free(efh.fh_fpath);
915 				continue;
916 			}
917 
918 			if (VERBOSE(ftp))
919 				NDMP_LOG(LOG_DEBUG, "%u dname: \"%s\"",
920 				    tsp->ts_dpos, nm);
921 
922 			if (pl + 1 + el > PATH_MAX) {
923 				/*
924 				 * The long paths were already encountered
925 				 * when processing non-dir entries in.
926 				 * traverse_level_nondir.
927 				 * We don't increase fss_longpath_err
928 				 * counter for them again here.
929 				 */
930 				NDMP_LOG(LOG_ERR, "Path %s/%s is too long.",
931 				    path, nm);
932 				if (STOP_ONLONG(ftp))
933 					rv = ENAMETOOLONG;
934 				free(efh.fh_fpath);
935 				continue;
936 			}
937 
938 			if (!S_ISDIR(est.st_mode))
939 				continue;
940 
941 			/*
942 			 * Call the callback function for the new
943 			 * directory found, then push the current
944 			 * directory on to the stack.  Then dive
945 			 * into the entry found.
946 			 */
947 			traverse_stats.fss_dir_calls++;
948 			en.tn_path = nm;
949 			en.tn_fh = &efh;
950 			en.tn_st = &est;
951 			rv = CALLBACK(&pn, &en);
952 
953 			if (rv < 0) {
954 				NEGATE(rv);
955 				free(efh.fh_fpath);
956 				break;
957 			}
958 			if (rv == FST_SKIP) {
959 				traverse_stats.fss_dir_skipped++;
960 				free(efh.fh_fpath);
961 				rv = 0;
962 				continue;
963 			}
964 
965 			/*
966 			 * Push the current directory on to the stack and
967 			 * dive into the entry found.
968 			 */
969 			if (cstack_push(sp, tsp, 0)) {
970 				rv = ENOMEM;
971 			} else {
972 				traverse_stats.fss_pushes++;
973 
974 				lp = tsp->ts_end;
975 				*tsp->ts_end = '/';
976 				(void) strcpy(tsp->ts_end + 1, nm);
977 
978 				tsp = new_tsp(path);
979 				if (!tsp)
980 					rv = ENOMEM;
981 				else {
982 					next_dir = 1;
983 					pl += el + 1;
984 					tsp->ts_fh = efh;
985 					tsp->ts_st = est;
986 					tsp->ts_ent = lp;
987 					pn.tn_fh = &tsp->ts_fh;
988 					pn.tn_st = &tsp->ts_st;
989 				}
990 			}
991 			break;
992 
993 		} while (rv == 0);
994 
995 		/*
996 		 * A new directory must be processed, go to the start of
997 		 * the loop, open it and process it.
998 		 */
999 		if (next_dir)
1000 			continue;
1001 skip_dir:
1002 		if (tsp) {
1003 			free(tsp->ts_fh.fh_fpath);
1004 			free(tsp);
1005 		}
1006 
1007 		if (rv == SKIP_ENTRY)
1008 			rv = 0;
1009 
1010 		if (rv == 0) {
1011 			if (cstack_pop(sp, (void **)&tsp, (int *)NULL))
1012 				break;
1013 
1014 			traverse_stats.fss_pops++;
1015 
1016 			if (VERBOSE(ftp))
1017 				NDMP_LOG(LOG_DEBUG,
1018 				    "Poped pl %d \"%s\"", pl, path);
1019 
1020 			*tsp->ts_end = '\0';
1021 			pl = tsp->ts_end - path;
1022 			pn.tn_fh = &tsp->ts_fh;
1023 			pn.tn_st = &tsp->ts_st;
1024 		}
1025 	} while (rv == 0);
1026 
1027 	/*
1028 	 * Pop and free all the remaining entries on the stack.
1029 	 */
1030 	while (!cstack_pop(sp, (void **)&tsp, (int *)NULL)) {
1031 		traverse_stats.fss_stack_residue++;
1032 
1033 		free(tsp->ts_fh.fh_fpath);
1034 		free(tsp);
1035 	}
1036 end:
1037 	free(darg.da_buf);
1038 	cstack_delete(sp);
1039 	return (rv);
1040 }
1041 
1042 /*
1043  * filecopy - Copy a file
1044  *
1045  * Parameters:
1046  *  char *dest  - Destination path
1047  *  char *src   - Source path
1048  *
1049  * Returns:
1050  *  0    - No errors
1051  *  #0   - Error occured
1052  *		-4   - read/write error
1053  *		-5   - source modified during copy
1054  *
1055  * Simplified version for Solaris
1056  */
1057 #define	BUFSIZE	32768
1058 int
1059 filecopy(char *dest, char *src)
1060 {
1061 	FILE *src_fh = 0;
1062 	FILE *dst_fh = 0;
1063 	struct stat64 src_attr;
1064 	struct stat64 dst_attr;
1065 	char *buf = 0;
1066 	u_longlong_t bytes_to_copy;
1067 	size_t nbytes;
1068 	int file_copied = 0;
1069 
1070 	buf = ndmp_malloc(BUFSIZE);
1071 	if (!buf)
1072 		return (-1);
1073 
1074 	src_fh = fopen(src, "r");
1075 	if (src_fh == 0) {
1076 		free(buf);
1077 		return (-2);
1078 	}
1079 
1080 	dst_fh = fopen(dest, "w");
1081 	if (dst_fh == NULL) {
1082 		free(buf);
1083 		(void) fclose(src_fh);
1084 		return (-3);
1085 	}
1086 
1087 	if (stat64(src, &src_attr) < 0) {
1088 		free(buf);
1089 		(void) fclose(src_fh);
1090 		(void) fclose(dst_fh);
1091 		return (-2);
1092 	}
1093 
1094 	bytes_to_copy = src_attr.st_size;
1095 	while (bytes_to_copy) {
1096 		if (bytes_to_copy > BUFSIZE)
1097 			nbytes = BUFSIZE;
1098 		else
1099 			nbytes = bytes_to_copy;
1100 
1101 		if ((fread(buf, nbytes, 1, src_fh) != 1) ||
1102 		    (fwrite(buf, nbytes, 1, dst_fh) != 1))
1103 			break;
1104 		bytes_to_copy -= nbytes;
1105 	}
1106 
1107 	(void) fclose(src_fh);
1108 	(void) fclose(dst_fh);
1109 
1110 	if (bytes_to_copy > 0) {
1111 		free(buf);
1112 		/* short read/write, remove the partial file */
1113 		return (-4);
1114 	}
1115 
1116 	if (stat64(src, &dst_attr) < 0) {
1117 		free(buf);
1118 		return (-2);
1119 	}
1120 
1121 	free(buf);
1122 
1123 	if (!file_copied)
1124 		return (-5);	/* source modified during copy */
1125 	else
1126 		return (0);
1127 }
1128