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