]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
FILE* seeking support (#2539)
authorljdarj <ljd@luigiscorner.mu>
Sat, 15 Mar 2025 18:17:27 +0000 (19:17 +0100)
committerGitHub <noreply@github.com>
Sat, 15 Mar 2025 18:17:27 +0000 (11:17 -0700)
Adding a seeker function to archive_read_open_FILE().

Fixes #437.

libarchive/archive_read_open_file.c
libarchive/test/test_open_file.c

index cf49ebd8fc9a29a1aaaf22a7bc2bb4f244e95f38..ecd56dce618dcde7204c3c5d487fb938436ca1ba 100644 (file)
@@ -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
index f4ca82bb01a0ee43146deff1cf4231a583ebec49..cc6b04d098482a66ee34aa449cd70a26ef8e3e63 100644 (file)
@@ -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;