summaryrefslogtreecommitdiff
path: root/ipc/kdbus/fs.c
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
commit57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch)
tree5e910f0e82173f4ef4f51111366a3f1299037a7b /ipc/kdbus/fs.c
Initial import
Diffstat (limited to 'ipc/kdbus/fs.c')
-rw-r--r--ipc/kdbus/fs.c508
1 files changed, 508 insertions, 0 deletions
diff --git a/ipc/kdbus/fs.c b/ipc/kdbus/fs.c
new file mode 100644
index 000000000..09c480924
--- /dev/null
+++ b/ipc/kdbus/fs.c
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2013-2015 Kay Sievers
+ * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+ * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org>
+ * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com>
+ * Copyright (C) 2013-2015 Linux Foundation
+ *
+ * kdbus is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/dcache.h>
+#include <linux/fs.h>
+#include <linux/fsnotify.h>
+#include <linux/init.h>
+#include <linux/ipc_namespace.h>
+#include <linux/magic.h>
+#include <linux/module.h>
+#include <linux/mount.h>
+#include <linux/mutex.h>
+#include <linux/namei.h>
+#include <linux/pagemap.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include "bus.h"
+#include "domain.h"
+#include "endpoint.h"
+#include "fs.h"
+#include "handle.h"
+#include "node.h"
+
+#define kdbus_node_from_dentry(_dentry) \
+ ((struct kdbus_node *)(_dentry)->d_fsdata)
+
+static struct inode *fs_inode_get(struct super_block *sb,
+ struct kdbus_node *node);
+
+/*
+ * Directory Management
+ */
+
+static inline unsigned char kdbus_dt_type(struct kdbus_node *node)
+{
+ switch (node->type) {
+ case KDBUS_NODE_DOMAIN:
+ case KDBUS_NODE_BUS:
+ return DT_DIR;
+ case KDBUS_NODE_CONTROL:
+ case KDBUS_NODE_ENDPOINT:
+ return DT_REG;
+ }
+
+ return DT_UNKNOWN;
+}
+
+static int fs_dir_fop_iterate(struct file *file, struct dir_context *ctx)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ struct kdbus_node *parent = kdbus_node_from_dentry(dentry);
+ struct kdbus_node *old, *next = file->private_data;
+
+ /*
+ * kdbusfs directory iterator (modelled after sysfs/kernfs)
+ * When iterating kdbusfs directories, we iterate all children of the
+ * parent kdbus_node object. We use ctx->pos to store the hash of the
+ * child and file->private_data to store a reference to the next node
+ * object. If ctx->pos is not modified via llseek while you iterate a
+ * directory, then we use the file->private_data node pointer to
+ * directly access the next node in the tree.
+ * However, if you directly seek on the directory, we have to find the
+ * closest node to that position and cannot use our node pointer. This
+ * means iterating the rb-tree to find the closest match and start over
+ * from there.
+ * Note that hash values are not necessarily unique. Therefore, llseek
+ * is not guaranteed to seek to the same node that you got when you
+ * retrieved the position. Seeking to 0, 1, 2 and >=INT_MAX is safe,
+ * though. We could use the inode-number as position, but this would
+ * require another rb-tree for fast access. Kernfs and others already
+ * ignore those conflicts, so we should be fine, too.
+ */
+
+ if (!dir_emit_dots(file, ctx))
+ return 0;
+
+ /* acquire @next; if deactivated, or seek detected, find next node */
+ old = next;
+ if (next && ctx->pos == next->hash) {
+ if (kdbus_node_acquire(next))
+ kdbus_node_ref(next);
+ else
+ next = kdbus_node_next_child(parent, next);
+ } else {
+ next = kdbus_node_find_closest(parent, ctx->pos);
+ }
+ kdbus_node_unref(old);
+
+ while (next) {
+ /* emit @next */
+ file->private_data = next;
+ ctx->pos = next->hash;
+
+ kdbus_node_release(next);
+
+ if (!dir_emit(ctx, next->name, strlen(next->name), next->id,
+ kdbus_dt_type(next)))
+ return 0;
+
+ /* find next node after @next */
+ old = next;
+ next = kdbus_node_next_child(parent, next);
+ kdbus_node_unref(old);
+ }
+
+ file->private_data = NULL;
+ ctx->pos = INT_MAX;
+
+ return 0;
+}
+
+static loff_t fs_dir_fop_llseek(struct file *file, loff_t offset, int whence)
+{
+ struct inode *inode = file_inode(file);
+ loff_t ret;
+
+ /* protect f_off against fop_iterate */
+ mutex_lock(&inode->i_mutex);
+ ret = generic_file_llseek(file, offset, whence);
+ mutex_unlock(&inode->i_mutex);
+
+ return ret;
+}
+
+static int fs_dir_fop_release(struct inode *inode, struct file *file)
+{
+ kdbus_node_unref(file->private_data);
+ return 0;
+}
+
+static const struct file_operations fs_dir_fops = {
+ .read = generic_read_dir,
+ .iterate = fs_dir_fop_iterate,
+ .llseek = fs_dir_fop_llseek,
+ .release = fs_dir_fop_release,
+};
+
+static struct dentry *fs_dir_iop_lookup(struct inode *dir,
+ struct dentry *dentry,
+ unsigned int flags)
+{
+ struct dentry *dnew = NULL;
+ struct kdbus_node *parent;
+ struct kdbus_node *node;
+ struct inode *inode;
+
+ parent = kdbus_node_from_dentry(dentry->d_parent);
+ if (!kdbus_node_acquire(parent))
+ return NULL;
+
+ /* returns reference to _acquired_ child node */
+ node = kdbus_node_find_child(parent, dentry->d_name.name);
+ if (node) {
+ dentry->d_fsdata = node;
+ inode = fs_inode_get(dir->i_sb, node);
+ if (IS_ERR(inode))
+ dnew = ERR_CAST(inode);
+ else
+ dnew = d_splice_alias(inode, dentry);
+
+ kdbus_node_release(node);
+ }
+
+ kdbus_node_release(parent);
+ return dnew;
+}
+
+static const struct inode_operations fs_dir_iops = {
+ .permission = generic_permission,
+ .lookup = fs_dir_iop_lookup,
+};
+
+/*
+ * Inode Management
+ */
+
+static const struct inode_operations fs_inode_iops = {
+ .permission = generic_permission,
+};
+
+static struct inode *fs_inode_get(struct super_block *sb,
+ struct kdbus_node *node)
+{
+ struct inode *inode;
+
+ inode = iget_locked(sb, node->id);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+ if (!(inode->i_state & I_NEW))
+ return inode;
+
+ inode->i_private = kdbus_node_ref(node);
+ inode->i_mapping->a_ops = &empty_aops;
+ inode->i_mode = node->mode & S_IALLUGO;
+ inode->i_atime = inode->i_ctime = inode->i_mtime = CURRENT_TIME;
+ inode->i_uid = node->uid;
+ inode->i_gid = node->gid;
+
+ switch (node->type) {
+ case KDBUS_NODE_DOMAIN:
+ case KDBUS_NODE_BUS:
+ inode->i_mode |= S_IFDIR;
+ inode->i_op = &fs_dir_iops;
+ inode->i_fop = &fs_dir_fops;
+ set_nlink(inode, 2);
+ break;
+ case KDBUS_NODE_CONTROL:
+ case KDBUS_NODE_ENDPOINT:
+ inode->i_mode |= S_IFREG;
+ inode->i_op = &fs_inode_iops;
+ inode->i_fop = &kdbus_handle_ops;
+ break;
+ }
+
+ unlock_new_inode(inode);
+
+ return inode;
+}
+
+/*
+ * Superblock Management
+ */
+
+static int fs_super_dop_revalidate(struct dentry *dentry, unsigned int flags)
+{
+ struct kdbus_node *node;
+
+ /* Force lookup on negatives */
+ if (!dentry->d_inode)
+ return 0;
+
+ node = kdbus_node_from_dentry(dentry);
+
+ /* see whether the node has been removed */
+ if (!kdbus_node_is_active(node))
+ return 0;
+
+ return 1;
+}
+
+static void fs_super_dop_release(struct dentry *dentry)
+{
+ kdbus_node_unref(dentry->d_fsdata);
+}
+
+static const struct dentry_operations fs_super_dops = {
+ .d_revalidate = fs_super_dop_revalidate,
+ .d_release = fs_super_dop_release,
+};
+
+static void fs_super_sop_evict_inode(struct inode *inode)
+{
+ struct kdbus_node *node = kdbus_node_from_inode(inode);
+
+ truncate_inode_pages_final(&inode->i_data);
+ clear_inode(inode);
+ kdbus_node_unref(node);
+}
+
+static const struct super_operations fs_super_sops = {
+ .statfs = simple_statfs,
+ .drop_inode = generic_delete_inode,
+ .evict_inode = fs_super_sop_evict_inode,
+};
+
+static int fs_super_fill(struct super_block *sb)
+{
+ struct kdbus_domain *domain = sb->s_fs_info;
+ struct inode *inode;
+ int ret;
+
+ sb->s_blocksize = PAGE_CACHE_SIZE;
+ sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
+ sb->s_magic = KDBUS_SUPER_MAGIC;
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+ sb->s_op = &fs_super_sops;
+ sb->s_time_gran = 1;
+
+ inode = fs_inode_get(sb, &domain->node);
+ if (IS_ERR(inode))
+ return PTR_ERR(inode);
+
+ sb->s_root = d_make_root(inode);
+ if (!sb->s_root) {
+ /* d_make_root iput()s the inode on failure */
+ return -ENOMEM;
+ }
+
+ /* sb holds domain reference */
+ sb->s_root->d_fsdata = &domain->node;
+ sb->s_d_op = &fs_super_dops;
+
+ /* sb holds root reference */
+ domain->dentry = sb->s_root;
+
+ if (!kdbus_node_activate(&domain->node))
+ return -ESHUTDOWN;
+
+ ret = kdbus_domain_populate(domain, KDBUS_MAKE_ACCESS_WORLD);
+ if (ret < 0)
+ return ret;
+
+ sb->s_flags |= MS_ACTIVE;
+ return 0;
+}
+
+static void fs_super_kill(struct super_block *sb)
+{
+ struct kdbus_domain *domain = sb->s_fs_info;
+
+ if (domain) {
+ kdbus_node_deactivate(&domain->node);
+ domain->dentry = NULL;
+ }
+
+ kill_anon_super(sb);
+ kdbus_domain_unref(domain);
+}
+
+static int fs_super_set(struct super_block *sb, void *data)
+{
+ int ret;
+
+ ret = set_anon_super(sb, data);
+ if (!ret)
+ sb->s_fs_info = data;
+
+ return ret;
+}
+
+static struct dentry *fs_super_mount(struct file_system_type *fs_type,
+ int flags, const char *dev_name,
+ void *data)
+{
+ struct kdbus_domain *domain;
+ struct super_block *sb;
+ int ret;
+
+ domain = kdbus_domain_new(KDBUS_MAKE_ACCESS_WORLD);
+ if (IS_ERR(domain))
+ return ERR_CAST(domain);
+
+ sb = sget(fs_type, NULL, fs_super_set, flags, domain);
+ if (IS_ERR(sb)) {
+ kdbus_node_deactivate(&domain->node);
+ kdbus_domain_unref(domain);
+ return ERR_CAST(sb);
+ }
+
+ WARN_ON(sb->s_fs_info != domain);
+ WARN_ON(sb->s_root);
+
+ ret = fs_super_fill(sb);
+ if (ret < 0) {
+ /* calls into ->kill_sb() when done */
+ deactivate_locked_super(sb);
+ return ERR_PTR(ret);
+ }
+
+ return dget(sb->s_root);
+}
+
+static struct file_system_type fs_type = {
+ .name = KBUILD_MODNAME "fs",
+ .owner = THIS_MODULE,
+ .mount = fs_super_mount,
+ .kill_sb = fs_super_kill,
+ .fs_flags = FS_USERNS_MOUNT,
+};
+
+/**
+ * kdbus_fs_init() - register kdbus filesystem
+ *
+ * This registers a filesystem with the VFS layer. The filesystem is called
+ * `KBUILD_MODNAME "fs"', which usually resolves to `kdbusfs'. The nameing
+ * scheme allows to set KBUILD_MODNAME to "kdbus2" and you will get an
+ * independent filesystem for developers.
+ *
+ * Each mount of the kdbusfs filesystem has an kdbus_domain attached.
+ * Operations on this mount will only affect the attached domain. On each mount
+ * a new domain is automatically created and used for this mount exclusively.
+ * If you want to share a domain across multiple mounts, you need to bind-mount
+ * it.
+ *
+ * Mounts of kdbusfs (with a different domain each) are unrelated to each other
+ * and will never have any effect on any domain but their own.
+ *
+ * Return: 0 on success, negative error otherwise.
+ */
+int kdbus_fs_init(void)
+{
+ return register_filesystem(&fs_type);
+}
+
+/**
+ * kdbus_fs_exit() - unregister kdbus filesystem
+ *
+ * This does the reverse to kdbus_fs_init(). It unregisters the kdbusfs
+ * filesystem from VFS and cleans up any allocated resources.
+ */
+void kdbus_fs_exit(void)
+{
+ unregister_filesystem(&fs_type);
+}
+
+/* acquire domain of @node, making sure all ancestors are active */
+static struct kdbus_domain *fs_acquire_domain(struct kdbus_node *node)
+{
+ struct kdbus_domain *domain;
+ struct kdbus_node *iter;
+
+ /* caller must guarantee that @node is linked */
+ for (iter = node; iter->parent; iter = iter->parent)
+ if (!kdbus_node_is_active(iter->parent))
+ return NULL;
+
+ /* root nodes are always domains */
+ if (WARN_ON(iter->type != KDBUS_NODE_DOMAIN))
+ return NULL;
+
+ domain = kdbus_domain_from_node(iter);
+ if (!kdbus_node_acquire(&domain->node))
+ return NULL;
+
+ return domain;
+}
+
+/**
+ * kdbus_fs_flush() - flush dcache entries of a node
+ * @node: Node to flush entries of
+ *
+ * This flushes all VFS filesystem cache entries for a node and all its
+ * children. This should be called whenever a node is destroyed during
+ * runtime. It will flush the cache entries so the linked objects can be
+ * deallocated.
+ *
+ * This is a no-op if you call it on active nodes (they really should stay in
+ * cache) or on nodes with deactivated parents (flushing the parent is enough).
+ * Furthermore, there is no need to call it on nodes whose lifetime is bound to
+ * their parents'. In those cases, the parent-flush will always also flush the
+ * children.
+ */
+void kdbus_fs_flush(struct kdbus_node *node)
+{
+ struct dentry *dentry, *parent_dentry = NULL;
+ struct kdbus_domain *domain;
+ struct qstr name;
+
+ /* active nodes should remain in cache */
+ if (!kdbus_node_is_deactivated(node))
+ return;
+
+ /* nodes that were never linked were never instantiated */
+ if (!node->parent)
+ return;
+
+ /* acquire domain and verify all ancestors are active */
+ domain = fs_acquire_domain(node);
+ if (!domain)
+ return;
+
+ switch (node->type) {
+ case KDBUS_NODE_ENDPOINT:
+ if (WARN_ON(!node->parent || !node->parent->name))
+ goto exit;
+
+ name.name = node->parent->name;
+ name.len = strlen(node->parent->name);
+ parent_dentry = d_hash_and_lookup(domain->dentry, &name);
+ if (IS_ERR_OR_NULL(parent_dentry))
+ goto exit;
+
+ /* fallthrough */
+ case KDBUS_NODE_BUS:
+ if (WARN_ON(!node->name))
+ goto exit;
+
+ name.name = node->name;
+ name.len = strlen(node->name);
+ dentry = d_hash_and_lookup(parent_dentry ? : domain->dentry,
+ &name);
+ if (!IS_ERR_OR_NULL(dentry)) {
+ d_invalidate(dentry);
+ dput(dentry);
+ }
+
+ dput(parent_dentry);
+ break;
+
+ default:
+ /* all other types are bound to their parent lifetime */
+ break;
+ }
+
+exit:
+ kdbus_node_release(&domain->node);
+}