summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/libarchive/archive_read_extract.c352
1 files changed, 213 insertions, 139 deletions
diff --git a/lib/libarchive/archive_read_extract.c b/lib/libarchive/archive_read_extract.c
index 6dd23d7..9ed6aab 100644
--- a/lib/libarchive/archive_read_extract.c
+++ b/lib/libarchive/archive_read_extract.c
@@ -74,6 +74,7 @@ struct extract {
mode_t default_dir_mode;
struct archive_string mkdirpath;
struct fixup_entry *fixup_list;
+ struct fixup_entry *current_fixup;
/*
* Cached stat data from disk for the current entry.
@@ -109,9 +110,9 @@ static int extract_hard_link(struct archive *, struct archive_entry *, int);
static int extract_symlink(struct archive *, struct archive_entry *, int);
static gid_t lookup_gid(struct archive *, const char *uname, gid_t);
static uid_t lookup_uid(struct archive *, const char *uname, uid_t);
-static int mkdirpath(struct archive *, const char *);
-static int mkdirpath_recursive(struct archive *, char *,
- const struct stat *, mode_t, int);
+static int mkdirpath(struct archive *, const char *, int flags);
+static int mkdirpath_internal(struct archive *, char *, int flags);
+static int mkdirpath_recursive(struct archive *, char *, int flags);
static int restore_metadata(struct archive *, struct archive_entry *,
int flags);
#ifdef HAVE_POSIX_ACL
@@ -160,6 +161,7 @@ archive_read_extract(struct archive *a, struct archive_entry *entry, int flags)
umask(extract->umask = umask(0)); /* Read the current umask. */
extract->default_dir_mode = DEFAULT_DIR_MODE & ~extract->umask;
extract->pst = NULL;
+ extract->current_fixup = NULL;
restore_pwd = -1;
/*
@@ -217,16 +219,22 @@ archive_read_extract(struct archive *a, struct archive_entry *entry, int flags)
}
/*
- * Cleanup function for archive_extract. Free name/mode list and
- * restore permissions and dir timestamps. This must be done last;
- * otherwise, the dir permission might prevent us from restoring a
- * file. Similarly, the act of restoring a file touches the directory
- * and changes the timestamp on the dir, so we have to touch-up the
- * timestamps at the end as well. Note that tar/cpio do not require
- * that archives be in a particular order; there is no way to know
- * when the last file has been restored within a directory, so there's
- * no way to optimize the memory usage here by fixing up the directory
- * any earlier than the end-of-archive.
+ * Cleanup function for archive_extract. Mostly, this involves processing
+ * the fixup list, which is used to address a number of problems:
+ * * Dir permissions might prevent us from restoring a file in that
+ * dir, so we restore the dir 0700 first, then correct the
+ * mode at the end.
+ * * Similarly, the act of restoring a file touches the directory
+ * and changes the timestamp on the dir, so we have to touch-up the
+ * timestamps at the end as well.
+ * * Some file flags can interfere with the restore by, for example,
+ * preventing the creation of hardlinks to those files.
+ *
+ * Note that tar/cpio do not require that archives be in a particular
+ * order; there is no way to know when the last file has been restored
+ * within a directory, so there's no way to optimize the memory usage
+ * here by fixing up the directory any earlier than the
+ * end-of-archive.
*
* XXX TODO: Directory ACLs should be restored here, for the same
* reason we set directory perms here. XXX
@@ -246,6 +254,7 @@ archive_extract_cleanup(struct archive *a)
p = sort_dir_list(extract->fixup_list);
while (p != NULL) {
+ extract->pst = NULL; /* Mark stat buff as out-of-date. */
if (p->fixup & FIXUP_TIMES) {
struct timeval times[2];
times[1].tv_sec = p->mtime;
@@ -336,6 +345,40 @@ sort_dir_list(struct fixup_entry *p)
return (p);
}
+/*
+ * Returns a new, initialized fixup entry.
+ */
+static struct fixup_entry *
+new_fixup(struct archive *a, const char *pathname)
+{
+ struct extract *extract;
+ struct fixup_entry *fe;
+
+ extract = a->extract;
+ fe = malloc(sizeof(struct fixup_entry));
+ if (fe == NULL)
+ return (NULL);
+ fe->next = extract->fixup_list;
+ extract->fixup_list = fe;
+ fe->fixup = 0;
+ fe->name = strdup(pathname);
+ return (fe);
+}
+
+/*
+ * Returns a fixup structure for the current entry.
+ */
+static struct fixup_entry *
+current_fixup(struct archive *a, const char *pathname)
+{
+ struct extract *extract;
+
+ extract = a->extract;
+ if (extract->current_fixup == NULL)
+ extract->current_fixup = new_fixup(a, pathname);
+ return (extract->current_fixup);
+}
+
static int
extract_file(struct archive *a, struct archive_entry *entry, int flags)
{
@@ -366,7 +409,7 @@ extract_file(struct archive *a, struct archive_entry *entry, int flags)
/* Might be a non-existent parent dir; try fixing that. */
if (fd < 0) {
- mkdirpath(a, name);
+ mkdirpath(a, name, flags);
fd = open(name, O_WRONLY | O_CREAT | O_EXCL, mode);
}
if (fd < 0) {
@@ -384,130 +427,169 @@ static int
extract_dir(struct archive *a, struct archive_entry *entry, int flags)
{
struct extract *extract;
- const struct stat *st;
- char *p;
- size_t len;
- mode_t mode;
+ struct fixup_entry *fe;
+ char *path, *p;
extract = a->extract;
+ extract->pst = NULL; /* Invalidate cached stat data. */
/* Copy path to mutable storage. */
- archive_strcpy(&(extract->mkdirpath),
- archive_entry_pathname(entry));
- p = extract->mkdirpath.s;
- len = strlen(p);
- if (len > 2 && p[len - 1] == '.' && p[len - 2] == '/')
- p[--len] = '\0'; /* Remove trailing "/." */
- if (len > 2 && p[len - 1] == '/')
- p[--len] = '\0'; /* Remove trailing "/" */
- /* Recursively try to build the path. */
- st = archive_entry_stat(entry);
- mode = st->st_mode;
- /* Obey umask unless ARCHIVE_EXTRACT_PERM for explicit dirs. */
- if ((flags & ARCHIVE_EXTRACT_PERM) == 0)
- mode &= ~extract->umask;
- extract->pst = NULL; /* Invalidate cached stat data. */
- if (mkdirpath_recursive(a, p, st, mode, flags))
+ archive_strcpy(&(extract->mkdirpath), archive_entry_pathname(entry));
+ path = extract->mkdirpath.s;
+
+ /* Deal with any troublesome trailing path elements. */
+ for (;;) {
+ if (*path == '\0')
+ return (ARCHIVE_OK);
+ /* Locate last element; trim trailing '/'. */
+ p = strrchr(path, '/');
+ if (p != NULL) {
+ if (p[1] == '\0') {
+ *p = '\0';
+ continue;
+ }
+ p++;
+ } else
+ p = path;
+ /* Trim trailing '.'. */
+ if (p[0] == '.' && p[1] == '\0') {
+ p[0] = '\0';
+ continue;
+ }
+ /* Just exit on trailing '..'. */
+ if (p[0] == '.' && p[1] == '.' && p[2] == '\0')
+ return (ARCHIVE_OK);
+ break;
+ }
+
+ if (mkdir(path, SECURE_DIR_MODE) == 0)
+ goto success;
+
+ if (extract->pst == NULL && stat(path, &extract->st) == 0)
+ extract->pst = &extract->st;
+
+ if (extract->pst != NULL) {
+ extract->pst = &extract->st;
+ if (S_ISDIR(extract->pst->st_mode))
+ goto success;
+ /* It exists but isn't a dir. */
+ if ((flags & ARCHIVE_EXTRACT_UNLINK))
+ unlink(path);
+ } else {
+ /* Doesn't already exist; try building the parent path. */
+ if (mkdirpath(a, p, flags) != ARCHIVE_OK)
+ return (ARCHIVE_WARN);
+ }
+
+ /* One final attempt to create the dir. */
+ if (mkdirpath_internal(a, path, flags) != ARCHIVE_OK)
return (ARCHIVE_WARN);
- archive_entry_set_mode(entry, 0700);
+
+success:
+ /* Add this dir to the fixup list. */
+ fe = current_fixup(a, path);
+ fe->fixup |= FIXUP_MODE;
+ fe->mode = archive_entry_mode(entry);
+ if ((flags & ARCHIVE_EXTRACT_PERM) == 0)
+ fe->mode &= ~extract->umask;
+ if (flags & ARCHIVE_EXTRACT_TIME) {
+ fe->fixup |= FIXUP_TIMES;
+ fe->mtime = archive_entry_mtime(entry);
+ fe->mtime_nanos = archive_entry_mtime_nsec(entry);
+ fe->atime = archive_entry_atime(entry);
+ fe->atime_nanos = archive_entry_atime_nsec(entry);
+ }
+ /* For now, set the mode to SECURE_DIR_MODE. */
+ archive_entry_set_mode(entry, SECURE_DIR_MODE);
return (restore_metadata(a, entry, flags));
}
/*
- * Convenience form.
+ * Create the parent of the specified path. Copy the provided
+ * path into mutable storage first.
*/
static int
-mkdirpath(struct archive *a, const char *path)
+mkdirpath(struct archive *a, const char *path, int flags)
{
struct extract *extract;
- char *p;
extract = a->extract;
/* Copy path to mutable storage. */
archive_strcpy(&(extract->mkdirpath), path);
- p = extract->mkdirpath.s;
- p = strrchr(extract->mkdirpath.s, '/');
- if (p == NULL)
- return (ARCHIVE_OK);
- *p = '\0';
- /* Recursively try to build the path. */
- if (mkdirpath_recursive(a, extract->mkdirpath.s,
- NULL, extract->default_dir_mode, 0))
- return (ARCHIVE_WARN);
- return (ARCHIVE_OK);
+ return (mkdirpath_internal(a, extract->mkdirpath.s, flags));
+}
+
+/*
+ * Handle remaining setup for mkdirpath_recursive(), assuming
+ * path is already in mutable storage.
+ */
+static int
+mkdirpath_internal(struct archive *a, char *path, int flags)
+{
+ char *slash;
+ mode_t old_umask;
+ int r;
+
+ /* Remove tail element to obtain parent name. */
+ slash = strrchr(path, '/');
+ if (slash == NULL)
+ return (ARCHIVE_OK);
+ *slash = '\0';
+ old_umask = umask(~SECURE_DIR_MODE);
+ r = mkdirpath_recursive(a, path, flags);
+ umask(old_umask);
+ *slash = '/';
+ return (r);
}
/*
- * Returns 0 if it successfully created necessary directories.
+ * Create the specified dir, recursing to create parents as necessary.
+ *
+ * Returns ARCHIVE_OK if the path exists when we're done here.
* Otherwise, returns ARCHIVE_WARN.
*/
static int
-mkdirpath_recursive(struct archive *a, char *path,
- const struct stat *desired_stat, mode_t mode, int flags)
+mkdirpath_recursive(struct archive *a, char *path, int flags)
{
struct stat st;
struct extract *extract;
struct fixup_entry *le;
- char *p;
- mode_t writable_mode = SECURE_DIR_MODE;
+ char *slash, *base;
int r;
extract = a->extract;
+ r = ARCHIVE_OK;
- if (path[0] == '.' && path[1] == 0)
- return (ARCHIVE_OK);
-
- if (mode != writable_mode ||
- (desired_stat != NULL && (flags & ARCHIVE_EXTRACT_TIME))) {
- /* Add this dir to the fixup list. */
- le = malloc(sizeof(struct fixup_entry));
- le->fixup = 0;
- le->next = extract->fixup_list;
- extract->fixup_list = le;
- le->name = strdup(path);
-
- if (mode != writable_mode) {
- le->mode = mode;
- le->fixup |= FIXUP_MODE;
- mode = writable_mode;
- }
- if (flags & ARCHIVE_EXTRACT_TIME) {
- le->mtime = desired_stat->st_mtime;
- le->mtime_nanos = ARCHIVE_STAT_MTIME_NANOS(desired_stat);
- le->atime = desired_stat->st_atime;
- le->atime_nanos = ARCHIVE_STAT_ATIME_NANOS(desired_stat);
- le->fixup |= FIXUP_TIMES;
+ /* Check for special names and just skip them. */
+ slash = strrchr(path, '/');
+ base = strrchr(path, '/');
+ if (slash == NULL)
+ base = path;
+ else
+ base = slash + 1;
+
+ if (base[0] == '\0' ||
+ (base[0] == '.' && base[1] == '\0') ||
+ (base[0] == '.' && base[1] == '.' && base[2] == '\0')) {
+ /* Don't bother trying to create null path, '.', or '..'. */
+ if (slash != NULL) {
+ *slash = '\0';
+ r = mkdirpath_recursive(a, path, flags);
+ *slash = '/';
+ return (r);
}
+ return (ARCHIVE_OK);
}
/*
- * Try to make the longest dir first. Most archives are
- * written in a reasonable order, so this will almost always
- * save us from having to inspect the parent dirs.
- */
- if (mkdir(path, mode) == 0)
- return (ARCHIVE_OK);
- /*
- * Do "unlink first" after. The preceding syscall will always
- * fail if something already exists, so we save a little time
- * in the common case by not trying to unlink until we know
- * something is there.
- */
- if ((flags & ARCHIVE_EXTRACT_UNLINK))
- unlink(path);
- /*
* Yes, this should be stat() and not lstat(). Using lstat()
- * here loses the ability to extract through symlinks. If
- * clients don't want to extract through symlinks, they should
- * specify ARCHIVE_EXTRACT_UNLINK.
- *
- * Note that this cannot use the extract->st cache.
+ * here loses the ability to extract through symlinks. Also note
+ * that this should not use the extract->st cache.
*/
if (stat(path, &st) == 0) {
- /* Already exists! */
if (S_ISDIR(st.st_mode))
return (ARCHIVE_OK);
if ((flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) {
@@ -515,44 +597,40 @@ mkdirpath_recursive(struct archive *a, char *path,
"Can't create directory '%s'", path);
return (ARCHIVE_WARN);
}
- /* Not a dir: remove it and create a directory. */
- if (unlink(path) == 0 &&
- mkdir(path, mode) == 0)
- return (ARCHIVE_OK);
- } else if (errno != ENOENT) {
+ if (unlink(path) != 0) {
+ archive_set_error(a, errno,
+ "Can't create directory '%s': "
+ "Conflicting file cannot be removed");
+ return (ARCHIVE_WARN);
+ }
+ } else if (errno != ENOENT && errno != ENOTDIR) {
/* Stat failed? */
archive_set_error(a, errno, "Can't test directory '%s'", path);
return (ARCHIVE_WARN);
- }
-
- /* Doesn't exist: try creating parent dir. */
- p = strrchr(path, '/');
- if (p != NULL) {
- *p = '\0'; /* Terminate path name. */
- /* Note that implicit dirs always obey the umask. */
- r = mkdirpath_recursive(a, path, NULL,
- extract->default_dir_mode, 0);
- *p = '/'; /* Restore the '/' we just overwrote. */
+ } else if (slash != NULL) {
+ *slash = '\0';
+ r = mkdirpath_recursive(a, path, flags);
+ *slash = '/';
if (r != ARCHIVE_OK)
return (r);
- /* Parent exists now; let's create the last component. */
- p++;
- /* Of course, "", ".", and ".." are easy. */
- if (p[0] == '\0')
- return (ARCHIVE_OK);
- if (p[0] == '.' && p[1] == '\0')
- return (ARCHIVE_OK);
- if (p[0] == '.' && p[1] == '.' && p[2] == '\0')
- return (ARCHIVE_OK);
- if (mkdir(path, mode) == 0)
- return (ARCHIVE_OK);
- /*
- * Without the following check, a/b/../b/c/d fails at
- * the second visit to 'b', so 'd' can't be created.
- */
- if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
- return (ARCHIVE_OK);
}
+
+ if (mkdir(path, SECURE_DIR_MODE) == 0) {
+ le = new_fixup(a, path);
+ le->fixup |= FIXUP_MODE;
+ le->mode = extract->default_dir_mode;
+ return (ARCHIVE_OK);
+ }
+
+ /*
+ * Without the following check, a/b/../b/c/d fails at the
+ * second visit to 'b', so 'd' can't be created. Note that we
+ * don't add it to the fixup list here, as it's already been
+ * added.
+ */
+ if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
+ return (ARCHIVE_OK);
+
archive_set_error(a, errno, "Failed to create dir '%s'", path);
return (ARCHIVE_WARN);
}
@@ -578,7 +656,7 @@ extract_hard_link(struct archive *a, struct archive_entry *entry, int flags)
if (r != 0) {
/* Might be a non-existent parent dir; try fixing that. */
- mkdirpath(a, pathname);
+ mkdirpath(a, pathname, flags);
r = link(linkname, pathname);
}
@@ -615,7 +693,7 @@ extract_symlink(struct archive *a, struct archive_entry *entry, int flags)
if (r != 0) {
/* Might be a non-existent parent dir; try fixing that. */
- mkdirpath(a, pathname);
+ mkdirpath(a, pathname, flags);
r = symlink(linkname, pathname);
}
@@ -648,7 +726,7 @@ extract_device(struct archive *a, struct archive_entry *entry,
/* Might be a non-existent parent dir; try fixing that. */
if (r != 0 && errno == ENOENT) {
- mkdirpath(a, archive_entry_pathname(entry));
+ mkdirpath(a, archive_entry_pathname(entry), flags);
r = mknod(archive_entry_pathname(entry), mode,
archive_entry_rdev(entry));
}
@@ -697,7 +775,7 @@ extract_fifo(struct archive *a, struct archive_entry *entry, int flags)
/* Might be a non-existent parent dir; try fixing that. */
if (r != 0 && errno == ENOENT) {
- mkdirpath(a, archive_entry_pathname(entry));
+ mkdirpath(a, archive_entry_pathname(entry), flags);
r = mkfifo(archive_entry_pathname(entry),
archive_entry_mode(entry));
}
@@ -901,12 +979,8 @@ set_perm(struct archive *a, struct archive_entry *entry, int mode, int flags)
* all of this if it's not necessary.
*/
if ((critical_flags != 0) && (set & critical_flags)) {
- le = malloc(sizeof(struct fixup_entry));
- le->fixup = FIXUP_FFLAGS;
- le->next = extract->fixup_list;
- extract->fixup_list = le;
- le->name = strdup(archive_entry_pathname(entry));
- le->mode = archive_entry_mode(entry);
+ le = current_fixup(a, archive_entry_pathname(entry));
+ le->fixup |= FIXUP_FFLAGS;
le->fflags_set = set;
} else {
r = set_fflags(a, archive_entry_pathname(entry),
OpenPOWER on IntegriCloud