summaryrefslogtreecommitdiff
path: root/usr.bin/tar/write.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/tar/write.c')
-rw-r--r--usr.bin/tar/write.c291
1 files changed, 116 insertions, 175 deletions
diff --git a/usr.bin/tar/write.c b/usr.bin/tar/write.c
index dfcbcba274a4..3d833e93f8d3 100644
--- a/usr.bin/tar/write.c
+++ b/usr.bin/tar/write.c
@@ -37,7 +37,6 @@ __FBSDID("$FreeBSD$");
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
-#include <fts.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
@@ -51,6 +50,7 @@ __FBSDID("$FreeBSD$");
#endif
#include "bsdtar.h"
+#include "tree.h"
/* Fixed size of uname/gname caches. */
#define name_cache_size 101
@@ -514,205 +514,146 @@ append_archive(struct bsdtar *bsdtar, struct archive *a, const char *filename)
static void
write_hierarchy(struct bsdtar *bsdtar, struct archive *a, const char *path)
{
- FTS *fts;
- FTSENT *ftsent;
- int ftsoptions;
- char *fts_argv[2];
+ struct tree *tree;
+ char symlink_mode = bsdtar->symlink_mode;
+ dev_t first_dev = 0;
+ int dev_recorded = 0;
+ int tree_ret;
#ifdef __linux
int fd, r;
unsigned long fflags;
#endif
- /*
- * Sigh: fts_open modifies it's first parameter, so we have to
- * copy 'path' to mutable storage.
- */
- fts_argv[0] = strdup(path);
- if (fts_argv[0] == NULL)
- bsdtar_errc(bsdtar, 1, ENOMEM, "Can't open %s", path);
- fts_argv[1] = NULL;
- ftsoptions = FTS_PHYSICAL;
- switch (bsdtar->symlink_mode) {
- case 'H':
- ftsoptions |= FTS_COMFOLLOW;
- break;
- case 'L':
- ftsoptions = FTS_COMFOLLOW | FTS_LOGICAL;
- break;
- }
- if (bsdtar->option_dont_traverse_mounts)
- ftsoptions |= FTS_XDEV;
-
- fts = fts_open(fts_argv, ftsoptions, NULL);
+ tree = tree_open(path);
-
- if (!fts) {
+ if (!tree) {
bsdtar_warnc(bsdtar, errno, "%s: Cannot open", path);
bsdtar->return_value = 1;
return;
}
- while ((ftsent = fts_read(fts))) {
- switch (ftsent->fts_info) {
- case FTS_NS:
- bsdtar_warnc(bsdtar, ftsent->fts_errno,
- "%s: Could not stat", ftsent->fts_path);
- bsdtar->return_value = 1;
- break;
- case FTS_ERR:
- bsdtar_warnc(bsdtar, ftsent->fts_errno, "%s",
- ftsent->fts_path);
- bsdtar->return_value = 1;
- break;
- case FTS_DNR:
- bsdtar_warnc(bsdtar, ftsent->fts_errno,
- "%s: Cannot read directory contents",
- ftsent->fts_path);
- bsdtar->return_value = 1;
- break;
- case FTS_W: /* Skip Whiteout entries */
- break;
- case FTS_DC: /* Directory that causes cycle */
- /* XXX Does this need special handling ? */
- break;
- case FTS_D:
- /*
- * If this dir is flagged "nodump" and we're
- * honoring such flags, tell FTS to skip the
- * entire tree and don't write the entry for the
- * directory itself.
- */
-#ifdef HAVE_CHFLAGS
- if (bsdtar->option_honor_nodump &&
- (ftsent->fts_statp->st_flags & UF_NODUMP)) {
- fts_set(fts, ftsent, FTS_SKIP);
- break;
- }
-#endif
-
-#ifdef __linux
- /*
- * Linux has a nodump flag too but to read it
- * we have to open() the dir and do an ioctl on it...
- */
- if (bsdtar->option_honor_nodump &&
- ((fd = open(ftsent->fts_name, O_RDONLY|O_NONBLOCK)) >= 0) &&
- ((r = ioctl(fd, EXT2_IOC_GETFLAGS, &fflags)),
- close(fd), r) >= 0 &&
- (fflags & EXT2_NODUMP_FL)) {
- fts_set(fts, ftsent, FTS_SKIP);
- break;
- }
-#endif
-
- /*
- * In -u mode, we need to check whether this
- * is newer than what's already in the archive.
- * In all modes, we need to obey --newerXXX flags.
- */
- if (!new_enough(bsdtar, ftsent->fts_path,
- ftsent->fts_statp))
- break;
- /*
- * If this dir is excluded by a filename
- * pattern, tell FTS to skip the entire tree
- * and don't write the entry for the directory
- * itself.
- */
- if (excluded(bsdtar, ftsent->fts_path)) {
- fts_set(fts, ftsent, FTS_SKIP);
- break;
- }
+ while ((tree_ret = tree_next(tree))) {
+ const char *name = tree_current_path(tree);
+ const struct stat *st = NULL, *lst = NULL;
+ int descend;
- /*
- * If the user vetoes the directory, skip
- * the whole thing.
- */
- if (bsdtar->option_interactive &&
- !yes("add '%s'", ftsent->fts_path)) {
- fts_set(fts, ftsent, FTS_SKIP);
- break;
- }
+ if (tree_ret == TREE_ERROR)
+ bsdtar_warnc(bsdtar, errno, "%s", name);
+ if (tree_ret != TREE_REGULAR)
+ continue;
+ lst = tree_current_lstat(tree);
+ if (lst == NULL) {
+ /* Couldn't lstat(); must not exist. */
+ bsdtar_warnc(bsdtar, errno, "%s: Cannot open", path);
+ bsdtar->return_value = 1;
+ continue;
+ }
+ if (S_ISLNK(lst->st_mode))
+ st = tree_current_stat(tree);
+ /* Default: descend into any dir or symlink to dir. */
+ /* We'll adjust this later on. */
+ descend = 0;
+ if ((st != NULL) && S_ISDIR(st->st_mode))
+ descend = 1;
+ if ((lst != NULL) && S_ISDIR(lst->st_mode))
+ descend = 1;
+
+ /*
+ * If user has asked us not to cross mount points,
+ * then don't descend into into a dir on a different
+ * device.
+ */
+ if (!dev_recorded) {
+ first_dev = lst->st_dev;
+ dev_recorded = 1;
+ }
+ if (bsdtar->option_dont_traverse_mounts) {
+ if (lst != NULL && lst->st_dev != first_dev)
+ descend = 0;
+ }
- /*
- * If we're not recursing, tell FTS to skip the
- * tree but do fall through and write the entry
- * for the dir itself.
- */
- if (bsdtar->option_no_subdirs)
- fts_set(fts, ftsent, FTS_SKIP);
- write_entry(bsdtar, a, ftsent->fts_statp,
- ftsent->fts_path, ftsent->fts_pathlen,
- ftsent->fts_accpath);
- break;
- case FTS_F:
- case FTS_SL:
- case FTS_SLNONE:
- case FTS_DEFAULT:
- /*
- * Skip this file if it's flagged "nodump" and we're
- * honoring that flag.
- */
-#if defined(HAVE_CHFLAGS) && defined(UF_NODUMP)
- if (bsdtar->option_honor_nodump &&
- (ftsent->fts_statp->st_flags & UF_NODUMP))
- break;
+ /*
+ * If this file/dir is flagged "nodump" and we're
+ * honoring such flags, skip this file/dir.
+ */
+#ifdef HAVE_CHFLAGS
+ if (bsdtar->option_honor_nodump &&
+ (lst->st_flags & UF_NODUMP))
+ continue;
#endif
#ifdef __linux
- /*
- * Linux has a nodump flag too but to read it
- * we have to open() the file and do an ioctl on it...
- */
- if (bsdtar->option_honor_nodump &&
- S_ISREG(ftsent->fts_statp->st_mode) &&
- ((fd = open(ftsent->fts_name, O_RDONLY|O_NONBLOCK)) >= 0) &&
- ((r = ioctl(fd, EXT2_IOC_GETFLAGS, &fflags)),
- close(fd), r) >= 0 &&
- (fflags & EXT2_NODUMP_FL))
- break;
+ /*
+ * Linux has a nodump flag too but to read it
+ * we have to open() the file/dir and do an ioctl on it...
+ */
+ if (bsdtar->option_honor_nodump &&
+ ((fd = open(name, O_RDONLY|O_NONBLOCK)) >= 0) &&
+ ((r = ioctl(fd, EXT2_IOC_GETFLAGS, &fflags)),
+ close(fd), r) >= 0 &&
+ (fflags & EXT2_NODUMP_FL))
+ continue;
#endif
- /*
- * Skip this file if it's excluded by a
- * filename pattern.
- */
- if (excluded(bsdtar, ftsent->fts_path))
- break;
-
- /*
- * In -u mode, we need to check whether this
- * is newer than what's already in the archive.
- */
- if (!new_enough(bsdtar, ftsent->fts_path,
- ftsent->fts_statp))
- break;
+ /*
+ * If this file/dir is excluded by a filename
+ * pattern, skip it.
+ */
+ if (excluded(bsdtar, name))
+ continue;
- if (bsdtar->option_interactive &&
- !yes("add '%s'", ftsent->fts_path)) {
- break;
- }
+ /*
+ * If the user vetoes this file/directory, skip it.
+ */
+ if (bsdtar->option_interactive &&
+ !yes("add '%s'", name))
+ continue;
- write_entry(bsdtar, a, ftsent->fts_statp,
- ftsent->fts_path, ftsent->fts_pathlen,
- ftsent->fts_accpath);
+ /*
+ * If this is a dir, decide whether or not to recurse.
+ */
+ if (bsdtar->option_no_subdirs)
+ descend = 0;
+
+ /*
+ * Distinguish 'L'/'P'/'H' symlink following.
+ */
+ switch(symlink_mode) {
+ case 'H':
+ /* 'H': First item (from command line) like 'L'. */
+ lst = tree_current_stat(tree);
+ /* 'H': After the first item, rest like 'P'. */
+ symlink_mode = 'P';
break;
- case FTS_DP:
+ case 'L':
+ /* 'L': Do descend through a symlink to dir. */
+ /* 'L': Archive symlink to file as file. */
+ lst = tree_current_stat(tree);
break;
default:
- bsdtar_warnc(bsdtar, 0,
- "%s: Hierarchy traversal error %d\n",
- ftsent->fts_path,
- ftsent->fts_info);
+ /* 'P': Don't descend through a symlink to dir. */
+ if (!S_ISDIR(lst->st_mode))
+ descend = 0;
+ /* 'P': Archive symlink to file as symlink. */
+ /* lst = tree_current_lstat(tree); */
break;
}
+ if (descend)
+ tree_descend(tree);
+
+ /*
+ * In -u mode, we need to check whether this
+ * is newer than what's already in the archive.
+ * In all modes, we need to obey --newerXXX flags.
+ */
+ if (new_enough(bsdtar, name, lst)) {
+ write_entry(bsdtar, a, lst, name,
+ tree_current_pathlen(tree),
+ tree_current_access_path(tree));
+ }
}
- if (errno)
- bsdtar_warnc(bsdtar, errno, "%s", path);
- if (fts_close(fts))
- bsdtar_warnc(bsdtar, errno, "fts_close failed");
- free(fts_argv[0]);
+ tree_close(tree);
}
/*
@@ -750,7 +691,7 @@ write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st,
/* Display entry as we process it. This format is required by SUSv2. */
if (bsdtar->verbose)
- safe_fprintf(stderr, "a %s", pathname);
+ safe_fprintf(stderr, "a %s", archive_entry_pathname(entry));
/* Read symbolic link information. */
if ((st->st_mode & S_IFMT) == S_IFLNK) {
@@ -784,7 +725,7 @@ write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st,
#ifdef __linux
if ((S_ISREG(st->st_mode) || S_ISDIR(st->st_mode)) &&
((fd = open(accpath, O_RDONLY|O_NONBLOCK)) >= 0) &&
- ((r = ioctl(fd, EXT2_IOC_GETFLAGS, &stflags)), close(fd), r) >= 0 &&
+ ((r = ioctl(fd, EXT2_IOC_GETFLAGS, &stflags)), close(fd), (fd = -1), r) >= 0 &&
stflags) {
archive_entry_set_fflags(entry, stflags, 0);
}