From patchwork Fri Apr 3 14:04:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2100 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=1775225177; bh=9wBUvrliMsQAU1rXkNH6YEnPXkOqJixzfo8fl/NvPoQ=; 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=soSwx9Hs8akKAU85fbrk5fNvhkUVTr6elEYZuXAH3vXSKX6GhqVwL5PDXGW9uJYuX g6OobUW8jLwamNg4+eZKH8GmjCxGXQnO58yhX0DJNtBSguy3yEG1AFEsBM5iwBrjCi oCa+jVFrVleoSHIWqdnOM6PJZ3b/XBSlKCbet0B31m/6Z2jKMB6Kd7cRO51rTMc9ar yzGc4ft3QFpnFOzHr7M9f4jJbjvZFlVYtZr27hwv3WWDA4f0pdCPg9NDVFhSzXM62j kM1tSjGvdVuGwxjUd9xET5rd6bWSYMQKEZWRLFTL8BDhoYWdKG4cT44dGFUxFUDSBb h0vXtSg8zfUfw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D45F06A34E for ; Fri, 3 Apr 2026 08:06:17 -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 YPSLRAVO8pp4 for ; Fri, 3 Apr 2026 08:06:17 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775225174; bh=9wBUvrliMsQAU1rXkNH6YEnPXkOqJixzfo8fl/NvPoQ=; 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=jrKf7J6l5lZshGbMp3SkOzT8nqtNU9S6GRJpGbHI9RBzHrKRsLOdXxD0VTaFkGjjJ /JkuuoTQR/cpXORdE7lQuVk6bXHAbzZbXCi9/ZaClqO5ztylzSlSWINqzJktaPmCBl Ln9s4EQE6bN0wPT/xWDrqHT+Xi6AXDZbn6VVyJUlA3DA3YxcR4dUKQ1IShmOoo9D4V NfEmsrw1i+YAGZEPwITzZf7HM1bGYXBvbIsiDZBbzvrUm9cf9fv/E5A7YykBpOmt/U zR0WmZDT56INUeYaRvcJA7/6Q4UfsLRjpI5yZpEse7JE5gYApB33eOs+38xMWTsc8S An7ViVBHTLq9w== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 622246A35F for ; Fri, 3 Apr 2026 08:06:14 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775225172; bh=mg7dH1br2VKaMrECOGBVMfr3lyEgCqtrXYHOwb1nqB4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ClGFUgSoQUD9XO6RiuNx5fbjrp5/lqPFvan3xSk1zFCvisqnkF2PxliQ2+pBajSpD QLTIMB3z9fULgDZS18p2P9EiH9/h8uji0rL77o2MgW5eEwC/v94Pnf2TwSOq9NG6ze bYw6w6IwZtdkHfAfH/Xge0SLLoXsWG/5QCj5SnqkvxClEWUOBz2W1IYgyNXdkYmuKX 64rMDC8BO9c/OihFy7P3Jq0sTL/SQhKUn1hbaYepK11HggFkTLgsGCYCG43eREB86S ZMHUmLSDBDa0C6j4kLLQfeBrp4/eOzCMR+IylKagjep7NJzuEy5gvS5MFHq1ZotbN+ tbisgVJYFAC3Q== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 136936A376; Fri, 3 Apr 2026 08:06:12 -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 qTp5g1q_Sy8h; Fri, 3 Apr 2026 08:06:12 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1775225166; bh=/EM2gdYx+NPFGnqV6+4ojO1rhUTUh2Ey/geVjghHN3Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=O4Qof9Oivf2b/E6hnCeWgzGHaeNO0ZX7ngNu5+AuzN4aAzrerQnIobpn0KL1grcN7 BkQJH5YhpOzEBWRcHmMOF4st9k7ZXAPf2nwIZ3V9XAi0hvvxcw3hCXNsku/EN7iuP+ lEAoh7WvHQzMyFQe9UNX5SM68VXCXEdgZ+Nr2PMZiY/yIdlnGGsBgSadSW32hn0az3 iBMmeI+OW1+H8Zz8pNZwlfoyE9vN6PCQAnJt3JMTZZvRDNJ9FJPjc8Djj+yHeg048B goTluUEqZpo2u1dTkC+27oldbiJ7GSgDBvcCJtToEjYFR7u+pmbc/ZnI8KWkKRPTeE xZBspYb1vL/nw== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 3248A6A350; Fri, 3 Apr 2026 08:06:06 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 3 Apr 2026 08:04:37 -0600 Message-ID: <20260403140523.1998228-16-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: NY53CDQ5K742WKDOWH5MJADQVVOXJLIA X-Message-ID-Hash: NY53CDQ5K742WKDOWH5MJADQVVOXJLIA 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 15/34] vfs: Add current working directory and path helpers 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 a current working directory (cwd) to the VFS layer, stored in the vfs_rootfs driver-private data, with vfs_chdir() and vfs_getcwd() to change and query it. Add vfs_resolve_cwd() which resolves relative paths against the cwd, and use it in vfs_ls() so that relative paths work. Add vfs_resolve_mount() which combines vfs_root lookup, cwd resolution and mount lookup in one call. Signed-off-by: Simon Glass --- fs/vfs.c | 194 ++++++++++++++++++++++++++++++++++++++++++---- fs/vfs_internal.h | 4 + include/vfs.h | 30 +++++++ test/dm/fs.c | 42 ++++++++++ 4 files changed, 256 insertions(+), 14 deletions(-) diff --git a/fs/vfs.c b/fs/vfs.c index f06844f2a33..79b1ca0ef89 100644 --- a/fs/vfs.c +++ b/fs/vfs.c @@ -11,6 +11,7 @@ #define LOG_CATEGORY UCLASS_MOUNT +#include #include #include #include @@ -30,6 +31,139 @@ (pos) && ((mnt) = dev_get_uclass_priv(pos)); \ uclass_next_device(&(pos))) +/** + * canonicalise_path() - Remove . and .. components from an absolute path + * + * Modifies @buf in place. The path must start with '/'. + * + * @buf: Absolute path to canonicalise (modified in place) + */ +static int canonicalise_path(char *buf) +{ + char *p, *token; + char *stack[64]; + int depth = 0; + + /* Tokenise by '/' and resolve . and .. */ + p = buf + 1; /* skip leading '/' */ + while (*p) { + token = p; + while (*p && *p != '/') + p++; + if (*p) + *p++ = '\0'; + + if (!*token || !strcmp(token, ".")) { + continue; + } else if (!strcmp(token, "..")) { + if (depth > 0) + depth--; + } else { + if (depth >= ARRAY_SIZE(stack)) + return -ENAMETOOLONG; + stack[depth++] = token; + } + } + + /* + * Rebuild the path from the stack. This always fits in buf since + * removing . and .. components can only shorten the path. + */ + p = buf; + *p++ = '/'; + for (int i = 0; i < depth; i++) { + if (i > 0) + *p++ = '/'; + strcpy(p, stack[i]); + p += strlen(stack[i]); + } + *p = '\0'; + + /* Ensure root is "/" not "" */ + if (!buf[1]) + buf[0] = '/'; + + return 0; +} + +const char *vfs_path_resolve(const char *cwd, const char *path, char *buf, + int size) +{ + if (!path || !*path) { + strncpy(buf, cwd, size); + buf[size - 1] = '\0'; + return buf; + } + + if (*path == '/') { + strncpy(buf, path, size); + buf[size - 1] = '\0'; + } else { + int len = strlen(cwd); + + if (len == 1) + snprintf(buf, size, "/%s", path); + else + snprintf(buf, size, "%s/%s", cwd, path); + } + + if (canonicalise_path(buf)) + return NULL; + + return buf; +} + +const char *vfs_getcwd(void) +{ + struct udevice *vfs = vfs_root(); + struct vfs_priv *priv; + + if (!vfs) + return "/"; + priv = dev_get_priv(vfs); + + return priv->cwd; +} + +int vfs_chdir(const char *path) +{ + char resolved[FILE_MAX_PATH_LEN]; + struct udevice *vfs, *mnt; + const char *subpath, *abs; + struct vfs_priv *priv; + int len, ret; + + vfs = vfs_root(); + if (!vfs) + return log_msg_ret("vci", -ENXIO); + priv = dev_get_priv(vfs); + + abs = vfs_path_resolve(vfs_getcwd(), path, resolved, sizeof(resolved)); + if (!abs) + return log_msg_ret("vcp", -ENAMETOOLONG); + + /* Verify the path exists by trying to resolve it */ + if (strcmp(abs, "/")) { + ret = vfs_find_mount(vfs, abs, &mnt, &subpath); + if (ret) + return log_msg_ret("vcf", ret); + } + + len = strlen(abs); + + /* Strip trailing slash (but keep "/" for root) */ + if (len > 1 && abs[len - 1] == '/') + len--; + + if (len >= sizeof(priv->cwd)) + return log_msg_ret("vcl", -ENAMETOOLONG); + + memcpy(priv->cwd, abs, len); + priv->cwd[len] = '\0'; + + return 0; +} + /** * find_mount() - Check whether a directory is a mount point * @@ -174,10 +308,13 @@ static int vfs_mount_path(struct udevice *mnt_dev, char *buf, int size) static int vfs_rootfs_mount(struct udevice *dev) { struct fs_priv *uc_priv = dev_get_uclass_priv(dev); + struct vfs_priv *priv = dev_get_priv(dev); if (uc_priv->mounted) return log_msg_ret("rfm", -EISCONN); + strcpy(priv->cwd, "/"); + uc_priv->mounted = true; return 0; @@ -243,6 +380,34 @@ int vfs_find_mount(struct udevice *vfs, const char *path, struct udevice **mntp, return 0; } +/** + * vfs_resolve_mount() - Resolve a path to its mount and subpath + * + * Handles vfs_root lookup, cwd resolution and mount lookup in one call. + * + * @path: Absolute or relative VFS path + * @resolved: Buffer for cwd-resolved path + * @size: Size of @resolved + * @mntp: Returns the UCLASS_MOUNT device (NULL for root) + * @subpathp: Returns the remaining path within the mount + * Return: 0 if OK, -ve on error + */ +static int vfs_resolve_mount(const char *path, char *resolved, int size, + struct udevice **mntp, const char **subpathp) +{ + struct udevice *vfs; + + vfs = vfs_root(); + if (!vfs) + return log_msg_ret("vrv", -ENXIO); + + path = vfs_path_resolve(vfs_getcwd(), path, resolved, size); + if (!path) + return log_msg_ret("vrp", -ENAMETOOLONG); + + return vfs_find_mount(vfs, path, mntp, subpathp); +} + int vfs_resolve(struct udevice *vfs, const char *path, struct udevice **dirp) { @@ -296,11 +461,13 @@ int vfs_umount(struct udevice *mnt_dev) int vfs_umount_path(struct udevice *vfs, const char *path) { + char resolved[FILE_MAX_PATH_LEN]; struct udevice *mnt_dev; const char *subpath; int ret; - ret = vfs_find_mount(vfs, path, &mnt_dev, &subpath); + ret = vfs_resolve_mount(path, resolved, sizeof(resolved), + &mnt_dev, &subpath); if (ret) return log_msg_ret("vuf", ret); @@ -368,32 +535,31 @@ int vfs_open_file(const char *path, enum dir_open_flags_t oflags, int vfs_ls(const char *path) { - struct udevice *vfs, *mnt, *dir = NULL; + char resolved[FILE_MAX_PATH_LEN]; + struct udevice *mnt, *dir = NULL; struct fs_dir_stream *strm; struct fs_dirent dent; const char *subpath; bool empty = true; int ret; - vfs = vfs_root(); - if (!vfs) - return -ENXIO; + ret = vfs_resolve_mount(path, resolved, sizeof(resolved), + &mnt, &subpath); + if (ret) + return ret; - ret = vfs_find_mount(vfs, path, &mnt, &subpath); - if (!ret && mnt) { + if (mnt) { struct vfsmount *m = dev_get_uclass_priv(mnt); ret = fs_lookup_dir(m->target, subpath, &dir); - if (ret) - return ret; - } else if (!ret || !path[1]) { + } else { /* Root "/" - list the VFS root dir */ + struct udevice *vfs = vfs_root(); + ret = fs_lookup_dir(vfs, "", &dir); - if (ret) - return ret; - } else { - return ret; } + if (ret) + return ret; ret = dir_open(dir, &strm); if (ret) diff --git a/fs/vfs_internal.h b/fs/vfs_internal.h index ac7f756e24f..36539741402 100644 --- a/fs/vfs_internal.h +++ b/fs/vfs_internal.h @@ -8,15 +8,19 @@ #ifndef __VFS_INTERNAL_H #define __VFS_INTERNAL_H +#include + struct udevice; /** * struct vfs_priv - VFS root FS device driver-private data * * @mount_count: Counter for unique mount device names + * @cwd: Current working directory (absolute path) */ struct vfs_priv { int mount_count; + char cwd[FILE_MAX_PATH_LEN]; }; /** diff --git a/include/vfs.h b/include/vfs.h index 53d1932e861..521494e4199 100644 --- a/include/vfs.h +++ b/include/vfs.h @@ -41,6 +41,36 @@ struct vfsmount { */ int vfs_init(void); +/** + * vfs_chdir() - Change the current working directory + * + * @path: New directory (absolute or relative to current cwd) + * Return: 0 if OK, -ve on error + */ +int vfs_chdir(const char *path); + +/** + * vfs_getcwd() - Get the current working directory + * + * Return: pointer to the cwd string (static buffer, do not free) + */ +const char *vfs_getcwd(void); + +/** + * vfs_path_resolve() - Resolve a path against a given working directory + * + * If @path starts with '/', it is used as an absolute path. Otherwise it + * is joined with @cwd. In both cases, '.' and '..' components are resolved. + * + * @cwd: Current working directory (must be absolute) + * @path: Input path (absolute or relative), or NULL/empty for cwd + * @buf: Buffer to write the resolved absolute path + * @size: Size of @buf + * Return: pointer to @buf + */ +const char *vfs_path_resolve(const char *cwd, const char *path, + char *buf, int size); + /** * vfs_root() - Get the VFS root FS device * diff --git a/test/dm/fs.c b/test/dm/fs.c index b14083590c6..e45441e5241 100644 --- a/test/dm/fs.c +++ b/test/dm/fs.c @@ -284,4 +284,46 @@ static int dm_test_vfs_cmd(struct unit_test_state *uts) } DM_TEST(dm_test_vfs_cmd, UTF_SCAN_FDT); +/* Test current working directory */ +static int dm_test_vfs_cwd(struct unit_test_state *uts) +{ + struct udevice *vfs, *fsdev, *dir; + + ut_assertok(vfs_init()); + vfs = vfs_root(); + ut_assertnonnull(vfs); + + /* Default cwd is "/" */ + ut_asserteq_str("/", vfs_getcwd()); + + /* Mount 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)); + + /* cd to /host */ + ut_assertok(vfs_chdir("/host")); + ut_asserteq_str("/host", vfs_getcwd()); + + /* ls with NULL should list cwd (i.e. /host) */ + console_record_reset_enable(); + ut_assertok(vfs_ls(NULL)); + ut_assert_skip_to_linen("DIR "); + console_record_reset_enable(); + + /* cd back to root */ + ut_assertok(vfs_chdir("/")); + ut_asserteq_str("/", vfs_getcwd()); + + /* cd to non-existent path should fail */ + ut_asserteq(-ENOENT, vfs_chdir("/nowhere")); + /* cwd should be unchanged */ + ut_asserteq_str("/", vfs_getcwd()); + + ut_assertok(vfs_umount_path(vfs, "/host")); + + return 0; +} +DM_TEST(dm_test_vfs_cwd, UTF_SCAN_FDT); + #endif