@@ -2990,7 +2990,7 @@ config CMD_FS_LEGACY
config CMD_VFS
bool "fs - virtual filesystem commands"
depends on VFS
- default y if SANDBOX
+ default VFS
help
Provides the 'fs' command with mount, umount and ls subcommands
for the virtual filesystem layer.
@@ -94,7 +94,7 @@ obj-$(CONFIG_CMD_FPGA) += fpga.o
obj-$(CONFIG_CMD_LUKS) += luks.o
obj-$(CONFIG_CMD_FPGAD) += fpgad.o
obj-$(CONFIG_CMD_FS_LEGACY) += fs_legacy.o
-obj-$(CONFIG_CMD_VFS) += vfs.o
+obj-$(CONFIG_CMD_VFS) += vfs.o fs.o
obj-$(CONFIG_CMD_FUSE) += fuse.o
obj-$(CONFIG_CMD_FWU_METADATA) += fwu_mdata.o
obj-$(CONFIG_CMD_GETTIME) += gettime.o
new file mode 100644
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * VFS-based filesystem commands - mount, umount, ls, load
+ *
+ * These replace the legacy commands in cmd/fs_legacy.c with versions that
+ * use absolute paths through the virtual filesystem layer.
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ */
+
+#include <command.h>
+#include <dm.h>
+#include <env.h>
+#include <file.h>
+#include <fs_legacy.h>
+#include <mapmem.h>
+#include <vfs.h>
+#include <dm/uclass.h>
+
+int do_load(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[],
+ int fstype);
+int do_fs_types(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[]);
+
+static int mount_handler(int argc, char *const argv[])
+{
+ struct udevice *vfs, *fsdev, *dir, *mnt;
+ const char *subpath;
+ int ret;
+
+ vfs = vfs_root();
+ if (!vfs)
+ return -ENXIO;
+
+ if (argc < 2) {
+ vfs_print_mounts();
+ return 0;
+ }
+
+ if (argc < 3)
+ return -EINVAL;
+
+ /* Check if already mounted */
+ ret = vfs_find_mount(vfs, argv[2], &mnt, &subpath);
+ if (!ret && mnt && !*subpath)
+ return -EBUSY;
+
+ ret = uclass_get_device_by_name(UCLASS_FS, argv[1], &fsdev);
+ if (ret)
+ return ret;
+
+ ret = vfs_resolve(vfs, argv[2], &dir);
+ if (ret)
+ return ret;
+
+ return vfs_mount(vfs, dir, fsdev);
+}
+
+static int do_mount(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ int ret;
+
+ ret = mount_handler(argc, argv);
+ if (ret) {
+ printf("mount failed: %dE\n", ret);
+ return CMD_RET_FAILURE;
+ }
+
+ return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD(
+ mount, 3, 1, do_mount,
+ "mount a filesystem",
+ "[<dev> <mountpoint>]\n"
+ " - With no args, list all mounts\n"
+ " - Mount device 'dev' at 'mountpoint'"
+);
+
+static int umount_handler(const char *path)
+{
+ struct udevice *vfs;
+
+ vfs = vfs_root();
+ if (!vfs)
+ return -ENXIO;
+
+ return vfs_umount_path(vfs, path);
+}
+
+static int do_umount(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ int ret;
+
+ if (argc < 2)
+ return CMD_RET_USAGE;
+
+ ret = umount_handler(argv[1]);
+ if (ret) {
+ printf("Error: %dE\n", ret);
+ return CMD_RET_FAILURE;
+ }
+
+ return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD(
+ umount, 2, 1, do_umount,
+ "unmount a filesystem",
+ "<mountpoint>\n"
+ " - Unmount the filesystem at 'mountpoint'"
+);
+
+static int do_cd(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ const char *path = argc >= 2 ? argv[1] : "/";
+ int ret;
+
+ ret = vfs_chdir(path);
+ if (ret) {
+ printf("Error: %dE\n", ret);
+ return CMD_RET_FAILURE;
+ }
+
+ return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD(
+ cd, 2, 1, do_cd,
+ "change working directory",
+ "[<path>]\n"
+ " - Change to 'path' in the VFS (default /)"
+);
+
+static int do_pwd(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ printf("%s\n", vfs_getcwd());
+
+ return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD(
+ pwd, 1, 1, do_pwd,
+ "print working directory",
+ "\n - Print the current VFS working directory"
+);
+
+static int do_ls(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ const char *path = argc >= 2 ? argv[1] : NULL;
+ int ret;
+
+ ret = vfs_ls(path);
+ if (ret) {
+ printf("Error: %dE\n", ret);
+ return CMD_RET_FAILURE;
+ }
+
+ return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD(
+ ls, 2, 1, do_ls,
+ "list files in a directory (default cwd)",
+ "[<path>]\n"
+ " - List files at 'path' in the VFS (default cwd)"
+);
+
+
+static int do_vfs_load(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct file_uc_priv *uc_priv;
+ ulong addr, bytes = 0;
+ struct udevice *fil;
+ long len_read;
+ loff_t pos = 0;
+ void *buf;
+ int ret;
+
+ if (argc < 3)
+ return CMD_RET_USAGE;
+
+ addr = hextoul(argv[1], NULL);
+ if (argc >= 4)
+ bytes = hextoul(argv[3], NULL);
+ if (argc >= 5)
+ pos = hextoull(argv[4], NULL);
+
+ ret = vfs_open_file(argv[2], DIR_O_RDONLY, &fil);
+ if (ret) {
+ printf("Error: %dE\n", ret);
+ return CMD_RET_FAILURE;
+ }
+
+ uc_priv = dev_get_uclass_priv(fil);
+ if (!bytes)
+ bytes = uc_priv->size - pos;
+
+ buf = map_sysmem(addr, bytes);
+ len_read = file_read_at(fil, buf, pos, bytes);
+ unmap_sysmem(buf);
+
+ if (len_read < 0) {
+ printf("Read failed: %ldE\n", len_read);
+ return CMD_RET_FAILURE;
+ }
+
+ env_set_hex("fileaddr", addr);
+ env_set_hex("filesize", len_read);
+
+ printf("%ld bytes read\n", len_read);
+
+ return CMD_RET_SUCCESS;
+}
+
+static int do_load_vfs(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ char *endp;
+
+ /*
+ * Detect legacy syntax: load <interface> [<dev[:part]> ...]
+ * If argv[1] is not a pure hex number, assume legacy syntax.
+ */
+ if (argc >= 2) {
+ hextoul(argv[1], &endp);
+ if (*endp)
+ return do_load(cmdtp, flag, argc, argv, FS_TYPE_ANY);
+ }
+
+ return do_vfs_load(cmdtp, flag, argc, argv);
+}
+
+static int do_fstypes(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ return do_fs_types(cmdtp, flag, argc, argv);
+}
+
+U_BOOT_CMD(
+ fstypes, 1, 1, do_fstypes,
+ "List supported filesystem types", ""
+);
+
+U_BOOT_CMD(
+ load, 7, 0, do_load_vfs,
+ "load binary file from a filesystem",
+ "<addr> <path> [bytes [pos]]\n"
+ " - Load binary file from 'path' in the VFS to address 'addr'.\n"
+ " 'bytes' gives the size to load in bytes.\n"
+ " If 'bytes' is 0 or omitted, the file is read until the end.\n"
+ " 'pos' gives the file byte position to start reading from.\n"
+ " If 'pos' is 0 or omitted, the file is read from the start.\n"
+ "load <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]\n"
+ " - Legacy: load from block device interface"
+);
@@ -46,6 +46,21 @@ int fs_split_path(const char *fname, char **subdirp, const char **leafp)
return 0;
}
+void fs_split_path_inplace(char *fname, const char **dirp, const char **leafp)
+{
+ char *last_slash;
+
+ last_slash = strrchr(fname, '/');
+ if (last_slash) {
+ *leafp = last_slash + 1;
+ *last_slash = '\0';
+ *dirp = fname;
+ } else {
+ *leafp = fname;
+ *dirp = "";
+ }
+}
+
int fs_lookup_dir(struct udevice *dev, const char *path, struct udevice **dirp)
{
struct fs_ops *ops = fs_get_ops(dev);
@@ -408,8 +408,57 @@ static int vfs_resolve_mount(const char *path, char *resolved, int size,
return vfs_find_mount(vfs, path, mntp, subpathp);
}
-int vfs_resolve(struct udevice *vfs, const char *path,
- struct udevice **dirp)
+/**
+ * vfs_resolve_dir() - Resolve a path to its parent directory and leaf name
+ *
+ * Resolves the mount, splits the subpath into directory and leaf, and
+ * looks up the directory device.
+ *
+ * @path: Absolute or relative VFS path
+ * @dirp: Returns the UCLASS_DIR device for the parent directory
+ * @leafp: Returns allocated copy of the leaf filename (caller must free)
+ * Return: 0 if OK, -ve on error
+ */
+static int vfs_resolve_dir(const char *path, struct udevice **dirp,
+ char **leafp)
+{
+ char resolved[FILE_MAX_PATH_LEN];
+ struct udevice *mnt;
+ struct vfsmount *mnt_priv;
+ const char *subpath, *dirpart, *leaf;
+ char *sub;
+ int ret;
+
+ ret = vfs_resolve_mount(path, resolved, sizeof(resolved),
+ &mnt, &subpath);
+ if (ret)
+ return log_msg_ret("vdm", ret);
+
+ if (!mnt)
+ return log_msg_ret("vdr", -ENOENT);
+
+ mnt_priv = dev_get_uclass_priv(mnt);
+
+ /*
+ * Split in place - subpath points into resolved[], so we can
+ * modify it. After the split, dirpart is the directory portion
+ * and leaf points to the leaf filename.
+ */
+ sub = (char *)subpath;
+ fs_split_path_inplace(sub, &dirpart, &leaf);
+
+ ret = fs_lookup_dir(mnt_priv->target, dirpart, dirp);
+ if (ret)
+ return log_msg_ret("vdd", ret);
+
+ *leafp = strdup(leaf);
+ if (!*leafp)
+ return log_msg_ret("vdl", -ENOMEM);
+
+ return 0;
+}
+
+int vfs_resolve(struct udevice *vfs, const char *path, struct udevice **dirp)
{
struct udevice *cur_fs, *best;
const char *remain;
@@ -501,34 +550,18 @@ void vfs_print_mounts(void)
int vfs_open_file(const char *path, enum dir_open_flags_t oflags,
struct udevice **filp)
{
- struct udevice *vfs, *mnt, *dir;
- const char *subpath, *leaf;
- struct vfsmount *mnt_priv;
- char *dirpath;
+ struct udevice *dir;
+ char *leaf;
int ret;
- vfs = vfs_root();
- if (!vfs)
- return log_msg_ret("voi", -ENXIO);
-
- ret = vfs_find_mount(vfs, path, &mnt, &subpath);
- if (ret)
- return log_msg_ret("vom", ret);
-
- mnt_priv = dev_get_uclass_priv(mnt);
-
- ret = fs_split_path(subpath, &dirpath, &leaf);
- if (ret)
- return log_msg_ret("vos", ret);
-
- ret = fs_lookup_dir(mnt_priv->target, dirpath, &dir);
- free(dirpath);
+ ret = vfs_resolve_dir(path, &dir, &leaf);
if (ret)
- return log_msg_ret("vod", ret);
+ return log_msg_ret("vof", ret);
ret = dir_open_file(dir, leaf, oflags, filp);
+ free(leaf);
if (ret)
- return log_msg_ret("vof", ret);
+ return log_msg_ret("voo", ret);
return 0;
}
@@ -122,4 +122,18 @@ int fs_lookup_dir(struct udevice *dev, const char *path, struct udevice **dirp);
*/
int fs_split_path(const char *fname, char **subdirp, const char **leafp);
+/**
+ * fs_split_path_inplace() - Split a path into directory and leaf in place
+ *
+ * Modifies @fname by null-terminating at the last '/'. Sets @dirp to
+ * point to the directory part and @leafp to the leaf. If there is no
+ * '/', @dirp is set to "" and @leafp points to @fname unchanged.
+ *
+ * @fname: Path to split (modified in place when it contains '/')
+ * @dirp: Returns pointer to the directory part
+ * @leafp: Returns pointer to the leaf filename
+ */
+void fs_split_path_inplace(char *fname, const char **dirp,
+ const char **leafp);
+
#endif
@@ -16,7 +16,8 @@ from fstest_defs import SMALL_FILE, BIG_FILE
from fstest_helpers import assert_fs_integrity
-@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('sandbox')
+@pytest.mark.boardspec('!sandbox')
@pytest.mark.slow
class TestFsBasic:
"""Test basic filesystem operations via C unit tests."""
@@ -26,7 +26,8 @@ def str2fat(long_filename):
name = '%s~1' % name[:6]
return '%-8s %s' % (name, ext)
-@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('sandbox')
+@pytest.mark.boardspec('!sandbox')
@pytest.mark.slow
class TestFsExt(object):
def test_fs_ext1(self, ubman, fs_obj_ext):
@@ -15,7 +15,8 @@ from tempfile import NamedTemporaryFile
import pytest
-@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('sandbox')
+@pytest.mark.buildconfigspec('fs_ext4l')
class TestExt4l:
"""Test ext4l filesystem operations."""
@@ -79,6 +80,7 @@ class TestExt4l:
with ubman.log.section('Test ext4l msgs'):
ubman.run_ut('fs', 'fs_test_ext4l_msgs', fs_image=ext4_image)
+ @pytest.mark.boardspec('!sandbox')
def test_ls(self, ubman, ext4_image):
"""Test that ext4l can list directory contents."""
with ubman.log.section('Test ext4l ls'):
@@ -114,6 +116,7 @@ class TestExt4l:
with ubman.log.section('Test ext4l statfs'):
ubman.run_ut('fs', 'fs_test_ext4l_statfs', fs_image=ext4_image)
+ @pytest.mark.boardspec('!sandbox')
def test_fsinfo(self, ubman, ext4_image):
"""Test that fsinfo command displays filesystem statistics."""
with ubman.log.section('Test ext4l fsinfo'):
@@ -4,7 +4,8 @@
import pytest
-@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('sandbox')
+@pytest.mark.boardspec('!sandbox')
@pytest.mark.buildconfigspec('cmd_fs_generic')
def test_fstypes(ubman):
"""Test that `fstypes` prints a result which includes `sandbox`."""
@@ -11,7 +11,8 @@ This test verifies fat specific file system behaviour.
import pytest
import re
-@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('sandbox')
+@pytest.mark.boardspec('!sandbox')
@pytest.mark.slow
class TestFsFat(object):
def test_fs_fat1(self, ubman, fs_obj_fat):