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 */
66boolean_t
67rootfs_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 */
99typedef 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 */
110typedef 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 */
127traverse_statistics_t traverse_stats;
128
129#define	MAX_DENT_BUF_SIZE	(8 * 1024)
130
131typedef 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
138typedef struct dent_arg {
139	char *da_buf;
140	int da_end;
141	int da_size;
142} dent_arg_t;
143
144static 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 */
151static int
152fs_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 */
182static traverse_state_t *
183new_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 */
202int
203fs_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 */
221static int
222fs_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
271skip_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 */
285int
286fs_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 */
326int
327traverse_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 */
638static int
639traverse_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 */
744int
745traverse_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;
1001skip_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	}
1036end:
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
1058int
1059filecopy(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