xref: /illumos-gate/usr/src/cmd/ndmpd/tlm/tlm_traverse.c (revision 46b592853d0f4f11781b6b0a7533f267c6aee132)
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 		if (lstat64(file_path, &st) != 0) {
259 			rv = -1;
260 			break;
261 		}
262 
263 		fh.fh_fid = st.st_ino;
264 
265 		if (S_ISDIR(st.st_mode))
266 			goto skip_entry;
267 
268 		if (fs_populate_dents(darg, strlen(ptr->d_name),
269 		    (char *)ptr->d_name, n_entries, &st, &fh) != 0)
270 			break;
271 
272 skip_entry:
273 		p = p + ptr->d_reclen;
274 		len -= ptr->d_reclen;
275 	} while (len);
276 
277 	*cookie = (longlong_t)(p - (char *)buf);
278 	*nbyte = len;
279 	return (rv);
280 }
281 
282 /*
283  * Read the directory entries and return the information about
284  * each entry
285  */
286 int
287 fs_readdir(fs_fhandle_t *ts_fh, char *path, long *dpos,
288     char *nm, int *el, fs_fhandle_t *efh, struct stat64 *est)
289 {
290 	struct dirent *dp;
291 	char  file_path[PATH_MAX + 1];
292 	DIR *dirp;
293 	int rv;
294 
295 	if ((dirp = opendir(ts_fh->fh_fpath)) == NULL)
296 		return (errno);
297 
298 	seekdir(dirp, *dpos);
299 	if ((dp = readdir(dirp)) == NULL) {
300 		rv = 0;  /* skip this dir */
301 		*el = 0;
302 	} else {
303 		(void) snprintf(file_path, PATH_MAX, "%s/", path);
304 		(void) strlcat(file_path, dp->d_name, PATH_MAX);
305 
306 		rv = fs_getstat(file_path, efh, est);
307 		if (rv == 0) {
308 			*dpos = telldir(dirp);
309 			(void) strlcpy(nm, dp->d_name, NAME_MAX);
310 			*el = strlen(dp->d_name);
311 		} else {
312 			*el = 0;
313 		}
314 	}
315 	(void) closedir(dirp);
316 	return (rv);
317 }
318 
319 /*
320  * Traverse the file system in the post-order way.  The description
321  * and example is in the header file.
322  *
323  * The callback function should return 0, on success and non-zero on
324  * failure.  If the callback function returns non-zero return value,
325  * the traversing stops.
326  */
327 int
328 traverse_post(struct fs_traverse *ftp)
329 {
330 	char path[PATH_MAX + 1]; /* full path name of the current dir */
331 	char nm[NAME_MAX + 1]; /* directory entry name */
332 	char *lp; /* last position on the path */
333 	int next_dir, rv;
334 	int pl, el; /* path and directory entry length */
335 	cstack_t *sp;
336 	fs_fhandle_t pfh, efh;
337 	struct stat64 pst, est;
338 	traverse_state_t *tsp;
339 	struct fst_node pn, en; /* parent and entry nodes */
340 
341 	if (!ftp || !ftp->ft_path || !*ftp->ft_path || !ftp->ft_callbk) {
342 		NDMP_LOG(LOG_DEBUG, "Invalid argument");
343 		errno = EINVAL;
344 		return (-1);
345 	}
346 
347 	/* set the default log function if it's not already set */
348 	if (!ftp->ft_logfp) {
349 		ftp->ft_logfp = (ft_log_t)syslog;
350 		NDMP_LOG(LOG_DEBUG, "Log to system log \"%s\"", ftp->ft_path);
351 	}
352 
353 	/* set the logical path to physical path if it's not already set */
354 	if (!ftp->ft_lpath) {
355 		NDMP_LOG(LOG_DEBUG,
356 		    "report the same paths: \"%s\"", ftp->ft_path);
357 		ftp->ft_lpath = ftp->ft_path;
358 	}
359 
360 	pl = strlen(ftp->ft_lpath);
361 	if (pl + 1 > PATH_MAX) { /* +1 for the '/' */
362 		NDMP_LOG(LOG_DEBUG, "lpath too long \"%s\"", ftp->ft_path);
363 		errno = ENAMETOOLONG;
364 		return (-1);
365 	}
366 	(void) strcpy(path, ftp->ft_lpath);
367 	(void) memset(&pfh, 0, sizeof (pfh));
368 	rv = fs_getstat(ftp->ft_lpath, &pfh, &pst);
369 
370 	if (rv != 0) {
371 		NDMP_LOG(LOG_DEBUG,
372 		    "Error %d on fs_getstat(%s)", rv, ftp->ft_path);
373 		return (rv);
374 	}
375 
376 	if (!S_ISDIR(pst.st_mode)) {
377 		pn.tn_path = ftp->ft_lpath;
378 		pn.tn_fh = &pfh;
379 		pn.tn_st = &pst;
380 		en.tn_path = NULL;
381 		en.tn_fh = NULL;
382 		en.tn_st = NULL;
383 		rv = CALLBACK(&pn, &en);
384 		if (VERBOSE(ftp))
385 			NDMP_LOG(LOG_DEBUG, "CALLBACK(%s): %d", pn.tn_path, rv);
386 		free(pfh.fh_fpath);
387 		return (rv);
388 	}
389 
390 	sp = cstack_new();
391 	if (!sp) {
392 		errno = ENOMEM;
393 		free(pfh.fh_fpath);
394 		return (-1);
395 	}
396 	tsp = new_tsp(path);
397 	if (!tsp) {
398 		cstack_delete(sp);
399 		errno = ENOMEM;
400 		free(pfh.fh_fpath);
401 		return (-1);
402 	}
403 	tsp->ts_ent = tsp->ts_end;
404 	tsp->ts_fh = pfh;
405 	tsp->ts_st = pst;
406 	pn.tn_path = path;
407 	pn.tn_fh = &tsp->ts_fh;
408 	pn.tn_st = &tsp->ts_st;
409 
410 	rv = 0;
411 	next_dir = 1;
412 	do {
413 		if (next_dir) {
414 			traverse_stats.fss_newdirs++;
415 
416 			*tsp->ts_end = '\0';
417 			if (VERBOSE(ftp))
418 				NDMP_LOG(LOG_DEBUG, "pl %d \"%s\"", pl, path);
419 		}
420 
421 		next_dir = 0;
422 		do {
423 			el = NAME_MAX;
424 			rv = fs_readdir(&tsp->ts_fh, pn.tn_path,
425 			    &tsp->ts_dpos, nm, &el,
426 			    &efh, &est);
427 
428 			if (rv != 0) {
429 				free(efh.fh_fpath);
430 				traverse_stats.fss_readdir_err++;
431 
432 				NDMP_LOG(LOG_DEBUG,
433 				    "Error %d on readdir(%s) pos %d",
434 				    rv, path, tsp->ts_dpos);
435 				if (STOP_ONERR(ftp))
436 					break;
437 				rv = SKIP_ENTRY;
438 
439 				continue;
440 			}
441 
442 			/* done with this directory */
443 			if (el == 0) {
444 				if (VERBOSE(ftp))
445 					NDMP_LOG(LOG_DEBUG,
446 					    "Done(%s)", pn.tn_path);
447 				break;
448 			}
449 			nm[el] = '\0';
450 
451 			if (rootfs_dot_or_dotdot(nm)) {
452 				free(efh.fh_fpath);
453 				continue;
454 			}
455 
456 			if (VERBOSE(ftp))
457 				NDMP_LOG(LOG_DEBUG, "%u dname: \"%s\"",
458 				    tsp->ts_dpos, nm);
459 
460 			if (pl + 1 + el > PATH_MAX) {
461 				traverse_stats.fss_longpath_err++;
462 
463 				NDMP_LOG(LOG_ERR, "Path %s/%s is too long.",
464 				    path, nm);
465 				if (STOP_ONLONG(ftp))
466 					rv = ENAMETOOLONG;
467 				free(efh.fh_fpath);
468 				continue;
469 			}
470 
471 			/*
472 			 * Push the current directory on to the stack and
473 			 * dive into the entry found.
474 			 */
475 			if (S_ISDIR(est.st_mode)) {
476 
477 				assert(tsp != NULL);
478 				if (cstack_push(sp, tsp, 0)) {
479 					rv = ENOMEM;
480 					free(efh.fh_fpath);
481 					break;
482 				}
483 				traverse_stats.fss_pushes++;
484 
485 				/*
486 				 * Concatenate the current entry with the
487 				 * current path.  This will be the path of
488 				 * the new directory to be scanned.
489 				 *
490 				 * Note:
491 				 * sprintf(tsp->ts_end, "/%s", de->d_name);
492 				 * could be used here, but concatenating
493 				 * strings like this might be faster.
494 				 * The length of the new path has been
495 				 * checked above.  So strcpy() can be
496 				 * safe and should not lead to a buffer
497 				 * over-run.
498 				 */
499 				lp = tsp->ts_end;
500 				*tsp->ts_end = '/';
501 				(void) strcpy(tsp->ts_end + 1, nm);
502 
503 				tsp = new_tsp(path);
504 				if (!tsp) {
505 					free(efh.fh_fpath);
506 					rv = ENOMEM;
507 				} else {
508 					next_dir = 1;
509 					pl += el;
510 					tsp->ts_fh = efh;
511 					tsp->ts_st = est;
512 					tsp->ts_ent = lp;
513 					pn.tn_fh = &tsp->ts_fh;
514 					pn.tn_st = &tsp->ts_st;
515 				}
516 				break;
517 			} else {
518 				/*
519 				 * The entry is not a directory so the
520 				 * callback function must be called.
521 				 */
522 				traverse_stats.fss_nondir_calls++;
523 
524 				en.tn_path = nm;
525 				en.tn_fh = &efh;
526 				en.tn_st = &est;
527 				rv = CALLBACK(&pn, &en);
528 				free(efh.fh_fpath);
529 				if (VERBOSE(ftp))
530 					NDMP_LOG(LOG_DEBUG,
531 					    "CALLBACK(%s/%s): %d",
532 					    pn.tn_path, en.tn_path, rv);
533 
534 				if (rv != 0)
535 					break;
536 			}
537 		} while (rv == 0);
538 
539 		/*
540 		 * A new directory must be processed, go to the start of
541 		 * the loop, open it and process it.
542 		 */
543 		if (next_dir)
544 			continue;
545 
546 		if (rv == SKIP_ENTRY)
547 			rv = 0; /* We should skip the current directory */
548 
549 		if (rv == 0) {
550 			/*
551 			 * Remove the ent from the end of path and send it
552 			 * as an entry of the path.
553 			 */
554 			lp = tsp->ts_ent;
555 			*lp = '\0';
556 			efh = tsp->ts_fh;
557 			est = tsp->ts_st;
558 			free(tsp);
559 			if (cstack_pop(sp, (void **)&tsp, (int *)NULL))
560 				break;
561 
562 			assert(tsp != NULL);
563 			pl = tsp->ts_end - path;
564 
565 			if (VERBOSE(ftp))
566 				NDMP_LOG(LOG_DEBUG, "poped pl %d 0x%p \"%s\"",
567 				    pl, tsp, path);
568 
569 			traverse_stats.fss_pops++;
570 			traverse_stats.fss_dir_calls++;
571 
572 			pn.tn_fh = &tsp->ts_fh;
573 			pn.tn_st = &tsp->ts_st;
574 			en.tn_path = lp + 1;
575 			en.tn_fh = &efh;
576 			en.tn_st = &est;
577 
578 			rv = CALLBACK(&pn, &en);
579 			free(efh.fh_fpath);
580 			if (VERBOSE(ftp))
581 				NDMP_LOG(LOG_DEBUG, "CALLBACK(%s/%s): %d",
582 				    pn.tn_path, en.tn_path, rv);
583 			/*
584 			 * Does not need to free tsp here.  It will be released
585 			 * later.
586 			 */
587 		}
588 
589 		if (rv != 0 && tsp) {
590 			free(tsp->ts_fh.fh_fpath);
591 			free(tsp);
592 		}
593 
594 	} while (rv == 0);
595 
596 	/*
597 	 * For the 'ftp->ft_path' directory itself.
598 	 */
599 	if (rv == 0) {
600 		traverse_stats.fss_dir_calls++;
601 
602 		pn.tn_fh = &efh;
603 		pn.tn_st = &est;
604 		en.tn_path = NULL;
605 		en.tn_fh = NULL;
606 		en.tn_st = NULL;
607 		rv = CALLBACK(&pn, &en);
608 		if (VERBOSE(ftp))
609 			NDMP_LOG(LOG_DEBUG, "CALLBACK(%s): %d", pn.tn_path, rv);
610 	}
611 
612 	/*
613 	 * Pop and free all the remaining entries on the stack.
614 	 */
615 	while (!cstack_pop(sp, (void **)&tsp, (int *)NULL)) {
616 		traverse_stats.fss_stack_residue++;
617 
618 		free(tsp->ts_fh.fh_fpath);
619 		free(tsp);
620 	}
621 
622 	cstack_delete(sp);
623 	return (rv);
624 }
625 
626 /*
627  * In one pass, read all the directory entries of the specified
628  * directory and call the callback function for non-directory
629  * entries.
630  *
631  * On return:
632  *    0: Lets the directory to be scanned for directory entries.
633  *    < 0: Completely stops traversing.
634  *    FST_SKIP: stops further scanning of the directory.  Traversing
635  *        will continue with the next directory in the hierarchy.
636  *    SKIP_ENTRY: Failed to get the directory entries, so the caller
637  *	  should skip this entry.
638  */
639 static int
640 traverse_level_nondir(struct fs_traverse *ftp,
641     traverse_state_t *tsp, struct fst_node *pnp, dent_arg_t *darg)
642 {
643 	int pl; /* path length */
644 	int rv;
645 	struct fst_node en; /* entry node */
646 	longlong_t cookie_verf;
647 	fs_dent_info_t *dent;
648 	struct dirent *buf;
649 	size_t len = 0;
650 	int fd;
651 
652 	rv = 0;
653 	pl = strlen(pnp->tn_path);
654 
655 	buf = ndmp_malloc(MAX_DENT_BUF_SIZE);
656 	if (buf == NULL)
657 		return (errno);
658 
659 	fd = open(tsp->ts_fh.fh_fpath, O_RDONLY);
660 	if (fd == -1) {
661 		free(buf);
662 		return (errno);
663 	}
664 
665 	while (rv == 0) {
666 		long i, n_entries;
667 
668 		darg->da_end = 0;
669 		n_entries = 0;
670 		rv = fs_getdents(fd, buf, &len, pnp->tn_path, &tsp->ts_dpos,
671 		    &cookie_verf, &n_entries, darg);
672 		if (rv < 0) {
673 			traverse_stats.fss_readdir_err++;
674 
675 			NDMP_LOG(LOG_DEBUG, "Error %d on readdir(%s) pos %d",
676 			    rv, pnp->tn_path, tsp->ts_dpos);
677 			if (STOP_ONERR(ftp)) {
678 				NEGATE(rv);
679 				break;
680 			}
681 			/*
682 			 * We cannot read the directory entry, we should
683 			 * skip to the next directory.
684 			 */
685 			rv = SKIP_ENTRY;
686 			continue;
687 		} else {
688 			/* Break at the end of directory */
689 			if (rv > 0)
690 				rv = 0;
691 			else
692 				break;
693 		}
694 
695 		/* LINTED imporper alignment */
696 		dent = (fs_dent_info_t *)darg->da_buf;
697 		/* LINTED imporper alignment */
698 		for (i = 0; i < n_entries; i++, dent = (fs_dent_info_t *)
699 		    ((char *)dent + dent->fd_len)) {
700 
701 			if (VERBOSE(ftp))
702 				NDMP_LOG(LOG_DEBUG, "i %u dname: \"%s\"",
703 				    dent->fd_fh.fh_fid, dent->fd_name);
704 
705 			if ((pl + strlen(dent->fd_name)) > PATH_MAX) {
706 				traverse_stats.fss_longpath_err++;
707 
708 				NDMP_LOG(LOG_ERR, "Path %s/%s is too long.",
709 				    pnp->tn_path, dent->fd_name);
710 				if (STOP_ONLONG(ftp))
711 					rv = -ENAMETOOLONG;
712 				free(dent->fd_fh.fh_fpath);
713 				continue;
714 			}
715 
716 			/*
717 			 * The entry is not a directory so the callback
718 			 * function must be called.
719 			 */
720 			if (!S_ISDIR(dent->fd_attr.st_mode)) {
721 				traverse_stats.fss_nondir_calls++;
722 
723 				en.tn_path = dent->fd_name;
724 				en.tn_fh = &dent->fd_fh;
725 				en.tn_st = &dent->fd_attr;
726 				rv = CALLBACK(pnp, &en);
727 				dent->fd_fh.fh_fpath = NULL;
728 				if (rv < 0)
729 					break;
730 				if (rv == FST_SKIP) {
731 					traverse_stats.fss_nondir_skipped++;
732 					break;
733 				}
734 			}
735 		}
736 	}
737 
738 	free(buf);
739 	(void) close(fd);
740 	return (rv);
741 }
742 
743 /*
744  * Traverse the file system in the level-order way.  The description
745  * and example is in the header file.
746  */
747 int
748 traverse_level(struct fs_traverse *ftp)
749 {
750 	char path[PATH_MAX + 1];	/* full path name of the current dir */
751 	char nm[NAME_MAX + 1];	/* directory entry name */
752 	char *lp;		/* last position on the path */
753 	int next_dir, rv;
754 	int pl, el;		/* path and directory entry length */
755 
756 	cstack_t *sp;
757 	fs_fhandle_t pfh, efh;
758 	struct stat64 pst, est;
759 	traverse_state_t *tsp;
760 	struct fst_node pn, en;  /* parent and entry nodes */
761 	dent_arg_t darg;
762 
763 	if (!ftp || !ftp->ft_path || !*ftp->ft_path || !ftp->ft_callbk) {
764 		NDMP_LOG(LOG_DEBUG, "Invalid argument");
765 		errno = EINVAL;
766 		return (-1);
767 	}
768 	/* set the default log function if it's not already set */
769 	if (!ftp->ft_logfp) {
770 		ftp->ft_logfp = (ft_log_t)syslog;
771 		NDMP_LOG(LOG_DEBUG, "Log to system log \"%s\"", ftp->ft_path);
772 	}
773 	if (!ftp->ft_lpath) {
774 		NDMP_LOG(LOG_DEBUG,
775 		    "report the same paths \"%s\"", ftp->ft_path);
776 		ftp->ft_lpath = ftp->ft_path;
777 	}
778 
779 	pl = strlen(ftp->ft_lpath);
780 	if (pl + 1 > PATH_MAX) { /* +1 for the '/' */
781 		NDMP_LOG(LOG_DEBUG, "lpath too long \"%s\"", ftp->ft_path);
782 		errno = ENAMETOOLONG;
783 		return (-1);
784 	}
785 	(void) strcpy(path, ftp->ft_lpath);
786 	(void) memset(&pfh, 0, sizeof (pfh));
787 	rv = fs_getstat(ftp->ft_lpath, &pfh, &pst);
788 	if (rv != 0) {
789 		NDMP_LOG(LOG_DEBUG,
790 		    "Error %d on fs_getstat(%s)", rv, ftp->ft_path);
791 		return (-1);
792 	}
793 
794 	en.tn_path = NULL;
795 	en.tn_fh = NULL;
796 	en.tn_st = NULL;
797 	if (!S_ISDIR(pst.st_mode)) {
798 		pn.tn_path = ftp->ft_lpath;
799 		pn.tn_fh = &pfh;
800 		pn.tn_st = &pst;
801 		rv = CALLBACK(&pn, &en);
802 		if (VERBOSE(ftp))
803 			NDMP_LOG(LOG_DEBUG, "CALLBACK(%s): %d", pn.tn_path, rv);
804 
805 		free(pfh.fh_fpath);
806 		return (rv);
807 	}
808 
809 	sp = cstack_new();
810 	if (!sp) {
811 		free(pfh.fh_fpath);
812 		errno = ENOMEM;
813 		return (-1);
814 	}
815 	tsp = new_tsp(path);
816 	if (!tsp) {
817 		cstack_delete(sp);
818 		free(pfh.fh_fpath);
819 		errno = ENOMEM;
820 		return (-1);
821 	}
822 
823 	darg.da_buf = ndmp_malloc(MAX_DENT_BUF_SIZE);
824 	if (!darg.da_buf) {
825 		cstack_delete(sp);
826 		free(pfh.fh_fpath);
827 		free(tsp);
828 		errno = ENOMEM;
829 		return (-1);
830 	}
831 	darg.da_size = MAX_DENT_BUF_SIZE;
832 
833 	tsp->ts_ent = tsp->ts_end;
834 	tsp->ts_fh = pfh;
835 	tsp->ts_st = pst;
836 	pn.tn_path = path;
837 	pn.tn_fh = &tsp->ts_fh;
838 	pn.tn_st = &tsp->ts_st;
839 
840 	/* call the callback function on the path itself */
841 	traverse_stats.fss_dir_calls++;
842 	rv = CALLBACK(&pn, &en);
843 	if (rv < 0) {
844 		free(tsp);
845 		goto end;
846 	}
847 	if (rv == FST_SKIP) {
848 		traverse_stats.fss_dir_skipped++;
849 		free(tsp);
850 		rv = 0;
851 		goto end;
852 	}
853 
854 	rv = 0;
855 	next_dir = 1;
856 	do {
857 		if (next_dir) {
858 			traverse_stats.fss_newdirs++;
859 
860 			*tsp->ts_end = '\0';
861 			if (VERBOSE(ftp))
862 				NDMP_LOG(LOG_DEBUG, "pl %d \"%s\"", pl, path);
863 
864 			rv = traverse_level_nondir(ftp, tsp, &pn, &darg);
865 			if (rv < 0) {
866 				NEGATE(rv);
867 				free(tsp->ts_fh.fh_fpath);
868 				free(tsp);
869 				break;
870 			}
871 			/*
872 			 * If skipped by the callback function or
873 			 * error happened reading the information
874 			 */
875 			if (rv == FST_SKIP || rv == SKIP_ENTRY) {
876 				/*
877 				 * N.B. next_dir should be set to 0 as
878 				 * well. This prevents the infinite loop.
879 				 * If it's not set the same directory will
880 				 * be poped from the stack and will be
881 				 * scanned again.
882 				 */
883 				next_dir = 0;
884 				rv = 0;
885 				goto skip_dir;
886 			}
887 
888 			/* re-start reading entries of the directory */
889 			tsp->ts_dpos = 0;
890 		}
891 
892 		next_dir = 0;
893 		do {
894 			el = NAME_MAX;
895 			rv = fs_readdir(&tsp->ts_fh, pn.tn_path,
896 			    &tsp->ts_dpos, nm, &el, &efh,
897 			    &est);
898 			if (rv != 0) {
899 				traverse_stats.fss_readdir_err++;
900 
901 				NDMP_LOG(LOG_DEBUG,
902 				    "Error %d on readdir(%s) pos %d",
903 				    rv, path, tsp->ts_dpos);
904 				if (STOP_ONERR(ftp))
905 					break;
906 				rv = SKIP_ENTRY;
907 				continue;
908 			}
909 
910 			/* done with this directory */
911 			if (el == 0)
912 				break;
913 
914 			nm[el] = '\0';
915 
916 			if (rootfs_dot_or_dotdot(nm)) {
917 				free(efh.fh_fpath);
918 				continue;
919 			}
920 
921 			if (VERBOSE(ftp))
922 				NDMP_LOG(LOG_DEBUG, "%u dname: \"%s\"",
923 				    tsp->ts_dpos, nm);
924 
925 			if (pl + 1 + el > PATH_MAX) {
926 				/*
927 				 * The long paths were already encountered
928 				 * when processing non-dir entries in.
929 				 * traverse_level_nondir.
930 				 * We don't increase fss_longpath_err
931 				 * counter for them again here.
932 				 */
933 				NDMP_LOG(LOG_ERR, "Path %s/%s is too long.",
934 				    path, nm);
935 				if (STOP_ONLONG(ftp))
936 					rv = ENAMETOOLONG;
937 				free(efh.fh_fpath);
938 				continue;
939 			}
940 
941 			if (!S_ISDIR(est.st_mode))
942 				continue;
943 
944 			/*
945 			 * Call the callback function for the new
946 			 * directory found, then push the current
947 			 * directory on to the stack.  Then dive
948 			 * into the entry found.
949 			 */
950 			traverse_stats.fss_dir_calls++;
951 			en.tn_path = nm;
952 			en.tn_fh = &efh;
953 			en.tn_st = &est;
954 			rv = CALLBACK(&pn, &en);
955 
956 			if (rv < 0) {
957 				NEGATE(rv);
958 				free(efh.fh_fpath);
959 				break;
960 			}
961 			if (rv == FST_SKIP) {
962 				traverse_stats.fss_dir_skipped++;
963 				free(efh.fh_fpath);
964 				rv = 0;
965 				continue;
966 			}
967 
968 			/*
969 			 * Push the current directory on to the stack and
970 			 * dive into the entry found.
971 			 */
972 			if (cstack_push(sp, tsp, 0)) {
973 				rv = ENOMEM;
974 			} else {
975 				traverse_stats.fss_pushes++;
976 
977 				lp = tsp->ts_end;
978 				*tsp->ts_end = '/';
979 				(void) strcpy(tsp->ts_end + 1, nm);
980 
981 				tsp = new_tsp(path);
982 				if (!tsp)
983 					rv = ENOMEM;
984 				else {
985 					next_dir = 1;
986 					pl += el + 1;
987 					tsp->ts_fh = efh;
988 					tsp->ts_st = est;
989 					tsp->ts_ent = lp;
990 					pn.tn_fh = &tsp->ts_fh;
991 					pn.tn_st = &tsp->ts_st;
992 				}
993 			}
994 			break;
995 
996 		} while (rv == 0);
997 
998 		/*
999 		 * A new directory must be processed, go to the start of
1000 		 * the loop, open it and process it.
1001 		 */
1002 		if (next_dir)
1003 			continue;
1004 skip_dir:
1005 		if (tsp) {
1006 			free(tsp->ts_fh.fh_fpath);
1007 			free(tsp);
1008 		}
1009 
1010 		if (rv == SKIP_ENTRY)
1011 			rv = 0;
1012 
1013 		if (rv == 0) {
1014 			if (cstack_pop(sp, (void **)&tsp, (int *)NULL))
1015 				break;
1016 
1017 			traverse_stats.fss_pops++;
1018 
1019 			if (VERBOSE(ftp))
1020 				NDMP_LOG(LOG_DEBUG,
1021 				    "Poped pl %d \"%s\"", pl, path);
1022 
1023 			*tsp->ts_end = '\0';
1024 			pl = tsp->ts_end - path;
1025 			pn.tn_fh = &tsp->ts_fh;
1026 			pn.tn_st = &tsp->ts_st;
1027 		}
1028 	} while (rv == 0);
1029 
1030 	/*
1031 	 * Pop and free all the remaining entries on the stack.
1032 	 */
1033 	while (!cstack_pop(sp, (void **)&tsp, (int *)NULL)) {
1034 		traverse_stats.fss_stack_residue++;
1035 
1036 		free(tsp->ts_fh.fh_fpath);
1037 		free(tsp);
1038 	}
1039 end:
1040 	free(darg.da_buf);
1041 	cstack_delete(sp);
1042 	return (rv);
1043 }
1044 
1045 /*
1046  * filecopy - Copy a file
1047  *
1048  * Parameters:
1049  *  char *dest  - Destination path
1050  *  char *src   - Source path
1051  *
1052  * Returns:
1053  *  0    - No errors
1054  *  #0   - Error occured
1055  *		-4   - read/write error
1056  *		-5   - source modified during copy
1057  *
1058  * Simplified version for Solaris
1059  */
1060 #define	BUFSIZE	32768
1061 int
1062 filecopy(char *dest, char *src)
1063 {
1064 	FILE *src_fh = 0;
1065 	FILE *dst_fh = 0;
1066 	struct stat64 src_attr;
1067 	struct stat64 dst_attr;
1068 	char *buf = 0;
1069 	u_longlong_t bytes_to_copy;
1070 	size_t nbytes;
1071 	int file_copied = 0;
1072 
1073 	buf = ndmp_malloc(BUFSIZE);
1074 	if (!buf)
1075 		return (-1);
1076 
1077 	src_fh = fopen(src, "r");
1078 	if (src_fh == 0) {
1079 		free(buf);
1080 		return (-2);
1081 	}
1082 
1083 	dst_fh = fopen(dest, "w");
1084 	if (dst_fh == NULL) {
1085 		free(buf);
1086 		(void) fclose(src_fh);
1087 		return (-3);
1088 	}
1089 
1090 	if (stat64(src, &src_attr) < 0) {
1091 		free(buf);
1092 		(void) fclose(src_fh);
1093 		(void) fclose(dst_fh);
1094 		return (-2);
1095 	}
1096 
1097 	bytes_to_copy = src_attr.st_size;
1098 	while (bytes_to_copy) {
1099 		if (bytes_to_copy > BUFSIZE)
1100 			nbytes = BUFSIZE;
1101 		else
1102 			nbytes = bytes_to_copy;
1103 
1104 		if ((fread(buf, nbytes, 1, src_fh) != 1) ||
1105 		    (fwrite(buf, nbytes, 1, dst_fh) != 1))
1106 			break;
1107 		bytes_to_copy -= nbytes;
1108 	}
1109 
1110 	(void) fclose(src_fh);
1111 	(void) fclose(dst_fh);
1112 
1113 	if (bytes_to_copy > 0) {
1114 		free(buf);
1115 		/* short read/write, remove the partial file */
1116 		return (-4);
1117 	}
1118 
1119 	if (stat64(src, &dst_attr) < 0) {
1120 		free(buf);
1121 		return (-2);
1122 	}
1123 
1124 	free(buf);
1125 
1126 	if (!file_copied)
1127 		return (-5);	/* source modified during copy */
1128 	else
1129 		return (0);
1130 }
1131