diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/libarchive/archive_write_disk.c | 124 | ||||
-rw-r--r-- | lib/libarchive/test/test_write_disk.c | 71 | ||||
-rw-r--r-- | lib/libarchive/test/test_write_disk_hardlink.c | 87 |
3 files changed, 223 insertions, 59 deletions
diff --git a/lib/libarchive/archive_write_disk.c b/lib/libarchive/archive_write_disk.c index 14a0529589a4..b7313bcfbd1b 100644 --- a/lib/libarchive/archive_write_disk.c +++ b/lib/libarchive/archive_write_disk.c @@ -176,7 +176,7 @@ struct archive_write_disk { int fd; /* Current offset for writing data to the file. */ off_t offset; - /* Maximum size of file. */ + /* Maximum size of file, -1 if unknown. */ off_t filesize; /* Dir we were in before this restore; only for deep paths. */ int restore_pwd; @@ -231,7 +231,8 @@ static int set_time(struct archive_write_disk *); static struct fixup_entry *sort_dir_list(struct fixup_entry *p); static gid_t trivial_lookup_gid(void *, const char *, gid_t); static uid_t trivial_lookup_uid(void *, const char *, uid_t); - +static ssize_t write_data_block(struct archive_write_disk *, + const char *, size_t, off_t); static struct archive_vtable *archive_write_disk_vtable(void); @@ -337,7 +338,10 @@ _archive_write_header(struct archive *_a, struct archive_entry *entry) a->offset = 0; a->uid = a->user_uid; a->mode = archive_entry_mode(a->entry); - a->filesize = archive_entry_size(a->entry); + if (archive_entry_size_is_set(a->entry)) + a->filesize = archive_entry_size(a->entry); + else + a->filesize = -1; archive_strcpy(&(a->_name_data), archive_entry_pathname(a->entry)); a->name = a->_name_data.s; archive_clear_error(&a->archive); @@ -496,89 +500,113 @@ archive_write_disk_set_skip_file(struct archive *_a, dev_t d, ino_t i) } static ssize_t -_archive_write_data_block(struct archive *_a, - const void *buff, size_t size, off_t offset) +write_data_block(struct archive_write_disk *a, + const char *buff, size_t size, off_t offset) { - 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; + ssize_t block_size = 0, bytes_to_write; + int r; - __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, - ARCHIVE_STATE_DATA, "archive_write_disk_block"); - if (a->fd < 0) { - archive_set_error(&a->archive, 0, "File not open"); + if (a->filesize == 0 || a->fd < 0) { + archive_set_error(&a->archive, 0, + "Attempt to write to an empty file"); return (ARCHIVE_WARN); } - archive_clear_error(&a->archive); 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; } + if (a->filesize >= 0 && (off_t)(offset + size) > a->filesize) + size = (size_t)(a->filesize - offset); + /* Write the data. */ while (size > 0) { - if (block_size != -1) { - const char *buf; + if (block_size == 0) { + bytes_to_write = size; + } else { + /* We're sparsifying the file. */ + const char *p, *end; + off_t block_end; - for (buf = buff; size; ++buf, --size, ++offset) { - if (*buf != '\0') + /* Skip leading zero bytes. */ + for (p = buff, end = buff + size; p < end; ++p) { + if (*p != '\0') break; } + offset += p - buff; + size -= p - buff; + buff = p; if (size == 0) break; - bytes_to_write = block_size - offset % block_size; - buff = buf; - } else + + /* Calculate next block boundary after offset. */ + block_end + = (offset / block_size) * block_size + block_size; + + /* If the adjusted write would cross block boundary, + * truncate it to the block boundary. */ bytes_to_write = size; + if (offset + bytes_to_write > block_end) + bytes_to_write = block_end - offset; + } + /* 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"); + archive_set_error(&a->archive, errno, + "Seek failed"); return (ARCHIVE_FATAL); } } - bytes_written = write(a->fd, buff, size); + bytes_written = write(a->fd, buff, bytes_to_write); if (bytes_written < 0) { archive_set_error(&a->archive, errno, "Write failed"); return (ARCHIVE_WARN); } - buff = (const char *)buff + bytes_written; + buff += bytes_written; size -= bytes_written; offset += bytes_written; a->archive.file_position += bytes_written; a->archive.raw_position += bytes_written; a->last_offset = a->offset = offset; } - a->offset = offset; - return (r); + return (bytes_written); +} + +static ssize_t +_archive_write_data_block(struct archive *_a, + const void *buff, size_t size, off_t offset) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + ssize_t r; + + __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_DATA, "archive_write_disk_block"); + + r = write_data_block(a, buff, size, offset); + + if (r < 0) + return (r); + if ((size_t)r < size) { + archive_set_error(&a->archive, 0, + "Write request too large"); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); } 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; - int r; __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, ARCHIVE_STATE_DATA, "archive_write_data"); - if (a->fd < 0) - return (ARCHIVE_OK); - r = _archive_write_data_block(_a, buff, size, a->offset); - if (r < ARCHIVE_OK) - return (r); - return size; + return (write_data_block(a, buff, size, a->offset)); } static int @@ -594,7 +622,15 @@ _archive_write_finish_entry(struct archive *_a) return (ARCHIVE_OK); archive_clear_error(&a->archive); - if (a->last_offset != a->filesize && a->fd >= 0) { + /* Pad or truncate file to the right size. */ + if (a->fd < 0) { + /* There's no file. */ + } else if (a->filesize < 0) { + /* File size is unknown, so we can't set the size. */ + } else if (a->last_offset == a->filesize) { + /* Last write ended at exactly the filesize; we're done. */ + /* Hopefully, this is the common case. */ + } else { if (ftruncate(a->fd, a->filesize) == -1 && a->filesize == 0) { archive_set_error(&a->archive, errno, @@ -611,7 +647,8 @@ _archive_write_finish_entry(struct archive *_a) 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"); + archive_set_error(&a->archive, errno, + "Seek failed"); return (ARCHIVE_FATAL); } if (write(a->fd, &nul, 1) < 0) { @@ -619,6 +656,7 @@ _archive_write_finish_entry(struct archive *_a) "Write to restore size failed"); return (ARCHIVE_FATAL); } + a->pst = NULL; } } @@ -973,7 +1011,7 @@ create_filesystem_object(struct archive_write_disk *a) * 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) { diff --git a/lib/libarchive/test/test_write_disk.c b/lib/libarchive/test/test_write_disk.c index 41eb1d88bd78..b91288bf1683 100644 --- a/lib/libarchive/test/test_write_disk.c +++ b/lib/libarchive/test/test_write_disk.c @@ -84,7 +84,7 @@ static void create_reg_file(struct archive_entry *ae, const char *msg) * the entry being a maximum size. */ archive_entry_set_size(ae, sizeof(data)); - archive_entry_set_mtime(ae, 123456789, 0); + archive_entry_set_mtime(ae, 123456789, 0); assertEqualIntA(ad, 0, archive_write_header(ad, ae)); assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data))); assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); @@ -152,6 +152,61 @@ static void create_reg_file2(struct archive_entry *ae, const char *msg) free(compare); free(data); } + +static void create_reg_file3(struct archive_entry *ae, const char *msg) +{ + static const char data[]="abcdefghijklmnopqrstuvwxyz"; + struct archive *ad; + struct stat st; + + /* Write the entry to disk. */ + assert((ad = archive_write_disk_new()) != NULL); + failure("%s", msg); + /* Set the size smaller than the data and verify the truncation. */ + archive_entry_set_size(ae, 5); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(5, archive_write_data(ad, data, sizeof(data))); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); +#if ARCHIVE_VERSION_NUMBER < 2000000 + archive_write_finish(ad); +#else + assertEqualInt(0, archive_write_finish(ad)); +#endif + /* Test the entry on disk. */ + assert(0 == stat(archive_entry_pathname(ae), &st)); + failure("st.st_mode=%o archive_entry_mode(ae)=%o", + st.st_mode, archive_entry_mode(ae)); + assertEqualInt(st.st_mode, (archive_entry_mode(ae) & ~UMASK)); + assertEqualInt(st.st_size, 5); +} + + +static void create_reg_file4(struct archive_entry *ae, const char *msg) +{ + static const char data[]="abcdefghijklmnopqrstuvwxyz"; + struct archive *ad; + struct stat st; + + /* Write the entry to disk. */ + assert((ad = archive_write_disk_new()) != NULL); + /* Leave the size unset. The data should not be truncated. */ + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(ARCHIVE_OK, + archive_write_data_block(ad, data, sizeof(data), 0)); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); +#if ARCHIVE_VERSION_NUMBER < 2000000 + archive_write_finish(ad); +#else + assertEqualInt(0, archive_write_finish(ad)); +#endif + /* Test the entry on disk. */ + assert(0 == stat(archive_entry_pathname(ae), &st)); + failure("st.st_mode=%o archive_entry_mode(ae)=%o", + st.st_mode, archive_entry_mode(ae)); + assertEqualInt(st.st_mode, (archive_entry_mode(ae) & ~UMASK)); + failure(msg); + assertEqualInt(st.st_size, sizeof(data)); +} #endif DEFINE_TEST(test_write_disk) @@ -178,6 +233,20 @@ DEFINE_TEST(test_write_disk) create_reg_file2(ae, "Test creating another regular file"); archive_entry_free(ae); + /* A regular file with a size restriction */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file3"); + archive_entry_set_mode(ae, S_IFREG | 0755); + create_reg_file3(ae, "Regular file with size restriction"); + archive_entry_free(ae); + + /* A regular file with an unspecified size */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file3"); + archive_entry_set_mode(ae, S_IFREG | 0755); + create_reg_file4(ae, "Regular file with unspecified size"); + archive_entry_free(ae); + /* A regular file over an existing file */ assert((ae = archive_entry_new()) != NULL); archive_entry_copy_pathname(ae, "file"); diff --git a/lib/libarchive/test/test_write_disk_hardlink.c b/lib/libarchive/test/test_write_disk_hardlink.c index f8197c8f8d0b..6e38d86b5bc6 100644 --- a/lib/libarchive/test/test_write_disk_hardlink.c +++ b/lib/libarchive/test/test_write_disk_hardlink.c @@ -61,18 +61,48 @@ DEFINE_TEST(test_write_disk_hardlink) archive_entry_set_mode(ae, S_IFREG | 0755); archive_entry_set_size(ae, sizeof(data)); assertEqualIntA(ad, 0, archive_write_header(ad, ae)); - assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data))); + assertEqualInt(sizeof(data), + archive_write_data(ad, data, sizeof(data))); assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); archive_entry_free(ae); - /* Link. */ + /* Link. Size of zero means this doesn't carry data. */ assert((ae = archive_entry_new()) != NULL); archive_entry_copy_pathname(ae, "link1b"); - archive_entry_set_mode(ae, S_IFREG | 0600); + archive_entry_set_mode(ae, S_IFREG | 0642); archive_entry_set_size(ae, 0); archive_entry_copy_hardlink(ae, "link1a"); assertEqualIntA(ad, 0, archive_write_header(ad, ae)); - assertEqualInt(0, archive_write_data(ad, data, sizeof(data))); + assertEqualInt(ARCHIVE_WARN, + archive_write_data(ad, data, sizeof(data))); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); + archive_entry_free(ae); + + /* + * Repeat tar approach test, but use unset to mark the + * hardlink as having no data. + */ + + /* Regular file. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "link2a"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, sizeof(data)); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(sizeof(data), + archive_write_data(ad, data, sizeof(data))); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); + archive_entry_free(ae); + + /* Link. Unset size means this doesn't carry data. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "link2b"); + archive_entry_set_mode(ae, S_IFREG | 0642); + archive_entry_unset_size(ae); + archive_entry_copy_hardlink(ae, "link2a"); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(ARCHIVE_WARN, + archive_write_data(ad, data, sizeof(data))); assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); archive_entry_free(ae); @@ -83,7 +113,7 @@ DEFINE_TEST(test_write_disk_hardlink) /* Regular file. */ assert((ae = archive_entry_new()) != NULL); - archive_entry_copy_pathname(ae, "link2a"); + archive_entry_copy_pathname(ae, "link3a"); archive_entry_set_mode(ae, S_IFREG | 0600); archive_entry_set_size(ae, sizeof(data)); assertEqualIntA(ad, 0, archive_write_header(ad, ae)); @@ -93,10 +123,10 @@ DEFINE_TEST(test_write_disk_hardlink) /* Link. */ assert((ae = archive_entry_new()) != NULL); - archive_entry_copy_pathname(ae, "link2b"); + archive_entry_copy_pathname(ae, "link3b"); archive_entry_set_mode(ae, S_IFREG | 0755); archive_entry_set_size(ae, sizeof(data)); - archive_entry_copy_hardlink(ae, "link2a"); + archive_entry_copy_hardlink(ae, "link3a"); assertEqualIntA(ad, 0, archive_write_header(ad, ae)); assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data))); assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); @@ -109,7 +139,7 @@ DEFINE_TEST(test_write_disk_hardlink) /* Regular file. */ assert((ae = archive_entry_new()) != NULL); - archive_entry_copy_pathname(ae, "link3a"); + archive_entry_copy_pathname(ae, "link4a"); archive_entry_set_mode(ae, S_IFREG | 0600); archive_entry_set_size(ae, 0); assertEqualIntA(ad, 0, archive_write_header(ad, ae)); @@ -123,10 +153,10 @@ DEFINE_TEST(test_write_disk_hardlink) /* Link. */ assert((ae = archive_entry_new()) != NULL); - archive_entry_copy_pathname(ae, "link3b"); + archive_entry_copy_pathname(ae, "link4b"); archive_entry_set_mode(ae, S_IFREG | 0755); archive_entry_set_size(ae, sizeof(data)); - archive_entry_copy_hardlink(ae, "link3a"); + archive_entry_copy_hardlink(ae, "link4a"); assertEqualIntA(ad, 0, archive_write_header(ad, ae)); assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data))); assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); @@ -138,36 +168,63 @@ DEFINE_TEST(test_write_disk_hardlink) #endif /* Test the entries on disk. */ + + /* Test #1 */ assert(0 == stat("link1a", &st)); + /* If the hardlink was successfully created and the archive + * doesn't carry data for it, we consider it to be + * non-authoritive for meta data as well. This is consistent + * with GNU tar and BSD pax. */ assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK); assertEqualInt(st.st_size, sizeof(data)); assertEqualInt(st.st_nlink, 2); assert(0 == stat("link1b", &st2)); - assertEqualInt(st2.st_mode, (S_IFREG | 0755) & ~UMASK); - assertEqualInt(st2.st_size, sizeof(data)); - assertEqualInt(st2.st_nlink, 2); + assertEqualInt(st.st_mode, st2.st_mode); + assertEqualInt(st.st_size, st2.st_size); + assertEqualInt(st.st_nlink, st2.st_nlink); assertEqualInt(st.st_ino, st2.st_ino); assertEqualInt(st.st_dev, st2.st_dev); + /* Test #2: Should produce identical results to test #1 */ + /* Note that marking a hardlink with size = 0 is treated the + * same as having an unset size. This is partly for backwards + * compatibility (we used to not have unset tracking, so + * relied on size == 0) and partly to match the model used by + * common file formats that store a size of zero for + * hardlinks. */ assert(0 == stat("link2a", &st)); assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK); assertEqualInt(st.st_size, sizeof(data)); assertEqualInt(st.st_nlink, 2); assert(0 == stat("link2b", &st2)); + assertEqualInt(st.st_mode, st2.st_mode); + assertEqualInt(st.st_size, st2.st_size); + assertEqualInt(st.st_nlink, st2.st_nlink); + assertEqualInt(st.st_ino, st2.st_ino); + assertEqualInt(st.st_dev, st2.st_dev); + + /* Test #3 */ + assert(0 == stat("link3a", &st)); + assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK); + assertEqualInt(st.st_size, sizeof(data)); + assertEqualInt(st.st_nlink, 2); + + assert(0 == stat("link3b", &st2)); assertEqualInt(st2.st_mode, (S_IFREG | 0755) & ~UMASK); assertEqualInt(st2.st_size, sizeof(data)); assertEqualInt(st2.st_nlink, 2); assertEqualInt(st.st_ino, st2.st_ino); assertEqualInt(st.st_dev, st2.st_dev); - assert(0 == stat("link3a", &st)); + /* Test #4 */ + assert(0 == stat("link4a", &st)); assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK); assertEqualInt(st.st_size, sizeof(data)); assertEqualInt(st.st_nlink, 2); - assert(0 == stat("link3b", &st2)); + assert(0 == stat("link4b", &st2)); assertEqualInt(st2.st_mode, (S_IFREG | 0755) & ~UMASK); assertEqualInt(st2.st_size, sizeof(data)); assertEqualInt(st2.st_nlink, 2); |