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