From: ljdarj Date: Sat, 15 Mar 2025 18:17:27 +0000 (+0100) Subject: FILE* seeking support (#2539) X-Git-Tag: v3.8.0~65 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=09a2ed4853cd177264076a88c98e525e892a0d0b;p=thirdparty%2Flibarchive.git FILE* seeking support (#2539) Adding a seeker function to archive_read_open_FILE(). Fixes #437. --- diff --git a/libarchive/archive_read_open_file.c b/libarchive/archive_read_open_file.c index cf49ebd8f..ecd56dce6 100644 --- a/libarchive/archive_read_open_file.c +++ b/libarchive/archive_read_open_file.c @@ -56,9 +56,10 @@ struct read_FILE_data { char can_skip; }; -static int file_close(struct archive *, void *); -static ssize_t file_read(struct archive *, void *, const void **buff); -static int64_t file_skip(struct archive *, void *, int64_t request); +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, int); +static int64_t FILE_skip(struct archive *, void *, int64_t); int archive_read_open_FILE(struct archive *a, FILE *f) @@ -69,7 +70,7 @@ archive_read_open_FILE(struct archive *a, FILE *f) void *b; archive_clear_error(a); - mine = malloc(sizeof(*mine)); + mine = calloc(1, sizeof(*mine)); b = malloc(block_size); if (mine == NULL || b == NULL) { archive_set_error(a, ENOMEM, "No memory"); @@ -90,22 +91,22 @@ archive_read_open_FILE(struct archive *a, FILE *f) archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino); /* Enable the seek optimization only for regular files. */ mine->can_skip = 1; - } else - mine->can_skip = 0; + } #if defined(__CYGWIN__) || defined(_WIN32) setmode(fileno(mine->f), O_BINARY); #endif - archive_read_set_read_callback(a, file_read); - archive_read_set_skip_callback(a, file_skip); - archive_read_set_close_callback(a, file_close); + archive_read_set_read_callback(a, FILE_read); + archive_read_set_skip_callback(a, FILE_skip); + archive_read_set_seek_callback(a, FILE_seek); + archive_read_set_close_callback(a, FILE_close); archive_read_set_callback_data(a, mine); return (archive_read_open1(a)); } static ssize_t -file_read(struct archive *a, void *client_data, const void **buff) +FILE_read(struct archive *a, void *client_data, const void **buff) { struct read_FILE_data *mine = (struct read_FILE_data *)client_data; size_t bytes_read; @@ -119,13 +120,13 @@ file_read(struct archive *a, void *client_data, const void **buff) } static int64_t -file_skip(struct archive *a, void *client_data, int64_t request) +FILE_skip(struct archive *a, void *client_data, int64_t request) { struct read_FILE_data *mine = (struct read_FILE_data *)client_data; -#if HAVE_FSEEKO - off_t skip = (off_t)request; -#elif HAVE__FSEEKI64 +#if HAVE__FSEEKI64 int64_t skip = request; +#elif HAVE_FSEEKO + off_t skip = (off_t)request; #else long skip = (long)request; #endif @@ -167,8 +168,57 @@ file_skip(struct archive *a, void *client_data, int64_t request) return (request); } +/* + * TODO: Store the offset and use it in the read callback. + */ +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; +#if HAVE__FSEEKI64 + int64_t skip = request; +#elif HAVE_FSEEKO + off_t skip = (off_t)request; +#else + long skip = (long)request; +#endif + int skip_bits = sizeof(skip) * 8 - 1; + (void)a; /* UNUSED */ + + /* If request is too big for a long or an off_t, reduce it. */ + if (sizeof(request) > sizeof(skip)) { + int64_t max_skip = + (((int64_t)1 << (skip_bits - 1)) - 1) * 2 + 1; + if (request > max_skip) + skip = max_skip; + } + +#ifdef __ANDROID__ + /* Newer Android versions have fseeko...to meditate. */ + int64_t ret = lseek(fileno(mine->f), skip, whence); + if (ret >= 0) { + return ret; + } +#elif HAVE__FSEEKI64 + if (_fseeki64(mine->f, skip, whence) == 0) { + return _ftelli64(mine->f); + } +#elif HAVE_FSEEKO + if (fseeko(mine->f, skip, whence) == 0) { + return ftello(mine->f); + } +#else + if (fseek(mine->f, skip, whence) == 0) { + return ftell(mine->f); + } +#endif + /* If we arrive here, the input is corrupted or truncated so fail. */ + archive_set_error(a, errno, "Error seeking in FILE* pointer"); + return (ARCHIVE_FATAL); +} + static int -file_close(struct archive *a, void *client_data) +FILE_close(struct archive *a, void *client_data) { struct read_FILE_data *mine = (struct read_FILE_data *)client_data; @@ -176,4 +226,4 @@ file_close(struct archive *a, void *client_data) free(mine->buffer); free(mine); return (ARCHIVE_OK); -} +} \ No newline at end of file diff --git a/libarchive/test/test_open_file.c b/libarchive/test/test_open_file.c index f4ca82bb0..cc6b04d09 100644 --- a/libarchive/test/test_open_file.c +++ b/libarchive/test/test_open_file.c @@ -31,14 +31,14 @@ DEFINE_TEST(test_open_file) struct archive *a; FILE *f; - f = fopen("test.tar", "wb"); + f = fopen("test.7z", "wb"); assert(f != NULL); if (f == NULL) return; /* Write an archive through this FILE *. */ assert((a = archive_write_new()) != NULL); - assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_ustar(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_7zip(a)); assertEqualIntA(a, ARCHIVE_OK, archive_write_add_filter_none(a)); assertEqualIntA(a, ARCHIVE_OK, archive_write_open_FILE(a, f)); @@ -70,9 +70,10 @@ DEFINE_TEST(test_open_file) fclose(f); /* - * Now, read the data back. + * Now, read the data back. 7z requiring seeking, that also + * tests that the seeking support works. */ - f = fopen("test.tar", "rb"); + f = fopen("test.7z", "rb"); assert(f != NULL); if (f == NULL) return;