]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Issue 94: When writing into an fd that's not seekable (e.g., a pipe),
authorTim Kientzle <kientzle@gmail.com>
Wed, 10 Nov 2010 07:05:47 +0000 (02:05 -0500)
committerTim Kientzle <kientzle@gmail.com>
Wed, 10 Nov 2010 07:05:47 +0000 (02:05 -0500)
we have to fill in zero holes for sparse files.

SVN-Revision: 2783

libarchive/archive_read_data_into_fd.c

index cfb63220cabc3ef1189a477b77285097b919875b..664a632b13d2300eb47d25777ae6cc292028fc92 100644 (file)
@@ -45,36 +45,72 @@ __FBSDID("$FreeBSD: src/lib/libarchive/archive_read_data_into_fd.c,v 1.16 2008/0
 /*
  * This implementation minimizes copying of data and is sparse-file aware.
  */
+int
+pad_to(struct archive *a, int fd, int can_lseek,
+    size_t nulls_size, const char *nulls,
+    int64_t target_offset, int64_t actual_offset)
+{
+       size_t to_write;
+       ssize_t bytes_written;
+
+       if (can_lseek) {
+               actual_offset = lseek(fd,
+                   target_offset - actual_offset, SEEK_CUR);
+               if (actual_offset != target_offset) {
+                       archive_set_error(a, errno, "Seek error");
+                       return (ARCHIVE_FATAL);
+               }
+               return (ARCHIVE_OK);
+       }
+       while (target_offset > actual_offset) {
+               to_write = nulls_size;
+               if (target_offset < actual_offset + nulls_size)
+                       to_write = (size_t)(target_offset - actual_offset);
+               bytes_written = write(fd, nulls, to_write);
+               if (bytes_written < 0) {
+                       archive_set_error(a, errno, "Write error");
+                       return (ARCHIVE_FATAL);
+               }
+               actual_offset += bytes_written;
+       }
+       return (ARCHIVE_OK);
+}
+
+
 int
 archive_read_data_into_fd(struct archive *a, int fd)
 {
-       int r;
+       struct stat st;
+       int r, r2;
        const void *buff;
        size_t size, bytes_to_write;
-       ssize_t bytes_written, total_written;
+       ssize_t bytes_written;
 #if ARCHIVE_VERSION_NUMBER < 3000000
-       off_t offset;
+       off_t target_offset;
 #else
-       int64_t offset;
+       int64_t target_offset;
 #endif
-       int64_t output_offset;
+       int64_t actual_offset = 0;
+       int can_lseek;
+       char *nulls = NULL;
+       size_t nulls_size = 16384;
 
        archive_check_magic(a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_DATA,
            "archive_read_data_into_fd");
 
-       total_written = 0;
-       output_offset = 0;
+       can_lseek = (fstat(fd, &st) == 0) && S_ISREG(st.st_mode);
+       if (!can_lseek)
+               nulls = calloc(1, nulls_size);
 
-       while ((r = archive_read_data_block(a, &buff, &size, &offset)) ==
+       while ((r = archive_read_data_block(a, &buff, &size, &target_offset)) ==
            ARCHIVE_OK) {
                const char *p = buff;
-               if (offset > output_offset) {
-                       output_offset = lseek(fd,
-                           offset - output_offset, SEEK_CUR);
-                       if (output_offset != offset) {
-                               archive_set_error(a, errno, "Seek error");
-                               return (ARCHIVE_FATAL);
-                       }
+               if (target_offset > actual_offset) {
+                       r = pad_to(a, fd, can_lseek, nulls_size, nulls,
+                           target_offset, actual_offset);
+                       if (r != ARCHIVE_OK)
+                               break;
+                       actual_offset = target_offset;
                }
                while (size > 0) {
                        bytes_to_write = size;
@@ -83,15 +119,23 @@ archive_read_data_into_fd(struct archive *a, int fd)
                        bytes_written = write(fd, p, bytes_to_write);
                        if (bytes_written < 0) {
                                archive_set_error(a, errno, "Write error");
-                               return (ARCHIVE_FATAL);
+                               r = ARCHIVE_FATAL;
+                               break;
                        }
-                       output_offset += bytes_written;
-                       total_written += bytes_written;
+                       actual_offset += bytes_written;
                        p += bytes_written;
                        size -= bytes_written;
                }
        }
 
+       if (r == ARCHIVE_EOF && target_offset > actual_offset) {
+               r2 = pad_to(a, fd, can_lseek, nulls_size, nulls,
+                   target_offset, actual_offset);
+               if (r2 != ARCHIVE_OK)
+                       r = r2;
+       }
+
+       free(nulls);
        if (r != ARCHIVE_EOF)
                return (r);
        return (ARCHIVE_OK);