[Concept,v2,25/30] ext4l: Add mkdir support for directory creation

Message ID 20260102005112.552256-26-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>

Implement ext4l_mkdir() to create directories on ext4 filesystems.
The function parses the path to extract the parent directory and
basename, resolves the parent inode, checks for existing entries,
and calls the Linux ext4_mkdir() function to create the directory.

Hook ext4l_mkdir into the filesystem layer via the .mkdir callback
in fs_legacy.c, enabling the standard 'mkdir' command to work with
ext4l filesystems.

Add a unit test that verifies directory creation, duplicate detection
(-EEXIST), nested directory creation, and error handling for
non-existent parent directories (-ENOENT).

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

(no changes since v1)

 fs/ext4l/interface.c | 43 ++++++++++++++++++++++++++++++++++++++++
 fs/fs_legacy.c       |  2 +-
 include/ext4l.h      | 10 ++++++++++
 test/fs/ext4l.c      | 47 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 101 insertions(+), 1 deletion(-)
  

Patch

diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c
index 442617a0d3e..010db3a3631 100644
--- a/fs/ext4l/interface.c
+++ b/fs/ext4l/interface.c
@@ -1206,6 +1206,49 @@  out:
 	return ret;
 }
 
+int ext4l_mkdir(const char *dirname)
+{
+	struct dentry *dentry, *dir_dentry, *result;
+	char *path_copy;
+	int ret;
+
+	ret = ext4l_resolve_file(dirname, &dir_dentry, &dentry, &path_copy);
+	if (ret)
+		return ret;
+
+	if (dentry->d_inode) {
+		/* Directory already exists */
+		ret = -EEXIST;
+		goto out;
+	}
+
+	/* Create the directory with mode 0755 (rwxr-xr-x) */
+	result = ext4_mkdir(&nop_mnt_idmap, dir_dentry->d_inode, dentry,
+			    S_IFDIR | 0755);
+	if (IS_ERR(result)) {
+		ret = PTR_ERR(result);
+		goto out;
+	}
+
+	ret = 0;
+
+	/* 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 1eb79b338ee..b53d420f279 100644
--- a/fs/fs_legacy.c
+++ b/fs/fs_legacy.c
@@ -289,7 +289,7 @@  static struct fstype_info fstypes[] = {
 		.readdir = ext4l_readdir,
 		.closedir = ext4l_closedir,
 		.unlink = ext4l_op_ptr(ext4l_unlink, fs_unlink_unsupported),
-		.mkdir = fs_mkdir_unsupported,
+		.mkdir = ext4l_op_ptr(ext4l_mkdir, fs_mkdir_unsupported),
 		.ln = fs_ln_unsupported,
 		.rename = fs_rename_unsupported,
 		.statfs = ext4l_statfs,
diff --git a/include/ext4l.h b/include/ext4l.h
index abcf17f99ad..1a12ba1ac1c 100644
--- a/include/ext4l.h
+++ b/include/ext4l.h
@@ -101,6 +101,16 @@  int ext4l_write(const char *filename, void *buf, loff_t offset, loff_t len,
  */
 int ext4l_unlink(const char *filename);
 
+/**
+ * ext4l_mkdir() - Create a directory
+ *
+ * @dirname: Path of directory to create
+ * Return: 0 on success, -EEXIST if directory already exists,
+ *	   -ENOTDIR if parent is not a directory, -EROFS if read-only,
+ *	   negative on other errors
+ */
+int ext4l_mkdir(const char *dirname);
+
 /**
  * ext4l_get_uuid() - Get the filesystem UUID
  *
diff --git a/test/fs/ext4l.c b/test/fs/ext4l.c
index dec6e3340bb..5930e4234ba 100644
--- a/test/fs/ext4l.c
+++ b/test/fs/ext4l.c
@@ -479,3 +479,50 @@  static int fs_test_ext4l_unlink_norun(struct unit_test_state *uts)
 }
 FS_TEST_ARGS(fs_test_ext4l_unlink_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
 	     { "fs_image", UT_ARG_STR });
+
+/**
+ * fs_test_ext4l_mkdir_norun() - Test ext4l_mkdir function
+ *
+ * Verifies that ext4l can create directories on the filesystem.
+ *
+ * Arguments:
+ *   fs_image: Path to the ext4 filesystem image
+ */
+static int fs_test_ext4l_mkdir_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(EXT4L_ARG_IMAGE);
+	static int test_counter;
+	char dir_name[32];
+	char subdir_name[64];
+	int ret;
+
+	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 directory names to avoid issues with test re-runs */
+	snprintf(dir_name, sizeof(dir_name), "/testdir%d", test_counter);
+	snprintf(subdir_name, sizeof(subdir_name), "%s/subdir", dir_name);
+	test_counter++;
+
+	/* Create a new directory */
+	ret = ext4l_mkdir(dir_name);
+	ut_assertok(ret);
+
+	/* Verify directory exists */
+	ut_asserteq(1, ext4l_exists(dir_name));
+
+	/* Verify creating duplicate returns -EEXIST */
+	ut_asserteq(-EEXIST, ext4l_mkdir(dir_name));
+
+	/* Create nested directory */
+	ut_assertok(ext4l_mkdir(subdir_name));
+	ut_asserteq(1, ext4l_exists(subdir_name));
+
+	/* Verify creating directory in non-existent parent returns -ENOENT */
+	ut_asserteq(-ENOENT, ext4l_mkdir("/nonexistent/dir"));
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_ext4l_mkdir_norun, UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
+	     { "fs_image", UT_ARG_STR });