]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Support seeking in files.
authorTim Kientzle <kientzle@gmail.com>
Sat, 19 Nov 2011 03:05:56 +0000 (22:05 -0500)
committerTim Kientzle <kientzle@gmail.com>
Sat, 19 Nov 2011 03:05:56 +0000 (22:05 -0500)
Noone actually uses this yet, but I have some ideas for using
this to fix the Zip and ISO reader.

SVN-Revision: 3808

libarchive/archive.h
libarchive/archive_read.c
libarchive/archive_read_open_filename.c
libarchive/archive_read_private.h

index 00873770742988b7f496dad5c2e91b6a5abdadba..4858a626608779ef93dbcc6163272541a6971248 100644 (file)
@@ -190,10 +190,21 @@ struct archive_entry;
 typedef __LA_SSIZE_T   archive_read_callback(struct archive *,
                            void *_client_data, const void **_buffer);
 
-/* Skips at most request bytes from archive and returns the skipped amount */
+/* Skips at most request bytes from archive and returns the skipped amount.
+ * This may skip fewer bytes than requested; it may even skip zero bytes.
+ * If you do skip fewer bytes than requested, libarchive will invoke your
+ * read callback and discard data as necessary to make up the full skip.
+ */
 typedef __LA_INT64_T   archive_skip_callback(struct archive *,
                            void *_client_data, __LA_INT64_T request);
 
+/* Seeks to specified location in the file and returns the position.
+ * Whence values are SEEK_SET, SEEK_CUR, SEEK_END from stdio.h.
+ * Return ARCHIVE_FATAL if the seek fails for any reason.
+ */
+typedef __LA_INT64_T   archive_seek_callback(struct archive *,
+    void *_client_data, __LA_INT64_T offset, int whence);
+
 /* Returns size actually written, zero on EOF, -1 on error. */
 typedef __LA_SSIZE_T   archive_write_callback(struct archive *,
                            void *_client_data,
@@ -343,17 +354,19 @@ __LA_DECL int archive_read_support_format_gnutar(struct archive *);
 __LA_DECL int archive_read_support_format_iso9660(struct archive *);
 __LA_DECL int archive_read_support_format_lha(struct archive *);
 __LA_DECL int archive_read_support_format_mtree(struct archive *);
+__LA_DECL int archive_read_support_format_rar(struct archive *);
 __LA_DECL int archive_read_support_format_raw(struct archive *);
 __LA_DECL int archive_read_support_format_tar(struct archive *);
 __LA_DECL int archive_read_support_format_xar(struct archive *);
 __LA_DECL int archive_read_support_format_zip(struct archive *);
-__LA_DECL int archive_read_support_format_rar(struct archive *);
 
 /* Set various callbacks. */
 __LA_DECL int archive_read_set_open_callback(struct archive *,
     archive_open_callback *);
 __LA_DECL int archive_read_set_read_callback(struct archive *,
     archive_read_callback *);
+__LA_DECL int archive_read_set_seek_callback(struct archive *,
+    archive_seek_callback *);
 __LA_DECL int archive_read_set_skip_callback(struct archive *,
     archive_skip_callback *);
 __LA_DECL int archive_read_set_close_callback(struct archive *,
index 84e11e92996b2083d521a4c12086c66e56dbb308..b6474f864c18fb4623b22d2a4111323de46489c7 100644 (file)
@@ -179,26 +179,64 @@ client_read_proxy(struct archive_read_filter *self, const void **buff)
 static int64_t
 client_skip_proxy(struct archive_read_filter *self, int64_t request)
 {
-       int64_t ask, get, total;
-       /* Seek requests over 1GiB are broken down into multiple
-        * seeks.  This avoids overflows when the requests get
-        * passed through 32-bit arguments. */
-       int64_t skip_limit = (int64_t)1 << 30;
-
-       if (self->archive->client.skipper == NULL)
-               return (0);
-       total = 0;
-       for (;;) {
-               ask = request;
-               if (ask > skip_limit)
-                       ask = skip_limit;
-               get = (self->archive->client.skipper)(&self->archive->archive,
-                       self->data, ask);
-               if (get == 0)
-                       return (total);
-               request -= get;
-               total += get;
+       if (request < 0)
+               __archive_errx(1, "Negative skip requested.");
+       if (request == 0)
+               return 0;
+
+       if (self->archive->client.skipper != NULL) {
+               /* Seek requests over 1GiB are broken down into
+                * multiple seeks.  This avoids overflows when the
+                * requests get passed through 32-bit arguments. */
+               int64_t skip_limit = (int64_t)1 << 30;
+               int64_t total = 0;
+               for (;;) {
+                       int64_t get, ask = request;
+                       if (ask > skip_limit)
+                               ask = skip_limit;
+                       get = (self->archive->client.skipper)(&self->archive->archive,
+                           self->data, ask);
+                       if (get == 0)
+                               return (total);
+                       request -= get;
+                       total += get;
+               }
+               return total;
+       } else if (self->archive->client.seeker != NULL
+               && request > 64 * 1024) {
+               /* If the client provided a seeker but not a skipper,
+                * we can use the seeker to skip forward.
+                *
+                * Note: This isn't always a good idea.  The client
+                * skipper is allowed to skip by less than requested
+                * if it needs to maintain block alignment.  The
+                * seeker is not allowed to play such games, so using
+                * the seeker here may be a performance loss compared
+                * to just reading and discarding.  That's why we
+                * only do this for skips of over 64k.
+                */
+               int64_t before = self->position;
+               int64_t after = (self->archive->client.seeker)(&self->archive->archive,
+                   self->data, request, SEEK_CUR);
+               if (after != before + request)
+                       return ARCHIVE_FATAL;
+               return after - before;
        }
+       return 0;
+}
+
+static int64_t
+client_seek_proxy(struct archive_read_filter *self, int64_t offset, int whence)
+{
+       /* DO NOT use the skipper here!  If we transparently handled
+        * forward seek here by using the skipper, that will break
+        * other libarchive code that assumes a successful forward
+        * seek means it can also seek backwards.
+        */
+       if (self->archive->client.seeker == NULL)
+               return (ARCHIVE_FAILED);
+       return (self->archive->client.seeker)(&self->archive->archive,
+           self->data, offset, whence);
 }
 
 static int
@@ -245,6 +283,17 @@ archive_read_set_skip_callback(struct archive *_a,
        return ARCHIVE_OK;
 }
 
+int
+archive_read_set_seek_callback(struct archive *_a,
+    archive_seek_callback *client_seeker)
+{
+       struct archive_read *a = (struct archive_read *)_a;
+       archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW,
+           "archive_read_set_seek_callback");
+       a->client.seeker = client_seeker;
+       return ARCHIVE_OK;
+}
+
 int
 archive_read_set_close_callback(struct archive *_a,
     archive_close_callback *client_closer)
@@ -304,6 +353,7 @@ archive_read_open1(struct archive *_a)
        filter->data = a->client.data;
        filter->read = client_read_proxy;
        filter->skip = client_skip_proxy;
+       filter->seek = client_seek_proxy;
        filter->close = client_close_proxy;
        filter->name = "none";
        filter->code = ARCHIVE_COMPRESSION_NONE;
@@ -423,7 +473,7 @@ _archive_read_next_header2(struct archive *_a, struct archive_entry *entry)
        }
 
        /* Record start-of-header offset in uncompressed stream. */
-       a->header_position = a->filter->bytes_consumed;
+       a->header_position = a->filter->position;
 
        ++_a->file_count;
        ret = (a->format->read_header)(a, entry);
@@ -490,6 +540,8 @@ choose_format(struct archive_read *a)
                        bid = (a->format->bid)(a);
                        if (bid == ARCHIVE_FATAL)
                                return (ARCHIVE_FATAL);
+                       if (a->filter->position != 0)
+                               __archive_read_seek(a, 0, SEEK_SET);
                        if ((bid > best_bid) || (best_bid_slot < 0)) {
                                best_bid = bid;
                                best_bid_slot = i;
@@ -857,7 +909,7 @@ static int64_t
 _archive_filter_bytes(struct archive *_a, int n)
 {
        struct archive_read_filter *f = get_filter(_a, n);
-       return f == NULL ? -1 : f->bytes_consumed;
+       return f == NULL ? -1 : f->position;
 }
 
 /*
@@ -1207,7 +1259,7 @@ advance_file_pointer(struct archive_read_filter *filter, int64_t request)
                filter->next += min;
                filter->avail -= min;
                request -= min;
-               filter->bytes_consumed += min;
+               filter->position += min;
                total_bytes_skipped += min;
        }
 
@@ -1217,7 +1269,7 @@ advance_file_pointer(struct archive_read_filter *filter, int64_t request)
                filter->client_next += min;
                filter->client_avail -= min;
                request -= min;
-               filter->bytes_consumed += min;
+               filter->position += min;
                total_bytes_skipped += min;
        }
        if (request == 0)
@@ -1230,7 +1282,7 @@ advance_file_pointer(struct archive_read_filter *filter, int64_t request)
                        filter->fatal = 1;
                        return (bytes_skipped);
                }
-               filter->bytes_consumed += bytes_skipped;
+               filter->position += bytes_skipped;
                total_bytes_skipped += bytes_skipped;
                request -= bytes_skipped;
                if (request == 0)
@@ -1258,12 +1310,40 @@ advance_file_pointer(struct archive_read_filter *filter, int64_t request)
                        filter->client_avail = bytes_read - request;
                        filter->client_total = bytes_read;
                        total_bytes_skipped += request;
-                       filter->bytes_consumed += request;
+                       filter->position += request;
                        return (total_bytes_skipped);
                }
 
-               filter->bytes_consumed += bytes_read;
+               filter->position += bytes_read;
                total_bytes_skipped += bytes_read;
                request -= bytes_read;
        }
 }
+
+/**
+ * Returns ARCHIVE_FAILED if seeking isn't supported.
+ */
+int64_t
+__archive_read_seek(struct archive_read *a, int64_t offset, int whence)
+{
+       return __archive_read_filter_seek(a->filter, offset, whence);
+}
+
+int64_t
+__archive_read_filter_seek(struct archive_read_filter *filter, int64_t offset, int whence)
+{
+       int64_t r;
+
+       if (filter->closed || filter->fatal)
+               return (ARCHIVE_FATAL);
+       if (filter->seek == NULL)
+               return (ARCHIVE_FAILED);
+       r = filter->seek(filter, offset, whence);
+       if (r >= 0) {
+               filter->avail = filter->client_avail = 0;
+               filter->next = filter->buffer;
+               filter->position = r;
+               filter->end_of_file = 0;
+       }
+       return r;
+}
index 87e5ae7e9ec5918fea43011a5115468be9fe47a5..c06d1a17d0dd71ea6c7ab4c99950e6aae3b64b0a 100644 (file)
@@ -76,6 +76,7 @@ struct read_file_data {
 
 static int     file_close(struct archive *, void *);
 static ssize_t file_read(struct archive *, void *, const void **buff);
+static int64_t file_seek(struct archive *, void *, int64_t request, int);
 static int64_t file_skip(struct archive *, void *, int64_t request);
 static int64_t file_skip_lseek(struct archive *, void *, int64_t request);
 
@@ -214,8 +215,10 @@ archive_read_open_filename(struct archive *a, const char *filename,
        mine->st_mode = st.st_mode;
 
        /* Disk-like inputs can use lseek(). */
-       if (is_disk_like)
+       if (is_disk_like) {
+               archive_read_set_seek_callback(a, file_seek);
                mine->use_lseek = 1;
+       }
 
        archive_read_set_read_callback(a, file_read);
        archive_read_set_skip_callback(a, file_skip);
@@ -292,7 +295,13 @@ file_skip_lseek(struct archive *a, void *client_data, int64_t request)
 
        /* We use off_t here because lseek() is declared that way. */
 
-       /* TODO: Deal with case where off_t isn't 64 bits. */
+       /* TODO: Deal with case where off_t isn't 64 bits.
+        * This shouldn't be a problem on Linux or other POSIX
+        * systems, since the configuration logic for libarchive
+        * tries to obtain a 64-bit off_t.  It's still an issue
+        * on Windows, though, so it might suffice to just use
+        * _lseeki64() on Windows.
+        */
        if ((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0 &&
            (new_offset = lseek(mine->fd, request, SEEK_CUR)) >= 0)
                return (new_offset - old_offset);
@@ -332,6 +341,27 @@ file_skip(struct archive *a, void *client_data, int64_t request)
        return (0);
 }
 
+static int64_t
+file_seek(struct archive *a, void *client_data, int64_t request, int whence)
+{
+       struct read_file_data *mine = (struct read_file_data *)client_data;
+       off_t r;
+
+       /* We use off_t here because lseek() is declared that way. */
+       /* See above for notes about when off_t is less than 64 bits. */
+       r = lseek(mine->fd, request, whence);
+       if (r >= 0)
+               return r;
+
+       /* If the input is corrupted or truncated, fail. */
+       if (mine->filename[0] == '\0')
+               archive_set_error(a, errno, "Error seeking in stdin");
+       else
+               archive_set_error(a, errno, "Error seeking in '%s'",
+                   mine->filename);
+       return (ARCHIVE_FATAL);
+}
+
 static int
 file_close(struct archive *a, void *client_data)
 {
index 4e09a01c7a39bb716995a8c92ea08f9b2f61b59c..5fbb07d3215e53725d35e4e63856e8b6343f8d75 100644 (file)
@@ -76,7 +76,7 @@ struct archive_read_filter_bidder {
  * corresponding bidder above.
  */
 struct archive_read_filter {
-       int64_t bytes_consumed;
+       int64_t position;
        /* Essentially all filters will need these values, so
         * just declare them here. */
        struct archive_read_filter_bidder *bidder; /* My bidder. */
@@ -86,6 +86,8 @@ struct archive_read_filter {
        ssize_t (*read)(struct archive_read_filter *, const void **);
        /* Skip forward this many bytes. */
        int64_t (*skip)(struct archive_read_filter *self, int64_t request);
+       /* Seek to an absolute location. */
+       int64_t (*seek)(struct archive_read_filter *self, int64_t offset, int whence);
        /* Close (just this filter) and free(self). */
        int (*close)(struct archive_read_filter *self);
        /* My private data. */
@@ -120,6 +122,7 @@ struct archive_read_client {
        archive_open_callback   *opener;
        archive_read_callback   *reader;
        archive_skip_callback   *skipper;
+       archive_seek_callback   *seeker;
        archive_close_callback  *closer;
        void *data;
 };
@@ -199,6 +202,8 @@ int __archive_read_get_bidder(struct archive_read *a,
 const void *__archive_read_ahead(struct archive_read *, size_t, ssize_t *);
 const void *__archive_read_filter_ahead(struct archive_read_filter *,
     size_t, ssize_t *);
+int64_t        __archive_read_seek(struct archive_read*, int64_t, int);
+int64_t        __archive_read_filter_seek(struct archive_read_filter *, int64_t, int);
 int64_t        __archive_read_consume(struct archive_read *, int64_t);
 int64_t        __archive_read_filter_consume(struct archive_read_filter *, int64_t);
 int __archive_read_program(struct archive_read_filter *, const char *);