diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
commit | 57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch) | |
tree | 5e910f0e82173f4ef4f51111366a3f1299037a7b /ipc/kdbus/domain.c |
Initial import
Diffstat (limited to 'ipc/kdbus/domain.c')
-rw-r--r-- | ipc/kdbus/domain.c | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/ipc/kdbus/domain.c b/ipc/kdbus/domain.c new file mode 100644 index 000000000..ac9f760c1 --- /dev/null +++ b/ipc/kdbus/domain.c @@ -0,0 +1,296 @@ +/* + * 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 + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * 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/fs.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include "bus.h" +#include "domain.h" +#include "handle.h" +#include "item.h" +#include "limits.h" +#include "util.h" + +static void kdbus_domain_control_free(struct kdbus_node *node) +{ + kfree(node); +} + +static struct kdbus_node *kdbus_domain_control_new(struct kdbus_domain *domain, + unsigned int access) +{ + struct kdbus_node *node; + int ret; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + return ERR_PTR(-ENOMEM); + + kdbus_node_init(node, KDBUS_NODE_CONTROL); + + node->free_cb = kdbus_domain_control_free; + node->mode = domain->node.mode; + node->mode = S_IRUSR | S_IWUSR; + if (access & (KDBUS_MAKE_ACCESS_GROUP | KDBUS_MAKE_ACCESS_WORLD)) + node->mode |= S_IRGRP | S_IWGRP; + if (access & KDBUS_MAKE_ACCESS_WORLD) + node->mode |= S_IROTH | S_IWOTH; + + ret = kdbus_node_link(node, &domain->node, "control"); + if (ret < 0) + goto exit_free; + + return node; + +exit_free: + kdbus_node_deactivate(node); + kdbus_node_unref(node); + return ERR_PTR(ret); +} + +static void kdbus_domain_free(struct kdbus_node *node) +{ + struct kdbus_domain *domain = + container_of(node, struct kdbus_domain, node); + + put_user_ns(domain->user_namespace); + ida_destroy(&domain->user_ida); + idr_destroy(&domain->user_idr); + kfree(domain); +} + +/** + * kdbus_domain_new() - create a new domain + * @access: The access mode for this node (KDBUS_MAKE_ACCESS_*) + * + * Return: a new kdbus_domain on success, ERR_PTR on failure + */ +struct kdbus_domain *kdbus_domain_new(unsigned int access) +{ + struct kdbus_domain *d; + int ret; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + return ERR_PTR(-ENOMEM); + + kdbus_node_init(&d->node, KDBUS_NODE_DOMAIN); + + d->node.free_cb = kdbus_domain_free; + d->node.mode = S_IRUSR | S_IXUSR; + if (access & (KDBUS_MAKE_ACCESS_GROUP | KDBUS_MAKE_ACCESS_WORLD)) + d->node.mode |= S_IRGRP | S_IXGRP; + if (access & KDBUS_MAKE_ACCESS_WORLD) + d->node.mode |= S_IROTH | S_IXOTH; + + mutex_init(&d->lock); + idr_init(&d->user_idr); + ida_init(&d->user_ida); + + /* Pin user namespace so we can guarantee domain-unique bus * names. */ + d->user_namespace = get_user_ns(current_user_ns()); + + ret = kdbus_node_link(&d->node, NULL, NULL); + if (ret < 0) + goto exit_unref; + + return d; + +exit_unref: + kdbus_node_deactivate(&d->node); + kdbus_node_unref(&d->node); + return ERR_PTR(ret); +} + +/** + * kdbus_domain_ref() - take a domain reference + * @domain: Domain + * + * Return: the domain itself + */ +struct kdbus_domain *kdbus_domain_ref(struct kdbus_domain *domain) +{ + if (domain) + kdbus_node_ref(&domain->node); + return domain; +} + +/** + * kdbus_domain_unref() - drop a domain reference + * @domain: Domain + * + * When the last reference is dropped, the domain internal structure + * is freed. + * + * Return: NULL + */ +struct kdbus_domain *kdbus_domain_unref(struct kdbus_domain *domain) +{ + if (domain) + kdbus_node_unref(&domain->node); + return NULL; +} + +/** + * kdbus_domain_populate() - populate static domain nodes + * @domain: domain to populate + * @access: KDBUS_MAKE_ACCESS_* access restrictions for new nodes + * + * Allocate and activate static sub-nodes of the given domain. This will fail if + * you call it on a non-active node or if the domain was already populated. + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_domain_populate(struct kdbus_domain *domain, unsigned int access) +{ + struct kdbus_node *control; + + /* + * Create a control-node for this domain. We drop our own reference + * immediately, effectively causing the node to be deactivated and + * released when the parent domain is. + */ + control = kdbus_domain_control_new(domain, access); + if (IS_ERR(control)) + return PTR_ERR(control); + + kdbus_node_activate(control); + kdbus_node_unref(control); + return 0; +} + +/** + * kdbus_user_lookup() - lookup a kdbus_user object + * @domain: domain of the user + * @uid: uid of the user; INVALID_UID for an anon user + * + * Lookup the kdbus user accounting object for the given domain. If INVALID_UID + * is passed, a new anonymous user is created which is private to the caller. + * + * Return: The user object is returned, ERR_PTR on failure. + */ +struct kdbus_user *kdbus_user_lookup(struct kdbus_domain *domain, kuid_t uid) +{ + struct kdbus_user *u = NULL, *old = NULL; + int ret; + + mutex_lock(&domain->lock); + + if (uid_valid(uid)) { + old = idr_find(&domain->user_idr, __kuid_val(uid)); + /* + * If the object is about to be destroyed, ignore it and + * replace the slot in the IDR later on. + */ + if (old && kref_get_unless_zero(&old->kref)) { + mutex_unlock(&domain->lock); + return old; + } + } + + u = kzalloc(sizeof(*u), GFP_KERNEL); + if (!u) { + ret = -ENOMEM; + goto exit; + } + + kref_init(&u->kref); + u->domain = kdbus_domain_ref(domain); + u->uid = uid; + atomic_set(&u->buses, 0); + atomic_set(&u->connections, 0); + + if (uid_valid(uid)) { + if (old) { + idr_replace(&domain->user_idr, u, __kuid_val(uid)); + old->uid = INVALID_UID; /* mark old as removed */ + } else { + ret = idr_alloc(&domain->user_idr, u, __kuid_val(uid), + __kuid_val(uid) + 1, GFP_KERNEL); + if (ret < 0) + goto exit; + } + } + + /* + * Allocate the smallest possible index for this user; used + * in arrays for accounting user quota in receiver queues. + */ + ret = ida_simple_get(&domain->user_ida, 1, 0, GFP_KERNEL); + if (ret < 0) + goto exit; + + u->id = ret; + mutex_unlock(&domain->lock); + return u; + +exit: + if (u) { + if (uid_valid(u->uid)) + idr_remove(&domain->user_idr, __kuid_val(u->uid)); + kdbus_domain_unref(u->domain); + kfree(u); + } + mutex_unlock(&domain->lock); + return ERR_PTR(ret); +} + +static void __kdbus_user_free(struct kref *kref) +{ + struct kdbus_user *user = container_of(kref, struct kdbus_user, kref); + + WARN_ON(atomic_read(&user->buses) > 0); + WARN_ON(atomic_read(&user->connections) > 0); + + mutex_lock(&user->domain->lock); + ida_simple_remove(&user->domain->user_ida, user->id); + if (uid_valid(user->uid)) + idr_remove(&user->domain->user_idr, __kuid_val(user->uid)); + mutex_unlock(&user->domain->lock); + + kdbus_domain_unref(user->domain); + kfree(user); +} + +/** + * kdbus_user_ref() - take a user reference + * @u: User + * + * Return: @u is returned + */ +struct kdbus_user *kdbus_user_ref(struct kdbus_user *u) +{ + if (u) + kref_get(&u->kref); + return u; +} + +/** + * kdbus_user_unref() - drop a user reference + * @u: User + * + * Return: NULL + */ +struct kdbus_user *kdbus_user_unref(struct kdbus_user *u) +{ + if (u) + kref_put(&u->kref, __kdbus_user_free); + return NULL; +} |