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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
11
cmd/Kconfig
11
cmd/Kconfig
@@ -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
|
||||
|
||||
14
cmd/fs.c
14
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
|
||||
|
||||
53
doc/usage/cmd/fsinfo.rst
Normal file
53
doc/usage/cmd/fsinfo.rst
Normal 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.
|
||||
@@ -81,6 +81,7 @@ Shell commands
|
||||
cmd/fdt
|
||||
cmd/font
|
||||
cmd/for
|
||||
cmd/fsinfo
|
||||
cmd/fwu_mdata
|
||||
cmd/gpio
|
||||
cmd/gpt
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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--;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
300
test/fs/ext4l.c
300
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>
|
||||
@@ -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 });
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user