@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * VFS-based filesystem commands - mount, umount, ls, load, size
+ * VFS-based filesystem commands - mount, umount, ls, load, save, size
*
* These replace the legacy commands in cmd/fs_legacy.c with versions that
* use absolute paths through the virtual filesystem layer.
@@ -213,6 +213,126 @@ U_BOOT_CMD_COMPLETE(
vfs_cmd_complete
);
+#define COPY_BUF_SIZE 0x1000
+
+int do_cp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+ struct file_uc_priv *uc_priv;
+ struct udevice *src, *dst;
+ char buf[COPY_BUF_SIZE];
+ loff_t remaining, pos;
+ long total = 0;
+ int ret;
+
+ if (argc < 3)
+ return CMD_RET_USAGE;
+
+ ret = vfs_open_file(argv[1], DIR_O_RDONLY, &src);
+ if (ret) {
+ printf("Source '%s' not found: %dE\n", argv[1], ret);
+ return CMD_RET_FAILURE;
+ }
+
+ ret = vfs_open_file(argv[2], DIR_O_WRONLY, &dst);
+ if (ret) {
+ printf("Dest '%s' failed: %dE\n", argv[2], ret);
+ return CMD_RET_FAILURE;
+ }
+
+ uc_priv = dev_get_uclass_priv(src);
+ remaining = uc_priv->size;
+ pos = 0;
+
+ while (remaining > 0) {
+ long chunk = min((loff_t)COPY_BUF_SIZE, remaining);
+ long nread, nwritten;
+
+ nread = file_read_at(src, buf, pos, chunk);
+ if (nread < 0) {
+ printf("Read failed: %ldE\n", nread);
+ return CMD_RET_FAILURE;
+ }
+ if (!nread)
+ break;
+
+ nwritten = file_write_at(dst, buf, pos, nread);
+ if (nwritten < 0) {
+ printf("Write failed: %ldE\n", nwritten);
+ return CMD_RET_FAILURE;
+ }
+
+ pos += nread;
+ remaining -= nread;
+ total += nwritten;
+ }
+
+ printf("%ld bytes copied\n", total);
+
+ return CMD_RET_SUCCESS;
+}
+
+static const char *fs_type_name(unsigned int type)
+{
+ switch (type) {
+ case FS_DT_DIR:
+ return "directory";
+ case FS_DT_REG:
+ return "regular file";
+ case FS_DT_LNK:
+ return "symbolic link";
+ default:
+ return "unknown";
+ }
+}
+
+static int do_stat(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct fs_dirent dent;
+ int ret;
+
+ if (argc < 2)
+ return CMD_RET_USAGE;
+
+ ret = vfs_stat(argv[1], &dent);
+ if (ret) {
+ printf("Error: %dE\n", ret);
+ return CMD_RET_FAILURE;
+ }
+
+ printf(" File: %s\n", dent.name);
+ printf(" Size: %llu\n", dent.size);
+ printf(" Type: %s\n", fs_type_name(dent.type));
+ if (dent.change_time.tm_year) {
+ printf("Modify: %04d-%02d-%02d %02d:%02d:%02d\n",
+ dent.change_time.tm_year, dent.change_time.tm_mon,
+ dent.change_time.tm_mday, dent.change_time.tm_hour,
+ dent.change_time.tm_min, dent.change_time.tm_sec);
+ }
+ if (dent.access_time.tm_year) {
+ printf("Access: %04d-%02d-%02d %02d:%02d:%02d\n",
+ dent.access_time.tm_year, dent.access_time.tm_mon,
+ dent.access_time.tm_mday, dent.access_time.tm_hour,
+ dent.access_time.tm_min, dent.access_time.tm_sec);
+ }
+ if (dent.create_time.tm_year) {
+ printf(" Birth: %04d-%02d-%02d %02d:%02d:%02d\n",
+ dent.create_time.tm_year, dent.create_time.tm_mon,
+ dent.create_time.tm_mday, dent.create_time.tm_hour,
+ dent.create_time.tm_min, dent.create_time.tm_sec);
+ }
+
+ return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD_COMPLETE(
+ stat, 2, 1, do_stat,
+ "display file status",
+ "<path>\n"
+ " - Show type, size and timestamps of a file or directory",
+ vfs_cmd_complete
+);
+
static int do_size(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
@@ -233,12 +353,13 @@ static int do_size(struct cmd_tbl *cmdtp, int flag, int argc,
return CMD_RET_SUCCESS;
}
-U_BOOT_CMD(
+U_BOOT_CMD_COMPLETE(
size, 2, 0, do_size,
"determine a file's size",
"<path>\n"
" - Find file at 'path' in the VFS, determine its size,\n"
- " and store in the 'filesize' variable."
+ " and store in the 'filesize' variable.",
+ vfs_cmd_complete
);
static int do_vfs_load(struct cmd_tbl *cmdtp, int flag, int argc,
@@ -330,3 +451,51 @@ U_BOOT_CMD_COMPLETE(
" - Legacy: load from block device interface",
vfs_cmd_complete
);
+
+static int do_save(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct udevice *fil;
+ long bytes, written;
+ unsigned long addr;
+ loff_t pos = 0;
+ void *buf;
+ int ret;
+
+ if (argc < 4)
+ return CMD_RET_USAGE;
+
+ addr = hextoul(argv[1], NULL);
+ bytes = hextoul(argv[3], NULL);
+ if (argc >= 5)
+ pos = hextoul(argv[4], NULL);
+
+ ret = vfs_open_file(argv[2], DIR_O_WRONLY, &fil);
+ if (ret) {
+ printf("Error: %dE\n", ret);
+ return CMD_RET_FAILURE;
+ }
+
+ buf = map_sysmem(addr, bytes);
+ written = file_write_at(fil, buf, pos, bytes);
+ unmap_sysmem(buf);
+
+ if (written < 0) {
+ printf("Write failed: %ldE\n", written);
+ return CMD_RET_FAILURE;
+ }
+
+ printf("%ld bytes written\n", written);
+
+ return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD_COMPLETE(
+ save, 5, 0, do_save,
+ "save memory to a file",
+ "<addr> <path> <bytes> [pos]\n"
+ " - Save 'bytes' from address 'addr' to 'path' in the VFS.\n"
+ " 'pos' gives the file byte position to start writing to.\n"
+ " If 'pos' is 0 or omitted, the file is written from the start.",
+ vfs_cmd_complete
+);
@@ -104,11 +104,14 @@ static int do_fs_ls(struct cmd_tbl *cmdtp, int flag, int argc,
U_BOOT_LONGHELP(fs,
"mount [<dev> <mountpoint>] - list or create mounts\n"
- "fs mount -t <type> <iface> <dev:part> <path> - mount from block device\n"
+ "fs mount <iface> <dev:part> <path> - auto-detect and mount\n"
+ "fs mount -t <type> <iface> <dev:part> <path> - mount specific type\n"
"fs umount <mountpoint> - unmount a filesystem\n"
- "fs ls [<path>] - list directory (default /)");
+ "fs ls [<path>] - list directory (default /)\n"
+ "fs cp <source> <dest> - copy a file");
U_BOOT_CMD_WITH_SUBCMDS(fs, "Filesystem operations", fs_help_text,
U_BOOT_SUBCMD_MKENT(mount, 6, 1, do_fs_mount),
U_BOOT_SUBCMD_MKENT(umount, 2, 1, do_fs_umount),
- U_BOOT_SUBCMD_MKENT(ls, 2, 1, do_fs_ls));
+ U_BOOT_SUBCMD_MKENT(ls, 2, 1, do_fs_ls),
+ U_BOOT_SUBCMD_MKENT(cp, 3, 0, do_cp));
@@ -292,6 +292,14 @@ int fs_mount_blkdev(const char *type, struct blk_desc *desc, int part_num,
*/
void vfs_print_mounts(void);
+/**
+ * do_cp() - Copy a file within the VFS
+ *
+ * Implements the 'fs cp' subcommand. Defined in cmd/fs.c, declared here
+ * so that cmd/vfs.c can reference it.
+ */
+int do_cp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
+
#ifdef CONFIG_AUTO_COMPLETE
/**
* vfs_complete() - Complete a partial VFS path
@@ -14,6 +14,7 @@
#include <os.h>
#include <vfs.h>
#include <dm/test.h>
+#include <dm/uclass-internal.h>
#include <test/ut.h>
#define READ_SIZE 0x20
@@ -232,6 +233,15 @@ static int dm_test_vfs_complete(struct unit_test_state *uts)
/* Non-matching prefix should return 0 */
ut_asserteq(0, vfs_complete(buf, "/z", 16, cmdv));
+ /* Create a known file to complete against */
+ memcpy(map_sysmem(0x10000, 5), "hello", 5);
+ ut_assertok(run_command("save 10000 /host/.comp_test 5", 0));
+ console_record_reset_enable();
+
+ /* Complete inside mount */
+ ut_assert(vfs_complete(buf, "/host/.comp_", 16, cmdv) >= 1);
+ ut_asserteq_str(".comp_test", cmdv[0]);
+
/* Complete with empty prefix shows entries */
ut_assert(vfs_complete(buf, "/host/", 16, cmdv) >= 1);
@@ -460,4 +470,186 @@ static int dm_test_vfs_cwd(struct unit_test_state *uts)
}
DM_TEST(dm_test_vfs_cwd, UTF_SCAN_FDT);
+/* Test cd and pwd commands */
+static int dm_test_vfs_cd(struct unit_test_state *uts)
+{
+ ut_assertok(vfs_init());
+
+ ut_assertok(run_command("mount hostfs /host", 0));
+ ut_assert_console_end();
+
+ /* Default cwd is root */
+ ut_assertok(run_command("pwd", 0));
+ ut_assert_nextline("/");
+ ut_assert_console_end();
+
+ /* cd to an absolute path */
+ ut_assertok(run_command("cd /host", 0));
+ ut_assert_console_end();
+ ut_assertok(run_command("pwd", 0));
+ ut_assert_nextline("/host");
+ ut_assert_console_end();
+
+ /* ls without a path should list cwd */
+ ut_assertok(run_command("ls", 0));
+ ut_assert_skip_to_linen("DIR ");
+ console_record_reset_enable();
+
+ /* cd back to root */
+ ut_assertok(run_command("cd /", 0));
+ ut_assert_console_end();
+ ut_assertok(run_command("pwd", 0));
+ ut_assert_nextline("/");
+ ut_assert_console_end();
+
+ ut_assertok(run_command("umount /host", 0));
+ ut_assert_console_end();
+
+ return 0;
+}
+DM_TEST(dm_test_vfs_cd, UTF_SCAN_FDT);
+
+/* Test multi-component path resolution (files in subdirectories) */
+static int dm_test_vfs_path(struct unit_test_state *uts)
+{
+ ut_assertok(vfs_init());
+
+ ut_assertok(run_command("mount hostfs /host", 0));
+ ut_assert_console_end();
+
+ /* stat a file in a subdirectory */
+ ut_assertok(run_command("stat /host/cmd/vfs.c", 0));
+ ut_assert_nextline(" File: vfs.c");
+ ut_assert_nextlinen(" Size: ");
+ ut_assert_nextline(" Type: regular file");
+ console_record_reset_enable();
+
+ /* load from a subdirectory */
+ ut_assertok(run_command("load 1000000 /host/cmd/vfs.c", 0));
+ ut_assert_nextlinen("%s", "");
+ ut_assert_console_end();
+
+ /* ls a nested subdirectory */
+ ut_assertok(run_command("ls /host/arch", 0));
+ ut_assert_skip_to_linen("DIR ");
+ console_record_reset_enable();
+
+ ut_assertok(run_command("umount /host", 0));
+ ut_assert_console_end();
+
+ return 0;
+}
+DM_TEST(dm_test_vfs_path, UTF_SCAN_FDT);
+
+/* Test the cp command via VFS */
+static int dm_test_vfs_cp(struct unit_test_state *uts)
+{
+ char buf[32];
+
+ ut_assertok(vfs_init());
+ ut_assertok(run_command("mount hostfs /host", 0));
+ ut_assert_console_end();
+
+ /* Write a source file */
+ memcpy(map_sysmem(0x1000, 5), "hello", 5);
+ ut_assertok(run_command("save 1000 /host/.cp_src 5", 0));
+ ut_assert_nextline("5 bytes written");
+ ut_assert_console_end();
+
+ /* Copy it */
+ ut_assertok(run_command("fs cp /host/.cp_src /host/.cp_dst", 0));
+ ut_assert_nextline("5 bytes copied");
+ ut_assert_console_end();
+
+ /* Read back the copy and verify */
+ memset(map_sysmem(0x2000, 8), 0, 8);
+ ut_assertok(run_command("load 2000 /host/.cp_dst", 0));
+ ut_assert_nextline("5 bytes read");
+ ut_assert_console_end();
+
+ memcpy(buf, map_sysmem(0x2000, 5), 5);
+ buf[5] = '\0';
+ ut_asserteq_str("hello", buf);
+
+ os_unlink(".cp_src");
+ os_unlink(".cp_dst");
+
+ ut_assertok(run_command("umount /host", 0));
+ ut_assert_console_end();
+
+ return 0;
+}
+DM_TEST(dm_test_vfs_cp, UTF_SCAN_FDT);
+
+/* Test the stat command via VFS */
+static int dm_test_vfs_stat(struct unit_test_state *uts)
+{
+ ut_assertok(vfs_init());
+
+ ut_assertok(run_command("mount hostfs /host", 0));
+ ut_assert_console_end();
+
+ /* stat a regular file */
+ ut_assertok(run_command("stat /host/README", 0));
+ ut_assert_nextline(" File: README");
+ ut_assert_nextlinen(" Size: ");
+ ut_assert_nextline(" Type: regular file");
+ console_record_reset_enable();
+
+ /* stat a directory */
+ ut_assertok(run_command("stat /host/cmd", 0));
+ ut_assert_nextline(" File: cmd");
+ ut_assert_nextlinen(" Size: ");
+ ut_assert_nextline(" Type: directory");
+ console_record_reset_enable();
+
+ /* stat non-existent file */
+ ut_asserteq(1, run_command("stat /host/does-not-exist", 0));
+ console_record_reset_enable();
+
+ ut_assertok(run_command("umount /host", 0));
+ ut_assert_console_end();
+
+ return 0;
+}
+DM_TEST(dm_test_vfs_stat, UTF_SCAN_FDT);
+
+/* Test save and load round-trip via VFS */
+static int dm_test_vfs_save(struct unit_test_state *uts)
+{
+ char buf[32];
+
+ ut_assertok(vfs_init());
+
+ /* Mount hostfs */
+ ut_assertok(run_command("mount hostfs /host", 0));
+ ut_assert_console_end();
+
+ /* Write a known pattern to a temp file */
+ memset(map_sysmem(0x1000, 16), 0, 16);
+ memcpy(map_sysmem(0x1000, 11), "hello world", 11);
+ ut_assertok(run_command("save 1000 /host/.vfs_test_tmp 0xb", 0));
+ ut_assert_nextline("11 bytes written");
+ ut_assert_console_end();
+
+ /* Read it back and verify */
+ memset(map_sysmem(0x2000, 16), 0, 16);
+ ut_assertok(run_command("load 2000 /host/.vfs_test_tmp", 0));
+ ut_assert_nextline("11 bytes read");
+ ut_assert_console_end();
+
+ memcpy(buf, map_sysmem(0x2000, 11), 11);
+ buf[11] = '\0';
+ ut_asserteq_str("hello world", buf);
+
+ /* Clean up the temp file */
+ os_unlink(".vfs_test_tmp");
+
+ ut_assertok(run_command("umount /host", 0));
+ ut_assert_console_end();
+
+ return 0;
+}
+DM_TEST(dm_test_vfs_save, UTF_SCAN_FDT);
+
#endif