From: Tim Kientzle Date: Sat, 26 Jul 2008 21:20:32 +0000 (-0400) Subject: If the size is unknown, don't limit data writes. X-Git-Tag: v2.6.0~124 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5b5d1d58f9606ce7bb9ed9f56244c0f223b6baeb;p=thirdparty%2Flibarchive.git If the size is unknown, don't limit data writes. This seems to straighten out some of the disk-write semantics and should solve a problem with Zip extraction when the size of the file is not known in advance. SVN-Revision: 168 --- diff --git a/libarchive/archive_write_disk.c b/libarchive/archive_write_disk.c index 3355b129a..bc5f3e052 100644 --- a/libarchive/archive_write_disk.c +++ b/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); @@ -489,87 +493,111 @@ 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->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 @@ -585,32 +613,32 @@ _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) { + /* 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 { + /* + * The write handlers truncate long writes, so we + * never have to shorten a file here. Some systems + * can lengthen files with ftruncate(), but this is + * more portable: + */ + const char nul = '\0'; + if (lseek(a->fd, a->filesize - 1, SEEK_SET) < 0) { + archive_set_error(&a->archive, errno, "Seek failed"); + return (ARCHIVE_FAILED); + } + if (write(a->fd, &nul, 1) < 0) { archive_set_error(&a->archive, errno, - "File size could not be restored"); + "Write to restore size failed"); 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. */ diff --git a/libarchive/test/test_write_disk.c b/libarchive/test/test_write_disk.c index 2d2b8ffea..42ded9189 100644 --- a/libarchive/test/test_write_disk.c +++ b/libarchive/test/test_write_disk.c @@ -25,8 +25,6 @@ #include "test.h" __FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_disk.c,v 1.9 2008/06/15 10:35:22 kientzle Exp $"); -#if ARCHIVE_VERSION_STAMP >= 1009000 - #define UMASK 022 static void create(struct archive_entry *ae, const char *msg) @@ -39,11 +37,7 @@ static void create(struct archive_entry *ae, const char *msg) failure("%s", msg); assertEqualIntA(ad, 0, archive_write_header(ad, ae)); assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); -#if ARCHIVE_API_VERSION > 1 assertEqualInt(0, archive_write_finish(ad)); -#else - archive_write_finish(ad); -#endif /* Test the entries on disk. */ assert(0 == stat(archive_entry_pathname(ae), &st)); failure("st.st_mode=%o archive_entry_mode(ae)=%o", @@ -126,11 +120,7 @@ static void create_reg_file2(struct archive_entry *ae, const char *msg) archive_write_data_block(ad, data + i, 1000, i)); } assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); -#if ARCHIVE_API_VERSION > 1 assertEqualInt(0, archive_write_finish(ad)); -#else - archive_write_finish(ad); -#endif /* Test the entries on disk. */ assert(0 == stat(archive_entry_pathname(ae), &st)); failure("st.st_mode=%o archive_entry_mode(ae)=%o", @@ -146,13 +136,57 @@ static void create_reg_file2(struct archive_entry *ae, const char *msg) free(compare); free(data); } -#endif + +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)); + assertEqualInt(0, archive_write_finish(ad)); + /* 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)); + assertEqualInt(0, archive_write_finish(ad)); + /* 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)); +} + DEFINE_TEST(test_write_disk) { -#if ARCHIVE_VERSION_STAMP < 1009000 - skipping("archive_write_disk interface"); -#else struct archive_entry *ae; /* Force the umask to something predictable. */ @@ -172,6 +206,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"); @@ -199,5 +247,4 @@ DEFINE_TEST(test_write_disk) archive_entry_set_mode(ae, S_IFREG | 0744); create(ae, "Test creating a file over an existing dir."); archive_entry_free(ae); -#endif } diff --git a/libarchive/test/test_write_disk_hardlink.c b/libarchive/test/test_write_disk_hardlink.c index dda384a85..098c2851e 100644 --- a/libarchive/test/test_write_disk_hardlink.c +++ b/libarchive/test/test_write_disk_hardlink.c @@ -72,7 +72,8 @@ DEFINE_TEST(test_write_disk_hardlink) 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);