2 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
8 * Copyright (c) 2007, The Storage Networking Industry Association.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * - Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
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
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
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.
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.
45 #include <sys/types.h>
46 #include <sys/param.h>
61 #include "tlm_proto.h"
64 * Check if it's "." or ".."
67 rootfs_dot_or_dotdot(char *name
)
72 if ((name
[1] == 0) || (name
[1] == '.' && name
[2] == 0))
79 * Macros on fs_traverse flags.
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)
85 #define CALLBACK(pp, ep) \
86 (*(ftp)->ft_callbk)((ftp)->ft_arg, pp, ep)
88 #define NEGATE(rv) ((rv) = -(rv))
91 * The traversing state that is pushed onto the stack.
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.
99 typedef struct traverse_state
{
102 long ts_dpos
; /* position in the directory when reading its entries */
108 * Statistics gathering structure.
110 typedef struct traverse_statistics
{
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
;
121 ulong_t fss_stack_residue
;
122 } traverse_statistics_t
;
125 * Global instance of statistics variable.
127 traverse_statistics_t traverse_stats
;
129 #define MAX_DENT_BUF_SIZE (8 * 1024)
132 struct stat64 fd_attr
;
138 typedef struct dent_arg
{
144 static int traverse_level_nondir(struct fs_traverse
*ftp
,
145 traverse_state_t
*tsp
, struct fst_node
*pnp
,
149 * Gather some directory entry information and return them
152 fs_populate_dents(void *arg
, int namelen
,
153 char *name
, long *countp
, struct stat64
*attr
,
156 dent_arg_t
*darg
= (dent_arg_t
*)arg
;
157 int reclen
= sizeof (fs_dent_info_t
) + namelen
;
158 fs_dent_info_t
*dent
;
160 if ((darg
->da_end
+ reclen
) > darg
->da_size
)
163 /* LINTED improper alignment */
164 dent
= (fs_dent_info_t
*)(darg
->da_buf
+ darg
->da_end
);
166 dent
->fd_attr
= *attr
;
168 (void) strcpy(dent
->fd_name
, name
);
170 dent
->fd_len
= reclen
;
171 darg
->da_end
+= reclen
;
180 * Creates a new traversing state based on the path passed to it.
182 static traverse_state_t
*
185 traverse_state_t
*tsp
;
186 tsp
= ndmp_malloc(sizeof (traverse_state_t
));
190 tsp
->ts_end
= strchr(path
, '\0');
191 if (*(tsp
->ts_end
-1) == '/')
192 *--tsp
->ts_end
= '\0';
200 * Create a file handle and get stats for the given path
203 fs_getstat(char *path
, fs_fhandle_t
*fh
, struct stat64
*st
)
205 if (lstat64(path
, st
) == -1)
208 fh
->fh_fid
= st
->st_ino
;
210 if (!S_ISDIR(st
->st_mode
))
213 fh
->fh_fpath
= strdup(path
);
218 * Get directory entries info and return in the buffer. Cookie
219 * will keep the state of each call
222 fs_getdents(int fildes
, struct dirent
*buf
, size_t *nbyte
,
223 char *pn_path
, long *dpos
, longlong_t
*cookie
,
224 long *n_entries
, dent_arg_t
*darg
)
227 char file_path
[PATH_MAX
+ 1];
235 (void) memset((char *)buf
, 0, MAX_DENT_BUF_SIZE
);
236 *nbyte
= rv
= getdents(fildes
, buf
, darg
->da_size
);
243 p
= (char *)buf
+ *cookie
;
246 /* LINTED improper alignment */
247 ptr
= (struct dirent
*)p
;
250 if (rootfs_dot_or_dotdot(ptr
->d_name
))
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
));
257 if (lstat64(file_path
, &st
) != 0) {
262 fh
.fh_fid
= st
.st_ino
;
264 if (S_ISDIR(st
.st_mode
))
267 if (fs_populate_dents(darg
, strlen(ptr
->d_name
),
268 (char *)ptr
->d_name
, n_entries
, &st
, &fh
) != 0)
272 p
= p
+ ptr
->d_reclen
;
273 len
-= ptr
->d_reclen
;
276 *cookie
= (longlong_t
)(p
- (char *)buf
);
282 * Read the directory entries and return the information about
286 fs_readdir(fs_fhandle_t
*ts_fh
, char *path
, long *dpos
,
287 char *nm
, int *el
, fs_fhandle_t
*efh
, struct stat64
*est
)
290 char file_path
[PATH_MAX
+ 1];
294 if ((dirp
= opendir(ts_fh
->fh_fpath
)) == NULL
)
297 seekdir(dirp
, *dpos
);
298 if ((dp
= readdir(dirp
)) == NULL
) {
299 rv
= 0; /* skip this dir */
302 (void) snprintf(file_path
, PATH_MAX
, "%s/", path
);
303 (void) strlcat(file_path
, dp
->d_name
, PATH_MAX
+ 1);
305 rv
= fs_getstat(file_path
, efh
, est
);
307 *dpos
= telldir(dirp
);
308 (void) strlcpy(nm
, dp
->d_name
, NAME_MAX
+ 1);
309 *el
= strlen(dp
->d_name
);
314 (void) closedir(dirp
);
319 * Traverse the file system in the post-order way. The description
320 * and example is in the header file.
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.
327 traverse_post(struct fs_traverse
*ftp
)
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 */
333 int pl
, el
; /* path and directory entry length */
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 */
340 if (!ftp
|| !ftp
->ft_path
|| !*ftp
->ft_path
|| !ftp
->ft_callbk
) {
341 NDMP_LOG(LOG_DEBUG
, "Invalid argument");
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
);
352 /* set the logical path to physical path if it's not already set */
353 if (!ftp
->ft_lpath
) {
355 "report the same paths: \"%s\"", ftp
->ft_path
);
356 ftp
->ft_lpath
= ftp
->ft_path
;
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
;
365 (void) strcpy(path
, ftp
->ft_lpath
);
366 (void) memset(&pfh
, 0, sizeof (pfh
));
367 rv
= fs_getstat(ftp
->ft_lpath
, &pfh
, &pst
);
371 "Error %d on fs_getstat(%s)", rv
, ftp
->ft_path
);
375 if (!S_ISDIR(pst
.st_mode
)) {
376 pn
.tn_path
= ftp
->ft_lpath
;
382 rv
= CALLBACK(&pn
, &en
);
384 NDMP_LOG(LOG_DEBUG
, "CALLBACK(%s): %d", pn
.tn_path
, rv
);
402 tsp
->ts_ent
= tsp
->ts_end
;
406 pn
.tn_fh
= &tsp
->ts_fh
;
407 pn
.tn_st
= &tsp
->ts_st
;
413 traverse_stats
.fss_newdirs
++;
417 NDMP_LOG(LOG_DEBUG
, "pl %d \"%s\"", pl
, path
);
423 rv
= fs_readdir(&tsp
->ts_fh
, pn
.tn_path
,
424 &tsp
->ts_dpos
, nm
, &el
,
429 traverse_stats
.fss_readdir_err
++;
432 "Error %d on readdir(%s) pos %d",
433 rv
, path
, tsp
->ts_dpos
);
441 /* done with this directory */
445 "Done(%s)", pn
.tn_path
);
450 if (rootfs_dot_or_dotdot(nm
)) {
456 NDMP_LOG(LOG_DEBUG
, "%u dname: \"%s\"",
459 if (pl
+ 1 + el
> PATH_MAX
) {
460 traverse_stats
.fss_longpath_err
++;
462 NDMP_LOG(LOG_ERR
, "Path %s/%s is too long.",
464 if (STOP_ONLONG(ftp
))
471 * Push the current directory on to the stack and
472 * dive into the entry found.
474 if (S_ISDIR(est
.st_mode
)) {
477 if (cstack_push(sp
, tsp
, 0)) {
482 traverse_stats
.fss_pushes
++;
485 * Concatenate the current entry with the
486 * current path. This will be the path of
487 * the new directory to be scanned.
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
500 (void) strcpy(tsp
->ts_end
+ 1, nm
);
512 pn
.tn_fh
= &tsp
->ts_fh
;
513 pn
.tn_st
= &tsp
->ts_st
;
518 * The entry is not a directory so the
519 * callback function must be called.
521 traverse_stats
.fss_nondir_calls
++;
526 rv
= CALLBACK(&pn
, &en
);
530 "CALLBACK(%s/%s): %d",
531 pn
.tn_path
, en
.tn_path
, rv
);
539 * A new directory must be processed, go to the start of
540 * the loop, open it and process it.
545 if (rv
== SKIP_ENTRY
)
546 rv
= 0; /* We should skip the current directory */
550 * Remove the ent from the end of path and send it
551 * as an entry of the path.
558 if (cstack_pop(sp
, (void **)&tsp
, (int *)NULL
))
562 pl
= tsp
->ts_end
- path
;
565 NDMP_LOG(LOG_DEBUG
, "poped pl %d 0x%p \"%s\"",
568 traverse_stats
.fss_pops
++;
569 traverse_stats
.fss_dir_calls
++;
571 pn
.tn_fh
= &tsp
->ts_fh
;
572 pn
.tn_st
= &tsp
->ts_st
;
577 rv
= CALLBACK(&pn
, &en
);
580 NDMP_LOG(LOG_DEBUG
, "CALLBACK(%s/%s): %d",
581 pn
.tn_path
, en
.tn_path
, rv
);
583 * Does not need to free tsp here. It will be released
588 if (rv
!= 0 && tsp
) {
589 free(tsp
->ts_fh
.fh_fpath
);
596 * For the 'ftp->ft_path' directory itself.
599 traverse_stats
.fss_dir_calls
++;
606 rv
= CALLBACK(&pn
, &en
);
608 NDMP_LOG(LOG_DEBUG
, "CALLBACK(%s): %d", pn
.tn_path
, rv
);
612 * Pop and free all the remaining entries on the stack.
614 while (!cstack_pop(sp
, (void **)&tsp
, (int *)NULL
)) {
615 traverse_stats
.fss_stack_residue
++;
617 free(tsp
->ts_fh
.fh_fpath
);
626 * In one pass, read all the directory entries of the specified
627 * directory and call the callback function for non-directory
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.
639 traverse_level_nondir(struct fs_traverse
*ftp
,
640 traverse_state_t
*tsp
, struct fst_node
*pnp
, dent_arg_t
*darg
)
642 int pl
; /* path length */
644 struct fst_node en
; /* entry node */
645 longlong_t cookie_verf
;
646 fs_dent_info_t
*dent
;
652 pl
= strlen(pnp
->tn_path
);
654 buf
= ndmp_malloc(MAX_DENT_BUF_SIZE
);
658 fd
= open(tsp
->ts_fh
.fh_fpath
, O_RDONLY
);
669 rv
= fs_getdents(fd
, buf
, &len
, pnp
->tn_path
, &tsp
->ts_dpos
,
670 &cookie_verf
, &n_entries
, darg
);
672 traverse_stats
.fss_readdir_err
++;
674 NDMP_LOG(LOG_DEBUG
, "Error %d on readdir(%s) pos %d",
675 rv
, pnp
->tn_path
, tsp
->ts_dpos
);
679 * We cannot read the directory entry, we should
680 * skip to the next directory.
685 /* Break at the end of directory */
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
)) {
699 NDMP_LOG(LOG_DEBUG
, "i %u dname: \"%s\"",
700 dent
->fd_fh
.fh_fid
, dent
->fd_name
);
702 if ((pl
+ strlen(dent
->fd_name
)) > PATH_MAX
) {
703 traverse_stats
.fss_longpath_err
++;
705 NDMP_LOG(LOG_ERR
, "Path %s/%s is too long.",
706 pnp
->tn_path
, dent
->fd_name
);
707 if (STOP_ONLONG(ftp
))
709 free(dent
->fd_fh
.fh_fpath
);
714 * The entry is not a directory so the callback
715 * function must be called.
717 if (!S_ISDIR(dent
->fd_attr
.st_mode
)) {
718 traverse_stats
.fss_nondir_calls
++;
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
;
727 if (rv
== FST_SKIP
) {
728 traverse_stats
.fss_nondir_skipped
++;
741 * Traverse the file system in the level-order way. The description
742 * and example is in the header file.
745 traverse_level(struct fs_traverse
*ftp
)
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 */
751 int pl
, el
; /* path and directory entry length */
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 */
760 if (!ftp
|| !ftp
->ft_path
|| !*ftp
->ft_path
|| !ftp
->ft_callbk
) {
761 NDMP_LOG(LOG_DEBUG
, "Invalid argument");
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
);
770 if (!ftp
->ft_lpath
) {
772 "report the same paths \"%s\"", ftp
->ft_path
);
773 ftp
->ft_lpath
= ftp
->ft_path
;
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
;
782 (void) strcpy(path
, ftp
->ft_lpath
);
783 (void) memset(&pfh
, 0, sizeof (pfh
));
784 rv
= fs_getstat(ftp
->ft_lpath
, &pfh
, &pst
);
787 "Error %d on fs_getstat(%s)", rv
, ftp
->ft_path
);
794 if (!S_ISDIR(pst
.st_mode
)) {
795 pn
.tn_path
= ftp
->ft_lpath
;
798 rv
= CALLBACK(&pn
, &en
);
800 NDMP_LOG(LOG_DEBUG
, "CALLBACK(%s): %d", pn
.tn_path
, rv
);
820 darg
.da_buf
= ndmp_malloc(MAX_DENT_BUF_SIZE
);
828 darg
.da_size
= MAX_DENT_BUF_SIZE
;
830 tsp
->ts_ent
= tsp
->ts_end
;
834 pn
.tn_fh
= &tsp
->ts_fh
;
835 pn
.tn_st
= &tsp
->ts_st
;
837 /* call the callback function on the path itself */
838 traverse_stats
.fss_dir_calls
++;
839 rv
= CALLBACK(&pn
, &en
);
844 if (rv
== FST_SKIP
) {
845 traverse_stats
.fss_dir_skipped
++;
855 traverse_stats
.fss_newdirs
++;
859 NDMP_LOG(LOG_DEBUG
, "pl %d \"%s\"", pl
, path
);
861 rv
= traverse_level_nondir(ftp
, tsp
, &pn
, &darg
);
864 free(tsp
->ts_fh
.fh_fpath
);
869 * If skipped by the callback function or
870 * error happened reading the information
872 if (rv
== FST_SKIP
|| rv
== SKIP_ENTRY
) {
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
885 /* re-start reading entries of the directory */
892 rv
= fs_readdir(&tsp
->ts_fh
, pn
.tn_path
,
893 &tsp
->ts_dpos
, nm
, &el
, &efh
,
896 traverse_stats
.fss_readdir_err
++;
899 "Error %d on readdir(%s) pos %d",
900 rv
, path
, tsp
->ts_dpos
);
907 /* done with this directory */
913 if (rootfs_dot_or_dotdot(nm
)) {
919 NDMP_LOG(LOG_DEBUG
, "%u dname: \"%s\"",
922 if (pl
+ 1 + el
> PATH_MAX
) {
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.
930 NDMP_LOG(LOG_ERR
, "Path %s/%s is too long.",
932 if (STOP_ONLONG(ftp
))
938 if (!S_ISDIR(est
.st_mode
))
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.
947 traverse_stats
.fss_dir_calls
++;
951 rv
= CALLBACK(&pn
, &en
);
958 if (rv
== FST_SKIP
) {
959 traverse_stats
.fss_dir_skipped
++;
966 * Push the current directory on to the stack and
967 * dive into the entry found.
969 if (cstack_push(sp
, tsp
, 0)) {
972 traverse_stats
.fss_pushes
++;
976 (void) strcpy(tsp
->ts_end
+ 1, nm
);
987 pn
.tn_fh
= &tsp
->ts_fh
;
988 pn
.tn_st
= &tsp
->ts_st
;
996 * A new directory must be processed, go to the start of
997 * the loop, open it and process it.
1003 free(tsp
->ts_fh
.fh_fpath
);
1007 if (rv
== SKIP_ENTRY
)
1011 if (cstack_pop(sp
, (void **)&tsp
, (int *)NULL
))
1014 traverse_stats
.fss_pops
++;
1018 "Poped pl %d \"%s\"", pl
, path
);
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
;
1028 * Pop and free all the remaining entries on the stack.
1030 while (!cstack_pop(sp
, (void **)&tsp
, (int *)NULL
)) {
1031 traverse_stats
.fss_stack_residue
++;
1033 free(tsp
->ts_fh
.fh_fpath
);
1043 * filecopy - Copy a file
1046 * char *dest - Destination path
1047 * char *src - Source path
1051 * #0 - Error occured
1052 * -4 - read/write error
1053 * -5 - source modified during copy
1055 * Simplified version for Solaris
1057 #define BUFSIZE 32768
1059 filecopy(char *dest
, char *src
)
1063 struct stat64 src_attr
;
1064 struct stat64 dst_attr
;
1066 u_longlong_t bytes_to_copy
;
1068 int file_copied
= 0;
1070 buf
= ndmp_malloc(BUFSIZE
);
1074 src_fh
= fopen(src
, "r");
1080 dst_fh
= fopen(dest
, "w");
1081 if (dst_fh
== NULL
) {
1083 (void) fclose(src_fh
);
1087 if (stat64(src
, &src_attr
) < 0) {
1089 (void) fclose(src_fh
);
1090 (void) fclose(dst_fh
);
1094 bytes_to_copy
= src_attr
.st_size
;
1095 while (bytes_to_copy
) {
1096 if (bytes_to_copy
> BUFSIZE
)
1099 nbytes
= bytes_to_copy
;
1101 if ((fread(buf
, nbytes
, 1, src_fh
) != 1) ||
1102 (fwrite(buf
, nbytes
, 1, dst_fh
) != 1))
1104 bytes_to_copy
-= nbytes
;
1107 (void) fclose(src_fh
);
1108 (void) fclose(dst_fh
);
1110 if (bytes_to_copy
> 0) {
1112 /* short read/write, remove the partial file */
1116 if (stat64(src
, &dst_attr
) < 0) {
1124 return (-5); /* source modified during copy */