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,
__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 *,
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
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)
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;
}
/* 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);
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;
_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;
}
/*
filter->next += min;
filter->avail -= min;
request -= min;
- filter->bytes_consumed += min;
+ filter->position += min;
total_bytes_skipped += min;
}
filter->client_next += min;
filter->client_avail -= min;
request -= min;
- filter->bytes_consumed += min;
+ filter->position += min;
total_bytes_skipped += min;
}
if (request == 0)
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)
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;
+}
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);
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);
/* 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);
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)
{
* 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. */
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. */
archive_open_callback *opener;
archive_read_callback *reader;
archive_skip_callback *skipper;
+ archive_seek_callback *seeker;
archive_close_callback *closer;
void *data;
};
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 *);