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