[Concept,22/26] ext4l: Add symlink support

Message ID 20251231223008.3251711-23-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_ln() to create symbolic links. This uses the Linux ext4_symlink()
function which supports both fast symlinks (stored in inode) and regular
symlinks (stored in data blocks).

Fix the fscrypt_prepare_symlink() stub to properly init the disk_link
structure with the symlink target, which is required for symlink creation
to work correctly.

Add some notes about U-Boot's argument ordering with symlinks.

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

 fs/ext4l/ext4_uboot.h |  2 +-
 fs/ext4l/interface.c  | 48 ++++++++++++++++++++++++++++++++++
 fs/fs_legacy.c        |  2 +-
 include/ext4l.h       | 11 ++++++++
 test/fs/ext4l.c       | 60 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 121 insertions(+), 2 deletions(-)
  

Patch

diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h
index da59956eee8..5ea573fcfb8 100644
--- a/fs/ext4l/ext4_uboot.h
+++ b/fs/ext4l/ext4_uboot.h
@@ -1579,7 +1579,7 @@  static inline char *d_path(const struct path *path, char *buf, int buflen)
 #define fscrypt_dio_supported(i)		(1)
 #define fscrypt_has_permitted_context(p, c)	({ (void)(p); (void)(c); 1; })
 #define fscrypt_is_nokey_name(d)		({ (void)(d); 0; })
-#define fscrypt_prepare_symlink(d, s, l, m, dl)	({ (void)(d); (void)(s); (void)(l); (void)(m); (void)(dl); 0; })
+#define fscrypt_prepare_symlink(d, s, l, m, dl)	({ (void)(d); (void)(m); (dl)->name = (unsigned char *)(s); (dl)->len = (l) + 1; 0; })
 #define fscrypt_encrypt_symlink(i, s, l, d)	({ (void)(i); (void)(s); (void)(l); (void)(d); 0; })
 #define fscrypt_prepare_link(o, d, n)		({ (void)(o); (void)(d); (void)(n); 0; })
 #define fscrypt_prepare_rename(od, ode, nd, nde, f) ({ (void)(od); (void)(ode); (void)(nd); (void)(nde); (void)(f); 0; })
diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c
index 010db3a3631..c9dd25dd7b4 100644
--- a/fs/ext4l/interface.c
+++ b/fs/ext4l/interface.c
@@ -1249,6 +1249,54 @@  out:
 	return ret;
 }
 
+int ext4l_ln(const char *filename, const char *linkname)
+{
+	struct dentry *dentry, *dir_dentry;
+	char *path_copy;
+	int ret;
+
+	/*
+	 * Note: The parameter naming follows U-Boot's convention:
+	 * - filename: the target file the link should point to
+	 * - linkname: the path of the symlink to create
+	 */
+	if (!filename)
+		return -EINVAL;
+
+	ret = ext4l_resolve_file(linkname, &dir_dentry, &dentry, &path_copy);
+	if (ret)
+		return ret;
+
+	if (dentry->d_inode) {
+		/* File already exists */
+		ret = -EEXIST;
+		goto out;
+	}
+
+	/* Create the symlink - filename is what the link points to */
+	ret = ext4_symlink(&nop_mnt_idmap, dir_dentry->d_inode, dentry,
+			   filename);
+	if (ret)
+		goto out;
+
+	/* 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:
+	kfree(dentry);
+	kfree(dir_dentry);
+	free(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 b53d420f279..a325c2631d4 100644
--- a/fs/fs_legacy.c
+++ b/fs/fs_legacy.c
@@ -290,7 +290,7 @@  static struct fstype_info fstypes[] = {
 		.closedir = ext4l_closedir,
 		.unlink = ext4l_op_ptr(ext4l_unlink, fs_unlink_unsupported),
 		.mkdir = ext4l_op_ptr(ext4l_mkdir, fs_mkdir_unsupported),
-		.ln = fs_ln_unsupported,
+		.ln = ext4l_op_ptr(ext4l_ln, fs_ln_unsupported),
 		.rename = fs_rename_unsupported,
 		.statfs = ext4l_statfs,
 	},
diff --git a/include/ext4l.h b/include/ext4l.h
index 1a12ba1ac1c..d0e420c8da2 100644
--- a/include/ext4l.h
+++ b/include/ext4l.h
@@ -111,6 +111,17 @@  int ext4l_unlink(const char *filename);
  */
 int ext4l_mkdir(const char *dirname);
 
+/**
+ * ext4l_ln() - Create a symbolic link
+ *
+ * @filename: Path of symlink to create
+ * @target: Target path the symlink points to
+ * Return: 0 on success, -EEXIST if file already exists,
+ *	   -ENOTDIR if parent is not a directory, -EROFS if read-only,
+ *	   negative on other errors
+ */
+int ext4l_ln(const char *filename, const char *target);
+
 /**
  * ext4l_get_uuid() - Get the filesystem UUID
  *
diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c
index 5930e4234ba..1d46a752f32 100644
--- a/test/fs/ext4l.c
+++ b/test/fs/ext4l.c
@@ -526,3 +526,63 @@  static int fs_test_ext4l_mkdir_norun(struct unit_test_state *uts)
 }
 FS_TEST_ARGS(fs_test_ext4l_mkdir_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
 	     { "fs_image", UT_ARG_STR });
+
+/**
+ * fs_test_ext4l_ln_norun() - Test ext4l_ln function
+ *
+ * Verifies that ext4l can create symbolic links on the filesystem.
+ *
+ * Arguments:
+ *   fs_image: Path to the ext4 filesystem image
+ */
+static int fs_test_ext4l_ln_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(EXT4L_ARG_IMAGE);
+	static int test_counter;
+	char link_name[32];
+	const char *target = "/testfile.txt";
+	loff_t size;
+	loff_t actread;
+	char buf[32];
+
+	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 symlink names to avoid issues with test re-runs */
+	snprintf(link_name, sizeof(link_name), "/testlink%d", test_counter);
+	test_counter++;
+
+	/*
+	 * Create a symbolic link. ext4l_ln follows U-Boot's ln command
+	 * convention: ext4l_ln(target, linkname) creates linkname pointing
+	 * to target.
+	 */
+	ut_assertok(ext4l_ln(target, link_name));
+
+	/* Verify symlink exists */
+	ut_asserteq(1, ext4l_exists(link_name));
+
+	/*
+	 * Size through symlink should be target file's size (12 bytes),
+	 * since ext4l_resolve_path follows symlinks (like stat, not lstat)
+	 */
+	ut_assertok(ext4l_size(link_name, &size));
+	ut_asserteq(12, size);
+
+	/* Verify we can read through the symlink */
+	memset(buf, '\0', sizeof(buf));
+	ut_assertok(ext4l_read(link_name, buf, 0, 0, &actread));
+	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 symlink in non-existent parent returns -ENOENT */
+	ut_asserteq(-ENOENT, ext4l_ln(target, "/nonexistent/link"));
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_ext4l_ln_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
+	     { "fs_image", UT_ARG_STR });