[Concept,25/34] vfs: Support VFS paths in the source command

Message ID 20260403140523.1998228-26-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>

Extend the source command to accept VFS paths:

    source /boot/boot.scr
    source /host/setup.cmd

When the argument contains a '/', it is treated as a VFS path. The
file is loaded into memory and checked for a mkimage header. If
present, cmd_source_script() handles it as a FIT or legacy image
script. Otherwise, the file is executed as a plain text command list
via run_command_list().

This is the key integration point for using VFS in boot scripts.

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

 cmd/source.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 test/dm/fs.c | 33 ++++++++++++++++++++++++
 2 files changed, 105 insertions(+)
  

Patch

diff --git a/cmd/source.c b/cmd/source.c
index c9b5f8e400a..c5a3e41d8ef 100644
--- a/cmd/source.c
+++ b/cmd/source.c
@@ -16,13 +16,78 @@ 
 
 #include <command.h>
 #include <env.h>
+#include <dm.h>
+#include <file.h>
+#include <fs_common.h>
 #include <image.h>
 #include <log.h>
 #include <malloc.h>
 #include <mapmem.h>
+#include <vfs.h>
 #include <asm/byteorder.h>
 #include <asm/io.h>
 
+#if IS_ENABLED(CONFIG_VFS)
+/**
+ * source_vfs_file() - Load and execute a script from a VFS path
+ *
+ * Loads the file into memory. If it has a valid image header, runs it
+ * via cmd_source_script(). Otherwise treats it as a plain text command
+ * list.
+ *
+ * @path: VFS path to the script file
+ * Return: 0 on success, non-zero on error
+ */
+static int source_vfs_file(const char *path)
+{
+	struct file_uc_priv *uc_priv;
+	struct udevice *fil;
+	long actual;
+	char *buf;
+	ulong addr;
+	int ret;
+
+	ret = vfs_open_file(path, DIR_O_RDONLY, &fil);
+	if (ret) {
+		printf("## Cannot find script '%s'\n", path);
+		return CMD_RET_FAILURE;
+	}
+
+	uc_priv = dev_get_uclass_priv(fil);
+	buf = malloc(uc_priv->size + 1);
+	if (!buf)
+		return CMD_RET_FAILURE;
+
+	actual = file_read_at(fil, buf, 0, uc_priv->size);
+	if (actual < 0) {
+		free(buf);
+		printf("## Cannot read script '%s'\n", path);
+		return CMD_RET_FAILURE;
+	}
+
+	buf[actual] = '\0';
+
+	/* Check for a mkimage header (legacy or FIT) */
+	if (actual >= sizeof(struct legacy_img_hdr) &&
+	    image_get_magic((struct legacy_img_hdr *)buf) == IH_MAGIC) {
+		addr = map_to_sysmem(buf);
+		ret = cmd_source_script(addr, NULL, NULL);
+	} else {
+		/* Plain text command list */
+		ret = run_command_list(buf, actual, 0);
+	}
+
+	free(buf);
+
+	return ret;
+}
+
+static bool is_vfs_path(const char *arg)
+{
+	return arg && (arg[0] == '/' || strchr(arg, '/'));
+}
+#endif
+
 static int do_source(struct cmd_tbl *cmdtp, int flag, int argc,
 		     char *const argv[])
 {
@@ -30,6 +95,13 @@  static int do_source(struct cmd_tbl *cmdtp, int flag, int argc,
 	int rcode;
 	const char *fit_uname = NULL, *confname = NULL;
 
+#if IS_ENABLED(CONFIG_VFS)
+	if (argc >= 2 && is_vfs_path(argv[1])) {
+		printf("## Executing script from '%s'\n", argv[1]);
+		return source_vfs_file(argv[1]);
+	}
+#endif
+
 	/* Find script image */
 	if (argc < 2) {
 		addr = CONFIG_SYS_LOAD_ADDR;
diff --git a/test/dm/fs.c b/test/dm/fs.c
index 4158d2ff3dc..2e046a4bd0c 100644
--- a/test/dm/fs.c
+++ b/test/dm/fs.c
@@ -6,6 +6,7 @@ 
  */
 
 #include <console.h>
+#include <env.h>
 #include <dir.h>
 #include <dm.h>
 #include <file.h>
@@ -793,6 +794,38 @@  static int dm_test_vfs_symlink(struct unit_test_state *uts)
 }
 DM_TEST(dm_test_vfs_symlink, UTF_SCAN_FDT);
 
+/* Test source command with VFS path */
+static int dm_test_vfs_source(struct unit_test_state *uts)
+{
+	ut_assertok(vfs_init());
+
+	ut_assertok(run_command("mount hostfs /host", 0));
+	ut_assert_console_end();
+
+	/* Create a simple script that sets an env var */
+	memcpy(map_sysmem(0x1000, 18), "setenv vfs_test ok", 18);
+	ut_assertok(run_command("save 1000 /host/.vfs_script 12", 0));
+	ut_assert_nextline("18 bytes written");
+	ut_assert_console_end();
+
+	/* Run it via source */
+	ut_assertok(run_command("source /host/.vfs_script", 0));
+	ut_assert_nextlinen("## Executing script");
+	ut_assert_console_end();
+
+	/* Verify the env var was set */
+	ut_asserteq_str("ok", env_get("vfs_test"));
+
+	os_unlink(".vfs_script");
+	env_set("vfs_test", NULL);
+
+	ut_assertok(run_command("umount /host", 0));
+	ut_assert_console_end();
+
+	return 0;
+}
+DM_TEST(dm_test_vfs_source, UTF_SCAN_FDT);
+
 /* Test the ln command via VFS */
 static int dm_test_vfs_ln(struct unit_test_state *uts)
 {