[Concept,24/26] ext4l: Add rename support

Message ID 20251231223008.3251711-25-sjg@u-boot.org
State New
Headers
Series ext4l: Add write support (part L) |

Commit Message

Simon Glass Dec. 31, 2025, 10:29 p.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

Add ext4l_rename() to rename files and directories, including moves
across directories. This uses the Linux ext4_rename() function.

Also fix the symlink test to verify reading through symlinks works
correctly, since ext4l_resolve_path follows symlinks (stat behavior).

Add Python test wrappers for mkdir, ln, and rename tests.

Co-developed-by: Claude <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 fs/ext4l/interface.c                | 54 +++++++++++++++++++++++++
 fs/fs_legacy.c                      |  2 +-
 include/ext4l.h                     | 11 ++++++
 test/fs/ext4l.c                     | 61 +++++++++++++++++++++++++++++
 test/py/tests/test_fs/test_ext4l.py | 15 +++++++
 5 files changed, 142 insertions(+), 1 deletion(-)
  

Patch

diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c
index acd9ba5511e..f581c32359e 100644
--- a/fs/ext4l/interface.c
+++ b/fs/ext4l/interface.c
@@ -1309,6 +1309,60 @@  out:
 	return ret;
 }
 
+int ext4l_rename(const char *old_path, const char *new_path)
+{
+	struct dentry *old_dentry, *new_dentry;
+	struct dentry *old_dir_dentry, *new_dir_dentry;
+	char *old_path_copy, *new_path_copy;
+	int ret;
+
+	/* Check new_path before ext4l_resolve_file checks old_path */
+	if (!new_path)
+		return -EINVAL;
+
+	ret = ext4l_resolve_file(old_path, &old_dir_dentry, &old_dentry,
+				 &old_path_copy);
+	if (ret)
+		return ret;
+
+	if (!old_dentry->d_inode) {
+		/* Source file doesn't exist */
+		ret = -ENOENT;
+		goto out_old;
+	}
+
+	ret = ext4l_resolve_file(new_path, &new_dir_dentry, &new_dentry,
+				 &new_path_copy);
+	if (ret)
+		goto out_old;
+
+	/* Perform the rename */
+	ret = ext4_rename(&nop_mnt_idmap, old_dir_dentry->d_inode, old_dentry,
+			  new_dir_dentry->d_inode, new_dentry, 0);
+	if (ret)
+		goto out_new;
+
+	/* Sync all dirty buffers */
+	{
+		int sync_ret = bh_cache_sync();
+
+		if (sync_ret)
+			ret = sync_ret;
+		/* Commit superblock with updated free counts */
+		ext4_commit_super(ext4l_sb);
+	}
+
+out_new:
+	kfree(new_dentry);
+	kfree(new_dir_dentry);
+	free(new_path_copy);
+out_old:
+	kfree(old_dentry);
+	kfree(old_dir_dentry);
+	free(old_path_copy);
+	return ret;
+}
+
 void ext4l_close(void)
 {
 	ext4l_close_internal(false);
diff --git a/fs/fs_legacy.c b/fs/fs_legacy.c
index a325c2631d4..6e1bb449410 100644
--- a/fs/fs_legacy.c
+++ b/fs/fs_legacy.c
@@ -291,7 +291,7 @@  static struct fstype_info fstypes[] = {
 		.unlink = ext4l_op_ptr(ext4l_unlink, fs_unlink_unsupported),
 		.mkdir = ext4l_op_ptr(ext4l_mkdir, fs_mkdir_unsupported),
 		.ln = ext4l_op_ptr(ext4l_ln, fs_ln_unsupported),
-		.rename = fs_rename_unsupported,
+		.rename = ext4l_op_ptr(ext4l_rename, fs_rename_unsupported),
 		.statfs = ext4l_statfs,
 	},
 #endif
diff --git a/include/ext4l.h b/include/ext4l.h
index 882a27dad42..59990a3a6f4 100644
--- a/include/ext4l.h
+++ b/include/ext4l.h
@@ -125,6 +125,17 @@  int ext4l_mkdir(const char *dirname);
  */
 int ext4l_ln(const char *filename, const char *target);
 
+/**
+ * ext4l_rename() - Rename a file or directory
+ *
+ * @old_path: Current path of file or directory
+ * @new_path: New path for file or directory
+ * Return: 0 on success, -ENOENT if source not found,
+ *	   -ENOTDIR if parent is not a directory, -EROFS if read-only,
+ *	   negative on other errors
+ */
+int ext4l_rename(const char *old_path, const char *new_path);
+
 /**
  * ext4l_get_uuid() - Get the filesystem UUID
  *
diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c
index c1d10dcc816..0843ba1d5ba 100644
--- a/test/fs/ext4l.c
+++ b/test/fs/ext4l.c
@@ -586,3 +586,64 @@  static int fs_test_ext4l_ln_norun(struct unit_test_state *uts)
 }
 FS_TEST_ARGS(fs_test_ext4l_ln_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
 	     { "fs_image", UT_ARG_STR });
+
+/**
+ * fs_test_ext4l_rename_norun() - Test ext4l_rename function
+ *
+ * Verifies that ext4l can rename files and directories on the filesystem.
+ *
+ * Arguments:
+ *   fs_image: Path to the ext4 filesystem image
+ */
+static int fs_test_ext4l_rename_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(EXT4L_ARG_IMAGE);
+	const char *test_data = "rename test\n";
+	size_t test_len = strlen(test_data);
+	static int test_counter;
+	char old_name[32], new_name[32], subdir_name[32], moved_name[64];
+	loff_t actwrite, size;
+
+	ut_assertnonnull(fs_image);
+	ut_assertok(run_commandf("host bind 0 %s", fs_image));
+	ut_assertok(fs_set_blk_dev("host", "0", FS_TYPE_ANY));
+
+	/* Use unique names to avoid issues with test re-runs */
+	snprintf(old_name, sizeof(old_name), "/renameme%d.txt", test_counter);
+	snprintf(new_name, sizeof(new_name), "/renamed%d.txt", test_counter);
+	snprintf(subdir_name, sizeof(subdir_name), "/renamedir%d", test_counter);
+	snprintf(moved_name, sizeof(moved_name), "%s/moved.txt", subdir_name);
+	test_counter++;
+
+	/* Create a file to rename */
+	ut_assertok(ext4l_write(old_name, (void *)test_data, 0,
+				test_len, &actwrite));
+	ut_asserteq(test_len, actwrite);
+
+	/* Verify file exists */
+	ut_asserteq(1, ext4l_exists(old_name));
+
+	/* Rename the file */
+	ut_assertok(ext4l_rename(old_name, new_name));
+
+	/* Verify old name no longer exists, new name does */
+	ut_asserteq(0, ext4l_exists(old_name));
+	ut_asserteq(1, ext4l_exists(new_name));
+
+	/* Verify file size is preserved */
+	ut_assertok(ext4l_size(new_name, &size));
+	ut_asserteq(test_len, size);
+
+	/* Verify renaming non-existent file returns -ENOENT */
+	ut_asserteq(-ENOENT, ext4l_rename("/nonexistent", "/newname"));
+
+	/* Test cross-directory rename */
+	ut_assertok(ext4l_mkdir(subdir_name));
+	ut_assertok(ext4l_rename(new_name, moved_name));
+	ut_asserteq(0, ext4l_exists(new_name));
+	ut_asserteq(1, ext4l_exists(moved_name));
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_ext4l_rename_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
+	     { "fs_image", UT_ARG_STR });
diff --git a/test/py/tests/test_fs/test_ext4l.py b/test/py/tests/test_fs/test_ext4l.py
index edea38f7faf..0930ebd01ea 100644
--- a/test/py/tests/test_fs/test_ext4l.py
+++ b/test/py/tests/test_fs/test_ext4l.py
@@ -128,3 +128,18 @@  class TestExt4l:
         """Test that ext4l can delete files."""
         with ubman.log.section('Test ext4l unlink'):
             ubman.run_ut('fs', 'fs_test_ext4l_unlink', fs_image=ext4_image)
+
+    def test_mkdir(self, ubman, ext4_image):
+        """Test that ext4l can create directories."""
+        with ubman.log.section('Test ext4l mkdir'):
+            ubman.run_ut('fs', 'fs_test_ext4l_mkdir', fs_image=ext4_image)
+
+    def test_ln(self, ubman, ext4_image):
+        """Test that ext4l can create symbolic links."""
+        with ubman.log.section('Test ext4l ln'):
+            ubman.run_ut('fs', 'fs_test_ext4l_ln', fs_image=ext4_image)
+
+    def test_rename(self, ubman, ext4_image):
+        """Test that ext4l can rename files and directories."""
+        with ubman.log.section('Test ext4l rename'):
+            ubman.run_ut('fs', 'fs_test_ext4l_rename', fs_image=ext4_image)