From 09a2ed4853cd177264076a88c98e525e892a0d0b Mon Sep 17 00:00:00 2001
From: ljdarj <ljd@luigiscorner.mu>
Date: Sat, 15 Mar 2025 19:17:27 +0100
Subject: [PATCH] FILE* seeking support (#2539)

Adding a seeker function to archive_read_open_FILE().

Fixes #437.

Upstream-Status: Backport [https://github.com/libarchive/libarchive/commit/09a2ed4853cd177264076a88c98e525e892a0d0b]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
 libarchive/archive_read_open_file.c | 82 +++++++++++++++++++++++------
 libarchive/test/test_open_file.c    |  9 ++--
 2 files changed, 71 insertions(+), 20 deletions(-)

diff --git a/libarchive/archive_read_open_file.c b/libarchive/archive_read_open_file.c
index cf49ebd8..ecd56dce 100644
--- a/libarchive/archive_read_open_file.c
+++ b/libarchive/archive_read_open_file.c
@@ -57,9 +57,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)
@@ -70,7 +71,7 @@ archive_read_open_FILE(struct archive *a, FILE *f)
 	void *b;
 
 	archive_clear_error(a);
-	mine = (struct read_FILE_data *)malloc(sizeof(*mine));
+	mine = (struct read_FILE_data *)calloc(1, sizeof(*mine));
 	b = malloc(block_size);
 	if (mine == NULL || b == NULL) {
 		archive_set_error(a, ENOMEM, "No memory");
@@ -91,22 +92,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;
@@ -120,13 +121,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
@@ -168,8 +169,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;
 
@@ -177,4 +227,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 f4ca82bb..cc6b04d0 100644
--- a/libarchive/test/test_open_file.c
+++ b/libarchive/test/test_open_file.c
@@ -32,14 +32,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));
 
@@ -71,9 +71,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;
