@@ -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; })
@@ -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);
@@ -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,
},
@@ -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
*
@@ -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 });