summaryrefslogtreecommitdiff
path: root/ipc/kdbus/queue.c
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/kdbus/queue.c')
-rw-r--r--ipc/kdbus/queue.c678
1 files changed, 678 insertions, 0 deletions
diff --git a/ipc/kdbus/queue.c b/ipc/kdbus/queue.c
new file mode 100644
index 000000000000..a449464a3975
--- /dev/null
+++ b/ipc/kdbus/queue.c
@@ -0,0 +1,678 @@
+/*
+ * 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/audit.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/hashtable.h>
+#include <linux/idr.h>
+#include <linux/init.h>
+#include <linux/math64.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/syscalls.h>
+#include <linux/uio.h>
+
+#include "util.h"
+#include "domain.h"
+#include "connection.h"
+#include "item.h"
+#include "message.h"
+#include "metadata.h"
+#include "queue.h"
+#include "reply.h"
+
+/**
+ * kdbus_queue_init() - initialize data structure related to a queue
+ * @queue: The queue to initialize
+ */
+void kdbus_queue_init(struct kdbus_queue *queue)
+{
+ INIT_LIST_HEAD(&queue->msg_list);
+ queue->msg_prio_queue = RB_ROOT;
+}
+
+/**
+ * kdbus_queue_peek() - Retrieves an entry from a queue
+ * @queue: The queue
+ * @priority: The minimum priority of the entry to peek
+ * @use_priority: Boolean flag whether or not to peek by priority
+ *
+ * Look for a entry in a queue, either by priority, or the oldest one (FIFO).
+ * The entry is not freed, put off the queue's lists or anything else.
+ *
+ * Return: the peeked queue entry on success, NULL if no suitable msg is found
+ */
+struct kdbus_queue_entry *kdbus_queue_peek(struct kdbus_queue *queue,
+ s64 priority, bool use_priority)
+{
+ struct kdbus_queue_entry *e;
+
+ if (list_empty(&queue->msg_list))
+ return NULL;
+
+ if (use_priority) {
+ /* get next entry with highest priority */
+ e = rb_entry(queue->msg_prio_highest,
+ struct kdbus_queue_entry, prio_node);
+
+ /* no entry with the requested priority */
+ if (e->priority > priority)
+ return NULL;
+ } else {
+ /* ignore the priority, return the next entry in the entry */
+ e = list_first_entry(&queue->msg_list,
+ struct kdbus_queue_entry, entry);
+ }
+
+ return e;
+}
+
+static void kdbus_queue_entry_link(struct kdbus_queue_entry *entry)
+{
+ struct kdbus_queue *queue = &entry->conn->queue;
+ struct rb_node **n, *pn = NULL;
+ bool highest = true;
+
+ lockdep_assert_held(&entry->conn->lock);
+ if (WARN_ON(!list_empty(&entry->entry)))
+ return;
+
+ /* sort into priority entry tree */
+ n = &queue->msg_prio_queue.rb_node;
+ while (*n) {
+ struct kdbus_queue_entry *e;
+
+ pn = *n;
+ e = rb_entry(pn, struct kdbus_queue_entry, prio_node);
+
+ /* existing node for this priority, add to its list */
+ if (likely(entry->priority == e->priority)) {
+ list_add_tail(&entry->prio_entry, &e->prio_entry);
+ goto prio_done;
+ }
+
+ if (entry->priority < e->priority) {
+ n = &pn->rb_left;
+ } else {
+ n = &pn->rb_right;
+ highest = false;
+ }
+ }
+
+ /* cache highest-priority entry */
+ if (highest)
+ queue->msg_prio_highest = &entry->prio_node;
+
+ /* new node for this priority */
+ rb_link_node(&entry->prio_node, pn, n);
+ rb_insert_color(&entry->prio_node, &queue->msg_prio_queue);
+ INIT_LIST_HEAD(&entry->prio_entry);
+
+prio_done:
+ /* add to unsorted fifo list */
+ list_add_tail(&entry->entry, &queue->msg_list);
+}
+
+static void kdbus_queue_entry_unlink(struct kdbus_queue_entry *entry)
+{
+ struct kdbus_queue *queue = &entry->conn->queue;
+
+ lockdep_assert_held(&entry->conn->lock);
+ if (list_empty(&entry->entry))
+ return;
+
+ list_del_init(&entry->entry);
+
+ if (list_empty(&entry->prio_entry)) {
+ /*
+ * Single entry for this priority, update cached
+ * highest-priority entry, remove the tree node.
+ */
+ if (queue->msg_prio_highest == &entry->prio_node)
+ queue->msg_prio_highest = rb_next(&entry->prio_node);
+
+ rb_erase(&entry->prio_node, &queue->msg_prio_queue);
+ } else {
+ struct kdbus_queue_entry *q;
+
+ /*
+ * Multiple entries for this priority entry, get next one in
+ * the list. Update cached highest-priority entry, store the
+ * new one as the tree node.
+ */
+ q = list_first_entry(&entry->prio_entry,
+ struct kdbus_queue_entry, prio_entry);
+ list_del(&entry->prio_entry);
+
+ if (queue->msg_prio_highest == &entry->prio_node)
+ queue->msg_prio_highest = &q->prio_node;
+
+ rb_replace_node(&entry->prio_node, &q->prio_node,
+ &queue->msg_prio_queue);
+ }
+}
+
+/**
+ * kdbus_queue_entry_new() - allocate a queue entry
+ * @conn_dst: destination connection
+ * @kmsg: kmsg object the queue entry should track
+ * @user: user to account message on (or NULL for kernel messages)
+ *
+ * Allocates a queue entry based on a given kmsg and allocate space for
+ * the message payload and the requested metadata in the connection's pool.
+ * The entry is not actually added to the queue's lists at this point.
+ *
+ * Return: the allocated entry on success, or an ERR_PTR on failures.
+ */
+struct kdbus_queue_entry *kdbus_queue_entry_new(struct kdbus_conn *conn_dst,
+ const struct kdbus_kmsg *kmsg,
+ struct kdbus_user *user)
+{
+ struct kdbus_msg_resources *res = kmsg->res;
+ const struct kdbus_msg *msg = &kmsg->msg;
+ struct kdbus_queue_entry *entry;
+ size_t memfd_cnt = 0;
+ struct kvec kvec[2];
+ size_t meta_size;
+ size_t msg_size;
+ u64 payload_off;
+ u64 size = 0;
+ int ret = 0;
+
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&entry->entry);
+ entry->priority = msg->priority;
+ entry->dst_name_id = kmsg->dst_name_id;
+ entry->msg_res = kdbus_msg_resources_ref(res);
+ entry->proc_meta = kdbus_meta_proc_ref(kmsg->proc_meta);
+ entry->conn_meta = kdbus_meta_conn_ref(kmsg->conn_meta);
+ entry->conn = kdbus_conn_ref(conn_dst);
+
+ if (kmsg->msg.src_id == KDBUS_SRC_ID_KERNEL)
+ msg_size = msg->size;
+ else
+ msg_size = offsetof(struct kdbus_msg, items);
+
+ /* sum up the size of the needed slice */
+ size = msg_size;
+
+ if (res) {
+ size += res->vec_count *
+ KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
+
+ if (res->memfd_count) {
+ entry->memfd_offset =
+ kcalloc(res->memfd_count, sizeof(size_t),
+ GFP_KERNEL);
+ if (!entry->memfd_offset) {
+ ret = -ENOMEM;
+ goto exit_free_entry;
+ }
+
+ size += res->memfd_count *
+ KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd));
+ }
+
+ if (res->fds_count)
+ size += KDBUS_ITEM_SIZE(sizeof(int) * res->fds_count);
+
+ if (res->dst_name)
+ size += KDBUS_ITEM_SIZE(strlen(res->dst_name) + 1);
+ }
+
+ /*
+ * Remember the offset of the metadata part, so we can override
+ * this part later during kdbus_queue_entry_install().
+ */
+ entry->meta_offset = size;
+
+ if (entry->proc_meta || entry->conn_meta) {
+ entry->attach_flags =
+ atomic64_read(&conn_dst->attach_flags_recv);
+
+ ret = kdbus_meta_export_prepare(entry->proc_meta,
+ entry->conn_meta,
+ &entry->attach_flags,
+ &meta_size);
+ if (ret < 0)
+ goto exit_free_entry;
+
+ size += meta_size;
+ }
+
+ payload_off = size;
+ size += kmsg->pool_size;
+ size = KDBUS_ALIGN8(size);
+
+ ret = kdbus_conn_quota_inc(conn_dst, user, size,
+ res ? res->fds_count : 0);
+ if (ret < 0)
+ goto exit_free_entry;
+
+ entry->slice = kdbus_pool_slice_alloc(conn_dst->pool, size, true);
+ if (IS_ERR(entry->slice)) {
+ ret = PTR_ERR(entry->slice);
+ entry->slice = NULL;
+ kdbus_conn_quota_dec(conn_dst, user, size,
+ res ? res->fds_count : 0);
+ goto exit_free_entry;
+ }
+
+ /* we accounted for exactly 'size' bytes, make sure it didn't grow */
+ WARN_ON(kdbus_pool_slice_size(entry->slice) != size);
+ entry->user = kdbus_user_ref(user);
+
+ /* copy message header */
+ kvec[0].iov_base = (char *)msg;
+ kvec[0].iov_len = msg_size;
+
+ ret = kdbus_pool_slice_copy_kvec(entry->slice, 0, kvec, 1, msg_size);
+ if (ret < 0)
+ goto exit_free_entry;
+
+ /* 'size' will now track the write position */
+ size = msg_size;
+
+ /* create message payload items */
+ if (res) {
+ size_t dst_name_len = 0;
+ unsigned int i;
+ size_t sz = 0;
+
+ if (res->dst_name) {
+ dst_name_len = strlen(res->dst_name) + 1;
+ sz += KDBUS_ITEM_SIZE(dst_name_len);
+ }
+
+ for (i = 0; i < res->data_count; ++i) {
+ struct kdbus_vec v;
+ struct kdbus_memfd m;
+
+ switch (res->data[i].type) {
+ case KDBUS_MSG_DATA_VEC:
+ sz += KDBUS_ITEM_SIZE(sizeof(v));
+ break;
+
+ case KDBUS_MSG_DATA_MEMFD:
+ sz += KDBUS_ITEM_SIZE(sizeof(m));
+ break;
+ }
+ }
+
+ if (sz) {
+ struct kdbus_item *items, *item;
+
+ items = kmalloc(sz, GFP_KERNEL);
+ if (!items) {
+ ret = -ENOMEM;
+ goto exit_free_entry;
+ }
+
+ item = items;
+
+ if (res->dst_name)
+ item = kdbus_item_set(item, KDBUS_ITEM_DST_NAME,
+ res->dst_name,
+ dst_name_len);
+
+ for (i = 0; i < res->data_count; ++i) {
+ struct kdbus_msg_data *d = res->data + i;
+ struct kdbus_memfd m = {};
+ struct kdbus_vec v = {};
+
+ switch (d->type) {
+ case KDBUS_MSG_DATA_VEC:
+ v.size = d->size;
+ v.offset = d->vec.off;
+ if (v.offset != ~0ULL)
+ v.offset += payload_off;
+
+ item = kdbus_item_set(item,
+ KDBUS_ITEM_PAYLOAD_OFF,
+ &v, sizeof(v));
+ break;
+
+ case KDBUS_MSG_DATA_MEMFD:
+ /*
+ * Remember the location of memfds, so
+ * we can override the content from
+ * kdbus_queue_entry_install().
+ */
+ entry->memfd_offset[memfd_cnt++] =
+ msg_size +
+ (char *)item - (char *)items +
+ offsetof(struct kdbus_item,
+ memfd);
+
+ item = kdbus_item_set(item,
+ KDBUS_ITEM_PAYLOAD_MEMFD,
+ &m, sizeof(m));
+ break;
+ }
+ }
+
+ kvec[0].iov_base = items;
+ kvec[0].iov_len = sz;
+
+ ret = kdbus_pool_slice_copy_kvec(entry->slice, size,
+ kvec, 1, sz);
+ kfree(items);
+
+ if (ret < 0)
+ goto exit_free_entry;
+
+ size += sz;
+ }
+
+ /*
+ * Remember the location of the FD part, so we can override the
+ * content in kdbus_queue_entry_install().
+ */
+ if (res->fds_count) {
+ entry->fds_offset = size;
+ size += KDBUS_ITEM_SIZE(sizeof(int) * res->fds_count);
+ }
+ }
+
+ /* finally, copy over the actual message payload */
+ if (kmsg->iov_count) {
+ ret = kdbus_pool_slice_copy_iovec(entry->slice, payload_off,
+ kmsg->iov,
+ kmsg->iov_count,
+ kmsg->pool_size);
+ if (ret < 0)
+ goto exit_free_entry;
+ }
+
+ return entry;
+
+exit_free_entry:
+ kdbus_queue_entry_free(entry);
+ return ERR_PTR(ret);
+}
+
+/**
+ * kdbus_queue_entry_free() - free resources of an entry
+ * @entry: The entry to free
+ *
+ * Removes resources allocated by a queue entry, along with the entry itself.
+ * Note that the entry's slice is not freed at this point.
+ */
+void kdbus_queue_entry_free(struct kdbus_queue_entry *entry)
+{
+ if (!entry)
+ return;
+
+ lockdep_assert_held(&entry->conn->lock);
+
+ kdbus_queue_entry_unlink(entry);
+ kdbus_reply_unref(entry->reply);
+
+ if (entry->slice) {
+ kdbus_conn_quota_dec(entry->conn, entry->user,
+ kdbus_pool_slice_size(entry->slice),
+ entry->msg_res ?
+ entry->msg_res->fds_count : 0);
+ kdbus_pool_slice_release(entry->slice);
+ kdbus_user_unref(entry->user);
+ }
+
+ kdbus_msg_resources_unref(entry->msg_res);
+ kdbus_meta_conn_unref(entry->conn_meta);
+ kdbus_meta_proc_unref(entry->proc_meta);
+ kdbus_conn_unref(entry->conn);
+ kfree(entry->memfd_offset);
+ kfree(entry);
+}
+
+/**
+ * kdbus_queue_entry_install() - install message components into the
+ * receiver's process
+ * @entry: The queue entry to install
+ * @return_flags: Pointer to store the return flags for userspace
+ * @install_fds: Whether or not to install associated file descriptors
+ *
+ * This function will create a slice to transport the message header, the
+ * metadata items and other items for information stored in @entry, and
+ * store it as entry->slice.
+ *
+ * If @install_fds is %true, file descriptors will as well be installed.
+ * This function must always be called from the task context of the receiver.
+ *
+ * Return: 0 on success.
+ */
+int kdbus_queue_entry_install(struct kdbus_queue_entry *entry,
+ u64 *return_flags, bool install_fds)
+{
+ u64 msg_size = entry->meta_offset;
+ struct kdbus_conn *conn_dst = entry->conn;
+ struct kdbus_msg_resources *res;
+ bool incomplete_fds = false;
+ struct kvec kvec[2];
+ size_t memfds = 0;
+ int i, ret;
+
+ lockdep_assert_held(&conn_dst->lock);
+
+ if (entry->proc_meta || entry->conn_meta) {
+ size_t meta_size;
+
+ ret = kdbus_meta_export(entry->proc_meta,
+ entry->conn_meta,
+ entry->attach_flags,
+ entry->slice,
+ entry->meta_offset,
+ &meta_size);
+ if (ret < 0)
+ return ret;
+
+ msg_size += meta_size;
+ }
+
+ /* Update message size at offset 0 */
+ kvec[0].iov_base = &msg_size;
+ kvec[0].iov_len = sizeof(msg_size);
+
+ ret = kdbus_pool_slice_copy_kvec(entry->slice, 0, kvec, 1,
+ sizeof(msg_size));
+ if (ret < 0)
+ return ret;
+
+ res = entry->msg_res;
+
+ if (!res)
+ return 0;
+
+ if (res->fds_count) {
+ struct kdbus_item_header hdr;
+ size_t off;
+ int *fds;
+
+ fds = kmalloc_array(res->fds_count, sizeof(int), GFP_KERNEL);
+ if (!fds)
+ return -ENOMEM;
+
+ for (i = 0; i < res->fds_count; i++) {
+ if (install_fds) {
+ fds[i] = get_unused_fd_flags(O_CLOEXEC);
+ if (fds[i] >= 0)
+ fd_install(fds[i],
+ get_file(res->fds[i]));
+ else
+ incomplete_fds = true;
+ } else {
+ fds[i] = -1;
+ }
+ }
+
+ off = entry->fds_offset;
+
+ hdr.type = KDBUS_ITEM_FDS;
+ hdr.size = KDBUS_ITEM_HEADER_SIZE +
+ sizeof(int) * res->fds_count;
+
+ kvec[0].iov_base = &hdr;
+ kvec[0].iov_len = sizeof(hdr);
+
+ kvec[1].iov_base = fds;
+ kvec[1].iov_len = sizeof(int) * res->fds_count;
+
+ ret = kdbus_pool_slice_copy_kvec(entry->slice, off,
+ kvec, 2, hdr.size);
+ kfree(fds);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ for (i = 0; i < res->data_count; ++i) {
+ struct kdbus_msg_data *d = res->data + i;
+ struct kdbus_memfd m;
+
+ if (d->type != KDBUS_MSG_DATA_MEMFD)
+ continue;
+
+ m.start = d->memfd.start;
+ m.size = d->size;
+ m.fd = -1;
+
+ if (install_fds) {
+ m.fd = get_unused_fd_flags(O_CLOEXEC);
+ if (m.fd < 0) {
+ m.fd = -1;
+ incomplete_fds = true;
+ } else {
+ fd_install(m.fd,
+ get_file(d->memfd.file));
+ }
+ }
+
+ kvec[0].iov_base = &m;
+ kvec[0].iov_len = sizeof(m);
+
+ ret = kdbus_pool_slice_copy_kvec(entry->slice,
+ entry->memfd_offset[memfds++],
+ kvec, 1, sizeof(m));
+ if (ret < 0)
+ return ret;
+ }
+
+ if (incomplete_fds)
+ *return_flags |= KDBUS_RECV_RETURN_INCOMPLETE_FDS;
+
+ return 0;
+}
+
+/**
+ * kdbus_queue_entry_enqueue() - enqueue an entry
+ * @entry: entry to enqueue
+ * @reply: reply to link to this entry (or NULL if none)
+ *
+ * This enqueues an unqueued entry into the message queue of the linked
+ * connection. It also binds a reply object to the entry so we can remember it
+ * when the message is moved.
+ *
+ * Once this call returns (and the connection lock is released), this entry can
+ * be dequeued by the target connection. Note that the entry will not be removed
+ * from the queue until it is destroyed.
+ */
+void kdbus_queue_entry_enqueue(struct kdbus_queue_entry *entry,
+ struct kdbus_reply *reply)
+{
+ lockdep_assert_held(&entry->conn->lock);
+
+ if (WARN_ON(entry->reply) || WARN_ON(!list_empty(&entry->entry)))
+ return;
+
+ entry->reply = kdbus_reply_ref(reply);
+ kdbus_queue_entry_link(entry);
+}
+
+/**
+ * kdbus_queue_entry_move() - move queue entry
+ * @e: queue entry to move
+ * @dst: destination connection to queue the entry on
+ *
+ * This moves a queue entry onto a different connection. It allocates a new
+ * slice on the target connection and copies the message over. If the copy
+ * succeeded, we move the entry from @src to @dst.
+ *
+ * On failure, the entry is left untouched.
+ *
+ * The queue entry must be queued right now, and after the call succeeds it will
+ * be queued on the destination, but no longer on the source.
+ *
+ * The caller must hold the connection lock of the source *and* destination.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int kdbus_queue_entry_move(struct kdbus_queue_entry *e,
+ struct kdbus_conn *dst)
+{
+ struct kdbus_pool_slice *slice = NULL;
+ struct kdbus_conn *src = e->conn;
+ size_t size, fds;
+ int ret;
+
+ lockdep_assert_held(&src->lock);
+ lockdep_assert_held(&dst->lock);
+
+ if (WARN_ON(IS_ERR(e->user)) || WARN_ON(list_empty(&e->entry)))
+ return -EINVAL;
+ if (src == dst)
+ return 0;
+
+ size = kdbus_pool_slice_size(e->slice);
+ fds = e->msg_res ? e->msg_res->fds_count : 0;
+
+ ret = kdbus_conn_quota_inc(dst, e->user, size, fds);
+ if (ret < 0)
+ return ret;
+
+ slice = kdbus_pool_slice_alloc(dst->pool, size, true);
+ if (IS_ERR(slice)) {
+ ret = PTR_ERR(slice);
+ slice = NULL;
+ goto error;
+ }
+
+ ret = kdbus_pool_slice_copy(slice, e->slice);
+ if (ret < 0)
+ goto error;
+
+ kdbus_queue_entry_unlink(e);
+ kdbus_conn_quota_dec(src, e->user, size, fds);
+ kdbus_pool_slice_release(e->slice);
+ kdbus_conn_unref(e->conn);
+
+ e->slice = slice;
+ e->conn = kdbus_conn_ref(dst);
+ kdbus_queue_entry_link(e);
+
+ return 0;
+
+error:
+ kdbus_pool_slice_release(slice);
+ kdbus_conn_quota_dec(dst, e->user, size, fds);
+ return ret;
+}