I'm building a generic firmware variable filesystem on top of kernfs
and I'd like to be able to create and unlink files.

The hooks are fairly straightforward. create() returns a kernfs_node*,
which is safe with regards to cleanup on error paths, because there
is no way that things can fail after that point in the current
implementation. However, currently O_EXCL is not implemented and
that may create failure paths, in which case we may need to revisit
this later.

Signed-off-by: Daniel Axtens <d...@axtens.net>
---
 fs/kernfs/dir.c        | 55 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/kernfs.h |  3 +++
 2 files changed, 58 insertions(+)

diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
index 016ba88f7335..74fe51dbd027 100644
--- a/fs/kernfs/dir.c
+++ b/fs/kernfs/dir.c
@@ -1175,6 +1175,59 @@ static int kernfs_iop_rename(struct inode *old_dir, 
struct dentry *old_dentry,
        return ret;
 }
 
+static int kernfs_iop_create(struct inode *dir, struct dentry *dentry,
+                            umode_t mode, bool excl)
+{
+       struct kernfs_node *parent = dir->i_private;
+       struct kernfs_node *kn;
+       struct kernfs_syscall_ops *scops = kernfs_root(parent)->syscall_ops;
+
+       if (!scops || !scops->create)
+               return -EPERM;
+
+       if (!kernfs_get_active(parent))
+               return -ENODEV;
+
+       // TODO: add some locking to ensure that scops->create
+       // is called only once, and possibly to handle the O_EXCL case
+       WARN_ONCE(excl, "excl unimplemented");
+
+       kn = scops->create(parent, dentry->d_name.name, mode);
+
+       if (!kn)
+               return -EPERM;
+
+       if (IS_ERR(kn))
+               return PTR_ERR(kn);
+
+       d_instantiate(dentry, kernfs_get_inode(dir->i_sb, kn));
+
+       return 0;
+}
+
+static int kernfs_iop_unlink(struct inode *dir, struct dentry *dentry)
+{
+       struct kernfs_node *parent = dir->i_private;
+       struct kernfs_node *kn = d_inode(dentry)->i_private;
+       struct kernfs_syscall_ops *scops = kernfs_root(parent)->syscall_ops;
+       int ret;
+
+
+       if (!scops || !scops->unlink)
+               return -EPERM;
+
+       if (!kernfs_get_active(parent))
+               return -ENODEV;
+
+       ret = scops->unlink(kn);
+       if (ret)
+               return ret;
+
+       drop_nlink(d_inode(dentry));
+       dput(dentry);
+       return 0;
+};
+
 const struct inode_operations kernfs_dir_iops = {
        .lookup         = kernfs_iop_lookup,
        .permission     = kernfs_iop_permission,
@@ -1185,6 +1238,8 @@ const struct inode_operations kernfs_dir_iops = {
        .mkdir          = kernfs_iop_mkdir,
        .rmdir          = kernfs_iop_rmdir,
        .rename         = kernfs_iop_rename,
+       .create         = kernfs_iop_create,
+       .unlink         = kernfs_iop_unlink,
 };
 
 static struct kernfs_node *kernfs_leftmost_descendant(struct kernfs_node *pos)
diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h
index 2bf477f86eb1..282b96acbd7e 100644
--- a/include/linux/kernfs.h
+++ b/include/linux/kernfs.h
@@ -179,6 +179,9 @@ struct kernfs_syscall_ops {
                      const char *new_name);
        int (*show_path)(struct seq_file *sf, struct kernfs_node *kn,
                         struct kernfs_root *root);
+       struct kernfs_node* (*create)(struct kernfs_node *parent,
+                                     const char *name, umode_t mode);
+       int (*unlink)(struct kernfs_node *kn);
 };
 
 struct kernfs_root {
-- 
2.19.1

Reply via email to