Merge branch 'exti' into 'master'

fs: ext4l: Complete read-only filesystem support (Part I)

See merge request u-boot/u-boot!345
This commit is contained in:
Simon Glass
2025-12-27 21:20:01 +00:00
17 changed files with 998 additions and 29 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -2835,9 +2835,20 @@ config CMD_FS_GENERIC
config CMD_FS_UUID
bool "fsuuid command"
default y if SANDBOX
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

View File

@@ -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

53
doc/usage/cmd/fsinfo.rst Normal file
View File

@@ -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.

View File

@@ -81,6 +81,7 @@ Shell commands
cmd/fdt
cmd/font
cmd/for
cmd/fsinfo
cmd/fwu_mdata
cmd/gpio
cmd/gpt

View File

@@ -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)

View File

@@ -11,9 +11,12 @@
#include <blk.h>
#include <env.h>
#include <fs.h>
#include <fs_legacy.h>
#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>
@@ -33,6 +36,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;
@@ -42,6 +48,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 +60,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 +70,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
*/
@@ -73,8 +82,49 @@ 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_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
*
* @blk_dev: Block device descriptor
* @partition: Partition info (can be NULL for whole disk)
*/
@@ -110,6 +160,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 +355,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 +402,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 +414,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 +605,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
@@ -623,9 +678,306 @@ 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;
}
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;
}
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)
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--;
}

View File

@@ -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)
@@ -266,16 +280,19 @@ static struct fstype_info fstypes[] = {
.probe = ext4l_probe,
.close = ext4l_close,
.ls = ext4l_ls,
.exists = fs_exists_unsupported,
.size = fs_size_unsupported,
.read = fs_read_unsupported,
.exists = ext4l_exists,
.size = ext4l_size,
.read = ext4l_read,
.write = fs_write_unsupported,
.uuid = fs_uuid_unsupported,
.opendir = fs_opendir_unsupported,
.uuid = ext4l_uuid,
.opendir = ext4l_opendir,
.readdir = ext4l_readdir,
.closedir = ext4l_closedir,
.unlink = fs_unlink_unsupported,
.mkdir = fs_mkdir_unsupported,
.ln = fs_ln_unsupported,
.rename = fs_rename_unsupported,
.statfs = ext4l_statfs,
},
#endif
#if IS_ENABLED(CONFIG_SANDBOX) && !IS_ENABLED(CONFIG_XPL_BUILD)
@@ -296,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)
@@ -316,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
@@ -337,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
@@ -359,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
@@ -382,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)
@@ -404,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)
@@ -426,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)
@@ -448,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
{
@@ -467,6 +492,7 @@ static struct fstype_info fstypes[] = {
.mkdir = fs_mkdir_unsupported,
.ln = fs_ln_unsupported,
.rename = fs_rename_unsupported,
.statfs = fs_statfs_unsupported,
},
};
@@ -622,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,
@@ -1066,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)
{

View File

@@ -11,6 +11,9 @@
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
@@ -30,16 +33,90 @@ void ext4l_close(void);
/**
* ext4l_ls() - List directory contents
*
* @dirname: Directory path to list
* Return: 0 on success, negative on error
*/
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_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_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
*
* @uuid: Buffer to receive the 16-byte UUID
* Return: 0 on success, -ENODEV if not mounted
*/
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_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
*
* @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__ */

View File

@@ -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

View File

@@ -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
*

View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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>
@@ -68,12 +69,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,11 +103,296 @@ 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;
}
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 });
/**
* 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 });
/**
* 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 });
/**
* 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 });
/**
* 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 });
/**
* fs_test_ext4l_fsinfo_norun() - Test fsinfo command
*
* 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);
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"));
/* 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 });

View File

@@ -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,52 @@ 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
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
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
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
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
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'):
output = ubman.run_command(
f'ut -f fs fs_test_ext4l_fsinfo_norun fs_image={ext4_image}')
assert 'failures: 0' in output

View File

@@ -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)