[Concept,v2,27/30] ext4l: Update symlink to replace existing files

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

Commit Message

Simon Glass Jan. 2, 2026, 12:50 a.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

The ext4l_ln() function returned -EEXIST when creating a symlink where
a file already exists. This differs from the old ext4 implementation
which deletes any existing file before creating the symlink (like ln -sf
behaviour).

Update ext4l_ln() to match this behaviour by calling __ext4_unlink() to
remove any existing non-directory file before creating the symlink.
Directories cannot be replaced with symlinks and return -EISDIR.

This allows test_symlink3 to pass.

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

(no changes since v1)

 fs/ext4l/interface.c | 18 +++++++++++++++---
 include/ext4l.h      |  5 ++++-
 test/fs/ext4l.c      |  4 ++--
 3 files changed, 21 insertions(+), 6 deletions(-)
  

Patch

diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c
index c9dd25dd7b4..acd9ba5511e 100644
--- a/fs/ext4l/interface.c
+++ b/fs/ext4l/interface.c
@@ -1268,9 +1268,21 @@  int ext4l_ln(const char *filename, const char *linkname)
 		return ret;
 
 	if (dentry->d_inode) {
-		/* File already exists */
-		ret = -EEXIST;
-		goto out;
+		/* File already exists - delete it first (like ln -sf) */
+		if (S_ISDIR(dentry->d_inode->i_mode)) {
+			/* Cannot replace a directory with a symlink */
+			ret = -EISDIR;
+			goto out;
+		}
+
+		ret = __ext4_unlink(dir_dentry->d_inode, &dentry->d_name,
+				    dentry->d_inode, dentry);
+		if (ret)
+			goto out;
+
+		/* Release inode to free data blocks */
+		iput(dentry->d_inode);
+		dentry->d_inode = NULL;
 	}
 
 	/* Create the symlink - filename is what the link points to */
diff --git a/include/ext4l.h b/include/ext4l.h
index d0e420c8da2..882a27dad42 100644
--- a/include/ext4l.h
+++ b/include/ext4l.h
@@ -114,9 +114,12 @@  int ext4l_mkdir(const char *dirname);
 /**
  * ext4l_ln() - Create a symbolic link
  *
+ * Creates the symlink, replacing any existing file (like ln -sf).
+ * Refuses to replace a directory.
+ *
  * @filename: Path of symlink to create
  * @target: Target path the symlink points to
- * Return: 0 on success, -EEXIST if file already exists,
+ * Return: 0 on success, -EISDIR if target is a directory,
  *	   -ENOTDIR if parent is not a directory, -EROFS if read-only,
  *	   negative on other errors
  */
diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c
index 1d46a752f32..c1d10dcc816 100644
--- a/test/fs/ext4l.c
+++ b/test/fs/ext4l.c
@@ -576,8 +576,8 @@  static int fs_test_ext4l_ln_norun(struct unit_test_state *uts)
 	ut_asserteq(12, actread);
 	ut_asserteq_str("hello world\n", buf);
 
-	/* Verify creating duplicate returns -EEXIST */
-	ut_asserteq(-EEXIST, ext4l_ln(target, link_name));
+	/* Verify creating duplicate succeeds (like ln -sf) */
+	ut_assertok(ext4l_ln(target, link_name));
 
 	/* Verify creating symlink in non-existent parent returns -ENOENT */
 	ut_asserteq(-ENOENT, ext4l_ln(target, "/nonexistent/link"));