]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
If the size is unknown, don't limit data writes.
authorTim Kientzle <kientzle@gmail.com>
Sat, 26 Jul 2008 21:20:32 +0000 (17:20 -0400)
committerTim Kientzle <kientzle@gmail.com>
Sat, 26 Jul 2008 21:20:32 +0000 (17:20 -0400)
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

libarchive/archive_write_disk.c
libarchive/test/test_write_disk.c
libarchive/test/test_write_disk_hardlink.c

index 3355b129a55baa1d570e6c8e41b2afecc98de887..bc5f3e0521541a1364c8cb956bffc8dbb907460b 100644 (file)
@@ -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. */
index 2d2b8ffeaf38f6ddb9269dfb03f08fa494b3199f..42ded91893fa7c6c3f8803d8383188d4968b9857 100644 (file)
@@ -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
 }
index dda384a85a162c438553f06f413bd64187900ec4..098c2851e27f75753b71935a9ff5bbd2e0b3f3f9 100644 (file)
@@ -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);