summaryrefslogtreecommitdiff
path: root/archivers/libarchive/files/libarchive/archive_write_disk.c
diff options
context:
space:
mode:
Diffstat (limited to 'archivers/libarchive/files/libarchive/archive_write_disk.c')
-rw-r--r--archivers/libarchive/files/libarchive/archive_write_disk.c163
1 files changed, 120 insertions, 43 deletions
diff --git a/archivers/libarchive/files/libarchive/archive_write_disk.c b/archivers/libarchive/files/libarchive/archive_write_disk.c
index 620beac48ed..8010c1330ea 100644
--- a/archivers/libarchive/files/libarchive/archive_write_disk.c
+++ b/archivers/libarchive/files/libarchive/archive_write_disk.c
@@ -187,6 +187,8 @@ struct archive_write_disk {
/* UID/GID to use in restoring this entry. */
uid_t uid;
gid_t gid;
+ /* Last offset written to disk. */
+ off_t last_offset;
};
/*
@@ -242,6 +244,31 @@ static int _archive_write_finish_entry(struct archive *);
static ssize_t _archive_write_data(struct archive *, const void *, size_t);
static ssize_t _archive_write_data_block(struct archive *, const void *, size_t, off_t);
+static int
+_archive_write_disk_lazy_stat(struct archive_write_disk *a)
+{
+ if (a->pst != NULL) {
+ /* Already have stat() data available. */
+ return (ARCHIVE_OK);
+ }
+#ifdef HAVE_FSTAT
+ if (a->fd >= 0 && fstat(a->fd, &a->st) == 0) {
+ a->pst = &a->st;
+ return (ARCHIVE_OK);
+ }
+#endif
+ /*
+ * XXX At this point, symlinks should not be hit, otherwise
+ * XXX a race occured. Do we want to check explicitly for that?
+ */
+ if (lstat(a->name, &a->st) == 0) {
+ a->pst = &a->st;
+ return (ARCHIVE_OK);
+ }
+ archive_set_error(&a->archive, errno, "Couldn't stat file");
+ return (ARCHIVE_WARN);
+}
+
static struct archive_vtable *
archive_write_disk_vtable(void)
{
@@ -294,7 +321,7 @@ _archive_write_header(struct archive *_a, struct archive_entry *entry)
archive_clear_error(&a->archive);
if (a->archive.state & ARCHIVE_STATE_DATA) {
r = _archive_write_finish_entry(&a->archive);
- if (r != ARCHIVE_OK)
+ if (r == ARCHIVE_FATAL)
return (r);
}
@@ -308,6 +335,7 @@ _archive_write_header(struct archive *_a, struct archive_entry *entry)
}
a->entry = archive_entry_clone(entry);
a->fd = -1;
+ a->last_offset = 0;
a->offset = 0;
a->uid = a->user_uid;
a->mode = archive_entry_mode(a->entry);
@@ -463,6 +491,7 @@ _archive_write_data_block(struct archive *_a,
{
struct archive_write_disk *a = (struct archive_write_disk *)_a;
ssize_t bytes_written = 0;
+ ssize_t block_size, bytes_to_write;
int r = ARCHIVE_OK;
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
@@ -473,31 +502,53 @@ _archive_write_data_block(struct archive *_a,
}
archive_clear_error(&a->archive);
- /* Seek if necessary to the specified offset. */
- if (offset != a->offset) {
- if (lseek(a->fd, offset, SEEK_SET) < 0) {
- archive_set_error(&a->archive, errno, "Seek failed");
- return (ARCHIVE_WARN);
- }
- a->offset = offset;
+ if (a->flags & ARCHIVE_EXTRACT_SPARSE) {
+ if ((r = _archive_write_disk_lazy_stat(a)) != ARCHIVE_OK)
+ return (r);
+ block_size = a->pst->st_blksize;
+ } else
+ block_size = -1;
+
+ if ((off_t)(offset + size) > a->filesize) {
+ size = (size_t)(a->filesize - a->offset);
+ archive_set_error(&a->archive, 0,
+ "Write request too large");
+ r = ARCHIVE_WARN;
}
/* Write the data. */
- while (size > 0 && a->offset < a->filesize) {
- if ((off_t)(a->offset + size) > a->filesize) {
- size = (size_t)(a->filesize - a->offset);
- archive_set_error(&a->archive, errno,
- "Write request too large");
- r = ARCHIVE_WARN;
- }
+ while (size > 0) {
+ if (block_size != -1) {
+ const char *buf;
+
+ for (buf = buff; size; ++buf, --size, ++offset) {
+ if (*buf != '\0')
+ break;
+ }
+ if (size == 0)
+ break;
+ bytes_to_write = block_size - offset % block_size;
+ buff = buf;
+ } else
+ bytes_to_write = size;
+ /* Seek if necessary to the specified offset. */
+ if (offset != a->last_offset) {
+ if (lseek(a->fd, offset, SEEK_SET) < 0) {
+ archive_set_error(&a->archive, errno, "Seek failed");
+ return (ARCHIVE_FATAL);
+ }
+ }
bytes_written = write(a->fd, buff, size);
if (bytes_written < 0) {
archive_set_error(&a->archive, errno, "Write failed");
return (ARCHIVE_WARN);
}
+ buff = (const char *)buff + bytes_written;
size -= bytes_written;
- a->offset += bytes_written;
+ offset += bytes_written;
+ a->last_offset = a->offset = offset;
}
+ a->offset = offset;
return (r);
}
@@ -505,7 +556,6 @@ static ssize_t
_archive_write_data(struct archive *_a, const void *buff, size_t size)
{
struct archive_write_disk *a = (struct archive_write_disk *)_a;
- off_t offset;
int r;
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
@@ -513,11 +563,10 @@ _archive_write_data(struct archive *_a, const void *buff, size_t size)
if (a->fd < 0)
return (ARCHIVE_OK);
- offset = a->offset;
r = _archive_write_data_block(_a, buff, size, a->offset);
if (r < ARCHIVE_OK)
return (r);
- return (a->offset - offset);
+ return size;
}
static int
@@ -533,6 +582,34 @@ _archive_write_finish_entry(struct archive *_a)
return (ARCHIVE_OK);
archive_clear_error(&a->archive);
+ if (a->last_offset != a->filesize && a->fd >= 0) {
+ if (ftruncate(a->fd, a->filesize) == -1 &&
+ a->filesize == 0) {
+ archive_set_error(&a->archive, errno,
+ "File size could not be restored");
+ return (ARCHIVE_FAILED);
+ }
+ /*
+ * Explicitly stat the file as some platforms might not
+ * implement the XSI option to extend files via ftruncate.
+ */
+ a->pst = NULL;
+ if ((ret = _archive_write_disk_lazy_stat(a)) != ARCHIVE_OK)
+ return (ret);
+ if (a->st.st_size != a->filesize) {
+ const char nul = '\0';
+ if (lseek(a->fd, a->st.st_size - 1, SEEK_SET) < 0) {
+ archive_set_error(&a->archive, errno, "Seek failed");
+ return (ARCHIVE_FATAL);
+ }
+ if (write(a->fd, &nul, 1) < 0) {
+ archive_set_error(&a->archive, errno,
+ "Write to restore size failed");
+ return (ARCHIVE_FATAL);
+ }
+ }
+ }
+
/* Restore metadata. */
/*
@@ -723,11 +800,13 @@ restore_entry(struct archive_write_disk *a)
* object isn't a dir.
*/
if (unlink(a->name) == 0) {
- /* We removed it, we're done. */
+ /* We removed it, reset cached stat. */
+ a->pst = NULL;
} else if (errno == ENOENT) {
/* File didn't exist, that's just as good. */
} else if (rmdir(a->name) == 0) {
/* It was a dir, but now it's gone. */
+ a->pst = NULL;
} else {
/* We tried, but couldn't get rid of it. */
archive_set_error(&a->archive, errno,
@@ -768,6 +847,7 @@ restore_entry(struct archive_write_disk *a)
"Can't remove already-existing dir");
return (ARCHIVE_WARN);
}
+ a->pst = NULL;
/* Try again. */
en = create_filesystem_object(a);
} else if (en == EEXIST) {
@@ -807,6 +887,7 @@ restore_entry(struct archive_write_disk *a)
"Can't unlink already-existing object");
return (ARCHIVE_WARN);
}
+ a->pst = NULL;
/* Try again. */
en = create_filesystem_object(a);
} else if (!S_ISDIR(a->mode)) {
@@ -866,8 +947,18 @@ create_filesystem_object(struct archive_write_disk *a)
* New cpio and pax formats allow hardlink entries
* to carry data, so we may have to open the file
* for hardlink entries.
+ *
+ * If the hardlink was successfully created and
+ * the archive doesn't have carry data for it,
+ * consider it to be non-authoritive for meta data.
+ * This is consistent with GNU tar and BSD pax.
+ * If the hardlink does carry data, let the last
+ * archive entry decide ownership.
*/
- if (r == 0 && a->filesize > 0) {
+ if (r == 0 && a->filesize == 0) {
+ a->todo = 0;
+ a->deferred = 0;
+ } if (r == 0 && a->filesize > 0) {
a->fd = open(a->name, O_WRONLY | O_TRUNC | O_BINARY);
if (a->fd < 0)
r = errno;
@@ -1203,6 +1294,7 @@ check_symlinks(struct archive_write_disk *a)
pn[0] = c;
return (ARCHIVE_WARN);
}
+ a->pst = NULL;
/*
* Even if we did remove it, a warning
* is in order. The warning is silly,
@@ -1226,6 +1318,7 @@ check_symlinks(struct archive_write_disk *a)
pn[0] = c;
return (ARCHIVE_WARN);
}
+ a->pst = NULL;
} else {
archive_set_error(&a->archive, 0,
"Cannot extract through symlink %s",
@@ -1608,19 +1701,8 @@ set_mode(struct archive_write_disk *a, int mode)
* process, since systems sometimes set GID from
* the enclosing dir or based on ACLs.
*/
- if (a->pst != NULL) {
- /* Already have stat() data available. */
-#ifdef HAVE_FSTAT
- } else if (a->fd >= 0 && fstat(a->fd, &a->st) == 0) {
- a->pst = &a->st;
-#endif
- } else if (stat(a->name, &a->st) == 0) {
- a->pst = &a->st;
- } else {
- archive_set_error(&a->archive, errno,
- "Couldn't stat file");
- return (ARCHIVE_WARN);
- }
+ if ((r = _archive_write_disk_lazy_stat(a)) != ARCHIVE_OK)
+ return (r);
if (a->pst->st_gid != a->gid) {
mode &= ~ S_ISGID;
if (a->flags & ARCHIVE_EXTRACT_OWNER) {
@@ -1783,6 +1865,8 @@ static int
set_fflags_platform(struct archive_write_disk *a, int fd, const char *name,
mode_t mode, unsigned long set, unsigned long clear)
{
+ int r;
+
(void)mode; /* UNUSED */
if (set == 0 && clear == 0)
return (ARCHIVE_OK);
@@ -1793,15 +1877,8 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name,
* about the correct approach if we're overwriting an existing
* file that already has flags on it. XXX
*/
- if (fd >= 0 && fstat(fd, &a->st) == 0)
- a->pst = &a->st;
- else if (lstat(name, &a->st) == 0)
- a->pst = &a->st;
- else {
- archive_set_error(&a->archive, errno,
- "Couldn't stat file");
- return (ARCHIVE_WARN);
- }
+ if ((r = _archive_write_disk_lazy_stat(a)) != ARCHIVE_OK)
+ return (r);
a->st.st_flags &= ~clear;
a->st.st_flags |= set;