diff options
Diffstat (limited to 'lib/libarchive/archive_write_disk.c')
-rw-r--r-- | lib/libarchive/archive_write_disk.c | 112 |
1 files changed, 92 insertions, 20 deletions
diff --git a/lib/libarchive/archive_write_disk.c b/lib/libarchive/archive_write_disk.c index 86426f1..9bf7d33 100644 --- a/lib/libarchive/archive_write_disk.c +++ b/lib/libarchive/archive_write_disk.c @@ -204,6 +204,7 @@ static void edit_deep_directories(struct archive_write_disk *ad); static int cleanup_pathname(struct archive_write_disk *); static int create_dir(struct archive_write_disk *, char *); static int create_parent_dir(struct archive_write_disk *, char *); +static int older(struct stat *, struct archive_entry *); static int restore_entry(struct archive_write_disk *); #ifdef HAVE_POSIX_ACL static int set_acl(struct archive_write_disk *, int fd, struct archive_entry *, @@ -292,7 +293,11 @@ _archive_write_header(struct archive *_a, struct archive_entry *entry) a->pst = NULL; a->current_fixup = NULL; a->deferred = 0; - a->entry = entry; + if (a->entry) { + archive_entry_free(a->entry); + a->entry = NULL; + } + a->entry = archive_entry_clone(entry); a->fd = -1; a->offset = 0; a->uid = a->user_uid; @@ -544,6 +549,11 @@ _archive_write_finish_entry(struct archive *_a) close(a->fd); a->fd = -1; } + /* If there's an entry, we can release it now. */ + if (a->entry) { + archive_entry_free(a->entry); + a->entry = NULL; + } a->archive.state = ARCHIVE_STATE_HEADER; return (ret); } @@ -682,21 +692,42 @@ restore_entry(struct archive_write_disk *a) /* Try creating it first; if this fails, we'll try to recover. */ en = create_filesystem_object(a); - if (en == ENOTDIR || en == ENOENT) { + if ((en == ENOTDIR || en == ENOENT) + && !(a->flags & ARCHIVE_EXTRACT_NO_AUTODIR)) { /* If the parent dir doesn't exist, try creating it. */ create_parent_dir(a, a->name); /* Now try to create the object again. */ en = create_filesystem_object(a); } - if (en == EEXIST) { + if ((en == EISDIR || en == EEXIST) + && (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) { /* If we're not overwriting, we're done. */ - if (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE) { - archive_set_error(&a->archive, en, "Already exists"); + archive_set_error(&a->archive, en, "Already exists"); + return (ARCHIVE_WARN); + } + + /* + * Some platforms return EISDIR if you call + * open(O_WRONLY | O_EXCL | O_CREAT) on a directory, some + * return EEXIST. POSIX is ambiguous, requiring EISDIR + * for open(O_WRONLY) on a dir and EEXIST for open(O_EXCL | O_CREAT) + * on an existing item. + */ + if (en == EISDIR) { + /* A dir is in the way of a non-dir, rmdir it. */ + if (rmdir(a->name) != 0) { + archive_set_error(&a->archive, errno, + "Can't remove already-existing dir"); return (ARCHIVE_WARN); } - - /* Find out what's in the way before we go any further. */ + /* Try again. */ + en = create_filesystem_object(a); + } else if (en == EEXIST) { + /* + * We know something is in the way, but we don't know what; + * we need to find out before we go any further. + */ if (lstat(a->name, &a->st) != 0) { archive_set_error(&a->archive, errno, "Can't stat existing object"); @@ -705,6 +736,14 @@ restore_entry(struct archive_write_disk *a) /* TODO: if it's a symlink... */ + if (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER) { + if (!older(&(a->st), a->entry)) { + archive_set_error(&a->archive, 0, + "File on disk is not older; skipping."); + return (ARCHIVE_FAILED); + } + } + /* If it's our archive, we're done. */ if (a->skip_file_dev > 0 && a->skip_file_ino > 0 && @@ -1405,16 +1444,13 @@ success: static int set_time(struct archive_write_disk *a) { - const struct stat *st; struct timeval times[2]; - st = archive_entry_stat(a->entry); + times[1].tv_sec = archive_entry_mtime(a->entry); + times[1].tv_usec = archive_entry_mtime_nsec(a->entry) / 1000; - times[1].tv_sec = st->st_mtime; - times[1].tv_usec = ARCHIVE_STAT_MTIME_NANOS(st) / 1000; - - times[0].tv_sec = st->st_atime; - times[0].tv_usec = ARCHIVE_STAT_ATIME_NANOS(st) / 1000; + times[0].tv_sec = archive_entry_atime(a->entry); + times[0].tv_usec = archive_entry_atime_nsec(a->entry) / 1000; #ifdef HAVE_FUTIMES if (a->fd >= 0 && futimes(a->fd, times) == 0) { @@ -1450,11 +1486,10 @@ set_time(struct archive_write_disk *a) static int set_time(struct archive_write_disk *a) { - const struct stat *st = archive_entry_stat(a->entry); struct utimbuf times; - times.modtime = st->st_mtime; - times.actime = st->st_atime; + times.modtime = archive_entry_mtime(a->entry); + times.actime = archive_entry_atime(a->entry); if (!S_ISLNK(a->mode) && utime(a->name, ×) != 0) { archive_set_error(&a->archive, errno, "Can't update time for %s", a->name); @@ -1479,6 +1514,7 @@ static int set_mode(struct archive_write_disk *a, int mode) { int r = ARCHIVE_OK; + mode &= 07777; /* Strip off file type bits. */ if (a->todo & TODO_SGID_CHECK) { /* @@ -1539,7 +1575,8 @@ set_mode(struct archive_write_disk *a, int mode) * impact. */ if (lchmod(a->name, mode) != 0) { - archive_set_error(&a->archive, errno, "Can't set permissions"); + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); r = ARCHIVE_WARN; } #endif @@ -1554,7 +1591,7 @@ set_mode(struct archive_write_disk *a, int mode) if (a->fd >= 0) { if (fchmod(a->fd, mode) != 0) { archive_set_error(&a->archive, errno, - "Can't set permissions"); + "Can't set permissions to 0%o", (int)mode); r = ARCHIVE_WARN; } } else @@ -1563,7 +1600,7 @@ set_mode(struct archive_write_disk *a, int mode) * we'll just use chmod(). */ if (chmod(a->name, mode) != 0) { archive_set_error(&a->archive, errno, - "Can't set permissions"); + "Can't set permissions to 0%o", (int)mode); r = ARCHIVE_WARN; } } @@ -1998,3 +2035,38 @@ trivial_lookup_uid(void *private_data, const char *uname, uid_t uid) (void)uname; /* UNUSED */ return (uid); } + +/* + * Test if file on disk is older than entry. + */ +static int +older(struct stat *st, struct archive_entry *entry) +{ + /* First, test the seconds and return if we have a definite answer. */ + /* Definitely older. */ + if (st->st_mtime < archive_entry_mtime(entry)) + return (1); + /* Definitely younger. */ + if (st->st_mtime > archive_entry_mtime(entry)) + return (0); + /* If this platform supports fractional seconds, try those. */ +#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC + /* Definitely older. */ + if (st->st_mtimespec.tv_nsec < archive_entry_mtime_nsec(entry)) + return (1); + /* Definitely younger. */ + if (st->st_mtimespec.tv_nsec > archive_entry_mtime_nsec(entry)) + return (0); +#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + /* Definitely older. */ + if (st->st_mtim.tv_nsec < archive_entry_mtime_nsec(entry)) + return (1); + /* Definitely older. */ + if (st->st_mtim.tv_nsec > archive_entry_mtime_nsec(entry)) + return (0); +#else + /* This system doesn't have high-res timestamps. */ +#endif + /* Same age, so not older. */ + return (0); +} |