From: Tom Gundersen <t...@jklm.no>

This hooks up all the file-operations on a bus1-file-descriptor. It
implements the ioctls as defined in the UAPI, as well as mmap() and
poll() support.

Signed-off-by: Tom Gundersen <t...@jklm.no>
Signed-off-by: David Herrmann <dh.herrm...@gmail.com>
---
 ipc/bus1/main.c |  46 +++
 ipc/bus1/peer.c | 934 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 ipc/bus1/peer.h |   8 +
 3 files changed, 987 insertions(+), 1 deletion(-)

diff --git a/ipc/bus1/main.c b/ipc/bus1/main.c
index 51034f3..d5a726a 100644
--- a/ipc/bus1/main.c
+++ b/ipc/bus1/main.c
@@ -14,10 +14,17 @@
 #include <linux/init.h>
 #include <linux/miscdevice.h>
 #include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/seq_file.h>
+#include <linux/uio.h>
+#include <uapi/linux/bus1.h>
 #include "main.h"
 #include "peer.h"
 #include "tests.h"
 #include "user.h"
+#include "util/active.h"
+#include "util/pool.h"
+#include "util/queue.h"
 
 static int bus1_fop_open(struct inode *inode, struct file *file)
 {
@@ -37,6 +44,41 @@ static int bus1_fop_release(struct inode *inode, struct file 
*file)
        return 0;
 }
 
+static unsigned int bus1_fop_poll(struct file *file,
+                                 struct poll_table_struct *wait)
+{
+       struct bus1_peer *peer = file->private_data;
+       unsigned int mask;
+
+       poll_wait(file, &peer->waitq, wait);
+
+       /* access queue->front unlocked */
+       rcu_read_lock();
+       if (bus1_active_is_deactivated(&peer->active)) {
+               mask = POLLHUP;
+       } else {
+               mask = POLLOUT | POLLWRNORM;
+               if (bus1_queue_is_readable_rcu(&peer->data.queue))
+                       mask |= POLLIN | POLLRDNORM;
+       }
+       rcu_read_unlock();
+
+       return mask;
+}
+
+static int bus1_fop_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       struct bus1_peer *peer = file->private_data;
+       int r;
+
+       if (!bus1_peer_acquire(peer))
+               return -ESHUTDOWN;
+
+       r = bus1_pool_mmap(&peer->data.pool, vma);
+       bus1_peer_release(peer);
+       return r;
+}
+
 static void bus1_fop_show_fdinfo(struct seq_file *m, struct file *file)
 {
        struct bus1_peer *peer = file->private_data;
@@ -48,7 +90,11 @@ const struct file_operations bus1_fops = {
        .owner                  = THIS_MODULE,
        .open                   = bus1_fop_open,
        .release                = bus1_fop_release,
+       .poll                   = bus1_fop_poll,
        .llseek                 = noop_llseek,
+       .mmap                   = bus1_fop_mmap,
+       .unlocked_ioctl         = bus1_peer_ioctl,
+       .compat_ioctl           = bus1_peer_ioctl,
        .show_fdinfo            = bus1_fop_show_fdinfo,
 };
 
diff --git a/ipc/bus1/peer.c b/ipc/bus1/peer.c
index 0ff7a98..f0da4a7 100644
--- a/ipc/bus1/peer.c
+++ b/ipc/bus1/peer.c
@@ -23,11 +23,52 @@
 #include <linux/uaccess.h>
 #include <linux/uio.h>
 #include <linux/wait.h>
+#include <uapi/linux/bus1.h>
+#include "handle.h"
 #include "main.h"
+#include "message.h"
 #include "peer.h"
+#include "tx.h"
 #include "user.h"
 #include "util.h"
 #include "util/active.h"
+#include "util/pool.h"
+#include "util/queue.h"
+
+static struct bus1_queue_node *
+bus1_peer_free_qnode(struct bus1_queue_node *qnode)
+{
+       struct bus1_message *m;
+       struct bus1_handle *h;
+
+       /*
+        * Queue-nodes are generic entities that can only be destroyed by who
+        * created them. That is, they have no embedded release callback.
+        * Instead, we must detect them by type. Since the queue logic is kept
+        * generic, it cannot provide this helper. Instead, we have this small
+        * destructor here, which simply dispatches to the correct handler.
+        */
+
+       if (qnode) {
+               switch (bus1_queue_node_get_type(qnode)) {
+               case BUS1_MSG_DATA:
+                       m = container_of(qnode, struct bus1_message, qnode);
+                       bus1_message_unref(m);
+                       break;
+               case BUS1_MSG_NODE_DESTROY:
+               case BUS1_MSG_NODE_RELEASE:
+                       h = container_of(qnode, struct bus1_handle, qnode);
+                       bus1_handle_unref(h);
+                       break;
+               case BUS1_MSG_NONE:
+               default:
+                       WARN(1, "Unknown message type\n");
+                       break;
+               }
+       }
+
+       return NULL;
+}
 
 /**
  * bus1_peer_new() - allocate new peer
@@ -47,6 +88,7 @@ struct bus1_peer *bus1_peer_new(void)
        const struct cred *cred = current_cred();
        struct bus1_peer *peer;
        struct bus1_user *user;
+       int r;
 
        user = bus1_user_ref_by_uid(cred->uid);
        if (IS_ERR(user))
@@ -75,9 +117,14 @@ struct bus1_peer *bus1_peer_new(void)
 
        /* initialize peer-private section */
        mutex_init(&peer->local.lock);
+       peer->local.seed = NULL;
        peer->local.map_handles = RB_ROOT;
        peer->local.handle_ids = 0;
 
+       r = bus1_pool_init(&peer->data.pool, KBUILD_MODNAME "-peer");
+       if (r < 0)
+               goto error;
+
        if (!IS_ERR_OR_NULL(bus1_debugdir)) {
                char idstr[22];
 
@@ -96,6 +143,103 @@ struct bus1_peer *bus1_peer_new(void)
 
        bus1_active_activate(&peer->active);
        return peer;
+
+error:
+       bus1_peer_free(peer);
+       return ERR_PTR(r);
+}
+
+static void bus1_peer_flush(struct bus1_peer *peer, u64 flags)
+{
+       struct bus1_queue_node *qlist, *qnode;
+       struct bus1_handle *h, *safe;
+       struct bus1_tx tx;
+       size_t n_slices;
+       u64 ts;
+       int n;
+
+       lockdep_assert_held(&peer->local.lock);
+
+       bus1_tx_init(&tx, peer);
+
+       if (flags & BUS1_PEER_RESET_FLAG_FLUSH) {
+               /* protect handles on the seed */
+               if (!(flags & BUS1_PEER_RESET_FLAG_FLUSH_SEED) &&
+                   peer->local.seed) {
+                       /*
+                        * XXX: When the flush operation does not ask for a
+                        *      RESET of the seed, we want to protect the nodes
+                        *      that were instantiated with this seed.
+                        *      Right now, we do not support this, but rather
+                        *      treat all nodes as local nodes. If node
+                        *      injection will be supported one day, we should
+                        *      make sure to drop n_user of all seed-handles to
+                        *      0 here, to make sure they're skipped in the
+                        *      mass-destruction below.
+                        */
+               }
+
+               /* first destroy all live anchors */
+               mutex_lock(&peer->data.lock);
+               rbtree_postorder_for_each_entry_safe(h, safe,
+                                                    &peer->local.map_handles,
+                                                    rb_to_peer) {
+                       if (!bus1_handle_is_anchor(h) ||
+                           !bus1_handle_is_live(h))
+                               continue;
+
+                       bus1_handle_destroy_locked(h, &tx);
+               }
+               mutex_unlock(&peer->data.lock);
+
+               /* atomically commit the destruction transaction */
+               ts = bus1_tx_commit(&tx);
+
+               /* now release all user handles */
+               rbtree_postorder_for_each_entry_safe(h, safe,
+                                                    &peer->local.map_handles,
+                                                    rb_to_peer) {
+                       n = atomic_xchg(&h->n_user, 0);
+                       bus1_handle_forget_keep(h);
+
+                       if (bus1_handle_is_anchor(h)) {
+                               if (n > 1)
+                                       bus1_handle_release_n(h, n - 1, true);
+                               bus1_handle_release(h, false);
+                       } else {
+                               bus1_handle_release_n(h, n, true);
+                       }
+               }
+               peer->local.map_handles = RB_ROOT;
+
+               /* finally flush the queue and pool */
+               mutex_lock(&peer->data.lock);
+               qlist = bus1_queue_flush(&peer->data.queue, ts);
+               bus1_pool_flush(&peer->data.pool, &n_slices);
+               mutex_unlock(&peer->data.lock);
+
+               while ((qnode = qlist)) {
+                       qlist = qnode->next;
+                       qnode->next = NULL;
+                       bus1_peer_free_qnode(qnode);
+               }
+       }
+
+       /* drop seed if requested */
+       if (flags & BUS1_PEER_RESET_FLAG_FLUSH_SEED)
+               peer->local.seed = bus1_message_unref(peer->local.seed);
+
+       bus1_tx_deinit(&tx);
+}
+
+static void bus1_peer_cleanup(struct bus1_active *a, void *userdata)
+{
+       struct bus1_peer *peer = container_of(a, struct bus1_peer, active);
+
+       mutex_lock(&peer->local.lock);
+       bus1_peer_flush(peer, BUS1_PEER_RESET_FLAG_FLUSH |
+                             BUS1_PEER_RESET_FLAG_FLUSH_SEED);
+       mutex_unlock(&peer->local.lock);
 }
 
 static int bus1_peer_disconnect(struct bus1_peer *peer)
@@ -104,7 +248,7 @@ static int bus1_peer_disconnect(struct bus1_peer *peer)
        bus1_active_drain(&peer->active, &peer->waitq);
 
        if (!bus1_active_cleanup(&peer->active, &peer->waitq,
-                                NULL, NULL))
+                                bus1_peer_cleanup, NULL))
                return -ESHUTDOWN;
 
        return 0;
@@ -133,6 +277,7 @@ struct bus1_peer *bus1_peer_free(struct bus1_peer *peer)
 
        /* deinitialize peer-private section */
        WARN_ON(!RB_EMPTY_ROOT(&peer->local.map_handles));
+       WARN_ON(peer->local.seed);
        mutex_destroy(&peer->local.lock);
 
        /* deinitialize data section */
@@ -150,3 +295,790 @@ struct bus1_peer *bus1_peer_free(struct bus1_peer *peer)
 
        return NULL;
 }
+
+static int bus1_peer_ioctl_peer_query(struct bus1_peer *peer,
+                                     unsigned long arg)
+{
+       struct bus1_cmd_peer_reset __user *uparam = (void __user *)arg;
+       struct bus1_cmd_peer_reset param;
+
+       BUILD_BUG_ON(_IOC_SIZE(BUS1_CMD_PEER_QUERY) != sizeof(param));
+
+       if (copy_from_user(&param, uparam, sizeof(param)))
+               return -EFAULT;
+       if (unlikely(param.flags))
+               return -EINVAL;
+
+       mutex_lock(&peer->local.lock);
+       param.peer_flags = peer->flags & BUS1_PEER_FLAG_WANT_SECCTX;
+       param.max_slices = -1;
+       param.max_handles = -1;
+       param.max_inflight_bytes = -1;
+       param.max_inflight_fds = -1;
+       mutex_unlock(&peer->local.lock);
+
+       return copy_to_user(uparam, &param, sizeof(param)) ? -EFAULT : 0;
+}
+
+static int bus1_peer_ioctl_peer_reset(struct bus1_peer *peer,
+                                     unsigned long arg)
+{
+       struct bus1_cmd_peer_reset __user *uparam = (void __user *)arg;
+       struct bus1_cmd_peer_reset param;
+
+       BUILD_BUG_ON(_IOC_SIZE(BUS1_CMD_PEER_RESET) != sizeof(param));
+
+       if (copy_from_user(&param, uparam, sizeof(param)))
+               return -EFAULT;
+       if (unlikely(param.flags & ~(BUS1_PEER_RESET_FLAG_FLUSH |
+                                    BUS1_PEER_RESET_FLAG_FLUSH_SEED)))
+               return -EINVAL;
+       if (unlikely(param.peer_flags != -1 &&
+                    (param.peer_flags & ~BUS1_PEER_FLAG_WANT_SECCTX)))
+               return -EINVAL;
+       if (unlikely(param.max_slices != -1 ||
+                    param.max_handles != -1 ||
+                    param.max_inflight_bytes != -1 ||
+                    param.max_inflight_fds != -1))
+               return -EINVAL;
+
+       mutex_lock(&peer->local.lock);
+
+       if (param.peer_flags != -1)
+               peer->flags = param.peer_flags;
+
+       bus1_peer_flush(peer, param.flags);
+
+       mutex_unlock(&peer->local.lock);
+
+       return 0;
+}
+
+static int bus1_peer_ioctl_handle_release(struct bus1_peer *peer,
+                                         unsigned long arg)
+{
+       struct bus1_handle *h = NULL;
+       bool is_new, strong = true;
+       u64 id;
+       int r;
+
+       BUILD_BUG_ON(_IOC_SIZE(BUS1_CMD_HANDLE_RELEASE) != sizeof(id));
+
+       if (get_user(id, (const u64 __user *)arg))
+               return -EFAULT;
+
+       mutex_lock(&peer->local.lock);
+
+       h = bus1_handle_import(peer, id, &is_new);
+       if (IS_ERR(h)) {
+               r = PTR_ERR(h);
+               goto exit;
+       }
+
+       if (is_new) {
+               /*
+                * A handle is non-public only if the import lazily created the
+                * node. In that case the node is live and the last reference
+                * cannot be dropped until the node is destroyed. Hence, we
+                * return EBUSY.
+                *
+                * Since we did not modify the node, and the node was lazily
+                * created, there is no point in keeping the node allocated. We
+                * simply pretend we didn't allocate it so the next operation
+                * will just do the lazy allocation again.
+                */
+               bus1_handle_forget(h);
+               r = -EBUSY;
+               goto exit;
+       }
+
+       if (atomic_read(&h->n_user) == 1 && bus1_handle_is_anchor(h)) {
+               if (bus1_handle_is_live(h)) {
+                       r = -EBUSY;
+                       goto exit;
+               }
+
+               strong = false;
+       }
+
+       WARN_ON(atomic_dec_return(&h->n_user) < 0);
+       bus1_handle_forget(h);
+       bus1_handle_release(h, strong);
+
+       r = 0;
+
+exit:
+       mutex_unlock(&peer->local.lock);
+       bus1_handle_unref(h);
+       return r;
+}
+
+static int bus1_peer_transfer(struct bus1_peer *src,
+                             struct bus1_peer *dst,
+                             struct bus1_cmd_handle_transfer *param)
+{
+       struct bus1_handle *src_h = NULL, *dst_h = NULL;
+       bool is_new;
+       int r;
+
+       bus1_mutex_lock2(&src->local.lock, &dst->local.lock);
+
+       src_h = bus1_handle_import(src, param->src_handle, &is_new);
+       if (IS_ERR(src_h)) {
+               r = PTR_ERR(src_h);
+               src_h = NULL;
+               goto exit;
+       }
+
+       if (!bus1_handle_is_live(src_h)) {
+               /*
+                * If @src_h has a destruction queued, we cannot guarantee that
+                * we can join the transaction. Hence, we bail out and tell the
+                * caller that the node is already destroyed.
+                *
+                * In case @src_h->anchor is on one of the peers involved, this
+                * is properly synchronized. However, if it is a 3rd party node
+                * then it might not be committed, yet.
+                *
+                * XXX: We really ought to settle on the destruction. This
+                *      requires some waitq to settle on, though.
+                */
+               param->dst_handle = BUS1_HANDLE_INVALID;
+               r = 0;
+               goto exit;
+       }
+
+       dst_h = bus1_handle_ref_by_other(dst, src_h);
+       if (!dst_h) {
+               dst_h = bus1_handle_new_remote(dst, src_h);
+               if (IS_ERR(dst_h)) {
+                       r = PTR_ERR(dst_h);
+                       dst_h = NULL;
+                       goto exit;
+               }
+       }
+
+       if (is_new) {
+               WARN_ON(src_h != bus1_handle_acquire(src_h, false));
+               WARN_ON(atomic_inc_return(&src_h->n_user) != 1);
+       }
+
+       dst_h = bus1_handle_acquire(dst_h, true);
+       param->dst_handle = bus1_handle_identify(dst_h);
+       bus1_handle_export(dst_h);
+       WARN_ON(atomic_inc_return(&dst_h->n_user) < 1);
+
+       r = 0;
+
+exit:
+       bus1_handle_forget(src_h);
+       bus1_mutex_unlock2(&src->local.lock, &dst->local.lock);
+       bus1_handle_unref(dst_h);
+       bus1_handle_unref(src_h);
+       return r;
+}
+
+static int bus1_peer_ioctl_handle_transfer(struct bus1_peer *src,
+                                          unsigned long arg)
+{
+       struct bus1_cmd_handle_transfer __user *uparam = (void __user *)arg;
+       struct bus1_cmd_handle_transfer param;
+       struct bus1_peer *dst = NULL;
+       struct fd dst_f;
+       int r;
+
+       BUILD_BUG_ON(_IOC_SIZE(BUS1_CMD_HANDLE_TRANSFER) != sizeof(param));
+
+       if (copy_from_user(&param, (void __user *)arg, sizeof(param)))
+               return -EFAULT;
+       if (unlikely(param.flags))
+               return -EINVAL;
+
+       if (param.dst_fd != -1) {
+               dst_f = fdget(param.dst_fd);
+               if (!dst_f.file)
+                       return -EBADF;
+               if (dst_f.file->f_op != &bus1_fops) {
+                       fdput(dst_f);
+                       return -EOPNOTSUPP;
+               }
+
+               dst = bus1_peer_acquire(dst_f.file->private_data);
+               fdput(dst_f);
+               if (!dst)
+                       return -ESHUTDOWN;
+       }
+
+       r = bus1_peer_transfer(src, dst ?: src, &param);
+       bus1_peer_release(dst);
+       if (r < 0)
+               return r;
+
+       return copy_to_user(uparam, &param, sizeof(param)) ? -EFAULT : 0;
+}
+
+static int bus1_peer_ioctl_nodes_destroy(struct bus1_peer *peer,
+                                        unsigned long arg)
+{
+       struct bus1_cmd_nodes_destroy param;
+       size_t n_charge = 0, n_discharge = 0;
+       struct bus1_handle *h, *list = BUS1_TAIL;
+       const u64 __user *ptr_nodes;
+       struct bus1_tx tx;
+       bool is_new;
+       u64 i, id;
+       int r;
+
+       BUILD_BUG_ON(_IOC_SIZE(BUS1_CMD_NODES_DESTROY) != sizeof(param));
+
+       if (copy_from_user(&param, (void __user *)arg, sizeof(param)))
+               return -EFAULT;
+       if (unlikely(param.flags & ~BUS1_NODES_DESTROY_FLAG_RELEASE_HANDLES))
+               return -EINVAL;
+       if (unlikely(param.ptr_nodes != (u64)(unsigned long)param.ptr_nodes))
+               return -EFAULT;
+
+       mutex_lock(&peer->local.lock);
+
+       bus1_tx_init(&tx, peer);
+       ptr_nodes = (const u64 __user *)(unsigned long)param.ptr_nodes;
+
+       for (i = 0; i < param.n_nodes; ++i) {
+               if (get_user(id, ptr_nodes + i)) {
+                       r = -EFAULT;
+                       goto exit;
+               }
+
+               h = bus1_handle_import(peer, id, &is_new);
+               if (IS_ERR(h)) {
+                       r = PTR_ERR(h);
+                       goto exit;
+               }
+
+               if (h->tlink) {
+                       bus1_handle_unref(h);
+                       r = -ENOTUNIQ;
+                       goto exit;
+               }
+
+               h->tlink = list;
+               list = h;
+
+               if (!bus1_handle_is_anchor(h)) {
+                       r = -EREMOTE;
+                       goto exit;
+               }
+
+               if (!bus1_handle_is_live(h)) {
+                       r = -ESTALE;
+                       goto exit;
+               }
+
+               if (is_new)
+                       ++n_charge;
+       }
+
+       /* nothing below this point can fail, anymore */
+
+       mutex_lock(&peer->data.lock);
+       for (h = list; h != BUS1_TAIL; h = h->tlink) {
+               if (!bus1_handle_is_public(h)) {
+                       WARN_ON(h != bus1_handle_acquire_locked(h, false));
+                       WARN_ON(atomic_inc_return(&h->n_user) != 1);
+               }
+
+               bus1_handle_destroy_locked(h, &tx);
+       }
+       mutex_unlock(&peer->data.lock);
+
+       bus1_tx_commit(&tx);
+
+       while (list != BUS1_TAIL) {
+               h = list;
+               list = h->tlink;
+               h->tlink = NULL;
+
+               if (param.flags & BUS1_NODES_DESTROY_FLAG_RELEASE_HANDLES) {
+                       ++n_discharge;
+                       if (atomic_dec_return(&h->n_user) == 0) {
+                               bus1_handle_forget(h);
+                               bus1_handle_release(h, false);
+                       } else {
+                               bus1_handle_release(h, true);
+                       }
+               }
+
+               bus1_handle_unref(h);
+       }
+
+       r = 0;
+
+exit:
+       while (list != BUS1_TAIL) {
+               h = list;
+               list = h->tlink;
+               h->tlink = NULL;
+
+               bus1_handle_forget(h);
+               bus1_handle_unref(h);
+       }
+       bus1_tx_deinit(&tx);
+       mutex_unlock(&peer->local.lock);
+       return r;
+}
+
+static int bus1_peer_ioctl_slice_release(struct bus1_peer *peer,
+                                        unsigned long arg)
+{
+       size_t n_slices = 0;
+       u64 offset;
+       int r;
+
+       BUILD_BUG_ON(_IOC_SIZE(BUS1_CMD_SLICE_RELEASE) != sizeof(offset));
+
+       if (get_user(offset, (const u64 __user *)arg))
+               return -EFAULT;
+
+       mutex_lock(&peer->data.lock);
+       r = bus1_pool_release_user(&peer->data.pool, offset, &n_slices);
+       mutex_unlock(&peer->data.lock);
+       return r;
+}
+
+static struct bus1_message *bus1_peer_new_message(struct bus1_peer *peer,
+                                                 struct bus1_factory *f,
+                                                 u64 id)
+{
+       struct bus1_message *m = NULL;
+       struct bus1_handle *h = NULL;
+       struct bus1_peer *p = NULL;
+       bool is_new;
+       int r;
+
+       h = bus1_handle_import(peer, id, &is_new);
+       if (IS_ERR(h))
+               return ERR_CAST(h);
+
+       if (h->tlink) {
+               r = -ENOTUNIQ;
+               goto error;
+       }
+
+       if (bus1_handle_is_anchor(h))
+               p = bus1_peer_acquire(peer);
+       else
+               p = bus1_handle_acquire_owner(h);
+       if (!p) {
+               r = -ESHUTDOWN;
+               goto error;
+       }
+
+       m = bus1_factory_instantiate(f, h, p);
+       if (IS_ERR(m)) {
+               r = PTR_ERR(m);
+               goto error;
+       }
+
+       /* marker to detect duplicates */
+       h->tlink = BUS1_TAIL;
+
+       /* m->dst pins the handle for us */
+       bus1_handle_unref(h);
+
+       /* merge charge into factory (which shares the lookup with us) */
+       if (is_new)
+               ++f->n_handles_charge;
+
+       return m;
+
+error:
+       bus1_peer_release(p);
+       if (is_new)
+               bus1_handle_forget(h);
+       bus1_handle_unref(h);
+       return ERR_PTR(r);
+}
+
+static int bus1_peer_ioctl_send(struct bus1_peer *peer,
+                               unsigned long arg)
+{
+       struct bus1_queue_node *mlist = NULL;
+       struct bus1_factory *factory = NULL;
+       const u64 __user *ptr_destinations;
+       struct bus1_cmd_send param;
+       struct bus1_message *m;
+       struct bus1_peer *p;
+       size_t i, n_charge = 0;
+       struct bus1_tx tx;
+       u8 stack[512];
+       u64 id;
+       int r;
+
+       BUILD_BUG_ON(_IOC_SIZE(BUS1_CMD_SEND) != sizeof(param));
+
+       if (copy_from_user(&param, (void __user *)arg, sizeof(param)))
+               return -EFAULT;
+       if (unlikely(param.flags & ~(BUS1_SEND_FLAG_CONTINUE |
+                                    BUS1_SEND_FLAG_SEED)))
+               return -EINVAL;
+
+       /* check basic limits; avoids integer-overflows later on */
+       if (unlikely(param.n_destinations > INT_MAX) ||
+           unlikely(param.n_vecs > UIO_MAXIOV) ||
+           unlikely(param.n_fds > BUS1_FD_MAX))
+               return -EMSGSIZE;
+
+       /* 32bit pointer validity checks */
+       if (unlikely(param.ptr_destinations !=
+                    (u64)(unsigned long)param.ptr_destinations) ||
+           unlikely(param.ptr_errors !=
+                    (u64)(unsigned long)param.ptr_errors) ||
+           unlikely(param.ptr_vecs !=
+                    (u64)(unsigned long)param.ptr_vecs) ||
+           unlikely(param.ptr_handles !=
+                    (u64)(unsigned long)param.ptr_handles) ||
+           unlikely(param.ptr_fds !=
+                    (u64)(unsigned long)param.ptr_fds))
+               return -EFAULT;
+
+       mutex_lock(&peer->local.lock);
+
+       bus1_tx_init(&tx, peer);
+       ptr_destinations =
+               (const u64 __user *)(unsigned long)param.ptr_destinations;
+
+       factory = bus1_factory_new(peer, &param, stack, sizeof(stack));
+       if (IS_ERR(factory)) {
+               r = PTR_ERR(factory);
+               factory = NULL;
+               goto exit;
+       }
+
+       if (param.flags & BUS1_SEND_FLAG_SEED) {
+               if (unlikely((param.flags & BUS1_SEND_FLAG_CONTINUE) ||
+                            param.n_destinations)) {
+                       r = -EINVAL;
+                       goto exit;
+               }
+
+               /* XXX: set seed */
+               r = -ENOTSUPP;
+               goto exit;
+       } else {
+               for (i = 0; i < param.n_destinations; ++i) {
+                       if (get_user(id, ptr_destinations + i)) {
+                               r = -EFAULT;
+                               goto exit;
+                       }
+
+                       m = bus1_peer_new_message(peer, factory, id);
+                       if (IS_ERR(m)) {
+                               r = PTR_ERR(m);
+                               goto exit;
+                       }
+
+                       if (!bus1_handle_is_public(m->dst))
+                               ++n_charge;
+
+                       m->qnode.next = mlist;
+                       mlist = &m->qnode;
+               }
+
+               r = bus1_factory_seal(factory);
+               if (r < 0)
+                       goto exit;
+
+               /*
+                * Now everything is prepared, charged, and pinned. Iterate
+                * each message, acquire references, and stage the message.
+                * From here on, we must not error out, anymore.
+                */
+
+               while (mlist) {
+                       m = container_of(mlist, struct bus1_message, qnode);
+                       mlist = m->qnode.next;
+                       m->qnode.next = NULL;
+
+                       if (!bus1_handle_is_public(m->dst)) {
+                               --factory->n_handles_charge;
+                               WARN_ON(m->dst != bus1_handle_acquire(m->dst,
+                                                                     false));
+                               WARN_ON(atomic_inc_return(&m->dst->n_user)
+                                                                       != 1);
+                       }
+
+                       m->dst->tlink = NULL;
+
+                       /* this consumes @m and @m->qnode.owner */
+                       bus1_message_stage(m, &tx);
+               }
+
+               WARN_ON(factory->n_handles_charge != 0);
+               bus1_tx_commit(&tx);
+       }
+
+       r = 0;
+
+exit:
+       while (mlist) {
+               m = container_of(mlist, struct bus1_message, qnode);
+               mlist = m->qnode.next;
+               m->qnode.next = NULL;
+
+               p = m->qnode.owner;
+               m->dst->tlink = NULL;
+
+               bus1_handle_forget(m->dst);
+               bus1_message_unref(m);
+               bus1_peer_release(p);
+       }
+       bus1_factory_free(factory);
+       bus1_tx_deinit(&tx);
+       mutex_unlock(&peer->local.lock);
+       return r;
+}
+
+static struct bus1_queue_node *bus1_peer_peek(struct bus1_peer *peer,
+                                             struct bus1_cmd_recv *param,
+                                             bool *morep)
+{
+       struct bus1_queue_node *qnode;
+       struct bus1_message *m;
+       struct bus1_handle *h;
+       u64 ts;
+
+       lockdep_assert_held(&peer->local.lock);
+
+       if (unlikely(param->flags & BUS1_RECV_FLAG_SEED)) {
+               if (!peer->local.seed)
+                       return ERR_PTR(-EAGAIN);
+
+               *morep = false;
+               return &peer->local.seed->qnode;
+       }
+
+       mutex_lock(&peer->data.lock);
+       while ((qnode = bus1_queue_peek(&peer->data.queue, morep))) {
+               switch (bus1_queue_node_get_type(qnode)) {
+               case BUS1_MSG_DATA:
+                       m = container_of(qnode, struct bus1_message, qnode);
+                       h = m->dst;
+                       break;
+               case BUS1_MSG_NODE_DESTROY:
+               case BUS1_MSG_NODE_RELEASE:
+                       m = NULL;
+                       h = container_of(qnode, struct bus1_handle, qnode);
+                       break;
+               case BUS1_MSG_NONE:
+               default:
+                       mutex_unlock(&peer->data.lock);
+                       WARN(1, "Unknown message type\n");
+                       return ERR_PTR(-ENOTRECOVERABLE);
+               }
+
+               ts = bus1_queue_node_get_timestamp(qnode);
+               if (ts <= peer->data.queue.flush ||
+                   !bus1_handle_is_public(h) ||
+                   !bus1_handle_is_live_at(h, ts)) {
+                       bus1_queue_remove(&peer->data.queue, &peer->waitq,
+                                         qnode);
+                       if (m) {
+                               mutex_unlock(&peer->data.lock);
+                               bus1_message_unref(m);
+                               mutex_lock(&peer->data.lock);
+                       } else {
+                               bus1_handle_unref(h);
+                       }
+
+                       continue;
+               }
+
+               if (!m && !(param->flags & BUS1_RECV_FLAG_PEEK))
+                       bus1_queue_remove(&peer->data.queue, &peer->waitq,
+                                         qnode);
+
+               break;
+       }
+       mutex_unlock(&peer->data.lock);
+
+       return qnode ?: ERR_PTR(-EAGAIN);
+}
+
+static int bus1_peer_ioctl_recv(struct bus1_peer *peer,
+                               unsigned long arg)
+{
+       struct bus1_queue_node *qnode = NULL;
+       struct bus1_cmd_recv param;
+       struct bus1_message *m;
+       struct bus1_handle *h;
+       unsigned int type;
+       bool more = false;
+       int r;
+
+       BUILD_BUG_ON(_IOC_SIZE(BUS1_CMD_RECV) != sizeof(param));
+
+       if (copy_from_user(&param, (void __user *)arg, sizeof(param)))
+               return -EFAULT;
+       if (unlikely(param.flags & ~(BUS1_RECV_FLAG_PEEK |
+                                    BUS1_RECV_FLAG_SEED |
+                                    BUS1_RECV_FLAG_INSTALL_FDS)))
+               return -EINVAL;
+
+       mutex_lock(&peer->local.lock);
+
+       qnode = bus1_peer_peek(peer, &param, &more);
+       if (IS_ERR(qnode)) {
+               r = PTR_ERR(qnode);
+               goto exit;
+       }
+
+       type = bus1_queue_node_get_type(qnode);
+       switch (type) {
+       case BUS1_MSG_DATA:
+               m = container_of(qnode, struct bus1_message, qnode);
+               WARN_ON(m->dst->id == BUS1_HANDLE_INVALID);
+
+               if (param.max_offset < m->slice->offset + m->slice->size) {
+                       r = -ERANGE;
+                       goto exit;
+               }
+
+               r = bus1_message_install(m, &param);
+               if (r < 0)
+                       goto exit;
+
+               param.msg.type = BUS1_MSG_DATA;
+               param.msg.flags = m->flags;
+               param.msg.destination = m->dst->id;
+               param.msg.uid = m->uid;
+               param.msg.gid = m->gid;
+               param.msg.pid = m->pid;
+               param.msg.tid = m->tid;
+               param.msg.offset = m->slice->offset;
+               param.msg.n_bytes = m->n_bytes;
+               param.msg.n_handles = m->n_handles;
+               param.msg.n_fds = m->n_files;
+               param.msg.n_secctx = m->n_secctx;
+
+               if (likely(!(param.flags & BUS1_RECV_FLAG_PEEK))) {
+                       if (unlikely(param.flags & BUS1_RECV_FLAG_SEED)) {
+                               peer->local.seed = NULL;
+                       } else {
+                               mutex_lock(&peer->data.lock);
+                               bus1_queue_remove(&peer->data.queue,
+                                                 &peer->waitq, qnode);
+                               mutex_unlock(&peer->data.lock);
+                       }
+                       bus1_message_unref(m);
+               }
+               break;
+       case BUS1_MSG_NODE_DESTROY:
+       case BUS1_MSG_NODE_RELEASE:
+               h = container_of(qnode, struct bus1_handle, qnode);
+               WARN_ON(h->id == BUS1_HANDLE_INVALID);
+
+               param.msg.type = type;
+               param.msg.flags = 0;
+               param.msg.destination = h->id;
+               param.msg.uid = -1;
+               param.msg.gid = -1;
+               param.msg.pid = 0;
+               param.msg.tid = 0;
+               param.msg.offset = BUS1_OFFSET_INVALID;
+               param.msg.n_bytes = 0;
+               param.msg.n_handles = 0;
+               param.msg.n_fds = 0;
+               param.msg.n_secctx = 0;
+
+               if (likely(!(param.flags & BUS1_RECV_FLAG_PEEK)))
+                       bus1_handle_unref(h);
+               break;
+       case BUS1_MSG_NONE:
+       default:
+               WARN(1, "Unknown message type\n");
+               r = -ENOTRECOVERABLE;
+               goto exit;
+       }
+
+       if (more)
+               param.msg.flags |= BUS1_MSG_FLAG_CONTINUE;
+
+       if (copy_to_user((void __user *)arg, &param, sizeof(param)))
+               r = -EFAULT;
+       else
+               r = 0;
+
+exit:
+       mutex_unlock(&peer->local.lock);
+       return r;
+}
+
+/**
+ * bus1_peer_ioctl() - handle peer ioctls
+ * @file:              file the ioctl is called on
+ * @cmd:               ioctl command
+ * @arg:               ioctl argument
+ *
+ * This handles the given ioctl (cmd+arg) on a peer. This expects the peer to
+ * be stored in the private_data field of @file.
+ *
+ * Multiple ioctls can be called in parallel just fine. No locking is needed.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+long bus1_peer_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       struct bus1_peer *peer = file->private_data;
+       int r;
+
+       /*
+        * First handle ioctls that do not require an active-reference, then
+        * all the remaining ones wrapped in an active reference.
+        */
+       switch (cmd) {
+       case BUS1_CMD_PEER_DISCONNECT:
+               if (unlikely(arg))
+                       return -EINVAL;
+
+               r = bus1_peer_disconnect(peer);
+               break;
+       default:
+               if (!bus1_peer_acquire(peer))
+                       return -ESHUTDOWN;
+
+               switch (cmd) {
+               case BUS1_CMD_PEER_QUERY:
+                       r = bus1_peer_ioctl_peer_query(peer, arg);
+                       break;
+               case BUS1_CMD_PEER_RESET:
+                       r = bus1_peer_ioctl_peer_reset(peer, arg);
+                       break;
+               case BUS1_CMD_HANDLE_RELEASE:
+                       r = bus1_peer_ioctl_handle_release(peer, arg);
+                       break;
+               case BUS1_CMD_HANDLE_TRANSFER:
+                       r = bus1_peer_ioctl_handle_transfer(peer, arg);
+                       break;
+               case BUS1_CMD_NODES_DESTROY:
+                       r = bus1_peer_ioctl_nodes_destroy(peer, arg);
+                       break;
+               case BUS1_CMD_SLICE_RELEASE:
+                       r = bus1_peer_ioctl_slice_release(peer, arg);
+                       break;
+               case BUS1_CMD_SEND:
+                       r = bus1_peer_ioctl_send(peer, arg);
+                       break;
+               case BUS1_CMD_RECV:
+                       r = bus1_peer_ioctl_recv(peer, arg);
+                       break;
+               default:
+                       r = -ENOTTY;
+                       break;
+               }
+
+               bus1_peer_release(peer);
+               break;
+       }
+
+       return r;
+}
diff --git a/ipc/bus1/peer.h b/ipc/bus1/peer.h
index 5eb558f..26c051f 100644
--- a/ipc/bus1/peer.h
+++ b/ipc/bus1/peer.h
@@ -52,11 +52,13 @@
 #include <linux/rcupdate.h>
 #include <linux/rbtree.h>
 #include <linux/wait.h>
+#include <uapi/linux/bus1.h>
 #include "user.h"
 #include "util/active.h"
 #include "util/pool.h"
 #include "util/queue.h"
 
+struct bus1_message;
 struct cred;
 struct dentry;
 struct pid_namespace;
@@ -73,8 +75,12 @@ struct pid_namespace;
  * @active:                    active references
  * @debugdir:                  debugfs root of this peer, or NULL/ERR_PTR
  * @data.lock:                 data lock
+ * @data.pool:                 data pool
  * @data.queue:                        message queue
  * @local.lock:                        local peer runtime lock
+ * @local.seed:                        pinned seed message
+ * @local.map_handles:         map of owned handles (by handle ID)
+ * @local.handle_ids:          handle ID allocator
  */
 struct bus1_peer {
        u64 id;
@@ -95,6 +101,7 @@ struct bus1_peer {
 
        struct {
                struct mutex lock;
+               struct bus1_message *seed;
                struct rb_root map_handles;
                u64 handle_ids;
        } local;
@@ -102,6 +109,7 @@ struct bus1_peer {
 
 struct bus1_peer *bus1_peer_new(void);
 struct bus1_peer *bus1_peer_free(struct bus1_peer *peer);
+long bus1_peer_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
 
 /**
  * bus1_peer_acquire() - acquire active reference to peer
-- 
2.10.1

Reply via email to