[Concept,16/16] fs: ext4l: Add statfs support

Message ID 20251227204318.886983-17-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>

Implement ext4l_statfs() to return filesystem statistics using the
ext4 superblock. The function returns block size, total block count,
and free block count.

Add unit test to verify the statfs implementation returns valid data.

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

 fs/ext4l/interface.c                | 22 ++++++++++++
 fs/fs_legacy.c                      |  2 +-
 include/ext4l.h                     |  9 +++++
 test/fs/ext4l.c                     | 52 ++++++++++++++++++++++++++---
 test/py/tests/test_fs/test_ext4l.py |  7 ++++
 5 files changed, 86 insertions(+), 6 deletions(-)
  

Patch

diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c
index f25664369e6..7c37c99488a 100644
--- a/fs/ext4l/interface.c
+++ b/fs/ext4l/interface.c
@@ -12,6 +12,7 @@ 
 #include <blk.h>
 #include <env.h>
 #include <fs.h>
+#include <fs_legacy.h>
 #include <membuf.h>
 #include <part.h>
 #include <malloc.h>
@@ -100,6 +101,27 @@  int ext4l_uuid(char *uuid_str)
 	return 0;
 }
 
+/**
+ * ext4l_statfs() - Get filesystem statistics
+ *
+ * @stats: Pointer to fs_statfs structure to fill
+ * Return: 0 on success, -ENODEV if not mounted
+ */
+int ext4l_statfs(struct fs_statfs *stats)
+{
+	struct ext4_super_block *es;
+
+	if (!ext4l_sb)
+		return -ENODEV;
+
+	es = EXT4_SB(ext4l_sb)->s_es;
+	stats->bsize = ext4l_sb->s_blocksize;
+	stats->blocks = ext4_blocks_count(es);
+	stats->bfree = ext4_free_blocks_count(es);
+
+	return 0;
+}
+
 /**
  * ext4l_set_blk_dev() - Set the block device for ext4l operations
  *
diff --git a/fs/fs_legacy.c b/fs/fs_legacy.c
index efb5ab669ff..155092519dd 100644
--- a/fs/fs_legacy.c
+++ b/fs/fs_legacy.c
@@ -292,7 +292,7 @@  static struct fstype_info fstypes[] = {
 		.mkdir = fs_mkdir_unsupported,
 		.ln = fs_ln_unsupported,
 		.rename = fs_rename_unsupported,
-		.statfs = fs_statfs_unsupported,
+		.statfs = ext4l_statfs,
 	},
 #endif
 #if IS_ENABLED(CONFIG_SANDBOX) && !IS_ENABLED(CONFIG_XPL_BUILD)
diff --git a/include/ext4l.h b/include/ext4l.h
index 9d9e79b7695..9cfe4867ffa 100644
--- a/include/ext4l.h
+++ b/include/ext4l.h
@@ -13,6 +13,7 @@  struct blk_desc;
 struct disk_partition;
 struct fs_dir_stream;
 struct fs_dirent;
+struct fs_statfs;
 
 /**
  * ext4l_probe() - Probe a block device for an ext4 filesystem
@@ -84,6 +85,14 @@  int ext4l_get_uuid(u8 *uuid);
  */
 int ext4l_uuid(char *uuid_str);
 
+/**
+ * ext4l_statfs() - Get filesystem statistics
+ *
+ * @stats: Pointer to fs_statfs structure to fill
+ * Return: 0 on success, -ENODEV if not mounted
+ */
+int ext4l_statfs(struct fs_statfs *stats);
+
 /**
  * ext4l_opendir() - Open a directory for iteration
  *
diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c
index 2641ac3678c..43801f252f7 100644
--- a/test/fs/ext4l.c
+++ b/test/fs/ext4l.c
@@ -331,7 +331,7 @@  FS_TEST_ARGS(fs_test_ext4l_uuid_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
 /**
  * fs_test_ext4l_fsinfo_norun() - Test fsinfo command
  *
- * This test verifies that the fsinfo command displays filesystem statistics.
+ * Verifies that the fsinfo command displays filesystem statistics.
  *
  * Arguments:
  *   fs_image: Path to the ext4 filesystem image
@@ -339,18 +339,60 @@  FS_TEST_ARGS(fs_test_ext4l_uuid_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
 static int fs_test_ext4l_fsinfo_norun(struct unit_test_state *uts)
 {
 	const char *fs_image = ut_str(EXT4L_ARG_IMAGE);
+	struct fs_statfs stats;
+	u64 used;
 
 	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));
+	ut_assertok(ext4l_statfs(&stats));
+	used = stats.blocks - stats.bfree;
+
 	console_record_reset_enable();
 	ut_assertok(run_commandf("fsinfo host 0"));
-	ut_assert_nextlinen("Block size:");
-	ut_assert_nextlinen("Total blocks:");
-	ut_assert_nextlinen("Used blocks:");
-	ut_assert_nextlinen("Free blocks:");
+
+	/* Skip any EXT4-fs mount messages, check output format */
+	ut_assert_skip_to_line("Block size: %lu bytes", stats.bsize);
+	ut_assert_nextlinen("Total blocks: %llu (%llu bytes,",
+			    stats.blocks, stats.blocks * stats.bsize);
+	ut_assert_nextlinen("Used blocks: %llu (%llu bytes,",
+			    used, used * stats.bsize);
+	ut_assert_nextlinen("Free blocks: %llu (%llu bytes,",
+			    stats.bfree, stats.bfree * stats.bsize);
 	ut_assert_console_end();
 
 	return 0;
 }
 FS_TEST_ARGS(fs_test_ext4l_fsinfo_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
 	     { "fs_image", UT_ARG_STR });
+
+/**
+ * fs_test_ext4l_statfs_norun() - Test ext4l_statfs function
+ *
+ * Verifies that ext4l can return filesystem statistics.
+ *
+ * Arguments:
+ *   fs_image: Path to the ext4 filesystem image
+ */
+static int fs_test_ext4l_statfs_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(EXT4L_ARG_IMAGE);
+	struct fs_statfs stats;
+
+	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));
+
+	/* Get filesystem statistics */
+	ut_assertok(ext4l_statfs(&stats));
+
+	/* Verify reasonable values for a 64MB filesystem */
+	ut_asserteq(SZ_4K, stats.bsize);
+	ut_assert(stats.blocks > 0);
+	ut_assert(stats.bfree > 0);
+	ut_assert(stats.bfree <= stats.blocks);
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_ext4l_statfs_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 0a9da40f358..754c2cc69c4 100644
--- a/test/py/tests/test_fs/test_ext4l.py
+++ b/test/py/tests/test_fs/test_ext4l.py
@@ -125,6 +125,13 @@  class TestExt4l:
                 f'ut -f fs fs_test_ext4l_uuid_norun fs_image={ext4_image}')
             assert 'failures: 0' in output
 
+    def test_statfs(self, ubman, ext4_image):
+        """Test that ext4l can return filesystem statistics."""
+        with ubman.log.section('Test ext4l statfs'):
+            output = ubman.run_command(
+                f'ut -f fs fs_test_ext4l_statfs_norun fs_image={ext4_image}')
+            assert 'failures: 0' in output
+
     def test_fsinfo(self, ubman, ext4_image):
         """Test that fsinfo command displays filesystem statistics."""
         with ubman.log.section('Test ext4l fsinfo'):