[PATCH 00/16] fs: ext4l: Complete read-only filesystem support (Part I)
From: Simon Glass <simon.glass@canonical.com> This series completes read-only support for the ext4l filesystem driver, which is a port of the Linux ext4 driver to U-Boot. The ext4l driver provides more complete ext4 support than the existing ext4 driver, including proper handling of extents, directory hashing, and other ext4 features. Changes include: Sandbox infrastructure: - Fix IRQ macros and buffer_head includes for sandbox builds Core fixes: - Fix path lookup by implementing proper dentry operations - Fix fscrypt_match_name to do actual name comparison Filesystem operations: - Add directory listing (opendir/readdir/closedir) - Add file existence check (exists) - Add file size query (size) - Add file read support (read) - Add UUID query (uuid) - Add filesystem statistics (statfs) New command: - Add fsinfo command to display filesystem statistics Testing: - Add comprehensive unit tests for all operations - Enable fsuuid command for sandbox testing Simon Glass (16): CLAUDE.md: Add note about test-declaration placement buildman: Fix flaky test_kconfig_change test properly linux: buffer_head: Include atomic.h directly sandbox: Convert IRQ macros to static inline functions ext4l: Fix path lookup by implementing dentry operations cmd: Enable fsuuid command for sandbox ext4l: Fix comment formatting test: ext4l: Handle variable console output in tests ext4l: Add open/read/close directory ext4l: Add exists() support ext4l: Add size() support ext4l: Add read() support ext4l: Add uuid() support fs: Add statfs method to the filesystem interface cmd: Add fsinfo command fs: ext4l: Add statfs support CLAUDE.md | 2 + arch/sandbox/include/asm/system.h | 10 +- cmd/Kconfig | 11 + cmd/fs.c | 14 ++ doc/usage/cmd/fsinfo.rst | 53 +++++ doc/usage/index.rst | 1 + fs/ext4l/ext4_uboot.h | 16 +- fs/ext4l/interface.c | 352 ++++++++++++++++++++++++++++ fs/fs_legacy.c | 81 ++++++- include/ext4l.h | 77 ++++++ include/fs_cmd.h | 11 + include/fs_legacy.h | 21 ++ include/linux/buffer_head.h | 5 +- include/linux/fs.h | 1 + test/fs/ext4l.c | 300 +++++++++++++++++++++++- test/py/tests/test_fs/test_ext4l.py | 64 ++++- tools/buildman/func_test.py | 8 +- 17 files changed, 998 insertions(+), 29 deletions(-) create mode 100644 doc/usage/cmd/fsinfo.rst -- 2.43.0 base-commit: d71f2f8e9b27ea99b928b3f8fb211a82682a8873 branch: exti
From: Simon Glass <simon.glass@canonical.com> Test declarations should immediately follow the closing brace of the function they declare, with no blank line in between. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- CLAUDE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index ca0571ecef8..b2590b17fab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,3 +72,5 @@ pyt <test_name> - Keep commit messages concise - focus on the key change and essential details only - Code should be formatted to 80 columns and not have trailing spaces - Remember to use Co-developed-by instead of Co-Authored-By in commits +- Test declarations (e.g., UNIT_TEST macro) should immediately follow the + closing } of the function they declare, with no blank line in between -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The previous fix using -T4 is insufficient because queue-based job distribution does not guarantee one board per thread. Even with 4 threads and 4 boards, a faster thread can finish its board and grab another from the queue, causing .config to leak between boards in the same thread's work directory. Fix by using -P (per-board-out-dir) which gives each board its own output directory, completely preventing .config leakage regardless of thread scheduling. Fixes: 17618b597508 ("buildman: Fix flaky test_kconfig_change test") Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- tools/buildman/func_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/buildman/func_test.py b/tools/buildman/func_test.py index 31cff04a060..6167ea525e7 100644 --- a/tools/buildman/func_test.py +++ b/tools/buildman/func_test.py @@ -1285,13 +1285,13 @@ something: me config_exists[0] = True return exists - # Run buildman with kconfig checking enabled. Use -T4 to ensure each - # board gets its own thread, avoiding .config leaking between boards - # when a thread processes multiple boards (which happens with <4 CPUs) + # Run buildman with kconfig checking enabled. Use -P to give each + # board its own output directory, preventing .config from leaking + # between boards when a thread processes multiple boards sequentially. with mock.patch.object(builderthread, 'kconfig_changed_since', mock_kconfig_changed): self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir, - '-T4') + '-P') # Verify kconfig_changed_since was called self.assertGreater(call_count[0], 0) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The comment about atomic_t being expected from the including file is outdated. Include asm-generic/atomic.h directly to provide the atomic_t type definition. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/buffer_head.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index 0f8f5b6caf1..5fbcc757c80 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -12,10 +12,7 @@ #include <linux/types.h> #include <linux/list.h> #include <linux/spinlock.h> -/* - * Note: atomic_t and sector_t are expected to be defined by the including - * file (ext4_uboot.h) before including this header. - */ +#include <asm-generic/atomic.h> enum bh_state_bits { BH_Uptodate, /* Contains valid data */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Convert the local_irq_* macros to static inline functions to avoid "unused variable 'flags'" warnings when building with the atomic operations from asm-generic/atomic.h. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- arch/sandbox/include/asm/system.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/sandbox/include/asm/system.h b/arch/sandbox/include/asm/system.h index 7933b6292e2..438e2f65dc8 100644 --- a/arch/sandbox/include/asm/system.h +++ b/arch/sandbox/include/asm/system.h @@ -7,10 +7,10 @@ #define __ASM_SANDBOX_SYSTEM_H /* Define this as nops for sandbox architecture */ -#define local_irq_save(x) -#define local_irq_enable() -#define local_irq_disable() -#define local_save_flags(x) -#define local_irq_restore(x) +#define local_irq_save(x) ((x) = 0) +#define local_irq_enable() do { } while (0) +#define local_irq_disable() do { } while (0) +#define local_save_flags(x) ((x) = 0) +#define local_irq_restore(x) do { (void)(x); } while (0) #endif -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The fscrypt_match_name stub macro always returns 1, causing every directory entry to match regardless of name. Also d_splice_alias is a no-op that returns NULL, so the inode found by ext4_lookup is never associated with the dentry. Fix fscrypt_match_name to properly compare name lengths and contents. Fix d_splice_alias, d_instantiate and d_instantiate_new to set d->d_inode. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 52f479e9673..a90289c8fa5 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -1426,10 +1426,10 @@ typedef unsigned int projid_t; #define d_find_any_alias(i) ({ (void)(i); (struct dentry *)NULL; }) #define dget_parent(d) ({ (void)(d); (struct dentry *)NULL; }) #define dput(d) do { (void)(d); } while (0) -#define d_splice_alias(i, d) ({ (void)(i); (void)(d); (struct dentry *)NULL; }) +#define d_splice_alias(i, d) ({ (d)->d_inode = (i); (d); }) #define d_obtain_alias(i) ({ (void)(i); (struct dentry *)NULL; }) -#define d_instantiate_new(d, i) do { (void)(d); (void)(i); } while (0) -#define d_instantiate(d, i) do { (void)(d); (void)(i); } while (0) +#define d_instantiate_new(d, i) ((void)((d)->d_inode = (i))) +#define d_instantiate(d, i) ((void)((d)->d_inode = (i))) #define d_tmpfile(f, i) do { (void)(f); (void)(i); } while (0) #define d_invalidate(d) do { (void)(d); } while (0) #define finish_open_simple(f, e) (e) @@ -1555,7 +1555,6 @@ static inline char *d_path(const struct path *path, char *buf, int buflen) #define fscrypt_limit_io_blocks(i, lb, l) (l) #define fscrypt_prepare_setattr(d, a) ({ (void)(d); (void)(a); 0; }) #define fscrypt_dio_supported(i) (1) -#define fscrypt_match_name(f, n, l) ({ (void)(f); (void)(n); (void)(l); 1; }) #define fscrypt_has_permitted_context(p, c) ({ (void)(p); (void)(c); 1; }) #define fscrypt_is_nokey_name(d) ({ (void)(d); 0; }) #define fscrypt_prepare_symlink(d, s, l, m, dl) ({ (void)(d); (void)(s); (void)(l); (void)(m); (void)(dl); 0; }) @@ -1572,6 +1571,15 @@ struct fscrypt_name { bool is_nokey_name; }; +static inline int fscrypt_match_name(const struct fscrypt_name *fname, + const u8 *de_name, u32 de_name_len) +{ + if (fname->usr_fname->len != de_name_len) + return 0; + + return !memcmp(fname->usr_fname->name, de_name, de_name_len); +} + /* fsverity stubs */ #define fsverity_prepare_setattr(d, a) ({ (void)(d); (void)(a); 0; }) #define fsverity_active(i) (0) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The fsuuid command is useful for testing filesystem UUID support. Enable it by default for sandbox builds. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- cmd/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/Kconfig b/cmd/Kconfig index 5dad8d7deb7..5cb34509746 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -2835,6 +2835,7 @@ config CMD_FS_GENERIC config CMD_FS_UUID bool "fsuuid command" + default y if SANDBOX help Enables fsuuid command for filesystem UUID. -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add blank line after function doc titles per kernel-doc style. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/interface.c | 9 +++++++++ include/ext4l.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index b897f30c223..6e146f246bd 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -42,6 +42,7 @@ static char ext4l_msg_data[EXT4L_MSG_BUF_SIZE]; /** * ext4l_get_blk_dev() - Get the current block device + * * Return: Block device descriptor or NULL if not mounted */ struct blk_desc *ext4l_get_blk_dev(void) @@ -53,6 +54,7 @@ struct blk_desc *ext4l_get_blk_dev(void) /** * ext4l_get_partition() - Get the current partition info + * * Return: Partition info pointer */ struct disk_partition *ext4l_get_partition(void) @@ -62,6 +64,7 @@ struct disk_partition *ext4l_get_partition(void) /** * ext4l_get_uuid() - Get the filesystem UUID + * * @uuid: Buffer to receive the 16-byte UUID * Return: 0 on success, -ENODEV if not mounted */ @@ -75,6 +78,7 @@ int ext4l_get_uuid(u8 *uuid) /** * ext4l_set_blk_dev() - Set the block device for ext4l operations + * * @blk_dev: Block device descriptor * @partition: Partition info (can be NULL for whole disk) */ @@ -110,6 +114,7 @@ static void ext4l_msg_init(void) /** * ext4l_record_msg() - Record a message in the buffer + * * @msg: Message string to record * @len: Length of message */ @@ -304,6 +309,7 @@ err_exit_es: /** * ext4l_read_symlink() - Read the target of a symlink inode + * * @inode: Symlink inode * @target: Buffer to store target * @max_len: Maximum length of target buffer @@ -350,6 +356,7 @@ static int ext4l_resolve_path_internal(const char *path, struct inode **inodep, /** * ext4l_resolve_path() - Resolve path to inode + * * @path: Path to resolve * @inodep: Output inode pointer * Return: 0 on success, negative on error @@ -361,6 +368,7 @@ static int ext4l_resolve_path(const char *path, struct inode **inodep) /** * ext4l_resolve_path_internal() - Resolve path with symlink following + * * @path: Path to resolve * @inodep: Output inode pointer * @depth: Current recursion depth (for symlink loop detection) @@ -551,6 +559,7 @@ static int ext4l_resolve_path_internal(const char *path, struct inode **inodep, /** * ext4l_dir_actor() - Directory entry callback for ext4_readdir + * * @ctx: Directory context * @name: Entry name * @namelen: Length of name diff --git a/include/ext4l.h b/include/ext4l.h index e6ca11c163a..333d9db139c 100644 --- a/include/ext4l.h +++ b/include/ext4l.h @@ -30,6 +30,7 @@ void ext4l_close(void); /** * ext4l_ls() - List directory contents + * * @dirname: Directory path to list * Return: 0 on success, negative on error */ @@ -37,6 +38,7 @@ int ext4l_ls(const char *dirname); /** * ext4l_get_uuid() - Get the filesystem UUID + * * @uuid: Buffer to receive the 16-byte UUID * Return: 0 on success, -ENODEV if not mounted */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Directory entries appear in hash order and mount messages may appear multiple times during probe operations. Update tests to skip remaining output after finding expected content. We could perhaps refine this with more powerful matching, but that is left for another day. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/fs/ext4l.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c index 122b022d8d8..4c477ce3338 100644 --- a/test/fs/ext4l.c +++ b/test/fs/ext4l.c @@ -68,12 +68,14 @@ static int fs_test_ext4l_msgs_norun(struct unit_test_state *uts) /* * Check messages. The probe test runs first and doesn't unmount, - * so the journal needs recovery. Verify both messages. + * so the journal needs recovery. The filesystem may be mounted + * multiple times during probe operations. Just verify we see the + * expected mount message at least once. */ - ut_assert_nextline("EXT4-fs (ext4l_mmc0): recovery complete"); - ut_assert_nextline("EXT4-fs (ext4l_mmc0): mounted filesystem %s r/w with ordered data mode. Quota mode: disabled.", - uuid_str); - ut_assert_console_end(); + ut_assert_skip_to_line("EXT4-fs (ext4l_mmc0): mounted filesystem %s r/w" + " with ordered data mode. Quota mode: disabled.", + uuid_str); + /* Skip any remaining messages */ return 0; } @@ -100,9 +102,10 @@ static int fs_test_ext4l_ls_norun(struct unit_test_state *uts) * The Python test adds testfile.txt (12 bytes) to the image. * Directory entries appear in hash order which varies between runs. * Verify the file entry appears with correct size (12 bytes). + * Other entries like ., .., subdir, lost+found may also appear. */ ut_assert_skip_to_line(" 12 testfile.txt"); - ut_assert_console_end(); + /* Skip any remaining entries */ return 0; } -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Implement directory-iteration for the ext4l filesystem driver, allowing callers to iterate through directory entries one at a time. Add ext4l_opendir() which opens a directory and returns a stream handle, ext4l_readdir() which returns the next directory entry, and ext4l_closedir() which closes the stream and frees resources. The implementation uses a struct dir_context to capture single entries from ext4_readdir(), with logic to skip previously returned entries since the htree code may re-emit them. Update struct file to include a position. Wire these functions into fs_legacy.c for the ext4l filesystem type. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/interface.c | 210 ++++++++++++++++++++++++++++ fs/fs_legacy.c | 4 +- include/ext4l.h | 28 ++++ include/linux/fs.h | 1 + test/fs/ext4l.c | 85 +++++++++++ test/py/tests/test_fs/test_ext4l.py | 22 ++- 6 files changed, 348 insertions(+), 2 deletions(-) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index 6e146f246bd..e7f09fd45dc 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -11,6 +11,7 @@ #include <blk.h> #include <env.h> +#include <fs.h> #include <membuf.h> #include <part.h> #include <malloc.h> @@ -33,6 +34,9 @@ static struct blk_desc *ext4l_blk_dev; static struct disk_partition ext4l_partition; static int ext4l_mounted; +/* Count of open directory streams (prevents unmount while iterating) */ +static int ext4l_open_dirs; + /* Global super_block pointer for filesystem operations */ static struct super_block *ext4l_sb; @@ -634,7 +638,213 @@ int ext4l_ls(const char *dirname) void ext4l_close(void) { + if (ext4l_open_dirs > 0) + return; + ext4l_dev_desc = NULL; ext4l_sb = NULL; ext4l_clear_blk_dev(); } + +/** + * struct ext4l_dir - ext4l directory stream state + * @parent: base fs_dir_stream structure + * @dirent: directory entry to return to caller + * @dir_inode: pointer to directory inode + * @file: file structure for ext4_readdir + * @entry_found: flag set by actor when entry is captured + * @last_ino: inode number of last returned entry (to skip on next call) + * @skip_last: true if we need to skip the last_ino entry + * + * The filesystem stays mounted while directory streams are open (ext4l_close + * checks ext4l_open_dirs), so we can keep direct pointers to inodes. + */ +struct ext4l_dir { + struct fs_dir_stream parent; + struct fs_dirent dirent; + struct inode *dir_inode; + struct file file; + bool entry_found; + u64 last_ino; + bool skip_last; +}; + +/** + * struct ext4l_readdir_ctx - Extended dir_context with back-pointer + * @ctx: base dir_context structure (must be first) + * @dir: pointer to ext4l_dir for state updates + */ +struct ext4l_readdir_ctx { + struct dir_context ctx; + struct ext4l_dir *dir; +}; + +/** + * ext4l_opendir_actor() - dir_context actor that captures single entry + * + * This actor is called by ext4_readdir for each directory entry. It captures + * the first entry found (skipping the previously returned entry if needed) + * and returns non-zero to stop iteration. + */ +static int ext4l_opendir_actor(struct dir_context *ctx, const char *name, + int namelen, loff_t offset, u64 ino, + unsigned int d_type) +{ + struct ext4l_readdir_ctx *rctx; + struct ext4l_dir *dir; + struct fs_dirent *dent; + struct inode *inode; + + rctx = container_of(ctx, struct ext4l_readdir_ctx, ctx); + dir = rctx->dir; + + /* + * Skip the entry we returned last time. The htree code may call us + * with the same entry again due to its extra_fname handling. + */ + if (dir->skip_last && ino == dir->last_ino) { + dir->skip_last = false; + return 0; /* Continue to next entry */ + } + + dent = &dir->dirent; + + /* Copy name */ + if (namelen >= FS_DIRENT_NAME_LEN) + namelen = FS_DIRENT_NAME_LEN - 1; + memcpy(dent->name, name, namelen); + dent->name[namelen] = '\0'; + + /* Set type based on d_type hint */ + switch (d_type) { + case DT_DIR: + dent->type = FS_DT_DIR; + break; + case DT_LNK: + dent->type = FS_DT_LNK; + break; + default: + dent->type = FS_DT_REG; + break; + } + + /* Look up inode to get size and other attributes */ + inode = ext4_iget(ext4l_sb, ino, 0); + if (!IS_ERR(inode)) { + dent->size = inode->i_size; + /* Refine type from inode mode if needed */ + if (S_ISDIR(inode->i_mode)) + dent->type = FS_DT_DIR; + else if (S_ISLNK(inode->i_mode)) + dent->type = FS_DT_LNK; + else + dent->type = FS_DT_REG; + } else { + dent->size = 0; + } + + dir->entry_found = true; + dir->last_ino = ino; + + /* + * Return non-zero to stop iteration after one entry. + * dir_emit() returns (actor(...) == 0), so: + * actor returns 0 -> dir_emit returns 1 (continue) + * actor returns non-zero -> dir_emit returns 0 (stop) + */ + return 1; +} + +int ext4l_opendir(const char *filename, struct fs_dir_stream **dirsp) +{ + struct ext4l_dir *dir; + struct inode *inode; + int ret; + + if (!ext4l_mounted) + return -ENODEV; + + ret = ext4l_resolve_path(filename, &inode); + if (ret) + return ret; + + if (!S_ISDIR(inode->i_mode)) + return -ENOTDIR; + + dir = calloc(1, sizeof(*dir)); + if (!dir) + return -ENOMEM; + + dir->dir_inode = inode; + dir->entry_found = false; + + /* Set up file structure for ext4_readdir */ + dir->file.f_inode = inode; + dir->file.f_mapping = inode->i_mapping; + dir->file.private_data = kzalloc(sizeof(struct dir_private_info), + GFP_KERNEL); + if (!dir->file.private_data) { + free(dir); + return -ENOMEM; + } + + /* Increment open dir count to prevent unmount */ + ext4l_open_dirs++; + + *dirsp = (struct fs_dir_stream *)dir; + + return 0; +} + +int ext4l_readdir(struct fs_dir_stream *dirs, struct fs_dirent **dentp) +{ + struct ext4l_dir *dir = (struct ext4l_dir *)dirs; + struct ext4l_readdir_ctx ctx; + int ret; + + if (!ext4l_mounted) + return -ENODEV; + + memset(&dir->dirent, '\0', sizeof(dir->dirent)); + dir->entry_found = false; + + /* Skip the entry we returned last time (htree may re-emit it) */ + if (dir->last_ino) + dir->skip_last = true; + + /* Set up extended dir_context for this iteration */ + memset(&ctx, '\0', sizeof(ctx)); + ctx.ctx.actor = ext4l_opendir_actor; + ctx.ctx.pos = dir->file.f_pos; + ctx.dir = dir; + + ret = ext4_readdir(&dir->file, &ctx.ctx); + + /* Update file position for next call */ + dir->file.f_pos = ctx.ctx.pos; + + if (ret < 0) + return ret; + + if (!dir->entry_found) + return -ENOENT; + + *dentp = &dir->dirent; + + return 0; +} + +void ext4l_closedir(struct fs_dir_stream *dirs) +{ + struct ext4l_dir *dir = (struct ext4l_dir *)dirs; + + if (dir) { + if (dir->file.private_data) + ext4_htree_free_dir_info(dir->file.private_data); + free(dir); + } + + /* Decrement open dir count */ + if (ext4l_open_dirs > 0) + ext4l_open_dirs--; +} diff --git a/fs/fs_legacy.c b/fs/fs_legacy.c index 6ca9d6e647a..7d293468ea8 100644 --- a/fs/fs_legacy.c +++ b/fs/fs_legacy.c @@ -271,7 +271,9 @@ static struct fstype_info fstypes[] = { .read = fs_read_unsupported, .write = fs_write_unsupported, .uuid = fs_uuid_unsupported, - .opendir = fs_opendir_unsupported, + .opendir = ext4l_opendir, + .readdir = ext4l_readdir, + .closedir = ext4l_closedir, .unlink = fs_unlink_unsupported, .mkdir = fs_mkdir_unsupported, .ln = fs_ln_unsupported, diff --git a/include/ext4l.h b/include/ext4l.h index 333d9db139c..6d8eba84f4e 100644 --- a/include/ext4l.h +++ b/include/ext4l.h @@ -11,6 +11,8 @@ struct blk_desc; struct disk_partition; +struct fs_dir_stream; +struct fs_dirent; /** * ext4l_probe() - Probe a block device for an ext4 filesystem @@ -44,4 +46,30 @@ int ext4l_ls(const char *dirname); */ int ext4l_get_uuid(u8 *uuid); +/** + * ext4l_opendir() - Open a directory for iteration + * + * @filename: Directory path + * @dirsp: Returns directory stream pointer + * Return: 0 on success, -ENODEV if not mounted, -ENOTDIR if not a directory, + * -ENOMEM on allocation failure + */ +int ext4l_opendir(const char *filename, struct fs_dir_stream **dirsp); + +/** + * ext4l_readdir() - Read the next directory entry + * + * @dirs: Directory stream from ext4l_opendir + * @dentp: Returns pointer to directory entry + * Return: 0 on success, -ENODEV if not mounted, -ENOENT at end of directory + */ +int ext4l_readdir(struct fs_dir_stream *dirs, struct fs_dirent **dentp); + +/** + * ext4l_closedir() - Close a directory stream + * + * @dirs: Directory stream to close + */ +void ext4l_closedir(struct fs_dir_stream *dirs); + #endif /* __EXT4L_H__ */ diff --git a/include/linux/fs.h b/include/linux/fs.h index ef28c12c022..090ee192061 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -98,6 +98,7 @@ struct file { void *private_data; struct file_ra_state f_ra; struct path f_path; + loff_t f_pos; }; /* Get inode from file */ diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c index 4c477ce3338..d9ed21407e7 100644 --- a/test/fs/ext4l.c +++ b/test/fs/ext4l.c @@ -111,3 +111,88 @@ static int fs_test_ext4l_ls_norun(struct unit_test_state *uts) } FS_TEST_ARGS(fs_test_ext4l_ls_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }); + +/** + * fs_test_ext4l_opendir_norun() - Test ext4l opendir/readdir/closedir + * + * Verifies that the ext4l driver can iterate through directory entries using + * the opendir/readdir/closedir interface. It checks: + * - Regular files (testfile.txt) + * - Subdirectories (subdir) + * - Symlinks (link.txt) + * - Files in subdirectories (subdir/nested.txt) + * + * Arguments: + * fs_image: Path to the ext4 filesystem image + */ +static int fs_test_ext4l_opendir_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(EXT4L_ARG_IMAGE); + struct fs_dir_stream *dirs; + struct fs_dirent *dent; + bool found_testfile = false; + bool found_subdir = false; + bool found_symlink = false; + bool found_nested = false; + int count = 0; + + 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)); + + /* Open root directory */ + ut_assertok(ext4l_opendir("/", &dirs)); + ut_assertnonnull(dirs); + + /* Iterate through entries */ + while (!ext4l_readdir(dirs, &dent)) { + ut_assertnonnull(dent); + count++; + if (!strcmp(dent->name, "testfile.txt")) { + found_testfile = true; + ut_asserteq(FS_DT_REG, dent->type); + ut_asserteq(12, dent->size); + } else if (!strcmp(dent->name, "subdir")) { + found_subdir = true; + ut_asserteq(FS_DT_DIR, dent->type); + } else if (!strcmp(dent->name, "link.txt")) { + found_symlink = true; + ut_asserteq(FS_DT_LNK, dent->type); + } + } + + ext4l_closedir(dirs); + + /* Verify we found expected entries */ + ut_assert(found_testfile); + ut_assert(found_subdir); + ut_assert(found_symlink); + /* At least ., .., testfile.txt, subdir, link.txt */ + ut_assert(count >= 5); + + /* Now test reading the subdirectory */ + ut_assertok(fs_set_blk_dev("host", "0", FS_TYPE_ANY)); + ut_assertok(ext4l_opendir("/subdir", &dirs)); + ut_assertnonnull(dirs); + + count = 0; + while (!ext4l_readdir(dirs, &dent)) { + ut_assertnonnull(dent); + count++; + if (!strcmp(dent->name, "nested.txt")) { + found_nested = true; + ut_asserteq(FS_DT_REG, dent->type); + ut_asserteq(12, dent->size); + } + } + + ext4l_closedir(dirs); + + ut_assert(found_nested); + /* At least ., .., nested.txt */ + ut_assert(count >= 3); + + return 0; +} +FS_TEST_ARGS(fs_test_ext4l_opendir_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 93b6d4d34e8..073b02a80ce 100644 --- a/test/py/tests/test_fs/test_ext4l.py +++ b/test/py/tests/test_fs/test_ext4l.py @@ -37,14 +37,27 @@ class TestExt4l: shell=True) check_call(f'mkfs.ext4 -q {image_path}', shell=True) - # Add a test file using debugfs (no mount required) + # Add test files using debugfs (no mount required) with NamedTemporaryFile(mode='w', delete=False) as tmp: tmp.write('hello world\n') tmp_path = tmp.name try: + # Add a regular file check_call(f'debugfs -w {image_path} ' f'-R "write {tmp_path} testfile.txt" 2>/dev/null', shell=True) + # Add a subdirectory + check_call(f'debugfs -w {image_path} ' + f'-R "mkdir subdir" 2>/dev/null', + shell=True) + # Add a file in the subdirectory + check_call(f'debugfs -w {image_path} ' + f'-R "write {tmp_path} subdir/nested.txt" 2>/dev/null', + shell=True) + # Add a symlink + check_call(f'debugfs -w {image_path} ' + f'-R "symlink link.txt testfile.txt" 2>/dev/null', + shell=True) finally: os.unlink(tmp_path) except CalledProcessError: @@ -76,3 +89,10 @@ class TestExt4l: output = ubman.run_command( f'ut -f fs fs_test_ext4l_ls_norun fs_image={ext4_image}') assert 'failures: 0' in output + + def test_opendir(self, ubman, ext4_image): + """Test that ext4l can iterate directory entries.""" + with ubman.log.section('Test ext4l opendir'): + output = ubman.run_command( + f'ut -f fs fs_test_ext4l_opendir_norun fs_image={ext4_image}') + assert 'failures: 0' in output -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Implement ext4l_exists() to check if a file or directory exists on the filesystem. This uses ext4l_resolve_path() to look up the path and returns 1 if found, 0 otherwise. Wire the function into fs_legacy.c and add a basic test. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/interface.c | 13 +++++++++++++ fs/fs_legacy.c | 2 +- include/ext4l.h | 8 ++++++++ test/fs/ext4l.c | 27 +++++++++++++++++++++++++++ test/py/tests/test_fs/test_ext4l.py | 7 +++++++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index e7f09fd45dc..409a3ff7edc 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -636,6 +636,19 @@ int ext4l_ls(const char *dirname) return ret; } +int ext4l_exists(const char *filename) +{ + struct inode *inode; + + if (!filename) + return 0; + + if (ext4l_resolve_path(filename, &inode)) + return 0; + + return 1; +} + void ext4l_close(void) { if (ext4l_open_dirs > 0) diff --git a/fs/fs_legacy.c b/fs/fs_legacy.c index 7d293468ea8..849c0304668 100644 --- a/fs/fs_legacy.c +++ b/fs/fs_legacy.c @@ -266,7 +266,7 @@ static struct fstype_info fstypes[] = { .probe = ext4l_probe, .close = ext4l_close, .ls = ext4l_ls, - .exists = fs_exists_unsupported, + .exists = ext4l_exists, .size = fs_size_unsupported, .read = fs_read_unsupported, .write = fs_write_unsupported, diff --git a/include/ext4l.h b/include/ext4l.h index 6d8eba84f4e..23d53b30d9e 100644 --- a/include/ext4l.h +++ b/include/ext4l.h @@ -38,6 +38,14 @@ void ext4l_close(void); */ int ext4l_ls(const char *dirname); +/** + * ext4l_exists() - Check if a file or directory exists + * + * @filename: Path to check + * Return: 1 if exists, 0 if not + */ +int ext4l_exists(const char *filename); + /** * ext4l_get_uuid() - Get the filesystem UUID * diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c index d9ed21407e7..79813375ff1 100644 --- a/test/fs/ext4l.c +++ b/test/fs/ext4l.c @@ -196,3 +196,30 @@ static int fs_test_ext4l_opendir_norun(struct unit_test_state *uts) } FS_TEST_ARGS(fs_test_ext4l_opendir_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }); + +/** + * fs_test_ext4l_exists_norun() - Test ext4l_exists function + * + * Verifies that ext4l_exists correctly reports file existence. + * + * Arguments: + * fs_image: Path to the ext4 filesystem image + */ +static int fs_test_ext4l_exists_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(EXT4L_ARG_IMAGE); + + 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)); + + /* Test existing directory */ + ut_asserteq(1, ext4l_exists("/")); + + /* Test non-existent paths */ + ut_asserteq(0, ext4l_exists("/no/such/path")); + + return 0; +} +FS_TEST_ARGS(fs_test_ext4l_exists_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 073b02a80ce..890b6aeaef1 100644 --- a/test/py/tests/test_fs/test_ext4l.py +++ b/test/py/tests/test_fs/test_ext4l.py @@ -96,3 +96,10 @@ class TestExt4l: output = ubman.run_command( f'ut -f fs fs_test_ext4l_opendir_norun fs_image={ext4_image}') assert 'failures: 0' in output + + def test_exists(self, ubman, ext4_image): + """Test that ext4l_exists reports file existence correctly.""" + with ubman.log.section('Test ext4l exists'): + output = ubman.run_command( + f'ut -f fs fs_test_ext4l_exists_norun fs_image={ext4_image}') + assert 'failures: 0' in output -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add ext4l_size() function to retrieve the size of a file or directory. Wire it into the filesystem operations table in fs_legacy.c. 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 | 14 ++++++++++++ fs/fs_legacy.c | 2 +- include/ext4l.h | 9 ++++++++ test/fs/ext4l.c | 34 +++++++++++++++++++++++++++++ test/py/tests/test_fs/test_ext4l.py | 7 ++++++ 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index 409a3ff7edc..17648a59077 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -649,6 +649,20 @@ int ext4l_exists(const char *filename) return 1; } +int ext4l_size(const char *filename, loff_t *sizep) +{ + struct inode *inode; + int ret; + + ret = ext4l_resolve_path(filename, &inode); + if (ret) + return ret; + + *sizep = inode->i_size; + + return 0; +} + void ext4l_close(void) { if (ext4l_open_dirs > 0) diff --git a/fs/fs_legacy.c b/fs/fs_legacy.c index 849c0304668..5edc35c4cdb 100644 --- a/fs/fs_legacy.c +++ b/fs/fs_legacy.c @@ -267,7 +267,7 @@ static struct fstype_info fstypes[] = { .close = ext4l_close, .ls = ext4l_ls, .exists = ext4l_exists, - .size = fs_size_unsupported, + .size = ext4l_size, .read = fs_read_unsupported, .write = fs_write_unsupported, .uuid = fs_uuid_unsupported, diff --git a/include/ext4l.h b/include/ext4l.h index 23d53b30d9e..6fee701f335 100644 --- a/include/ext4l.h +++ b/include/ext4l.h @@ -46,6 +46,15 @@ int ext4l_ls(const char *dirname); */ int ext4l_exists(const char *filename); +/** + * ext4l_size() - Get the size of a file + * + * @filename: Path to file + * @sizep: Returns the file size + * Return: 0 on success, negative on error + */ +int ext4l_size(const char *filename, loff_t *sizep); + /** * ext4l_get_uuid() - Get the filesystem UUID * diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c index 79813375ff1..f58a91893cc 100644 --- a/test/fs/ext4l.c +++ b/test/fs/ext4l.c @@ -11,6 +11,7 @@ #include <ext4l.h> #include <fs.h> #include <fs_legacy.h> +#include <linux/sizes.h> #include <u-boot/uuid.h> #include <test/test.h> #include <test/ut.h> @@ -223,3 +224,36 @@ static int fs_test_ext4l_exists_norun(struct unit_test_state *uts) } FS_TEST_ARGS(fs_test_ext4l_exists_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }); + +/** + * fs_test_ext4l_size_norun() - Test ext4l_size function + * + * Verifies that ext4l_size correctly reports file size. + * + * Arguments: + * fs_image: Path to the ext4 filesystem image + */ +static int fs_test_ext4l_size_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(EXT4L_ARG_IMAGE); + loff_t size; + + 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)); + + /* Test root directory size - one block on a 4K block filesystem */ + ut_assertok(ext4l_size("/", &size)); + ut_asserteq(SZ_4K, size); + + /* Test file size - testfile.txt contains "hello world\n" */ + ut_assertok(ext4l_size("/testfile.txt", &size)); + ut_asserteq(12, size); + + /* Test non-existent path returns -ENOENT */ + ut_asserteq(-ENOENT, ext4l_size("/no/such/path", &size)); + + return 0; +} +FS_TEST_ARGS(fs_test_ext4l_size_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 890b6aeaef1..922fa37a7d8 100644 --- a/test/py/tests/test_fs/test_ext4l.py +++ b/test/py/tests/test_fs/test_ext4l.py @@ -103,3 +103,10 @@ class TestExt4l: output = ubman.run_command( f'ut -f fs fs_test_ext4l_exists_norun fs_image={ext4_image}') assert 'failures: 0' in output + + def test_size(self, ubman, ext4_image): + """Test that ext4l_size reports file size correctly.""" + with ubman.log.section('Test ext4l size'): + output = ubman.run_command( + f'ut -f fs fs_test_ext4l_size_norun fs_image={ext4_image}') + assert 'failures: 0' in output -- 2.43.0
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(-) 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 -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The filesystem uuid method is not implemented. Add ext4l_uuid() which returns the filesystem UUID as a string and wire it into the filesystem operations table. Add a test. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/interface.c | 20 ++++++++++++++++++ fs/fs_legacy.c | 2 +- include/ext4l.h | 8 ++++++++ test/fs/ext4l.c | 32 +++++++++++++++++++++++++++++ test/py/tests/test_fs/test_ext4l.py | 7 +++++++ 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index 34e659cd28b..f25664369e6 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -15,6 +15,7 @@ #include <membuf.h> #include <part.h> #include <malloc.h> +#include <u-boot/uuid.h> #include <linux/errno.h> #include <linux/jbd2.h> #include <linux/types.h> @@ -80,6 +81,25 @@ int ext4l_get_uuid(u8 *uuid) return 0; } +/** + * ext4l_uuid() - Get the filesystem UUID as a string + * + * @uuid_str: Buffer to receive the UUID string (must be at least 37 bytes) + * Return: 0 on success, -ENODEV if not mounted + */ +int ext4l_uuid(char *uuid_str) +{ + u8 uuid[16]; + int ret; + + ret = ext4l_get_uuid(uuid); + if (ret) + return ret; + uuid_bin_to_str(uuid, uuid_str, UUID_STR_FORMAT_STD); + + 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 27a2d7be220..71f8e56715f 100644 --- a/fs/fs_legacy.c +++ b/fs/fs_legacy.c @@ -270,7 +270,7 @@ static struct fstype_info fstypes[] = { .size = ext4l_size, .read = ext4l_read, .write = fs_write_unsupported, - .uuid = fs_uuid_unsupported, + .uuid = ext4l_uuid, .opendir = ext4l_opendir, .readdir = ext4l_readdir, .closedir = ext4l_closedir, diff --git a/include/ext4l.h b/include/ext4l.h index 643060ee44c..9d9e79b7695 100644 --- a/include/ext4l.h +++ b/include/ext4l.h @@ -76,6 +76,14 @@ int ext4l_read(const char *filename, void *buf, loff_t offset, loff_t len, */ int ext4l_get_uuid(u8 *uuid); +/** + * ext4l_uuid() - Get the filesystem UUID as a string + * + * @uuid_str: Buffer to receive the UUID string (must be at least 37 bytes) + * Return: 0 on success, -ENODEV if not mounted + */ +int ext4l_uuid(char *uuid_str); + /** * ext4l_opendir() - Open a directory for iteration * diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c index 1bea9186d5a..02ad13ec71d 100644 --- a/test/fs/ext4l.c +++ b/test/fs/ext4l.c @@ -295,3 +295,35 @@ static int fs_test_ext4l_read_norun(struct unit_test_state *uts) } FS_TEST_ARGS(fs_test_ext4l_read_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }); + +/** + * fs_test_ext4l_uuid_norun() - Test ext4l_uuid function + * + * Verifies that ext4l can return the filesystem UUID. + * + * Arguments: + * fs_image: Path to the ext4 filesystem image + */ +static int fs_test_ext4l_uuid_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(EXT4L_ARG_IMAGE); + char uuid_str[UUID_STR_LEN + 1]; + + 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 the UUID string */ + ut_assertok(ext4l_uuid(uuid_str)); + + /* Verify it's a valid UUID format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) */ + ut_asserteq(UUID_STR_LEN, strlen(uuid_str)); + ut_asserteq('-', uuid_str[8]); + ut_asserteq('-', uuid_str[13]); + ut_asserteq('-', uuid_str[18]); + ut_asserteq('-', uuid_str[23]); + + return 0; +} +FS_TEST_ARGS(fs_test_ext4l_uuid_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 4064a6c53ff..2bbbe766e6a 100644 --- a/test/py/tests/test_fs/test_ext4l.py +++ b/test/py/tests/test_fs/test_ext4l.py @@ -117,3 +117,10 @@ class TestExt4l: output = ubman.run_command( f'ut -f fs fs_test_ext4l_read_norun fs_image={ext4_image}') assert 'failures: 0' in output + + def test_uuid(self, ubman, ext4_image): + """Test that ext4l can return the filesystem UUID.""" + with ubman.log.section('Test ext4l uuid'): + output = ubman.run_command( + f'ut -f fs fs_test_ext4l_uuid_norun fs_image={ext4_image}') + assert 'failures: 0' in output -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add infrastructure for obtaining filesystem statistics. This includes: - struct fs_statfs to hold block size, total blocks, and free blocks - statfs method in struct fstype_info - fs_statfs() wrapper function - fs_statfs_unsupported() stub for filesystems without support Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/fs_legacy.c | 37 +++++++++++++++++++++++++++++++++++++ include/fs_legacy.h | 21 +++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/fs/fs_legacy.c b/fs/fs_legacy.c index 71f8e56715f..e8a5f8d9672 100644 --- a/fs/fs_legacy.c +++ b/fs/fs_legacy.c @@ -154,6 +154,11 @@ static inline int fs_rename_unsupported(const char *old_path, return -1; } +static inline int fs_statfs_unsupported(struct fs_statfs *stats) +{ + return -1; +} + struct fstype_info { int fstype; char *name; @@ -195,6 +200,13 @@ struct fstype_info { int (*mkdir)(const char *dirname); int (*ln)(const char *filename, const char *target); int (*rename)(const char *old_path, const char *new_path); + + /* + * Get filesystem statistics. On success return 0 and fill stats + * with block size, total blocks, and free blocks. On error + * return -errno. See fs_statfs(). + */ + int (*statfs)(struct fs_statfs *stats); }; static struct fstype_info fstypes[] = { @@ -228,6 +240,7 @@ static struct fstype_info fstypes[] = { #else .rename = fs_rename_unsupported, #endif + .statfs = fs_statfs_unsupported, }, #endif @@ -256,6 +269,7 @@ static struct fstype_info fstypes[] = { .unlink = fs_unlink_unsupported, .mkdir = fs_mkdir_unsupported, .rename = fs_rename_unsupported, + .statfs = fs_statfs_unsupported, }, #endif #if CONFIG_IS_ENABLED(FS_EXT4L) @@ -278,6 +292,7 @@ static struct fstype_info fstypes[] = { .mkdir = fs_mkdir_unsupported, .ln = fs_ln_unsupported, .rename = fs_rename_unsupported, + .statfs = fs_statfs_unsupported, }, #endif #if IS_ENABLED(CONFIG_SANDBOX) && !IS_ENABLED(CONFIG_XPL_BUILD) @@ -298,6 +313,7 @@ static struct fstype_info fstypes[] = { .mkdir = fs_mkdir_unsupported, .ln = fs_ln_unsupported, .rename = fs_rename_unsupported, + .statfs = fs_statfs_unsupported, }, #endif #if CONFIG_IS_ENABLED(SEMIHOSTING) @@ -318,6 +334,7 @@ static struct fstype_info fstypes[] = { .mkdir = fs_mkdir_unsupported, .ln = fs_ln_unsupported, .rename = fs_rename_unsupported, + .statfs = fs_statfs_unsupported, }, #endif #ifndef CONFIG_XPL_BUILD @@ -339,6 +356,7 @@ static struct fstype_info fstypes[] = { .mkdir = fs_mkdir_unsupported, .ln = fs_ln_unsupported, .rename = fs_rename_unsupported, + .statfs = fs_statfs_unsupported, }, #endif #endif @@ -361,6 +379,7 @@ static struct fstype_info fstypes[] = { .mkdir = fs_mkdir_unsupported, .ln = fs_ln_unsupported, .rename = fs_rename_unsupported, + .statfs = fs_statfs_unsupported, }, #endif #endif @@ -384,6 +403,7 @@ static struct fstype_info fstypes[] = { .unlink = fs_unlink_unsupported, .mkdir = fs_mkdir_unsupported, .rename = fs_rename_unsupported, + .statfs = fs_statfs_unsupported, }, #endif #if IS_ENABLED(CONFIG_FS_EROFS) @@ -406,6 +426,7 @@ static struct fstype_info fstypes[] = { .unlink = fs_unlink_unsupported, .mkdir = fs_mkdir_unsupported, .rename = fs_rename_unsupported, + .statfs = fs_statfs_unsupported, }, #endif #if IS_ENABLED(CONFIG_FS_EXFAT) @@ -428,6 +449,7 @@ static struct fstype_info fstypes[] = { .unlink = exfat_fs_unlink, .mkdir = exfat_fs_mkdir, .rename = exfat_fs_rename, + .statfs = fs_statfs_unsupported, }, #endif #if CONFIG_IS_ENABLED(VIRTIO_FS) @@ -450,6 +472,7 @@ static struct fstype_info fstypes[] = { .unlink = fs_unlink_unsupported, .mkdir = fs_mkdir_unsupported, .rename = fs_rename_unsupported, + .statfs = fs_statfs_unsupported, }, #endif { @@ -469,6 +492,7 @@ static struct fstype_info fstypes[] = { .mkdir = fs_mkdir_unsupported, .ln = fs_ln_unsupported, .rename = fs_rename_unsupported, + .statfs = fs_statfs_unsupported, }, }; @@ -624,6 +648,19 @@ int fs_size(const char *filename, loff_t *size) return ret; } +int fs_statfs(struct fs_statfs *stats) +{ + int ret; + + struct fstype_info *info = fs_get_info(fs_type); + + ret = info->statfs(stats); + + fs_close(); + + return ret; +} + #if CONFIG_IS_ENABLED(LMB) /* Check if a file may be read to the given address */ static int fs_read_lmb_check(const char *filename, ulong addr, loff_t offset, diff --git a/include/fs_legacy.h b/include/fs_legacy.h index f1be6fbdc13..331a15cf7bd 100644 --- a/include/fs_legacy.h +++ b/include/fs_legacy.h @@ -172,6 +172,27 @@ struct fs_dirent *fs_readdir(struct fs_dir_stream *dirs); */ void fs_closedir(struct fs_dir_stream *dirs); +/** + * struct fs_statfs - filesystem statistics + * + * @bsize: block size + * @blocks: total blocks + * @bfree: free blocks + */ +struct fs_statfs { + ulong bsize; + u64 blocks; + u64 bfree; +}; + +/** + * fs_statfs - Get filesystem statistics + * + * @stats: pointer to struct fs_statfs to fill + * Return: 0 on success, -1 on error + */ +int fs_statfs(struct fs_statfs *stats); + /** * fs_unlink - delete a file or directory * -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add the fsinfo command to display filesystem statistics including block size, total blocks, used blocks, and free blocks. Both raw byte counts and human-readable sizes are shown. Example output: => fsinfo mmc 0:1 Block size: 4096 bytes Total blocks: 16384 (67108864 bytes, 64 MiB) Used blocks: 2065 (8458240 bytes, 8.1 MiB) Free blocks: 14319 (58650624 bytes, 55.9 MiB) Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- cmd/Kconfig | 10 ++++++ cmd/fs.c | 14 ++++++++ doc/usage/cmd/fsinfo.rst | 53 +++++++++++++++++++++++++++++ doc/usage/index.rst | 1 + fs/fs_legacy.c | 32 +++++++++++++++++ include/fs_cmd.h | 11 ++++++ test/fs/ext4l.c | 27 +++++++++++++++ test/py/tests/test_fs/test_ext4l.py | 7 ++++ 8 files changed, 155 insertions(+) create mode 100644 doc/usage/cmd/fsinfo.rst diff --git a/cmd/Kconfig b/cmd/Kconfig index 5cb34509746..d602f430ab6 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -2839,6 +2839,16 @@ config CMD_FS_UUID help Enables fsuuid command for filesystem UUID. +config CMD_FSINFO + bool "fsinfo command" + depends on !CMD_JFFS2 + default y if SANDBOX + help + Enables the fsinfo command which prints filesystem statistics + such as block size, total blocks, used blocks and free blocks. + This command conflicts with the JFFS2 fsinfo command, so it + cannot be enabled when JFFS2 support is enabled. + config CMD_LUKS bool "luks command" depends on BLK_LUKS diff --git a/cmd/fs.c b/cmd/fs.c index 7058f17b24f..960fd024c75 100644 --- a/cmd/fs.c +++ b/cmd/fs.c @@ -152,3 +152,17 @@ U_BOOT_CMD( " - renames/moves a file/directory in 'dev' on 'interface' from\n" " 'old_path' to 'new_path'" ); + +#ifdef CONFIG_CMD_FSINFO +static int do_fsinfo_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_fs_statfs(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD(fsinfo, 3, 1, do_fsinfo_wrapper, + "Print filesystem information", + "<interface> <dev[:part]>\n" + " - Print filesystem statistics (block size, total/used/free blocks)" +); +#endif diff --git a/doc/usage/cmd/fsinfo.rst b/doc/usage/cmd/fsinfo.rst new file mode 100644 index 00000000000..5129a9a29b9 --- /dev/null +++ b/doc/usage/cmd/fsinfo.rst @@ -0,0 +1,53 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. index:: + single: fsinfo (command) + +fsinfo command +============== + +Synopsis +-------- + +:: + + fsinfo <interface> <dev[:part]> + +Description +----------- + +The fsinfo command displays filesystem statistics for a partition including +block size, total blocks, used blocks, and free blocks. Both raw byte counts +and human-readable sizes are shown. + +interface + interface for accessing the block device (mmc, sata, scsi, usb, ....) + +dev + device number + +part + partition number, defaults to 1 + +Example +------- + +:: + + => fsinfo mmc 0:1 + Block size: 4096 bytes + Total blocks: 16384 (67108864 bytes, 64 MiB) + Used blocks: 2065 (8458240 bytes, 8.1 MiB) + Free blocks: 14319 (58650624 bytes, 55.9 MiB) + +Configuration +------------- + +The fsinfo command is only available if CONFIG_CMD_FS_GENERIC=y. + +Return value +------------ + +The return value $? is set to 0 (true) if the command succeeded and to 1 +(false) otherwise. If the filesystem does not support statfs, an error +message is displayed. diff --git a/doc/usage/index.rst b/doc/usage/index.rst index bbea78a17b2..6c1414a0b84 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -81,6 +81,7 @@ Shell commands cmd/fdt cmd/font cmd/for + cmd/fsinfo cmd/fwu_mdata cmd/gpio cmd/gpt diff --git a/fs/fs_legacy.c b/fs/fs_legacy.c index e8a5f8d9672..efb5ab669ff 100644 --- a/fs/fs_legacy.c +++ b/fs/fs_legacy.c @@ -1105,6 +1105,38 @@ int do_fs_type(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) return CMD_RET_SUCCESS; } +int do_fs_statfs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct fs_statfs st; + u64 used; + int ret; + + if (argc != 3) + return CMD_RET_USAGE; + + if (fs_set_blk_dev(argv[1], argv[2], FS_TYPE_ANY)) + return 1; + + ret = fs_statfs(&st); + if (ret) { + printf("** Filesystem info not supported **\n"); + return CMD_RET_FAILURE; + } + + used = st.blocks - st.bfree; + printf("Block size: %lu bytes\n", st.bsize); + printf("Total blocks: %llu (%llu bytes, ", st.blocks, + st.blocks * st.bsize); + print_size(st.blocks * st.bsize, ")\n"); + printf("Used blocks: %llu (%llu bytes, ", used, used * st.bsize); + print_size(used * st.bsize, ")\n"); + printf("Free blocks: %llu (%llu bytes, ", st.bfree, + st.bfree * st.bsize); + print_size(st.bfree * st.bsize, ")\n"); + + return 0; +} + int do_rm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[], int fstype) { diff --git a/include/fs_cmd.h b/include/fs_cmd.h index 3b58ad516e7..b28799d5ae4 100644 --- a/include/fs_cmd.h +++ b/include/fs_cmd.h @@ -80,4 +80,15 @@ int do_fs_type(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); */ int do_fs_types(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]); +/** + * do_fs_statfs - Get filesystem statistics. + * + * @cmdtp: Command information for fsinfo + * @flag: Command flags (CMD_FLAG\_...) + * @argc: Number of arguments + * @argv: List of arguments + * Return: result (see enum command_ret_t) + */ +int do_fs_statfs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); + #endif diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c index 02ad13ec71d..2641ac3678c 100644 --- a/test/fs/ext4l.c +++ b/test/fs/ext4l.c @@ -327,3 +327,30 @@ static int fs_test_ext4l_uuid_norun(struct unit_test_state *uts) } FS_TEST_ARGS(fs_test_ext4l_uuid_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL, { "fs_image", UT_ARG_STR }); + +/** + * fs_test_ext4l_fsinfo_norun() - Test fsinfo command + * + * This test verifies that the fsinfo command displays filesystem statistics. + * + * Arguments: + * fs_image: Path to the ext4 filesystem image + */ +static int fs_test_ext4l_fsinfo_norun(struct unit_test_state *uts) +{ + const char *fs_image = ut_str(EXT4L_ARG_IMAGE); + + ut_assertnonnull(fs_image); + ut_assertok(run_commandf("host bind 0 %s", fs_image)); + 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:"); + 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 }); diff --git a/test/py/tests/test_fs/test_ext4l.py b/test/py/tests/test_fs/test_ext4l.py index 2bbbe766e6a..0a9da40f358 100644 --- a/test/py/tests/test_fs/test_ext4l.py +++ b/test/py/tests/test_fs/test_ext4l.py @@ -124,3 +124,10 @@ class TestExt4l: output = ubman.run_command( f'ut -f fs fs_test_ext4l_uuid_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'): + output = ubman.run_command( + f'ut -f fs fs_test_ext4l_fsinfo_norun fs_image={ext4_image}') + assert 'failures: 0' in output -- 2.43.0
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(-) 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'): -- 2.43.0
participants (1)
-
Simon Glass