[Concept,12/16] ext4l: Add read() support

Message ID 20251227204318.886983-13-sjg@u-boot.org
State New
Headers
Series fs: ext4l: Complete read-only filesystem support (Part I) |

Commit Message

Simon Glass Dec. 27, 2025, 8:43 p.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

Add ext4l_read() function to read file contents. The function resolves
the path to an inode, then reads the file block by block using
ext4_bread() and copies the data to the output buffer.

Signed-off-by: Simon Glass <sjg@chromium.org>

Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 fs/ext4l/interface.c                | 64 +++++++++++++++++++++++++++++
 fs/fs_legacy.c                      |  2 +-
 include/ext4l.h                     | 13 ++++++
 test/fs/ext4l.c                     | 38 +++++++++++++++++
 test/py/tests/test_fs/test_ext4l.py |  7 ++++
 5 files changed, 123 insertions(+), 1 deletion(-)
  

Patch

diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c
index 17648a59077..34e659cd28b 100644
--- a/fs/ext4l/interface.c
+++ b/fs/ext4l/interface.c
@@ -663,6 +663,70 @@  int ext4l_size(const char *filename, loff_t *sizep)
 	return 0;
 }
 
+int ext4l_read(const char *filename, void *buf, loff_t offset, loff_t len,
+	       loff_t *actread)
+{
+	uint copy_len, blk_off, blksize;
+	loff_t bytes_left, file_size;
+	struct buffer_head *bh;
+	struct inode *inode;
+	ext4_lblk_t block;
+	char *dst;
+	int ret;
+
+	*actread = 0;
+
+	ret = ext4l_resolve_path(filename, &inode);
+	if (ret) {
+		printf("** File not found %s **\n", filename);
+		return ret;
+	}
+
+	file_size = inode->i_size;
+	if (offset >= file_size)
+		return 0;
+
+	/* If len is 0, read the whole file from offset */
+	if (!len)
+		len = file_size - offset;
+
+	/* Clamp to file size */
+	if (offset + len > file_size)
+		len = file_size - offset;
+
+	blksize = inode->i_sb->s_blocksize;
+	bytes_left = len;
+	dst = buf;
+
+	while (bytes_left > 0) {
+		/* Calculate logical block number and offset within block */
+		block = offset / blksize;
+		blk_off = offset % blksize;
+
+		/* Read the block */
+		bh = ext4_bread(NULL, inode, block, 0);
+		if (IS_ERR(bh))
+			return PTR_ERR(bh);
+		if (!bh)
+			return -EIO;
+
+		/* Calculate how much to copy from this block */
+		copy_len = blksize - blk_off;
+		if (copy_len > bytes_left)
+			copy_len = bytes_left;
+
+		memcpy(dst, bh->b_data + blk_off, copy_len);
+		brelse(bh);
+
+		dst += copy_len;
+		offset += copy_len;
+		bytes_left -= copy_len;
+		*actread += copy_len;
+	}
+
+	return 0;
+}
+
 void ext4l_close(void)
 {
 	if (ext4l_open_dirs > 0)
diff --git a/fs/fs_legacy.c b/fs/fs_legacy.c
index 5edc35c4cdb..27a2d7be220 100644
--- a/fs/fs_legacy.c
+++ b/fs/fs_legacy.c
@@ -268,7 +268,7 @@  static struct fstype_info fstypes[] = {
 		.ls = ext4l_ls,
 		.exists = ext4l_exists,
 		.size = ext4l_size,
-		.read = fs_read_unsupported,
+		.read = ext4l_read,
 		.write = fs_write_unsupported,
 		.uuid = fs_uuid_unsupported,
 		.opendir = ext4l_opendir,
diff --git a/include/ext4l.h b/include/ext4l.h
index 6fee701f335..643060ee44c 100644
--- a/include/ext4l.h
+++ b/include/ext4l.h
@@ -55,6 +55,19 @@  int ext4l_exists(const char *filename);
  */
 int ext4l_size(const char *filename, loff_t *sizep);
 
+/**
+ * ext4l_read() - Read data from a file
+ *
+ * @filename: Path to file
+ * @buf: Buffer to read data into
+ * @offset: Byte offset to start reading from
+ * @len: Number of bytes to read (0 = read entire file from offset)
+ * @actread: Returns actual bytes read
+ * Return: 0 on success, negative on error
+ */
+int ext4l_read(const char *filename, void *buf, loff_t offset, loff_t len,
+	       loff_t *actread);
+
 /**
  * ext4l_get_uuid() - Get the filesystem UUID
  *
diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c
index f58a91893cc..1bea9186d5a 100644
--- a/test/fs/ext4l.c
+++ b/test/fs/ext4l.c
@@ -257,3 +257,41 @@  static int fs_test_ext4l_size_norun(struct unit_test_state *uts)
 }
 FS_TEST_ARGS(fs_test_ext4l_size_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
 	     { "fs_image", UT_ARG_STR });
+
+/**
+ * fs_test_ext4l_read_norun() - Test ext4l_read function
+ *
+ * Verifies that ext4l can read file contents.
+ *
+ * Arguments:
+ *   fs_image: Path to the ext4 filesystem image
+ */
+static int fs_test_ext4l_read_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(EXT4L_ARG_IMAGE);
+	loff_t actread;
+	char buf[32];
+
+	ut_assertnonnull(fs_image);
+	ut_assertok(run_commandf("host bind 0 %s", fs_image));
+	ut_assertok(fs_set_blk_dev("host", "0", FS_TYPE_ANY));
+
+	/* Read the test file - contains "hello world\n" (12 bytes) */
+	memset(buf, '\0', sizeof(buf));
+	ut_assertok(ext4l_read("/testfile.txt", buf, 0, 0, &actread));
+	ut_asserteq(12, actread);
+	ut_asserteq_str("hello world\n", buf);
+
+	/* Test partial read with offset */
+	memset(buf, '\0', sizeof(buf));
+	ut_assertok(ext4l_read("/testfile.txt", buf, 6, 5, &actread));
+	ut_asserteq(5, actread);
+	ut_asserteq_str("world", buf);
+
+	/* Verify read returns error for non-existent path */
+	ut_asserteq(-ENOENT, ext4l_read("/no/such/file", buf, 0, 10, &actread));
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_ext4l_read_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
+	     { "fs_image", UT_ARG_STR });
diff --git a/test/py/tests/test_fs/test_ext4l.py b/test/py/tests/test_fs/test_ext4l.py
index 922fa37a7d8..4064a6c53ff 100644
--- a/test/py/tests/test_fs/test_ext4l.py
+++ b/test/py/tests/test_fs/test_ext4l.py
@@ -110,3 +110,10 @@  class TestExt4l:
             output = ubman.run_command(
                 f'ut -f fs fs_test_ext4l_size_norun fs_image={ext4_image}')
             assert 'failures: 0' in output
+
+    def test_read(self, ubman, ext4_image):
+        """Test that ext4l can read file contents."""
+        with ubman.log.section('Test ext4l read'):
+            output = ubman.run_command(
+                f'ut -f fs fs_test_ext4l_read_norun fs_image={ext4_image}')
+            assert 'failures: 0' in output