From patchwork Fri Apr 3 14:04:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2093 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775225164; bh=ZWRtrY2l6xsCEQUlhhCuCAS4oCEZ1eMtLYBA9Fetxtc=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=OhJyCX2i22x5+HM8RC5ad1ief5hOrS4pQBY65GzufbLgB/YzftTTCNyfC3Q1RbTmt 3oCRlyuyj1Jxjayr8GtaTUimkLTvcf3FERJTJMoRnkck2fZeAe46Y28ftbELVtE1P6 4cwhZWZtNeIVm1E8Z76WT/UzGfdE8yKMM+k6AH5L/3ouEG7+cEofC15qZjpTjVea04 jbVd1IKirIseUJRTHfRyWZ9aPNji/BcMXgXn+kgMYc7n65tepX7VDx/1NhRiwieLG7 qXdXd+hzkAUvf7lnLdCNF1D8Kc/AFaga4F7g7eLKuowltDD8Mo/AbOUoTP4d19etxz fhcu83oaerpKA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6BE436A347 for ; Fri, 3 Apr 2026 08:06:04 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id nSc_20Tjhw37 for ; Fri, 3 Apr 2026 08:06:04 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775225160; bh=ZWRtrY2l6xsCEQUlhhCuCAS4oCEZ1eMtLYBA9Fetxtc=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=FrM/wFoXXyMy/26Ac3VwXU9nWxy85MCD18ddtxReUGFcbaLMRDtr4BB1Oi/K1xGvm fzIA9ebctaLAHmPbFfZmEJCWbGYqE96j6gzH6Z/5YUKLhwaSnu1jCbV3Qg870tnmBI P/99ev6JIz5j6FZSuOzCJX0SiPYwgy1nf7oASaFudOli4vSxjMsB7fULhoEW+Ogrg4 E6b++dol419ly7HfXSH1A29rf4EXUdjRsaDVC49qi9klPHMin8Z2D4Xa+Qofr/gBvT Nf2Wp9R4NyQ08yl3S0tCxwhTYysDRGQEN3LCoIizJNAhDu64kCGxllCG+83lRXKUDE r1PhCnFuRgLXQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D73B76A35D for ; Fri, 3 Apr 2026 08:06:00 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775225157; bh=I5zr9bw0KA9nP4iZU1hSQSqtlnX7M3lvnTPVhabKZDU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kjJIWP9Htz6uipoy50nQ4ruJNBgz/xjtOXhczTxon15xSq+Z7Pt1CvnNsPL56YFWe Kok2xedED3vWcaBKUGJIFsV4gqAzghky4vAtbuQI6MZTgxh7GDO/vOeptpWx+M+5Gd NxmsdHXJKSBCLiOBssCpIC6kV71ZabKLblvT1uGFWNaG5TBe4xgt/T+C1ikf3Kvf6p goqtNzoCpKnEfTsWyVb3SvDYtih9/Q7lhYc32cVepoaWIE3pYIROtegLY/gACTv1oL aSBiNQPyhtdGikSoY6Y55NmtptBYORyuRP/a9x5awpG8Y3PdfQkzjMs2hWFjV2DsYf nBK/XArQgoc2w== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B0F196A34C; Fri, 3 Apr 2026 08:05:57 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id Uh51TVW_VEUp; Fri, 3 Apr 2026 08:05:57 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775225156; bh=RwSZ1I908Kmmuz+342qRnyH2Z2hTq3YabUNzFzTBvNE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kJRvkFmmNmT2CbFqHd6DnIIkyApzTXRyHcULPD32trtYwKU09Uo1vW+fL6VU5Ka13 NEd3fBoiIyZjLapnDP/sds9e1r0Xof+rRtu/+KrYIJ7pJ2EeJkWU/AAWwF43cF0z3j CptWfXzOa+HUXr7FxTjmjCNHJ3JnKPn9OUE9ZNyHhteBGB/OKPFq9lpvZLcs9oVy9v 6gRaM0KIj4dC1peHWO1i0HCj+gI/q/RKsGspT1VlapSU1Sn7w+tnAVCajVvQJCJtu7 nlgWbD8nMtL2ZPyi4KFexO610zQj3NSuw9ushh7q9k6wr2uLqJFBURCepd/bMLx4Ni 2OEMNESuD3BYg== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 5886067EE6; Fri, 3 Apr 2026 08:05:56 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 3 Apr 2026 08:04:30 -0600 Message-ID: <20260403140523.1998228-9-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260403140523.1998228-1-sjg@u-boot.org> References: <20260403140523.1998228-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: Q4TM7S3TJXR6GRYR5WZHPMNKVSW564PL X-Message-ID-Hash: Q4TM7S3TJXR6GRYR5WZHPMNKVSW564PL X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 08/34] vfs: Add mount table and path resolution List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Add the ability to mount filesystems at directories and resolve paths through the mount tree. Mounts are tracked as UCLASS_MOUNT devices, each linking a mount-point directory to a UCLASS_FS device. Path resolution walks each component, looking it up as a directory in the current filesystem and checking whether it is a mount point. This allows nested mounts and returns the remaining subpath within the matched filesystem. Signed-off-by: Simon Glass --- fs/Makefile | 2 +- fs/fs_mount.c | 69 +++++++++++++ fs/vfs.c | 253 ++++++++++++++++++++++++++++++++++++++++++++++ fs/vfs_internal.h | 22 ++++ include/vfs.h | 71 +++++++++++++ test/dm/fs.c | 113 +++++++++++++++++++++ 6 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 fs/fs_mount.c diff --git a/fs/Makefile b/fs/Makefile index 704ac6e4866..345a4627241 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -8,7 +8,7 @@ obj-$(CONFIG_$(PHASE_)FS_LEGACY) += fs_legacy.o fs_internal.o obj-$(CONFIG_$(PHASE_)FS) += fs-uclass.o obj-$(CONFIG_$(PHASE_)DIR) += dir-uclass.o obj-$(CONFIG_$(PHASE_)FILE) += file-uclass.o -obj-$(CONFIG_$(PHASE_)VFS) += mount-uclass.o vfs.o vfs_dir.o +obj-$(CONFIG_$(PHASE_)VFS) += fs_mount.o mount-uclass.o vfs.o vfs_dir.o ifdef CONFIG_XPL_BUILD obj-$(CONFIG_SPL_FS_FAT) += fat/ diff --git a/fs/fs_mount.c b/fs/fs_mount.c new file mode 100644 index 00000000000..11e7302eb18 --- /dev/null +++ b/fs/fs_mount.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Mount device driver for the VFS layer + * + * Copyright 2026 Simon Glass + */ + +#include +#include +#include +#include "vfs_internal.h" +#include +#include + +int fs_mount_init(struct udevice *vfs, struct udevice *dir, + struct udevice *fsdev) +{ + struct vfs_priv *priv = dev_get_priv(vfs); + struct vfsmount *mnt; + char dev_name[30]; + struct udevice *dev; + char *str; + int ret; + + snprintf(dev_name, sizeof(dev_name), "mount.%d", priv->mount_count); + str = strdup(dev_name); + if (!str) + return log_msg_ret("fms", -ENOMEM); + + ret = device_bind_driver(vfs, "mount", str, &dev); + if (ret) { + free(str); + return log_msg_ret("fmb", ret); + } + device_set_name_alloced(dev); + + ret = device_probe(dev); + if (ret) { + device_unbind(dev); + return log_msg_ret("fmp", ret); + } + + mnt = dev_get_uclass_priv(dev); + mnt->dir = dir; + mnt->target = fsdev; + priv->mount_count++; + + return 0; +} + +int fs_mount_uninit(struct udevice *mnt_dev) +{ + int ret; + + ret = device_remove(mnt_dev, DM_REMOVE_NORMAL); + if (ret) + return log_msg_ret("fdr", ret); + + ret = device_unbind(mnt_dev); + if (ret) + return log_msg_ret("fdb", ret); + + return 0; +} + +U_BOOT_DRIVER(mount) = { + .name = "mount", + .id = UCLASS_MOUNT, +}; diff --git a/fs/vfs.c b/fs/vfs.c index 439c9760829..c42e60b4dc6 100644 --- a/fs/vfs.c +++ b/fs/vfs.c @@ -14,7 +14,10 @@ #include #include #include +#include #include +#include +#include #include #include "vfs_internal.h" #include @@ -22,6 +25,150 @@ #include #include +#define vfs_foreach_mount(mnt, pos) \ + for (uclass_first_device(UCLASS_MOUNT, &(pos)); \ + (pos) && ((mnt) = dev_get_uclass_priv(pos)); \ + uclass_next_device(&(pos))) + +/** + * find_mount() - Check whether a directory is a mount point + * + * @dir: UCLASS_DIR device to check + * @mntp: Returns the UCLASS_MOUNT device if found + * Return: 0 if found, -ENOENT if not + */ +static int find_mount(struct udevice *dir, struct udevice **mntp) +{ + struct vfsmount *mnt; + struct udevice *dev; + + vfs_foreach_mount(mnt, dev) { + if (mnt->dir == dir) { + *mntp = dev; + return 0; + } + } + + return -ENOENT; +} + +/** + * find_mount_by_target() - Find the mount for a given FS device + * + * @fsdev: UCLASS_FS device to search for + * @mntp: Returns the UCLASS_MOUNT device if found + * Return: 0 if found, -ENOENT if not + */ +static int find_mount_by_target(struct udevice *fsdev, struct udevice **mntp) +{ + struct vfsmount *mnt; + struct udevice *dev; + + vfs_foreach_mount(mnt, dev) { + if (mnt->target == fsdev) { + *mntp = dev; + return 0; + } + } + + return -ENOENT; +} + +/** + * walk_path() - Walk a path following mount points + * + * For each path component, looks up the directory in the current + * filesystem and checks if it is a mount point. Stops when a component + * cannot be looked up or is not a mount point. + * + * @path: Path to walk (without leading '/') + * @start: Starting FS device + * @mntp: Returns the deepest mount found, or NULL if none + * @remainp: Returns pointer to the remaining unresolved path + * Return: The FS after the deepest mount crossed (same as @start if none) + */ +static struct udevice *walk_path(const char *path, struct udevice *start, + struct udevice **mntp, const char **remainp) +{ + struct udevice *best = NULL, *cur_fs = start; + const char *best_remain = path; + const char *p = path; + + while (*p) { + char component[FS_DIRENT_NAME_LEN]; + struct udevice *comp_dir, *mnt_dev; + struct vfsmount *mnt; + const char *slash; + int len; + + slash = strchr(p, '/'); + len = slash ? slash - p : strlen(p); + if (len >= sizeof(component)) + break; + + memcpy(component, p, len); + component[len] = '\0'; + + if (fs_lookup_dir(cur_fs, component, &comp_dir)) + break; + + if (find_mount(comp_dir, &mnt_dev)) + break; + + /* Found a mount - record it and cross into the FS */ + best = mnt_dev; + p += len; + if (*p == '/') + p++; + best_remain = p; + + mnt = dev_get_uclass_priv(mnt_dev); + cur_fs = mnt->target; + } + + *mntp = best; + *remainp = best_remain; + + return cur_fs; +} + +/** + * vfs_mount_path() - Build the full path for a mount device + * + * Walks up the device tree to reconstruct the absolute path, using + * dir_uc_priv->path from each mount's directory to get the component name. + * + * @mnt_dev: UCLASS_MOUNT device + * @buf: Buffer to write path into + * @size: Size of buffer + * Return: 0 if OK, -ve on error + */ +static int vfs_mount_path(struct udevice *mnt_dev, char *buf, int size) +{ + struct vfsmount *mnt = dev_get_uclass_priv(mnt_dev); + struct dir_uc_priv *uc_priv = dev_get_uclass_priv(mnt->dir); + struct udevice *parent_fs = dev_get_parent(mnt->dir); + + if (parent_fs == vfs_root()) { + snprintf(buf, size, "/%s", uc_priv->path); + } else { + char parent_path[FILE_MAX_PATH_LEN]; + struct udevice *pdev; + int ret; + + ret = find_mount_by_target(parent_fs, &pdev); + if (ret) + return ret; + + ret = vfs_mount_path(pdev, parent_path, sizeof(parent_path)); + if (ret) + return ret; + snprintf(buf, size, "%s/%s", parent_path, uc_priv->path); + } + + return 0; +} + /* VFS root filesystem - provides an empty root directory */ static int vfs_rootfs_mount(struct udevice *dev) @@ -71,6 +218,112 @@ static int vfs_rootfs_lookup_dir(struct udevice *dev, const char *path, /* Exported functions */ +int vfs_find_mount(struct udevice *vfs, const char *path, struct udevice **mntp, + const char **subpathp) +{ + struct udevice *best; + const char *p; + + p = path; + if (*p == '/') + p++; + + walk_path(p, vfs, &best, subpathp); + + if (!best) { + if (!*p) { + *mntp = NULL; + return 0; + } + return log_msg_ret("vfn", -ENOENT); + } + + *mntp = best; + + return 0; +} + +int vfs_resolve(struct udevice *vfs, const char *path, + struct udevice **dirp) +{ + struct udevice *cur_fs, *best; + const char *remain; + + if (!path || *path != '/') + return log_msg_ret("vrp", -EINVAL); + + cur_fs = walk_path(path + 1, vfs, &best, &remain); + + /* Remaining path must be at most one component (the target dir) */ + if (strchr(remain, '/')) + return log_msg_ret("vrm", -ENOENT); + + return fs_lookup_dir(cur_fs, remain, dirp); +} + +int vfs_mount(struct udevice *vfs, struct udevice *dir, struct udevice *fsdev) +{ + int ret; + + ret = fs_mount(fsdev); + if (ret && ret != -EISCONN) + return log_msg_ret("vmm", ret); + + ret = fs_mount_init(vfs, dir, fsdev); + if (ret) { + fs_unmount(fsdev); + return log_msg_ret("vmc", ret); + } + + return 0; +} + +int vfs_umount(struct udevice *mnt_dev) +{ + struct vfsmount *mnt = dev_get_uclass_priv(mnt_dev); + int ret; + + ret = fs_unmount(mnt->target); + if (ret && ret != -ENOTCONN) + return log_msg_ret("vuu", ret); + + ret = fs_mount_uninit(mnt_dev); + if (ret) + return log_msg_ret("vud", ret); + + return 0; +} + +int vfs_umount_path(struct udevice *vfs, const char *path) +{ + struct udevice *mnt_dev; + const char *subpath; + int ret; + + ret = vfs_find_mount(vfs, path, &mnt_dev, &subpath); + if (ret) + return log_msg_ret("vuf", ret); + + /* Make sure the entire path was consumed (exact match) */ + if (!mnt_dev || *subpath) + return log_msg_ret("vup", -ENOENT); + + return vfs_umount(mnt_dev); +} + +void vfs_print_mounts(void) +{ + struct vfsmount *mnt; + struct udevice *dev; + + vfs_foreach_mount(mnt, dev) { + char path[FILE_MAX_PATH_LEN]; + + if (!vfs_mount_path(dev, path, sizeof(path))) + printf("%-20s %s\n", path, mnt->target->name); + } +} + struct udevice *vfs_root(void) { struct udevice *dev; diff --git a/fs/vfs_internal.h b/fs/vfs_internal.h index b7e6b37d55d..ac7f756e24f 100644 --- a/fs/vfs_internal.h +++ b/fs/vfs_internal.h @@ -19,4 +19,26 @@ struct vfs_priv { int mount_count; }; +/** + * fs_mount_init() - Create a mount device + * + * Binds and probes a UCLASS_MOUNT device as a child of @vfs, linking + * @dir to @fsdev. + * + * @vfs: VFS root FS device + * @dir: UCLASS_DIR device that is the mount point + * @fsdev: UCLASS_FS device to mount + * Return: 0 if OK, -ve on error + */ +int fs_mount_init(struct udevice *vfs, struct udevice *dir, + struct udevice *fsdev); + +/** + * fs_mount_uninit() - Remove and unbind a mount device + * + * @mnt_dev: UCLASS_MOUNT device to destroy + * Return: 0 if OK, -ve on error + */ +int fs_mount_uninit(struct udevice *mnt_dev); + #endif diff --git a/include/vfs.h b/include/vfs.h index 1bf1762ab52..d7b6449b088 100644 --- a/include/vfs.h +++ b/include/vfs.h @@ -46,4 +46,75 @@ int vfs_init(void); */ struct udevice *vfs_root(void); +/** + * vfs_resolve() - Resolve a path to a directory + * + * Walks the path, following mount points along the way. For each + * component, looks up the directory in the current filesystem. If the + * directory does not exist (e.g. in the VFS rootfs), it is created. + * + * For "/host", looks up (or creates) "host" in the VFS rootfs. + * For "/mnt/data", follows the mount at /mnt, then looks up "data" + * in the mounted filesystem. + * + * @vfs: VFS root FS device + * @path: Absolute path (must start with '/') + * @dirp: Returns the UCLASS_DIR device for the final component + * Return: 0 if OK, -ve on error + */ +int vfs_resolve(struct udevice *vfs, const char *path, + struct udevice **dirp); + +/** + * vfs_mount() - Mount a filesystem at a directory + * + * Creates a UCLASS_MOUNT device linking @dir to @fsdev. + * + * @vfs: VFS root FS device + * @dir: UCLASS_DIR device for the mount point + * @fsdev: UCLASS_FS device to mount + * Return: 0 if OK, -ve on error + */ +int vfs_mount(struct udevice *vfs, struct udevice *dir, struct udevice *fsdev); + +/** + * vfs_umount() - Unmount a filesystem + * + * @mnt_dev: UCLASS_MOUNT device to unmount + * Return: 0 if OK, -ve on error + */ +int vfs_umount(struct udevice *mnt_dev); + +/** + * vfs_umount_path() - Unmount the filesystem at a path + * + * @vfs: VFS root FS device + * @path: Mount point to remove + * Return: 0 if OK, -ENOENT if not mounted, other -ve on error + */ +int vfs_umount_path(struct udevice *vfs, const char *path); + +/** + * vfs_find_mount() - Find the mount covering a path + * + * Walks the mount tree from the VFS root, following mount points for + * each path component. Returns the deepest mount and the remaining + * subpath. + * + * @vfs: VFS root FS device + * @path: Absolute path to resolve + * @mntp: Returns the UCLASS_MOUNT device + * @subpathp: Returns pointer into @path for the remaining path within the + * mounted filesystem + * Return: 0 if OK (with @mntp set to NULL if path is the VFS root), + * -ENOENT if no mount covers this path + */ +int vfs_find_mount(struct udevice *vfs, const char *path, + struct udevice **mntp, const char **subpathp); + +/** + * vfs_print_mounts() - Print all current mounts + */ +void vfs_print_mounts(void); + #endif diff --git a/test/dm/fs.c b/test/dm/fs.c index 1d395031dee..f31a11e90cb 100644 --- a/test/dm/fs.c +++ b/test/dm/fs.c @@ -5,6 +5,7 @@ * Copyright 2025 Simon Glass */ +#include #include #include #include @@ -128,10 +129,122 @@ static int dm_test_vfs_init(struct unit_test_state *uts) ut_asserteq(-ENOENT, dir_read(dir, strm, &dent)); ut_assertok(dir_close(dir, strm)); + /* vfs_resolve("/") should return the root dir */ + ut_assertok(vfs_resolve(vfs, "/", &dir)); + ut_assertnonnull(dir); + + /* vfs_resolve with bad paths should fail */ + ut_asserteq(-EINVAL, vfs_resolve(vfs, NULL, &dir)); + ut_asserteq(-EINVAL, vfs_resolve(vfs, "no_slash", &dir)); + /* rootfs cannot be unmounted */ ut_asserteq(-EBUSY, fs_unmount(vfs)); return 0; } DM_TEST(dm_test_vfs_init, UTF_SCAN_FDT); + +/* Test that the root directory lists mount points */ +static int dm_test_vfs_dir(struct unit_test_state *uts) +{ + struct udevice *vfs, *fsdev, *dir, *root_dir; + struct fs_dir_stream *strm; + struct fs_dirent dent; + + ut_assertok(vfs_init()); + vfs = vfs_root(); + ut_assertnonnull(vfs); + + /* Root dir should be empty before any mounts */ + ut_assertok(fs_lookup_dir(vfs, "", &root_dir)); + ut_assertok(dir_open(root_dir, &strm)); + ut_asserteq(-ENOENT, dir_read(root_dir, strm, &dent)); + ut_assertok(dir_close(root_dir, strm)); + + /* Mount the sandbox FS at /host */ + ut_assertok(uclass_get_device_by_name(UCLASS_FS, "hostfs", &fsdev)); + ut_assertok(vfs_resolve(vfs, "/host", &dir)); + ut_assertok(vfs_mount(vfs, dir, fsdev)); + + /* Root dir should now list "host" */ + ut_assertok(fs_lookup_dir(vfs, "", &root_dir)); + ut_assertok(dir_open(root_dir, &strm)); + ut_assertok(dir_read(root_dir, strm, &dent)); + ut_asserteq_str("host", dent.name); + ut_asserteq(FS_DT_DIR, dent.type); + ut_asserteq(-ENOENT, dir_read(root_dir, strm, &dent)); + ut_assertok(dir_close(root_dir, strm)); + + ut_assertok(vfs_umount_path(vfs, "/host")); + + return 0; +} +DM_TEST(dm_test_vfs_dir, UTF_SCAN_FDT); + +/* Test basic VFS mount, find_mount, ls and umount */ +static int dm_test_vfs_mount(struct unit_test_state *uts) +{ + struct udevice *vfs, *fsdev, *dir, *mnt; + const char *subpath; + + ut_assertok(vfs_init()); + vfs = vfs_root(); + ut_assertnonnull(vfs); + + /* Find the sandbox FS (not the vfs_rootfs) */ + ut_assertok(uclass_get_device_by_name(UCLASS_FS, "hostfs", &fsdev)); + + /* Resolve /host to a mount-point DIR */ + ut_assertok(vfs_resolve(vfs, "/host", &dir)); + + /* Mount the sandbox FS at /host */ + ut_assertok(vfs_mount(vfs, dir, fsdev)); + + /* Mounting same FS at another path is OK (-EISCONN ignored) */ + ut_assertok(vfs_resolve(vfs, "/other", &dir)); + ut_assertok(vfs_mount(vfs, dir, fsdev)); + ut_assertok(vfs_umount_path(vfs, "/other")); + + /* vfs_print_mounts() should show the /host mount */ + console_record_reset_enable(); + vfs_print_mounts(); + ut_assert_nextlinen("/host"); + ut_assert_console_end(); + + /* find_mount should resolve /host exactly */ + ut_assertok(vfs_find_mount(vfs, "/host", &mnt, &subpath)); + ut_asserteq_str("", subpath); + + /* find_mount should strip mount prefix from subpath */ + ut_assertok(vfs_find_mount(vfs, "/host/some/path", &mnt, &subpath)); + ut_asserteq_str("some/path", subpath); + + /* find_mount should handle trailing component */ + ut_assertok(vfs_find_mount(vfs, "/host/file.txt", &mnt, &subpath)); + ut_asserteq_str("file.txt", subpath); + + /* find_mount should fail for unmounted path */ + ut_asserteq(-ENOENT, vfs_find_mount(vfs, "/nowhere", &mnt, &subpath)); + + /* find_mount with partial prefix should not match */ + ut_asserteq(-ENOENT, vfs_find_mount(vfs, "/hostal", &mnt, &subpath)); + + /* vfs_resolve with intermediate non-mount should fail */ + ut_asserteq(-ENOENT, vfs_resolve(vfs, "/bogus/sub", &dir)); + + /* Unmount */ + ut_assertok(vfs_umount_path(vfs, "/host")); + + /* Should not be mounted any more */ + ut_asserteq(-ENOENT, vfs_find_mount(vfs, "/host", &mnt, &subpath)); + + /* Double umount should fail */ + ut_asserteq(-ENOENT, vfs_umount_path(vfs, "/host")); + + /* Umount of never-mounted path should fail */ + ut_asserteq(-ENOENT, vfs_umount_path(vfs, "/bogus")); + + return 0; +} +DM_TEST(dm_test_vfs_mount, UTF_SCAN_FDT); #endif