diff mbox series

[4/5] samples/kernfs: Allow creating and removing directories

Message ID 20250121154707.43185-1-me@davidreaver.com (mailing list archive)
State New
Headers show
Series samples/kernfs: Add a pseudo-filesystem to demonstrate kernfs usage | expand

Commit Message

David Reaver Jan. 21, 2025, 3:47 p.m. UTC
Users can mkdir and rmdir sample_kernfs directories, similar to how cgroups
are added and removed in the cgroup pseudo-filesystem. New directories
automatically get a counter file.

kernfs doesn't expose functions to traverse child nodes. We demonstrate how
to keep track of child nodes ourselves in sample_kernfs_directory.

Removing a directory is surprisingly tricky and can deadlock if you use
kernfs_remove() instead of kernfs_remove_self(), so a comment explains the
motivation for using kernfs_remove_self(). I also added a comment
explaining the lack of locking when manipulating the subdirs/children
lists.

Signed-off-by: David Reaver <me@davidreaver.com>
---
 samples/kernfs/sample_kernfs.c | 94 ++++++++++++++++++++++++++++++++--
 1 file changed, 91 insertions(+), 3 deletions(-)
diff mbox series

Patch

diff --git a/samples/kernfs/sample_kernfs.c b/samples/kernfs/sample_kernfs.c
index b6d44fc3b935..e632b5f66924 100644
--- a/samples/kernfs/sample_kernfs.c
+++ b/samples/kernfs/sample_kernfs.c
@@ -17,9 +17,13 @@ 
 /**
  * struct sample_kernfs_directory - Represents a directory in the pseudo-filesystem
  * @count: Holds the current count in the counter file.
+ * @subdirs: Holds the list of this directory's subdirectories.
+ * @siblings: Used to add this dir to parent's subdirs list.
  */
 struct sample_kernfs_directory {
 	atomic64_t count;
+	struct list_head subdirs;
+	struct list_head siblings;
 };

 static struct sample_kernfs_directory *sample_kernfs_create_dir(void)
@@ -30,6 +34,9 @@  static struct sample_kernfs_directory *sample_kernfs_create_dir(void)
 	if (!dir)
 		return NULL;

+	INIT_LIST_HEAD(&dir->subdirs);
+	INIT_LIST_HEAD(&dir->siblings);
+
 	return dir;
 }

@@ -101,6 +108,87 @@  static int sample_kernfs_populate_dir(struct kernfs_node *dir_kn)
 	return 0;
 }

+static void sample_kernfs_remove_subtree(struct sample_kernfs_directory *dir)
+{
+	struct sample_kernfs_directory *child, *tmp;
+
+	/*
+	 * Recursively remove children. This approach is acceptable for this
+	 * sample since we expect the tree depth to remain small and manageable.
+	 * For real-world filesystems, an iterative approach should be used to
+	 * avoid stack overflows.
+	 *
+	 * Also, we could be more careful with locking our lists, but kernfs
+	 * holds a tree-wide lock before calling our rmdir, so we should be
+	 * safe.
+	 */
+	list_for_each_entry_safe(child, tmp, &dir->subdirs, siblings) {
+		sample_kernfs_remove_subtree(child);
+	}
+
+	/* Remove this directory from its parent's subdirs list */
+	list_del(&dir->siblings);
+
+	kfree(dir);
+}
+
+static int sample_kernfs_mkdir(struct kernfs_node *parent_kn, const char *name, umode_t mode)
+{
+	struct kernfs_node *dir_kn;
+	struct sample_kernfs_directory *dir, *parent_dir;
+	int ret;
+
+	dir = sample_kernfs_create_dir();
+	if (!dir)
+		return -ENOMEM;
+
+	/* dir gets stored in dir_kn->priv so we can access it later. */
+	dir_kn = kernfs_create_dir_ns(parent_kn, name, mode, current_fsuid(),
+				      current_fsgid(), dir, NULL);
+
+	if (IS_ERR(dir_kn)) {
+		ret = PTR_ERR(dir_kn);
+		goto err_free_dir;
+	}
+
+	ret = sample_kernfs_populate_dir(dir_kn);
+	if (ret)
+		goto err_free_dir_kn;
+
+	/* Add directory to parent->subdirs */
+	parent_dir = parent_kn->priv;
+	list_add(&dir->siblings, &parent_dir->subdirs);
+
+	return 0;
+
+err_free_dir_kn:
+	kernfs_remove(dir_kn);
+err_free_dir:
+	sample_kernfs_remove_subtree(dir);
+	return ret;
+}
+
+static int sample_kernfs_rmdir(struct kernfs_node *kn)
+{
+	struct sample_kernfs_directory *dir = kn->priv;
+
+	/*
+	 * kernfs_remove_self avoids a deadlock by breaking active protection;
+	 * see kernfs_break_active_protection(). This is required since
+	 * kernfs_iop_rmdir() holds a tree-wide lock.
+	 */
+	kernfs_remove_self(kn);
+
+	sample_kernfs_remove_subtree(dir);
+
+	return 0;
+}
+
+static struct kernfs_syscall_ops sample_kernfs_kf_syscall_ops = {
+	.mkdir		= sample_kernfs_mkdir,
+	.rmdir		= sample_kernfs_rmdir,
+};
+
 static void sample_kernfs_fs_context_free(struct fs_context *fc)
 {
 	struct kernfs_fs_context *kfc = fc->fs_private;
@@ -132,7 +220,7 @@  static int sample_kernfs_init_fs_context(struct fs_context *fc)
 	}

 	/* dir gets stored in root->priv so we can access it later. */
-	root = kernfs_create_root(NULL, 0, root_dir);
+	root = kernfs_create_root(&sample_kernfs_kf_syscall_ops, 0, root_dir);
 	if (IS_ERR(root)) {
 		err = PTR_ERR(root);
 		goto err_free_dir;
@@ -153,7 +241,7 @@  static int sample_kernfs_init_fs_context(struct fs_context *fc)
 err_free_root:
 	kernfs_destroy_root(root);
 err_free_dir:
-	kfree(root_dir);
+	sample_kernfs_remove_subtree(root_dir);
 err_free_kfc:
 	kfree(kfc);
 	return err;
@@ -167,7 +255,7 @@  static void sample_kernfs_kill_sb(struct super_block *sb)

 	kernfs_kill_sb(sb);
 	kernfs_destroy_root(root);
-	kfree(root_dir);
+	sample_kernfs_remove_subtree(root_dir);
 }

 static struct file_system_type sample_kernfs_fs_type = {