From c2a8c82a44e2ef60255f5b9d21482c3cc26a60e5 Mon Sep 17 00:00:00 2001
From: Andres Mejia
Date: Fri, 25 Jan 2013 20:57:48 -0500
Subject: [PATCH] Fix broken RAR seek support. Using SEEK_CUR or SEEK_END
produced the wrong results, SEEK_SET was fine however. Note that
archive_read_data() doesn't function exactly as POSIX read. Currently,
downstreams will have to check the current file position after after call to
archive_read_data() if they are to use SEEK_CUR afterwards.
---
libarchive/archive_read_support_format_rar.c | 61 +++++++++++++---
libarchive/test/test_read_format_rar.c | 73 +++++++++++++++++---
2 files changed, 114 insertions(+), 20 deletions(-)
diff --git a/libarchive/archive_read_support_format_rar.c b/libarchive/archive_read_support_format_rar.c
index 7492a50ba..1bd95a395 100644
--- a/libarchive/archive_read_support_format_rar.c
+++ b/libarchive/archive_read_support_format_rar.c
@@ -201,6 +201,7 @@ struct lzss
struct data_block_offsets
{
+ int64_t header_size;
int64_t start_offset;
int64_t end_offset;
};
@@ -241,6 +242,7 @@ struct rar
int64_t bytes_uncopied;
int64_t offset;
int64_t offset_outgoing;
+ int64_t offset_seek;
char valid;
unsigned int unp_offset;
unsigned int unp_buffer_size;
@@ -1025,25 +1027,49 @@ archive_read_format_rar_seek_data(struct archive_read *a, int64_t offset,
switch (whence)
{
case SEEK_SET:
+ client_offset = 0;
break;
-
case SEEK_CUR:
- offset += rar->offset;
+ client_offset = rar->offset_seek;
break;
-
case SEEK_END:
- offset += rar->unp_size - 1;
- break;
+ client_offset = rar->unp_size;
+ }
+ client_offset += offset;
+ if (client_offset < 0)
+ {
+ /* Can't seek past beginning of data block */
+ return -1;
+ }
+ else if (client_offset > rar->unp_size)
+ {
+ /*
+ * Set the returned offset but only seek to the end of
+ * the data block.
+ */
+ rar->offset_seek = client_offset;
+ client_offset = rar->unp_size;
}
- if (offset < 0)
- offset = 0;
- else if (offset > rar->unp_size - 1)
- offset = rar->unp_size - 1;
/* Find the appropriate offset in the client */
i = 0;
- client_offset = offset;
client_offset += rar->dbo[i].start_offset;
+ if (rar->main_flags & MHD_VOLUME &&
+ client_offset < rar->dbo[rar->cursor].start_offset)
+ {
+ /* The header of the first multivolume file must be re-read */
+ ret = __archive_read_seek(a,
+ rar->dbo[i].start_offset - rar->dbo[i].header_size, SEEK_SET);
+ if (ret < (ARCHIVE_OK))
+ return ret;
+ ret = archive_read_format_rar_read_header(a, a->entry);
+ if (ret != (ARCHIVE_OK))
+ {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Error during seek of RAR file");
+ return (ARCHIVE_FAILED);
+ }
+ }
while (rar->main_flags & MHD_VOLUME &&
client_offset > rar->dbo[i].end_offset)
{
@@ -1088,7 +1114,16 @@ archive_read_format_rar_seek_data(struct archive_read *a, int64_t offset,
rar->bytes_unconsumed = 0;
rar->offset = 0;
- return ret;
+ /*
+ * If a seek past the end of file was requested, return the requested
+ * offset.
+ */
+ if (ret == rar->unp_size && rar->offset_seek > rar->unp_size)
+ return rar->offset_seek;
+
+ /* Return the new offset */
+ rar->offset_seek = ret;
+ return rar->offset_seek;
}
else
{
@@ -1409,6 +1444,7 @@ read_header(struct archive_read *a, struct archive_entry *entry,
archive_set_error(&a->archive, ENOMEM, "Couldn't allocate memory.");
return (ARCHIVE_FATAL);
}
+ rar->dbo[rar->cursor].header_size = header_size;
rar->dbo[rar->cursor].start_offset = -1;
rar->dbo[rar->cursor].end_offset = -1;
}
@@ -1432,6 +1468,7 @@ read_header(struct archive_read *a, struct archive_entry *entry,
archive_set_error(&a->archive, ENOMEM, "Couldn't allocate memory.");
return (ARCHIVE_FATAL);
}
+ rar->dbo[0].header_size = header_size;
rar->dbo[0].start_offset = -1;
rar->dbo[0].end_offset = -1;
rar->cursor = 0;
@@ -1487,6 +1524,7 @@ read_header(struct archive_read *a, struct archive_entry *entry,
rar->bytes_uncopied = rar->bytes_unconsumed = 0;
rar->lzss.position = rar->offset = 0;
+ rar->offset_seek = 0;
rar->dictionary_size = 0;
rar->offset_outgoing = 0;
rar->br.cache_avail = 0;
@@ -1698,6 +1736,7 @@ read_data_stored(struct archive_read *a, const void **buff, size_t *size,
*size = bytes_avail;
*offset = rar->offset;
rar->offset += bytes_avail;
+ rar->offset_seek += bytes_avail;
rar->bytes_remaining -= bytes_avail;
rar->bytes_unconsumed = bytes_avail;
/* Calculate File CRC. */
diff --git a/libarchive/test/test_read_format_rar.c b/libarchive/test/test_read_format_rar.c
index 4317a1adc..0cc7d08ac 100644
--- a/libarchive/test/test_read_format_rar.c
+++ b/libarchive/test/test_read_format_rar.c
@@ -1166,6 +1166,7 @@ DEFINE_TEST(test_read_format_rar_multivolume_seek_data)
};
char buff[64];
int file_size = 20111;
+ int64_t result;
const char file_test_txt1[] = "d. \n
\n"
"
\n
\n