From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753582AbbCINLB (ORCPT ); Mon, 9 Mar 2015 09:11:01 -0400 Received: from mail.linuxfoundation.org ([140.211.169.12]:35650 "EHLO mail.linuxfoundation.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753225AbbCINK5 (ORCPT ); Mon, 9 Mar 2015 09:10:57 -0400 From: Greg Kroah-Hartman To: arnd@arndb.de, ebiederm@xmission.com, gnomes@lxorguk.ukuu.org.uk, teg@jklm.no, jkosina@suse.cz, luto@amacapital.net, linux-api@vger.kernel.org, linux-kernel@vger.kernel.org Cc: daniel@zonque.org, dh.herrmann@gmail.com, tixxdz@opendz.org, Greg Kroah-Hartman Subject: [PATCH 08/14] kdbus: add code for notifications and matches Date: Mon, 9 Mar 2015 14:09:14 +0100 Message-Id: <1425906560-13798-9-git-send-email-gregkh@linuxfoundation.org> X-Mailer: git-send-email 2.3.1 In-Reply-To: <1425906560-13798-1-git-send-email-gregkh@linuxfoundation.org> References: <1425906560-13798-1-git-send-email-gregkh@linuxfoundation.org> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Daniel Mack This patch adds code for matches and notifications. Notifications are broadcast messages generated by the kernel, which notify subscribes when connections are created or destroyed, when well-known-names have been claimed, released or changed ownership, or when reply messages have timed out. Matches are used to tell the kernel driver which broadcast messages a connection is interested in. Matches can either be specific on one of the kernel-generated notification types, or carry a bloom filter mask to match against a message from userspace. The latter is a way to pre-filter messages from other connections in order to mitigate unnecessary wakeups. Signed-off-by: Daniel Mack Signed-off-by: David Herrmann Signed-off-by: Djalal Harouni Signed-off-by: Greg Kroah-Hartman --- ipc/kdbus/match.c | 559 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ipc/kdbus/match.h | 35 ++++ ipc/kdbus/notify.c | 248 ++++++++++++++++++++++++ ipc/kdbus/notify.h | 30 +++ 4 files changed, 872 insertions(+) create mode 100644 ipc/kdbus/match.c create mode 100644 ipc/kdbus/match.h create mode 100644 ipc/kdbus/notify.c create mode 100644 ipc/kdbus/notify.h diff --git a/ipc/kdbus/match.c b/ipc/kdbus/match.c new file mode 100644 index 000000000000..30cec1ca819f --- /dev/null +++ b/ipc/kdbus/match.c @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman + * Copyright (C) 2013-2015 Daniel Mack + * Copyright (C) 2013-2015 David Herrmann + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "bus.h" +#include "connection.h" +#include "endpoint.h" +#include "handle.h" +#include "item.h" +#include "match.h" +#include "message.h" +#include "names.h" + +/** + * struct kdbus_match_db - message filters + * @entries_list: List of matches + * @mdb_rwlock: Match data lock + * @entries_count: Number of entries in database + */ +struct kdbus_match_db { + struct list_head entries_list; + struct rw_semaphore mdb_rwlock; + unsigned int entries_count; +}; + +/** + * struct kdbus_match_entry - a match database entry + * @cookie: User-supplied cookie to lookup the entry + * @list_entry: The list entry element for the db list + * @rules_list: The list head for tracking rules of this entry + */ +struct kdbus_match_entry { + u64 cookie; + struct list_head list_entry; + struct list_head rules_list; +}; + +/** + * struct kdbus_bloom_mask - mask to match against filter + * @generations: Number of generations carried + * @data: Array of bloom bit fields + */ +struct kdbus_bloom_mask { + u64 generations; + u64 *data; +}; + +/** + * struct kdbus_match_rule - a rule appended to a match entry + * @type: An item type to match agains + * @bloom_mask: Bloom mask to match a message's filter against, used + * with KDBUS_ITEM_BLOOM_MASK + * @name: Name to match against, used with KDBUS_ITEM_NAME, + * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE} + * @old_id: ID to match against, used with + * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE}, + * KDBUS_ITEM_ID_REMOVE + * @new_id: ID to match against, used with + * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE}, + * KDBUS_ITEM_ID_REMOVE + * @src_id: ID to match against, used with KDBUS_ITEM_ID + * @rules_entry: Entry in the entry's rules list + */ +struct kdbus_match_rule { + u64 type; + union { + struct kdbus_bloom_mask bloom_mask; + struct { + char *name; + u64 old_id; + u64 new_id; + }; + u64 src_id; + }; + struct list_head rules_entry; +}; + +static void kdbus_match_rule_free(struct kdbus_match_rule *rule) +{ + if (!rule) + return; + + switch (rule->type) { + case KDBUS_ITEM_BLOOM_MASK: + kfree(rule->bloom_mask.data); + break; + + case KDBUS_ITEM_NAME: + case KDBUS_ITEM_NAME_ADD: + case KDBUS_ITEM_NAME_REMOVE: + case KDBUS_ITEM_NAME_CHANGE: + kfree(rule->name); + break; + + case KDBUS_ITEM_ID: + case KDBUS_ITEM_ID_ADD: + case KDBUS_ITEM_ID_REMOVE: + break; + + default: + BUG(); + } + + list_del(&rule->rules_entry); + kfree(rule); +} + +static void kdbus_match_entry_free(struct kdbus_match_entry *entry) +{ + struct kdbus_match_rule *r, *tmp; + + if (!entry) + return; + + list_for_each_entry_safe(r, tmp, &entry->rules_list, rules_entry) + kdbus_match_rule_free(r); + + list_del(&entry->list_entry); + kfree(entry); +} + +/** + * kdbus_match_db_free() - free match db resources + * @mdb: The match database + */ +void kdbus_match_db_free(struct kdbus_match_db *mdb) +{ + struct kdbus_match_entry *entry, *tmp; + + if (!mdb) + return; + + list_for_each_entry_safe(entry, tmp, &mdb->entries_list, list_entry) + kdbus_match_entry_free(entry); + + kfree(mdb); +} + +/** + * kdbus_match_db_new() - create a new match database + * + * Return: a new kdbus_match_db on success, ERR_PTR on failure. + */ +struct kdbus_match_db *kdbus_match_db_new(void) +{ + struct kdbus_match_db *d; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + return ERR_PTR(-ENOMEM); + + init_rwsem(&d->mdb_rwlock); + INIT_LIST_HEAD(&d->entries_list); + + return d; +} + +static bool kdbus_match_bloom(const struct kdbus_bloom_filter *filter, + const struct kdbus_bloom_mask *mask, + const struct kdbus_conn *conn) +{ + size_t n = conn->ep->bus->bloom.size / sizeof(u64); + const u64 *m; + size_t i; + + /* + * The message's filter carries a generation identifier, the + * match's mask possibly carries an array of multiple generations + * of the mask. Select the mask with the closest match of the + * filter's generation. + */ + m = mask->data + (min(filter->generation, mask->generations - 1) * n); + + /* + * The message's filter contains the messages properties, + * the match's mask contains the properties to look for in the + * message. Check the mask bit field against the filter bit field, + * if the message possibly carries the properties the connection + * has subscribed to. + */ + for (i = 0; i < n; i++) + if ((filter->data[i] & m[i]) != m[i]) + return false; + + return true; +} + +static bool kdbus_match_rules(const struct kdbus_match_entry *entry, + struct kdbus_conn *conn_src, + struct kdbus_kmsg *kmsg) +{ + struct kdbus_match_rule *r; + + if (conn_src) + lockdep_assert_held(&conn_src->ep->bus->name_registry->rwlock); + + /* + * Walk all the rules and bail out immediately + * if any of them is unsatisfied. + */ + + list_for_each_entry(r, &entry->rules_list, rules_entry) { + if (conn_src) { + /* messages from userspace */ + + switch (r->type) { + case KDBUS_ITEM_BLOOM_MASK: + if (!kdbus_match_bloom(kmsg->bloom_filter, + &r->bloom_mask, + conn_src)) + return false; + break; + + case KDBUS_ITEM_ID: + if (r->src_id != conn_src->id && + r->src_id != KDBUS_MATCH_ID_ANY) + return false; + + break; + + case KDBUS_ITEM_NAME: + if (!kdbus_conn_has_name(conn_src, r->name)) + return false; + + break; + + default: + return false; + } + } else { + /* kernel notifications */ + + if (kmsg->notify_type != r->type) + return false; + + switch (r->type) { + case KDBUS_ITEM_ID_ADD: + if (r->new_id != KDBUS_MATCH_ID_ANY && + r->new_id != kmsg->notify_new_id) + return false; + + break; + + case KDBUS_ITEM_ID_REMOVE: + if (r->old_id != KDBUS_MATCH_ID_ANY && + r->old_id != kmsg->notify_old_id) + return false; + + break; + + case KDBUS_ITEM_NAME_ADD: + case KDBUS_ITEM_NAME_CHANGE: + case KDBUS_ITEM_NAME_REMOVE: + if ((r->old_id != KDBUS_MATCH_ID_ANY && + r->old_id != kmsg->notify_old_id) || + (r->new_id != KDBUS_MATCH_ID_ANY && + r->new_id != kmsg->notify_new_id) || + (r->name && kmsg->notify_name && + strcmp(r->name, kmsg->notify_name) != 0)) + return false; + + break; + + default: + return false; + } + } + } + + return true; +} + +/** + * kdbus_match_db_match_kmsg() - match a kmsg object agains the database entries + * @mdb: The match database + * @conn_src: The connection object originating the message + * @kmsg: The kmsg to perform the match on + * + * This function will walk through all the database entries previously uploaded + * with kdbus_match_db_add(). As soon as any of them has an all-satisfied rule + * set, this function will return true. + * + * The caller must hold the registry lock of conn_src->ep->bus, in case conn_src + * is non-NULL. + * + * Return: true if there was a matching database entry, false otherwise. + */ +bool kdbus_match_db_match_kmsg(struct kdbus_match_db *mdb, + struct kdbus_conn *conn_src, + struct kdbus_kmsg *kmsg) +{ + struct kdbus_match_entry *entry; + bool matched = false; + + down_read(&mdb->mdb_rwlock); + list_for_each_entry(entry, &mdb->entries_list, list_entry) { + matched = kdbus_match_rules(entry, conn_src, kmsg); + if (matched) + break; + } + up_read(&mdb->mdb_rwlock); + + return matched; +} + +static int kdbus_match_db_remove_unlocked(struct kdbus_match_db *mdb, + u64 cookie) +{ + struct kdbus_match_entry *entry, *tmp; + bool found = false; + + list_for_each_entry_safe(entry, tmp, &mdb->entries_list, list_entry) + if (entry->cookie == cookie) { + kdbus_match_entry_free(entry); + --mdb->entries_count; + found = true; + } + + return found ? 0 : -EBADSLT; +} + +/** + * kdbus_cmd_match_add() - handle KDBUS_CMD_MATCH_ADD + * @conn: connection to operate on + * @argp: command payload + * + * One call to this function (or one ioctl(KDBUS_CMD_MATCH_ADD), respectively, + * adds one new database entry with n rules attached to it. Each rule is + * described with an kdbus_item, and an entry is considered matching if all + * its rules are satisfied. + * + * The items attached to a kdbus_cmd_match struct have the following mapping: + * + * KDBUS_ITEM_BLOOM_MASK: A bloom mask + * KDBUS_ITEM_NAME: A connection's source name + * KDBUS_ITEM_ID: A connection ID + * KDBUS_ITEM_NAME_ADD: + * KDBUS_ITEM_NAME_REMOVE: + * KDBUS_ITEM_NAME_CHANGE: Well-known name changes, carry + * kdbus_notify_name_change + * KDBUS_ITEM_ID_ADD: + * KDBUS_ITEM_ID_REMOVE: Connection ID changes, carry + * kdbus_notify_id_change + * + * For kdbus_notify_{id,name}_change structs, only the ID and name fields + * are looked at when adding an entry. The flags are unused. + * + * Also note that KDBUS_ITEM_BLOOM_MASK, KDBUS_ITEM_NAME and KDBUS_ITEM_ID + * are used to match messages from userspace, while the others apply to + * kernel-generated notifications. + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_cmd_match_add(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_match_db *mdb = conn->match_db; + struct kdbus_match_entry *entry = NULL; + struct kdbus_cmd_match *cmd; + struct kdbus_item *item; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_BLOOM_MASK, .multiple = true }, + { .type = KDBUS_ITEM_NAME, .multiple = true }, + { .type = KDBUS_ITEM_ID, .multiple = true }, + { .type = KDBUS_ITEM_NAME_ADD, .multiple = true }, + { .type = KDBUS_ITEM_NAME_REMOVE, .multiple = true }, + { .type = KDBUS_ITEM_NAME_CHANGE, .multiple = true }, + { .type = KDBUS_ITEM_ID_ADD, .multiple = true }, + { .type = KDBUS_ITEM_ID_REMOVE, .multiple = true }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE | + KDBUS_MATCH_REPLACE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + if (!kdbus_conn_is_ordinary(conn)) + return -EOPNOTSUPP; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + ret = -ENOMEM; + goto exit; + } + + entry->cookie = cmd->cookie; + INIT_LIST_HEAD(&entry->list_entry); + INIT_LIST_HEAD(&entry->rules_list); + + KDBUS_ITEMS_FOREACH(item, cmd->items, KDBUS_ITEMS_SIZE(cmd, items)) { + struct kdbus_match_rule *rule; + size_t size = item->size - offsetof(struct kdbus_item, data); + + rule = kzalloc(sizeof(*rule), GFP_KERNEL); + if (!rule) { + ret = -ENOMEM; + goto exit; + } + + rule->type = item->type; + INIT_LIST_HEAD(&rule->rules_entry); + + switch (item->type) { + case KDBUS_ITEM_BLOOM_MASK: { + u64 bsize = conn->ep->bus->bloom.size; + u64 generations; + u64 remainder; + + generations = div64_u64_rem(size, bsize, &remainder); + if (size < bsize || remainder > 0) { + ret = -EDOM; + break; + } + + rule->bloom_mask.data = kmemdup(item->data, + size, GFP_KERNEL); + if (!rule->bloom_mask.data) { + ret = -ENOMEM; + break; + } + + rule->bloom_mask.generations = generations; + break; + } + + case KDBUS_ITEM_NAME: + if (!kdbus_name_is_valid(item->str, false)) { + ret = -EINVAL; + break; + } + + rule->name = kstrdup(item->str, GFP_KERNEL); + if (!rule->name) + ret = -ENOMEM; + + break; + + case KDBUS_ITEM_ID: + rule->src_id = item->id; + break; + + case KDBUS_ITEM_NAME_ADD: + case KDBUS_ITEM_NAME_REMOVE: + case KDBUS_ITEM_NAME_CHANGE: + rule->old_id = item->name_change.old_id.id; + rule->new_id = item->name_change.new_id.id; + + if (size > sizeof(struct kdbus_notify_name_change)) { + rule->name = kstrdup(item->name_change.name, + GFP_KERNEL); + if (!rule->name) + ret = -ENOMEM; + } + + break; + + case KDBUS_ITEM_ID_ADD: + case KDBUS_ITEM_ID_REMOVE: + if (item->type == KDBUS_ITEM_ID_ADD) + rule->new_id = item->id_change.id; + else + rule->old_id = item->id_change.id; + + break; + } + + if (ret < 0) { + kdbus_match_rule_free(rule); + goto exit; + } + + list_add_tail(&rule->rules_entry, &entry->rules_list); + } + + down_write(&mdb->mdb_rwlock); + + /* Remove any entry that has the same cookie as the current one. */ + if (cmd->flags & KDBUS_MATCH_REPLACE) + kdbus_match_db_remove_unlocked(mdb, entry->cookie); + + /* + * If the above removal caught any entry, there will be room for the + * new one. + */ + if (++mdb->entries_count > KDBUS_MATCH_MAX) { + --mdb->entries_count; + ret = -EMFILE; + } else { + list_add_tail(&entry->list_entry, &mdb->entries_list); + entry = NULL; + } + + up_write(&mdb->mdb_rwlock); + +exit: + kdbus_match_entry_free(entry); + return kdbus_args_clear(&args, ret); +} + +/** + * kdbus_cmd_match_remove() - handle KDBUS_CMD_MATCH_REMOVE + * @conn: connection to operate on + * @argp: command payload + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_cmd_match_remove(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_cmd_match *cmd; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + if (!kdbus_conn_is_ordinary(conn)) + return -EOPNOTSUPP; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + down_write(&conn->match_db->mdb_rwlock); + ret = kdbus_match_db_remove_unlocked(conn->match_db, cmd->cookie); + up_write(&conn->match_db->mdb_rwlock); + + return kdbus_args_clear(&args, ret); +} diff --git a/ipc/kdbus/match.h b/ipc/kdbus/match.h new file mode 100644 index 000000000000..ea4292938deb --- /dev/null +++ b/ipc/kdbus/match.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman + * Copyright (C) 2013-2015 Daniel Mack + * Copyright (C) 2013-2015 David Herrmann + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni + * + * 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. + */ + +#ifndef __KDBUS_MATCH_H +#define __KDBUS_MATCH_H + +struct kdbus_conn; +struct kdbus_kmsg; +struct kdbus_match_db; + +struct kdbus_match_db *kdbus_match_db_new(void); +void kdbus_match_db_free(struct kdbus_match_db *db); +int kdbus_match_db_add(struct kdbus_conn *conn, + struct kdbus_cmd_match *cmd); +int kdbus_match_db_remove(struct kdbus_conn *conn, + struct kdbus_cmd_match *cmd); +bool kdbus_match_db_match_kmsg(struct kdbus_match_db *db, + struct kdbus_conn *conn_src, + struct kdbus_kmsg *kmsg); + +int kdbus_cmd_match_add(struct kdbus_conn *conn, void __user *argp); +int kdbus_cmd_match_remove(struct kdbus_conn *conn, void __user *argp); + +#endif diff --git a/ipc/kdbus/notify.c b/ipc/kdbus/notify.c new file mode 100644 index 000000000000..e4a454222f09 --- /dev/null +++ b/ipc/kdbus/notify.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman + * Copyright (C) 2013-2015 Daniel Mack + * Copyright (C) 2013-2015 David Herrmann + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "bus.h" +#include "connection.h" +#include "domain.h" +#include "endpoint.h" +#include "item.h" +#include "message.h" +#include "notify.h" + +static inline void kdbus_notify_add_tail(struct kdbus_kmsg *kmsg, + struct kdbus_bus *bus) +{ + spin_lock(&bus->notify_lock); + list_add_tail(&kmsg->notify_entry, &bus->notify_list); + spin_unlock(&bus->notify_lock); +} + +static int kdbus_notify_reply(struct kdbus_bus *bus, u64 id, + u64 cookie, u64 msg_type) +{ + struct kdbus_kmsg *kmsg = NULL; + + WARN_ON(id == 0); + + kmsg = kdbus_kmsg_new(bus, 0); + if (IS_ERR(kmsg)) + return PTR_ERR(kmsg); + + /* + * a kernel-generated notification can only contain one + * struct kdbus_item, so make a shortcut here for + * faster lookup in the match db. + */ + kmsg->notify_type = msg_type; + kmsg->msg.flags = KDBUS_MSG_SIGNAL; + kmsg->msg.dst_id = id; + kmsg->msg.src_id = KDBUS_SRC_ID_KERNEL; + kmsg->msg.payload_type = KDBUS_PAYLOAD_KERNEL; + kmsg->msg.cookie_reply = cookie; + kmsg->msg.items[0].type = msg_type; + + kdbus_notify_add_tail(kmsg, bus); + + return 0; +} + +/** + * kdbus_notify_reply_timeout() - queue a timeout reply + * @bus: Bus which queues the messages + * @id: The destination's connection ID + * @cookie: The cookie to set in the reply. + * + * Queues a message that has a KDBUS_ITEM_REPLY_TIMEOUT item attached. + * + * Return: 0 on success, negative errno on failure. + */ +int kdbus_notify_reply_timeout(struct kdbus_bus *bus, u64 id, u64 cookie) +{ + return kdbus_notify_reply(bus, id, cookie, KDBUS_ITEM_REPLY_TIMEOUT); +} + +/** + * kdbus_notify_reply_dead() - queue a 'dead' reply + * @bus: Bus which queues the messages + * @id: The destination's connection ID + * @cookie: The cookie to set in the reply. + * + * Queues a message that has a KDBUS_ITEM_REPLY_DEAD item attached. + * + * Return: 0 on success, negative errno on failure. + */ +int kdbus_notify_reply_dead(struct kdbus_bus *bus, u64 id, u64 cookie) +{ + return kdbus_notify_reply(bus, id, cookie, KDBUS_ITEM_REPLY_DEAD); +} + +/** + * kdbus_notify_name_change() - queue a notification about a name owner change + * @bus: Bus which queues the messages + * @type: The type if the notification; KDBUS_ITEM_NAME_ADD, + * KDBUS_ITEM_NAME_CHANGE or KDBUS_ITEM_NAME_REMOVE + * @old_id: The id of the connection that used to own the name + * @new_id: The id of the new owner connection + * @old_flags: The flags to pass in the KDBUS_ITEM flags field for + * the old owner + * @new_flags: The flags to pass in the KDBUS_ITEM flags field for + * the new owner + * @name: The name that was removed or assigned to a new owner + * + * Return: 0 on success, negative errno on failure. + */ +int kdbus_notify_name_change(struct kdbus_bus *bus, u64 type, + u64 old_id, u64 new_id, + u64 old_flags, u64 new_flags, + const char *name) +{ + struct kdbus_kmsg *kmsg = NULL; + size_t name_len, extra_size; + + name_len = strlen(name) + 1; + extra_size = sizeof(struct kdbus_notify_name_change) + name_len; + kmsg = kdbus_kmsg_new(bus, extra_size); + if (IS_ERR(kmsg)) + return PTR_ERR(kmsg); + + kmsg->msg.flags = KDBUS_MSG_SIGNAL; + kmsg->msg.dst_id = KDBUS_DST_ID_BROADCAST; + kmsg->msg.src_id = KDBUS_SRC_ID_KERNEL; + kmsg->msg.payload_type = KDBUS_PAYLOAD_KERNEL; + kmsg->notify_type = type; + kmsg->notify_old_id = old_id; + kmsg->notify_new_id = new_id; + kmsg->msg.items[0].type = type; + kmsg->msg.items[0].name_change.old_id.id = old_id; + kmsg->msg.items[0].name_change.old_id.flags = old_flags; + kmsg->msg.items[0].name_change.new_id.id = new_id; + kmsg->msg.items[0].name_change.new_id.flags = new_flags; + memcpy(kmsg->msg.items[0].name_change.name, name, name_len); + kmsg->notify_name = kmsg->msg.items[0].name_change.name; + + kdbus_notify_add_tail(kmsg, bus); + + return 0; +} + +/** + * kdbus_notify_id_change() - queue a notification about a unique ID change + * @bus: Bus which queues the messages + * @type: The type if the notification; KDBUS_ITEM_ID_ADD or + * KDBUS_ITEM_ID_REMOVE + * @id: The id of the connection that was added or removed + * @flags: The flags to pass in the KDBUS_ITEM flags field + * + * Return: 0 on success, negative errno on failure. + */ +int kdbus_notify_id_change(struct kdbus_bus *bus, u64 type, u64 id, u64 flags) +{ + struct kdbus_kmsg *kmsg = NULL; + + kmsg = kdbus_kmsg_new(bus, sizeof(struct kdbus_notify_id_change)); + if (IS_ERR(kmsg)) + return PTR_ERR(kmsg); + + kmsg->msg.flags = KDBUS_MSG_SIGNAL; + kmsg->msg.dst_id = KDBUS_DST_ID_BROADCAST; + kmsg->msg.src_id = KDBUS_SRC_ID_KERNEL; + kmsg->msg.payload_type = KDBUS_PAYLOAD_KERNEL; + kmsg->notify_type = type; + + switch (type) { + case KDBUS_ITEM_ID_ADD: + kmsg->notify_new_id = id; + break; + + case KDBUS_ITEM_ID_REMOVE: + kmsg->notify_old_id = id; + break; + + default: + BUG(); + } + + kmsg->msg.items[0].type = type; + kmsg->msg.items[0].id_change.id = id; + kmsg->msg.items[0].id_change.flags = flags; + + kdbus_notify_add_tail(kmsg, bus); + + return 0; +} + +/** + * kdbus_notify_flush() - send a list of collected messages + * @bus: Bus which queues the messages + * + * The list is empty after sending the messages. + */ +void kdbus_notify_flush(struct kdbus_bus *bus) +{ + LIST_HEAD(notify_list); + struct kdbus_kmsg *kmsg, *tmp; + + mutex_lock(&bus->notify_flush_lock); + down_read(&bus->name_registry->rwlock); + + spin_lock(&bus->notify_lock); + list_splice_init(&bus->notify_list, ¬ify_list); + spin_unlock(&bus->notify_lock); + + list_for_each_entry_safe(kmsg, tmp, ¬ify_list, notify_entry) { + kdbus_meta_conn_collect(kmsg->conn_meta, kmsg, NULL, + KDBUS_ATTACH_TIMESTAMP); + + if (kmsg->msg.dst_id != KDBUS_DST_ID_BROADCAST) { + struct kdbus_conn *conn; + + conn = kdbus_bus_find_conn_by_id(bus, kmsg->msg.dst_id); + if (conn) { + kdbus_bus_eavesdrop(bus, NULL, kmsg); + kdbus_conn_entry_insert(NULL, conn, kmsg, NULL); + kdbus_conn_unref(conn); + } + } else { + kdbus_bus_broadcast(bus, NULL, kmsg); + } + + list_del(&kmsg->notify_entry); + kdbus_kmsg_free(kmsg); + } + + up_read(&bus->name_registry->rwlock); + mutex_unlock(&bus->notify_flush_lock); +} + +/** + * kdbus_notify_free() - free a list of collected messages + * @bus: Bus which queues the messages + */ +void kdbus_notify_free(struct kdbus_bus *bus) +{ + struct kdbus_kmsg *kmsg, *tmp; + + list_for_each_entry_safe(kmsg, tmp, &bus->notify_list, notify_entry) { + list_del(&kmsg->notify_entry); + kdbus_kmsg_free(kmsg); + } +} diff --git a/ipc/kdbus/notify.h b/ipc/kdbus/notify.h new file mode 100644 index 000000000000..03df464cb735 --- /dev/null +++ b/ipc/kdbus/notify.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman + * Copyright (C) 2013-2015 Daniel Mack + * Copyright (C) 2013-2015 David Herrmann + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni + * + * 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. + */ + +#ifndef __KDBUS_NOTIFY_H +#define __KDBUS_NOTIFY_H + +struct kdbus_bus; + +int kdbus_notify_id_change(struct kdbus_bus *bus, u64 type, u64 id, u64 flags); +int kdbus_notify_reply_timeout(struct kdbus_bus *bus, u64 id, u64 cookie); +int kdbus_notify_reply_dead(struct kdbus_bus *bus, u64 id, u64 cookie); +int kdbus_notify_name_change(struct kdbus_bus *bus, u64 type, + u64 old_id, u64 new_id, + u64 old_flags, u64 new_flags, + const char *name); +void kdbus_notify_flush(struct kdbus_bus *bus); +void kdbus_notify_free(struct kdbus_bus *bus); + +#endif -- 2.3.1