[Concept,23/34] vfs: Add mkdir, rm, mv, df, symlink, ln and umount-all

Message ID 20260403140523.1998228-24-sjg@u-boot.org
State New
Headers
Series Add a virtual filesystem (VFS) layer to U-Boot |

Commit Message

Simon Glass April 3, 2026, 2:04 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

Add VFS operations for directory and file manipulation:
- vfs_mkdir() for creating directories
- vfs_unlink() for file deletion
- vfs_rename() for rename/move (same-mount only)
- vfs_statfs() and vfs_print_df() for filesystem usage
- vfs_readlink() for reading symbolic links
- vfs_ln() for creating symbolic links
- vfs_umount_all() for unmounting all filesystems

Add corresponding fs_ops (mkdir, unlink, rename, statfs, readlink, ln)
and fs_do_*() wrappers in fs-uclass.c. Implement in ext4l where
applicable.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 fs/fs-uclass.c         |  61 ++++++++++++
 fs/sandbox/sandboxfs.c |  33 +++++++
 fs/vfs.c               | 208 ++++++++++++++++++++++++++++++++++++++++-
 include/fs.h           | 122 ++++++++++++++++++++++++
 include/vfs.h          |  17 ++++
 5 files changed, 437 insertions(+), 4 deletions(-)
  

Patch

diff --git a/fs/fs-uclass.c b/fs/fs-uclass.c
index 435fd32bc75..7338a266da9 100644
--- a/fs/fs-uclass.c
+++ b/fs/fs-uclass.c
@@ -115,6 +115,67 @@  int fs_mount(struct udevice *dev)
 	return 0;
 }
 
+int fs_do_ln(struct udevice *dev, const char *path, const char *target)
+{
+	struct fs_ops *ops = fs_get_ops(dev);
+
+	if (!ops->ln)
+		return log_msg_ret("fln", -ENOSYS);
+
+	return ops->ln(dev, path, target);
+}
+
+int fs_do_rename(struct udevice *dev, const char *old_path,
+		 const char *new_path)
+{
+	struct fs_ops *ops = fs_get_ops(dev);
+
+	if (!ops->rename)
+		return log_msg_ret("frn", -ENOSYS);
+
+	return ops->rename(dev, old_path, new_path);
+}
+
+int fs_readlink(struct udevice *dev, const char *path, char *buf, int size)
+{
+	struct fs_ops *ops = fs_get_ops(dev);
+
+	if (!ops->readlink)
+		return log_msg_ret("frl", -ENOSYS);
+
+	return ops->readlink(dev, path, buf, size);
+}
+
+int fs_do_statfs(struct udevice *dev, struct fs_statfs *stats)
+{
+	struct fs_ops *ops = fs_get_ops(dev);
+
+	if (!ops->statfs)
+		return log_msg_ret("fss", -ENOSYS);
+
+	return ops->statfs(dev, stats);
+}
+
+int fs_do_unlink(struct udevice *dev, const char *path)
+{
+	struct fs_ops *ops = fs_get_ops(dev);
+
+	if (!ops->unlink)
+		return log_msg_ret("fsu", -ENOSYS);
+
+	return ops->unlink(dev, path);
+}
+
+int fs_do_mkdir(struct udevice *dev, const char *path)
+{
+	struct fs_ops *ops = fs_get_ops(dev);
+
+	if (!ops->mkdir)
+		return log_msg_ret("fsm", -ENOSYS);
+
+	return ops->mkdir(dev, path);
+}
+
 int fs_unmount(struct udevice *dev)
 {
 	struct fs_ops *ops = fs_get_ops(dev);
diff --git a/fs/sandbox/sandboxfs.c b/fs/sandbox/sandboxfs.c
index 70fb829ae67..87eec0b2acd 100644
--- a/fs/sandbox/sandboxfs.c
+++ b/fs/sandbox/sandboxfs.c
@@ -417,6 +417,34 @@  static int sandbox_fs_lookup_dir(struct udevice *dev, const char *path,
 	return 0;
 }
 
+static int sandbox_fs_ln(struct udevice *dev, const char *path,
+			 const char *target)
+{
+	return os_symlink(target, path);
+}
+
+static int sandbox_fs_rename(struct udevice *dev, const char *old_path,
+			     const char *new_path)
+{
+	return os_rename(old_path, new_path);
+}
+
+static int sandbox_fs_readlink(struct udevice *dev, const char *path,
+			       char *buf, int size)
+{
+	return os_readlink(path, buf, size);
+}
+
+static int sandbox_fs_unlink(struct udevice *dev, const char *path)
+{
+	return os_unlink(path);
+}
+
+static int sandbox_fs_mkdir(struct udevice *dev, const char *path)
+{
+	return os_mkdir(path, 0755);
+}
+
 static int sandbox_fs_remove(struct udevice *dev)
 {
 	return 0;
@@ -426,6 +454,11 @@  static const struct fs_ops sandbox_fs_ops = {
 	.mount		= sandbox_fs_mount,
 	.unmount	= sandbox_fs_unmount,
 	.lookup_dir	= sandbox_fs_lookup_dir,
+	.ln		= sandbox_fs_ln,
+	.rename		= sandbox_fs_rename,
+	.readlink	= sandbox_fs_readlink,
+	.unlink		= sandbox_fs_unlink,
+	.mkdir		= sandbox_fs_mkdir,
 };
 
 static const struct udevice_id sandbox_fs_ids[] = {
diff --git a/fs/vfs.c b/fs/vfs.c
index 328b1076280..2e781e00d7e 100644
--- a/fs/vfs.c
+++ b/fs/vfs.c
@@ -19,6 +19,7 @@ 
 #include <file.h>
 #include <fs.h>
 #include <fs_common.h>
+#include <fs_legacy.h>
 #include <malloc.h>
 #include <part.h>
 #include <vfs.h>
@@ -431,8 +432,8 @@  static int vfs_resolve_dir(const char *path, struct udevice **dirp,
 	char *sub;
 	int ret;
 
-	ret = vfs_resolve_mount(path, resolved, sizeof(resolved),
-				&mnt, &subpath);
+	ret = vfs_resolve_mount(path, resolved,
+				sizeof(resolved), &mnt, &subpath);
 	if (ret)
 		return log_msg_ret("vdm", ret);
 
@@ -536,6 +537,36 @@  bool vfs_is_mount_point(struct udevice *dir)
 	return !find_mount(dir, &mnt);
 }
 
+int vfs_umount_all(void)
+{
+	struct udevice *dev, *next;
+	struct uclass *uc;
+	int ret, err = 0;
+
+	ret = uclass_get(UCLASS_MOUNT, &uc);
+	if (ret)
+		return ret;
+
+	uclass_foreach_dev_safe(dev, next, uc) {
+		struct vfsmount *mnt = dev_get_uclass_priv(dev);
+
+		if (!device_active(dev))
+			continue;
+
+		ret = fs_unmount(mnt->target);
+		if (ret && ret != -ENOTCONN) {
+			err = ret;
+			continue;
+		}
+
+		ret = fs_mount_uninit(dev);
+		if (ret)
+			err = ret;
+	}
+
+	return err;
+}
+
 void vfs_print_mounts(void)
 {
 	struct vfsmount *mnt;
@@ -568,6 +599,175 @@  int vfs_open_file(const char *path, enum dir_open_flags_t oflags,
 	return 0;
 }
 
+void vfs_print_df(void)
+{
+	struct vfsmount *mnt;
+	struct udevice *dev;
+
+	vfs_foreach_mount(mnt, dev) {
+		char path[FILE_MAX_PATH_LEN];
+		struct fs_statfs stats;
+		int ret;
+
+		if (vfs_mount_path(dev, path, sizeof(path)))
+			continue;
+
+		ret = fs_do_statfs(mnt->target, &stats);
+		if (!ret) {
+			u64 used = stats.blocks - stats.bfree;
+
+			printf("%-16s %6lu %10llu %10llu %10llu\n", path,
+			       stats.bsize, stats.blocks * stats.bsize,
+			       used * stats.bsize,
+			       stats.bfree * stats.bsize);
+		} else {
+			printf("%-16s %6s %10s %10s %10s\n", path,
+			       "-", "-", "-", "-");
+		}
+	}
+}
+
+int vfs_ln(const char *path, const char *target)
+{
+	char resolved[FILE_MAX_PATH_LEN];
+	struct udevice *vfs, *mnt;
+	const char *subpath;
+	struct vfsmount *mnt_priv;
+	int ret;
+
+	vfs = vfs_root();
+	if (!vfs)
+		return log_msg_ret("vli", -ENXIO);
+
+	path = vfs_path_resolve(vfs_getcwd(), path, resolved, sizeof(resolved));
+
+	if (!path)
+		return log_msg_ret("vrp", -ENAMETOOLONG);
+	ret = vfs_find_mount(vfs, path, &mnt, &subpath);
+	if (ret)
+		return log_msg_ret("vlm", ret);
+
+	mnt_priv = dev_get_uclass_priv(mnt);
+
+	return fs_do_ln(mnt_priv->target, subpath, target);
+}
+
+int vfs_rename(const char *old_path, const char *new_path)
+{
+	char resolved_old[FILE_MAX_PATH_LEN];
+	char resolved_new[FILE_MAX_PATH_LEN];
+	struct udevice *mnt_old, *mnt_new;
+	const char *sub_old, *sub_new;
+	struct vfsmount *mnt_priv;
+	int ret;
+
+	ret = vfs_resolve_mount(old_path, resolved_old,
+				sizeof(resolved_old),
+				&mnt_old, &sub_old);
+	if (ret)
+		return log_msg_ret("vro", ret);
+
+	ret = vfs_resolve_mount(new_path, resolved_new,
+				sizeof(resolved_new),
+				&mnt_new, &sub_new);
+	if (ret)
+		return log_msg_ret("vrn", ret);
+
+	/* Both paths must be on the same mount */
+	if (mnt_old != mnt_new)
+		return log_msg_ret("vrx", -EXDEV);
+
+	mnt_priv = dev_get_uclass_priv(mnt_old);
+
+	return fs_do_rename(mnt_priv->target, sub_old, sub_new);
+}
+
+int vfs_readlink(const char *path, char *buf, int size)
+{
+	char resolved[FILE_MAX_PATH_LEN];
+	struct udevice *mnt;
+	struct vfsmount *mnt_priv;
+	const char *subpath;
+	int ret;
+
+	ret = vfs_resolve_mount(path, resolved,
+				sizeof(resolved), &mnt, &subpath);
+	if (ret)
+		return log_msg_ret("rlm", ret);
+
+	mnt_priv = dev_get_uclass_priv(mnt);
+
+	return fs_readlink(mnt_priv->target, subpath, buf, size);
+}
+
+int vfs_statfs(const char *path, struct fs_statfs *stats)
+{
+	char resolved[FILE_MAX_PATH_LEN];
+	struct udevice *mnt;
+	struct vfsmount *mnt_priv;
+	const char *subpath;
+	int ret;
+
+	ret = vfs_resolve_mount(path, resolved,
+				sizeof(resolved), &mnt, &subpath);
+	if (ret)
+		return log_msg_ret("dfm", ret);
+
+	mnt_priv = dev_get_uclass_priv(mnt);
+
+	return fs_do_statfs(mnt_priv->target, stats);
+}
+
+int vfs_unlink(const char *path)
+{
+	char resolved[FILE_MAX_PATH_LEN];
+	struct udevice *vfs, *mnt;
+	struct vfsmount *mnt_priv;
+	const char *subpath;
+	int ret;
+
+	vfs = vfs_root();
+	if (!vfs)
+		return log_msg_ret("vui", -ENXIO);
+
+	path = vfs_path_resolve(vfs_getcwd(), path, resolved, sizeof(resolved));
+	if (!path)
+		return log_msg_ret("vup", -ENAMETOOLONG);
+
+	ret = vfs_find_mount(vfs, path, &mnt, &subpath);
+	if (ret)
+		return log_msg_ret("vum", ret);
+
+	mnt_priv = dev_get_uclass_priv(mnt);
+
+	return fs_do_unlink(mnt_priv->target, subpath);
+}
+
+int vfs_mkdir(const char *path)
+{
+	char resolved[FILE_MAX_PATH_LEN];
+	struct udevice *vfs, *mnt;
+	struct vfsmount *mnt_priv;
+	const char *subpath;
+	int ret;
+
+	vfs = vfs_root();
+	if (!vfs)
+		return log_msg_ret("vmi", -ENXIO);
+
+	path = vfs_path_resolve(vfs_getcwd(), path, resolved, sizeof(resolved));
+	if (!path)
+		return log_msg_ret("vmp", -ENAMETOOLONG);
+
+	ret = vfs_find_mount(vfs, path, &mnt, &subpath);
+	if (ret)
+		return log_msg_ret("vmm", ret);
+
+	mnt_priv = dev_get_uclass_priv(mnt);
+
+	return fs_do_mkdir(mnt_priv->target, subpath);
+}
+
 int vfs_stat(const char *path, struct fs_dirent *dent)
 {
 	struct fs_dir_stream *strm;
@@ -612,8 +812,8 @@  int vfs_ls(const char *path)
 	bool empty = true;
 	int ret;
 
-	ret = vfs_resolve_mount(path, resolved, sizeof(resolved),
-				&mnt, &subpath);
+	ret = vfs_resolve_mount(path, resolved,
+				sizeof(resolved), &mnt, &subpath);
 	if (ret)
 		return ret;
 
diff --git a/include/fs.h b/include/fs.h
index 925f810902b..74cfd6af831 100644
--- a/include/fs.h
+++ b/include/fs.h
@@ -14,6 +14,7 @@ 
 #include <fs_common.h>
 #include <part.h>
 
+struct fs_statfs;
 struct udevice;
 
 enum {
@@ -79,6 +80,66 @@  struct fs_ops {
 	 */
 	int (*lookup_dir)(struct udevice *dev, const char *path,
 			  struct udevice **dirp);
+
+	/**
+	 * rename() - Rename or move a file or directory
+	 *
+	 * @dev: Filesystem device
+	 * @old_path: Current path
+	 * @new_path: New path
+	 * Return 0 if OK, -ve on error
+	 */
+	int (*rename)(struct udevice *dev, const char *old_path,
+		      const char *new_path);
+
+	/**
+	 * ln() - Create a symbolic link
+	 *
+	 * @dev: Filesystem device
+	 * @path: Path of symlink to create
+	 * @target: Target the symlink points to
+	 * Return 0 if OK, -ve on error
+	 */
+	int (*ln)(struct udevice *dev, const char *path, const char *target);
+
+	/**
+	 * readlink() - Read the target of a symbolic link
+	 *
+	 * @dev: Filesystem device
+	 * @path: Path to the symbolic link
+	 * @buf: Buffer to receive the target path
+	 * @size: Size of buffer
+	 * Return: length of target string, or -ve on error
+	 */
+	int (*readlink)(struct udevice *dev, const char *path, char *buf,
+			int size);
+
+	/**
+	 * statfs() - Get filesystem statistics
+	 *
+	 * @dev: Filesystem device
+	 * @stats: Returns filesystem statistics
+	 * Return 0 if OK, -ve on error
+	 */
+	int (*statfs)(struct udevice *dev, struct fs_statfs *stats);
+
+	/**
+	 * unlink() - Delete a file
+	 *
+	 * @dev: Filesystem device
+	 * @path: Path of the file to delete
+	 * Return 0 if OK, -ve on error
+	 */
+	int (*unlink)(struct udevice *dev, const char *path);
+
+	/**
+	 * mkdir() - Create a directory
+	 *
+	 * @dev: Filesystem device
+	 * @path: Path of the directory to create
+	 * Return 0 if OK, -ve on error
+	 */
+	int (*mkdir)(struct udevice *dev, const char *path);
 };
 
 /* Get access to a filesystem's operations */
@@ -112,6 +173,67 @@  int fs_unmount(struct udevice *dev);
  */
 int fs_lookup_dir(struct udevice *dev, const char *path, struct udevice **dirp);
 
+/**
+ * fs_do_ln() - Create a symbolic link on a filesystem
+ *
+ * @dev: Filesystem device
+ * @path: Path of symlink to create (within the filesystem)
+ * @target: Target the symlink points to
+ * Return: 0 if OK, -ENOSYS if not supported, other -ve on error
+ */
+int fs_do_ln(struct udevice *dev, const char *path, const char *target);
+
+/**
+ * fs_do_rename() - Rename or move a file on a filesystem
+ *
+ * Both paths must be within the same filesystem.
+ *
+ * @dev: Filesystem device
+ * @old_path: Current path (within the filesystem)
+ * @new_path: New path (within the filesystem)
+ * Return: 0 if OK, -ENOSYS if not supported, other -ve on error
+ */
+int fs_do_rename(struct udevice *dev, const char *old_path,
+		 const char *new_path);
+
+/**
+ * fs_readlink() - Read a symbolic link target on a filesystem
+ *
+ * @dev: Filesystem device
+ * @path: Path to the symbolic link (within the filesystem)
+ * @buf: Buffer to receive the target path
+ * @size: Size of buffer
+ * Return: length of target, -ENOSYS if not supported, other -ve on error
+ */
+int fs_readlink(struct udevice *dev, const char *path, char *buf, int size);
+
+/**
+ * fs_do_statfs() - Get filesystem statistics
+ *
+ * @dev: Filesystem device
+ * @stats: Returns filesystem statistics
+ * Return: 0 if OK, -ENOSYS if not supported, other -ve on error
+ */
+int fs_do_statfs(struct udevice *dev, struct fs_statfs *stats);
+
+/**
+ * fs_do_unlink() - Delete a file on a filesystem
+ *
+ * @dev: Filesystem device
+ * @path: Path of the file to delete (within the filesystem)
+ * Return: 0 if OK, -ENOSYS if not supported, other -ve on error
+ */
+int fs_do_unlink(struct udevice *dev, const char *path);
+
+/**
+ * fs_do_mkdir() - Create a directory on a filesystem
+ *
+ * @dev: Filesystem device
+ * @path: Path of the directory to create (within the filesystem)
+ * Return: 0 if OK, -ENOSYS if not supported, other -ve on error
+ */
+int fs_do_mkdir(struct udevice *dev, const char *path);
+
 /**
  * fs_split_path() - Get a list of subdirs in a filename
  *
diff --git a/include/vfs.h b/include/vfs.h
index 04cbd5d0ca9..9f82bc52bf9 100644
--- a/include/vfs.h
+++ b/include/vfs.h
@@ -131,6 +131,15 @@  int vfs_umount(struct udevice *mnt_dev);
  */
 int vfs_umount_path(struct udevice *vfs, const char *path);
 
+/**
+ * vfs_umount_all() - Unmount all filesystems
+ *
+ * Unmounts all mounted filesystems. Returns an error if any unmount fails.
+ *
+ * Return: 0 if OK, -ve on error
+ */
+int vfs_umount_all(void);
+
 /**
  * vfs_find_mount() - Find the mount covering a path
  *
@@ -292,6 +301,14 @@  int fs_mount_blkdev(const char *type, struct blk_desc *desc, int part_num,
  */
 void vfs_print_mounts(void);
 
+/**
+ * vfs_print_df() - Print filesystem usage for all mounts
+ *
+ * Iterates all mounts and prints statfs info for those that support it.
+ * Mounts that do not support statfs are listed with dashes.
+ */
+void vfs_print_df(void);
+
 /**
  * do_cp() - Copy a file within the VFS
  *