Import kdbus test 32/269232/2
authorMateusz Majewski <m.majewski2@samsung.com>
Mon, 6 Dec 2021 10:27:27 +0000 (11:27 +0100)
committerŁukasz Stelmach <l.stelmach@samsung.com>
Wed, 12 Jan 2022 21:14:51 +0000 (22:14 +0100)
Change-Id: I02d685dbb73055daebd5bed9640937ac05e33e35
Signed-off-by: Łukasz Stelmach <l.stelmach@samsung.com>
32 files changed:
samples/.gitignore [new file with mode: 0644]
samples/Makefile [new file with mode: 0644]
samples/kdbus-api.h [new file with mode: 0644]
samples/kdbus-workers.c [new file with mode: 0644]
tests/kdbus/.gitignore [new file with mode: 0644]
tests/kdbus/Makefile [new file with mode: 0644]
tests/kdbus/kdbus-api.h [new file with mode: 0644]
tests/kdbus/kdbus-enum.c [new file with mode: 0644]
tests/kdbus/kdbus-enum.h [new file with mode: 0644]
tests/kdbus/kdbus-test.c [new file with mode: 0644]
tests/kdbus/kdbus-test.h [new file with mode: 0644]
tests/kdbus/kdbus-util.c [new file with mode: 0644]
tests/kdbus/kdbus-util.h [new file with mode: 0644]
tests/kdbus/test-activator.c [new file with mode: 0644]
tests/kdbus/test-benchmark.c [new file with mode: 0644]
tests/kdbus/test-bus.c [new file with mode: 0644]
tests/kdbus/test-chat.c [new file with mode: 0644]
tests/kdbus/test-connection.c [new file with mode: 0644]
tests/kdbus/test-daemon.c [new file with mode: 0644]
tests/kdbus/test-endpoint.c [new file with mode: 0644]
tests/kdbus/test-fd.c [new file with mode: 0644]
tests/kdbus/test-free.c [new file with mode: 0644]
tests/kdbus/test-match.c [new file with mode: 0644]
tests/kdbus/test-message.c [new file with mode: 0644]
tests/kdbus/test-metadata-ns.c [new file with mode: 0644]
tests/kdbus/test-monitor.c [new file with mode: 0644]
tests/kdbus/test-names.c [new file with mode: 0644]
tests/kdbus/test-policy-ns.c [new file with mode: 0644]
tests/kdbus/test-policy-priv.c [new file with mode: 0644]
tests/kdbus/test-policy.c [new file with mode: 0644]
tests/kdbus/test-sync.c [new file with mode: 0644]
tests/kdbus/test-timeout.c [new file with mode: 0644]

diff --git a/samples/.gitignore b/samples/.gitignore
new file mode 100644 (file)
index 0000000..ee07d98
--- /dev/null
@@ -0,0 +1 @@
+kdbus-workers
diff --git a/samples/Makefile b/samples/Makefile
new file mode 100644 (file)
index 0000000..137f842
--- /dev/null
@@ -0,0 +1,9 @@
+# kbuild trick to avoid linker error. Can be omitted if a module is built.
+obj- := dummy.o
+
+hostprogs-$(CONFIG_SAMPLE_KDBUS) += kdbus-workers
+
+always := $(hostprogs-y)
+
+HOSTCFLAGS_kdbus-workers.o += -I$(objtree)/usr/include
+HOSTLOADLIBES_kdbus-workers := -lrt
diff --git a/samples/kdbus-api.h b/samples/kdbus-api.h
new file mode 100644 (file)
index 0000000..5ed5907
--- /dev/null
@@ -0,0 +1,114 @@
+#ifndef KDBUS_API_H
+#define KDBUS_API_H
+
+#include <sys/ioctl.h>
+#include <linux/kdbus.h>
+
+#define KDBUS_ALIGN8(l) (((l) + 7) & ~7)
+#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data)
+#define KDBUS_ITEM_SIZE(s) KDBUS_ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE)
+#define KDBUS_ITEM_NEXT(item) \
+       (typeof(item))(((uint8_t *)item) + KDBUS_ALIGN8((item)->size))
+#define KDBUS_FOREACH(iter, first, _size)                              \
+       for (iter = (first);                                            \
+            ((uint8_t *)(iter) < (uint8_t *)(first) + (_size)) &&      \
+              ((uint8_t *)(iter) >= (uint8_t *)(first));               \
+            iter = (void*)(((uint8_t *)iter) + KDBUS_ALIGN8((iter)->size)))
+
+static inline int kdbus_cmd_bus_make(int control_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(control_fd, KDBUS_CMD_BUS_MAKE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_endpoint_make(int bus_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(bus_fd, KDBUS_CMD_ENDPOINT_MAKE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_endpoint_update(int ep_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(ep_fd, KDBUS_CMD_ENDPOINT_UPDATE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_hello(int bus_fd, struct kdbus_cmd_hello *cmd)
+{
+       int ret = ioctl(bus_fd, KDBUS_CMD_HELLO, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_update(int fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(fd, KDBUS_CMD_UPDATE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_byebye(int conn_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_BYEBYE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_free(int conn_fd, struct kdbus_cmd_free *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_FREE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_conn_info(int conn_fd, struct kdbus_cmd_info *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_CONN_INFO, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_bus_creator_info(int conn_fd, struct kdbus_cmd_info *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_BUS_CREATOR_INFO, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_list(int fd, struct kdbus_cmd_list *cmd)
+{
+       int ret = ioctl(fd, KDBUS_CMD_LIST, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_send(int conn_fd, struct kdbus_cmd_send *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_SEND, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_recv(int conn_fd, struct kdbus_cmd_recv *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_RECV, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_name_acquire(int conn_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_NAME_ACQUIRE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_name_release(int conn_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_NAME_RELEASE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_match_add(int conn_fd, struct kdbus_cmd_match *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_MATCH_ADD, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline int kdbus_cmd_match_remove(int conn_fd, struct kdbus_cmd_match *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_MATCH_REMOVE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+#endif /* KDBUS_API_H */
diff --git a/samples/kdbus-workers.c b/samples/kdbus-workers.c
new file mode 100644 (file)
index 0000000..d331e01
--- /dev/null
@@ -0,0 +1,1326 @@
+/*
+ * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * 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.
+ */
+
+/*
+ * Example: Workers
+ * This program computes prime-numbers based on the sieve of Eratosthenes. The
+ * master sets up a shared memory region and spawns workers which clear out the
+ * non-primes. The master reacts to keyboard input and to client-requests to
+ * control what each worker does. Note that this is in no way meant as efficient
+ * way to compute primes. It should only serve as example how a master/worker
+ * concept can be implemented with kdbus used as control messages.
+ *
+ * The main process is called the 'master'. It creates a new, private bus which
+ * will be used between the master and its workers to communicate. The master
+ * then spawns a fixed number of workers. Whenever a worker dies (detected via
+ * SIGCHLD), the master spawns a new worker. When done, the master waits for all
+ * workers to exit, prints a status report and exits itself.
+ *
+ * The master process does *not* keep track of its workers. Instead, this
+ * example implements a PULL model. That is, the master acquires a well-known
+ * name on the bus which each worker uses to request tasks from the master. If
+ * there are no more tasks, the master will return an empty task-list, which
+ * casues a worker to exit immediately.
+ *
+ * As tasks can be computationally expensive, we support cancellation. Whenever
+ * the master process is interrupted, it will drop its well-known name on the
+ * bus. This causes kdbus to broadcast a name-change notification. The workers
+ * check for broadcast messages regularly and will exit if they receive one.
+ *
+ * This example exists of 4 objects:
+ *  * master: The master object contains the context of the master process. This
+ *            process manages the prime-context, spawns workers and assigns
+ *            prime-ranges to each worker to compute.
+ *            The master itself does not do any prime-computations itself.
+ *  * child:  The child object contains the context of a worker. It inherits the
+ *            prime context from its parent (the master) and then creates a new
+ *            bus context to request prime-ranges to compute.
+ *  * prime:  The "prime" object is used to abstract how we compute primes. When
+ *            allocated, it prepares a memory region to hold 1 bit for each
+ *            natural number up to a fixed maximum ('MAX_PRIMES').
+ *            The memory region is backed by a memfd which we share between
+ *            processes. Each worker now gets assigned a range of natural
+ *            numbers which it clears multiples of off the memory region. The
+ *            master process is responsible of distributing all natural numbers
+ *            up to the fixed maximum to its workers.
+ *  * bus:    The bus object is an abstraction of the kdbus API. It is pretty
+ *            straightfoward and only manages the connection-fd plus the
+ *            memory-mapped pool in a single object.
+ *
+ * This example is in reversed order, which should make it easier to read
+ * top-down, but requires some forward-declarations. Just ignore those.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/memfd.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/poll.h>
+#include <sys/signalfd.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+#include "kdbus-api.h"
+
+/* FORWARD DECLARATIONS */
+
+#define POOL_SIZE (16 * 1024 * 1024)
+#define MAX_PRIMES (2UL << 24)
+#define WORKER_COUNT (16)
+#define PRIME_STEPS (65536 * 4)
+
+static const char *arg_busname = "example-workers";
+static const char *arg_modname = "kdbus";
+static const char *arg_master = "org.freedesktop.master";
+
+static int err_assert(int r_errno, const char *msg, const char *func, int line,
+                     const char *file)
+{
+       r_errno = (r_errno != 0) ? -abs(r_errno) : -EFAULT;
+       if (r_errno < 0) {
+               errno = -r_errno;
+               fprintf(stderr, "ERR: %s: %m (%s:%d in %s)\n",
+                       msg, func, line, file);
+       }
+       return r_errno;
+}
+
+#define err_r(_r, _msg) err_assert((_r), (_msg), __func__, __LINE__, __FILE__)
+#define err(_msg) err_r(errno, (_msg))
+
+struct prime;
+struct bus;
+struct master;
+struct child;
+
+struct prime {
+       int fd;
+       uint8_t *area;
+       size_t max;
+       size_t done;
+       size_t status;
+};
+
+static int prime_new(struct prime **out);
+static void prime_free(struct prime *p);
+static bool prime_done(struct prime *p);
+static void prime_consume(struct prime *p, size_t amount);
+static int prime_run(struct prime *p, struct bus *cancel, size_t number);
+static void prime_print(struct prime *p);
+
+struct bus {
+       int fd;
+       uint8_t *pool;
+};
+
+static int bus_open_connection(struct bus **out, uid_t uid, const char *name,
+                              uint64_t recv_flags);
+static void bus_close_connection(struct bus *b);
+static void bus_poool_free_slice(struct bus *b, uint64_t offset);
+static int bus_acquire_name(struct bus *b, const char *name);
+static int bus_install_name_loss_match(struct bus *b, const char *name);
+static int bus_poll(struct bus *b);
+static int bus_make(uid_t uid, const char *name);
+
+struct master {
+       size_t n_workers;
+       size_t max_workers;
+
+       int signal_fd;
+       int control_fd;
+
+       struct prime *prime;
+       struct bus *bus;
+};
+
+static int master_new(struct master **out);
+static void master_free(struct master *m);
+static int master_run(struct master *m);
+static int master_poll(struct master *m);
+static int master_handle_stdin(struct master *m);
+static int master_handle_signal(struct master *m);
+static int master_handle_bus(struct master *m);
+static int master_reply(struct master *m, const struct kdbus_msg *msg);
+static int master_waitpid(struct master *m);
+static int master_spawn(struct master *m);
+
+struct child {
+       struct bus *bus;
+       struct prime *prime;
+};
+
+static int child_new(struct child **out, struct prime *p);
+static void child_free(struct child *c);
+static int child_run(struct child *c);
+
+/* END OF FORWARD DECLARATIONS */
+
+/*
+ * This is the main entrypoint of this example. It is pretty straightforward. We
+ * create a master object, run the computation, print a status report and then
+ * exit. Nothing particularly interesting here, so lets look into the master
+ * object...
+ */
+int main(int argc, char **argv)
+{
+       struct master *m = NULL;
+       int r;
+
+       r = master_new(&m);
+       if (r < 0)
+               goto out;
+
+       r = master_run(m);
+       if (r < 0)
+               goto out;
+
+       if (0)
+               prime_print(m->prime);
+
+out:
+       master_free(m);
+       if (r < 0 && r != -EINTR)
+               fprintf(stderr, "failed\n");
+       else
+               fprintf(stderr, "done\n");
+       return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+/*
+ * ...this will allocate a new master context. It keeps track of the current
+ * number of children/workers that are running, manages a signalfd to track
+ * SIGCHLD, and creates a private kdbus bus. Afterwards, it opens its connection
+ * to the bus and acquires a well known-name (arg_master).
+ */
+static int master_new(struct master **out)
+{
+       struct master *m;
+       sigset_t smask;
+       int r;
+
+       m = calloc(1, sizeof(*m));
+       if (!m)
+               return err("cannot allocate master");
+
+       m->max_workers = WORKER_COUNT;
+       m->signal_fd = -1;
+       m->control_fd = -1;
+
+       /* Block SIGINT and SIGCHLD signals */
+       sigemptyset(&smask);
+       sigaddset(&smask, SIGINT);
+       sigaddset(&smask, SIGCHLD);
+       sigprocmask(SIG_BLOCK, &smask, NULL);
+
+       m->signal_fd = signalfd(-1, &smask, SFD_CLOEXEC);
+       if (m->signal_fd < 0) {
+               r = err("cannot create signalfd");
+               goto error;
+       }
+
+       r = prime_new(&m->prime);
+       if (r < 0)
+               goto error;
+
+       m->control_fd = bus_make(getuid(), arg_busname);
+       if (m->control_fd < 0) {
+               r = m->control_fd;
+               goto error;
+       }
+
+       /*
+        * Open a bus connection for the master, and require each received
+        * message to have a metadata item of type KDBUS_ITEM_PIDS attached.
+        * The current UID is needed to compute the name of the bus node to
+        * connect to.
+        */
+       r = bus_open_connection(&m->bus, getuid(),
+                               arg_busname, KDBUS_ATTACH_PIDS);
+       if (r < 0)
+               goto error;
+
+       /*
+        * Acquire a well-known name on the bus, so children can address
+        * messages to the master using KDBUS_DST_ID_NAME as destination-ID
+        * of messages.
+        */
+       r = bus_acquire_name(m->bus, arg_master);
+       if (r < 0)
+               goto error;
+
+       *out = m;
+       return 0;
+
+error:
+       master_free(m);
+       return r;
+}
+
+/* pretty straightforward destructor of a master object */
+static void master_free(struct master *m)
+{
+       if (!m)
+               return;
+
+       bus_close_connection(m->bus);
+       if (m->control_fd >= 0)
+               close(m->control_fd);
+       prime_free(m->prime);
+       if (m->signal_fd >= 0)
+               close(m->signal_fd);
+       free(m);
+}
+
+static int master_run(struct master *m)
+{
+       int res, r = 0;
+
+       while (!prime_done(m->prime)) {
+               while (m->n_workers < m->max_workers) {
+                       r = master_spawn(m);
+                       if (r < 0)
+                               break;
+               }
+
+               r = master_poll(m);
+               if (r < 0)
+                       break;
+       }
+
+       if (r < 0) {
+               bus_close_connection(m->bus);
+               m->bus = NULL;
+       }
+
+       while (m->n_workers > 0) {
+               res = master_poll(m);
+               if (res < 0) {
+                       if (m->bus) {
+                               bus_close_connection(m->bus);
+                               m->bus = NULL;
+                       }
+                       r = res;
+               }
+       }
+
+       return r == -EINTR ? 0 : r;
+}
+
+static int master_poll(struct master *m)
+{
+       struct pollfd fds[3] = {};
+       int r = 0, n = 0;
+
+       /*
+        * Add stdin, the eventfd and the connection owner file descriptor to
+        * the pollfd table, and handle incoming traffic on the latter in
+        * master_handle_bus().
+        */
+       fds[n].fd = STDIN_FILENO;
+       fds[n++].events = POLLIN;
+       fds[n].fd = m->signal_fd;
+       fds[n++].events = POLLIN;
+       if (m->bus) {
+               fds[n].fd = m->bus->fd;
+               fds[n++].events = POLLIN;
+       }
+
+       r = poll(fds, n, -1);
+       if (r < 0)
+               return err("poll() failed");
+
+       if (fds[0].revents & POLLIN)
+               r = master_handle_stdin(m);
+       else if (fds[0].revents)
+               r = err("ERR/HUP on stdin");
+       if (r < 0)
+               return r;
+
+       if (fds[1].revents & POLLIN)
+               r = master_handle_signal(m);
+       else if (fds[1].revents)
+               r = err("ERR/HUP on signalfd");
+       if (r < 0)
+               return r;
+
+       if (fds[2].revents & POLLIN)
+               r = master_handle_bus(m);
+       else if (fds[2].revents)
+               r = err("ERR/HUP on bus");
+
+       return r;
+}
+
+static int master_handle_stdin(struct master *m)
+{
+       char buf[128];
+       ssize_t l;
+       int r = 0;
+
+       l = read(STDIN_FILENO, buf, sizeof(buf));
+       if (l < 0)
+               return err("cannot read stdin");
+       if (l == 0)
+               return err_r(-EINVAL, "EOF on stdin");
+
+       while (l-- > 0) {
+               switch (buf[l]) {
+               case 'q':
+                       /* quit */
+                       r = -EINTR;
+                       break;
+               case '\n':
+               case ' ':
+                       /* ignore */
+                       break;
+               default:
+                       if (isgraph(buf[l]))
+                               fprintf(stderr, "invalid input '%c'\n", buf[l]);
+                       else
+                               fprintf(stderr, "invalid input 0x%x\n", buf[l]);
+                       break;
+               }
+       }
+
+       return r;
+}
+
+static int master_handle_signal(struct master *m)
+{
+       struct signalfd_siginfo val;
+       ssize_t l;
+
+       l = read(m->signal_fd, &val, sizeof(val));
+       if (l < 0)
+               return err("cannot read signalfd");
+       if (l != sizeof(val))
+               return err_r(-EINVAL, "invalid data from signalfd");
+
+       switch (val.ssi_signo) {
+       case SIGCHLD:
+               return master_waitpid(m);
+       case SIGINT:
+               return err_r(-EINTR, "interrupted");
+       default:
+               return err_r(-EINVAL, "caught invalid signal");
+       }
+}
+
+static int master_handle_bus(struct master *m)
+{
+       struct kdbus_cmd_recv recv = { .size = sizeof(recv) };
+       const struct kdbus_msg *msg = NULL;
+       const struct kdbus_item *item;
+       const struct kdbus_vec *vec = NULL;
+       int r = 0;
+
+       /*
+        * To receive a message, the KDBUS_CMD_RECV ioctl is used.
+        * It takes an argument of type 'struct kdbus_cmd_recv', which
+        * will contain information on the received message when the call
+        * returns. See kdbus.message(7).
+        */
+       r = kdbus_cmd_recv(m->bus->fd, &recv);
+       /*
+        * EAGAIN is returned when there is no message waiting on this
+        * connection. This is not an error - simply bail out.
+        */
+       if (r == -EAGAIN)
+               return 0;
+       if (r < 0)
+               return err_r(r, "cannot receive message");
+
+       /*
+        * Messages received by a connection are stored inside the connection's
+        * pool, at an offset that has been returned in the 'recv' command
+        * struct above. The value describes the relative offset from the
+        * start address of the pool. A message is described with
+        * 'struct kdbus_msg'. See kdbus.message(7).
+        */
+       msg = (void *)(m->bus->pool + recv.msg.offset);
+
+       /*
+        * A messages describes its actual payload in an array of items.
+        * KDBUS_FOREACH() is a simple iterator that walks such an array.
+        * struct kdbus_msg has a field to denote its total size, which is
+        * needed to determine the number of items in the array.
+        */
+       KDBUS_FOREACH(item, msg->items,
+                     msg->size - offsetof(struct kdbus_msg, items)) {
+               /*
+                * An item of type PAYLOAD_OFF describes in-line memory
+                * stored in the pool at a described offset. That offset is
+                * relative to the start address of the message header.
+                * This example program only expects one single item of that
+                * type, remembers the struct kdbus_vec member of the item
+                * when it sees it, and bails out if there is more than one
+                * of them.
+                */
+               if (item->type == KDBUS_ITEM_PAYLOAD_OFF) {
+                       if (vec) {
+                               r = err_r(-EEXIST,
+                                         "message with multiple vecs");
+                               break;
+                       }
+                       vec = &item->vec;
+                       if (vec->size != 1) {
+                               r = err_r(-EINVAL, "invalid message size");
+                               break;
+                       }
+
+               /*
+                * MEMFDs are transported as items of type PAYLOAD_MEMFD.
+                * If such an item is attached, a new file descriptor was
+                * installed into the task when KDBUS_CMD_RECV was called, and
+                * its number is stored in item->memfd.fd.
+                * Implementers *must* handle this item type and close the
+                * file descriptor when no longer needed in order to prevent
+                * file descriptor exhaustion. This example program just bails
+                * out with an error in this case, as memfds are not expected
+                * in this context.
+                */
+               } else if (item->type == KDBUS_ITEM_PAYLOAD_MEMFD) {
+                       r = err_r(-EINVAL, "message with memfd");
+                       break;
+               }
+       }
+       if (r < 0)
+               goto exit;
+       if (!vec) {
+               r = err_r(-EINVAL, "empty message");
+               goto exit;
+       }
+
+       switch (*((const uint8_t *)msg + vec->offset)) {
+       case 'r': {
+               r = master_reply(m, msg);
+               break;
+       }
+       default:
+               r = err_r(-EINVAL, "invalid message type");
+               break;
+       }
+
+exit:
+       /*
+        * We are done with the memory slice that was given to us through
+        * recv.msg.offset. Tell the kernel it can use it for other content
+        * in the future. See kdbus.pool(7).
+        */
+       bus_poool_free_slice(m->bus, recv.msg.offset);
+       return r;
+}
+
+static int master_reply(struct master *m, const struct kdbus_msg *msg)
+{
+       struct kdbus_cmd_send cmd;
+       struct kdbus_item *item;
+       struct kdbus_msg *reply;
+       size_t size, status, p[2];
+       int r;
+
+       /*
+        * This functions sends a message over kdbus. To do this, it uses the
+        * KDBUS_CMD_SEND ioctl, which takes a command struct argument of type
+        * 'struct kdbus_cmd_send'. This struct stores a pointer to the actual
+        * message to send. See kdbus.message(7).
+        */
+       p[0] = m->prime->done;
+       p[1] = prime_done(m->prime) ? 0 : PRIME_STEPS;
+
+       size = sizeof(*reply);
+       size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
+
+       /* Prepare the message to send */
+       reply = alloca(size);
+       memset(reply, 0, size);
+       reply->size = size;
+
+       /* Each message has a cookie that can be used to send replies */
+       reply->cookie = 1;
+
+       /* The payload_type is arbitrary, but it must be non-zero */
+       reply->payload_type = 0xdeadbeef;
+
+       /*
+        * We are sending a reply. Let the kernel know the cookie of the
+        * message we are replying to.
+        */
+       reply->cookie_reply = msg->cookie;
+
+       /*
+        * Messages can either be directed to a well-known name (stored as
+        * string) or to a unique name (stored as number). This example does
+        * the latter. If the message would be directed to a well-known name
+        * instead, the message's dst_id field would be set to
+        * KDBUS_DST_ID_NAME, and the name would be attaches in an item of type
+        * KDBUS_ITEM_DST_NAME. See below for an example, and also refer to
+        * kdbus.message(7).
+        */
+       reply->dst_id = msg->src_id;
+
+       /* Our message has exactly one item to store its payload */
+       item = reply->items;
+       item->type = KDBUS_ITEM_PAYLOAD_VEC;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec);
+       item->vec.address = (uintptr_t)p;
+       item->vec.size = sizeof(p);
+
+       /*
+        * Now prepare the command struct, and reference the message we want
+        * to send.
+        */
+       memset(&cmd, 0, sizeof(cmd));
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)reply;
+
+       /*
+        * Finally, employ the command on the connection owner
+        * file descriptor.
+        */
+       r = kdbus_cmd_send(m->bus->fd, &cmd);
+       if (r < 0)
+               return err_r(r, "cannot send reply");
+
+       if (p[1]) {
+               prime_consume(m->prime, p[1]);
+               status = m->prime->done * 10000 / m->prime->max;
+               if (status != m->prime->status) {
+                       m->prime->status = status;
+                       fprintf(stderr, "status: %7.3lf%%\n",
+                               (double)status / 100);
+               }
+       }
+
+       return 0;
+}
+
+static int master_waitpid(struct master *m)
+{
+       pid_t pid;
+       int r;
+
+       while ((pid = waitpid(-1, &r, WNOHANG)) > 0) {
+               if (m->n_workers > 0)
+                       --m->n_workers;
+               if (!WIFEXITED(r))
+                       r = err_r(-EINVAL, "child died unexpectedly");
+               else if (WEXITSTATUS(r) != 0)
+                       r = err_r(-WEXITSTATUS(r), "child failed");
+       }
+
+       return r;
+}
+
+static int master_spawn(struct master *m)
+{
+       struct child *c = NULL;
+       struct prime *p = NULL;
+       pid_t pid;
+       int r;
+
+       /* Spawn off one child and call child_run() inside it */
+
+       pid = fork();
+       if (pid < 0)
+               return err("cannot fork");
+       if (pid > 0) {
+               /* parent */
+               ++m->n_workers;
+               return 0;
+       }
+
+       /* child */
+
+       p = m->prime;
+       m->prime = NULL;
+       master_free(m);
+
+       r = child_new(&c, p);
+       if (r < 0)
+               goto exit;
+
+       r = child_run(c);
+
+exit:
+       child_free(c);
+       exit(abs(r));
+}
+
+static int child_new(struct child **out, struct prime *p)
+{
+       struct child *c;
+       int r;
+
+       c = calloc(1, sizeof(*c));
+       if (!c)
+               return err("cannot allocate child");
+
+       c->prime = p;
+
+       /*
+        * Open a connection to the bus and require each received message to
+        * carry a list of the well-known names the sendind connection currently
+        * owns. The current UID is needed in order to determine the name of the
+        * bus node to connect to.
+        */
+       r = bus_open_connection(&c->bus, getuid(),
+                               arg_busname, KDBUS_ATTACH_NAMES);
+       if (r < 0)
+               goto error;
+
+       /*
+        * Install a kdbus match so the child's connection gets notified when
+        * the master loses its well-known name.
+        */
+       r = bus_install_name_loss_match(c->bus, arg_master);
+       if (r < 0)
+               goto error;
+
+       *out = c;
+       return 0;
+
+error:
+       child_free(c);
+       return r;
+}
+
+static void child_free(struct child *c)
+{
+       if (!c)
+               return;
+
+       bus_close_connection(c->bus);
+       prime_free(c->prime);
+       free(c);
+}
+
+static int child_run(struct child *c)
+{
+       struct kdbus_cmd_send cmd;
+       struct kdbus_item *item;
+       struct kdbus_vec *vec = NULL;
+       struct kdbus_msg *msg;
+       struct timespec spec;
+       size_t n, steps, size;
+       int r = 0;
+
+       /*
+        * Let's send a message to the master and ask for work. To do this,
+        * we use the KDBUS_CMD_SEND ioctl, which takes an argument of type
+        * 'struct kdbus_cmd_send'. This struct stores a pointer to the actual
+        * message to send. See kdbus.message(7).
+        */
+       size = sizeof(*msg);
+       size += KDBUS_ITEM_SIZE(strlen(arg_master) + 1);
+       size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
+
+       msg = alloca(size);
+       memset(msg, 0, size);
+       msg->size = size;
+
+       /*
+        * Tell the kernel that we expect a reply to this message. This means
+        * that
+        *
+        * a) The remote peer will gain temporary permission to talk to us
+        *    even if it would not be allowed to normally.
+        *
+        * b) A timeout value is required.
+        *
+        *    For asynchronous send commands, if no reply is received, we will
+        *    get a kernel notification with an item of type
+        *    KDBUS_ITEM_REPLY_TIMEOUT attached.
+        *
+        *    For synchronous send commands (which this example does), the
+        *    ioctl will block until a reply is received or the timeout is
+        *    exceeded.
+        */
+       msg->flags = KDBUS_MSG_EXPECT_REPLY;
+
+       /* Set our cookie. Replies must use this cookie to send their reply. */
+       msg->cookie = 1;
+
+       /* The payload_type is arbitrary, but it must be non-zero */
+       msg->payload_type = 0xdeadbeef;
+
+       /*
+        * We are sending our message to the current owner of a well-known
+        * name. This makes an item of type KDBUS_ITEM_DST_NAME mandatory.
+        */
+       msg->dst_id = KDBUS_DST_ID_NAME;
+
+       /*
+        * Set the reply timeout to 5 seconds. Timeouts are always set in
+        * absolute timestamps, based con CLOCK_MONOTONIC. See kdbus.message(7).
+        */
+       clock_gettime(CLOCK_MONOTONIC_COARSE, &spec);
+       msg->timeout_ns += (5 + spec.tv_sec) * 1000ULL * 1000ULL * 1000ULL;
+       msg->timeout_ns += spec.tv_nsec;
+
+       /*
+        * Fill the appended items. First, set the well-known name of the
+        * destination we want to talk to.
+        */
+       item = msg->items;
+       item->type = KDBUS_ITEM_DST_NAME;
+       item->size = KDBUS_ITEM_HEADER_SIZE + strlen(arg_master) + 1;
+       strcpy(item->str, arg_master);
+
+       /*
+        * The 2nd item contains a vector to memory we want to send. It
+        * can be content of any type. In our case, we're sending a one-byte
+        * string only. The memory referenced by this item will be copied into
+        * the pool of the receiver connection, and does not need to be valid
+        * after the command is employed.
+        */
+       item = KDBUS_ITEM_NEXT(item);
+       item->type = KDBUS_ITEM_PAYLOAD_VEC;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec);
+       item->vec.address = (uintptr_t)"r";
+       item->vec.size = 1;
+
+       /* Set up the command struct and reference the message we prepared */
+       memset(&cmd, 0, sizeof(cmd));
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg;
+
+       /*
+        * The send commands knows a mode in which it will block until a
+        * reply to a message is received. This example uses that mode.
+        * The pool offset to the received reply will be stored in the command
+        * struct after the send command returned. See below.
+        */
+       cmd.flags = KDBUS_SEND_SYNC_REPLY;
+
+       /*
+        * Finally, employ the command on the connection owner
+        * file descriptor.
+        */
+       r = kdbus_cmd_send(c->bus->fd, &cmd);
+       if (r == -ESRCH || r == -EPIPE || r == -ECONNRESET)
+               return 0;
+       if (r < 0)
+               return err_r(r, "cannot send request to master");
+
+       /*
+        * The command was sent with the KDBUS_SEND_SYNC_REPLY flag set,
+        * and returned successfully, which means that cmd.reply.offset now
+        * points to a message inside our connection's pool where the reply
+        * is found. This is equivalent to receiving the reply with
+        * KDBUS_CMD_RECV, but it doesn't require waiting for the reply with
+        * poll() and also saves the ioctl to receive the message.
+        */
+       msg = (void *)(c->bus->pool + cmd.reply.offset);
+
+       /*
+        * A messages describes its actual payload in an array of items.
+        * KDBUS_FOREACH() is a simple iterator that walks such an array.
+        * struct kdbus_msg has a field to denote its total size, which is
+        * needed to determine the number of items in the array.
+        */
+       KDBUS_FOREACH(item, msg->items,
+                     msg->size - offsetof(struct kdbus_msg, items)) {
+               /*
+                * An item of type PAYLOAD_OFF describes in-line memory
+                * stored in the pool at a described offset. That offset is
+                * relative to the start address of the message header.
+                * This example program only expects one single item of that
+                * type, remembers the struct kdbus_vec member of the item
+                * when it sees it, and bails out if there is more than one
+                * of them.
+                */
+               if (item->type == KDBUS_ITEM_PAYLOAD_OFF) {
+                       if (vec) {
+                               r = err_r(-EEXIST,
+                                         "message with multiple vecs");
+                               break;
+                       }
+                       vec = &item->vec;
+                       if (vec->size != 2 * sizeof(size_t)) {
+                               r = err_r(-EINVAL, "invalid message size");
+                               break;
+                       }
+               /*
+                * MEMFDs are transported as items of type PAYLOAD_MEMFD.
+                * If such an item is attached, a new file descriptor was
+                * installed into the task when KDBUS_CMD_RECV was called, and
+                * its number is stored in item->memfd.fd.
+                * Implementers *must* handle this item type close the
+                * file descriptor when no longer needed in order to prevent
+                * file descriptor exhaustion. This example program just bails
+                * out with an error in this case, as memfds are not expected
+                * in this context.
+                */
+               } else if (item->type == KDBUS_ITEM_PAYLOAD_MEMFD) {
+                       r = err_r(-EINVAL, "message with memfd");
+                       break;
+               }
+       }
+       if (r < 0)
+               goto exit;
+       if (!vec) {
+               r = err_r(-EINVAL, "empty message");
+               goto exit;
+       }
+
+       n = ((size_t *)((const uint8_t *)msg + vec->offset))[0];
+       steps = ((size_t *)((const uint8_t *)msg + vec->offset))[1];
+
+       while (steps-- > 0) {
+               ++n;
+               r = prime_run(c->prime, c->bus, n);
+               if (r < 0)
+                       break;
+               r = bus_poll(c->bus);
+               if (r != 0) {
+                       r = r < 0 ? r : -EINTR;
+                       break;
+               }
+       }
+
+exit:
+       /*
+        * We are done with the memory slice that was given to us through
+        * cmd.reply.offset. Tell the kernel it can use it for other content
+        * in the future. See kdbus.pool(7).
+        */
+       bus_poool_free_slice(c->bus, cmd.reply.offset);
+       return r;
+}
+
+/*
+ * Prime Computation
+ *
+ */
+
+static int prime_new(struct prime **out)
+{
+       struct prime *p;
+       int r;
+
+       p = calloc(1, sizeof(*p));
+       if (!p)
+               return err("cannot allocate prime memory");
+
+       p->fd = -1;
+       p->area = MAP_FAILED;
+       p->max = MAX_PRIMES;
+
+       /*
+        * Prepare and map a memfd to store the bit-fields for the number
+        * ranges we want to perform the prime detection on.
+        */
+       p->fd = syscall(__NR_memfd_create, "prime-area", MFD_CLOEXEC);
+       if (p->fd < 0) {
+               r = err("cannot create memfd");
+               goto error;
+       }
+
+       r = ftruncate(p->fd, p->max / 8 + 1);
+       if (r < 0) {
+               r = err("cannot ftruncate area");
+               goto error;
+       }
+
+       p->area = mmap(NULL, p->max / 8 + 1, PROT_READ | PROT_WRITE,
+                      MAP_SHARED, p->fd, 0);
+       if (p->area == MAP_FAILED) {
+               r = err("cannot mmap memfd");
+               goto error;
+       }
+
+       *out = p;
+       return 0;
+
+error:
+       prime_free(p);
+       return r;
+}
+
+static void prime_free(struct prime *p)
+{
+       if (!p)
+               return;
+
+       if (p->area != MAP_FAILED)
+               munmap(p->area, p->max / 8 + 1);
+       if (p->fd >= 0)
+               close(p->fd);
+       free(p);
+}
+
+static bool prime_done(struct prime *p)
+{
+       return p->done >= p->max;
+}
+
+static void prime_consume(struct prime *p, size_t amount)
+{
+       p->done += amount;
+}
+
+static int prime_run(struct prime *p, struct bus *cancel, size_t number)
+{
+       size_t i, n = 0;
+       int r;
+
+       if (number < 2 || number > 65535)
+               return 0;
+
+       for (i = number * number;
+            i < p->max && i > number;
+            i += number) {
+               p->area[i / 8] |= 1 << (i % 8);
+
+               if (!(++n % (1 << 20))) {
+                       r = bus_poll(cancel);
+                       if (r != 0)
+                               return r < 0 ? r : -EINTR;
+               }
+       }
+
+       return 0;
+}
+
+static void prime_print(struct prime *p)
+{
+       size_t i, l = 0;
+
+       fprintf(stderr, "PRIMES:");
+       for (i = 0; i < p->max; ++i) {
+               if (!(p->area[i / 8] & (1 << (i % 8))))
+                       fprintf(stderr, "%c%7zu", !(l++ % 16) ? '\n' : ' ', i);
+       }
+       fprintf(stderr, "\nEND\n");
+}
+
+static int bus_open_connection(struct bus **out, uid_t uid, const char *name,
+                              uint64_t recv_flags)
+{
+       struct kdbus_cmd_hello hello;
+       char path[128];
+       struct bus *b;
+       int r;
+
+       /*
+        * The 'bus' object is our representation of a kdbus connection which
+        * stores two details: the connection owner file descriptor, and the
+        * mmap()ed memory of its associated pool. See kdbus.connection(7) and
+        * kdbus.pool(7).
+        */
+       b = calloc(1, sizeof(*b));
+       if (!b)
+               return err("cannot allocate bus memory");
+
+       b->fd = -1;
+       b->pool = MAP_FAILED;
+
+       /* Compute the name of the bus node to connect to. */
+       snprintf(path, sizeof(path), "/sys/fs/%s/%lu-%s/bus",
+                arg_modname, (unsigned long)uid, name);
+       b->fd = open(path, O_RDWR | O_CLOEXEC);
+       if (b->fd < 0) {
+               r = err("cannot open bus");
+               goto error;
+       }
+
+       /*
+        * To make a connection to the bus, the KDBUS_CMD_HELLO ioctl is used.
+        * It takes an argument of type 'struct kdbus_cmd_hello'.
+        */
+       memset(&hello, 0, sizeof(hello));
+       hello.size = sizeof(hello);
+
+       /*
+        * Specify a mask of metadata attach flags, describing metadata items
+        * that this new connection allows to be sent.
+        */
+       hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+
+       /*
+        * Specify a mask of metadata attach flags, describing metadata items
+        * that this new connection wants to be receive along with each message.
+        */
+       hello.attach_flags_recv = recv_flags;
+
+       /*
+        * A connection may choose the size of its pool, but the number has to
+        * comply with two rules: a) it must be greater than 0, and b) it must
+        * be a mulitple of PAGE_SIZE. See kdbus.pool(7).
+        */
+       hello.pool_size = POOL_SIZE;
+
+       /*
+        * Now employ the command on the file descriptor opened above.
+        * This command will turn the file descriptor into a connection-owner
+        * file descriptor that controls the life-time of the connection; once
+        * it's closed, the connection is shut down.
+        */
+       r = kdbus_cmd_hello(b->fd, &hello);
+       if (r < 0) {
+               err_r(r, "HELLO failed");
+               goto error;
+       }
+
+       bus_poool_free_slice(b, hello.offset);
+
+       /*
+        * Map the pool of the connection. Its size has been set in the
+        * command struct above. See kdbus.pool(7).
+        */
+       b->pool = mmap(NULL, POOL_SIZE, PROT_READ, MAP_SHARED, b->fd, 0);
+       if (b->pool == MAP_FAILED) {
+               r = err("cannot mmap pool");
+               goto error;
+       }
+
+       *out = b;
+       return 0;
+
+error:
+       bus_close_connection(b);
+       return r;
+}
+
+static void bus_close_connection(struct bus *b)
+{
+       if (!b)
+               return;
+
+       /*
+        * A bus connection is closed by simply calling close() on the
+        * connection owner file descriptor. The unique name and all owned
+        * well-known names of the conneciton will disappear.
+        * See kdbus.connection(7).
+        */
+       if (b->pool != MAP_FAILED)
+               munmap(b->pool, POOL_SIZE);
+       if (b->fd >= 0)
+               close(b->fd);
+       free(b);
+}
+
+static void bus_poool_free_slice(struct bus *b, uint64_t offset)
+{
+       struct kdbus_cmd_free cmd = {
+               .size = sizeof(cmd),
+               .offset = offset,
+       };
+       int r;
+
+       /*
+        * Once we're done with a piece of pool memory that was returned
+        * by a command, we have to call the KDBUS_CMD_FREE ioctl on it so it
+        * can be reused. The command takes an argument of type
+        * 'struct kdbus_cmd_free', in which the pool offset of the slice to
+        * free is stored. The ioctl is employed on the connection owner
+        * file descriptor. See kdbus.pool(7),
+        */
+       r = kdbus_cmd_free(b->fd, &cmd);
+       if (r < 0)
+               err_r(r, "cannot free pool slice");
+}
+
+static int bus_acquire_name(struct bus *b, const char *name)
+{
+       struct kdbus_item *item;
+       struct kdbus_cmd *cmd;
+       size_t size;
+       int r;
+
+       /*
+        * This function acquires a well-known name on the bus through the
+        * KDBUS_CMD_NAME_ACQUIRE ioctl. This ioctl takes an argument of type
+        * 'struct kdbus_cmd', which is assembled below. See kdbus.name(7).
+        */
+       size = sizeof(*cmd);
+       size += KDBUS_ITEM_SIZE(strlen(name) + 1);
+
+       cmd = alloca(size);
+       memset(cmd, 0, size);
+       cmd->size = size;
+
+       /*
+        * The command requires an item of type KDBUS_ITEM_NAME, and its
+        * content must be a valid bus name.
+        */
+       item = cmd->items;
+       item->type = KDBUS_ITEM_NAME;
+       item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1;
+       strcpy(item->str, name);
+
+       /*
+        * Employ the command on the connection owner file descriptor.
+        */
+       r = kdbus_cmd_name_acquire(b->fd, cmd);
+       if (r < 0)
+               return err_r(r, "cannot acquire name");
+
+       return 0;
+}
+
+static int bus_install_name_loss_match(struct bus *b, const char *name)
+{
+       struct kdbus_cmd_match *match;
+       struct kdbus_item *item;
+       size_t size;
+       int r;
+
+       /*
+        * In order to install a match for signal messages, we have to
+        * assemble a 'struct kdbus_cmd_match' and use it along with the
+        * KDBUS_CMD_MATCH_ADD ioctl. See kdbus.match(7).
+        */
+       size = sizeof(*match);
+       size += KDBUS_ITEM_SIZE(sizeof(item->name_change) + strlen(name) + 1);
+
+       match = alloca(size);
+       memset(match, 0, size);
+       match->size = size;
+
+       /*
+        * A match is comprised of many 'rules', each of which describes a
+        * mandatory detail of the message. All rules of a match must be
+        * satified in order to make a message pass.
+        */
+       item = match->items;
+
+       /*
+        * In this case, we're interested in notifications that inform us
+        * about a well-known name being removed from the bus.
+        */
+       item->type = KDBUS_ITEM_NAME_REMOVE;
+       item->size = KDBUS_ITEM_HEADER_SIZE +
+                       sizeof(item->name_change) + strlen(name) + 1;
+
+       /*
+        * We could limit the match further and require a specific unique-ID
+        * to be the new or the old owner of the name. In this case, however,
+        * we don't, and allow 'any' id.
+        */
+       item->name_change.old_id.id = KDBUS_MATCH_ID_ANY;
+       item->name_change.new_id.id = KDBUS_MATCH_ID_ANY;
+
+       /* Copy in the well-known name we're interested in */
+       strcpy(item->name_change.name, name);
+
+       /*
+        * Add the match through the KDBUS_CMD_MATCH_ADD ioctl, employed on
+        * the connection owner fd.
+        */
+       r = kdbus_cmd_match_add(b->fd, match);
+       if (r < 0)
+               return err_r(r, "cannot add match");
+
+       return 0;
+}
+
+static int bus_poll(struct bus *b)
+{
+       struct pollfd fds[1] = {};
+       int r;
+
+       /*
+        * A connection endpoint supports poll() and will wake-up the
+        * task with POLLIN set once a message has arrived.
+        */
+       fds[0].fd = b->fd;
+       fds[0].events = POLLIN;
+       r = poll(fds, sizeof(fds) / sizeof(*fds), 0);
+       if (r < 0)
+               return err("cannot poll bus");
+       return !!(fds[0].revents & POLLIN);
+}
+
+static int bus_make(uid_t uid, const char *name)
+{
+       struct kdbus_item *item;
+       struct kdbus_cmd *make;
+       char path[128], busname[128];
+       size_t size;
+       int r, fd;
+
+       /*
+        * Compute the full path to the 'control' node. 'arg_modname' may be
+        * set to a different value than 'kdbus' for development purposes.
+        * The 'control' node is the primary entry point to kdbus that must be
+        * used in order to create a bus. See kdbus(7) and kdbus.bus(7).
+        */
+       snprintf(path, sizeof(path), "/sys/fs/%s/control", arg_modname);
+
+       /*
+        * Compute the bus name. A valid bus name must always be prefixed with
+        * the EUID of the currently running process in order to avoid name
+        * conflicts. See kdbus.bus(7).
+        */
+       snprintf(busname, sizeof(busname), "%lu-%s", (unsigned long)uid, name);
+
+       fd = open(path, O_RDWR | O_CLOEXEC);
+       if (fd < 0)
+               return err("cannot open control file");
+
+       /*
+        * The KDBUS_CMD_BUS_MAKE ioctl takes an argument of type
+        * 'struct kdbus_cmd', and expects at least two items attached to
+        * it: one to decribe the bloom parameters to be propagated to
+        * connections of the bus, and the name of the bus that was computed
+        * above. Assemble this struct now, and fill it with values.
+        */
+       size = sizeof(*make);
+       size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_parameter));
+       size += KDBUS_ITEM_SIZE(strlen(busname) + 1);
+
+       make = alloca(size);
+       memset(make, 0, size);
+       make->size = size;
+
+       /*
+        * Each item has a 'type' and 'size' field, and must be stored at an
+        * 8-byte aligned address. The KDBUS_ITEM_NEXT macro is used to advance
+        * the pointer. See kdbus.item(7) for more details.
+        */
+       item = make->items;
+       item->type = KDBUS_ITEM_BLOOM_PARAMETER;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(item->bloom_parameter);
+       item->bloom_parameter.size = 8;
+       item->bloom_parameter.n_hash = 1;
+
+       /* The name of the new bus is stored in the next item. */
+       item = KDBUS_ITEM_NEXT(item);
+       item->type = KDBUS_ITEM_MAKE_NAME;
+       item->size = KDBUS_ITEM_HEADER_SIZE + strlen(busname) + 1;
+       strcpy(item->str, busname);
+
+       /*
+        * Now create the bus via the KDBUS_CMD_BUS_MAKE ioctl and return the
+        * fd that was used back to the caller of this function. This fd is now
+        * called a 'bus owner file descriptor', and it controls the life-time
+        * of the newly created bus; once the file descriptor is closed, the
+        * bus goes away, and all connections are shut down. See kdbus.bus(7).
+        */
+       r = kdbus_cmd_bus_make(fd, make);
+       if (r < 0) {
+               err_r(r, "cannot make bus");
+               close(fd);
+               return r;
+       }
+
+       return fd;
+}
diff --git a/tests/kdbus/.gitignore b/tests/kdbus/.gitignore
new file mode 100644 (file)
index 0000000..d3ef42f
--- /dev/null
@@ -0,0 +1 @@
+kdbus-test
diff --git a/tests/kdbus/Makefile b/tests/kdbus/Makefile
new file mode 100644 (file)
index 0000000..f60619c
--- /dev/null
@@ -0,0 +1,45 @@
+CFLAGS += -I../../../../usr/include/
+CFLAGS += -I../../../../samples/kdbus/
+CFLAGS += -std=gnu99
+CFLAGS += -DKBUILD_MODNAME=\"kdbus\" -D_GNU_SOURCE
+LDFLAGS = -pthread -lcap -lm
+
+.PHONY: all clean
+
+include ../lib.mk
+
+TEST_CUSTOM_PROGS := $(OUTPUT)/kdbus-test
+all: $(TEST_CUSTOM_PROGS)
+
+OBJS = \
+       kdbus-enum.o            \
+       kdbus-util.o            \
+       kdbus-test.o            \
+       test-activator.o        \
+       test-benchmark.o        \
+       test-bus.o              \
+       test-chat.o             \
+       test-connection.o       \
+       test-daemon.o           \
+       test-endpoint.o         \
+       test-fd.o               \
+       test-free.o             \
+       test-match.o            \
+       test-message.o          \
+       test-metadata-ns.o      \
+       test-monitor.o          \
+       test-names.o            \
+       test-policy.o           \
+       test-policy-ns.o        \
+       test-policy-priv.o      \
+       test-sync.o             \
+       test-timeout.o
+OBJS := $(patsubst %,$(OUTPUT)/%,$(OBJS))
+
+$(TEST_CUSTOM_PROGS): $(OBJS)
+       $(CC) -o $(TEST_CUSTOM_PROGS) $(OBJS) $(LDFLAGS)
+
+$(OBJS): $(OUTPUT)/%.o: %.c
+       $(CC) -c $^ -o $@ $(CFLAGS)
+
+EXTRA_CLEAN := $(TEST_CUSTOM_PROGS) $(OBJS)
diff --git a/tests/kdbus/kdbus-api.h b/tests/kdbus/kdbus-api.h
new file mode 100644 (file)
index 0000000..4189a6a
--- /dev/null
@@ -0,0 +1,114 @@
+#ifndef KDBUS_API_H
+#define KDBUS_API_H
+
+#include <sys/ioctl.h>
+#include "kdbus-util.h"
+
+#define KDBUS_ALIGN8(l) (((l) + 7) & ~7)
+#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data)
+#define KDBUS_ITEM_SIZE(s) KDBUS_ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE)
+#define KDBUS_ITEM_NEXT(item) \
+       (typeof(item))((uint8_t *)(item) + KDBUS_ALIGN8((item)->size))
+#define KDBUS_FOREACH(iter, first, _size)                              \
+       for ((iter) = (first);                                          \
+            ((uint8_t *)(iter) < (uint8_t *)(first) + (_size)) &&      \
+              ((uint8_t *)(iter) >= (uint8_t *)(first));               \
+            (iter) = (void *)((uint8_t *)(iter) + KDBUS_ALIGN8((iter)->size)))
+
+static inline wur int kdbus_cmd_bus_make(int control_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(control_fd, KDBUS_CMD_BUS_MAKE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_endpoint_make(int bus_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(bus_fd, KDBUS_CMD_ENDPOINT_MAKE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_endpoint_update(int ep_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(ep_fd, KDBUS_CMD_ENDPOINT_UPDATE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_hello(int bus_fd, struct kdbus_cmd_hello *cmd)
+{
+       int ret = ioctl(bus_fd, KDBUS_CMD_HELLO, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_update(int fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(fd, KDBUS_CMD_UPDATE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_byebye(int conn_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_BYEBYE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_free(int conn_fd, struct kdbus_cmd_free *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_FREE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_conn_info(int conn_fd, struct kdbus_cmd_info *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_CONN_INFO, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_bus_creator_info(int conn_fd, struct kdbus_cmd_info *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_BUS_CREATOR_INFO, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_list(int fd, struct kdbus_cmd_list *cmd)
+{
+       int ret = ioctl(fd, KDBUS_CMD_LIST, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_send(int conn_fd, struct kdbus_cmd_send *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_SEND, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_recv(int conn_fd, struct kdbus_cmd_recv *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_RECV, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_name_acquire(int conn_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_NAME_ACQUIRE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_name_release(int conn_fd, struct kdbus_cmd *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_NAME_RELEASE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_match_add(int conn_fd, struct kdbus_cmd_match *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_MATCH_ADD, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+static inline wur int kdbus_cmd_match_remove(int conn_fd, struct kdbus_cmd_match *cmd)
+{
+       int ret = ioctl(conn_fd, KDBUS_CMD_MATCH_REMOVE, cmd);
+       return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0;
+}
+
+#endif /* KDBUS_API_H */
diff --git a/tests/kdbus/kdbus-enum.c b/tests/kdbus/kdbus-enum.c
new file mode 100644 (file)
index 0000000..4b642c6
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013-2015 Kay Sievers
+ *
+ * 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 <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+struct kdbus_enum_table {
+       long long id;
+       const char *name;
+};
+
+#define TABLE(what) static struct kdbus_enum_table kdbus_table_##what[]
+#define ENUM(_id) { .id = _id, .name = STRINGIFY(_id) }
+#define LOOKUP(what)                                                   \
+       const char *enum_##what(long long id)                           \
+       {                                                               \
+               for (size_t i = 0; i < ELEMENTSOF(kdbus_table_##what); i++) \
+                       if (id == kdbus_table_##what[i].id)             \
+                               return kdbus_table_##what[i].name;      \
+               return "UNKNOWN";                                       \
+       }
+
+TABLE(CMD) = {
+       ENUM(KDBUS_CMD_BUS_MAKE),
+       ENUM(KDBUS_CMD_ENDPOINT_MAKE),
+       ENUM(KDBUS_CMD_HELLO),
+       ENUM(KDBUS_CMD_SEND),
+       ENUM(KDBUS_CMD_RECV),
+       ENUM(KDBUS_CMD_LIST),
+       ENUM(KDBUS_CMD_NAME_RELEASE),
+       ENUM(KDBUS_CMD_CONN_INFO),
+       ENUM(KDBUS_CMD_MATCH_ADD),
+       ENUM(KDBUS_CMD_MATCH_REMOVE),
+};
+LOOKUP(CMD);
+
+TABLE(MSG) = {
+       ENUM(_KDBUS_ITEM_NULL),
+       ENUM(KDBUS_ITEM_PAYLOAD_VEC),
+       ENUM(KDBUS_ITEM_PAYLOAD_OFF),
+       ENUM(KDBUS_ITEM_PAYLOAD_MEMFD),
+       ENUM(KDBUS_ITEM_FDS),
+       ENUM(KDBUS_ITEM_BLOOM_PARAMETER),
+       ENUM(KDBUS_ITEM_BLOOM_FILTER),
+       ENUM(KDBUS_ITEM_DST_NAME),
+       ENUM(KDBUS_ITEM_MAKE_NAME),
+       ENUM(KDBUS_ITEM_ATTACH_FLAGS_SEND),
+       ENUM(KDBUS_ITEM_ATTACH_FLAGS_RECV),
+       ENUM(KDBUS_ITEM_ID),
+       ENUM(KDBUS_ITEM_NAME),
+       ENUM(KDBUS_ITEM_TIMESTAMP),
+       ENUM(KDBUS_ITEM_CREDS),
+       ENUM(KDBUS_ITEM_PIDS),
+       ENUM(KDBUS_ITEM_AUXGROUPS),
+       ENUM(KDBUS_ITEM_OWNED_NAME),
+       ENUM(KDBUS_ITEM_TID_COMM),
+       ENUM(KDBUS_ITEM_PID_COMM),
+       ENUM(KDBUS_ITEM_EXE),
+       ENUM(KDBUS_ITEM_CMDLINE),
+       ENUM(KDBUS_ITEM_CGROUP),
+       ENUM(KDBUS_ITEM_CAPS),
+       ENUM(KDBUS_ITEM_SECLABEL),
+       ENUM(KDBUS_ITEM_AUDIT),
+       ENUM(KDBUS_ITEM_CONN_DESCRIPTION),
+       ENUM(KDBUS_ITEM_NAME_ADD),
+       ENUM(KDBUS_ITEM_NAME_REMOVE),
+       ENUM(KDBUS_ITEM_NAME_CHANGE),
+       ENUM(KDBUS_ITEM_ID_ADD),
+       ENUM(KDBUS_ITEM_ID_REMOVE),
+       ENUM(KDBUS_ITEM_REPLY_TIMEOUT),
+       ENUM(KDBUS_ITEM_REPLY_DEAD),
+};
+LOOKUP(MSG);
+
+TABLE(PAYLOAD) = {
+       ENUM(KDBUS_PAYLOAD_KERNEL),
+       ENUM(KDBUS_PAYLOAD_DBUS),
+};
+LOOKUP(PAYLOAD);
diff --git a/tests/kdbus/kdbus-enum.h b/tests/kdbus/kdbus-enum.h
new file mode 100644 (file)
index 0000000..ed28cca
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2013-2015 Kay Sievers
+ *
+ * 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.
+ */
+
+#pragma once
+
+const char *enum_CMD(long long id);
+const char *enum_MSG(long long id);
+const char *enum_MATCH(long long id);
+const char *enum_PAYLOAD(long long id);
diff --git a/tests/kdbus/kdbus-test.c b/tests/kdbus/kdbus-test.c
new file mode 100644 (file)
index 0000000..f8f2fcc
--- /dev/null
@@ -0,0 +1,1020 @@
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <assert.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <sys/syscall.h>
+#include <sys/eventfd.h>
+#include <linux/sched.h>
+
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+#include "kdbus-test.h"
+
+enum {
+       TEST_CREATE_BUS         = 1 << 0,
+       TEST_CREATE_CONN        = 1 << 1,
+};
+
+struct kdbus_test {
+       const char *name;
+       const char *desc;
+       int (*func)(struct kdbus_test_env *env);
+       unsigned int flags;
+       unsigned timeout;
+};
+
+struct kdbus_test_args {
+       bool mntns;
+       bool pidns;
+       bool userns;
+       bool no_timeout;
+       char *uid_map;
+       char *gid_map;
+       int loop;
+       int wait;
+       int fork;
+       int tap_output;
+       unsigned nTests;
+       char const * const *tests;
+       char *module;
+       char *root;
+       char *busname;
+};
+
+pthread_mutex_t global_print_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static const struct kdbus_test tests[] = {
+       {
+               .name   = "bus-make",
+               .desc   = "bus make functions",
+               .func   = kdbus_test_bus_make,
+               .flags  = 0,
+               .timeout = 10,
+       },
+       {
+               .name   = "hello",
+               .desc   = "the HELLO command",
+               .func   = kdbus_test_hello,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "byebye",
+               .desc   = "the BYEBYE command",
+               .func   = kdbus_test_byebye,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "chat",
+               .desc   = "a chat pattern",
+               .func   = kdbus_test_chat,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "daemon",
+               .desc   = "a simple daemon",
+               .func   = kdbus_test_daemon,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "fd-passing",
+               .desc   = "file descriptor passing",
+               .func   = kdbus_test_fd_passing,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "endpoint",
+               .desc   = "custom endpoint",
+               .func   = kdbus_test_custom_endpoint,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "monitor",
+               .desc   = "monitor functionality",
+               .func   = kdbus_test_monitor,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "name-basics",
+               .desc   = "basic name registry functions",
+               .func   = kdbus_test_name_basic,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "name-conflict",
+               .desc   = "name registry conflict details",
+               .func   = kdbus_test_name_conflict,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "name-queue",
+               .desc   = "queuing of names",
+               .func   = kdbus_test_name_queue,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "name-takeover",
+               .desc   = "takeover of names",
+               .func   = kdbus_test_name_takeover,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "message-basic",
+               .desc   = "basic message handling",
+               .func   = kdbus_test_message_basic,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "message-prio",
+               .desc   = "handling of messages with priority",
+               .func   = kdbus_test_message_prio,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "activator-quota",
+               .desc   = "activator message quotas are enforced",
+               .func   = kdbus_test_activator_quota,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 20,
+       },
+       {
+               .name   = "message-quota",
+               .desc   = "message quotas are enforced",
+               .func   = kdbus_test_message_quota,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 20,
+       },
+       {
+               .name   = "memory-access",
+               .desc   = "memory access",
+               .func   = kdbus_test_memory_access,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "timeout",
+               .desc   = "timeout",
+               .func   = kdbus_test_timeout,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "sync-byebye",
+               .desc   = "synchronous replies vs. BYEBYE",
+               .func   = kdbus_test_sync_byebye,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "sync-reply",
+               .desc   = "synchronous replies",
+               .func   = kdbus_test_sync_reply,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "big-meta",
+               .desc   = "big metadata",
+               .func   = kdbus_test_big_metadata,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "message-free",
+               .desc   = "freeing of memory",
+               .func   = kdbus_test_free,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "connection-info",
+               .desc   = "retrieving connection information",
+               .func   = kdbus_test_conn_info,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "connection-update",
+               .desc   = "updating connection information",
+               .func   = kdbus_test_conn_update,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "writable-pool",
+               .desc   = "verifying pools are never writable",
+               .func   = kdbus_test_writable_pool,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "policy",
+               .desc   = "policy",
+               .func   = kdbus_test_policy,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "policy-priv",
+               .desc   = "unprivileged bus access",
+               .func   = kdbus_test_policy_priv,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "policy-ns",
+               .desc   = "policy in user namespaces",
+               .func   = kdbus_test_policy_ns,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "metadata",
+               .desc   = "metadata",
+               .func   = kdbus_test_metadata,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "metadata-conn-info",
+               .desc   = "metadata in connection-info",
+               .func   = kdbus_test_metadata_conn_info,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "metadata-ns",
+               .desc   = "metadata in different namespaces",
+               .func   = kdbus_test_metadata_ns,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "match-id-add",
+               .desc   = "adding of matches by id",
+               .func   = kdbus_test_match_id_add,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "match-id-remove",
+               .desc   = "removing of matches by id",
+               .func   = kdbus_test_match_id_remove,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "match-replace",
+               .desc   = "replace of matches with the same cookie",
+               .func   = kdbus_test_match_replace,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "match-name-add",
+               .desc   = "adding of matches by name",
+               .func   = kdbus_test_match_name_add,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "match-name-remove",
+               .desc   = "removing of matches by name",
+               .func   = kdbus_test_match_name_remove,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "match-name-change",
+               .desc   = "matching for name changes",
+               .func   = kdbus_test_match_name_change,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "match-bloom",
+               .desc   = "matching with bloom filters",
+               .func   = kdbus_test_match_bloom,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "match-all",
+               .desc   = "itemless catch-all matching",
+               .func   = kdbus_test_match_itemless,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "activator",
+               .desc   = "activator connections",
+               .func   = kdbus_test_activator,
+               .flags  = TEST_CREATE_BUS | TEST_CREATE_CONN,
+               .timeout = 10,
+       },
+       {
+               .name   = "benchmark",
+               .desc   = "benchmark",
+               .func   = kdbus_test_benchmark,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "benchmark-nomemfds",
+               .desc   = "benchmark without using memfds",
+               .func   = kdbus_test_benchmark_nomemfds,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+       {
+               .name   = "benchmark-uds",
+               .desc   = "benchmark comparison to UDS",
+               .func   = kdbus_test_benchmark_uds,
+               .flags  = TEST_CREATE_BUS,
+               .timeout = 10,
+       },
+};
+
+#define N_TESTS ((int) (sizeof(tests) / sizeof(tests[0])))
+
+static wur int test_prepare_env(const struct kdbus_test *t,
+                           const struct kdbus_test_args *args,
+                           struct kdbus_test_env *env)
+{
+       if (t->flags & TEST_CREATE_BUS) {
+               char *s;
+               char *n = NULL;
+               int ret;
+
+               if (0 >= asprintf(&s, "%s/control", args->root))
+                       fail("asprintf");
+
+               env->control_fd = open(s, O_RDWR);
+               free(s);
+               ASSERT_RETURN(env->control_fd,>=,0);
+
+               if (!args->busname)
+                       ASSERT_NONZERO(n = unique_name("test-bus"));
+
+               ret = kdbus_create_bus(env->control_fd,
+                                      args->busname ?: n,
+                                      _KDBUS_ATTACH_ALL, &s);
+               free(n);
+               ASSERT_ZERO(ret);
+
+               if (0 >= asprintf(&env->buspath, "%s/%s/bus", args->root, s))
+                       fail("asprintf");
+               free(s);
+       }
+
+       if (t->flags & TEST_CREATE_CONN)
+               ASSERT_NONZERO(env->conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       env->root = args->root;
+       env->module = args->module;
+
+       return 0;
+}
+
+static void test_unprepare_env(struct kdbus_test_env *env)
+{
+       if (env->conn) {
+               kdbus_conn_free(env->conn);
+               env->conn = NULL;
+       }
+
+       if (env->control_fd >= 0) {
+               CLOSE(env->control_fd);
+               env->control_fd = -1;
+       }
+
+       if (env->buspath) {
+               free(env->buspath);
+               env->buspath = NULL;
+       }
+}
+
+static wur int test_run(const struct kdbus_test *t,
+                   const struct kdbus_test_args *kdbus_args,
+                   int wait)
+{
+       int ret;
+       struct kdbus_test_env env = {};
+
+       ret = test_prepare_env(t, kdbus_args, &env);
+       if (ret != TEST_OK)
+               return ret;
+
+       if (wait > 0) {
+               print("Sleeping %d seconds before running test ...\n", wait);
+               sleep(wait);
+       }
+
+       ret = t->func(&env);
+       test_unprepare_env(&env);
+       return ret;
+}
+
+static wur int test_run_forked(const struct kdbus_test *t,
+                          const struct kdbus_test_args *kdbus_args,
+                          int _wait)
+{
+       int ret, result;
+       pid_t pid, timer_pid = 0;
+
+       pid = fork();
+       if (pid < 0) {
+               return TEST_ERR;
+       } else if (pid == 0) {
+               ret = test_run(t, kdbus_args, _wait);
+               exit(ret);
+       }
+
+       if (!kdbus_args->no_timeout) {
+               timer_pid = fork();
+               if (timer_pid < 0) {
+                       return TEST_ERR;
+               } else if (timer_pid == 0) {
+                       sleep(t->timeout);
+                       exit(1);
+               }
+       }
+
+       result = -1;
+       for (;;) {
+               pid_t wpid = wait(&ret);
+               if (wpid <= 0)
+                       return TEST_ERR;
+               if (result >= 0)
+                       break;
+               result = !WIFEXITED(ret) || WEXITSTATUS(ret) ? TEST_ERR + (timer_pid==wpid) : TEST_OK;
+               if (kdbus_args->no_timeout)
+                       break;
+               kill(wpid ^ pid ^ timer_pid, SIGKILL);
+       }
+
+       return result;
+}
+
+static void print_test_result(int ret)
+{
+       switch (ret) {
+       case TEST_OK:
+               print("OK");
+               break;
+       case TEST_SKIP:
+               print("SKIPPED");
+               break;
+       case TEST_ERR:
+               print("ERROR");
+               break;
+       }
+}
+
+static void print_res(struct kdbus_test const *t, int ret) {
+       printf("%s;%s\n", t->name, !ret ? "PASS" : TEST_TIME==ret ? "FAIL;TIMEOUT" : "FAIL");
+}
+
+static wur int start_all_tests(struct kdbus_test_args const *kdbus_args)
+{
+       int ret;
+       unsigned int fail_cnt = 0;
+       unsigned int skip_cnt = 0;
+       unsigned int ok_cnt = 0;
+       unsigned int i;
+
+       if (kdbus_args->tap_output) {
+               print("1..%d\n", N_TESTS);
+               fflush(stdout);
+       }
+
+       kdbus_util_verbose = false;
+
+       for (i = 0; i < N_TESTS; i++) {
+               const struct kdbus_test *t = tests + i;
+
+               if (!kdbus_args->tap_output) {
+                       unsigned int n;
+
+                       print("Testing %s (%s) ", t->desc, t->name);
+                       for (n = 0; n < 60 - strlen(t->desc) - strlen(t->name); n++)
+                               print(".");
+                       print(" ");
+               }
+
+               ret = test_run_forked(t, kdbus_args, 0);
+               switch (ret) {
+               case TEST_OK:
+                       ok_cnt++;
+                       break;
+               case TEST_SKIP:
+                       skip_cnt++;
+                       break;
+               case TEST_ERR:
+               case TEST_TIME:
+                       fail_cnt++;
+                       break;
+               }
+
+               if (kdbus_args->tap_output) {
+                       print("%sok %d - %s%s (%s)\n",
+                              (ret >= TEST_ERR) ? "not " : "", i + 1,
+                              (ret == TEST_SKIP) ? "# SKIP " : "",
+                              t->desc, t->name);
+                       fflush(stdout);
+               } else {
+                       print_test_result(ret);
+                       print_res(t, ret);
+                       print("\n");
+               }
+       }
+
+       if (kdbus_args->tap_output)
+               print("Failed %d/%d tests, %.2f%% okay\n", fail_cnt, N_TESTS,
+                      100.0 - (fail_cnt * 100.0) / ((float) N_TESTS));
+       else
+               print("\nSUMMARY: %u tests passed, %u skipped, %u failed\n",
+                      ok_cnt, skip_cnt, fail_cnt);
+
+       return fail_cnt > 0 ? TEST_ERR : TEST_OK;
+}
+
+static wur int start_some_tests(struct kdbus_test_args const *kdbus_args)
+{
+       int i, ret = TEST_ERR;
+       bool test_found = false;
+       unsigned j=0, nTests = kdbus_args->nTests;
+
+       do {
+               char const *tName = kdbus_args->tests[j];
+               for (i = 0; i < N_TESTS; i++) {
+                       const struct kdbus_test *t = tests + i;
+
+                       if (strcmp(t->name, tName))
+                               continue;
+
+                       do {
+                               test_found = true;
+                               if (kdbus_args->fork)
+                                       ret = test_run_forked(t, kdbus_args,
+                                                                 kdbus_args->wait);
+                               else
+                                       ret = test_run(t, kdbus_args,
+                                                          kdbus_args->wait);
+
+                               print("Testing %s: ", t->desc);
+                               print_test_result(ret);
+                               print_res(t, ret);
+                               print("\n");
+
+                               if (ret != TEST_OK)
+                                       break;
+                       } while (kdbus_args->loop);
+               }
+
+               if (!test_found) {
+                       print("%s;UNKNOWN\n", tName);
+               }
+       } while (++j < nTests);
+
+       return ret;
+}
+
+static void usage(const char *argv0)
+{
+       unsigned int i, j;
+
+       print("Usage: %s [options]\n"
+              "Options:\n"
+              "\t-a, --tap             Output test results in TAP format\n"
+              "\t-m, --module <module> Kdbus module name\n"
+              "\t-x, --loop            Run in a loop\n"
+              "\t-f, --fork            Fork before running a test\n"
+              "\t-h, --help            Print this help\n"
+              "\t-R, --root <root>     Toplevel of the kdbus hierarchy\n"
+              "\t-t, --test <test-id>  Run one specific test only, in verbose mode\n"
+              "\t-b, --bus <busname>   Instead of generating a random bus name, take <busname>.\n"
+              "\t-w, --wait <secs>     Wait <secs> before actually starting test\n"
+              "\t    --mntns           New mount namespace\n"
+              "\t    --pidns           New PID namespace\n"
+              "\t    --userns          New user namespace\n"
+              "\t    --uidmap uid_map  UID map for user namespace\n"
+              "\t    --gidmap gid_map  GID map for user namespace\n"
+              "\n", argv0);
+
+       print("By default, all test are run once, and a summary is printed.\n"
+              "Available tests for --test:\n\n");
+
+       for (i = 0; i < N_TESTS; i++) {
+               const struct kdbus_test *t = tests + i;
+
+               print("\t%s", t->name);
+
+               for (j = 0; j < 24 - strlen(t->name); j++)
+                       print(" ");
+
+               print("Test %s\n", t->desc);
+       }
+
+       print("\n");
+       print("Note that some tests may, if run specifically by --test, "
+              "behave differently, and not terminate by themselves.\n");
+
+       exit(EXIT_FAILURE);
+}
+
+void print_kdbus_test_args(struct kdbus_test_args const *args)
+{
+       if (args->userns || args->pidns || args->mntns)
+               print("# Starting tests in new %s%s%s namespaces%s\n",
+                       args->mntns ? "MOUNT " : "",
+                       args->pidns ? "PID " : "",
+                       args->userns ? "USER " : "",
+                       args->mntns ? ", kdbusfs will be remounted" : "");
+       else
+               print("# Starting tests in the same namespaces\n");
+}
+
+void print_metadata_support(void)
+{
+       bool no_meta_audit, no_meta_cgroups, no_meta_seclabel;
+
+       /*
+        * KDBUS_ATTACH_CGROUP, KDBUS_ATTACH_AUDIT and
+        * KDBUS_ATTACH_SECLABEL
+        */
+       no_meta_audit = !config_auditsyscall_is_enabled();
+       no_meta_cgroups = !config_cgroups_is_enabled();
+       no_meta_seclabel = !config_security_is_enabled();
+
+       if (no_meta_audit | no_meta_cgroups | no_meta_seclabel)
+               print("# Starting tests without %s%s%s metadata support\n",
+                      no_meta_audit ? "AUDIT " : "",
+                      no_meta_cgroups ? "CGROUP " : "",
+                      no_meta_seclabel ? "SECLABEL " : "");
+       else
+               print("# Starting tests with full metadata support\n");
+}
+
+wur int run_tests(struct kdbus_test_args const *kdbus_args)
+{
+       int ret;
+       static char control[4096];
+
+       snprintf(control, sizeof(control), "%s/control", kdbus_args->root);
+
+       if (access(control, W_OK) < 0) {
+               print("Unable to locate control node at '%s'.\n",
+                       control);
+               return TEST_ERR;
+       }
+
+       if (kdbus_args->nTests) {
+               ret = start_some_tests(kdbus_args);
+       } else {
+               do {
+                       ret = start_all_tests(kdbus_args);
+                       if (ret != TEST_OK)
+                               break;
+               } while (kdbus_args->loop);
+       }
+
+       return ret;
+}
+
+static void nop_handler(int sig) { UNUSED(sig); }
+
+static wur int test_prepare_mounts(struct kdbus_test_args const *kdbus_args)
+{
+       int ret;
+       char kdbusfs[64] = {'\0'};
+
+       snprintf(kdbusfs, sizeof(kdbusfs), "%sfs", kdbus_args->module);
+
+       /* make current mount slave */
+       ret = mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL);
+       if (ret < 0) {
+               ret = -errno;
+               print("error mount() root: %d (%m)\n", ret);
+               return ret;
+       }
+
+       /* Remount procfs since we need it in our tests */
+       if (kdbus_args->pidns) {
+               ret = mount("proc", "/proc", "proc",
+                           MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL);
+               if (ret < 0) {
+                       ret = -errno;
+                       print("error mount() /proc : %d (%m)\n", ret);
+                       return ret;
+               }
+       }
+
+       /* Remount kdbusfs */
+       ret = mount(kdbusfs, kdbus_args->root, kdbusfs,
+                   MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL);
+       if (ret < 0) {
+               ret = -errno;
+               print("error mount() %s :%d (%m)\n", kdbusfs, ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+wur int run_tests_in_namespaces(struct kdbus_test_args const *kdbus_args)
+{
+       int ret;
+       int efd = -1;
+       int status;
+       pid_t pid, rpid;
+       struct sigaction oldsa;
+       struct sigaction sa = {
+               .sa_handler = nop_handler,
+               .sa_flags = SA_NOCLDSTOP,
+       };
+
+       efd = eventfd(0, EFD_CLOEXEC);
+       if (efd < 0) {
+               ret = -errno;
+               print("eventfd() failed: %d (%m)\n", ret);
+               return TEST_ERR;
+       }
+
+       ret = sigaction(SIGCHLD, &sa, &oldsa);
+       if (ret < 0) {
+               ret = -errno;
+               print("sigaction() failed: %d (%m)\n", ret);
+               return TEST_ERR;
+       }
+
+       /* setup namespaces */
+       pid = syscall(__NR_clone, SIGCHLD|
+                     (kdbus_args->userns ? CLONE_NEWUSER : 0) |
+                     (kdbus_args->mntns ? CLONE_NEWNS : 0) |
+                     (kdbus_args->pidns ? CLONE_NEWPID : 0), NULL);
+       if (pid < 0) {
+               print("clone() failed: %d (%m)\n", -errno);
+               return TEST_ERR;
+       }
+
+       if (pid == 0) {
+               eventfd_t event_status = 0;
+
+               ret = prctl(PR_SET_PDEATHSIG, SIGKILL);
+               if (ret < 0) {
+                       ret = -errno;
+                       print("error prctl(): %d (%m)\n", ret);
+                       exit(TEST_ERR);
+               }
+
+               /* reset sighandlers of childs */
+               ret = sigaction(SIGCHLD, &oldsa, NULL);
+               if (ret < 0) {
+                       ret = -errno;
+                       print("sigaction() failed: %d (%m)\n", ret);
+                       exit(TEST_ERR);
+               }
+
+               ret = eventfd_read(efd, &event_status);
+               if (ret < 0 || event_status != 1) {
+                       print("error eventfd_read()\n");
+                       exit(TEST_ERR);
+               }
+
+               if (kdbus_args->mntns) {
+                       ret = test_prepare_mounts(kdbus_args);
+                       if (ret < 0) {
+                               print("error preparing mounts\n");
+                               exit(TEST_ERR);
+                       }
+               }
+
+               ret = run_tests(kdbus_args);
+               exit(ret);
+       }
+
+       /* Setup userns mapping */
+       if (kdbus_args->userns) {
+               ret = userns_map_uid_gid(pid, kdbus_args->uid_map,
+                                        kdbus_args->gid_map);
+               if (ret < 0) {
+                       print("error mapping uid and gid in userns\n");
+                       eventfd_write(efd, 2);
+                       return TEST_ERR;
+               }
+       }
+
+       ret = eventfd_write(efd, 1);
+       if (ret < 0) {
+               ret = -errno;
+               print("error eventfd_write(): %d (%m)\n", ret);
+               return TEST_ERR;
+       }
+
+       rpid = waitpid(pid, &status, 0);
+       ASSERT_RETURN_VAL(rpid,==,pid, TEST_ERR);
+
+       CLOSE(efd);
+
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+               return TEST_ERR;
+
+       return TEST_OK;
+}
+
+wur int start_tests(struct kdbus_test_args const *kdbus_args)
+{
+       int ret;
+       bool namespaces;
+
+       namespaces = (kdbus_args->mntns || kdbus_args->pidns ||
+                     kdbus_args->userns);
+
+       /* for pidns we need mntns set */
+       if (kdbus_args->pidns && !kdbus_args->mntns) {
+               print("Failed: please set both pid and mnt namesapces\n");
+               return TEST_ERR;
+       }
+
+       if (kdbus_args->userns) {
+               if (!config_user_ns_is_enabled()) {
+                       print("User namespace not supported\n");
+                       return TEST_ERR;
+               }
+
+               if (!kdbus_args->uid_map || !kdbus_args->gid_map) {
+                       print("Failed: please specify uid or gid mapping\n");
+                       return TEST_ERR;
+               }
+       }
+
+       print_kdbus_test_args(kdbus_args);
+       print_metadata_support();
+
+       /* Start tests */
+       if (namespaces)
+               ret = run_tests_in_namespaces(kdbus_args);
+       else
+               ret = run_tests(kdbus_args);
+
+       return ret;
+}
+
+wur int main(int argc, char *argv[])
+{
+       int t, ret = 0;
+       struct kdbus_test_args kdbus_args;
+       static char fspath[4096];
+       enum {
+               ARG_MNTNS = 0x100,
+               ARG_PIDNS,
+               ARG_USERNS,
+               ARG_UIDMAP,
+               ARG_GIDMAP,
+       };
+
+       memset(&kdbus_args, 0, sizeof(kdbus_args));
+       kdbus_args.module = "kdbus";
+
+       static const struct option options[] = {
+               { "loop",       no_argument,            NULL, 'x' },
+               { "help",       no_argument,            NULL, 'h' },
+               { "root",       required_argument,      NULL, 'R' },
+               { "test",       no_argument,            NULL, 't' },
+               { "bus",        required_argument,      NULL, 'b' },
+               { "wait",       required_argument,      NULL, 'w' },
+               { "fork",       no_argument,            NULL, 'f' },
+               { "list",       no_argument,            NULL, 'l' },
+               { "module",     required_argument,      NULL, 'm' },
+               { "tap",        no_argument,            NULL, 'a' },
+               { "notimeout",  no_argument,    NULL, 'n' },
+               { "mntns",      no_argument,            NULL, ARG_MNTNS },
+               { "pidns",      no_argument,            NULL, ARG_PIDNS },
+               { "userns",     no_argument,            NULL, ARG_USERNS },
+               { "uidmap",     required_argument,      NULL, ARG_UIDMAP },
+               { "gidmap",     required_argument,      NULL, ARG_GIDMAP },
+               {}
+       };
+
+       srand(time(NULL));
+
+       bool gotT = 0;
+
+       while ((t = getopt_long(argc, argv, "xhtflnm:R:b:w:a:", options, NULL)) >= 0) {
+               switch (t) {
+               case 'x':
+                       kdbus_args.loop = 1;
+                       break;
+
+               case 'm':
+                       kdbus_args.module = optarg;
+                       break;
+
+               case 'R':
+                       kdbus_args.root = optarg;
+                       break;
+
+               case 't':
+                       gotT = 1;
+                       break;
+
+               case 'b':
+                       kdbus_args.busname = optarg;
+                       break;
+
+               case 'w':
+                       kdbus_args.wait = strtol(optarg, NULL, 10);
+                       break;
+
+               case 'f':
+                       kdbus_args.fork = 1;
+                       break;
+
+               case 'a':
+                       kdbus_args.tap_output = 1;
+                       break;
+
+               case 'n':
+                       kdbus_args.no_timeout = 1;
+                       break;
+
+               case 'l':
+                       {
+                               unsigned i = 0;
+                               do {
+                                       const struct kdbus_test *t = &tests[i];
+                                       printf("%s;%s\n", t->name, t->desc);
+                               } while (++i < TABSIZE(tests)-1);
+                       }
+                       return 0;
+
+               case ARG_MNTNS:
+                       kdbus_args.mntns = true;
+                       break;
+
+               case ARG_PIDNS:
+                       kdbus_args.pidns = true;
+                       break;
+
+               case ARG_USERNS:
+                       kdbus_args.userns = true;
+                       break;
+
+               case ARG_UIDMAP:
+                       kdbus_args.uid_map = optarg;
+                       break;
+
+               case ARG_GIDMAP:
+                       kdbus_args.gid_map = optarg;
+                       break;
+
+               case 'h':
+                       usage(argv[0]);
+
+               default:;
+               }
+       }
+
+       if ((kdbus_args.nTests = argc-optind)) {
+               kdbus_args.tests = (char const * const *)&argv[optind];
+               if (1 < kdbus_args.nTests || !gotT)
+                       kdbus_args.fork = 1;
+       }
+
+       if (!kdbus_args.root) {
+               snprintf(fspath, sizeof(fspath), "/sys/fs/%s",
+                        kdbus_args.module);
+               kdbus_args.root = fspath;
+       }
+
+       ret = start_tests(&kdbus_args);
+       if (ret == TEST_ERR)
+               return EXIT_FAILURE;
+
+       return 0;
+}
diff --git a/tests/kdbus/kdbus-test.h b/tests/kdbus/kdbus-test.h
new file mode 100644 (file)
index 0000000..e0a6abc
--- /dev/null
@@ -0,0 +1,113 @@
+#ifndef _TEST_KDBUS_H_
+#define _TEST_KDBUS_H_
+
+#include <pthread.h>
+#include <sys/syscall.h>
+#include "kdbus-util.h"
+
+struct kdbus_test_env {
+       char *buspath;
+       const char *root;
+       const char *module;
+       int control_fd;
+       struct kdbus_conn *conn;
+};
+
+enum {
+       TEST_OK,
+       TEST_SKIP,
+       TEST_ERR,
+       TEST_TIME,
+};
+
+#if 0
+       #define print_assert_success print
+#else
+       #define print_assert_success(...) do {} while (0)
+#endif
+
+#define PRINTF_FMT(...) _Generic((__VA_ARGS__),\
+       bool: "%d",\
+       int: "%d",\
+       long: "%ld",\
+       long long: "%lld",\
+       unsigned: "%u",\
+       unsigned long: "%lu",\
+       unsigned long long: "%llu",\
+       default: "%p")
+#define PRINTF_ARG(...) (_Generic((__VA_ARGS__),\
+       default: (__VA_ARGS__)))
+
+#define _ASSERT_REL_(val0, val0s, relop, val1, val1s, onfailure) do {\
+       __auto_type const _ASSERT_RETURN_VAL_val0_ = (val0);\
+       __auto_type const _ASSERT_RETURN_VAL_val1_ = (val1);\
+       if (!(_ASSERT_RETURN_VAL_val0_ relop _ASSERT_RETURN_VAL_val1_)) {                       \
+               /* must assemble format string in runtime because _Generic does not play well with string constant concatenation */\
+               char _ASSERT_REL_fmt_[sizeof("[tid %u] Assertion '(")+3+sizeof("=%s) %s (")+3+sizeof("=%s)' failed in %s(), %s:%d\n")];\
+               strcpy(_ASSERT_REL_fmt_, "[tid %u] Assertion '(");\
+               strcat(_ASSERT_REL_fmt_, PRINTF_FMT(_ASSERT_RETURN_VAL_val0_));\
+               strcat(_ASSERT_REL_fmt_, "=%s) %s (");\
+               strcat(_ASSERT_REL_fmt_, PRINTF_FMT(_ASSERT_RETURN_VAL_val1_));\
+               strcat(_ASSERT_REL_fmt_, "=%s)' failed in %s(), %s:%d\n");\
+               print(_ASSERT_REL_fmt_, syscall(SYS_gettid), PRINTF_ARG(_ASSERT_RETURN_VAL_val0_), val0s, #relop, PRINTF_ARG(_ASSERT_RETURN_VAL_val1_), val1s, __func__, __FILE__, __LINE__);\
+               onfailure;\
+       }\
+               else print_assert_success("Assertion '%s' SUCCEEDED in %s(), %s:%d\n", val0s " " #relop " " val1s, __func__, __FILE__, __LINE__);\
+} while (0)
+
+#define ASSERT_RETURN_VAL(val0, relop, val1, retval) _ASSERT_REL_(val0, #val0, relop, val1, #val1, return retval)
+#define ASSERT_EXIT_VAL(val0, relop, val1, retval)   _ASSERT_REL_(val0, #val0, relop, val1, #val1, exit(retval))
+
+#define ASSERT_RETURN(val0, relop, val1) _ASSERT_REL_(val0, #val0, relop, val1, #val1, return TEST_ERR)
+#define ASSERT_EXIT(val0, relop, val1)   _ASSERT_REL_(val0, #val0, relop, val1, #val1, exit(TEST_ERR))
+
+#define ASSERT_ZERO(...)         _ASSERT_REL_(((typeof((__VA_ARGS__)))0), "0", ==, (__VA_ARGS__), #__VA_ARGS__, return TEST_ERR)
+#define ASSERT_NONZERO(...)      _ASSERT_REL_(((typeof((__VA_ARGS__)))0), "0", !=, (__VA_ARGS__), #__VA_ARGS__, return TEST_ERR)
+#define ASSERT_EXIT_ZERO(...)    _ASSERT_REL_(((typeof((__VA_ARGS__)))0), "0", ==, (__VA_ARGS__), #__VA_ARGS__, exit(TEST_ERR))
+#define ASSERT_EXIT_NONZERO(...) _ASSERT_REL_(((typeof((__VA_ARGS__)))0), "0", !=, (__VA_ARGS__), #__VA_ARGS__, exit(TEST_ERR))
+
+wur int kdbus_test_activator(struct kdbus_test_env *env);
+wur int kdbus_test_benchmark(struct kdbus_test_env *env);
+wur int kdbus_test_benchmark_nomemfds(struct kdbus_test_env *env);
+wur int kdbus_test_benchmark_uds(struct kdbus_test_env *env);
+wur int kdbus_test_bus_make(struct kdbus_test_env *env);
+wur int kdbus_test_byebye(struct kdbus_test_env *env);
+wur int kdbus_test_chat(struct kdbus_test_env *env);
+wur int kdbus_test_conn_info(struct kdbus_test_env *env);
+wur int kdbus_test_conn_update(struct kdbus_test_env *env);
+wur int kdbus_test_daemon(struct kdbus_test_env *env);
+wur int kdbus_test_custom_endpoint(struct kdbus_test_env *env);
+wur int kdbus_test_fd_passing(struct kdbus_test_env *env);
+wur int kdbus_test_free(struct kdbus_test_env *env);
+wur int kdbus_test_hello(struct kdbus_test_env *env);
+wur int kdbus_test_match_bloom(struct kdbus_test_env *env);
+wur int kdbus_test_match_itemless(struct kdbus_test_env *env);
+wur int kdbus_test_match_id_add(struct kdbus_test_env *env);
+wur int kdbus_test_match_id_remove(struct kdbus_test_env *env);
+wur int kdbus_test_match_replace(struct kdbus_test_env *env);
+wur int kdbus_test_match_name_add(struct kdbus_test_env *env);
+wur int kdbus_test_match_name_change(struct kdbus_test_env *env);
+wur int kdbus_test_match_name_remove(struct kdbus_test_env *env);
+wur int kdbus_test_message_basic(struct kdbus_test_env *env);
+wur int kdbus_test_message_prio(struct kdbus_test_env *env);
+wur int kdbus_test_activator_quota(struct kdbus_test_env *env);
+wur int kdbus_test_message_quota(struct kdbus_test_env *env);
+wur int kdbus_test_memory_access(struct kdbus_test_env *env);
+wur int kdbus_test_metadata(struct kdbus_test_env *env);
+wur int kdbus_test_metadata_conn_info(struct kdbus_test_env *env);
+wur int kdbus_test_metadata_ns(struct kdbus_test_env *env);
+wur int kdbus_test_monitor(struct kdbus_test_env *env);
+wur int kdbus_test_name_basic(struct kdbus_test_env *env);
+wur int kdbus_test_name_conflict(struct kdbus_test_env *env);
+wur int kdbus_test_name_queue(struct kdbus_test_env *env);
+wur int kdbus_test_name_takeover(struct kdbus_test_env *env);
+wur int kdbus_test_policy(struct kdbus_test_env *env);
+wur int kdbus_test_policy_ns(struct kdbus_test_env *env);
+wur int kdbus_test_policy_priv(struct kdbus_test_env *env);
+wur int kdbus_test_sync_byebye(struct kdbus_test_env *env);
+wur int kdbus_test_sync_reply(struct kdbus_test_env *env);
+wur int kdbus_test_big_metadata(struct kdbus_test_env *env);
+wur int kdbus_test_timeout(struct kdbus_test_env *env);
+wur int kdbus_test_writable_pool(struct kdbus_test_env *env);
+
+#endif /* _TEST_KDBUS_H_ */
diff --git a/tests/kdbus/kdbus-util.c b/tests/kdbus/kdbus-util.c
new file mode 100644 (file)
index 0000000..49aa4b7
--- /dev/null
@@ -0,0 +1,1856 @@
+/*
+ * Copyright (C) 2013-2015 Daniel Mack
+ * Copyright (C) 2013-2015 Kay Sievers
+ * 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 <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <assert.h>
+#include <poll.h>
+#include <grp.h>
+#include <sys/capability.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <linux/unistd.h>
+
+#ifndef __NR_memfd_create
+  #ifdef __x86_64__
+    #define __NR_memfd_create 319
+  #elif defined __arm__
+    #define __NR_memfd_create 385
+  #else
+    #define __NR_memfd_create 356
+  #endif
+#endif
+
+#include "kdbus-api.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+#include "kdbus-test.h"
+
+#ifndef F_ADD_SEALS
+#define F_LINUX_SPECIFIC_BASE  1024
+#define F_ADD_SEALS     (F_LINUX_SPECIFIC_BASE + 9)
+#define F_GET_SEALS     (F_LINUX_SPECIFIC_BASE + 10)
+
+#define F_SEAL_SEAL     0x0001  /* prevent further seals from being set */
+#define F_SEAL_SHRINK   0x0002  /* prevent file from shrinking */
+#define F_SEAL_GROW     0x0004  /* prevent file from growing */
+#define F_SEAL_WRITE    0x0008  /* prevent writes */
+#endif
+
+/* maximum number of well-known names per connection */
+#define KDBUS_CONN_MAX_NAMES                   256
+
+int kdbus_util_verbose = true;
+
+wur int kdbus_sysfs_get_parameter_mask(const char *path, uint64_t *mask)
+{
+       int ret;
+       FILE *file;
+       unsigned long long value;
+
+       file = fopen(path, "r");
+       if (!file) {
+               ret = -errno;
+               kdbus_printf("--- error fopen(): %d (%m)\n", ret);
+               return ret;
+       }
+
+       ret = fscanf(file, "%llu", &value);
+       if (ret != 1) {
+               if (ferror(file))
+                       ret = -errno;
+               else
+                       ret = -EIO;
+
+               kdbus_printf("--- error fscanf(): %d\n", ret);
+               fclose(file);
+               return ret;
+       }
+
+       *mask = (uint64_t)value;
+
+       fclose(file);
+
+       return 0;
+}
+
+wur int kdbus_sysfs_set_parameter_mask(const char *path, uint64_t mask)
+{
+       int ret;
+       FILE *file;
+
+       file = fopen(path, "w");
+       if (!file) {
+               ret = -errno;
+               kdbus_printf("--- error open(): %d (%m)\n", ret);
+               return ret;
+       }
+
+       ret = fprintf(file, "%llu", (unsigned long long)mask);
+       if (ret <= 0) {
+               ret = -EIO;
+               kdbus_printf("--- error fprintf(): %d\n", ret);
+       }
+
+       fclose(file);
+
+       return ret > 0 ? 0 : ret;
+}
+
+wur int kdbus_create_bus(int control_fd, const char *name,
+                    uint64_t owner_meta, char **path)
+{
+       struct {
+               struct kdbus_cmd cmd;
+
+               /* bloom size item */
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       struct kdbus_bloom_parameter bloom;
+               } bp;
+
+               /* owner metadata items */
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       uint64_t flags;
+               } attach;
+
+               /* name item */
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       char str[64];
+               } name;
+       } bus_make;
+       int ret;
+
+       memset(&bus_make, 0, sizeof(bus_make));
+       bus_make.bp.size = sizeof(bus_make.bp);
+       bus_make.bp.type = KDBUS_ITEM_BLOOM_PARAMETER;
+       bus_make.bp.bloom.size = 64;
+       bus_make.bp.bloom.n_hash = 1;
+
+       snprintf(bus_make.name.str, sizeof(bus_make.name.str),
+                "%u-%s", getuid(), name);
+
+       bus_make.attach.type = KDBUS_ITEM_ATTACH_FLAGS_SEND;
+       bus_make.attach.size = sizeof(bus_make.attach);
+       bus_make.attach.flags = owner_meta;
+
+       bus_make.name.type = KDBUS_ITEM_MAKE_NAME;
+       bus_make.name.size = KDBUS_ITEM_HEADER_SIZE +
+                            strlen(bus_make.name.str) + 1;
+
+       bus_make.cmd.flags = KDBUS_MAKE_ACCESS_WORLD;
+       bus_make.cmd.size = sizeof(bus_make.cmd) +
+                            bus_make.bp.size +
+                            bus_make.attach.size +
+                            bus_make.name.size;
+
+       kdbus_printf("Creating bus with name >%s< on control fd %d ...\n",
+                    name, control_fd);
+
+       ret = kdbus_cmd_bus_make(control_fd, &bus_make.cmd);
+       if (ret < 0) {
+               kdbus_printf("--- error when making bus: %d (%m)\n", ret);
+               return ret;
+       }
+
+       if (ret == 0 && path)
+               *path = strdup(bus_make.name.str);
+
+       return ret;
+}
+
+wur struct kdbus_conn *
+kdbus_hello(const char *path, uint64_t flags,
+           const struct kdbus_item *item, size_t item_size)
+{
+       struct kdbus_cmd_free cmd_free = {};
+       int fd, ret;
+       struct {
+               struct kdbus_cmd_hello hello;
+
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       char str[16];
+               } conn_name;
+
+               uint8_t extra_items[item_size];
+       } h;
+       struct kdbus_conn *conn;
+
+       memset(&h, 0, sizeof(h));
+
+       if (item_size > 0)
+               memcpy(h.extra_items, item, item_size);
+
+       kdbus_printf("-- opening bus connection %s\n", path);
+       fd = open(path, O_RDWR|O_CLOEXEC);
+       if (fd < 0) {
+               kdbus_printf("--- error %d (%m)\n", fd);
+               return NULL;
+       }
+
+       h.hello.flags = flags | KDBUS_HELLO_ACCEPT_FD;
+       h.hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+       h.hello.attach_flags_recv = _KDBUS_ATTACH_ALL;
+       h.conn_name.type = KDBUS_ITEM_CONN_DESCRIPTION;
+       strcpy(h.conn_name.str, "this-is-my-name");
+       h.conn_name.size = KDBUS_ITEM_HEADER_SIZE + strlen(h.conn_name.str) + 1;
+
+       h.hello.size = sizeof(h);
+       h.hello.pool_size = POOL_SIZE;
+
+       ret = kdbus_cmd_hello(fd, (struct kdbus_cmd_hello *) &h.hello);
+       if (ret < 0) {
+               kdbus_printf("--- error when saying hello: %d (%m)\n", ret);
+               return NULL;
+       }
+       kdbus_printf("-- Our peer ID for %s: %llu -- bus uuid: '%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x'\n",
+                    path, (unsigned long long)h.hello.id,
+                    h.hello.id128[0],  h.hello.id128[1],  h.hello.id128[2],
+                    h.hello.id128[3],  h.hello.id128[4],  h.hello.id128[5],
+                    h.hello.id128[6],  h.hello.id128[7],  h.hello.id128[8],
+                    h.hello.id128[9],  h.hello.id128[10], h.hello.id128[11],
+                    h.hello.id128[12], h.hello.id128[13], h.hello.id128[14],
+                    h.hello.id128[15]);
+
+       cmd_free.size = sizeof(cmd_free);
+       cmd_free.offset = h.hello.offset;
+       ret = kdbus_cmd_free(fd, &cmd_free);
+       if (ret < 0 && !(flags & KDBUS_HELLO_POLICY_HOLDER && -EOPNOTSUPP == ret)) { /* free not supported for policy holders */
+               print("hello: KDBUS_CMD_FREE err(%d)\n", ret);
+               return NULL;
+       }
+
+       conn = alloc(sizeof(*conn));
+       if (!conn) {
+               kdbus_printf("unable to alloc()!?\n");
+               return NULL;
+       }
+
+       conn->buf = mmap(NULL, POOL_SIZE, PROT_READ, MAP_SHARED, fd, 0);
+       if (conn->buf == MAP_FAILED) {
+               free(conn);
+               CLOSE(fd);
+               kdbus_printf("--- error mmap (%m)\n");
+               return NULL;
+       }
+
+       conn->fd = fd;
+       conn->id = h.hello.id;
+       _Static_assert((typeof(conn->attach_flags_recv))_KDBUS_ATTACH_ALL == _KDBUS_ATTACH_ALL, "kdbus_conn::attach_flags_recv too narrow for _KDBUS_ATTACH_ALL");
+       conn->attach_flags_recv = _KDBUS_ATTACH_ALL;
+       return conn;
+}
+
+wur struct kdbus_conn *
+kdbus_hello_registrar(const char *path, const char *name,
+                     const struct kdbus_policy_access *access,
+                     size_t num_access, uint64_t flags)
+{
+       struct kdbus_item *item, *items;
+       size_t i, size;
+
+       size = KDBUS_ITEM_SIZE(strlen(name) + 1) +
+               num_access * KDBUS_ITEM_SIZE(sizeof(*access));
+
+       items = alloca(size);
+
+       item = items;
+       item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1;
+       item->type = KDBUS_ITEM_NAME;
+       strcpy(item->str, name);
+       item = KDBUS_ITEM_NEXT(item);
+
+       for (i = 0; i < num_access; i++) {
+               item->size = KDBUS_ITEM_HEADER_SIZE +
+                            sizeof(struct kdbus_policy_access);
+               item->type = KDBUS_ITEM_POLICY_ACCESS;
+
+               item->policy_access.type = access[i].type;
+               item->policy_access.access = access[i].access;
+               item->policy_access.id = access[i].id;
+
+               item = KDBUS_ITEM_NEXT(item);
+       }
+
+       return kdbus_hello(path, flags, items, size);
+}
+
+wur struct kdbus_conn *kdbus_hello_activator(const char *path, const char *name,
+                                  const struct kdbus_policy_access *access,
+                                  size_t num_access)
+{
+       return kdbus_hello_registrar(path, name, access, num_access,
+                                    KDBUS_HELLO_ACTIVATOR);
+}
+
+wur bool kdbus_item_in_message(struct kdbus_msg *msg, uint64_t type)
+{
+       const struct kdbus_item *item;
+
+       KDBUS_ITEM_FOREACH(item, msg, items)
+               if (item->type == type)
+                       return true;
+
+       return false;
+}
+
+wur int kdbus_bus_creator_info(struct kdbus_conn *conn,
+                          uint64_t flags,
+                          uint64_t *offset)
+{
+       struct kdbus_cmd_info *cmd;
+       size_t size = sizeof(*cmd);
+       int ret;
+
+       cmd = alloca(size);
+       memset(cmd, 0, size);
+       cmd->size = size;
+       cmd->attach_flags = flags;
+
+       ret = kdbus_cmd_bus_creator_info(conn->fd, cmd);
+       if (ret < 0) {
+               kdbus_printf("--- error when requesting info: %d (%m)\n", ret);
+               return ret;
+       }
+
+       if (offset)
+               *offset = cmd->offset;
+       else
+               ret = kdbus_free(conn, cmd->offset);
+
+       return ret;
+}
+
+wur static int kdbus_info_verify(struct kdbus_info *info, unsigned attach_flags);
+
+wur int kdbus_conn_info(struct kdbus_conn *conn, uint64_t id,
+                   const char *name, uint64_t flags,
+                   uint64_t *offset)
+{
+       struct kdbus_cmd_info *cmd;
+       size_t size = sizeof(*cmd);
+       size_t full_size;
+       struct kdbus_info *info;
+       int ret;
+
+       /*print("call prepared 0\n");*/
+
+       if (name)
+               size += KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1;
+
+       /*print("call prepared 1\n");*/
+
+       full_size = KDBUS_ALIGN8(size) + 1000;
+
+       /*print("call prepared 2\n");*/
+
+       cmd = malloc(full_size);
+       if (!cmd)
+               return -ENOMEM;
+       /*print("call prepared 3\n");*/
+       memset(cmd, 0, full_size);
+       /*print("call prepared 4\n");*/
+       cmd->size = size;
+       /*print("call prepared 5\n");*/
+       cmd->attach_flags = flags;
+       /*print("call prepared 6\n");*/
+
+       if (name) {
+               /*print("call prepared 7\n");*/
+               cmd->items[0].size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1;
+               /*print("call prepared 8\n");*/
+               cmd->items[0].type = KDBUS_ITEM_NAME;
+               /*print("call prepared 9 cmd(%p) cmd->items[0].str(%p) full_size(%u) len(%u) name(%s)\n", cmd, cmd->items[0].str, (unsigned)full_size, (unsigned)strlen(name), name);*/
+
+               /* tizen 3.0 strcpy reports buffer overflow for no apparent reason - memcpy doesn't */
+               memcpy(cmd->items[0].str, name, strlen(name));
+               /*strcpy(cmd->items[0].str, name);*/
+       } else {
+               /*print("call prepared 10\n");*/
+               cmd->id = id;
+       }
+
+       /*print("call prepared 11\n");*/
+
+       ret = kdbus_cmd_conn_info(conn->fd, cmd);
+       if (ret < 0) {
+               kdbus_printf("--- error when requesting info: %d (%m)\n", ret);
+               free(cmd);
+               return ret;
+       }
+
+       info = (struct kdbus_info *) (conn->buf + cmd->offset);
+       if (KDBUS_ALIGN8(info->size) != cmd->info_size) {
+               kdbus_printf("%s(): size mismatch: %d != %d\n", __func__,
+                               (int) info->size, (int) cmd->info_size);
+               free(cmd);
+               return -EIO;
+       }
+
+       if (offset) {
+               *offset = cmd->offset;
+               ASSERT_ZERO(kdbus_info_verify(info, flags));
+       } else
+               ret = kdbus_free(conn, cmd->offset);
+
+       free(cmd);
+       return ret;
+}
+
+void kdbus_conn_free(struct kdbus_conn *conn)
+{
+       if (!conn)
+               return;
+
+       if (conn->buf && munmap(conn->buf, POOL_SIZE))
+               fail("munmap(%p) err(%d)", conn->buf, errno);
+
+       if (conn->fd >= 0 && close(conn->fd))
+               fail("close(%d) err(%d)", conn->fd, errno);
+
+       free(conn);
+}
+
+wur int sys_memfd_create(const char *name, __u64 size)
+{
+       int ret, fd;
+
+       fd = syscall(__NR_memfd_create, name, 2/*MFD_ALLOW_SEALING*/);
+       if (fd < 0)
+               return fd;
+
+       ret = ftruncate(fd, size);
+       if (ret < 0) {
+               CLOSE(fd);
+               return ret;
+       }
+
+       return fd;
+}
+
+wur int sys_memfd_seal_set(int fd)
+{
+       return fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK |
+                        F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL);
+}
+
+wur off_t sys_memfd_get_size(int fd, off_t *size)
+{
+       struct stat stat;
+       int ret;
+
+       ret = fstat(fd, &stat);
+       if (ret < 0) {
+               kdbus_printf("stat() failed: %m\n");
+               return ret;
+       }
+
+       *size = stat.st_size;
+       return 0;
+}
+
+static wur int __kdbus_msg_send(const struct kdbus_conn *conn,
+                           const char *name,
+                           uint64_t cookie,
+                           uint64_t flags,
+                           uint64_t timeout,
+                           int64_t priority,
+                           uint64_t dst_id,
+                           uint64_t cmd_flags,
+                           int cancel_fd)
+{
+       struct kdbus_cmd_send *cmd = NULL;
+       struct kdbus_msg *msg = NULL;
+       const char ref1[1024 * 128 + 3] = "0123456789_0";
+       const char ref2[] = "0123456789_1";
+       struct kdbus_item *item;
+       struct timespec now;
+       uint64_t size;
+       int memfd = -1;
+       int ret;
+
+       size = sizeof(*msg) + 3 * KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
+
+       if (dst_id == KDBUS_DST_ID_BROADCAST)
+               size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64;
+       else {
+               memfd = sys_memfd_create("my-name-is-nice", 1024 * 1024);
+               if (memfd < 0) {
+                       kdbus_printf("failed to create memfd: %m\n");
+                       return memfd;
+               }
+
+               if (write(memfd, "kdbus memfd 1234567", 19) != 19) {
+                       ret = -errno;
+                       kdbus_printf("writing to memfd failed: %m\n");
+                       goto out;
+               }
+
+               ret = sys_memfd_seal_set(memfd);
+               if (ret < 0) {
+                       ret = -errno;
+                       kdbus_printf("memfd sealing failed: %m\n");
+                       goto out;
+               }
+
+               size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd));
+       }
+
+       if (name)
+               size += KDBUS_ITEM_SIZE(strlen(name) + 1);
+
+       msg = alloc(size);
+       if (!msg) {
+               ret = -errno;
+               kdbus_printf("unable to alloc()!?\n");
+               goto out;
+       }
+
+       if (dst_id == KDBUS_DST_ID_BROADCAST)
+               flags |= KDBUS_MSG_SIGNAL;
+
+       memset(msg, 0, size);
+       msg->flags = flags;
+       msg->priority = priority;
+       msg->size = size;
+       msg->src_id = conn->id;
+       msg->dst_id = name ? 0 : dst_id;
+       msg->cookie = cookie;
+       msg->payload_type = KDBUS_PAYLOAD_DBUS;
+
+       if (timeout) {
+               ret = clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
+               if (ret < 0)
+                       goto out;
+
+               msg->timeout_ns = now.tv_sec * 1000000000ULL +
+                                 now.tv_nsec + timeout;
+       }
+
+       item = msg->items;
+
+       if (name) {
+               item->type = KDBUS_ITEM_DST_NAME;
+               item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1;
+               strcpy(item->str, name);
+               item = KDBUS_ITEM_NEXT(item);
+       }
+
+       item->type = KDBUS_ITEM_PAYLOAD_VEC;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec);
+       item->vec.address = (uintptr_t)&ref1;
+       item->vec.size = sizeof(ref1);
+       item = KDBUS_ITEM_NEXT(item);
+
+       /* data padding for ref1 */
+       item->type = KDBUS_ITEM_PAYLOAD_VEC;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec);
+       item->vec.address = (uintptr_t)NULL;
+       item->vec.size =  KDBUS_ALIGN8(sizeof(ref1)) - sizeof(ref1);
+       item = KDBUS_ITEM_NEXT(item);
+
+       item->type = KDBUS_ITEM_PAYLOAD_VEC;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec);
+       item->vec.address = (uintptr_t)&ref2;
+       item->vec.size = sizeof(ref2);
+       item = KDBUS_ITEM_NEXT(item);
+
+       if (dst_id == KDBUS_DST_ID_BROADCAST) {
+               item->type = KDBUS_ITEM_BLOOM_FILTER;
+               item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64;
+               item->bloom_filter.generation = 0;
+       } else {
+               item->type = KDBUS_ITEM_PAYLOAD_MEMFD;
+               item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_memfd);
+               item->memfd.size = 16;
+               item->memfd.fd = memfd;
+       }
+       item = KDBUS_ITEM_NEXT(item);
+
+       size = sizeof(*cmd);
+       if (cancel_fd != -1)
+               size += KDBUS_ITEM_SIZE(sizeof(cancel_fd));
+
+       cmd = alloc(size);
+       if (!cmd) {
+               ret = -errno;
+               kdbus_printf("unable to alloc()!?\n");
+               goto out;
+       }
+
+       cmd->size = size;
+       cmd->flags = cmd_flags;
+       cmd->msg_address = (uintptr_t)msg;
+
+       item = cmd->items;
+
+       if (cancel_fd != -1) {
+               item->type = KDBUS_ITEM_CANCEL_FD;
+               item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(cancel_fd);
+               item->fds[0] = cancel_fd;
+               item = KDBUS_ITEM_NEXT(item);
+       }
+
+       ret = kdbus_cmd_send(conn->fd, cmd);
+       if (ret < 0) {
+               kdbus_printf("error sending message: %d (%m)\n", ret);
+               goto out;
+       }
+
+       if (cmd_flags & KDBUS_SEND_SYNC_REPLY) {
+               struct kdbus_msg *reply;
+               int dumpret;
+
+               kdbus_printf("SYNC REPLY @offset %llu:\n", cmd->reply.offset);
+               reply = (struct kdbus_msg *)(conn->buf + cmd->reply.offset);
+               dumpret = kdbus_msg_dump(reply);
+
+               kdbus_msg_free(reply);
+
+               ret = kdbus_free(conn, cmd->reply.offset);
+               if (!ret)
+                       ret = dumpret;
+       }
+
+out:
+       free(msg);
+       free(cmd);
+
+       if (memfd >= 0)
+               CLOSE(memfd);
+
+       return ret < 0 ? ret : 0;
+}
+
+wur int kdbus_msg_send(const struct kdbus_conn *conn, const char *name,
+                  uint64_t cookie, uint64_t flags, uint64_t timeout,
+                  int64_t priority, uint64_t dst_id)
+{
+       return __kdbus_msg_send(conn, name, cookie, flags, timeout, priority,
+                               dst_id, 0, -1);
+}
+
+wur int kdbus_msg_send_sync(const struct kdbus_conn *conn, const char *name,
+                       uint64_t cookie, uint64_t flags, uint64_t timeout,
+                       int64_t priority, uint64_t dst_id, int cancel_fd)
+{
+       return __kdbus_msg_send(conn, name, cookie, flags, timeout, priority,
+                               dst_id, KDBUS_SEND_SYNC_REPLY, cancel_fd);
+}
+
+wur int kdbus_msg_send_reply(const struct kdbus_conn *conn,
+                        uint64_t reply_cookie,
+                        uint64_t dst_id)
+{
+       struct kdbus_cmd_send cmd = {};
+       struct kdbus_msg *msg;
+       const char ref1[1024 * 128 + 3] = "0123456789_0";
+       struct kdbus_item *item;
+       uint64_t size;
+       int ret;
+
+       size = sizeof(struct kdbus_msg);
+       size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
+
+       msg = alloc(size);
+       if (!msg) {
+               kdbus_printf("unable to alloc()!?\n");
+               return -ENOMEM;
+       }
+
+       memset(msg, 0, size);
+       msg->size = size;
+       msg->src_id = conn->id;
+       msg->dst_id = dst_id;
+       msg->cookie_reply = reply_cookie;
+       msg->payload_type = KDBUS_PAYLOAD_DBUS;
+
+       item = msg->items;
+
+       item->type = KDBUS_ITEM_PAYLOAD_VEC;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec);
+       item->vec.address = (uintptr_t)&ref1;
+       item->vec.size = sizeof(ref1);
+       item = KDBUS_ITEM_NEXT(item);
+
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg;
+
+       ret = kdbus_cmd_send(conn->fd, &cmd);
+       if (ret < 0)
+               kdbus_printf("error sending message: %d (%m)\n", ret);
+
+       free(msg);
+
+       return ret;
+}
+
+static wur char *msg_id(uint64_t id, char *buf)
+{
+       if (id == 0)
+               return "KERNEL";
+       if (id == ~0ULL)
+               return "BROADCAST";
+       sprintf(buf, "%llu", (unsigned long long)id);
+       return buf;
+}
+
+wur int kdbus_msg_dump(const struct kdbus_msg *msg) {
+       const struct kdbus_item *item = msg->items;
+       char buf_src[32];
+       char buf_dst[32];
+       uint64_t timeout = 0;
+       uint64_t cookie_reply = 0;
+       int ret = 0;
+
+       if (msg->flags & KDBUS_MSG_EXPECT_REPLY)
+               timeout = msg->timeout_ns;
+       else
+               cookie_reply = msg->cookie_reply;
+
+       kdbus_printf("MESSAGE(%p): %s (%llu bytes) flags=0x%08llx, %s → %s, "
+                    "cookie=%llu, timeout=%llu cookie_reply=%llu priority=%lli\n",
+               msg,
+               enum_PAYLOAD(msg->payload_type), (unsigned long long)msg->size,
+               (unsigned long long)msg->flags,
+               msg_id(msg->src_id, buf_src), msg_id(msg->dst_id, buf_dst),
+               (unsigned long long)msg->cookie, (unsigned long long)timeout,
+               (unsigned long long)cookie_reply, (long long)msg->priority);
+
+       KDBUS_ITEM_FOREACH(item, msg, items) {
+               if (item->size < KDBUS_ITEM_HEADER_SIZE) {
+                       kdbus_printf("  +%s (%llu bytes) invalid data record\n",
+                                    enum_MSG(item->type), item->size);
+                       return -EINVAL;
+               }
+
+               switch (item->type) {
+               case KDBUS_ITEM_PAYLOAD_OFF: {
+                       char *s;
+
+                       if (item->vec.offset == ~0ULL)
+                               s = "[\\0-bytes]";
+                       else
+                               s = (char *)msg + item->vec.offset;
+
+                       kdbus_printf("  +%s (%llu bytes) off=%llu size=%llu '%s'\n",
+                              enum_MSG(item->type), item->size,
+                              (unsigned long long)item->vec.offset,
+                              (unsigned long long)item->vec.size, s);
+                       break;
+               }
+
+               case KDBUS_ITEM_FDS: {
+                       int i, n = (item->size - KDBUS_ITEM_HEADER_SIZE) /
+                                       sizeof(int);
+
+                       kdbus_printf("  +%s (%llu bytes, %d fds)\n",
+                              enum_MSG(item->type), item->size, n);
+
+                       for (i = 0; i < n; i++)
+                               kdbus_printf("    fd[%d] = %d\n",
+                                            i, item->fds[i]);
+
+                       break;
+               }
+
+               case KDBUS_ITEM_PAYLOAD_MEMFD: {
+                       char *buf;
+                       off_t size;
+
+                       buf = mmap(NULL, item->memfd.size, PROT_READ,
+                                  MAP_PRIVATE, item->memfd.fd, 0);
+                       if (buf == MAP_FAILED) {
+                               kdbus_printf("mmap() fd=%i size=%llu failed: %m\n",
+                                            item->memfd.fd, item->memfd.size);
+                               break;
+                       }
+
+                       if (sys_memfd_get_size(item->memfd.fd, &size) < 0) {
+                               kdbus_printf("KDBUS_CMD_MEMFD_SIZE_GET failed: %m\n");
+                               break;
+                       }
+
+                       kdbus_printf("  +%s (%llu bytes) fd=%i size=%llu filesize=%llu '0x%llx'\n",
+                              enum_MSG(item->type), item->size, item->memfd.fd,
+                              (unsigned long long)item->memfd.size,
+                              (unsigned long long)size, (unsigned long long)(size >= 8 ? *(unsigned long long *)buf : size >= 4 ? *(unsigned *)buf : *buf));
+                       munmap(buf, item->memfd.size);
+                       break;
+               }
+
+               case KDBUS_ITEM_CREDS:
+                       kdbus_printf("  +%s (%llu bytes) uid=%lld, euid=%lld, suid=%lld, fsuid=%lld, "
+                                                       "gid=%lld, egid=%lld, sgid=%lld, fsgid=%lld\n",
+                               enum_MSG(item->type), item->size,
+                               item->creds.uid, item->creds.euid,
+                               item->creds.suid, item->creds.fsuid,
+                               item->creds.gid, item->creds.egid,
+                               item->creds.sgid, item->creds.fsgid);
+                       break;
+
+               case KDBUS_ITEM_PIDS:
+                       kdbus_printf("  +%s (%llu bytes) pid=%lld, tid=%lld, ppid=%lld\n",
+                               enum_MSG(item->type), item->size,
+                               item->pids.pid, item->pids.tid,
+                               item->pids.ppid);
+                       break;
+
+               case KDBUS_ITEM_AUXGROUPS: {
+                       int i, n;
+
+                       kdbus_printf("  +%s (%llu bytes)\n",
+                                    enum_MSG(item->type), item->size);
+                       n = (item->size - KDBUS_ITEM_HEADER_SIZE) /
+                               sizeof(uint64_t);
+
+                       for (i = 0; i < n; i++)
+                               kdbus_printf("    gid[%d] = %lld\n",
+                                            i, item->data64[i]);
+                       break;
+               }
+
+               case KDBUS_ITEM_NAME:
+               case KDBUS_ITEM_PID_COMM:
+               case KDBUS_ITEM_TID_COMM:
+               case KDBUS_ITEM_EXE:
+               case KDBUS_ITEM_CGROUP:
+               case KDBUS_ITEM_SECLABEL:
+               case KDBUS_ITEM_DST_NAME:
+               case KDBUS_ITEM_CONN_DESCRIPTION:
+                       kdbus_printf("  +%s (%llu bytes) '%s' (%zu)\n",
+                                    enum_MSG(item->type), item->size,
+                                    item->str, strlen(item->str));
+                       break;
+
+               case KDBUS_ITEM_OWNED_NAME: {
+                       kdbus_printf("  +%s (%llu bytes) '%s' (%zu) flags=0x%08llx\n",
+                                    enum_MSG(item->type), item->size,
+                                    item->name.name, strlen(item->name.name),
+                                    item->name.flags);
+                       break;
+               }
+
+               case KDBUS_ITEM_CMDLINE: {
+                       size_t size = item->size - KDBUS_ITEM_HEADER_SIZE;
+                       const char *str = item->str;
+                       int count = 0;
+
+                       kdbus_printf("  +%s (%llu bytes) ",
+                                    enum_MSG(item->type), item->size);
+                       while (size) {
+                               kdbus_printf("'%s' ", str);
+                               size -= strlen(str) + 1;
+                               str += strlen(str) + 1;
+                               count++;
+                       }
+
+                       kdbus_printf("(%d string%s)\n",
+                                    count, (count == 1) ? "" : "s");
+                       break;
+               }
+
+               case KDBUS_ITEM_AUDIT:
+                       kdbus_printf("  +%s (%llu bytes) loginuid=%u sessionid=%u\n",
+                              enum_MSG(item->type), item->size,
+                              item->audit.loginuid, item->audit.sessionid);
+                       break;
+
+               case KDBUS_ITEM_CAPS: {
+                       const uint32_t *cap;
+                       int n, i;
+
+                       kdbus_printf("  +%s (%llu bytes) len=%llu bytes, last_cap %d\n",
+                                    enum_MSG(item->type), item->size,
+                                    (unsigned long long)item->size -
+                                       KDBUS_ITEM_HEADER_SIZE,
+                                    (int) item->caps.last_cap);
+
+                       cap = item->caps.caps;
+                       n = (item->size - offsetof(struct kdbus_item, caps.caps))
+                               / 4 / sizeof(uint32_t);
+
+                       kdbus_printf("    CapInh=");
+                       for (i = 0; i < n; i++)
+                               kdbus_printf("%08x", cap[(0 * n) + (n - i - 1)]);
+
+                       kdbus_printf(" CapPrm=");
+                       for (i = 0; i < n; i++)
+                               kdbus_printf("%08x", cap[(1 * n) + (n - i - 1)]);
+
+                       kdbus_printf(" CapEff=");
+                       for (i = 0; i < n; i++)
+                               kdbus_printf("%08x", cap[(2 * n) + (n - i - 1)]);
+
+                       kdbus_printf(" CapBnd=");
+                       for (i = 0; i < n; i++)
+                               kdbus_printf("%08x", cap[(3 * n) + (n - i - 1)]);
+                       kdbus_printf("\n");
+                       break;
+               }
+
+               case KDBUS_ITEM_TIMESTAMP:
+                       kdbus_printf("  +%s (%llu bytes) seq=%llu realtime=%lluns monotonic=%lluns\n",
+                              enum_MSG(item->type), item->size,
+                              (unsigned long long)item->timestamp.seqnum,
+                              (unsigned long long)item->timestamp.realtime_ns,
+                              (unsigned long long)item->timestamp.monotonic_ns);
+                       break;
+
+               case KDBUS_ITEM_REPLY_TIMEOUT:
+                       kdbus_printf("  +%s (%llu bytes) cookie=%llu\n",
+                              enum_MSG(item->type), item->size,
+                              msg->cookie_reply);
+                       break;
+
+               case KDBUS_ITEM_NAME_ADD:
+               case KDBUS_ITEM_NAME_REMOVE:
+               case KDBUS_ITEM_NAME_CHANGE:
+                       kdbus_printf("  +%s (%llu bytes) '%s', old id=%lld, now id=%lld, old_flags=0x%llx new_flags=0x%llx\n",
+                               enum_MSG(item->type),
+                               (unsigned long long) item->size,
+                               item->name_change.name,
+                               item->name_change.old_id.id,
+                               item->name_change.new_id.id,
+                               item->name_change.old_id.flags,
+                               item->name_change.new_id.flags);
+                       break;
+
+               case KDBUS_ITEM_ID_ADD:
+               case KDBUS_ITEM_ID_REMOVE:
+                       kdbus_printf("  +%s (%llu bytes) id=%llu flags=%llu\n",
+                              enum_MSG(item->type),
+                              (unsigned long long) item->size,
+                              (unsigned long long) item->id_change.id,
+                              (unsigned long long) item->id_change.flags);
+                       break;
+
+               default:
+                       kdbus_printf("  +%s (%llu bytes)\n",
+                                    enum_MSG(item->type), item->size);
+                       break;
+               }
+       }
+
+       if ((char *)item - ((char *)msg + msg->size) >= 8) {
+               kdbus_printf("invalid padding at end of message\n");
+               ret = -EINVAL;
+       }
+
+       kdbus_printf("\n");
+
+       return ret;
+}
+
+void kdbus_msg_free(struct kdbus_msg *msg)
+{
+       const struct kdbus_item *item;
+       int nfds, i;
+
+       if (!msg)
+               return;
+
+       KDBUS_ITEM_FOREACH(item, msg, items) {
+               switch (item->type) {
+               /* close all memfds */
+               case KDBUS_ITEM_PAYLOAD_MEMFD:
+                       if (-1 != item->memfd.fd)
+                               CLOSE(item->memfd.fd);
+                       break;
+               case KDBUS_ITEM_FDS:
+                       nfds = (item->size - KDBUS_ITEM_HEADER_SIZE) /
+                               sizeof(int);
+
+                       for (i = 0; i < nfds; i++)
+                               if (-1 != item->fds[i])
+                                       CLOSE(item->fds[i]);
+
+                       break;
+               }
+       }
+}
+
+wur static int verify_padding(char const *b, unsigned off)
+{
+       while (off%8) {
+               ASSERT_ZERO((unsigned)b[off]);
+               ++off;
+       }
+       return 0;
+}
+
+wur static int verify_string(unsigned size, char const *s)
+{
+       unsigned len;
+       ASSERT_NONZERO(size);
+       len = strlen(s)+1;
+       ASSERT_RETURN(len,==,size);
+       ASSERT_ZERO(verify_padding(s, len));
+       return 0;
+}
+wur static int verify_string_array(unsigned size, char const *s)
+{
+       unsigned off=0;
+       ASSERT_NONZERO(size);
+       while (size) {
+               unsigned len = strlen(s+off);
+               if (!len)
+                       break;
+               ++len;
+               ASSERT_RETURN(len,<=,size);
+               size -= len;
+               off += len;
+       }
+       ASSERT_ZERO(verify_padding(s, off));
+       return 0;
+}
+
+#define VERIFY_DECL\
+       const struct kdbus_item *item;\
+       unsigned item_count[14];\
+       unsigned i;\
+       memset(item_count, 0, sizeof(item_count));
+#define VERIFY_DECL_INLOOP\
+       unsigned idx;\
+       unsigned size;\
+       ASSERT_RETURN(item->size,>=,2*sizeof(uint64_t));\
+       ASSERT_RETURN((unsigned)item->size,==,item->size);\
+       size = item->size - 2*sizeof(uint64_t);
+#define VERIFY_META_CASES\
+       /* metadata */\
+       case KDBUS_ITEM_CREDS:\
+               ASSERT_RETURN(size,==,sizeof(struct kdbus_creds));\
+               break;\
+       case KDBUS_ITEM_PIDS:\
+               ASSERT_RETURN(size,==,sizeof(struct kdbus_pids));\
+               break;\
+       case KDBUS_ITEM_AUDIT:\
+               ASSERT_RETURN(size,==,sizeof(struct kdbus_audit));\
+               break;\
+       case KDBUS_ITEM_AUXGROUPS:\
+       case KDBUS_ITEM_CAPS:\
+               ASSERT_ZERO(size%sizeof(uint32_t));\
+               break;\
+       case KDBUS_ITEM_OWNED_NAME:\
+               ASSERT_ZERO(verify_string(size-offsetof(typeof(item->name), name), item->name.name));\
+               break;\
+       case KDBUS_ITEM_TID_COMM:\
+       case KDBUS_ITEM_PID_COMM:\
+       case KDBUS_ITEM_EXE:\
+       case KDBUS_ITEM_CGROUP:\
+       case KDBUS_ITEM_SECLABEL:\
+       case KDBUS_ITEM_CONN_DESCRIPTION:\
+               ASSERT_ZERO(verify_string(size, item->str));\
+               break;\
+       case KDBUS_ITEM_CMDLINE:\
+               ASSERT_ZERO(verify_string_array(size, item->str));\
+               break;
+#define VERIFY_META do {\
+       ASSERT_RETURN(item->type,>=,(uint64_t)_KDBUS_ITEM_ATTACH_BASE);\
+       idx = item->type - _KDBUS_ITEM_ATTACH_BASE;\
+       ASSERT_RETURN(idx,<,sizeof(item_count)/sizeof(*item_count));\
+       ASSERT_NONZERO(attach_flags & 1<<idx);\
+       if (KDBUS_ITEM_OWNED_NAME != item->type)\
+               ASSERT_ZERO(item_count[idx]);\
+       else\
+               ASSERT_RETURN(item_count[idx],<,(uint64_t)KDBUS_CONN_MAX_NAMES);\
+       ++item_count[idx];\
+} while (0)
+#define VERIFY_FINAL_ASSERT do {\
+       for (i=0; i<TABSIZE(item_count); ++i) {\
+               if (attach_flags & 1<<i &&\
+                       KDBUS_ITEM_TIMESTAMP-_KDBUS_ITEM_ATTACH_BASE != i && /* nameless connections happen */\
+                       KDBUS_ITEM_OWNED_NAME-_KDBUS_ITEM_ATTACH_BASE != i && /* nameless connections happen */\
+                       KDBUS_ITEM_CONN_DESCRIPTION-_KDBUS_ITEM_ATTACH_BASE != i && /* defining a connection description is optional */\
+                       KDBUS_ITEM_AUDIT-_KDBUS_ITEM_ATTACH_BASE != i) /* missing if CONFIG_AUDITSYSCALL not defined */\
+                       if (!item_count[i]) ASSERT_ZERO(i+1); /*hack to make i visible*/\
+       }\
+} while (0)
+
+wur static int kdbus_info_verify(struct kdbus_info *info, unsigned attach_flags)
+{
+       VERIFY_DECL;
+
+       KDBUS_ITEM_FOREACH(item, info, items) {
+               VERIFY_DECL_INLOOP;
+               switch(item->type) {
+                       VERIFY_META_CASES;
+                       default: ASSERT_ZERO(item->type ?: (uint64_t)-1);
+               }
+               VERIFY_META;
+       }
+
+       VERIFY_FINAL_ASSERT;
+
+       return 0;
+}
+
+wur static int kdbus_msg_verify(struct kdbus_msg *msg, unsigned attach_flags)
+{
+       bool user = false;
+
+       VERIFY_DECL;
+
+       if (msg->payload_type != KDBUS_PAYLOAD_KERNEL) {
+               ASSERT_RETURN(msg->payload_type,==,KDBUS_PAYLOAD_DBUS);
+               user = true;
+       }
+
+       KDBUS_ITEM_FOREACH(item, msg, items) {
+               VERIFY_DECL_INLOOP;
+
+               switch(item->type) {
+                       case KDBUS_ITEM_TIMESTAMP:
+                               ASSERT_RETURN(size,==,sizeof(struct kdbus_timestamp));
+                               break;
+                       VERIFY_META_CASES;
+
+                       /* user items */
+                       case KDBUS_ITEM_PAYLOAD_OFF:
+                               ASSERT_NONZERO(user);
+                               ASSERT_RETURN(size,==,sizeof(struct kdbus_vec));
+                               continue;
+                       case KDBUS_ITEM_PAYLOAD_MEMFD:
+                               ASSERT_NONZERO(user);
+                               ASSERT_RETURN(size,==,sizeof(struct kdbus_memfd));
+                               continue;
+                       case KDBUS_ITEM_FDS:
+                               ASSERT_NONZERO(user);
+                               ASSERT_ZERO(size%sizeof(int));
+                               continue;
+                       case KDBUS_ITEM_DST_NAME:
+                               ASSERT_NONZERO(user);
+                               ASSERT_ZERO(verify_string(size, item->str));
+                               continue;
+
+                       /* kernel items */
+                       case KDBUS_ITEM_NAME_ADD:
+                       case KDBUS_ITEM_NAME_REMOVE:
+                       case KDBUS_ITEM_NAME_CHANGE:
+                               ASSERT_ZERO(user);
+                               ASSERT_RETURN(size,>=,sizeof(struct kdbus_notify_name_change));
+                               ASSERT_ZERO(verify_string(size-sizeof(struct kdbus_notify_name_change), item->name_change.name));
+                               continue;
+                       case KDBUS_ITEM_ID_ADD:
+                       case KDBUS_ITEM_ID_REMOVE:
+                               ASSERT_ZERO(user);
+                               ASSERT_RETURN(size,==,sizeof(struct kdbus_notify_id_change));
+                               continue;
+                       case KDBUS_ITEM_REPLY_TIMEOUT:
+                       case KDBUS_ITEM_REPLY_DEAD:
+                               ASSERT_ZERO(user);
+                               ASSERT_ZERO(size);
+                               continue;
+
+                       case KDBUS_ITEM_BLOOM_FILTER:
+                               kdbus_printf("WARNING! KDBUS_ITEM_BLOOM_FILTER passed from the kernel\n");
+                               continue;
+
+                       default:
+                               ASSERT_ZERO(item->type ?: (uint64_t)-1);
+               }
+               if (KDBUS_ITEM_TIMESTAMP != item->type)
+                       ASSERT_NONZERO(user);
+               VERIFY_META;
+       }
+
+       /* we're requesting all metadata and that's what we mandate */
+       if (user) {
+               VERIFY_FINAL_ASSERT;
+               if (attach_flags & 1 << (KDBUS_ITEM_TIMESTAMP-_KDBUS_ITEM_ATTACH_BASE))
+                       ASSERT_NONZERO(item_count[KDBUS_ITEM_TIMESTAMP-_KDBUS_ITEM_ATTACH_BASE]);
+       } else if (attach_flags & 1 << (KDBUS_ITEM_TIMESTAMP-_KDBUS_ITEM_ATTACH_BASE))
+               ASSERT_NONZERO(item_count[KDBUS_ITEM_TIMESTAMP-_KDBUS_ITEM_ATTACH_BASE]);
+
+       return 0;
+}
+
+wur int kdbus_msg_recv(struct kdbus_conn *conn,
+                  struct kdbus_msg **msg_out,
+                  uint64_t *offset)
+{
+       struct kdbus_cmd_recv recv = { .size = sizeof(recv) };
+       struct kdbus_msg *msg;
+       int ret;
+
+       ret = kdbus_cmd_recv(conn->fd, &recv);
+       if (ret < 0)
+               return ret;
+
+       msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset);
+       ret = kdbus_msg_dump(msg);
+       ASSERT_ZERO(kdbus_msg_verify(msg, conn->attach_flags_recv));
+       if (ret < 0) {
+               kdbus_msg_free(msg);
+               return ret;
+       }
+
+       if (msg_out) {
+               *msg_out = msg;
+
+               if (offset)
+                       *offset = recv.msg.offset;
+       } else {
+               kdbus_msg_free(msg);
+
+               ret = kdbus_free(conn, recv.msg.offset);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+/*
+ * Returns: 0 on success, negative errno on failure.
+ *
+ * We must return -ETIMEDOUT, -ECONNREST, -EAGAIN and other errors.
+ * We must return the result of kdbus_msg_recv()
+ */
+wur int kdbus_msg_recv_poll(struct kdbus_conn *conn,
+                       int timeout_ms,
+                       struct kdbus_msg **msg_out,
+                       uint64_t *offset)
+{
+       int ret;
+
+       do {
+               struct timeval before, after, diff;
+               struct pollfd fd;
+
+               fd.fd = conn->fd;
+               fd.events = POLLIN | POLLPRI | POLLHUP;
+               fd.revents = 0;
+
+               gettimeofday(&before, NULL);
+               ret = poll(&fd, 1, timeout_ms);
+               gettimeofday(&after, NULL);
+
+               if (ret == 0) {
+                       ret = -ETIMEDOUT;
+                       break;
+               }
+
+               if (ret > 0) {
+                       if (fd.revents & POLLIN)
+                               ret = kdbus_msg_recv(conn, msg_out, offset);
+
+                       if (fd.revents & (POLLHUP | POLLERR))
+                               ret = -ECONNRESET;
+               }
+
+               if (ret == 0 || ret != -EAGAIN)
+                       break;
+
+               timersub(&after, &before, &diff);
+               timeout_ms -= diff.tv_sec * 1000UL +
+                             diff.tv_usec / 1000UL;
+       } while (timeout_ms > 0);
+
+       return ret;
+}
+
+wur int kdbus_free(const struct kdbus_conn *conn, uint64_t offset)
+{
+       struct kdbus_cmd_free cmd_free = {};
+       int ret;
+
+       cmd_free.size = sizeof(cmd_free);
+       cmd_free.offset = offset;
+       cmd_free.flags = 0;
+
+       ret = kdbus_cmd_free(conn->fd, &cmd_free);
+       if (ret < 0) {
+               kdbus_printf("KDBUS_CMD_FREE failed: %d (%m)\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+wur int kdbus_free_msg(struct kdbus_conn const *conn, struct kdbus_msg *msg)
+{
+       return kdbus_free(conn, (uintptr_t)msg - (uintptr_t)conn->buf);
+}
+
+wur int kdbus_name_acquire(struct kdbus_conn *conn,
+                      const char *name, uint64_t *flags)
+{
+       struct kdbus_cmd *cmd_name;
+       size_t name_len = strlen(name) + 1;
+       uint64_t size = sizeof(*cmd_name) + KDBUS_ITEM_SIZE(name_len);
+       struct kdbus_item *item;
+       int ret;
+
+       cmd_name = alloca(size);
+
+       memset(cmd_name, 0, size);
+
+       item = cmd_name->items;
+       item->size = KDBUS_ITEM_HEADER_SIZE + name_len;
+       item->type = KDBUS_ITEM_NAME;
+       strcpy(item->str, name);
+
+       cmd_name->size = size;
+       if (flags)
+               cmd_name->flags = *flags;
+
+       ret = kdbus_cmd_name_acquire(conn->fd, cmd_name);
+       if (ret < 0) {
+               kdbus_printf("error acquiring name: %s\n", strerror(-ret));
+               return ret;
+       }
+
+       kdbus_printf("%s(): flags after call: 0x%llx\n", __func__,
+                    cmd_name->return_flags);
+
+       if (flags)
+               *flags = cmd_name->return_flags;
+
+       return 0;
+}
+
+wur int kdbus_name_release(struct kdbus_conn *conn, const char *name)
+{
+       struct kdbus_cmd *cmd_name;
+       size_t name_len = strlen(name) + 1;
+       uint64_t size = sizeof(*cmd_name) + KDBUS_ITEM_SIZE(name_len);
+       struct kdbus_item *item;
+       int ret;
+
+       cmd_name = alloca(size);
+
+       memset(cmd_name, 0, size);
+
+       item = cmd_name->items;
+       item->size = KDBUS_ITEM_HEADER_SIZE + name_len;
+       item->type = KDBUS_ITEM_NAME;
+       strcpy(item->str, name);
+
+       cmd_name->size = size;
+
+       kdbus_printf("conn %lld giving up name '%s'\n",
+                    (unsigned long long) conn->id, name);
+
+       ret = kdbus_cmd_name_release(conn->fd, cmd_name);
+       if (ret < 0) {
+               kdbus_printf("error releasing name: %s\n", strerror(-ret));
+               return ret;
+       }
+
+       return 0;
+}
+
+wur int kdbus_list(struct kdbus_conn *conn, uint64_t flags)
+{
+       struct kdbus_cmd_list cmd_list = {};
+       struct kdbus_info *list, *name;
+       int ret;
+
+       cmd_list.size = sizeof(cmd_list);
+       cmd_list.flags = flags;
+
+       ret = kdbus_cmd_list(conn->fd, &cmd_list);
+       if (ret < 0) {
+               kdbus_printf("error listing names: %d (%m)\n", ret);
+               return ret;
+       }
+
+       kdbus_printf("REGISTRY:\n");
+       list = (struct kdbus_info *)(conn->buf + cmd_list.offset);
+
+       KDBUS_FOREACH(name, list, cmd_list.list_size) {
+               uint64_t flags = 0;
+               struct kdbus_item *item;
+               const char *n = "MISSING-NAME";
+
+               if (name->size == sizeof(struct kdbus_cmd))
+                       continue;
+
+               KDBUS_ITEM_FOREACH(item, name, items)
+                       if (item->type == KDBUS_ITEM_OWNED_NAME) {
+                               n = item->name.name;
+                               flags = item->name.flags;
+
+                               kdbus_printf("%8llu flags=0x%08llx conn=0x%08llx '%s'\n",
+                                            name->id,
+                                            (unsigned long long) flags,
+                                            name->flags, n);
+                       }
+       }
+       kdbus_printf("\n");
+
+       ret = kdbus_free(conn, cmd_list.offset);
+
+       return ret;
+}
+
+wur int kdbus_conn_update_attach_flags(struct kdbus_conn *conn,
+                                  uint64_t attach_flags_send,
+                                  uint64_t attach_flags_recv)
+{
+       int ret;
+       size_t size;
+       struct kdbus_cmd *update;
+       struct kdbus_item *item;
+
+       size = sizeof(struct kdbus_cmd);
+       size += KDBUS_ITEM_SIZE(sizeof(uint64_t)) * 2;
+
+       update = alloc(size);
+       if (!update) {
+               kdbus_printf("error alloc: %m\n");
+               return -ENOMEM;
+       }
+
+       memset(update, 0, size);
+       update->size = size;
+
+       item = update->items;
+
+       item->type = KDBUS_ITEM_ATTACH_FLAGS_SEND;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(uint64_t);
+       item->data64[0] = attach_flags_send;
+       item = KDBUS_ITEM_NEXT(item);
+
+       item->type = KDBUS_ITEM_ATTACH_FLAGS_RECV;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(uint64_t);
+       item->data64[0] = attach_flags_recv;
+       item = KDBUS_ITEM_NEXT(item);
+
+       ret = kdbus_cmd_update(conn->fd, update);
+       if (ret < 0)
+               kdbus_printf("error conn update: %d (%m)\n", ret);
+
+       free(update);
+       conn->attach_flags_recv = attach_flags_recv;
+
+       return ret;
+}
+
+wur int kdbus_conn_update_policy(struct kdbus_conn *conn, const char *name,
+                            const struct kdbus_policy_access *access,
+                            size_t num_access)
+{
+       struct kdbus_cmd *update;
+       struct kdbus_item *item;
+       size_t i, size;
+       int ret;
+
+       size = sizeof(struct kdbus_cmd);
+       size += KDBUS_ITEM_SIZE(strlen(name) + 1);
+       size += num_access * KDBUS_ITEM_SIZE(sizeof(struct kdbus_policy_access));
+
+       update = alloc(size);
+       if (!update) {
+               kdbus_printf("error alloc: %m\n");
+               return -ENOMEM;
+       }
+
+       memset(update, 0, size);
+       update->size = size;
+
+       item = update->items;
+
+       item->type = KDBUS_ITEM_NAME;
+       item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1;
+       strcpy(item->str, name);
+       item = KDBUS_ITEM_NEXT(item);
+
+       for (i = 0; i < num_access; i++) {
+               item->size = KDBUS_ITEM_HEADER_SIZE +
+                            sizeof(struct kdbus_policy_access);
+               item->type = KDBUS_ITEM_POLICY_ACCESS;
+
+               item->policy_access.type = access[i].type;
+               item->policy_access.access = access[i].access;
+               item->policy_access.id = access[i].id;
+
+               item = KDBUS_ITEM_NEXT(item);
+       }
+
+       ret = kdbus_cmd_update(conn->fd, update);
+       if (ret < 0)
+               kdbus_printf("error conn update: %d (%m)\n", ret);
+
+       free(update);
+
+       return ret;
+}
+
+wur int kdbus_add_match_id(struct kdbus_conn *conn, uint64_t cookie,
+                      uint64_t type, uint64_t id)
+{
+       struct {
+               struct kdbus_cmd_match cmd;
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       struct kdbus_notify_id_change chg;
+               } item;
+       } buf;
+       int ret;
+
+       memset(&buf, 0, sizeof(buf));
+
+       buf.cmd.size = sizeof(buf);
+       buf.cmd.cookie = cookie;
+       buf.item.size = sizeof(buf.item);
+       buf.item.type = type;
+       buf.item.chg.id = id;
+
+       ret = kdbus_cmd_match_add(conn->fd, &buf.cmd);
+       if (ret < 0)
+               kdbus_printf("--- error adding conn match: %d (%m)\n", ret);
+
+       return ret;
+}
+
+wur int kdbus_add_match_empty(struct kdbus_conn *conn)
+{
+       struct {
+               struct kdbus_cmd_match cmd;
+               struct kdbus_item item;
+       } buf;
+       int ret;
+
+       memset(&buf, 0, sizeof(buf));
+
+       buf.item.size = sizeof(uint64_t) * 3;
+       buf.item.type = KDBUS_ITEM_ID;
+       buf.item.id = KDBUS_MATCH_ID_ANY;
+
+       buf.cmd.size = sizeof(buf.cmd) + buf.item.size;
+
+       ret = kdbus_cmd_match_add(conn->fd, &buf.cmd);
+       if (ret < 0)
+               kdbus_printf("--- error adding conn match: %d (%m)\n", ret);
+
+       return ret;
+}
+
+static wur int all_ids_are_mapped(const char *path)
+{
+       int ret;
+       FILE *file;
+       uint32_t inside_id, length;
+
+       file = fopen(path, "r");
+       if (!file) {
+               ret = -errno;
+               kdbus_printf("error fopen() %s: %d (%m)\n",
+                            path, ret);
+               return ret;
+       }
+
+       ret = fscanf(file, "%u\t%*u\t%u", &inside_id, &length);
+       if (ret != 2) {
+               if (ferror(file))
+                       ret = -errno;
+               else
+                       ret = -EIO;
+
+               kdbus_printf("--- error fscanf(): %d\n", ret);
+               fclose(file);
+               return ret;
+       }
+
+       fclose(file);
+
+       /*
+        * If length is 4294967295 which means the invalid uid
+        * (uid_t) -1 then we are able to map all uid/gids
+        */
+       if (inside_id == 0 && length == (uid_t) -1)
+               return 1;
+
+       return 0;
+}
+
+wur int all_uids_gids_are_mapped(void)
+{
+       int ret;
+
+       ret = all_ids_are_mapped("/proc/self/uid_map");
+       if (ret <= 0) {
+               kdbus_printf("--- error not all uids are mapped\n");
+               return 0;
+       }
+
+       ret = all_ids_are_mapped("/proc/self/gid_map");
+       if (ret <= 0) {
+               kdbus_printf("--- error not all gids are mapped\n");
+               return 0;
+       }
+
+       return 1;
+}
+
+wur int drop_privileges(uid_t uid, gid_t gid)
+{
+       int ret;
+
+       ret = setgroups(0, NULL);
+       if (ret < 0) {
+               ret = -errno;
+               kdbus_printf("error setgroups: %d (%m)\n", ret);
+               return ret;
+       }
+
+       ret = setresgid(gid, gid, gid);
+       if (ret < 0) {
+               ret = -errno;
+               kdbus_printf("error setresgid: %d (%m)\n", ret);
+               return ret;
+       }
+
+       ret = setresuid(uid, uid, uid);
+       if (ret < 0) {
+               ret = -errno;
+               kdbus_printf("error setresuid: %d (%m)\n", ret);
+               return ret;
+       }
+
+       return ret;
+}
+
+wur uint64_t now(clockid_t clock)
+{
+       struct timespec spec;
+
+       clock_gettime(clock, &spec);
+       return spec.tv_sec * 1000ULL * 1000ULL * 1000ULL + spec.tv_nsec;
+}
+
+wur char *unique_name(const char *prefix)
+{
+       unsigned int i;
+       uint64_t u_now;
+       char n[17];
+       char *str;
+       int r;
+
+       /*
+        * This returns a random string which is guaranteed to be
+        * globally unique across all calls to unique_name(). We
+        * compose the string as:
+        *   <prefix>-<random>-<time>
+        * With:
+        *   <prefix>: string provided by the caller
+        *   <random>: a random alpha string of 16 characters
+        *   <time>: the current time in micro-seconds since last boot
+        *
+        * The <random> part makes the string always look vastly different,
+        * the <time> part makes sure no two calls return the same string.
+        */
+
+       u_now = now(CLOCK_MONOTONIC);
+
+       for (i = 0; i < sizeof(n) - 1; ++i)
+               n[i] = 'a' + (rand() % ('z' - 'a'));
+       n[sizeof(n) - 1] = 0;
+
+       r = asprintf(&str, "%s-%s-%" PRIu64, prefix, n, u_now);
+       if (r < 0)
+               return NULL;
+
+       return str;
+}
+
+static wur int do_userns_map_id(const char *map_file,
+                           const char *map_id)
+{
+       int ret;
+       int fd;
+       char *map;
+       unsigned int i;
+
+       map = strndupa(map_id, strlen(map_id));
+       if (!map) {
+               ret = -errno;
+               kdbus_printf("error strndupa %s: %d (%m)\n",
+                       map_file, ret);
+               return ret;
+       }
+
+       for (i = 0; i < strlen(map); i++)
+               if (map[i] == ',')
+                       map[i] = '\n';
+
+       fd = open(map_file, O_RDWR);
+       if (fd < 0) {
+               ret = -errno;
+               kdbus_printf("error open %s: %d (%m)\n",
+                       map_file, ret);
+               return ret;
+       }
+
+       ret = write(fd, map, strlen(map));
+       if (ret < 0) {
+               ret = -errno;
+               kdbus_printf("error write to %s: %d (%m)\n",
+                            map_file, ret);
+               goto out;
+       }
+
+       ret = 0;
+
+out:
+       CLOSE(fd);
+       return ret;
+}
+
+wur int userns_map_uid_gid(pid_t pid,
+                      const char *map_uid,
+                      const char *map_gid)
+{
+       int fd, ret;
+       char file_id[128] = {'\0'};
+
+       snprintf(file_id, sizeof(file_id), "/proc/%ld/uid_map",
+                (long) pid);
+
+       ret = do_userns_map_id(file_id, map_uid);
+       if (ret < 0)
+               return ret;
+
+       snprintf(file_id, sizeof(file_id), "/proc/%ld/setgroups",
+                (long) pid);
+
+       fd = open(file_id, O_WRONLY);
+       if (fd >= 0) {
+               ret = write(fd, "deny\n", 5);
+               CLOSE(fd);
+               if (ret != 5)
+                       return ret<0 ? ret : -EIO;
+       }
+
+       snprintf(file_id, sizeof(file_id), "/proc/%ld/gid_map",
+                (long) pid);
+
+       return do_userns_map_id(file_id, map_gid);
+}
+
+static wur int do_cap_get_flag(cap_t caps, cap_value_t cap)
+{
+       int ret;
+       cap_flag_value_t flag_set;
+
+       ret = cap_get_flag(caps, cap, CAP_EFFECTIVE, &flag_set);
+       if (ret < 0) {
+               ret = -errno;
+               kdbus_printf("error cap_get_flag(): %d (%m)\n", ret);
+               return ret;
+       }
+
+       return (flag_set == CAP_SET);
+}
+
+/*
+ * Returns:
+ *  1 in case all the requested effective capabilities are set.
+ *  0 in case we do not have the requested capabilities. This value
+ *    will be used to abort tests with TEST_SKIP
+ *  Negative errno on failure.
+ *
+ *  Terminate args with a negative value.
+ */
+wur int test_is_capable(int cap, ...)
+{
+       int ret;
+       va_list ap;
+       cap_t caps;
+
+       caps = cap_get_proc();
+       if (!caps) {
+               ret = -errno;
+               kdbus_printf("error cap_get_proc(): %d (%m)\n", ret);
+               return ret;
+       }
+
+       ret = do_cap_get_flag(caps, (cap_value_t)cap);
+       if (ret <= 0)
+               goto out;
+
+       va_start(ap, cap);
+       while ((cap = va_arg(ap, int)) > 0) {
+               ret = do_cap_get_flag(caps, (cap_value_t)cap);
+               if (ret <= 0)
+                       break;
+       }
+       va_end(ap);
+
+out:
+       cap_free(caps);
+       return ret;
+}
+
+wur int config_user_ns_is_enabled(void)
+{
+       return (access("/proc/self/uid_map", F_OK) == 0);
+}
+
+wur int config_auditsyscall_is_enabled(void)
+{
+       return (access("/proc/self/loginuid", F_OK) == 0);
+}
+
+wur int config_cgroups_is_enabled(void)
+{
+       return (access("/proc/self/cgroup", F_OK) == 0);
+}
+
+wur int config_security_is_enabled(void)
+{
+       int fd;
+       int ret;
+       char buf[128];
+
+       /* CONFIG_SECURITY is disabled */
+       if (access("/proc/self/attr/current", F_OK) != 0)
+               return 0;
+
+       /*
+        * Now only if read() fails with -EINVAL then we assume
+        * that SECLABEL and LSM are disabled
+        */
+       fd = open("/proc/self/attr/current", O_RDONLY|O_CLOEXEC);
+       if (fd < 0)
+               return 1;
+
+       ret = read(fd, buf, sizeof(buf));
+       if (ret == -1 && errno == EINVAL)
+               ret = 0;
+       else
+               ret = 1;
+
+       CLOSE(fd);
+
+       return ret;
+}
diff --git a/tests/kdbus/kdbus-util.h b/tests/kdbus/kdbus-util.h
new file mode 100644 (file)
index 0000000..654adc6
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2013-2015 Kay Sievers
+ * Copyright (C) 2013-2015 Daniel Mack
+ *
+ * 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.
+ */
+
+#pragma once
+
+#define BIT(X) (1 << (X))
+
+#include <time.h>
+#include <stdbool.h>
+#include <linux/kdbus.h>
+#include <sys/wait.h>
+
+/* backwards-incompatible tizen customizations */
+#define TIZEN
+#ifdef TIZEN
+       #define ONTIZEN(TIZEN,NONTIZEN) TIZEN
+#else
+       #define ONTIZEN(TIZEN,NONTIZEN) NONTIZEN
+#endif
+
+/* c99 compat */
+#define typeof __typeof__
+
+#define wur __attribute__((warn_unused_result))
+
+#define print(...) fprintf(stderr, ##__VA_ARGS__)
+
+#define UNUSED(VAL) do { __auto_type _UNUSED_val_ = (VAL); (void)_UNUSED_val_; } while (0)
+
+#define TABSIZE(T) (sizeof(T)/sizeof(T[0]))
+
+static inline wur void *alloc(size_t size) {
+       void *p;
+       int ret = posix_memalign(&p, 8, size);
+       return ret ? NULL : p;
+}
+
+#define MAX(A,B) ({\
+       typeof(A) __MAX__A__ = (A);\
+       typeof(B) __MAX__B__ = (B);\
+       __MAX__A__<__MAX__B__ ? __MAX__B__ : __MAX__A__;\
+})
+#define MIN(A,B) ({\
+       typeof(A) __MIN__A__ = (A);\
+       typeof(B) __MIN__B__ = (B);\
+       __MIN__A__>__MIN__B__ ? __MIN__B__ : __MIN__A__;\
+})
+
+#define KDBUS_TIMEOUT_INFINITE 0x3fffffffffffffffULL
+
+#define _STRINGIFY(x) #x
+#define STRINGIFY(x) _STRINGIFY(x)
+#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
+
+#define fail(FMT,...) do { print("[" __FILE__ ":" STRINGIFY(__LINE__) "] fail " FMT "\n", ##__VA_ARGS__); exit(2); } while (0)
+
+#define KDBUS_PTR(addr) ((void *)(uintptr_t)(addr))
+
+#define KDBUS_ALIGN8(l) (((l) + 7) & ~7)
+#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data)
+#define KDBUS_ITEM_SIZE(s) KDBUS_ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE)
+
+#define KDBUS_ITEM_NEXT(item) \
+       (typeof(item))((uint8_t *)(item) + KDBUS_ALIGN8((item)->size))
+#define KDBUS_ITEM_FOREACH(item, head, first)                          \
+       for ((item) = (head)->first;                                    \
+            ((uint8_t *)(item) < (uint8_t *)(head) + (head)->size) &&  \
+              ((uint8_t *)(item) >= (uint8_t *)(head));                \
+            (item) = KDBUS_ITEM_NEXT(item))
+#define KDBUS_FOREACH(iter, first, _size)                              \
+       for ((iter) = (first);                                          \
+            ((uint8_t *)(iter) < (uint8_t *)(first) + (_size)) &&      \
+              ((uint8_t *)(iter) >= (uint8_t *)(first));               \
+            (iter) = (void *)((uint8_t *)(iter) + KDBUS_ALIGN8((iter)->size)))
+
+#define _KDBUS_ATTACH_BITS_SET_NR (__builtin_popcountll(_KDBUS_ATTACH_ALL))
+
+/* Sum of KDBUS_ITEM_* that reflects _KDBUS_ATTACH_ALL */
+#define KDBUS_ATTACH_ITEMS_TYPE_SUM                                    \
+       ((((_KDBUS_ATTACH_BITS_SET_NR - 1) *                            \
+       ((_KDBUS_ATTACH_BITS_SET_NR - 1) + 1)) / 2) +                   \
+       (_KDBUS_ITEM_ATTACH_BASE * _KDBUS_ATTACH_BITS_SET_NR))
+
+#define POOL_SIZE (16 * 1024LU * 1024LU)
+
+#define UNPRIV_UID 65534
+#define UNPRIV_GID 65534
+
+/* Dump as user of process, useful for user namespace testing */
+#define SUID_DUMP_USER 1
+
+extern int kdbus_util_verbose;
+
+#define kdbus_printf(X...) \
+       if (kdbus_util_verbose) \
+               print(X)
+
+#define RUN_FORKED(_child_, _parent_) ASSERT_ZERO(({\
+               pid_t pid, rpid;\
+               int ret;\
+               \
+               pid = fork();\
+               if (pid == 0) {\
+                       _child_;\
+                       exit(0);\
+               } else if (pid > 0) {\
+                       _parent_;\
+                       rpid = waitpid(pid, &ret, 0);\
+                       ASSERT_RETURN(rpid,==,pid);\
+                       ASSERT_NONZERO(WIFEXITED(ret));\
+                       ASSERT_ZERO(WEXITSTATUS(ret));\
+                       ret = TEST_OK;\
+               } else {\
+                       ret = pid;\
+               }\
+               \
+               ret;\
+       }))
+
+#define RUN_UNPRIVILEGED(child_uid, child_gid, _child_, _parent_) RUN_FORKED(({\
+               ret = drop_privileges(child_uid, child_gid);\
+               ASSERT_EXIT_VAL(ret,==,0, ret);\
+               _child_;\
+       }), _parent_)
+
+#define RUN_UNPRIVILEGED_CONN(_var_, _bus_, _code_)                    \
+       RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({                     \
+               struct kdbus_conn *_var_;                               \
+               ASSERT_EXIT_NONZERO(_var_ = kdbus_hello(_bus_, 0, NULL, 0));                    \
+               _code_;                                                 \
+               kdbus_conn_free(_var_);                                 \
+       }), ({}))
+
+#define RUN_CLONE_CHILD(clone_ret, flags, _setup_, _child_body_,       \
+                       _parent_setup_, _parent_body_) ({               \
+       pid_t pid, rpid;                                                \
+       int ret;                                                        \
+       int efd = -1;                                                   \
+                                                                       \
+       _setup_;                                                        \
+       efd = eventfd(0, EFD_CLOEXEC);                                  \
+       ASSERT_RETURN(efd,>=,0);                                        \
+       *(clone_ret) = 0;                                               \
+       pid = syscall(__NR_clone, flags, NULL);                         \
+       if (pid == 0) {                                                 \
+               eventfd_t event_status = 0;                             \
+               ASSERT_EXIT_ZERO(prctl(PR_SET_PDEATHSIG, SIGKILL));                     \
+               ret = eventfd_read(efd, &event_status);                 \
+               if (ret < 0 || event_status != 1) {                     \
+                       kdbus_printf("error eventfd_read()\n");         \
+                       exit(EXIT_FAILURE);                             \
+               }                                                       \
+               _child_body_;                                           \
+               exit(0);                                                \
+       } else if (pid > 0) {                                           \
+               _parent_setup_;                                         \
+               ASSERT_RETURN(eventfd_write(efd, 1),>=,0);                              \
+               _parent_body_;                                          \
+               rpid = waitpid(pid, &ret, 0);                           \
+               ASSERT_RETURN(rpid,==,pid);                             \
+               ASSERT_NONZERO(WIFEXITED(ret));                         \
+               ASSERT_ZERO(WEXITSTATUS(ret));                  \
+               ret = TEST_OK;                                          \
+       } else {                                                        \
+               ret = -errno;                                           \
+               *(clone_ret) = -errno;                                  \
+       }                                                               \
+       CLOSE(efd);                                                     \
+       ret;                                                            \
+})
+
+#define ASSERT_NO_PENDING(CONN) do {\
+       struct kdbus_cmd_recv _ASSERT_NO_PENDING_recv_ = { .size = sizeof(_ASSERT_NO_PENDING_recv_) };\
+       int _ASSERT_NO_PENDING_fd_ = (CONN)->fd;\
+       int _ASSERT_NO_PENDING_ret_ = kdbus_cmd_recv(_ASSERT_NO_PENDING_fd_, &_ASSERT_NO_PENDING_recv_);\
+       ASSERT_RETURN(-EAGAIN,==,_ASSERT_NO_PENDING_ret_);\
+} while (0)
+
+#define CLOSE(...) do { __auto_type _CLOSE_fd_ = (__VA_ARGS__); if (close(_CLOSE_fd_)) fail("close(%d) err(%d)", _CLOSE_fd_, errno); } while (0)
+
+/* Enums for parent if it should drop privs or not */
+enum kdbus_drop_parent {
+       DO_NOT_DROP,
+       DROP_SAME_UNPRIV,
+       DROP_OTHER_UNPRIV,
+};
+
+struct kdbus_conn {
+       int fd;
+       uint64_t id;
+       unsigned attach_flags_recv;
+       unsigned char *buf;
+};
+
+wur int kdbus_sysfs_get_parameter_mask(const char *path, uint64_t *mask);
+wur int kdbus_sysfs_set_parameter_mask(const char *path, uint64_t mask);
+
+wur int sys_memfd_create(const char *name, __u64 size);
+wur int sys_memfd_seal_set(int fd);
+wur off_t sys_memfd_get_size(int fd, off_t *size);
+
+wur int kdbus_list(struct kdbus_conn *conn, uint64_t flags);
+wur int kdbus_name_release(struct kdbus_conn *conn, const char *name);
+wur int kdbus_name_acquire(struct kdbus_conn *conn, const char *name,
+                      uint64_t *flags);
+void kdbus_msg_free(struct kdbus_msg *msg);
+wur int kdbus_msg_recv(struct kdbus_conn *conn,
+                  struct kdbus_msg **msg, uint64_t *offset);
+wur int kdbus_msg_recv_poll(struct kdbus_conn *conn, int timeout_ms,
+                       struct kdbus_msg **msg_out, uint64_t *offset);
+wur int kdbus_free(const struct kdbus_conn *conn, uint64_t offset);
+wur int kdbus_free_msg(struct kdbus_conn const *conn, struct kdbus_msg *msg);
+wur int kdbus_msg_dump(const struct kdbus_msg *msg);
+wur int kdbus_create_bus(int control_fd, const char *name,
+                    uint64_t owner_meta, char **path);
+wur int kdbus_msg_send(const struct kdbus_conn *conn, const char *name,
+                  uint64_t cookie, uint64_t flags, uint64_t timeout,
+                  int64_t priority, uint64_t dst_id);
+wur int kdbus_msg_send_sync(const struct kdbus_conn *conn, const char *name,
+                       uint64_t cookie, uint64_t flags, uint64_t timeout,
+                       int64_t priority, uint64_t dst_id, int cancel_fd);
+wur int kdbus_msg_send_reply(const struct kdbus_conn *conn,
+                        uint64_t reply_cookie,
+                        uint64_t dst_id);
+wur struct kdbus_conn *kdbus_hello(const char *path, uint64_t hello_flags,
+                              const struct kdbus_item *item,
+                              size_t item_size);
+wur int timeout_msg_recv(struct kdbus_conn *conn, uint64_t type, uint64_t *cookie_reply, uint64_t *seqnum, uint64_t *monotonic_ns, uint64_t *realtime_ns);
+wur struct kdbus_conn *kdbus_hello_registrar(const char *path, const char *name,
+                                        const struct kdbus_policy_access *access,
+                                        size_t num_access, uint64_t flags);
+wur struct kdbus_conn *kdbus_hello_activator(const char *path, const char *name,
+                                        const struct kdbus_policy_access *access,
+                                        size_t num_access);
+wur bool kdbus_item_in_message(struct kdbus_msg *msg, uint64_t type);
+wur int kdbus_bus_creator_info(struct kdbus_conn *conn,
+                          uint64_t flags,
+                          uint64_t *offset);
+wur int kdbus_conn_info(struct kdbus_conn *conn, uint64_t id,
+                   const char *name, uint64_t flags, uint64_t *offset);
+void kdbus_conn_free(struct kdbus_conn *conn);
+wur int kdbus_conn_update_attach_flags(struct kdbus_conn *conn,
+                                  uint64_t attach_flags_send,
+                                  uint64_t attach_flags_recv);
+wur int kdbus_conn_update_policy(struct kdbus_conn *conn, const char *name,
+                            const struct kdbus_policy_access *access,
+                            size_t num_access);
+
+wur int kdbus_add_match_id(struct kdbus_conn *conn, uint64_t cookie,
+                      uint64_t type, uint64_t id);
+wur int kdbus_add_match_empty(struct kdbus_conn *conn);
+
+wur int all_uids_gids_are_mapped(void);
+wur int drop_privileges(uid_t uid, gid_t gid);
+wur uint64_t now(clockid_t clock);
+wur char *unique_name(const char *prefix);
+
+wur int userns_map_uid_gid(pid_t pid, const char *map_uid, const char *map_gid);
+wur int test_is_capable(int cap, ...);
+wur int config_user_ns_is_enabled(void);
+wur int config_auditsyscall_is_enabled(void);
+wur int config_cgroups_is_enabled(void);
+wur int config_security_is_enabled(void);
\ No newline at end of file
diff --git a/tests/kdbus/test-activator.c b/tests/kdbus/test-activator.c
new file mode 100644 (file)
index 0000000..f2a401c
--- /dev/null
@@ -0,0 +1,291 @@
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <poll.h>
+#include <sys/capability.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+static wur int kdbus_starter_poll(struct kdbus_conn *conn)
+{
+       int ret;
+       struct pollfd fd;
+
+       fd.fd = conn->fd;
+       fd.events = POLLIN | POLLPRI | POLLHUP;
+       fd.revents = 0;
+
+       ret = poll(&fd, 1, 100);
+       if (ret == 0)
+               return -ETIMEDOUT;
+       else if (ret > 0) {
+               if (fd.revents & POLLIN)
+                       return 0;
+
+               if (fd.revents & (POLLHUP | POLLERR))
+                       ret = -ECONNRESET;
+       }
+
+       return ret;
+}
+
+/* Ensure that kdbus activator logic is safe */
+static wur int kdbus_priv_activator(struct kdbus_test_env *env)
+{
+       struct kdbus_msg *msg = NULL;
+       uint64_t cookie = 0xdeadbeef;
+       uint64_t flags;
+       struct kdbus_conn *activator;
+       struct kdbus_conn *service;
+       struct kdbus_conn *client;
+       struct kdbus_conn *holder;
+       struct kdbus_policy_access *access;
+
+       access = (struct kdbus_policy_access[]){
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = getuid(),
+                       .access = KDBUS_POLICY_OWN,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = getuid(),
+                       .access = KDBUS_POLICY_TALK,
+               },
+       };
+
+       activator = kdbus_hello_activator(env->buspath, "foo.priv.activator",
+                                         access, 2);
+       ASSERT_NONZERO(activator);
+
+       service = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_NONZERO(service);
+
+       client = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_NONZERO(client);
+
+       /*
+        * Make sure that other users can't TALK to the activator
+        */
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               /* Try to talk using the ID */
+               ret = kdbus_msg_send(unpriv, NULL, 0xdeadbeef, 0, 0,
+                                    0, activator->id);
+               ASSERT_EXIT(ret,==,-ENXIO);
+
+               /* Try to talk to the name */
+               ret = kdbus_msg_send(unpriv, "foo.priv.activator",
+                                    0xdeadbeef, 0, 0, 0,
+                                    KDBUS_DST_ID_NAME);
+               ASSERT_EXIT(ret,==,ONTIZEN(0,-EPERM));
+       }));
+
+       /*
+        * Make sure that we did not receive anything, so the
+        * service will not be started automatically
+        */
+
+       ASSERT_RETURN(ONTIZEN(0,-ETIMEDOUT),==,kdbus_starter_poll(activator));
+
+       /*
+        * Now try to emulate the starter/service logic and
+        * acquire the name.
+        */
+
+       cookie++;
+       ASSERT_ZERO(kdbus_msg_send(service, "foo.priv.activator", cookie, 0, 0, 0, KDBUS_DST_ID_NAME));
+
+       ASSERT_ZERO(kdbus_starter_poll(activator));
+
+       /* Policies are still checked, access denied */
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               flags = KDBUS_NAME_REPLACE_EXISTING;
+               ASSERT_RETURN(ONTIZEN(0,-EPERM),==,kdbus_name_acquire(unpriv, "foo.priv.activator", &flags));
+       }));
+
+       flags = KDBUS_NAME_REPLACE_EXISTING;
+       ASSERT_ZERO(kdbus_name_acquire(service, "foo.priv.activator", &flags));
+
+#ifdef TIZEN
+       ASSERT_ZERO(kdbus_msg_recv_poll(service, 100, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie-1);
+       kdbus_msg_free(msg);
+#endif
+
+       ASSERT_ZERO(kdbus_msg_recv_poll(service, 100, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+       kdbus_msg_free(msg);
+
+       /* Try to talk, we still fail */
+
+       cookie++;
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               /* Try to talk to the name */
+               ASSERT_EXIT(ONTIZEN(0,-EPERM),==,kdbus_msg_send(unpriv, "foo.priv.activator", cookie, 0, 0, 0, KDBUS_DST_ID_NAME));
+       }));
+#ifdef TIZEN
+       ASSERT_ZERO(kdbus_msg_recv_poll(service, 100, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+       kdbus_msg_free(msg);
+#endif
+
+       /* Still nothing to read */
+
+       ASSERT_RETURN(-ETIMEDOUT,==,kdbus_msg_recv_poll(service, 100, NULL, NULL));
+
+       /* We receive every thing now */
+
+       cookie++;
+       ASSERT_ZERO(kdbus_msg_send(client, "foo.priv.activator", cookie, 0, 0, 0, KDBUS_DST_ID_NAME));
+       ASSERT_ZERO(kdbus_msg_recv_poll(service, 100, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       kdbus_msg_free(msg);
+
+       /* Policies default to deny TALK now */
+       kdbus_conn_free(activator);
+
+       cookie++;
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               /* Try to talk to the name */
+               ASSERT_EXIT(ONTIZEN(0,-EPERM),==,kdbus_msg_send(unpriv, "foo.priv.activator", cookie, 0, 0, 0, KDBUS_DST_ID_NAME));
+       }));
+
+       ASSERT_RETURN(ONTIZEN(0,-ETIMEDOUT),==,kdbus_msg_recv_poll(service, 100, &msg, NULL));
+#ifdef TIZEN
+       ASSERT_RETURN(msg->cookie,==,cookie);
+       kdbus_msg_free(msg);
+#endif
+
+       /* Same user is able to TALK */
+       cookie++;
+       ASSERT_ZERO(kdbus_msg_send(client, "foo.priv.activator", cookie, 0, 0, 0, KDBUS_DST_ID_NAME));
+       ASSERT_ZERO(kdbus_msg_recv_poll(service, 100, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       kdbus_msg_free(msg);
+
+       access = (struct kdbus_policy_access []){
+               {
+                       .type = KDBUS_POLICY_ACCESS_WORLD,
+                       .id = getuid(),
+                       .access = KDBUS_POLICY_TALK,
+               },
+       };
+
+       holder = kdbus_hello_registrar(env->buspath, "foo.priv.activator",
+                                      access, 1, KDBUS_HELLO_POLICY_HOLDER);
+       ASSERT_NONZERO(holder);
+
+       /* Now we are able to TALK to the name */
+
+       cookie++;
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               /* Try to talk to the name */
+               ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv, "foo.priv.activator", cookie, 0, 0, 0, KDBUS_DST_ID_NAME));
+       }));
+
+       ASSERT_ZERO(kdbus_msg_recv_poll(service, 100, NULL, NULL));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               flags = KDBUS_NAME_REPLACE_EXISTING;
+               ASSERT_EXIT(ONTIZEN(-EEXIST,-EPERM),==,kdbus_name_acquire(unpriv, "foo.priv.activator", &flags));
+       }));
+
+       kdbus_conn_free(service);
+       kdbus_conn_free(client);
+       kdbus_conn_free(holder);
+
+       return 0;
+}
+
+wur int kdbus_test_activator(struct kdbus_test_env *env)
+{
+       int ret;
+       struct kdbus_conn *activator;
+       struct pollfd fds[2];
+       bool activator_done = false;
+       struct kdbus_policy_access access[2];
+
+       access[0].type = KDBUS_POLICY_ACCESS_USER;
+       access[0].id = getuid();
+       access[0].access = KDBUS_POLICY_OWN;
+
+       access[1].type = KDBUS_POLICY_ACCESS_WORLD;
+       access[1].access = KDBUS_POLICY_TALK;
+
+       activator = kdbus_hello_activator(env->buspath, "foo.test.activator",
+                                         access, 2);
+       ASSERT_NONZERO(activator);
+
+       ASSERT_ZERO(kdbus_add_match_empty(env->conn));
+
+       ASSERT_ZERO(kdbus_list(env->conn, KDBUS_LIST_NAMES | KDBUS_LIST_UNIQUE | KDBUS_LIST_ACTIVATORS | KDBUS_LIST_QUEUED));
+
+       ASSERT_ZERO(kdbus_msg_send(env->conn, "foo.test.activator", 0xdeafbeef, 0, 0, 0, KDBUS_DST_ID_NAME));
+
+       fds[0].fd = activator->fd;
+       fds[1].fd = env->conn->fd;
+
+       kdbus_printf("-- entering poll loop ...\n");
+
+       for (;;) {
+               int i, nfds = sizeof(fds) / sizeof(fds[0]);
+
+               for (i = 0; i < nfds; i++) {
+                       fds[i].events = POLLIN | POLLPRI;
+                       fds[i].revents = 0;
+               }
+
+               ret = poll(fds, nfds, 3000);
+               ASSERT_RETURN(ret,>=,0);
+
+               ASSERT_ZERO(kdbus_list(env->conn, KDBUS_LIST_NAMES));
+
+               if ((fds[0].revents & POLLIN) && !activator_done) {
+                       uint64_t flags = KDBUS_NAME_REPLACE_EXISTING;
+
+                       kdbus_printf("Starter was called back!\n");
+
+                       ASSERT_ZERO(kdbus_name_acquire(env->conn, "foo.test.activator", &flags));
+
+                       activator_done = true;
+               }
+
+               if (fds[1].revents & POLLIN) {
+                       ASSERT_ZERO(kdbus_msg_recv(env->conn, NULL, NULL));
+                       break;
+               }
+       }
+
+       /* Check if all uids/gids are mapped */
+       if (!all_uids_gids_are_mapped())
+               return TEST_SKIP;
+
+       /* Check now capabilities, so we run the previous tests */
+       ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1);
+       ASSERT_RETURN(ret,>=,0);
+
+       if (!ret)
+               return TEST_SKIP;
+
+       ASSERT_ZERO(kdbus_priv_activator(env));
+
+       kdbus_conn_free(activator);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-benchmark.c b/tests/kdbus/test-benchmark.c
new file mode 100644 (file)
index 0000000..e53ebb5
--- /dev/null
@@ -0,0 +1,429 @@
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <assert.h>
+#include <poll.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <math.h>
+
+#include "kdbus-api.h"
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+#define SERVICE_NAME "foo.bar.echo"
+
+/*
+ * To have a banchmark comparison with unix socket, set:
+ * user_memfd  = false;
+ * compare_uds = true;
+ * attach_none = true;         do not attached metadata
+ */
+
+static bool use_memfd = true;          /* transmit memfd? */
+static bool compare_uds = false;               /* unix-socket comparison? */
+static bool attach_none = false;               /* clear attach-flags? */
+static char stress_payload[8192];
+
+struct stats {
+       uint64_t count;
+       uint64_t latency_acc;
+       uint64_t latency_low;
+       uint64_t latency_high;
+       uint64_t latency_avg;
+       uint64_t latency_ssquares;
+};
+
+static struct stats stats;
+
+static void reset_stats(void)
+{
+       stats.count = 0;
+       stats.latency_acc = 0;
+       stats.latency_low = UINT64_MAX;
+       stats.latency_high = 0;
+       stats.latency_avg = 0;
+       stats.latency_ssquares = 0;
+}
+
+static void dump_stats(bool is_uds)
+{
+       if (stats.count > 0) {
+               kdbus_printf("stats %s: %'llu packets processed, latency (nsecs) min/max/avg/dev %'7llu // %'7llu // %'7llu // %'7.f\n",
+                            is_uds ? " (UNIX)" : "(KDBUS)",
+                            (unsigned long long) stats.count,
+                            (unsigned long long) stats.latency_low,
+                            (unsigned long long) stats.latency_high,
+                            (unsigned long long) stats.latency_avg,
+                            sqrt(stats.latency_ssquares / stats.count));
+       } else {
+               kdbus_printf("*** no packets received. bus stuck?\n");
+       }
+}
+
+static void add_stats(uint64_t prev)
+{
+       uint64_t diff, latency_avg_prev;
+
+       diff = now(CLOCK_THREAD_CPUTIME_ID) - prev;
+
+       stats.count++;
+       stats.latency_acc += diff;
+
+       /* see Welford62 */
+       latency_avg_prev = stats.latency_avg;
+       stats.latency_avg = stats.latency_acc / stats.count;
+       stats.latency_ssquares += (diff - latency_avg_prev) * (diff - stats.latency_avg);
+
+       if (stats.latency_low > diff)
+               stats.latency_low = diff;
+
+       if (stats.latency_high < diff)
+               stats.latency_high = diff;
+}
+
+static wur int setup_simple_kdbus_msg(struct kdbus_conn *conn,
+                                 uint64_t dst_id,
+                                 struct kdbus_msg **msg_out)
+{
+       struct kdbus_msg *msg;
+       struct kdbus_item *item;
+       uint64_t size;
+
+       size = sizeof(struct kdbus_msg);
+       size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
+
+       msg = alloc(size);
+       ASSERT_RETURN_VAL(msg,!=,NULL, -ENOMEM);
+
+       memset(msg, 0, size);
+       msg->size = size;
+       msg->src_id = conn->id;
+       msg->dst_id = dst_id;
+       msg->payload_type = KDBUS_PAYLOAD_DBUS;
+
+       item = msg->items;
+
+       item->type = KDBUS_ITEM_PAYLOAD_VEC;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec);
+       item->vec.address = (uintptr_t) stress_payload;
+       item->vec.size = sizeof(stress_payload);
+       item = KDBUS_ITEM_NEXT(item);
+
+       *msg_out = msg;
+
+       return 0;
+}
+
+static wur int setup_memfd_kdbus_msg(struct kdbus_conn *conn,
+                                uint64_t dst_id,
+                                off_t *memfd_item_offset,
+                                struct kdbus_msg **msg_out)
+{
+       struct kdbus_msg *msg;
+       struct kdbus_item *item;
+       uint64_t size;
+
+       size = sizeof(struct kdbus_msg);
+       size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
+       size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd));
+
+       msg = alloc(size);
+       ASSERT_RETURN_VAL(msg,!=,NULL, -ENOMEM);
+
+       memset(msg, 0, size);
+       msg->size = size;
+       msg->src_id = conn->id;
+       msg->dst_id = dst_id;
+       msg->payload_type = KDBUS_PAYLOAD_DBUS;
+
+       item = msg->items;
+
+       item->type = KDBUS_ITEM_PAYLOAD_VEC;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec);
+       item->vec.address = (uintptr_t) stress_payload;
+       item->vec.size = sizeof(stress_payload);
+       item = KDBUS_ITEM_NEXT(item);
+
+       item->type = KDBUS_ITEM_PAYLOAD_MEMFD;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_memfd);
+       item->memfd.size = sizeof(uint64_t);
+
+       *memfd_item_offset = (unsigned char *)item - (unsigned char *)msg;
+       *msg_out = msg;
+
+       return 0;
+}
+
+static wur int
+send_echo_request(struct kdbus_conn *conn, void *kdbus_msg, off_t memfd_item_offset)
+{
+       struct kdbus_cmd_send cmd = {};
+       int memfd = -1;
+       int ret;
+
+       if (use_memfd) {
+               uint64_t now_ns = now(CLOCK_THREAD_CPUTIME_ID);
+               struct kdbus_item *item = memfd_item_offset + kdbus_msg;
+               memfd = sys_memfd_create("memfd-name", 0);
+               ASSERT_RETURN_VAL(memfd,>=,0, memfd);
+
+               ASSERT_RETURN_VAL(write(memfd, &now_ns, sizeof(now_ns)),==,(typeof(write(memfd, &now_ns, sizeof(now_ns))))sizeof(now_ns), -EAGAIN);
+
+               ASSERT_RETURN_VAL(sys_memfd_seal_set(memfd),==,0, -errno);
+
+               item->memfd.fd = memfd;
+       }
+
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)kdbus_msg;
+
+       ret = kdbus_cmd_send(conn->fd, &cmd);
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       if (-1 != memfd)
+               CLOSE(memfd);
+
+       return 0;
+}
+
+static wur int
+handle_echo_reply(struct kdbus_conn *conn, uint64_t send_ns)
+{
+       int ret;
+       struct kdbus_cmd_recv recv = { .size = sizeof(recv) };
+       struct kdbus_msg *msg;
+       const struct kdbus_item *item;
+       bool has_memfd = false;
+
+       ret = kdbus_cmd_recv(conn->fd, &recv);
+       if (ret == -EAGAIN)
+               return ret;
+
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       if (!use_memfd)
+               goto out;
+
+       msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset);
+
+       KDBUS_ITEM_FOREACH(item, msg, items) {
+               switch (item->type) {
+               case KDBUS_ITEM_PAYLOAD_MEMFD: {
+                       char *buf;
+
+                       buf = mmap(NULL, item->memfd.size, PROT_READ,
+                                  MAP_PRIVATE, item->memfd.fd, 0);
+                       if (MAP_FAILED == buf)
+                               print("memfdfail_item->memfd.size(%llu)_item->memfd.fd(%d)", (unsigned long long)item->memfd.size, item->memfd.fd);
+                       ASSERT_RETURN_VAL(buf,!=,MAP_FAILED, -EINVAL);
+                       ASSERT_RETURN_VAL(item->memfd.size,==,sizeof(uint64_t), -EINVAL);
+
+                       add_stats(*(uint64_t*)buf);
+                       munmap(buf, item->memfd.size);
+                       CLOSE(item->memfd.fd);
+                       has_memfd = true;
+                       break;
+               }
+
+               case KDBUS_ITEM_PAYLOAD_OFF:
+                       /* ignore */
+                       break;
+               }
+       }
+
+out:
+       if (!has_memfd)
+               add_stats(send_ns);
+
+       ret = kdbus_free(conn, recv.msg.offset);
+       ASSERT_RETURN_VAL(ret,==,0, -errno);
+
+       return 0;
+}
+
+static wur int benchmark(struct kdbus_test_env *env)
+{
+       static char buf[sizeof(stress_payload)];
+       struct kdbus_msg *kdbus_msg = NULL;
+       off_t memfd_cached_offset = 0;
+       int ret;
+       struct kdbus_conn *conn_a, *conn_b;
+       struct pollfd fds[2];
+       uint64_t start, send_ns, now_ns, diff;
+       unsigned int i;
+       int uds[2];
+
+       setlocale(LC_ALL, "");
+
+       for (i = 0; i < sizeof(stress_payload); i++)
+               stress_payload[i] = i;
+
+       /* setup kdbus pair */
+
+       conn_a = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_NONZERO(conn_a);
+       conn_b = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_NONZERO(conn_b);
+
+       ASSERT_ZERO(kdbus_add_match_empty(conn_a));
+
+       ASSERT_ZERO(kdbus_add_match_empty(conn_b));
+
+       ASSERT_ZERO(kdbus_name_acquire(conn_a, SERVICE_NAME, NULL));
+
+       if (attach_none)
+               ASSERT_ZERO(kdbus_conn_update_attach_flags(conn_a, _KDBUS_ATTACH_ALL, 0));
+
+       /* setup UDS pair */
+
+       ASSERT_ZERO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, uds));
+
+       /* setup a kdbus msg now */
+       if (use_memfd)
+               ASSERT_ZERO(setup_memfd_kdbus_msg(conn_b, conn_a->id, &memfd_cached_offset, &kdbus_msg));
+       else
+               ASSERT_ZERO(setup_simple_kdbus_msg(conn_b, conn_a->id, &kdbus_msg));
+
+       /* start benchmark */
+
+       kdbus_printf("-- entering poll loop ...\n");
+
+       ASSERT_NO_PENDING(conn_a);
+
+       do {
+               /* run kdbus benchmark */
+               fds[0].fd = conn_a->fd;
+               fds[1].fd = conn_b->fd;
+
+               start = now(CLOCK_THREAD_CPUTIME_ID);
+               reset_stats();
+
+               send_ns = now(CLOCK_THREAD_CPUTIME_ID);
+               ASSERT_ZERO(send_echo_request(conn_b, kdbus_msg, memfd_cached_offset));
+
+               while (1) {
+                       unsigned int nfds = sizeof(fds) / sizeof(fds[0]);
+                       unsigned int i;
+
+                       for (i = 0; i < nfds; i++) {
+                               fds[i].events = POLLIN | POLLPRI | POLLHUP;
+                               fds[i].revents = 0;
+                       }
+
+                       ret = poll(fds, nfds, 10);
+                       if (ret < 0)
+                               break;
+
+                       if (fds[0].revents & POLLIN) {
+                               ASSERT_ZERO(handle_echo_reply(conn_a, send_ns));
+
+                               send_ns = now(CLOCK_THREAD_CPUTIME_ID);
+                               ASSERT_ZERO(send_echo_request(conn_b, kdbus_msg, memfd_cached_offset));
+                       }
+
+                       now_ns = now(CLOCK_THREAD_CPUTIME_ID);
+                       diff = now_ns - start;
+                       if (diff > 1000000000ULL) {
+                               start = now_ns;
+
+                               dump_stats(false);
+                               ASSERT_ZERO(handle_echo_reply(conn_a, send_ns)); /* purge last message to prevent leaks */
+                               break;
+                       }
+               }
+
+               if (!compare_uds)
+                       continue;
+
+               /* run unix-socket benchmark as comparison */
+
+               fds[0].fd = uds[0];
+               fds[1].fd = uds[1];
+
+               /* cancel any pending message */
+               UNUSED(read(uds[1], buf, sizeof(buf)));
+
+               start = now(CLOCK_THREAD_CPUTIME_ID);
+               reset_stats();
+
+               send_ns = now(CLOCK_THREAD_CPUTIME_ID);
+               ASSERT_RETURN((typeof(write(uds[0], stress_payload, sizeof(stress_payload))))sizeof(stress_payload),==,write(uds[0], stress_payload, sizeof(stress_payload)));
+
+               while (1) {
+                       unsigned int nfds = sizeof(fds) / sizeof(fds[0]);
+                       unsigned int i;
+
+                       for (i = 0; i < nfds; i++) {
+                               fds[i].events = POLLIN | POLLPRI | POLLHUP;
+                               fds[i].revents = 0;
+                       }
+
+                       ret = poll(fds, nfds, 10);
+                       if (ret < 0)
+                               break;
+
+                       if (fds[1].revents & POLLIN) {
+                               ASSERT_RETURN((typeof(read(uds[1], buf, sizeof(buf))))sizeof(buf),==,read(uds[1], buf, sizeof(buf)));
+
+                               add_stats(send_ns);
+
+                               send_ns = now(CLOCK_THREAD_CPUTIME_ID);
+                               ASSERT_RETURN((typeof(write(uds[0], buf, sizeof(buf))))sizeof(buf),==,write(uds[0], buf, sizeof(buf)));
+                       }
+
+                       now_ns = now(CLOCK_THREAD_CPUTIME_ID);
+                       diff = now_ns - start;
+                       if (diff > 1000000000ULL) {
+                               start = now_ns;
+
+                               dump_stats(true);
+                               break;
+                       }
+               }
+
+       } while (kdbus_util_verbose);
+
+       kdbus_printf("-- closing bus connections\n");
+
+       free(kdbus_msg);
+
+       kdbus_conn_free(conn_a);
+       kdbus_conn_free(conn_b);
+
+       return (stats.count > 1) ? TEST_OK : TEST_ERR;
+}
+
+wur int kdbus_test_benchmark(struct kdbus_test_env *env)
+{
+       use_memfd = true;
+       attach_none = false;
+       compare_uds = false;
+       return benchmark(env);
+}
+
+wur int kdbus_test_benchmark_nomemfds(struct kdbus_test_env *env)
+{
+       use_memfd = false;
+       attach_none = false;
+       compare_uds = false;
+       return benchmark(env);
+}
+
+wur int kdbus_test_benchmark_uds(struct kdbus_test_env *env)
+{
+       use_memfd = false;
+       attach_none = true;
+       compare_uds = true;
+       return benchmark(env);
+}
diff --git a/tests/kdbus/test-bus.c b/tests/kdbus/test-bus.c
new file mode 100644 (file)
index 0000000..85fb222
--- /dev/null
@@ -0,0 +1,166 @@
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <limits.h>
+#include <sys/mman.h>
+#include <stdbool.h>
+
+#include "kdbus-api.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+#include "kdbus-test.h"
+
+static wur struct kdbus_item *kdbus_get_item(struct kdbus_info *info,
+                                        uint64_t type)
+{
+       struct kdbus_item *item;
+
+       KDBUS_ITEM_FOREACH(item, info, items)
+               if (item->type == type)
+                       return item;
+
+       return NULL;
+}
+
+static wur int test_bus_creator_info(const char *bus_path)
+{
+       int ret;
+       uint64_t offset;
+       struct kdbus_conn *conn;
+       struct kdbus_info *info;
+       struct kdbus_item *item;
+       char *tmp, *busname;
+
+       /* extract the bus-name from @bus_path */
+       tmp = strdup(bus_path);
+       ASSERT_NONZERO(tmp);
+       busname = strrchr(tmp, '/');
+       ASSERT_NONZERO(busname);
+       *busname = 0;
+       busname = strrchr(tmp, '/');
+       ASSERT_NONZERO(busname);
+       ++busname;
+
+       conn = kdbus_hello(bus_path, 0, NULL, 0);
+       ASSERT_NONZERO(conn);
+
+       ASSERT_ZERO(kdbus_bus_creator_info(conn, _KDBUS_ATTACH_ALL, &offset));
+
+       info = (struct kdbus_info *)(conn->buf + offset);
+
+       item = kdbus_get_item(info, KDBUS_ITEM_MAKE_NAME);
+       ASSERT_NONZERO(item);
+       ASSERT_ZERO(strcmp(item->str, busname));
+
+       ret = kdbus_free(conn, offset);
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       free(tmp);
+       kdbus_conn_free(conn);
+       return 0;
+}
+
+wur int kdbus_test_bus_make(struct kdbus_test_env *env)
+{
+       struct {
+               struct kdbus_cmd cmd;
+
+               /* bloom size item */
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       struct kdbus_bloom_parameter bloom;
+               } bs;
+
+               /* name item */
+               uint64_t n_size;
+               uint64_t n_type;
+               char name[64];
+       } bus_make;
+       char s[PATH_MAX], *name;
+       int control_fd2;
+       uid_t uid;
+
+       name = unique_name("");
+       ASSERT_NONZERO(name);
+
+       snprintf(s, sizeof(s), "%s/control", env->root);
+       env->control_fd = open(s, O_RDWR|O_CLOEXEC);
+       ASSERT_RETURN(env->control_fd,>=,0);
+
+       control_fd2 = open(s, O_RDWR|O_CLOEXEC);
+       ASSERT_RETURN(control_fd2,>=,0);
+
+       memset(&bus_make, 0, sizeof(bus_make));
+
+       bus_make.bs.size = sizeof(bus_make.bs);
+       bus_make.bs.type = KDBUS_ITEM_BLOOM_PARAMETER;
+       bus_make.bs.bloom.size = 64;
+       bus_make.bs.bloom.n_hash = 1;
+
+       bus_make.n_type = KDBUS_ITEM_MAKE_NAME;
+
+       uid = getuid();
+
+       /* missing uid prefix */
+       snprintf(bus_make.name, sizeof(bus_make.name), "foo");
+       bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1;
+       bus_make.cmd.size = sizeof(struct kdbus_cmd) +
+                           sizeof(bus_make.bs) + bus_make.n_size;
+       ASSERT_RETURN(-EINVAL,==,kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd));
+
+       /* non alphanumeric character */
+       snprintf(bus_make.name, sizeof(bus_make.name), "%u-blah@123", uid);
+       bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1;
+       bus_make.cmd.size = sizeof(struct kdbus_cmd) +
+                           sizeof(bus_make.bs) + bus_make.n_size;
+       ASSERT_RETURN(-EINVAL,==,kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd));
+
+       /* '-' at the end */
+       snprintf(bus_make.name, sizeof(bus_make.name), "%u-blah-", uid);
+       bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1;
+       bus_make.cmd.size = sizeof(struct kdbus_cmd) +
+                           sizeof(bus_make.bs) + bus_make.n_size;
+       ASSERT_RETURN(-EINVAL,==,kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd));
+
+       /* create a new bus */
+       snprintf(bus_make.name, sizeof(bus_make.name), "%u-%s-1", uid, name);
+       bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1;
+       bus_make.cmd.size = sizeof(struct kdbus_cmd) +
+                           sizeof(bus_make.bs) + bus_make.n_size;
+       ASSERT_ZERO(kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd));
+
+       ASSERT_RETURN(-EEXIST,==,kdbus_cmd_bus_make(control_fd2, &bus_make.cmd));
+
+       snprintf(s, sizeof(s), "%s/%u-%s-1/bus", env->root, uid, name);
+       ASSERT_ZERO(access(s, F_OK));
+
+       ASSERT_ZERO(test_bus_creator_info(s));
+
+       /* can't use the same fd for bus make twice, even though a different
+        * bus name is used
+        */
+       snprintf(bus_make.name, sizeof(bus_make.name), "%u-%s-2", uid, name);
+       bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1;
+       bus_make.cmd.size = sizeof(struct kdbus_cmd) +
+                           sizeof(bus_make.bs) + bus_make.n_size;
+       ASSERT_RETURN(-EBADFD,==,kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd));
+
+       /* create a new bus, with different fd and different bus name */
+       snprintf(bus_make.name, sizeof(bus_make.name), "%u-%s-2", uid, name);
+       bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1;
+       bus_make.cmd.size = sizeof(struct kdbus_cmd) +
+                           sizeof(bus_make.bs) + bus_make.n_size;
+       ASSERT_ZERO(kdbus_cmd_bus_make(control_fd2, &bus_make.cmd));
+
+       CLOSE(control_fd2);
+       free(name);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-chat.c b/tests/kdbus/test-chat.c
new file mode 100644 (file)
index 0000000..fe6eea7
--- /dev/null
@@ -0,0 +1,98 @@
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <poll.h>
+#include <stdbool.h>
+
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+wur int kdbus_test_chat(struct kdbus_test_env *env)
+{
+       int ret, cookie;
+       struct kdbus_conn *conn_a, *conn_b;
+       struct pollfd fds[2];
+       uint64_t flags;
+       int count;
+
+       conn_a = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_NONZERO(conn_a);
+       conn_b = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_NONZERO(conn_b);
+
+       flags = KDBUS_NAME_ALLOW_REPLACEMENT;
+       ASSERT_ZERO(kdbus_name_acquire(conn_a, "foo.bar.test", &flags));
+
+       ASSERT_ZERO(kdbus_name_acquire(conn_a, "foo.bar.baz", NULL));
+
+       flags = KDBUS_NAME_QUEUE;
+       ASSERT_ZERO(kdbus_name_acquire(conn_b, "foo.bar.baz", &flags));
+
+       ASSERT_ZERO(kdbus_name_acquire(conn_a, "foo.bar.double", NULL));
+
+       flags = 0;
+       ASSERT_ZERO(kdbus_name_acquire(conn_a, "foo.bar.double", &flags));
+       ASSERT_ZERO(flags & KDBUS_NAME_ACQUIRED);
+
+       ASSERT_ZERO(kdbus_name_release(conn_a, "foo.bar.double"));
+
+       ASSERT_RETURN(-ESRCH,==,kdbus_name_release(conn_a, "foo.bar.double"));
+
+       ASSERT_ZERO(kdbus_list(conn_b, KDBUS_LIST_UNIQUE | KDBUS_LIST_NAMES  | KDBUS_LIST_QUEUED | KDBUS_LIST_ACTIVATORS));
+
+       ASSERT_ZERO(kdbus_add_match_empty(conn_a));
+
+       ASSERT_ZERO(kdbus_add_match_empty(conn_b));
+
+       cookie = 0;
+       ASSERT_ZERO(kdbus_msg_send(conn_b, NULL, 0xc0000000 | cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+       fds[0].fd = conn_a->fd;
+       fds[1].fd = conn_b->fd;
+
+       kdbus_printf("-- entering poll loop ...\n");
+
+       for (count = 0;; count++) {
+               int i, nfds = sizeof(fds) / sizeof(fds[0]);
+
+               for (i = 0; i < nfds; i++) {
+                       fds[i].events = POLLIN | POLLPRI | POLLHUP;
+                       fds[i].revents = 0;
+               }
+
+               ret = poll(fds, nfds, 3000);
+               ASSERT_RETURN(ret,>=,0);
+
+               if (fds[0].revents & POLLIN) {
+                       if (count == 3)
+                               ASSERT_ZERO(kdbus_name_release(conn_a, "foo.bar.baz"));
+
+                       ASSERT_ZERO(kdbus_msg_recv(conn_a, NULL, NULL));
+                       ASSERT_ZERO(kdbus_msg_send(conn_a, NULL, 0xc0000000 | cookie++, 0, 0, 0, conn_b->id));
+               }
+
+               if (fds[1].revents & POLLIN) {
+                       ASSERT_ZERO(kdbus_msg_recv(conn_b, NULL, NULL));
+                       ASSERT_ZERO(kdbus_msg_send(conn_b, NULL, 0xc0000000 | cookie++, 0, 0, 0, conn_a->id));
+               }
+
+               ASSERT_ZERO(kdbus_list(conn_b, KDBUS_LIST_UNIQUE | KDBUS_LIST_NAMES  | KDBUS_LIST_QUEUED | KDBUS_LIST_ACTIVATORS));
+
+               if (count > 10)
+                       break;
+       }
+
+       kdbus_printf("-- closing bus connections\n");
+       kdbus_conn_free(conn_a);
+       kdbus_conn_free(conn_b);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-connection.c b/tests/kdbus/test-connection.c
new file mode 100644 (file)
index 0000000..73a3096
--- /dev/null
@@ -0,0 +1,527 @@
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/capability.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <stdbool.h>
+
+#include "kdbus-api.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+#include "kdbus-test.h"
+
+wur int kdbus_test_hello(struct kdbus_test_env *env)
+{
+       struct kdbus_cmd_free cmd_free = {};
+       struct kdbus_cmd_hello hello;
+       int fd;
+
+       memset(&hello, 0, sizeof(hello));
+
+       fd = open(env->buspath, O_RDWR|O_CLOEXEC);
+       ASSERT_RETURN(fd,>=,0);
+
+       hello.flags = KDBUS_HELLO_ACCEPT_FD;
+       hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+       hello.attach_flags_recv = _KDBUS_ATTACH_ALL;
+       hello.size = sizeof(struct kdbus_cmd_hello);
+       hello.pool_size = POOL_SIZE;
+
+       /* an unaligned hello must result in -EFAULT */
+       ASSERT_RETURN(-EFAULT,==,kdbus_cmd_hello(fd, (struct kdbus_cmd_hello *) ((char *) &hello + 1)));
+
+       /* a size of 0 must return EMSGSIZE */
+       hello.size = 1;
+       hello.flags = KDBUS_HELLO_ACCEPT_FD;
+       hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+       ASSERT_RETURN(-EINVAL,==,kdbus_cmd_hello(fd, &hello));
+
+       hello.size = sizeof(struct kdbus_cmd_hello);
+
+       /* check faulty flags */
+       hello.flags = 1ULL << 32;
+       hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+       ASSERT_RETURN(-EINVAL,==,kdbus_cmd_hello(fd, &hello));
+
+       /* check for faulty pool sizes */
+       hello.pool_size = 0;
+       hello.flags = KDBUS_HELLO_ACCEPT_FD;
+       hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+       ASSERT_RETURN(-EINVAL,==,kdbus_cmd_hello(fd, &hello));
+
+       hello.pool_size = 4097;
+       hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+       ASSERT_RETURN(-EINVAL,==,kdbus_cmd_hello(fd, &hello));
+
+       hello.pool_size = POOL_SIZE;
+
+       hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+       hello.offset = (__u64)-1;
+
+       /* success test */
+       ASSERT_ZERO(kdbus_cmd_hello(fd, &hello));
+
+       /* The kernel should have returned some items */
+       ASSERT_RETURN(hello.offset,!=,(__u64)-1);
+       cmd_free.size = sizeof(cmd_free);
+       cmd_free.offset = hello.offset;
+       ASSERT_ZERO(kdbus_cmd_free(fd, &cmd_free));
+
+       CLOSE(fd);
+
+       fd = open(env->buspath, O_RDWR|O_CLOEXEC);
+       ASSERT_RETURN(fd,>=,0);
+
+       /* no ACTIVATOR flag without a name */
+       hello.flags = KDBUS_HELLO_ACTIVATOR;
+       ASSERT_RETURN(-EINVAL,==,kdbus_cmd_hello(fd, &hello));
+
+       CLOSE(fd);
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_byebye(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       struct kdbus_cmd_recv cmd_recv = { .size = sizeof(cmd_recv) };
+       struct kdbus_cmd cmd_byebye = { .size = sizeof(cmd_byebye) };
+
+       /* create a 2nd connection */
+       conn = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_NONZERO(conn);
+
+       ASSERT_ZERO(kdbus_add_match_empty(conn));
+
+       ASSERT_ZERO(kdbus_add_match_empty(env->conn));
+
+       /* send over 1st connection */
+       ASSERT_ZERO(kdbus_msg_send(env->conn, NULL, 0, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+       /* say byebye on the 2nd, which must fail */
+       ASSERT_RETURN(-EBUSY,==,kdbus_cmd_byebye(conn->fd, &cmd_byebye));
+
+       /* receive the message */
+       ASSERT_ZERO(kdbus_cmd_recv(conn->fd, &cmd_recv));
+
+       ASSERT_ZERO(kdbus_free(conn, cmd_recv.msg.offset));
+
+       /* and try again */
+       ASSERT_ZERO(kdbus_cmd_byebye(conn->fd, &cmd_byebye));
+
+       /* a 2nd try should result in -ECONNRESET */
+       ASSERT_RETURN(-ECONNRESET,==,kdbus_cmd_byebye(conn->fd, &cmd_byebye));
+
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
+
+/* Get only the first item */
+static wur struct kdbus_item *kdbus_get_item(struct kdbus_info *info,
+                                        uint64_t type)
+{
+       struct kdbus_item *item;
+
+       KDBUS_ITEM_FOREACH(item, info, items)
+               if (item->type == type)
+                       return item;
+
+       return NULL;
+}
+
+static wur unsigned int kdbus_count_item(struct kdbus_info *info,
+                                    uint64_t type)
+{
+       unsigned int i = 0;
+       const struct kdbus_item *item;
+
+       KDBUS_ITEM_FOREACH(item, info, items)
+               if (item->type == type)
+                       i++;
+
+       return i;
+}
+
+static wur int kdbus_fuzz_conn_info(struct kdbus_test_env *env, int capable)
+{
+       unsigned int cnt = 0;
+       uint64_t offset = 0;
+       struct kdbus_info *info;
+       struct kdbus_conn *conn;
+       struct kdbus_conn *privileged;
+       const struct kdbus_item *item;
+       uint64_t valid_flags = KDBUS_ATTACH_NAMES |
+                              KDBUS_ATTACH_CREDS |
+                              KDBUS_ATTACH_PIDS |
+                              KDBUS_ATTACH_CONN_DESCRIPTION;
+
+       uint64_t invalid_flags = KDBUS_ATTACH_NAMES     |
+                                KDBUS_ATTACH_CREDS     |
+                                KDBUS_ATTACH_PIDS      |
+                                KDBUS_ATTACH_CAPS      |
+                                KDBUS_ATTACH_CGROUP    |
+                                KDBUS_ATTACH_CONN_DESCRIPTION;
+
+       struct kdbus_creds cached_creds;
+       uid_t ruid, euid, suid;
+       gid_t rgid, egid, sgid;
+
+       getresuid(&ruid, &euid, &suid);
+       getresgid(&rgid, &egid, &sgid);
+
+       cached_creds.uid = ruid;
+       cached_creds.euid = euid;
+       cached_creds.suid = suid;
+       cached_creds.fsuid = ruid;
+
+       cached_creds.gid = rgid;
+       cached_creds.egid = egid;
+       cached_creds.sgid = sgid;
+       cached_creds.fsgid = rgid;
+
+       struct kdbus_pids cached_pids = {
+               .pid    = getpid(),
+               .tid    = syscall(SYS_gettid),
+               .ppid   = getppid(),
+       };
+
+       ASSERT_ZERO(kdbus_conn_info(env->conn, env->conn->id, NULL, valid_flags, &offset));
+
+       info = (struct kdbus_info *)(env->conn->buf + offset);
+       ASSERT_RETURN(info->id,==,env->conn->id);
+
+       /* We do not have any well-known name */
+       item = kdbus_get_item(info, KDBUS_ITEM_NAME);
+       ASSERT_ZERO(item);
+
+       item = kdbus_get_item(info, KDBUS_ITEM_CONN_DESCRIPTION);
+       if (valid_flags & KDBUS_ATTACH_CONN_DESCRIPTION)
+               ASSERT_NONZERO(item);
+       else
+               ASSERT_ZERO(item);
+
+       ASSERT_ZERO(kdbus_free(env->conn, offset));
+
+       conn = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_NONZERO(conn);
+
+       privileged = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_NONZERO(privileged);
+
+       ASSERT_ZERO(kdbus_conn_info(conn, conn->id, NULL, valid_flags, &offset));
+
+       info = (struct kdbus_info *)(conn->buf + offset);
+       ASSERT_RETURN(info->id,==,conn->id);
+
+       /* We do not have any well-known name */
+       item = kdbus_get_item(info, KDBUS_ITEM_NAME);
+       ASSERT_ZERO(item);
+
+       cnt = kdbus_count_item(info, KDBUS_ITEM_CREDS);
+       if (valid_flags & KDBUS_ATTACH_CREDS) {
+               ASSERT_RETURN(cnt,==,1U);
+
+               item = kdbus_get_item(info, KDBUS_ITEM_CREDS);
+               ASSERT_NONZERO(item);
+
+               /* Compare received items with cached creds */
+               ASSERT_ZERO(memcmp(&item->creds, &cached_creds, sizeof(struct kdbus_creds)));
+       } else
+               ASSERT_ZERO(cnt);
+
+       item = kdbus_get_item(info, KDBUS_ITEM_PIDS);
+       if (valid_flags & KDBUS_ATTACH_PIDS) {
+               ASSERT_NONZERO(item);
+
+               /* Compare item->pids with cached PIDs */
+               ASSERT_RETURN(item->pids.pid,==,cached_pids.pid);
+               ASSERT_RETURN(item->pids.tid,==,cached_pids.tid);
+               ASSERT_RETURN(item->pids.ppid,==,cached_pids.ppid);
+       } else
+               ASSERT_ZERO(item);
+
+       /* We did not request KDBUS_ITEM_CAPS */
+       item = kdbus_get_item(info, KDBUS_ITEM_CAPS);
+       ASSERT_ZERO(item);
+
+       ASSERT_ZERO(kdbus_free(conn, offset));
+
+       ASSERT_ZERO(kdbus_name_acquire(conn, "com.example.a", NULL));
+
+       ASSERT_ZERO(kdbus_conn_info(conn, conn->id, NULL, valid_flags, &offset));
+
+       info = (struct kdbus_info *)(conn->buf + offset);
+       ASSERT_RETURN(info->id,==,conn->id);
+
+       item = kdbus_get_item(info, KDBUS_ITEM_OWNED_NAME);
+       if (valid_flags & KDBUS_ATTACH_NAMES) {
+               ASSERT_NONZERO(item);
+               ASSERT_ZERO(strcmp(item->name.name, "com.example.a"));
+       } else
+               ASSERT_ZERO(item);
+
+       ASSERT_ZERO(kdbus_free(conn, offset));
+
+       ASSERT_ZERO(kdbus_conn_info(conn, 0, "com.example.a", valid_flags, &offset));
+
+       info = (struct kdbus_info *)(conn->buf + offset);
+       ASSERT_RETURN(info->id,==,conn->id);
+
+       ASSERT_ZERO(kdbus_free(conn, offset));
+
+       /* does not have the necessary caps to drop to unprivileged */
+       if (!capable)
+               goto continue_test;
+
+       RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({
+               ASSERT_EXIT_ZERO(kdbus_conn_info(conn, conn->id, NULL, valid_flags, &offset));
+
+               info = (struct kdbus_info *)(conn->buf + offset);
+               ASSERT_EXIT(info->id,==,conn->id);
+
+               if (valid_flags & KDBUS_ATTACH_NAMES) {
+                       ASSERT_EXIT_NONZERO(item = kdbus_get_item(info, KDBUS_ITEM_OWNED_NAME));
+                       ASSERT_EXIT_ZERO(strcmp(item->name.name, "com.example.a"));
+               }
+
+               if (valid_flags & KDBUS_ATTACH_CREDS) {
+                       ASSERT_EXIT_NONZERO(item = kdbus_get_item(info, KDBUS_ITEM_CREDS));
+
+                       /* Compare received items with cached creds */
+                       ASSERT_EXIT_ZERO(memcmp(&item->creds, &cached_creds, sizeof(struct kdbus_creds)));
+               }
+
+               if (valid_flags & KDBUS_ATTACH_PIDS) {
+                       ASSERT_EXIT_NONZERO(item = kdbus_get_item(info, KDBUS_ITEM_PIDS));
+
+                       /*
+                        * Compare item->pids with cached pids of
+                        * privileged one.
+                        *
+                        * cmd_info will always return cached pids.
+                        */
+                       ASSERT_EXIT(item->pids.pid,==,cached_pids.pid);
+                       ASSERT_EXIT(item->pids.tid,==,cached_pids.tid);
+               }
+
+               ASSERT_ZERO(kdbus_free(conn, offset));
+
+               /*
+                * Use invalid_flags and make sure that userspace
+                * do not play with us.
+                */
+               ASSERT_EXIT_ZERO(kdbus_conn_info(conn, conn->id, NULL, invalid_flags, &offset));
+
+               /*
+                * Make sure that we return only one creds item and
+                * it points to the cached creds.
+                */
+               cnt = kdbus_count_item(info, KDBUS_ITEM_CREDS);
+               if (invalid_flags & KDBUS_ATTACH_CREDS) {
+                       ASSERT_EXIT(cnt,==,1U);
+
+                       ASSERT_EXIT_NONZERO(item = kdbus_get_item(info, KDBUS_ITEM_CREDS));
+
+                       /* Compare received items with cached creds */
+                       ASSERT_EXIT_ZERO(memcmp(&item->creds, &cached_creds, sizeof(struct kdbus_creds)));
+               } else
+                       ASSERT_EXIT_ZERO(cnt);
+
+               if (invalid_flags & KDBUS_ATTACH_PIDS) {
+                       cnt = kdbus_count_item(info, KDBUS_ITEM_PIDS);
+                       ASSERT_EXIT(cnt,==,1U);
+
+                       ASSERT_EXIT_NONZERO(item = kdbus_get_item(info, KDBUS_ITEM_PIDS));
+
+                       /* Compare item->pids with cached pids */
+                       ASSERT_EXIT(item->pids.pid,==,cached_pids.pid);
+                       ASSERT_EXIT(item->pids.tid,==,cached_pids.tid);
+               }
+
+               cnt = kdbus_count_item(info, KDBUS_ITEM_CGROUP);
+               if (invalid_flags & KDBUS_ATTACH_CGROUP)
+                       ASSERT_EXIT(cnt,==,1U);
+               else
+                       ASSERT_EXIT_ZERO(cnt);
+
+               cnt = kdbus_count_item(info, KDBUS_ITEM_CAPS);
+               if (invalid_flags & KDBUS_ATTACH_CAPS)
+                       ASSERT_EXIT(cnt,==,1U);
+               else
+                       ASSERT_EXIT_ZERO(cnt);
+
+               ASSERT_ZERO(kdbus_free(conn, offset));
+       }),
+       ({}));
+
+continue_test:
+
+       /* A second name */
+       ASSERT_ZERO(kdbus_name_acquire(conn, "com.example.b", NULL));
+
+       ASSERT_ZERO(kdbus_conn_info(conn, conn->id, NULL, valid_flags, &offset));
+
+       info = (struct kdbus_info *)(conn->buf + offset);
+       ASSERT_RETURN(info->id,==,conn->id);
+
+       cnt = kdbus_count_item(info, KDBUS_ITEM_OWNED_NAME);
+       if (valid_flags & KDBUS_ATTACH_NAMES)
+               ASSERT_RETURN(cnt,==,2U);
+       else
+               ASSERT_RETURN(cnt,==,0U);
+
+       ASSERT_ZERO(kdbus_free(conn, offset));
+
+       return 0;
+}
+
+wur int kdbus_test_conn_info(struct kdbus_test_env *env)
+{
+       int have_caps;
+       struct {
+               struct kdbus_cmd_info cmd_info;
+
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       char str[64];
+               } name;
+       } buf;
+
+       buf.cmd_info.size = sizeof(struct kdbus_cmd_info);
+       buf.cmd_info.flags = 0;
+       buf.cmd_info.attach_flags = 0;
+       buf.cmd_info.id = env->conn->id;
+
+       ASSERT_ZERO(kdbus_conn_info(env->conn, env->conn->id, NULL, 0, NULL));
+
+       /* try to pass a name that is longer than the buffer's size */
+       buf.name.size = KDBUS_ITEM_HEADER_SIZE + 1;
+       buf.name.type = KDBUS_ITEM_NAME;
+       strcpy(buf.name.str, "foo.bar.bla");
+
+       buf.cmd_info.id = 0;
+       buf.cmd_info.size = sizeof(buf.cmd_info) + buf.name.size;
+       ASSERT_RETURN(-EINVAL,==,kdbus_cmd_conn_info(env->conn->fd, (struct kdbus_cmd_info *) &buf));
+
+       /* Pass a non existent name */
+       ASSERT_RETURN(-ESRCH,==,kdbus_conn_info(env->conn, 0, "non.existent.name", 0, NULL));
+
+       if (!all_uids_gids_are_mapped())
+               return TEST_SKIP;
+
+       /* Test for caps here, so we run the previous test */
+       have_caps = test_is_capable(CAP_SETUID, CAP_SETGID, -1);
+       ASSERT_RETURN(have_caps,>=,0);
+
+       ASSERT_ZERO(kdbus_fuzz_conn_info(env, have_caps));
+
+       /* Now if we have skipped some tests then let the user know */
+       if (!have_caps)
+               return TEST_SKIP;
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_conn_update(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       struct kdbus_msg *msg;
+       int found = 0;
+
+       /*
+        * kdbus_hello() sets all attach flags. Receive a message by this
+        * connection, and make sure a timestamp item (just to pick one) is
+        * present.
+        */
+       conn = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_NONZERO(conn);
+
+       ASSERT_ZERO(kdbus_msg_send(env->conn, NULL, 0x12345678, 0, 0, 0, conn->id));
+
+       ASSERT_ZERO(kdbus_msg_recv(conn, &msg, NULL));
+
+       found = kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP);
+       ASSERT_RETURN(found,==,1);
+
+       kdbus_msg_free(msg);
+
+       /*
+        * Now, modify the attach flags and repeat the action. The item must
+        * now be missing.
+        */
+       found = 0;
+
+       ASSERT_ZERO(kdbus_conn_update_attach_flags(conn, _KDBUS_ATTACH_ALL, _KDBUS_ATTACH_ALL & ~KDBUS_ATTACH_TIMESTAMP));
+
+       ASSERT_ZERO(kdbus_msg_send(env->conn, NULL, 0x12345678, 0, 0, 0, conn->id));
+
+       ASSERT_ZERO(kdbus_msg_recv(conn, &msg, NULL));
+
+       found = kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP);
+       ASSERT_ZERO(found);
+
+       /* Provide a bogus attach_flags value */
+       ASSERT_RETURN(-EINVAL,==,kdbus_conn_update_attach_flags(conn, _KDBUS_ATTACH_ALL + 1, _KDBUS_ATTACH_ALL));
+
+       kdbus_msg_free(msg);
+
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_writable_pool(struct kdbus_test_env *env)
+{
+       struct kdbus_cmd_free cmd_free = {};
+       struct kdbus_cmd_hello hello;
+       int fd;
+       void *map;
+
+       fd = open(env->buspath, O_RDWR | O_CLOEXEC);
+       ASSERT_RETURN(fd,>=,0);
+
+       memset(&hello, 0, sizeof(hello));
+       hello.flags = KDBUS_HELLO_ACCEPT_FD;
+       hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+       hello.attach_flags_recv = _KDBUS_ATTACH_ALL;
+       hello.size = sizeof(struct kdbus_cmd_hello);
+       hello.pool_size = POOL_SIZE;
+       hello.offset = (__u64)-1;
+
+       /* success test */
+       ASSERT_ZERO(kdbus_cmd_hello(fd, &hello));
+
+       /* The kernel should have returned some items */
+       ASSERT_RETURN(hello.offset,!=,(__u64)-1);
+       cmd_free.size = sizeof(cmd_free);
+       cmd_free.offset = hello.offset;
+       ASSERT_ZERO(kdbus_cmd_free(fd, &cmd_free));
+
+       /* pools cannot be mapped writable */
+       map = mmap(NULL, POOL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+       ASSERT_RETURN(map,==,MAP_FAILED);
+
+       /* pools can always be mapped readable */
+       map = mmap(NULL, POOL_SIZE, PROT_READ, MAP_SHARED, fd, 0);
+       ASSERT_RETURN(map,!=,MAP_FAILED);
+
+       /* make sure we cannot change protection masks to writable */
+       ASSERT_RETURN(0,>,mprotect(map, POOL_SIZE, PROT_READ | PROT_WRITE));
+
+       munmap(map, POOL_SIZE);
+       CLOSE(fd);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-daemon.c b/tests/kdbus/test-daemon.c
new file mode 100644 (file)
index 0000000..066940e
--- /dev/null
@@ -0,0 +1,62 @@
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <poll.h>
+#include <stdbool.h>
+
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+wur int kdbus_test_daemon(struct kdbus_test_env *env)
+{
+       struct pollfd fds[2];
+       int count;
+       int ret;
+
+       /* This test doesn't make any sense in non-interactive mode */
+       if (!kdbus_util_verbose)
+               return TEST_OK;
+
+       print("Created connection %llu on bus '%s'\n",
+               (unsigned long long) env->conn->id, env->buspath);
+
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, "com.example.kdbus-test", NULL));
+       print("  Aquired name: com.example.kdbus-test\n");
+
+       fds[0].fd = env->conn->fd;
+       fds[1].fd = STDIN_FILENO;
+
+       print("Monitoring connections:\n");
+
+       for (count = 0;; count++) {
+               int i, nfds = sizeof(fds) / sizeof(fds[0]);
+
+               for (i = 0; i < nfds; i++) {
+                       fds[i].events = POLLIN | POLLPRI | POLLHUP;
+                       fds[i].revents = 0;
+               }
+
+               ret = poll(fds, nfds, -1);
+               if (ret <= 0)
+                       break;
+
+               if (fds[0].revents & POLLIN)
+                       ASSERT_ZERO(kdbus_msg_recv(env->conn, NULL, NULL));
+
+               /* stdin */
+               if (fds[1].revents & POLLIN)
+                       break;
+       }
+
+       print("Closing bus connection\n");
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-endpoint.c b/tests/kdbus/test-endpoint.c
new file mode 100644 (file)
index 0000000..a3e143f
--- /dev/null
@@ -0,0 +1,313 @@
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <libgen.h>
+#include <sys/capability.h>
+#include <sys/wait.h>
+#include <stdbool.h>
+
+#include "kdbus-api.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+#include "kdbus-test.h"
+
+#define KDBUS_SYSNAME_MAX_LEN                  63
+
+static wur int install_name_add_match(struct kdbus_conn *conn, const char *name)
+{
+       struct {
+               struct kdbus_cmd_match cmd;
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       struct kdbus_notify_name_change chg;
+               } item;
+               char name[64];
+       } buf;
+       int ret;
+
+       /* install the match rule */
+       memset(&buf, 0, sizeof(buf));
+       buf.item.type = KDBUS_ITEM_NAME_ADD;
+       buf.item.chg.old_id.id = KDBUS_MATCH_ID_ANY;
+       buf.item.chg.new_id.id = KDBUS_MATCH_ID_ANY;
+       strncpy(buf.name, name, sizeof(buf.name) - 1);
+       buf.item.size = sizeof(buf.item) + strlen(buf.name) + 1;
+       buf.cmd.size = sizeof(buf.cmd) + buf.item.size;
+
+       ret = kdbus_cmd_match_add(conn->fd, &buf.cmd);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static wur int create_endpoint(const char *buspath, uid_t uid, const char *name,
+                          uint64_t flags)
+{
+       struct {
+               struct kdbus_cmd cmd;
+
+               /* name item */
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       /* max should be KDBUS_SYSNAME_MAX_LEN */
+                       char str[128];
+               } name;
+       } ep_make;
+       int fd, ret;
+
+       fd = open(buspath, O_RDWR);
+       if (fd < 0)
+               return fd;
+
+       memset(&ep_make, 0, sizeof(ep_make));
+
+       snprintf(ep_make.name.str,
+                /* Use the KDBUS_SYSNAME_MAX_LEN or sizeof(str) */
+                KDBUS_SYSNAME_MAX_LEN > strlen(name) ?
+                KDBUS_SYSNAME_MAX_LEN : sizeof(ep_make.name.str),
+                "%u-%s", uid, name);
+
+       ep_make.name.type = KDBUS_ITEM_MAKE_NAME;
+       ep_make.name.size = KDBUS_ITEM_HEADER_SIZE +
+                           strlen(ep_make.name.str) + 1;
+
+       ep_make.cmd.flags = flags;
+       ep_make.cmd.size = sizeof(ep_make.cmd) + ep_make.name.size;
+
+       ret = kdbus_cmd_endpoint_make(fd, &ep_make.cmd);
+       if (ret < 0) {
+               kdbus_printf("error creating endpoint: %d (%m)\n", ret);
+               return ret;
+       }
+
+       return fd;
+}
+
+static wur int unpriv_test_custom_ep(const char *buspath)
+{
+       int ret, ep_fd1, ep_fd2;
+       char *ep1, *ep2, *tmp1, *tmp2;
+
+       ASSERT_NONZERO(tmp1 = strdup(buspath));
+       ASSERT_NONZERO(tmp2 = strdup(buspath));
+
+       ret = asprintf(&ep1, "%s/%u-%s", dirname(tmp1), getuid(), "apps1");
+       ASSERT_RETURN(ret,>,0);
+
+       ret = asprintf(&ep2, "%s/%u-%s", dirname(tmp2), getuid(), "apps2");
+       ASSERT_RETURN(ret,>,0);
+
+       free(tmp1);
+       free(tmp2);
+
+       /* endpoint only accessible to current uid */
+       ep_fd1 = create_endpoint(buspath, getuid(), "apps1", 0);
+       ASSERT_RETURN(ep_fd1,>=,0);
+
+       /* endpoint world accessible */
+       ep_fd2 = create_endpoint(buspath, getuid(), "apps2",
+                                 KDBUS_MAKE_ACCESS_WORLD);
+       ASSERT_RETURN(ep_fd2,>=,0);
+
+       RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_UID, ({
+               int ep_fd;
+               struct kdbus_conn *ep_conn;
+
+               /*
+                * Make sure that we are not able to create custom
+                * endpoints
+                */
+               ep_fd = create_endpoint(buspath, getuid(),
+                                       "unpriv_costum_ep", 0);
+               ASSERT_EXIT(ep_fd,==,-EPERM);
+
+               /*
+                * Endpoint "apps1" only accessible to same users,
+                * that own the endpoint. Access denied by VFS
+                */
+               ep_conn = kdbus_hello(ep1, 0, NULL, 0);
+               ASSERT_EXIT(errno,==,EACCES);
+               ASSERT_EXIT_ZERO(ep_conn);
+
+               /* Endpoint "apps2" world accessible */
+               ASSERT_EXIT_NONZERO(ep_conn = kdbus_hello(ep2, 0, NULL, 0));
+
+               kdbus_conn_free(ep_conn);
+
+               exit(EXIT_SUCCESS);
+       }),
+       ({}));
+
+       CLOSE(ep_fd1);
+       CLOSE(ep_fd2);
+       free(ep1);
+       free(ep2);
+
+       return 0;
+}
+
+static wur int update_endpoint(int fd, const char *name)
+{
+       int len = strlen(name) + 1;
+       struct {
+               struct kdbus_cmd cmd;
+
+               /* name item */
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       char str[KDBUS_ALIGN8(len)];
+               } name;
+
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       struct kdbus_policy_access access;
+               } access;
+       } ep_update;
+       int ret;
+
+       memset(&ep_update, 0, sizeof(ep_update));
+
+       ep_update.name.size = KDBUS_ITEM_HEADER_SIZE + len;
+       ep_update.name.type = KDBUS_ITEM_NAME;
+       strncpy(ep_update.name.str, name, sizeof(ep_update.name.str) - 1);
+
+       ep_update.access.size = sizeof(ep_update.access);
+       ep_update.access.type = KDBUS_ITEM_POLICY_ACCESS;
+       ep_update.access.access.type = KDBUS_POLICY_ACCESS_WORLD;
+       ep_update.access.access.access = KDBUS_POLICY_SEE;
+
+       ep_update.cmd.size = sizeof(ep_update);
+
+       ret = kdbus_cmd_endpoint_update(fd, &ep_update.cmd);
+       if (ret < 0) {
+               kdbus_printf("error updating endpoint: %d (%m)\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+wur int kdbus_test_custom_endpoint(struct kdbus_test_env *env)
+{
+       char *ep, *tmp;
+       int ret, ep_fd;
+       struct kdbus_msg *msg;
+       struct kdbus_conn *ep_conn;
+       struct kdbus_conn *reader;
+       const char *name = "foo.bar.baz";
+       const char *epname = "foo";
+       char fake_ep[KDBUS_SYSNAME_MAX_LEN + 1] = {'\0'};
+
+       memset(fake_ep, 'X', sizeof(fake_ep) - 1);
+
+       /* Try to create a custom endpoint with a long name */
+       ASSERT_RETURN(-ENAMETOOLONG,==,create_endpoint(env->buspath, getuid(), fake_ep, 0));
+
+       /* Try to create a custom endpoint with a different uid */
+       ASSERT_RETURN(-EINVAL,==,create_endpoint(env->buspath, getuid() + 1, "foobar", 0));
+
+       /* create a custom endpoint, and open a connection on it */
+       ASSERT_RETURN(0,<=,ep_fd = create_endpoint(env->buspath, getuid(), "foo", 0));
+
+       ASSERT_NONZERO(tmp = strdup(env->buspath));
+       ASSERT_RETURN(0,<=,asprintf(&ep, "%s/%u-%s", dirname(tmp), getuid(), epname));
+       free(tmp);
+
+       /* Register a connection that listen to broadcasts */
+       ASSERT_NONZERO(reader = kdbus_hello(ep, 0, NULL, 0));
+
+       /* Register to kernel signals */
+       ASSERT_ZERO(kdbus_add_match_id(reader, 0x1, KDBUS_ITEM_ID_ADD, KDBUS_MATCH_ID_ANY));
+       ASSERT_ZERO(kdbus_add_match_id(reader, 0x2, KDBUS_ITEM_ID_REMOVE, KDBUS_MATCH_ID_ANY));
+       ASSERT_ZERO(install_name_add_match(reader, name));
+
+       /* Monitor connections are not supported on custom endpoints */
+       ep_conn = kdbus_hello(ep, KDBUS_HELLO_MONITOR, NULL, 0);
+       ASSERT_RETURN(errno,==,EOPNOTSUPP);
+       ASSERT_ZERO(ep_conn);
+
+       ASSERT_NONZERO(ep_conn = kdbus_hello(ep, 0, NULL, 0));
+
+       /* Check that the reader got the IdAdd notification */
+       ASSERT_ZERO(kdbus_msg_recv(reader, &msg, NULL));
+       ASSERT_RETURN(msg->items[0].type,==,(typeof(msg->items[0].type))KDBUS_ITEM_ID_ADD);
+       ASSERT_RETURN(msg->items[0].id_change.id,==,ep_conn->id);
+       kdbus_msg_free(msg);
+
+       /*
+        * Add a name add match on the endpoint connection, acquire name from
+        * the unfiltered connection, and make sure the filtered connection
+        * did not get the notification on the name owner change. Also, the
+        * endpoint connection may not be able to call conn_info, neither on
+        * the name nor on the ID.
+        */
+       ASSERT_ZERO(install_name_add_match(ep_conn, name));
+
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, name, NULL));
+
+       ASSERT_RETURN(ONTIZEN(0,-EAGAIN),==,kdbus_msg_recv(ep_conn, NULL, NULL));
+       ASSERT_RETURN(ONTIZEN(0,-ESRCH),==,kdbus_conn_info(ep_conn, 0, name, 0, NULL));
+       ASSERT_RETURN(-ESRCH,==,kdbus_conn_info(ep_conn, 0, "random.crappy.name", 0, NULL));
+       ASSERT_RETURN(ONTIZEN(0,-ENXIO),==,kdbus_conn_info(ep_conn, env->conn->id, NULL, 0, NULL));
+       ASSERT_RETURN(-ENXIO,==,kdbus_conn_info(ep_conn, 0x0fffffffffffffffULL, NULL, 0, NULL));
+
+       /* Check that the reader did not receive the name notification */
+       ASSERT_RETURN(ONTIZEN(0,-EAGAIN),==,kdbus_msg_recv(reader, NULL, NULL));
+
+       /*
+        * Release the name again, update the custom endpoint policy,
+        * and try again. This time, the connection on the custom endpoint
+        * should have gotten it.
+        */
+       ASSERT_ZERO(kdbus_name_release(env->conn, name));
+
+       /* Check that the reader did not receive the name notification */
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(reader, NULL, NULL));
+
+       ASSERT_ZERO(update_endpoint(ep_fd, name));
+
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, name, NULL));
+
+       ASSERT_ZERO(kdbus_msg_recv(ep_conn, &msg, NULL));
+       ASSERT_RETURN(msg->items[0].type,==,(typeof(msg->items[0].type))KDBUS_ITEM_NAME_ADD);
+       ASSERT_ZERO(msg->items[0].name_change.old_id.id);
+       ASSERT_RETURN(msg->items[0].name_change.new_id.id,==,env->conn->id);
+       ASSERT_ZERO(strcmp(msg->items[0].name_change.name, name));
+       kdbus_msg_free(msg);
+
+       ASSERT_ZERO(kdbus_msg_recv(reader, &msg, NULL));
+       ASSERT_ZERO(strcmp(msg->items[0].name_change.name, name));
+
+       kdbus_msg_free(msg);
+
+       ASSERT_ZERO(kdbus_conn_info(ep_conn, 0, name, 0, NULL));
+
+       ASSERT_ZERO(kdbus_conn_info(ep_conn, env->conn->id, NULL, 0, NULL));
+
+       /* If we have privileges test custom endpoints */
+       ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1);
+       ASSERT_RETURN(ret,>=,0);
+
+       /*
+        * All uids/gids are mapped and we have the necessary caps
+        */
+       if (ret && all_uids_gids_are_mapped())
+               ASSERT_ZERO(unpriv_test_custom_ep(env->buspath));
+
+       kdbus_conn_free(reader);
+       kdbus_conn_free(ep_conn);
+       CLOSE(ep_fd);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-fd.c b/tests/kdbus/test-fd.c
new file mode 100644 (file)
index 0000000..6f315a9
--- /dev/null
@@ -0,0 +1,778 @@
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/eventfd.h>
+
+#include "kdbus-api.h"
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+#define KDBUS_MSG_MAX_ITEMS     128U
+#define KDBUS_USER_MAX_CONN    256U
+
+/* maximum number of inflight fds in a target queue per user */
+#define KDBUS_CONN_MAX_FDS_PER_USER    16U
+
+/* maximum number of memfd items per message */
+#define KDBUS_MSG_MAX_MEMFD_ITEMS       16U
+
+static wur int make_msg_payload_dbus(uint64_t src_id, uint64_t dst_id,
+                                uint64_t msg_size,
+                                struct kdbus_msg **msg_dbus)
+{
+       struct kdbus_msg *msg;
+
+       msg = alloc(msg_size);
+       ASSERT_RETURN_VAL(msg,!=,NULL, -ENOMEM);
+
+       memset(msg, 0, msg_size);
+       msg->size = msg_size;
+       msg->src_id = src_id;
+       msg->dst_id = dst_id;
+       msg->payload_type = KDBUS_PAYLOAD_DBUS;
+
+       *msg_dbus = msg;
+
+       return 0;
+}
+
+static void make_item_memfds(struct kdbus_item *item,
+                            int *memfds, size_t memfd_size)
+{
+       size_t i;
+
+       for (i = 0; i < memfd_size; i++) {
+               item->type = KDBUS_ITEM_PAYLOAD_MEMFD;
+               item->size = KDBUS_ITEM_HEADER_SIZE +
+                            sizeof(struct kdbus_memfd);
+               item->memfd.fd = memfds[i];
+               item->memfd.size = sizeof(uint64_t); /* const size */
+               item = KDBUS_ITEM_NEXT(item);
+       }
+}
+
+static void make_item_fds(struct kdbus_item *item,
+                         int *fd_array, size_t fd_size)
+{
+       size_t i;
+       item->type = KDBUS_ITEM_FDS;
+       item->size = KDBUS_ITEM_HEADER_SIZE + (sizeof(int) * fd_size);
+
+       for (i = 0; i < fd_size; i++)
+               item->fds[i] = fd_array[i];
+}
+
+static wur int memfd_write(const char *name, void *buf, size_t bufsize)
+{
+       ssize_t ret;
+       int memfd;
+
+       memfd = sys_memfd_create(name, 0);
+       ASSERT_RETURN_VAL(memfd,>=,0, memfd);
+
+       ret = write(memfd, buf, bufsize);
+       ASSERT_RETURN_VAL(ret,==,(ssize_t)bufsize, -EAGAIN);
+
+       ret = sys_memfd_seal_set(memfd);
+       ASSERT_RETURN_VAL(ret,==,0, -errno);
+
+       return memfd;
+}
+
+static wur int send_memfds(struct kdbus_conn *conn, uint64_t dst_id,
+                      int *memfds_array, size_t memfd_count)
+{
+       struct kdbus_cmd_send cmd = {};
+       struct kdbus_item *item;
+       struct kdbus_msg *msg;
+       uint64_t size;
+       int ret;
+
+       size = sizeof(struct kdbus_msg);
+       size += memfd_count * KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd));
+
+       if (dst_id == KDBUS_DST_ID_BROADCAST)
+               size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64;
+
+       ret = make_msg_payload_dbus(conn->id, dst_id, size, &msg);
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       item = msg->items;
+
+       if (dst_id == KDBUS_DST_ID_BROADCAST) {
+               item->type = KDBUS_ITEM_BLOOM_FILTER;
+               item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64;
+               item = KDBUS_ITEM_NEXT(item);
+
+               msg->flags |= KDBUS_MSG_SIGNAL;
+       }
+
+       make_item_memfds(item, memfds_array, memfd_count);
+
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg;
+
+       ret = kdbus_cmd_send(conn->fd, &cmd);
+       if (ret < 0) {
+               kdbus_printf("error sending message: %d (%m)\n", ret);
+               return ret;
+       }
+
+       free(msg);
+       return 0;
+}
+
+static wur int send_fds(struct kdbus_conn *conn, uint64_t dst_id,
+                   int *fd_array, size_t fd_count)
+{
+       struct kdbus_cmd_send cmd = {};
+       struct kdbus_item *item;
+       struct kdbus_msg *msg;
+       uint64_t size;
+       int ret;
+
+       size = sizeof(struct kdbus_msg);
+       size += KDBUS_ITEM_SIZE(sizeof(int) * fd_count);
+
+       if (dst_id == KDBUS_DST_ID_BROADCAST)
+               size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64;
+
+       ret = make_msg_payload_dbus(conn->id, dst_id, size, &msg);
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       item = msg->items;
+
+       if (dst_id == KDBUS_DST_ID_BROADCAST) {
+               item->type = KDBUS_ITEM_BLOOM_FILTER;
+               item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64;
+               item = KDBUS_ITEM_NEXT(item);
+
+               msg->flags |= KDBUS_MSG_SIGNAL;
+       }
+
+       make_item_fds(item, fd_array, fd_count);
+
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg;
+
+       ret = kdbus_cmd_send(conn->fd, &cmd);
+       if (ret < 0) {
+               kdbus_printf("error sending message: %d (%m)\n", ret);
+               return ret;
+       }
+
+       free(msg);
+       return ret;
+}
+
+static wur int send_fds_memfds(struct kdbus_conn *conn, uint64_t dst_id,
+                          int *fds_array, size_t fd_count,
+                          int *memfds_array, size_t memfd_count)
+{
+       struct kdbus_cmd_send cmd = {};
+       struct kdbus_item *item;
+       struct kdbus_msg *msg;
+       uint64_t size;
+       int ret;
+
+       size = sizeof(struct kdbus_msg);
+       size += memfd_count * KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd));
+       size += KDBUS_ITEM_SIZE(sizeof(int) * fd_count);
+
+       ret = make_msg_payload_dbus(conn->id, dst_id, size, &msg);
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       item = msg->items;
+
+       make_item_fds(item, fds_array, fd_count);
+       item = KDBUS_ITEM_NEXT(item);
+       make_item_memfds(item, memfds_array, memfd_count);
+
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg;
+
+       ret = kdbus_cmd_send(conn->fd, &cmd);
+       if (ret < 0) {
+               kdbus_printf("error sending message: %d (%m)\n", ret);
+               return ret;
+       }
+
+       free(msg);
+       return ret;
+}
+
+static wur struct kdbus_msg *
+get_kdbus_msg_with_fd(struct kdbus_conn *conn_src,
+                     uint64_t dst_id, uint64_t cookie, int fd)
+{
+       int ret;
+       uint64_t size;
+       struct kdbus_item *item;
+       struct kdbus_msg *msg;
+
+       size = sizeof(struct kdbus_msg);
+       if (fd >= 0)
+               size += KDBUS_ITEM_SIZE(sizeof(int));
+
+       ret = make_msg_payload_dbus(conn_src->id, dst_id, size, &msg);
+       ASSERT_RETURN_VAL(ret,==,0, NULL);
+
+       msg->cookie = cookie;
+
+       if (fd >= 0) {
+               item = msg->items;
+
+               make_item_fds(item, (int *)&fd, 1);
+       }
+
+       return msg;
+}
+
+static wur int kdbus_test_no_fds(struct kdbus_test_env *env,
+                            int *fds, int *memfd)
+{
+       pid_t pid;
+       int ret, status;
+       uint64_t cookie;
+       int connfd1, connfd2;
+       struct kdbus_msg *msg, *msg_sync_reply;
+       struct kdbus_cmd_hello hello;
+       struct kdbus_conn *conn_src, *conn_dst, *conn_dummy;
+       struct kdbus_cmd_send cmd = {};
+       struct kdbus_cmd_free cmd_free = {};
+
+       ASSERT_NONZERO(conn_src = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       connfd1 = open(env->buspath, O_RDWR|O_CLOEXEC);
+       ASSERT_RETURN(connfd1,>=,0);
+
+       connfd2 = open(env->buspath, O_RDWR|O_CLOEXEC);
+       ASSERT_RETURN(connfd2,>=,0);
+
+       /*
+        * Create connections without KDBUS_HELLO_ACCEPT_FD
+        * to test if send fd operations are blocked
+        */
+       ASSERT_NONZERO(conn_dst = alloc(sizeof(*conn_dst)));
+       ASSERT_NONZERO(conn_dummy = alloc(sizeof(*conn_dummy)));
+
+       memset(&hello, 0, sizeof(hello));
+       hello.size = sizeof(struct kdbus_cmd_hello);
+       hello.pool_size = POOL_SIZE;
+       hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+
+       ASSERT_ZERO(kdbus_cmd_hello(connfd1, &hello));
+
+       cmd_free.size = sizeof(cmd_free);
+       cmd_free.offset = hello.offset;
+       ASSERT_ZERO(kdbus_cmd_free(connfd1, &cmd_free));
+
+       conn_dst->fd = connfd1;
+       conn_dst->id = hello.id;
+       conn_dst->attach_flags_recv = 0;
+
+       memset(&hello, 0, sizeof(hello));
+       hello.size = sizeof(struct kdbus_cmd_hello);
+       hello.pool_size = POOL_SIZE;
+       hello.attach_flags_send = _KDBUS_ATTACH_ALL;
+
+       ASSERT_ZERO(kdbus_cmd_hello(connfd2, &hello));
+
+       cmd_free.size = sizeof(cmd_free);
+       cmd_free.offset = hello.offset;
+       ASSERT_ZERO(kdbus_cmd_free(connfd2, &cmd_free));
+
+       conn_dummy->fd = connfd2;
+       conn_dummy->id = hello.id;
+       conn_dummy->attach_flags_recv = 0;
+
+       conn_dst->buf = mmap(NULL, POOL_SIZE, PROT_READ,
+                            MAP_SHARED, connfd1, 0);
+       ASSERT_RETURN(conn_dst->buf,!=,MAP_FAILED);
+
+       conn_dummy->buf = mmap(NULL, POOL_SIZE, PROT_READ,
+                              MAP_SHARED, connfd2, 0);
+       ASSERT_RETURN(conn_dummy->buf,!=,MAP_FAILED);
+
+       /*
+        * Send fds to connection that do not accept fd passing
+        */
+       ASSERT_RETURN(-ECOMM,==,send_fds(conn_src, conn_dst->id, fds, 1));
+
+       /*
+        * memfd are kdbus payload
+        */
+       ASSERT_ZERO(send_memfds(conn_src, conn_dst->id, memfd, 1));
+
+       ASSERT_ZERO(kdbus_msg_recv_poll(conn_dst, 100, NULL, NULL));
+
+       cookie = time(NULL);
+
+       pid = fork();
+       ASSERT_RETURN_VAL(pid,>=,0, pid);
+
+       if (pid == 0) {
+               struct timespec now;
+
+               /*
+                * A sync send/reply to a connection that do not
+                * accept fds should fail if it contains an fd
+                */
+               ASSERT_EXIT_NONZERO(msg_sync_reply = get_kdbus_msg_with_fd(conn_dst, conn_dummy->id, cookie, fds[0]));
+
+               ASSERT_EXIT_ZERO(clock_gettime(CLOCK_MONOTONIC_COARSE, &now));
+
+               msg_sync_reply->timeout_ns = now.tv_sec * 1000000000ULL +
+                                            now.tv_nsec + 100000000ULL;
+               msg_sync_reply->flags = KDBUS_MSG_EXPECT_REPLY;
+
+               memset(&cmd, 0, sizeof(cmd));
+               cmd.size = sizeof(cmd);
+               cmd.msg_address = (uintptr_t)msg_sync_reply;
+               cmd.flags = KDBUS_SEND_SYNC_REPLY;
+
+               ASSERT_EXIT(-ECOMM,==,kdbus_cmd_send(conn_dst->fd, &cmd));
+
+               /*
+                * Now send a normal message, but the sync reply
+                * will fail since it contains an fd that the
+                * original sender do not want.
+                *
+                * The original sender will fail with -ETIMEDOUT
+                */
+               cookie++;
+               ASSERT_EXIT(-EREMOTEIO,==,kdbus_msg_send_sync(conn_dst, NULL, cookie, KDBUS_MSG_EXPECT_REPLY, 5000000000ULL, 0, conn_src->id, -1));
+
+               cookie++;
+               ASSERT_EXIT_ZERO(kdbus_msg_recv_poll(conn_dst, 100, &msg, NULL));
+               ASSERT_EXIT(msg->cookie,==,cookie);
+
+               free(msg_sync_reply);
+               kdbus_msg_free(msg);
+
+               exit(EXIT_SUCCESS);
+       }
+
+       ASSERT_RETURN(-ETIMEDOUT,==,kdbus_msg_recv_poll(conn_dummy, 100, NULL, NULL));
+
+       cookie++;
+       ASSERT_ZERO(kdbus_msg_recv_poll(conn_src, 100, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       kdbus_msg_free(msg);
+
+       /*
+        * Try to reply with a kdbus connection handle, this should
+        * fail with -EOPNOTSUPP
+        */
+       msg_sync_reply = get_kdbus_msg_with_fd(conn_src,
+                                              conn_dst->id,
+                                              cookie, conn_dst->fd);
+       ASSERT_NONZERO(msg_sync_reply);
+
+       msg_sync_reply->cookie_reply = cookie;
+
+       memset(&cmd, 0, sizeof(cmd));
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg_sync_reply;
+
+       ASSERT_RETURN(-EOPNOTSUPP,==,kdbus_cmd_send(conn_src->fd, &cmd));
+
+       free(msg_sync_reply);
+
+       /*
+        * Try to reply with a normal fd, this should fail even
+        * if the response is a sync reply
+        *
+        * From the sender view we fail with -ECOMM
+        */
+       msg_sync_reply = get_kdbus_msg_with_fd(conn_src,
+                                              conn_dst->id,
+                                              cookie, fds[0]);
+       ASSERT_NONZERO(msg_sync_reply);
+
+       msg_sync_reply->cookie_reply = cookie;
+
+       memset(&cmd, 0, sizeof(cmd));
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg_sync_reply;
+
+       ASSERT_RETURN(-ECOMM,==,kdbus_cmd_send(conn_src->fd, &cmd));
+
+       free(msg_sync_reply);
+
+       /*
+        * Resend another normal message and check if the queue
+        * is clear
+        */
+       cookie++;
+       ASSERT_ZERO(kdbus_msg_send(conn_src, NULL, cookie, 0, 0, 0, conn_dst->id));
+
+       ret = waitpid(pid, &status, 0);
+       ASSERT_RETURN_VAL(ret,>=,0, ret);
+
+       kdbus_conn_free(conn_dummy);
+       kdbus_conn_free(conn_dst);
+       kdbus_conn_free(conn_src);
+
+       return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR;
+}
+
+#define VALUE_CHECKED_SEED 0
+uint64_t value_checked_out = VALUE_CHECKED_SEED;
+uint64_t value_checked_in = VALUE_CHECKED_SEED;
+
+#define MAKE_FDS_(ARRAY_NAME,MAKE_ELEM) do {\
+       unsigned _MAKE_FDS_i_ = 0;\
+       do {\
+               ASSERT_RETURN(-1,==,ARRAY_NAME[_MAKE_FDS_i_]);\
+               ARRAY_NAME[_MAKE_FDS_i_] = MAKE_ELEM(++value_checked_out);\
+               ASSERT_RETURN(0,<=,ARRAY_NAME[_MAKE_FDS_i_]);\
+       } while (++_MAKE_FDS_i_ < ELEMENTSOF(ARRAY_NAME));\
+} while (0)
+
+#define REMAKE_FDS_(ARRAY_NAME,MAKE_ELEM) do {\
+       CLOSE_FDS(ARRAY_NAME);\
+       MAKE_FDS_(ARRAY_NAME,MAKE_ELEM);\
+} while (0)
+
+static wur int make_value_checked_fd(uint64_t value) {
+       int fd = eventfd(0, EFD_CLOEXEC);
+       if (fd < 0) {
+               int ret = -errno;
+               return ret<0 ? ret : -817;
+       }
+       if (eventfd_write(fd, value)) {
+               int ret = -errno;
+               return ret<0 ? ret : -818;
+       }
+       return fd;
+}
+static wur int make_value_checked_memfd(uint64_t value) {
+       return memfd_write("memfd-name", &value, sizeof(value));
+}
+
+#define D_FDS(ARRAY_NAME,COUNT) int ARRAY_NAME[COUNT] = { [0 ... COUNT-1] = -1 }
+
+#define CLOSE_FDS(ARRAY_NAME) do {\
+       unsigned _CLOSE_FDS_i_ = 0;\
+       do {\
+               ASSERT_RETURN(0,<=,ARRAY_NAME[_CLOSE_FDS_i_]);\
+               CLOSE(ARRAY_NAME[_CLOSE_FDS_i_]);\
+               ARRAY_NAME[_CLOSE_FDS_i_] = -1;\
+       } while (++_CLOSE_FDS_i_ < ELEMENTSOF(ARRAY_NAME));\
+} while (0)
+
+#define MAKE_FDS(ARRAY_NAME)    MAKE_FDS_(ARRAY_NAME, make_value_checked_fd)
+#define MAKE_MEMFDS(ARRAY_NAME) MAKE_FDS_(ARRAY_NAME, make_value_checked_memfd)
+#define REMAKE_FDS(ARRAY_NAME)    REMAKE_FDS_(ARRAY_NAME, make_value_checked_fd)
+#define REMAKE_MEMFDS(ARRAY_NAME) REMAKE_FDS_(ARRAY_NAME, make_value_checked_memfd)
+
+static wur int check_fds(int togo, int total_count, struct kdbus_msg const *msg) {
+       struct kdbus_item const *item;
+       int surplus_count;
+       ASSERT_RETURN(0,<,togo);
+       ASSERT_RETURN(togo,<=,total_count);
+       surplus_count = total_count - togo;
+       KDBUS_ITEM_FOREACH(item, msg, items)
+               if (KDBUS_ITEM_FDS == item->type) {
+                       unsigned n = (item->size - KDBUS_ITEM_HEADER_SIZE) / sizeof(int);
+                       unsigned i = 0;
+                       ASSERT_NONZERO(togo);
+                       for (; i < n; ++i) {
+                               int fd = item->fds[i];
+                               if (-1 != fd) {
+                                       eventfd_t value = VALUE_CHECKED_SEED;
+                                       ASSERT_ZERO(eventfd_read(fd, &value));
+                                       ASSERT_RETURN(value,==,++value_checked_in);
+                                       --togo;
+                               }
+                       }
+                       ASSERT_ZERO(togo);
+               }
+       ASSERT_ZERO(togo);
+       value_checked_in += surplus_count;
+       return 0;
+}
+
+static wur int check_memfds(int togo, int total_count, struct kdbus_msg const *msg) {
+       struct kdbus_item const *item;
+       int surplus_count;
+       ASSERT_RETURN(0,<,togo);
+       ASSERT_RETURN(togo,<=,total_count);
+       surplus_count = total_count - togo;
+       KDBUS_ITEM_FOREACH(item, msg, items)
+               if (KDBUS_ITEM_PAYLOAD_MEMFD == item->type) {
+                       int fd;
+                       ASSERT_NONZERO(togo);
+                       fd = item->memfd.fd;
+                       if (-1 != fd) {
+                               typeof(value_checked_in) *buf;
+                               off_t size;
+                               --togo;
+                               ASSERT_ZERO(sys_memfd_get_size(fd, &size));
+                               ASSERT_RETURN(size,==,(typeof(size))sizeof(value_checked_in));
+                               ASSERT_RETURN((uint64_t)size,==,item->memfd.size);
+                               ASSERT_RETURN(MAP_FAILED, !=, (buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)));
+                               ASSERT_RETURN(*buf,==,++value_checked_in);
+                               ASSERT_ZERO(munmap(buf, size));
+                       }
+               }
+       ASSERT_ZERO(togo);
+       value_checked_in += surplus_count;
+       return 0;
+}
+
+#define CHECK_FDS(ARRAY,COUNT,MSG) ASSERT_ZERO(check_fds((COUNT), ELEMENTSOF((ARRAY)), (MSG)))
+#define CHECK_MEMFDS(ARRAY,COUNT,MSG) ASSERT_ZERO(check_memfds((COUNT), ELEMENTSOF((ARRAY)), (MSG)))
+
+static wur int kdbus_send_multiple_fds(struct kdbus_conn *conn_src,
+                                  struct kdbus_conn *conn_dst)
+{
+       D_FDS(fds, KDBUS_CONN_MAX_FDS_PER_USER + 1);
+       D_FDS(memfds, KDBUS_MSG_MAX_ITEMS + 1);
+       struct kdbus_msg *msg;
+
+       MAKE_FDS(fds);
+
+       /* Send KDBUS_CONN_MAX_FDS_PER_USER with one more fd */
+       ASSERT_RETURN(-EMFILE,==,send_fds(conn_src, conn_dst->id, fds, KDBUS_CONN_MAX_FDS_PER_USER + 1));
+
+       /* Retry with the correct KDBUS_CONN_MAX_FDS_PER_USER */
+       ASSERT_ZERO(send_fds(conn_src, conn_dst->id, fds, KDBUS_CONN_MAX_FDS_PER_USER));
+       ASSERT_ZERO(kdbus_msg_recv(conn_dst, &msg, NULL));
+       CHECK_FDS(fds, KDBUS_CONN_MAX_FDS_PER_USER, msg);
+       kdbus_msg_free(msg);
+
+       MAKE_MEMFDS(memfds);
+
+       /* Send KDBUS_MSG_MAX_ITEMS with one more memfd */
+       ASSERT_RETURN(-E2BIG,==,send_memfds(conn_src, conn_dst->id, memfds, KDBUS_MSG_MAX_ITEMS + 1));
+       ASSERT_RETURN(-E2BIG,==,send_memfds(conn_src, conn_dst->id, memfds, KDBUS_MSG_MAX_MEMFD_ITEMS + 1));
+
+       /* Retry with the correct KDBUS_MSG_MAX_ITEMS */
+       ASSERT_ZERO(send_memfds(conn_src, conn_dst->id, memfds, KDBUS_MSG_MAX_MEMFD_ITEMS));
+       ASSERT_ZERO(kdbus_msg_recv(conn_dst, &msg, NULL));
+
+       /* Check we got the right number of fds */
+       CHECK_MEMFDS(memfds, KDBUS_MSG_MAX_MEMFD_ITEMS, msg);
+       kdbus_msg_free(msg);
+
+       /* reinitialize descriptors and cookies to check no stale data gets passed */
+       REMAKE_FDS(fds);
+       REMAKE_MEMFDS(memfds);
+
+       /*
+        * Combine multiple KDBUS_CONN_MAX_FDS_PER_USER+1 fds and
+        * 10 memfds
+        */
+       ASSERT_RETURN(-EMFILE,==,send_fds_memfds(conn_src, conn_dst->id, fds, KDBUS_CONN_MAX_FDS_PER_USER + 1, memfds, 10));
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(conn_dst, NULL, NULL));
+
+       /*
+        * Combine multiple KDBUS_CONN_MAX_FDS_PER_USER fds and
+        * (128 - 1) + 1 memfds, all fds take one item, while each
+        * memfd takes one item
+        */
+       ASSERT_RETURN(-E2BIG,==,send_fds_memfds(conn_src, conn_dst->id, fds, KDBUS_CONN_MAX_FDS_PER_USER, memfds, (KDBUS_MSG_MAX_ITEMS - 1) + 1));
+       ASSERT_RETURN(-E2BIG,==,send_fds_memfds(conn_src, conn_dst->id, fds, KDBUS_CONN_MAX_FDS_PER_USER, memfds, KDBUS_MSG_MAX_MEMFD_ITEMS + 1));
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(conn_dst, NULL, NULL));
+
+       /*
+        * Send KDBUS_CONN_MAX_FDS_PER_USER fds +
+        * KDBUS_MSG_MAX_MEMFD_ITEMS memfds
+        */
+       ASSERT_ZERO(send_fds_memfds(conn_src, conn_dst->id, fds, KDBUS_CONN_MAX_FDS_PER_USER, memfds, KDBUS_MSG_MAX_MEMFD_ITEMS));
+
+       ASSERT_ZERO(kdbus_msg_recv(conn_dst, &msg, NULL));
+
+       /* Check we got the right number of fds */
+       CHECK_FDS(fds, KDBUS_CONN_MAX_FDS_PER_USER, msg);
+       CHECK_MEMFDS(memfds, KDBUS_MSG_MAX_MEMFD_ITEMS, msg);
+       kdbus_msg_free(msg);
+
+       /* reinitialize descriptors and cookies to check no stale data gets passed */
+       REMAKE_FDS(fds);
+       REMAKE_MEMFDS(memfds);
+
+       /*
+        * Re-send fds + memfds, close them, but do not receive them
+        * and try to queue more
+        */
+       ASSERT_ZERO(send_fds_memfds(conn_src, conn_dst->id, fds, KDBUS_CONN_MAX_FDS_PER_USER, memfds, KDBUS_MSG_MAX_MEMFD_ITEMS));
+
+       /* close old references and get a new ones */
+       REMAKE_FDS(fds);
+
+       /* should fail since we have already fds in the queue */
+       ASSERT_RETURN(-EMFILE,==,send_fds(conn_src, conn_dst->id, fds, KDBUS_CONN_MAX_FDS_PER_USER));
+
+       /* This should succeed */
+       ASSERT_ZERO(send_memfds(conn_src, conn_dst->id, memfds, KDBUS_MSG_MAX_MEMFD_ITEMS));
+       ASSERT_ZERO(kdbus_msg_recv(conn_dst, &msg, NULL));
+       CHECK_FDS(fds, KDBUS_CONN_MAX_FDS_PER_USER, msg);
+       CHECK_MEMFDS(memfds, KDBUS_MSG_MAX_MEMFD_ITEMS, msg);
+       kdbus_msg_free(msg);
+
+       ASSERT_ZERO(kdbus_msg_recv(conn_dst, &msg, NULL));
+       /* retransmission of memfds from before - roll back the counter before checking */
+       value_checked_in -= ELEMENTSOF(memfds);
+       CHECK_MEMFDS(memfds, KDBUS_MSG_MAX_MEMFD_ITEMS, msg);
+       kdbus_msg_free(msg);
+
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(conn_dst, NULL, NULL));
+
+       CLOSE_FDS(fds);
+       CLOSE_FDS(memfds);
+
+       return 0;
+}
+
+wur int kdbus_test_fd_passing(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn_src, *conn_dst;
+       const char *str = "stackenblocken";
+       const struct kdbus_item *item;
+       struct kdbus_msg *msg;
+       unsigned int i;
+       uint64_t now;
+       int fds_conn[2];
+       int sock_pair[2];
+       int fds[2];
+       int memfd;
+       int ret;
+
+       now = (uint64_t) time(NULL);
+
+       /* create two connections */
+       ASSERT_NONZERO(conn_src = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(conn_dst = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       fds_conn[0] = conn_src->fd;
+       fds_conn[1] = conn_dst->fd;
+
+       ASSERT_ZERO(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair));
+
+       /* Setup memfd */
+       memfd = memfd_write("memfd-name", &now, sizeof(now));
+       ASSERT_RETURN(memfd,>=,0);
+
+       /* Setup pipes */
+       while ((ret = pipe(fds))) /* work around intermittent pipe failure on tizen 3.0 */
+               sleep(1);
+       ASSERT_ZERO(ret);
+
+       i = write(fds[1], str, strlen(str));
+       ASSERT_RETURN(i,==,strlen(str));
+
+       /*
+        * Try to pass the handle of a connection as message payload.
+        * This must fail.
+        */
+       ASSERT_RETURN(-ENOTSUP,==,send_fds(conn_src, conn_dst->id, fds_conn, 2));
+       ASSERT_RETURN(-ENOTSUP,==,send_fds(conn_dst, conn_src->id, fds_conn, 2));
+
+       /*
+        * Tizen: socket passing is allowed.
+        */
+       ASSERT_ZERO(send_fds(conn_src, conn_dst->id, sock_pair, 2));
+       ASSERT_ZERO(kdbus_msg_recv(conn_dst, &msg, NULL));
+       KDBUS_ITEM_FOREACH(item, msg, items)
+               if (item->type == KDBUS_ITEM_FDS) {
+                       int nfds = (item->size - KDBUS_ITEM_HEADER_SIZE) /
+                                       sizeof(int);
+                       ASSERT_RETURN(nfds,==,2);
+               }
+       kdbus_msg_free(msg);
+
+       /*
+        * Send fds and memfds to connection that do not accept fds
+        */
+       ASSERT_ZERO(kdbus_test_no_fds(env, fds, (int *)&memfd));
+
+       /* Try to broadcast file descriptors. This must fail. */
+       ASSERT_RETURN(-ENOTUNIQ,==,send_fds(conn_src, KDBUS_DST_ID_BROADCAST, fds, 1));
+
+       /* Try to broadcast memfd. This must succeed. */
+       ASSERT_ZERO(send_memfds(conn_src, KDBUS_DST_ID_BROADCAST, (int *)&memfd, 1));
+
+       /* Open code this loop */
+loop_send_fds:
+
+       /*
+        * Send the read end of the pipe and close it.
+        */
+       ASSERT_ZERO(send_fds(conn_src, conn_dst->id, fds, 1));
+       CLOSE(fds[0]);
+
+       ASSERT_ZERO(kdbus_msg_recv(conn_dst, &msg, NULL));
+
+       KDBUS_ITEM_FOREACH(item, msg, items) {
+               if (item->type == KDBUS_ITEM_FDS) {
+                       char tmp[14];
+                       int nfds = (item->size - KDBUS_ITEM_HEADER_SIZE) /
+                                       sizeof(int);
+                       ASSERT_RETURN(sizeof(tmp),==,strlen(str));
+                       ASSERT_RETURN(nfds,==,1);
+
+                       i = read(item->fds[0], tmp, sizeof(tmp));
+                       if (i != 0) {
+                               ASSERT_RETURN(i,==,sizeof(tmp));
+                               ASSERT_ZERO(memcmp(tmp, str, sizeof(tmp)));
+
+                               /* Write EOF */
+                               CLOSE(fds[1]);
+
+                               /*
+                                * Resend the read end of the pipe,
+                                * the receiver still holds a reference
+                                * to it...
+                                */
+                               goto loop_send_fds;
+                       }
+
+                       /* Got EOF */
+
+                       /*
+                        * Close the last reference to the read end
+                        * of the pipe, other references are
+                        * automatically closed just after send.
+                        */
+                       CLOSE(item->fds[0]);
+               }
+       }
+
+       /*
+        * Try to resend the read end of the pipe. Must fail with
+        * -EBADF since both the sender and receiver closed their
+        * references to it. We assume the above since sender and
+        * receiver are on the same process.
+        */
+       ASSERT_RETURN(-EBADF,==,send_fds(conn_src, conn_dst->id, fds, 1));
+
+       /* Then we clear out received any data... */
+
+       ASSERT_ZERO(kdbus_send_multiple_fds(conn_src, conn_dst));
+
+       CLOSE(sock_pair[0]);
+       CLOSE(sock_pair[1]);
+       CLOSE(memfd);
+
+       kdbus_conn_free(conn_src);
+       kdbus_conn_free(conn_dst);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-free.c b/tests/kdbus/test-free.c
new file mode 100644 (file)
index 0000000..2ec652f
--- /dev/null
@@ -0,0 +1,111 @@
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdbool.h>
+
+#include "kdbus-api.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+#include "kdbus-test.h"
+
+wur static int sample_ioctl_call(struct kdbus_test_env *env, unsigned *poff, unsigned *psize)
+{
+       unsigned off, size;
+       struct kdbus_cmd_list cmd_list = {
+               .flags = KDBUS_LIST_UNIQUE,
+               .size = sizeof(cmd_list),
+       };
+
+       /* DON'T FREE THIS SLICE OF MEMORY! */
+       ASSERT_ZERO(kdbus_cmd_list(env->conn->fd, &cmd_list));
+
+       #define A(L,R) do { ASSERT_RETURN((typeof(L))(R),==,(R)); *p##L = L = (R); } while (0)
+               A(off, cmd_list.offset);
+               ASSERT_RETURN(off,<,POOL_SIZE);
+               A(size, ((struct kdbus_info *)((uintptr_t)env->conn->buf + off))->size);
+       #undef A
+
+       ASSERT_RETURN(off+size,<,POOL_SIZE);
+       ASSERT_RETURN(off+size,>=,size);
+       ASSERT_RETURN(off+size,>,off);
+       ASSERT_ZERO(size % 8);
+       ASSERT_RETURN(size,>=,sizeof(struct kdbus_info));
+
+       /*print("off(%u) size(%u)\n", *offset, *size);*/
+
+       return TEST_OK;
+}
+
+wur static int area_before(unsigned off_left, unsigned size_left, unsigned off_right)
+{
+       ASSERT_RETURN(off_left+size_left,<=,off_right);
+       return 0;
+}
+
+int kdbus_test_free(struct kdbus_test_env *env)
+{
+       struct kdbus_cmd_free cmd_free = {};
+       unsigned offset[20], size[TABSIZE(offset)];
+       unsigned i;
+
+       /* free an unallocated buffer */
+       cmd_free.size = sizeof(cmd_free);
+       cmd_free.flags = 0;
+       cmd_free.offset = 0;
+       ASSERT_RETURN(-ENXIO,==,kdbus_cmd_free(env->conn->fd, &cmd_free));
+
+       /* free a buffer out of the pool's bounds */
+       cmd_free.size = sizeof(cmd_free);
+       cmd_free.offset = POOL_SIZE + 1;
+       ASSERT_RETURN(-ENXIO,==,kdbus_cmd_free(env->conn->fd, &cmd_free));
+
+       /*
+        * The user application is responsible for freeing the allocated
+        * memory with the KDBUS_CMD_FREE ioctl, so let's test what happens
+        * if we forget about it.
+        *
+        * Assert that memory areas don't overlap.
+        */
+
+       for (i=0; i<TABSIZE(offset); ++i) {
+               unsigned off, siz;
+               int j;
+               ASSERT_ZERO(sample_ioctl_call(env, &off, &siz));
+               for (j=i-1; j>=0; --j) {
+                       if (offset[j] > off) {
+                               ASSERT_ZERO(area_before(off,siz,offset[j]));
+                               offset[j+1] = offset[j];
+                               size[j+1] = size[j];
+                       } else {
+                               ASSERT_ZERO(area_before(offset[j],size[j],off));
+                               break;
+                       }
+               }
+               ASSERT_RETURN(j+1,>=,0);
+               ASSERT_RETURN((unsigned)(j+1),<=,i);
+               offset[j+1] = off;
+               size[j+1] = siz;
+       }
+
+       /* attempt to free invalid offsets around the ones above to test the underlying allocator */
+       {
+               unsigned j, last = offset[TABSIZE(offset)-1] + size[TABSIZE(offset)-1] + 1;
+               for (i=j=0; i<last; ++i) {
+                       if (i == offset[j]) {
+                               ++i;
+                               ++j;
+                       }
+                       cmd_free.size = sizeof(cmd_free);
+                       cmd_free.offset = i;
+                       ASSERT_RETURN(-ENXIO,==,kdbus_cmd_free(env->conn->fd, &cmd_free));
+               }
+       }
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-match.c b/tests/kdbus/test-match.c
new file mode 100644 (file)
index 0000000..6c35baf
--- /dev/null
@@ -0,0 +1,580 @@
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdbool.h>
+
+#include "kdbus-api.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+#include "kdbus-test.h"
+
+static wur int add_id_match_(struct kdbus_conn *conn, uint64_t flags, uint64_t notification_type, uint64_t id)
+{
+       struct {
+               struct kdbus_cmd_match cmd;
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       struct kdbus_notify_id_change chg;
+               } item;
+       } buf;
+       memset(&buf, 0, sizeof(buf));
+       buf.cmd.size = sizeof(buf);
+       buf.cmd.cookie = 0xdeafbeefdeaddead;
+       buf.cmd.flags = flags;
+       buf.item.size = sizeof(buf.item);
+       buf.item.type = notification_type;
+       buf.item.chg.id = id;
+       ASSERT_ZERO(kdbus_cmd_match_add(conn->fd, &buf.cmd));
+       return 0;
+}
+#define ADD_ID_MATCH(CONN,FLAGS,NOTIFICATION_TYPE,ID)\
+       ASSERT_ZERO(add_id_match_((CONN),(FLAGS),KDBUS_ITEM_ID_##NOTIFICATION_TYPE,(ID)))
+
+wur int kdbus_test_match_id_add(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       struct kdbus_msg *msg;
+
+       /* match on id add */
+       ADD_ID_MATCH(env->conn, 0, ADD, KDBUS_MATCH_ID_ANY);
+
+       /* create 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* 1st connection should have received a notification */
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+       ASSERT_NO_PENDING(env->conn);
+
+       ASSERT_RETURN(msg->items[0].type,==,(typeof(msg->items[0].type))KDBUS_ITEM_ID_ADD);
+       ASSERT_RETURN(msg->items[0].id_change.id,==,conn->id);
+
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_match_id_remove(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       struct kdbus_msg *msg;
+       size_t id;
+
+       /* create 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+       id = conn->id;
+
+       /* register match on 1st connection */
+       ADD_ID_MATCH(env->conn, 0, REMOVE, id);
+
+       /* remove 2nd connection again */
+       kdbus_conn_free(conn);
+
+       /* 1st connection should have received a notification */
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+
+       ASSERT_RETURN(msg->items[0].type,==,(typeof(msg->items[0].type))KDBUS_ITEM_ID_REMOVE);
+       ASSERT_RETURN(msg->items[0].id_change.id,==,id);
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_match_replace(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       struct kdbus_msg *msg;
+       size_t id;
+
+       /* add a match to id_add */
+       ASSERT_RETURN(kdbus_test_match_id_add(env),==,TEST_OK);
+
+       /* do a replace of the match from id_add to id_remove */
+       ADD_ID_MATCH(env->conn, KDBUS_MATCH_REPLACE, REMOVE, KDBUS_MATCH_ID_ANY);
+
+       /* create 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+       id = conn->id;
+
+       /* 1st connection should _not_ have received a notification */
+       ASSERT_NONZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+
+       /* remove 2nd connection */
+       kdbus_conn_free(conn);
+
+       /* 1st connection should _now_ have received a notification */
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+
+       ASSERT_RETURN(msg->items[0].type,==,(typeof(msg->items[0].type))KDBUS_ITEM_ID_REMOVE);
+       ASSERT_RETURN(msg->items[0].id_change.id,==,id);
+
+       return TEST_OK;
+}
+
+#define UNMATCHED_NAME "this.will.never.match"
+
+static wur int add_name_match_(struct kdbus_conn *conn, uint64_t flags, uint64_t old_id, uint64_t new_id, uint64_t notification_type, char const *name)
+{
+       struct {
+               struct kdbus_cmd_match cmd;
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       struct kdbus_notify_name_change chg;
+               } item;
+               char name[64];
+       } buf;
+       memset(&buf, 0, sizeof(buf));
+       buf.cmd.flags = flags;
+       buf.cmd.cookie = 0xdeaddeaddeafbeef;
+       buf.item.type = notification_type;
+       buf.item.chg.old_id.id = old_id;
+       buf.item.chg.new_id.id = new_id;
+       buf.item.size = sizeof(buf.item);
+       if (name) {
+               __auto_type name_size = strlen(name)+1;
+               memcpy(buf.name, name, name_size);
+               buf.item.size += name_size;
+       }
+       buf.cmd.size = sizeof(buf.cmd) + buf.item.size;
+       ASSERT_ZERO(kdbus_cmd_match_add(conn->fd, &buf.cmd));
+       return 0;
+}
+#define ADD_NAME_MATCH(CONN,FLAGS,OLD_ID,NEW_ID,NOTIFICATION_TYPE,NAME)\
+       ASSERT_ZERO(add_name_match_((CONN),(FLAGS),(OLD_ID),(NEW_ID),KDBUS_ITEM_NAME_##NOTIFICATION_TYPE,(NAME)))
+
+static wur int assert_single_match_(struct kdbus_conn *conn, uint64_t old_id, uint64_t new_id, uint64_t notification_type, char const *name)
+{
+       struct kdbus_msg *msg;
+       ASSERT_ZERO(kdbus_msg_recv(conn, &msg, NULL));
+       ASSERT_RETURN(msg->items[0].type,==,notification_type);
+       ASSERT_RETURN(msg->items[0].name_change.old_id.id,==,old_id);
+       ASSERT_RETURN(msg->items[0].name_change.new_id.id,==,new_id);
+       ASSERT_ZERO(strcmp(msg->items[0].name_change.name, name));
+       ASSERT_ZERO(kdbus_free_msg(conn, msg));
+       ASSERT_NO_PENDING(conn);
+       return 0;
+}
+#define ASSERT_SINGLE_MATCH(CONN,OLD_ID,NEW_ID,NOTIFICATION_TYPE,NAME)\
+       ASSERT_ZERO(assert_single_match_((CONN),(OLD_ID),(NEW_ID),KDBUS_ITEM_NAME_##NOTIFICATION_TYPE,(NAME)))
+
+static wur int kdbus_test_match_name_add_(struct kdbus_test_env *env, char const *name, struct kdbus_conn *listener)
+{
+       /* acquire the name */
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, name, NULL));
+
+       #define CHECK(CONN) ASSERT_SINGLE_MATCH((CONN), 0, env->conn->id, ADD, name)
+               CHECK(env->conn);
+               if (listener)
+                       CHECK(listener);
+       #undef CHECK
+
+       ASSERT_ZERO(kdbus_name_release(env->conn, name));
+
+       return 0;
+}
+
+wur int kdbus_test_match_name_add(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *listener;
+       char const *name = "foo.bla.blaz";
+
+       ASSERT_NONZERO(listener = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* install the match rule */
+       ADD_NAME_MATCH(env->conn, 0, KDBUS_MATCH_ID_ANY, KDBUS_MATCH_ID_ANY, ADD, name);
+
+       /* no accidental notifications when no match set up */
+       ASSERT_ZERO(kdbus_test_match_name_add_(env, name, NULL));
+       ASSERT_NO_PENDING(listener);
+
+       #define CHECK(ID,NAME,CONN_IF_WILL_MATCH) do {\
+               ADD_NAME_MATCH(listener, KDBUS_MATCH_REPLACE, KDBUS_MATCH_ID_ANY, (ID), ADD, NAME);\
+               ASSERT_ZERO(kdbus_test_match_name_add_(env, name, (CONN_IF_WILL_MATCH)));\
+               ASSERT_NO_PENDING(listener);\
+       } while (0)
+               CHECK(KDBUS_MATCH_ID_ANY, UNMATCHED_NAME, NULL); /* wrong name */
+               CHECK(KDBUS_MATCH_ID_ANY, NULL, listener); /* wildcard name */
+               CHECK(env->conn->id, NULL, listener); /* wildcard name + good id */
+               CHECK(env->conn->id + 1, NULL, NULL); /* wildcard name + bad id */
+       #undef CHECK
+       kdbus_conn_free(listener);
+
+       return 0;
+}
+
+static wur int kdbus_test_match_name_remove_(struct kdbus_test_env *env, char const *name, struct kdbus_conn *listener)
+{
+       /* acquire the name */
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, name, NULL));
+
+       /* release the name again */
+       ASSERT_ZERO(kdbus_name_release(env->conn, name));
+
+       /* we should have received a notification */
+       #define CHECK(CONN) ASSERT_SINGLE_MATCH((CONN), env->conn->id, 0, REMOVE, name)
+               CHECK(env->conn);
+               if (listener)
+                       CHECK(listener);
+       #undef CHECK
+
+       return 0;
+}
+
+wur int kdbus_test_match_name_remove(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *listener;
+       char const *name = "foo.bla.blazz";
+
+       ASSERT_NONZERO(listener = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* install the match rule */
+       ADD_NAME_MATCH(env->conn, 0, KDBUS_MATCH_ID_ANY, KDBUS_MATCH_ID_ANY, REMOVE, name);
+
+       /* no accidental notifications when no match set up */
+       ASSERT_ZERO(kdbus_test_match_name_remove_(env, name, NULL));
+       ASSERT_NO_PENDING(listener);
+
+       #define CHECK(ID,NAME,CONN_IF_WILL_MATCH) do {\
+               ADD_NAME_MATCH(listener, KDBUS_MATCH_REPLACE, (ID), KDBUS_MATCH_ID_ANY, REMOVE, NAME);\
+               ASSERT_ZERO(kdbus_test_match_name_remove_(env, name, (CONN_IF_WILL_MATCH)));\
+               ASSERT_NO_PENDING(listener);\
+       } while (0)
+               CHECK(KDBUS_MATCH_ID_ANY, UNMATCHED_NAME, NULL); /* wrong name */
+               CHECK(KDBUS_MATCH_ID_ANY, NULL, listener); /* wildcard name */
+               CHECK(env->conn->id, NULL, listener); /* wildcard name + good id */
+               CHECK(env->conn->id + 1, NULL, NULL); /* wildcard name + bad id */
+       #undef CHECK
+       kdbus_conn_free(listener);
+
+       return 0;
+}
+
+static wur int kdbus_test_match_name_change_(struct kdbus_test_env *env, struct kdbus_conn *conn, char const *name, struct kdbus_conn *listener)
+{
+       uint64_t flags;
+
+       /* acquire the name */
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, name, NULL));
+
+       /* allow the new connection to own the same name */
+       /* queue the 2nd connection as waiting owner */
+       flags = KDBUS_NAME_QUEUE;
+       ASSERT_ZERO(kdbus_name_acquire(conn, name, &flags));
+       ASSERT_NONZERO(flags & KDBUS_NAME_IN_QUEUE);
+
+       /* release name from 1st connection */
+       ASSERT_ZERO(kdbus_name_release(env->conn, name));
+
+       /* we should have received a notification */
+       #define CHECK(CONN) ASSERT_SINGLE_MATCH((CONN), env->conn->id, conn->id, CHANGE, name)
+               CHECK(env->conn);
+               if (listener)
+                       CHECK(listener);
+       #undef CHECK
+
+       ASSERT_ZERO(kdbus_name_release(conn, name));
+
+       return 0;
+}
+
+wur int kdbus_test_match_name_change(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *listener, *conn;
+       char const *name = "foo.bla.baz";
+
+       /* create a 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ASSERT_NONZERO(listener = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ADD_NAME_MATCH(env->conn, 0, KDBUS_MATCH_ID_ANY, KDBUS_MATCH_ID_ANY, CHANGE, name);
+
+       /* no accidental notifications when no match set up */
+       ASSERT_ZERO(kdbus_test_match_name_change_(env, conn, name, NULL));
+       ASSERT_NO_PENDING(listener);
+
+       #define CHECK(OLD_ID,NEW_ID,NAME,CONN_IF_WILL_MATCH) do {\
+               ADD_NAME_MATCH(listener, KDBUS_MATCH_REPLACE, (OLD_ID), (NEW_ID), CHANGE, NAME);\
+               ASSERT_ZERO(kdbus_test_match_name_change_(env, conn, name, (CONN_IF_WILL_MATCH)));\
+               ASSERT_NO_PENDING(listener);\
+       } while (0)
+               CHECK(KDBUS_MATCH_ID_ANY, KDBUS_MATCH_ID_ANY, UNMATCHED_NAME, NULL); /* wrong name */
+               CHECK(KDBUS_MATCH_ID_ANY, KDBUS_MATCH_ID_ANY, NULL, listener); /* wildcard name */
+
+               CHECK(env->conn->id, KDBUS_MATCH_ID_ANY, NULL, listener); /* wildcard name + good id */
+               CHECK(env->conn->id + 1, KDBUS_MATCH_ID_ANY, NULL, NULL); /* wildcard name + bad id */
+
+               CHECK(KDBUS_MATCH_ID_ANY, conn->id, NULL, listener); /* good id */
+               CHECK(KDBUS_MATCH_ID_ANY, conn->id + 1, NULL, NULL); /* bad id */
+
+               CHECK(env->conn->id, conn->id, NULL, listener); /* wildcard name + good id */
+
+               CHECK(env->conn->id + 1, conn->id, NULL, NULL); /* wildcard name + bad id */
+               CHECK(env->conn->id, conn->id + 1, NULL, NULL); /* wildcard name + bad id */
+       #undef CHECK
+       kdbus_conn_free(listener);
+       ASSERT_NO_PENDING(conn);
+       kdbus_conn_free(conn);
+
+       return 0;
+}
+
+static wur int send_bloom_filter(const struct kdbus_conn *conn,
+                            uint64_t cookie,
+                            const uint8_t *filter,
+                            size_t filter_size,
+                            uint64_t filter_generation)
+{
+       struct kdbus_cmd_send cmd = {};
+       struct kdbus_msg *msg;
+       struct kdbus_item *item;
+       uint64_t size;
+       int ret;
+
+       size = sizeof(struct kdbus_msg);
+       size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + filter_size;
+
+       msg = alloca(size);
+
+       memset(msg, 0, size);
+       msg->size = size;
+       msg->src_id = conn->id;
+       msg->dst_id = KDBUS_DST_ID_BROADCAST;
+       msg->flags = KDBUS_MSG_SIGNAL;
+       msg->payload_type = KDBUS_PAYLOAD_DBUS;
+       msg->cookie = cookie;
+
+       item = msg->items;
+       item->type = KDBUS_ITEM_BLOOM_FILTER;
+       item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) +
+                               filter_size;
+
+       item->bloom_filter.generation = filter_generation;
+       memcpy(item->bloom_filter.data, filter, filter_size);
+
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg;
+
+       ret = kdbus_cmd_send(conn->fd, &cmd);
+       if (ret < 0) {
+               kdbus_printf("error sending message: %d (%m)\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+wur int kdbus_test_match_bloom(struct kdbus_test_env *env)
+{
+       struct {
+               struct kdbus_cmd_match cmd;
+               struct {
+                       uint64_t size;
+                       uint64_t type;
+                       uint8_t data_gen0[64];
+                       uint8_t data_gen1[64];
+               } item;
+       } buf;
+       struct kdbus_conn *conn;
+       struct kdbus_msg *msg;
+       uint64_t cookie = 0xf000f00f;
+       uint8_t filter[64];
+
+       /* install the match rule */
+       memset(&buf, 0, sizeof(buf));
+       buf.cmd.size = sizeof(buf);
+
+       buf.item.size = sizeof(buf.item);
+       buf.item.type = KDBUS_ITEM_BLOOM_MASK;
+       buf.item.data_gen0[0] = 0x55;
+       buf.item.data_gen0[63] = 0x80;
+
+       buf.item.data_gen1[1] = 0xaa;
+       buf.item.data_gen1[9] = 0x02;
+
+       ASSERT_ZERO(kdbus_cmd_match_add(env->conn->fd, &buf.cmd));
+
+       /* create a 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* a message with a 0'ed out filter must not reach the other peer */
+       memset(filter, 0, sizeof(filter));
+       ASSERT_ZERO(send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 0));
+
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(env->conn, &msg, NULL));
+
+       /* now set the filter to the connection's mask and expect success */
+       filter[0] = 0x55;
+       filter[63] = 0x80;
+       ASSERT_ZERO(send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 0));
+
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       /* broaden the filter and try again. this should also succeed. */
+       filter[0] = 0xff;
+       filter[8] = 0xff;
+       filter[63] = 0xff;
+       ASSERT_ZERO(send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 0));
+
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       /* the same filter must not match against bloom generation 1 */
+       ASSERT_ZERO(send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 1));
+
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(env->conn, &msg, NULL));
+
+       /* set a different filter and try again */
+       filter[1] = 0xaa;
+       filter[9] = 0x02;
+       ASSERT_ZERO(send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 1));
+
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
+
+static wur int has_all_names(struct kdbus_item *item, unsigned size)
+{
+       char seen[256];
+       int left = sizeof(seen);
+       memset(seen, 0, sizeof(seen));
+
+       while (size && left) {
+               uint64_t item_size = KDBUS_ALIGN8(item->size);
+               if (item_size > size)
+                       return -1;
+               if (KDBUS_ITEM_OWNED_NAME == item->type) {
+                       int i;
+                       ASSERT_RETURN(255u,==,strlen(item->name.name));
+                       ASSERT_RETURN(0,==,memcmp(item->name.name, "big.n", 5));
+                       for (i=5;i<252;++i)
+                               ASSERT_RETURN('0',==,(int)item->name.name[i]);
+                       ASSERT_RETURN('0',<=,(int)item->name.name[252]);
+                       ASSERT_RETURN((int)item->name.name[252],<=,'9');
+                       ASSERT_RETURN('0',<=,(int)item->name.name[253]);
+                       ASSERT_RETURN((int)item->name.name[253],<=,'9');
+                       ASSERT_RETURN('0',<=,(int)item->name.name[254]);
+                       ASSERT_RETURN((int)item->name.name[254],<=,'9');
+                       i = item->name.name[254]-'0' + 10*(item->name.name[253]-'0') + 100*(item->name.name[252]-'0');
+                       ASSERT_RETURN(0,<=,i);
+                       ASSERT_RETURN(i,<,256);
+                       ASSERT_ZERO((int)seen[i]);
+                       seen[i] = 1;
+                       --left;
+               }
+               size -= (unsigned)item_size;
+               item = (typeof(item))((uintptr_t)item + (unsigned)item_size);
+       }
+
+       return left;
+}
+
+wur int kdbus_test_big_metadata(struct kdbus_test_env *env)
+{
+       uint64_t offset;
+       struct kdbus_conn *conn;
+       struct kdbus_msg *msg;
+       struct kdbus_info *info;
+       int i;
+       char buf[256];
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+       for (i=256; --i>=0;) {
+               sprintf(buf, "big.n%0250d", i);
+               /*print("\nreg (%s)\n", buf);*/
+               ASSERT_ZERO(kdbus_name_acquire(conn, buf, NULL));
+       }
+
+       /*print("\nreg done\n");*/
+
+       ASSERT_ZERO(kdbus_msg_send(conn, NULL, 1, 0, 0, 0, env->conn->id));
+       /*print("\nsend done\n");*/
+       /*print("\nfree done\n");*/
+
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+       /*print("\nrecv done\n");*/
+
+       ASSERT_ZERO(has_all_names(msg->items, msg->size - offsetof(typeof(*msg), items)));
+
+       kdbus_msg_free(msg);
+
+       ASSERT_ZERO(kdbus_conn_info(env->conn, conn->id, NULL, _KDBUS_ATTACH_ALL, &offset));
+       info = (struct kdbus_info *)(env->conn->buf + offset);
+       ASSERT_RETURN(info->id,==,conn->id);
+       ASSERT_ZERO(has_all_names(info->items, info->size - offsetof(typeof(*info), items)));
+
+       kdbus_conn_free(conn);
+
+       /*print("\nfree done\n");*/
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_match_itemless(struct kdbus_test_env *env)
+{
+       struct kdbus_cmd_match cmd;
+       struct kdbus_conn *conn;
+       struct kdbus_msg *msg;
+       uint64_t cookie = 0xf000f00f;
+       uint8_t filter[64];
+
+       /* install the match rule */
+       memset(&cmd, 0, sizeof(cmd));
+       cmd.size = sizeof(cmd);
+
+       ASSERT_ZERO(kdbus_cmd_match_add(env->conn->fd, &cmd));
+
+       /* create a 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* 1st connection should have received a notification */
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+       ASSERT_NO_PENDING(env->conn);
+       ASSERT_RETURN(msg->items[0].type,==,(typeof(msg->items[0].type))KDBUS_ITEM_ID_ADD);
+       ASSERT_RETURN(msg->items[0].id_change.id,==,conn->id);
+
+       /* even a message with a 0'ed out filter must match */
+       memset(filter, 0, sizeof(filter));
+       ASSERT_ZERO(send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 0));
+
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+       ASSERT_NO_PENDING(env->conn);
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       ASSERT_ZERO(kdbus_name_acquire(conn, "mein.volk", NULL));
+       /* 1st connection should have received a notification */
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+       ASSERT_NO_PENDING(env->conn);
+       ASSERT_RETURN(msg->items[0].type,==,(typeof(msg->items[0].type))KDBUS_ITEM_NAME_ADD);
+       ASSERT_RETURN(msg->items[0].name_change.new_id.id,==,conn->id);
+
+       ASSERT_ZERO(kdbus_name_release(conn, "mein.volk"));
+       /* 1st connection should have received a notification */
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+       ASSERT_NO_PENDING(env->conn);
+       ASSERT_RETURN(msg->items[0].type,==,(typeof(msg->items[0].type))KDBUS_ITEM_NAME_REMOVE);
+       ASSERT_RETURN(msg->items[0].name_change.old_id.id,==,conn->id);
+
+       kdbus_conn_free(conn);
+
+       /* 1st connection should have received a notification */
+       ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+       ASSERT_NO_PENDING(env->conn);
+       ASSERT_RETURN(msg->items[0].type,==,(typeof(msg->items[0].type))KDBUS_ITEM_ID_REMOVE);
+       ASSERT_RETURN(msg->items[0].id_change.id,==,conn->id);
+
+       ASSERT_NO_PENDING(env->conn);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-message.c b/tests/kdbus/test-message.c
new file mode 100644 (file)
index 0000000..aa12118
--- /dev/null
@@ -0,0 +1,704 @@
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <time.h>
+#include <stdbool.h>
+#include <sys/eventfd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "kdbus-api.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+#include "kdbus-test.h"
+
+/* maximum number of queued messages from the same individual user */
+#define KDBUS_CONN_MAX_MSGS                    256U
+
+/* maximum number of queued requests waiting for a reply */
+#define KDBUS_CONN_MAX_REQUESTS_PENDING                1024U
+
+/* maximum message payload size */
+#define KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE         (2 * 1024UL * 1024UL)
+
+wur int kdbus_test_message_basic(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       struct kdbus_conn *sender;
+       struct kdbus_msg *msg;
+       uint64_t cookie = 0x1234abcd5678eeff;
+       uint64_t offset;
+
+       ASSERT_NONZERO(sender = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* create a 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ASSERT_ZERO(kdbus_add_match_empty(conn));
+
+       ASSERT_ZERO(kdbus_add_match_empty(sender));
+
+       /* send over 1st connection */
+       ASSERT_ZERO(kdbus_msg_send(sender, NULL, cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+       /* Make sure that we do get our own broadcasts */
+       ASSERT_ZERO(kdbus_msg_recv(sender, &msg, &offset));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+       kdbus_msg_free(msg);
+
+       /* ... and receive on the 2nd */
+       ASSERT_ZERO(kdbus_msg_recv_poll(conn, 100, &msg, &offset));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       kdbus_msg_free(msg);
+
+       /* Msgs that expect a reply must have timeout and cookie */
+       ASSERT_RETURN(-EINVAL,==,kdbus_msg_send(sender, NULL, 0, KDBUS_MSG_EXPECT_REPLY, 1000000000, 0, conn->id)); /* no cookie */
+       ASSERT_RETURN(-EINVAL,==,kdbus_msg_send(sender, NULL, 1, KDBUS_MSG_EXPECT_REPLY, 0, 0, conn->id)); /* no timeout */
+       ASSERT_RETURN(-EINVAL,==,kdbus_msg_send(sender, NULL, 0, KDBUS_MSG_EXPECT_REPLY, 0, 0, conn->id)); /* neither cookie nor timeout */
+
+       /* Faked replies with a valid reply cookie are rejected iff not TIZEN */
+       ASSERT_RETURN(ONTIZEN(0,-EBADSLT),==,kdbus_msg_send_reply(conn, time(NULL) ^ cookie, sender->id));
+
+       ASSERT_ZERO(kdbus_free(conn, offset));
+
+       kdbus_conn_free(sender);
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
+
+static wur int msg_recv_prio(struct kdbus_conn *conn,
+                        int64_t requested_prio,
+                        int64_t expected_prio)
+{
+       struct kdbus_cmd_recv recv = {
+               .size = sizeof(recv),
+               .flags = KDBUS_RECV_USE_PRIORITY,
+               .priority = requested_prio,
+       };
+       struct kdbus_msg *msg;
+       int ret, dumpret;
+
+       ret = kdbus_cmd_recv(conn->fd, &recv);
+       if (ret < 0) {
+               kdbus_printf("error receiving message: %d (%m)\n", -errno);
+               return ret;
+       }
+
+       msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset);
+       dumpret = kdbus_msg_dump(msg);
+
+       if (msg->priority != expected_prio) {
+               kdbus_printf("expected message prio %lld, got %lld\n",
+                            (unsigned long long) expected_prio,
+                            (unsigned long long) msg->priority);
+               return -EINVAL;
+       }
+
+       kdbus_msg_free(msg);
+       ret = kdbus_free(conn, recv.msg.offset);
+       if (ret < 0)
+               return ret;
+       if (dumpret)
+               return dumpret;
+
+       return 0;
+}
+
+wur int kdbus_test_message_prio(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *a, *b;
+       uint64_t cookie = 0;
+
+       ASSERT_NONZERO(a = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(b = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0,   25, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -600, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0,   10, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0,  -35, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -100, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0,   20, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0,  -15, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -800, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -150, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0,   10, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0, -800, a->id));
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, ++cookie, 0, 0,  -10, a->id));
+
+       ASSERT_ZERO(msg_recv_prio(a, -200, -800));
+       ASSERT_ZERO(msg_recv_prio(a, -100, -800));
+       ASSERT_ZERO(msg_recv_prio(a, -400, -600));
+       ASSERT_RETURN(msg_recv_prio(a, -400, -600),==,-EAGAIN);
+       ASSERT_ZERO(msg_recv_prio(a, 10, -150));
+       ASSERT_ZERO(msg_recv_prio(a, 10, -100));
+
+       kdbus_printf("--- get priority (all)\n");
+       ASSERT_ZERO(kdbus_msg_recv(a, NULL, NULL));
+
+       kdbus_conn_free(a);
+       kdbus_conn_free(b);
+
+       return TEST_OK;
+}
+
+static wur int kdbus_test_notify_kernel_quota(struct kdbus_test_env *env)
+{
+       unsigned int i;
+       struct kdbus_conn *conn;
+       struct kdbus_conn *reader;
+       struct kdbus_msg *msg = NULL;
+       struct kdbus_cmd_recv recv = { .size = sizeof(recv) };
+
+       ASSERT_NONZERO(reader = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* Register for ID signals */
+       ASSERT_ZERO(kdbus_add_match_id(reader, 0x1, KDBUS_ITEM_ID_ADD, KDBUS_MATCH_ID_ANY));
+
+       ASSERT_ZERO(kdbus_add_match_id(reader, 0x2, KDBUS_ITEM_ID_REMOVE, KDBUS_MATCH_ID_ANY));
+
+       /* Each iteration two notifications: add and remove ID */
+       for (i = 0; i < KDBUS_CONN_MAX_MSGS / 2; i++) {
+               struct kdbus_conn *notifier;
+               ASSERT_NONZERO(notifier = kdbus_hello(env->buspath, 0, NULL, 0));
+               kdbus_conn_free(notifier);
+       }
+
+       /*
+        * Now the reader queue is full with kernel notfications,
+        * but as a user we still have room to push our messages.
+        */
+       ASSERT_ZERO(kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, 0, reader->id));
+
+       /* More ID kernel notifications that will be lost */
+       kdbus_conn_free(conn);
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       kdbus_conn_free(conn);
+
+       /*
+        * We lost only 3 packets since only signal msgs are
+        * accounted. The connection ID add/remove notification
+        */
+       ASSERT_ZERO(kdbus_cmd_recv(reader->fd, &recv));
+       ASSERT_NONZERO(recv.return_flags & KDBUS_RECV_RETURN_DROPPED_MSGS);
+       ASSERT_RETURN(recv.dropped_msgs,==,3U);
+
+       msg = (struct kdbus_msg *)(reader->buf + recv.msg.offset);
+       kdbus_msg_free(msg);
+
+       /* Read our queue */
+       for (i = 0; i < KDBUS_CONN_MAX_MSGS - 1; i++) {
+               memset(&recv, 0, sizeof(recv));
+               recv.size = sizeof(recv);
+
+               ASSERT_ZERO(kdbus_cmd_recv(reader->fd, &recv));
+               ASSERT_ZERO(recv.return_flags & KDBUS_RECV_RETURN_DROPPED_MSGS);
+
+               msg = (struct kdbus_msg *)(reader->buf + recv.msg.offset);
+               kdbus_msg_free(msg);
+       }
+
+       ASSERT_ZERO(kdbus_msg_recv(reader, NULL, NULL));
+
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(reader, NULL, NULL));
+
+       kdbus_conn_free(reader);
+
+       return 0;
+}
+
+/* Return the number of message successfully sent */
+static wur int kdbus_fill_conn_queue(struct kdbus_conn *conn_src,
+                                uint64_t dst_id,
+                                unsigned int max_msgs)
+{
+       unsigned int i;
+       uint64_t cookie = 0;
+       size_t size;
+       struct kdbus_cmd_send cmd = {};
+       struct kdbus_msg *msg;
+       int ret;
+
+       size = sizeof(struct kdbus_msg);
+       msg = malloc(size);
+       ASSERT_RETURN_VAL(msg,!=,NULL, -ENOMEM);
+
+       memset(msg, 0, size);
+       msg->size = size;
+       msg->src_id = conn_src->id;
+       msg->dst_id = dst_id;
+       msg->payload_type = KDBUS_PAYLOAD_DBUS;
+
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg;
+
+       for (i = 0; i < max_msgs; i++) {
+               msg->cookie = ++cookie;
+               ret = kdbus_cmd_send(conn_src->fd, &cmd);
+               if (ret < 0) {
+                       /*print("fill_conn_queue_senderr(%d)", ret);*/
+                       break;
+               }
+       }
+
+       free(msg);
+
+       return i;
+}
+
+wur int kdbus_test_activator_quota(struct kdbus_test_env *env)
+{
+       unsigned i, activator_msgs_count=0;
+       uint64_t cookie = time(NULL);
+       uint64_t flags = KDBUS_NAME_REPLACE_EXISTING;
+       struct kdbus_conn *conn;
+       struct kdbus_conn *sender;
+       struct kdbus_conn *activator;
+       struct kdbus_msg *msg;
+       struct kdbus_cmd_recv recv = { .size = sizeof(recv) };
+       struct kdbus_policy_access access = {
+               .type = KDBUS_POLICY_ACCESS_USER,
+               .id = geteuid(),
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       ASSERT_NONZERO(activator = kdbus_hello_activator(env->buspath, "foo.test.activator", &access, 1));
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(sender = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ASSERT_ZERO(kdbus_list(sender, KDBUS_LIST_NAMES | KDBUS_LIST_UNIQUE | KDBUS_LIST_ACTIVATORS | KDBUS_LIST_QUEUED));
+
+#define EXHAUST(EXPECTERR,SENDOP) do {\
+       for (i = 0;; ++i) {\
+               int ret = (SENDOP);\
+               ASSERT_RETURN(i,<=,KDBUS_CONN_MAX_MSGS);\
+               if (0 > ret) {\
+                       ASSERT_RETURN((EXPECTERR),==,ret);\
+                       ASSERT_RETURN(i,<,KDBUS_CONN_MAX_MSGS);\
+                       break;\
+               }\
+       }\
+} while (0)
+       EXHAUST(-ENOBUFS,kdbus_msg_send(sender, "foo.test.activator", ++cookie, 0, 0, 0, KDBUS_DST_ID_NAME));
+       activator_msgs_count = i;
+
+       /* we must have at least sent one message */
+       ASSERT_RETURN_VAL(i,>,0U, -errno);
+
+       /* Good, activator queue is full now */
+
+       /* ENXIO on direct send (activators can never be addressed by ID) */
+       ASSERT_RETURN(-ENXIO,==,kdbus_msg_send(conn, NULL, ++cookie, 0, 0, 0, activator->id));
+
+       /* can't queue more */
+       ASSERT_RETURN(-ENOBUFS,==,kdbus_msg_send(conn, "foo.test.activator", ++cookie, 0, 0, 0, KDBUS_DST_ID_NAME));
+
+       /* no match installed, so the broadcast will not inc dropped_msgs */
+       ASSERT_ZERO(kdbus_msg_send(conn, NULL, ++cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+       /* Check activator queue */
+       ASSERT_ZERO(kdbus_cmd_recv(activator->fd, &recv));
+       ASSERT_ZERO(recv.dropped_msgs);
+
+       msg = (struct kdbus_msg *)(activator->buf + recv.msg.offset);
+       ASSERT_RETURN(msg->src_id,==,sender->id);
+       ASSERT_RETURN(msg->dst_id,==,(typeof(msg->dst_id))KDBUS_DST_ID_NAME);
+       --activator_msgs_count;
+       kdbus_msg_free(msg);
+
+
+       /* Stage 1) of test check the pool memory quota */
+
+       /* Consume the connection pool memory */
+       EXHAUST(-ENOBUFS,kdbus_msg_send(conn, NULL, ++cookie, 0, 0, 0, conn->id));
+
+       /* consume one message, so later at least one can be moved */
+       memset(&recv, 0, sizeof(recv));
+       recv.size = sizeof(recv);
+       ASSERT_ZERO(kdbus_cmd_recv(conn->fd, &recv));
+       ASSERT_ZERO(recv.dropped_msgs);
+       msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset);
+       kdbus_msg_free(msg);
+
+       /* Try to acquire the name now */
+       ASSERT_ZERO(kdbus_name_acquire(conn, "foo.test.activator", &flags));
+
+       /* try to read messages and see if we have lost some */
+       memset(&recv, 0, sizeof(recv));
+       recv.size = sizeof(recv);
+       ASSERT_ZERO(kdbus_cmd_recv(conn->fd, &recv));
+       ASSERT_NONZERO(recv.dropped_msgs);
+
+       /* number of dropped msgs < received ones (at least one was moved) */
+       ASSERT_RETURN(recv.dropped_msgs,<,activator_msgs_count);
+
+       /* Deduct the number of dropped msgs from the activator msgs */
+       activator_msgs_count -= recv.dropped_msgs;
+
+       msg = (struct kdbus_msg *)(activator->buf + recv.msg.offset);
+       kdbus_msg_free(msg);
+       /*
+        * Release the name and hand it back to activator, now
+        * we should have 'activator_msgs_count' msgs again in
+        * the activator queue
+        */
+       ASSERT_ZERO(kdbus_name_release(conn, "foo.test.activator"));
+
+       /* make sure that we got our previous activator msgs */
+       ASSERT_ZERO(kdbus_msg_recv(activator, &msg, NULL));
+       ASSERT_RETURN(msg->src_id,==,sender->id);
+       ASSERT_RETURN(msg->dst_id,==,(typeof(msg->dst_id))KDBUS_DST_ID_NAME);
+       --activator_msgs_count;
+       kdbus_msg_free(msg);
+
+
+       /* Stage 2) of test check max message quota */
+
+       /* Empty conn queue and refill it to the brink anew */
+       EXHAUST(-EAGAIN,kdbus_msg_recv(conn, NULL, NULL));
+       EXHAUST(-ENOBUFS,kdbus_msg_send(sender, NULL, ++cookie, 0, 0, 0, conn->id));
+#undef EXHAUST
+
+       if (!activator_msgs_count)
+               ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(activator, NULL, NULL));
+
+       /* Acquire the name again */
+       flags = KDBUS_NAME_REPLACE_EXISTING;
+       ASSERT_ZERO(kdbus_name_acquire(conn, "foo.test.activator", &flags));
+
+       memset(&recv, 0, sizeof(recv));
+       recv.size = sizeof(recv);
+
+       /*
+        * Try to read messages and make sure that we have lost all
+        * the activator messages due to quota checks. Our queue is
+        * already full.
+        */
+       ASSERT_ZERO(kdbus_cmd_recv(conn->fd, &recv));
+       ASSERT_RETURN(recv.dropped_msgs,==,activator_msgs_count);
+
+       msg = (struct kdbus_msg *)(activator->buf + recv.msg.offset);
+       kdbus_msg_free(msg);
+
+       kdbus_conn_free(sender);
+       kdbus_conn_free(conn);
+       kdbus_conn_free(activator);
+
+       return TEST_OK;
+}
+
+#define TIMEOUT_CONNECTION_COUNT 8
+#define TIMEOUTS_PER_CONNECTION (MIN(KDBUS_CONN_MAX_REQUESTS_PENDING,KDBUS_CONN_MAX_MSGS)/TIMEOUT_CONNECTION_COUNT)
+
+static wur int kdbus_test_expected_reply_validate_timeouts(struct kdbus_conn *conn, uint64_t first_cookie, uint64_t type)
+{
+       uint64_t cookie_reply, seqnum, monotonic_ns, realtime_ns, prev_seqnum=0, prev_monotonic_ns=0, prev_realtime_ns=0;
+       unsigned i, next_cookie[TIMEOUT_CONNECTION_COUNT];
+       memset(next_cookie, 0, sizeof(next_cookie));
+       for (i=0; i < MIN(KDBUS_CONN_MAX_REQUESTS_PENDING,KDBUS_CONN_MAX_MSGS); i++) {
+               unsigned n, r;
+               ASSERT_ZERO(timeout_msg_recv(conn, type, &cookie_reply, &seqnum, &monotonic_ns, &realtime_ns));
+               #define A(W,R) do { ASSERT_RETURN(prev_##W,R,W); prev_##W = W; } while (0);
+                       A(seqnum,<)
+                       A(monotonic_ns,<=)
+                       A(realtime_ns,<=)
+               #undef A
+               ASSERT_RETURN(first_cookie,<=,cookie_reply);
+               cookie_reply -= first_cookie;
+               n = cookie_reply % TIMEOUT_CONNECTION_COUNT;
+               r = cookie_reply / TIMEOUT_CONNECTION_COUNT;
+               ASSERT_RETURN(r,==,next_cookie[n]);
+               ++next_cookie[n];
+       }
+       ASSERT_NO_PENDING(conn);
+       return 0;
+}
+
+static wur int kdbus_test_expected_reply_timeouts_or_quota(struct kdbus_test_env *env)
+{
+       unsigned i, n;
+       uint64_t first_cookie = 0x1234abcd5678eeff;
+       struct kdbus_conn *conn;
+       struct kdbus_conn *connections[1+TIMEOUT_CONNECTION_COUNT];
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       for (i = 0; i < 1+TIMEOUT_CONNECTION_COUNT; i++)
+               ASSERT_NONZERO(connections[i] = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       _Static_assert(!(KDBUS_CONN_MAX_REQUESTS_PENDING%TIMEOUT_CONNECTION_COUNT), "KDBUS_CONN_MAX_REQUESTS_PENDING) not a multitude of TIMEOUT_CONNECTION_COUNT - quota test needs to be modified to reflect that");
+
+       /* Send messages to TIMEOUT_CONNECTION_COUNT different connections */
+       for (i = 0; i < TIMEOUT_CONNECTION_COUNT; i++)
+               for (n = 0; n < TIMEOUTS_PER_CONNECTION; n++) {
+                       ASSERT_ZERO(kdbus_msg_send(conn, NULL, first_cookie + i + n*TIMEOUT_CONNECTION_COUNT,
+                                            KDBUS_MSG_EXPECT_REPLY,
+                                            200000000ULL /* 0.2s */, 0,
+                                            connections[i]->id));
+                       /* drain queue to avoid hitting KDBUS_CONN_MAX_MSGS */
+                       ASSERT_ZERO(kdbus_msg_recv(connections[i], NULL, NULL));
+               }
+
+       sleep(1); /* just to wait and see if timeout logic somehow destabilizes the system */
+
+       ASSERT_ZERO(kdbus_test_expected_reply_validate_timeouts(conn, first_cookie, KDBUS_ITEM_REPLY_TIMEOUT));
+
+       for (i = 0; i < 1+TIMEOUT_CONNECTION_COUNT; i++)
+               kdbus_conn_free(connections[i]);
+
+       kdbus_conn_free(conn);
+
+       return 0;
+}
+
+static wur int kdbus_test_expected_reply_quota(struct kdbus_test_env *env)
+{
+       unsigned i, n;
+       uint64_t first_cookie = 0x5678eeff1234abcd;
+       struct kdbus_conn *conn;
+       struct kdbus_conn *connections[1+TIMEOUT_CONNECTION_COUNT];
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       for (i = 0; i < 1+TIMEOUT_CONNECTION_COUNT; i++)
+               ASSERT_NONZERO(connections[i] = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* Send messages to TIMEOUT_CONNECTION_COUNT different connections */
+       for (i = 0; i < TIMEOUT_CONNECTION_COUNT; i++)
+               for (n = 0; n < KDBUS_CONN_MAX_REQUESTS_PENDING/TIMEOUT_CONNECTION_COUNT; n++) {
+                       ASSERT_ZERO(kdbus_msg_send(conn, NULL, first_cookie + i + n*TIMEOUT_CONNECTION_COUNT,
+                                            KDBUS_MSG_EXPECT_REPLY,
+                                            KDBUS_TIMEOUT_INFINITE, 0, /* massive timeout to make sure all pending replies do not timeout before quota check below */
+                                            connections[i]->id));
+                       /* drain queue to avoid hitting KDBUS_CONN_MAX_MSGS */
+                       ASSERT_ZERO(kdbus_msg_recv(connections[i], NULL, NULL));
+               }
+
+       /*
+        * Now try to send a message to the last connection,
+        * if we have reached KDBUS_CONN_MAX_REQUESTS_PENDING
+        * no further requests are allowed
+        */
+       ASSERT_RETURN(-EMLINK,==,kdbus_msg_send(conn, NULL, first_cookie + TIMEOUT_CONNECTION_COUNT*TIMEOUTS_PER_CONNECTION, KDBUS_MSG_EXPECT_REPLY, 1000000000ULL, 0, connections[TIMEOUT_CONNECTION_COUNT]->id));
+
+       for (i = 0; i < 1+TIMEOUT_CONNECTION_COUNT; i++)
+               kdbus_conn_free(connections[i]);
+
+       ASSERT_ZERO(kdbus_test_expected_reply_validate_timeouts(conn, first_cookie, KDBUS_ITEM_REPLY_DEAD));
+
+       kdbus_conn_free(conn);
+
+       return 0;
+}
+
+wur int kdbus_test_pool_quota(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *a, *b, *c;
+       struct kdbus_cmd_send cmd = {};
+       struct kdbus_item *item;
+       struct kdbus_msg *recv_msg;
+       struct kdbus_msg *msg;
+       uint64_t cookie = time(NULL);
+       uint64_t size;
+       unsigned int i;
+       char *payload;
+       int ret;
+
+       /* just a guard */
+       if (POOL_SIZE <= KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE ||
+           POOL_SIZE % KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE != 0)
+               return 0;
+
+       payload = calloc(KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE, sizeof(char));
+       ASSERT_RETURN_VAL(payload,!=,NULL, -ENOMEM);
+
+       ASSERT_NONZERO(a = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(b = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(c = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       size = sizeof(struct kdbus_msg);
+       size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
+
+       msg = malloc(size);
+       ASSERT_RETURN_VAL(msg,!=,NULL, -ENOMEM);
+
+       memset(msg, 0, size);
+       msg->size = size;
+       msg->src_id = a->id;
+       msg->dst_id = c->id;
+       msg->payload_type = KDBUS_PAYLOAD_DBUS;
+
+       item = msg->items;
+       item->type = KDBUS_ITEM_PAYLOAD_VEC;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec);
+       item->vec.address = (uintptr_t)payload;
+       item->vec.size = KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE;
+       item = KDBUS_ITEM_NEXT(item);
+
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg;
+
+       /*
+        * Send 2097248 bytes, a user is only allowed to get 33% of half of
+        * the free space of the pool, the already used space is
+        * accounted as free space
+        */
+       size += KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE;
+       for (i = size; i < (POOL_SIZE / 2 / 3); i += size) {
+               msg->cookie = cookie++;
+
+               ret = kdbus_cmd_send(a->fd, &cmd);
+               ASSERT_RETURN_VAL(ret,==,0, ret);
+       }
+
+       /* Try to get more than 33% */
+       msg->cookie = cookie++;
+       ASSERT_RETURN(-ENOBUFS,==,kdbus_cmd_send(a->fd, &cmd));
+
+       /* We still can pass small messages */
+       ASSERT_ZERO(kdbus_msg_send(b, NULL, cookie++, 0, 0, 0, c->id));
+
+       for (i = size; i < (POOL_SIZE / 2 / 3); i += size) {
+               ASSERT_ZERO(kdbus_msg_recv(c, &recv_msg, NULL));
+               ASSERT_RETURN(recv_msg->src_id,==,a->id);
+
+               kdbus_msg_free(recv_msg);
+       }
+
+       ASSERT_ZERO(kdbus_msg_recv(c, &recv_msg, NULL));
+       ASSERT_RETURN(recv_msg->src_id,==,b->id);
+
+       kdbus_msg_free(recv_msg);
+
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(c, NULL, NULL));
+
+       free(msg);
+       free(payload);
+
+       kdbus_conn_free(c);
+       kdbus_conn_free(b);
+       kdbus_conn_free(a);
+
+       return 0;
+}
+
+wur int kdbus_test_message_quota(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *a, *b;
+       uint64_t cookie = 0;
+       unsigned i;
+
+       ASSERT_ZERO(kdbus_test_notify_kernel_quota(env));
+
+       ASSERT_ZERO(kdbus_test_pool_quota(env));
+
+       ASSERT_ZERO(kdbus_test_expected_reply_timeouts_or_quota(env));
+
+       ASSERT_ZERO(kdbus_test_expected_reply_quota(env));
+
+       a = kdbus_hello(env->buspath, 0, NULL, 0);
+       b = kdbus_hello(env->buspath, 0, NULL, 0);
+
+       ASSERT_RETURN((typeof(kdbus_fill_conn_queue(b, a->id, KDBUS_CONN_MAX_MSGS)))KDBUS_CONN_MAX_MSGS,==,kdbus_fill_conn_queue(b, a->id, KDBUS_CONN_MAX_MSGS));
+
+       ASSERT_RETURN(-ENOBUFS,==,kdbus_msg_send(b, NULL, ++cookie, 0, 0, 0, a->id));
+
+       for (i = 0; i < KDBUS_CONN_MAX_MSGS; ++i)
+               ASSERT_ZERO(kdbus_msg_recv(a, NULL, NULL));
+
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(a, NULL, NULL));
+
+       ASSERT_RETURN((typeof(kdbus_fill_conn_queue(b, a->id, KDBUS_CONN_MAX_MSGS + 1)))KDBUS_CONN_MAX_MSGS,==,kdbus_fill_conn_queue(b, a->id, KDBUS_CONN_MAX_MSGS + 1));
+
+       ASSERT_RETURN(-ENOBUFS,==,kdbus_msg_send(b, NULL, ++cookie, 0, 0, 0, a->id));
+
+       kdbus_conn_free(a);
+       kdbus_conn_free(b);
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_memory_access(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *a, *b;
+       struct kdbus_cmd_send cmd = {};
+       struct kdbus_item *item;
+       struct kdbus_msg *msg;
+       uint64_t test_addr = 0;
+       char line[256];
+       uint64_t size;
+       FILE *f;
+
+       /*
+        * Search in /proc/kallsyms for the address of a kernel symbol that
+        * should always be there, regardless of the config. Use that address
+        * in a PAYLOAD_VEC item and make sure it's inaccessible.
+        */
+
+       f = fopen("/proc/kallsyms", "r");
+       if (!f)
+               return TEST_SKIP;
+
+       while (fgets(line, sizeof(line), f)) {
+               char *s = line;
+
+               if (!strsep(&s, " "))
+                       continue;
+
+               if (!strsep(&s, " "))
+                       continue;
+
+               if (!strncmp(s, "mutex_lock", 10)) {
+                       test_addr = strtoull(line, NULL, 16);
+                       break;
+               }
+       }
+
+       fclose(f);
+
+       if (!test_addr)
+               return TEST_SKIP;
+
+       ASSERT_NONZERO(a = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(b = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       size = sizeof(struct kdbus_msg);
+       size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
+
+       msg = alloca(size);
+       ASSERT_RETURN_VAL(msg,!=,NULL, -ENOMEM);
+
+       memset(msg, 0, size);
+       msg->size = size;
+       msg->src_id = a->id;
+       msg->dst_id = b->id;
+       msg->payload_type = KDBUS_PAYLOAD_DBUS;
+
+       item = msg->items;
+       item->type = KDBUS_ITEM_PAYLOAD_VEC;
+       item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec);
+       item->vec.address = test_addr;
+       item->vec.size = sizeof(void*);
+       item = KDBUS_ITEM_NEXT(item);
+
+       cmd.size = sizeof(cmd);
+       cmd.msg_address = (uintptr_t)msg;
+
+       ASSERT_RETURN(-EFAULT,==,kdbus_cmd_send(a->fd, &cmd));
+
+       kdbus_conn_free(b);
+       kdbus_conn_free(a);
+
+       return 0;
+}
diff --git a/tests/kdbus/test-metadata-ns.c b/tests/kdbus/test-metadata-ns.c
new file mode 100644 (file)
index 0000000..6b02ba7
--- /dev/null
@@ -0,0 +1,519 @@
+/*
+ * Test metadata in new namespaces. Even if our tests can run
+ * in a namespaced setup, this test is necessary so we can inspect
+ * metadata on the same kdbusfs but between multiple namespaces
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sched.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/prctl.h>
+#include <sys/eventfd.h>
+#include <sys/syscall.h>
+#include <sys/capability.h>
+#include <linux/sched.h>
+
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+static const struct kdbus_creds privileged_creds = {};
+
+static const struct kdbus_creds unmapped_creds = {
+       .uid    = UNPRIV_UID,
+       .euid   = UNPRIV_UID,
+       .suid   = UNPRIV_UID,
+       .fsuid  = UNPRIV_UID,
+       .gid    = UNPRIV_GID,
+       .egid   = UNPRIV_GID,
+       .sgid   = UNPRIV_GID,
+       .fsgid  = UNPRIV_GID,
+};
+
+static const struct kdbus_pids unmapped_pids = {};
+
+/* Get only the first item */
+static struct kdbus_item *kdbus_get_item(struct kdbus_msg *msg,
+                                        uint64_t type)
+{
+       struct kdbus_item *item;
+
+       KDBUS_ITEM_FOREACH(item, msg, items)
+               if (item->type == type)
+                       return item;
+
+       return NULL;
+}
+
+static wur int kdbus_match_kdbus_creds(struct kdbus_msg *msg,
+                                  const struct kdbus_creds *expected_creds)
+{
+       struct kdbus_item *item;
+       ASSERT_NONZERO(item = kdbus_get_item(msg, KDBUS_ITEM_CREDS));
+       ASSERT_ZERO(memcmp(&item->creds, expected_creds, sizeof(struct kdbus_creds)));
+       return 0;
+}
+
+static wur int kdbus_match_kdbus_pids(struct kdbus_msg *msg,
+                                 const struct kdbus_pids *expected_pids)
+{
+       struct kdbus_item *item;
+       ASSERT_NONZERO(item = kdbus_get_item(msg, KDBUS_ITEM_PIDS));
+       ASSERT_ZERO(memcmp(&item->pids, expected_pids, sizeof(struct kdbus_pids)));
+       return 0;
+}
+
+static wur int __kdbus_clone_userns_test(const char *bus,
+                                    struct kdbus_conn *conn,
+                                    uint64_t grandpa_pid,
+                                    int signal_fd)
+{
+       int clone_ret;
+       int ret;
+       struct kdbus_msg *msg = NULL;
+       const struct kdbus_item *item;
+       uint64_t cookie = time(NULL) ^ 0xdeadbeef;
+       struct kdbus_conn *unpriv_conn = NULL;
+       struct kdbus_pids parent_pids = {
+               .pid = getppid(),
+               .tid = getppid(),
+               .ppid = grandpa_pid,
+       };
+
+       ASSERT_EXIT_ZERO(drop_privileges(UNPRIV_UID, UNPRIV_GID));
+
+       ASSERT_EXIT_NONZERO(unpriv_conn = kdbus_hello(bus, 0, NULL, 0));
+
+       ASSERT_EXIT_ZERO(kdbus_add_match_empty(unpriv_conn));
+
+       /*
+        * ping privileged connection from this new unprivileged
+        * one
+        */
+
+       ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv_conn, NULL, cookie, 0, 0, 0, conn->id));
+
+       /*
+        * Since we just dropped privileges, the dumpable flag
+        * was just cleared which makes the /proc/$clone_child/uid_map
+        * to be owned by root, hence any userns uid mapping will fail
+        * with -EPERM since the mapping will be done by uid 65534.
+        *
+        * To avoid this set the dumpable flag again which makes
+        * procfs update the /proc/$clone_child/ inodes owner to 65534.
+        *
+        * Using this we will be able write to /proc/$clone_child/uid_map
+        * as uid 65534 and map the uid 65534 to 0 inside the user namespace.
+        */
+       ASSERT_EXIT_ZERO(prctl(PR_SET_DUMPABLE, SUID_DUMP_USER));
+
+       /* Make child privileged in its new userns and run tests */
+
+       ret = RUN_CLONE_CHILD(&clone_ret,
+                             SIGCHLD | CLONE_NEWUSER | CLONE_NEWPID,
+       ({ 0;  /* Clone setup, nothing */ }),
+       ({
+               eventfd_t event_status = 0;
+               struct kdbus_conn *userns_conn;
+
+               /* ping connection from the new user namespace */
+               ASSERT_EXIT_NONZERO(userns_conn = kdbus_hello(bus, 0, NULL, 0));
+
+               ASSERT_EXIT_ZERO(kdbus_add_match_empty(userns_conn));
+
+               cookie++;
+               ASSERT_EXIT_ZERO(kdbus_msg_send(userns_conn, NULL, cookie, 0, 0, 0, conn->id));
+
+               /* Parent did send */
+               ASSERT_RETURN(0,<=,eventfd_read(signal_fd, &event_status));
+               ASSERT_RETURN(event_status,==,(eventfd_t)1);
+
+               /*
+                * Receive from privileged connection
+                */
+               kdbus_printf("Privileged → unprivileged/privileged "
+                            "in its userns "
+                            "(different userns and pidns):\n");
+               ASSERT_EXIT_ZERO(kdbus_msg_recv_poll(userns_conn, 300, &msg, NULL));
+               ASSERT_EXIT(msg->dst_id,==,userns_conn->id);
+
+               ASSERT_EXIT_NONZERO(item = kdbus_get_item(msg, KDBUS_ITEM_CAPS));
+
+               /* uid/gid not mapped, so we have unpriv cached creds */
+               ASSERT_EXIT_ZERO(kdbus_match_kdbus_creds(msg, &unmapped_creds));
+
+               /*
+                * Diffent pid namepsaces. This is the child pidns
+                * so it should not see its parent kdbus_pids
+                */
+               ASSERT_EXIT_ZERO(kdbus_match_kdbus_pids(msg, &unmapped_pids));
+
+               kdbus_msg_free(msg);
+
+
+               /*
+                * Receive broadcast from privileged connection
+                */
+               kdbus_printf("Privileged → unprivileged/privileged "
+                            "in its userns "
+                            "(different userns and pidns):\n");
+               ASSERT_EXIT_ZERO(kdbus_msg_recv_poll(userns_conn, 300, &msg, NULL));
+               ASSERT_EXIT(msg->dst_id,==,KDBUS_DST_ID_BROADCAST);
+
+               ASSERT_EXIT_NONZERO(item = kdbus_get_item(msg, KDBUS_ITEM_CAPS));
+
+               /* uid/gid not mapped, so we have unpriv cached creds */
+               ASSERT_EXIT_ZERO(kdbus_match_kdbus_creds(msg, &unmapped_creds));
+
+               /*
+                * Diffent pid namepsaces. This is the child pidns
+                * so it should not see its parent kdbus_pids
+                */
+               ASSERT_EXIT_ZERO(kdbus_match_kdbus_pids(msg, &unmapped_pids));
+
+               kdbus_msg_free(msg);
+
+               kdbus_conn_free(userns_conn);
+       }),
+       ({
+               /* Parent setup map child uid/gid */
+               ASSERT_EXIT_ZERO(userns_map_uid_gid(pid, "0 65534 1", "0 65534 1"));
+       }),
+       ({ 0; }));
+       /* Unprivileged was not able to create user namespace */
+       if (clone_ret == -EPERM) {
+               kdbus_printf("-- CLONE_NEWUSER TEST Failed for "
+                            "uid: %u\n -- Make sure that your kernel "
+                            "do not allow CLONE_NEWUSER for "
+                            "unprivileged users\n", UNPRIV_UID);
+               ret = 0;
+               goto out;
+       }
+
+       ASSERT_EXIT_ZERO(ret);
+
+
+       /*
+        * Receive from privileged connection
+        */
+       kdbus_printf("\nPrivileged → unprivileged (same namespaces):\n");
+       ASSERT_EXIT_ZERO(kdbus_msg_recv_poll(unpriv_conn, 300, &msg, NULL));
+       ASSERT_EXIT(msg->dst_id,==,unpriv_conn->id);
+
+       /* will get the privileged creds */
+       ASSERT_EXIT_ZERO(kdbus_match_kdbus_creds(msg, &privileged_creds));
+
+       /* Same pidns so will get the kdbus_pids */
+       ASSERT_ZERO(kdbus_match_kdbus_pids(msg, &parent_pids));
+
+       kdbus_msg_free(msg);
+
+
+       /*
+        * Receive broadcast from privileged connection
+        */
+       kdbus_printf("\nPrivileged → unprivileged (same namespaces):\n");
+       ASSERT_EXIT_ZERO(kdbus_msg_recv_poll(unpriv_conn, 300, &msg, NULL));
+       ASSERT_EXIT(msg->dst_id,==,KDBUS_DST_ID_BROADCAST);
+
+       /* will get the privileged creds */
+       ASSERT_EXIT_ZERO(kdbus_match_kdbus_creds(msg, &privileged_creds));
+
+       ASSERT_ZERO(kdbus_match_kdbus_pids(msg, &parent_pids));
+
+       kdbus_msg_free(msg);
+
+out:
+       kdbus_conn_free(unpriv_conn);
+
+       return ret;
+}
+
+static wur int kdbus_clone_userns_test(const char *bus,
+                                  struct kdbus_conn *conn)
+{
+       int ret, status, efd;
+       pid_t pid, ppid;
+       uint64_t unpriv_conn_id, userns_conn_id;
+       struct kdbus_msg *msg;
+       const struct kdbus_item *item;
+       struct kdbus_pids expected_pids;
+       struct kdbus_conn *monitor;
+
+       kdbus_printf("STARTING TEST 'metadata-ns'.\n");
+
+       ASSERT_EXIT_NONZERO(monitor = kdbus_hello(bus, KDBUS_HELLO_MONITOR, NULL, 0));
+
+       /*
+        * parent will signal to child that is in its
+        * userns to read its queue
+        */
+       efd = eventfd(0, EFD_CLOEXEC);
+       ASSERT_RETURN_VAL(efd,>=,0, efd);
+
+       ppid = getppid();
+
+       pid = fork();
+       ASSERT_RETURN_VAL(pid,>=,0, -errno);
+
+       if (pid == 0) {
+               ret = prctl(PR_SET_PDEATHSIG, SIGKILL);
+               ASSERT_EXIT_VAL(ret,==,0, -errno);
+
+               ret = __kdbus_clone_userns_test(bus, conn, ppid, efd);
+               exit(ret);
+       }
+
+
+       /* Phase 1) privileged receives from unprivileged */
+
+       /*
+        * Receive from the unprivileged child
+        */
+       kdbus_printf("\nUnprivileged → privileged (same namespaces):\n");
+       ASSERT_ZERO(kdbus_msg_recv_poll(conn, 300, &msg, NULL));
+
+       unpriv_conn_id = msg->src_id;
+
+       /* Unprivileged user */
+       ASSERT_ZERO(kdbus_match_kdbus_creds(msg, &unmapped_creds));
+
+       /* Set the expected creds_pids */
+       expected_pids = (struct kdbus_pids) {
+               .pid = pid,
+               .tid = pid,
+               .ppid = getpid(),
+       };
+       ASSERT_ZERO(kdbus_match_kdbus_pids(msg, &expected_pids));
+
+       kdbus_msg_free(msg);
+
+
+       /*
+        * Receive from the unprivileged that is in his own
+        * userns and pidns
+        */
+
+       kdbus_printf("\nUnprivileged/privileged in its userns → privileged "
+                    "(different userns and pidns)\n");
+       ret = kdbus_msg_recv_poll(conn, 300, &msg, NULL);
+       if (ret == -ETIMEDOUT)
+               /* perhaps unprivileged userns is not allowed */
+               goto wait;
+
+       ASSERT_ZERO(ret);
+
+       userns_conn_id = msg->src_id;
+
+       ASSERT_NONZERO(item = kdbus_get_item(msg, KDBUS_ITEM_CAPS));
+
+       /*
+        * Compare received items, creds must be translated into
+        * the receiver user namespace, so the user is unprivileged
+        */
+       ASSERT_ZERO(kdbus_match_kdbus_creds(msg, &unmapped_creds));
+
+       /*
+        * We should have the kdbus_pids since we are the parent
+        * pidns
+        */
+       ASSERT_NONZERO(item = kdbus_get_item(msg, KDBUS_ITEM_PIDS));
+
+       ASSERT_NONZERO(memcmp(&item->pids, &unmapped_pids, sizeof(struct kdbus_pids)));
+
+       /*
+        * Parent pid of the unprivileged/privileged in its userns
+        * is the unprivileged child pid that was forked here.
+        */
+       ASSERT_RETURN((uint64_t)pid,==,item->pids.ppid);
+
+       kdbus_msg_free(msg);
+
+
+       /* Phase 2) Privileged connection sends now 3 packets */
+
+       /*
+        * Sending to unprivileged connections a unicast
+        */
+       ASSERT_ZERO(kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, 0, unpriv_conn_id));
+
+       /* signal to child that is in its userns */
+       ASSERT_ZERO(eventfd_write(efd, 1));
+
+       /*
+        * Sending to unprivileged/privilged in its userns
+        * connections a unicast
+        */
+       ASSERT_ZERO(kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, 0, userns_conn_id));
+
+       /*
+        * Sending to unprivileged connections a broadcast
+        */
+       ASSERT_ZERO(kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+
+wait:
+       ret = waitpid(pid, &status, 0);
+       ASSERT_RETURN(ret,>=,0);
+
+       ASSERT_NONZERO(WIFEXITED(status));
+       ASSERT_ZERO(WEXITSTATUS(status));
+
+       /* Dump monitor queue */
+       kdbus_printf("\n\nMonitor queue:\n");
+       for (;;) {
+               ret = kdbus_msg_recv_poll(monitor, 100, &msg, NULL);
+               if (ret < 0)
+                       break;
+
+               if (msg->payload_type == KDBUS_PAYLOAD_DBUS) {
+                       /*
+                        * Parent pidns should see all the
+                        * pids
+                        */
+                       ASSERT_NONZERO(item = kdbus_get_item(msg, KDBUS_ITEM_PIDS));
+                       ASSERT_NONZERO(item->pids.pid);
+                       ASSERT_NONZERO(item->pids.tid != 0);
+                       ASSERT_NONZERO(item->pids.ppid != 0);
+               }
+
+               kdbus_msg_free(msg);
+       }
+
+       kdbus_conn_free(monitor);
+       CLOSE(efd);
+
+       return 0;
+}
+
+wur int kdbus_test_metadata_ns(struct kdbus_test_env *env)
+{
+       int ret;
+       struct kdbus_conn *holder, *conn;
+       struct kdbus_policy_access policy_access = {
+               /* Allow world so we can inspect metadata in namespace */
+               .type = KDBUS_POLICY_ACCESS_WORLD,
+               .id = geteuid(),
+               .access = KDBUS_POLICY_TALK,
+       };
+
+       /*
+        * We require user-namespaces and all uids/gids
+        * should be mapped (we can just require the necessary ones)
+        */
+       if (!config_user_ns_is_enabled() ||
+           !all_uids_gids_are_mapped())
+               return TEST_SKIP;
+
+       ret = test_is_capable(CAP_SETUID, CAP_SETGID, CAP_SYS_ADMIN, -1);
+       ASSERT_RETURN(ret,>=,0);
+
+       /* no enough privileges, SKIP test */
+       if (!ret)
+               return TEST_SKIP;
+
+       ASSERT_NONZERO(holder = kdbus_hello_registrar(env->buspath, "com.example.metadata", &policy_access, 1, KDBUS_HELLO_POLICY_HOLDER));
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ASSERT_ZERO(kdbus_add_match_empty(conn));
+
+       ASSERT_ZERO(kdbus_name_acquire(conn, "com.example.metadata", NULL));
+
+       ASSERT_ZERO(kdbus_clone_userns_test(env->buspath, conn));
+
+       kdbus_conn_free(holder);
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
+
+#define TEST_METADATA_DECL\
+       int attach_flags_recv = _KDBUS_ATTACH_ALL;
+#define TEST_METADATA_DECL_INLOOP\
+       struct kdbus_item const *item;\
+       bool have_desc=false, have_name=false;\
+       if (attach_flags_recv & KDBUS_ATTACH_AUDIT) /* audit not generally supported - no reason to prolong the test by including it */\
+               attach_flags_recv &= ~KDBUS_ATTACH_AUDIT;
+#define TEST_METADATA(STRUCT) do {\
+       KDBUS_ITEM_FOREACH(item, (STRUCT), items) {\
+               if (KDBUS_ITEM_OWNED_NAME == item->type) {\
+                       ASSERT_ZERO(have_name);\
+                       have_name = true;\
+               } else if (KDBUS_ITEM_CONN_DESCRIPTION == item->type) {\
+                       ASSERT_ZERO(have_desc);\
+                       have_desc = true;\
+               }\
+       }\
+       if (attach_flags_recv & KDBUS_ATTACH_NAMES)\
+               ASSERT_NONZERO(have_name);\
+       else\
+               ASSERT_ZERO(have_name);\
+       if (attach_flags_recv & KDBUS_ATTACH_CONN_DESCRIPTION)\
+               ASSERT_NONZERO(have_desc);\
+       else\
+               ASSERT_ZERO(have_desc);\
+} while (0)
+
+wur int kdbus_test_metadata(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       struct kdbus_msg *msg;
+       uint64_t cookie = 0x1234abcd5678eeff;
+
+       TEST_METADATA_DECL;
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_ZERO(kdbus_name_acquire(conn, "dummy.name.yeah", NULL));
+
+       do {
+               TEST_METADATA_DECL_INLOOP;
+
+               ASSERT_ZERO(kdbus_conn_update_attach_flags(env->conn, _KDBUS_ATTACH_ALL, attach_flags_recv));
+               ASSERT_ZERO(kdbus_msg_send(conn, NULL, ++cookie, 0, 0, 0, env->conn->id));
+               ASSERT_ZERO(kdbus_msg_recv(env->conn, &msg, NULL));
+
+               TEST_METADATA(msg);
+
+               kdbus_msg_free(msg);
+               ASSERT_ZERO(kdbus_free(env->conn, (uintptr_t)msg - (uintptr_t)env->conn->buf));
+       } while (--attach_flags_recv >= 0);
+
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_metadata_conn_info(struct kdbus_test_env *env)
+{
+       TEST_METADATA_DECL;
+
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, "dummy.name.yeah", NULL));
+
+       do {
+               struct kdbus_info *info;
+               uint64_t offset;
+
+               TEST_METADATA_DECL_INLOOP;
+
+               ASSERT_ZERO(kdbus_conn_info(env->conn, env->conn->id, NULL, attach_flags_recv, &offset));
+               info = (struct kdbus_info *)(env->conn->buf + offset);
+               ASSERT_RETURN(info->id,==,env->conn->id);
+
+               TEST_METADATA(info);
+
+               ASSERT_ZERO(kdbus_free(env->conn, (uintptr_t)info - (uintptr_t)env->conn->buf));
+       } while (--attach_flags_recv >= 0);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-monitor.c b/tests/kdbus/test-monitor.c
new file mode 100644 (file)
index 0000000..a5918e5
--- /dev/null
@@ -0,0 +1,154 @@
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/capability.h>
+#include <sys/wait.h>
+
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+#include "kdbus-test.h"
+
+wur int kdbus_test_monitor(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *monitor, *conn;
+       unsigned int cookie = 0xdeadbeef;
+       struct kdbus_msg *msg;
+       uint64_t offset = 0;
+       int ret;
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* add matches to make sure the monitor do not trigger an item add or
+        * remove on connect and disconnect, respectively.
+        */
+       ASSERT_ZERO(kdbus_add_match_id(conn, 0x1, KDBUS_ITEM_ID_ADD, KDBUS_MATCH_ID_ANY));
+       ASSERT_ZERO(kdbus_add_match_id(conn, 0x2, KDBUS_ITEM_ID_REMOVE, KDBUS_MATCH_ID_ANY));
+
+       /* register a monitor */
+       ASSERT_NONZERO(monitor = kdbus_hello(env->buspath, KDBUS_HELLO_MONITOR, NULL, 0));
+
+       /* make sure we did not receive a monitor connect notification */
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(conn, &msg, &offset));
+
+       /* check that a monitor cannot acquire a name */
+       ASSERT_RETURN(-EOPNOTSUPP,==,kdbus_name_acquire(monitor, "foo.bar.baz", NULL));
+
+       ASSERT_ZERO(kdbus_msg_send(env->conn, NULL, cookie, 0, 0,  0, conn->id));
+
+       /* the recipient should have gotten the message */
+       ASSERT_ZERO(kdbus_msg_recv(conn, &msg, &offset));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+       kdbus_msg_free(msg);
+       ASSERT_ZERO(kdbus_free(conn, offset));
+
+       /* and so should the monitor */
+       ASSERT_ZERO(kdbus_msg_recv(monitor, &msg, &offset));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       kdbus_msg_free(msg);
+       ASSERT_ZERO(kdbus_free(monitor, offset));
+
+       /* make sure there are no pending messages */
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(conn, &msg, &offset));
+
+       /* Installing matches for monitors must fail */
+       ASSERT_RETURN(-EOPNOTSUPP,==,kdbus_add_match_empty(monitor));
+
+       cookie++;
+       ASSERT_ZERO(kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+       /* make sure there are no pending messages */
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(conn, &msg, &offset));
+
+       /* The monitor should get the message. */
+       ASSERT_ZERO(kdbus_msg_recv_poll(monitor, 100, &msg, &offset));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       kdbus_msg_free(msg);
+       ASSERT_ZERO(kdbus_free(monitor, offset));
+
+       /*
+        * Since we are the only monitor, update the attach flags
+        * and tell we are not interested in attach flags recv
+        */
+
+       ASSERT_ZERO(kdbus_conn_update_attach_flags(monitor, _KDBUS_ATTACH_ALL, 0));
+
+       cookie++;
+       ASSERT_ZERO(kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+       /* make sure there are no pending messages */
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(conn, &msg, &offset));
+
+       ASSERT_ZERO(kdbus_msg_recv_poll(monitor, 100, &msg, &offset));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       ASSERT_ZERO(kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP));
+
+       kdbus_msg_free(msg);
+       ASSERT_ZERO(kdbus_free(monitor, offset));
+
+       /*
+        * Now we are interested in KDBUS_ITEM_TIMESTAMP and
+        * KDBUS_ITEM_CREDS
+        */
+       ASSERT_ZERO(kdbus_conn_update_attach_flags(monitor, _KDBUS_ATTACH_ALL, KDBUS_ATTACH_TIMESTAMP | KDBUS_ATTACH_CREDS));
+
+       cookie++;
+       ASSERT_ZERO(kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+       /* make sure there are no pending messages */
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(conn, &msg, &offset));
+
+       ASSERT_ZERO(kdbus_msg_recv_poll(monitor, 100, &msg, &offset));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       ASSERT_RETURN(1,==,kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP));
+
+       ASSERT_RETURN(1,==,kdbus_item_in_message(msg, KDBUS_ITEM_CREDS));
+
+       /* the KDBUS_ITEM_PID_COMM was not requested */
+       ASSERT_ZERO(kdbus_item_in_message(msg, KDBUS_ITEM_PID_COMM));
+
+       kdbus_msg_free(msg);
+       ASSERT_ZERO(kdbus_free(monitor, offset));
+
+       /* make sure there are no pending messages */
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(conn, &msg, &offset));
+
+       kdbus_conn_free(monitor);
+       /* make sure we did not receive a monitor disconnect notification */
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(conn, &msg, &offset));
+
+       kdbus_conn_free(conn);
+
+       /* Make sure that monitor as unprivileged is not allowed */
+       ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1);
+       ASSERT_RETURN(ret,>=,0);
+
+       if (ret && all_uids_gids_are_mapped())
+               RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_UID, ({
+                       monitor = kdbus_hello(env->buspath, KDBUS_HELLO_MONITOR, NULL, 0);
+                       ASSERT_EXIT(errno,==,EPERM);
+                       ASSERT_EXIT_ZERO(monitor);
+
+                       exit(EXIT_SUCCESS);
+               }),
+               ({}));
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-names.c b/tests/kdbus/test-names.c
new file mode 100644 (file)
index 0000000..aedbac7
--- /dev/null
@@ -0,0 +1,236 @@
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <limits.h>
+#include <getopt.h>
+#include <stdbool.h>
+
+#include "kdbus-api.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+#include "kdbus-test.h"
+
+struct test_name {
+       const char *name;
+       __u64 owner_id;
+       __u64 flags;
+};
+
+static wur bool conn_test_names(const struct kdbus_conn *conn,
+                           const struct test_name *tests,
+                           unsigned int n_tests)
+{
+       struct kdbus_cmd_list cmd_list = {};
+       struct kdbus_info *name, *list;
+       unsigned int i;
+
+       cmd_list.size = sizeof(cmd_list);
+       cmd_list.flags = KDBUS_LIST_NAMES |
+                        KDBUS_LIST_ACTIVATORS |
+                        KDBUS_LIST_QUEUED;
+
+       ASSERT_ZERO(kdbus_cmd_list(conn->fd, &cmd_list));
+
+       list = (struct kdbus_info *)(conn->buf + cmd_list.offset);
+
+       for (i = 0; i < n_tests; i++) {
+               const struct test_name *t = tests + i;
+               bool found = false;
+
+               KDBUS_FOREACH(name, list, cmd_list.list_size) {
+                       struct kdbus_item *item;
+
+                       KDBUS_ITEM_FOREACH(item, name, items) {
+                               if (item->type != KDBUS_ITEM_OWNED_NAME ||
+                                   strcmp(item->name.name, t->name) != 0)
+                                       continue;
+
+                               if (t->owner_id == name->id &&
+                                   t->flags == item->name.flags) {
+                                       found = true;
+                                       break;
+                               }
+                       }
+               }
+
+               if (!found)
+                       return false;
+       }
+
+       return true;
+}
+
+static wur bool conn_is_name_primary_owner(const struct kdbus_conn *conn,
+                                      const char *needle)
+{
+       struct test_name t = {
+               .name = needle,
+               .owner_id = conn->id,
+               .flags = KDBUS_NAME_PRIMARY,
+       };
+
+       return conn_test_names(conn, &t, 1);
+}
+
+wur int kdbus_test_name_basic(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       char *name, *dot_name, *invalid_name, *wildcard_name;
+
+       name = "foo.bla.blaz";
+       dot_name = ".bla.blaz";
+       invalid_name = "foo";
+       wildcard_name = "foo.bla.bl.*";
+
+       /* create a 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* acquire name "foo.bar.xxx" name */
+       ASSERT_ZERO(kdbus_name_acquire(conn, "foo.bar.xxx", NULL));
+
+       /* Name is not valid, must fail */
+       ASSERT_RETURN(-EINVAL,==,kdbus_name_acquire(env->conn, dot_name, NULL));
+
+       ASSERT_RETURN(-EINVAL,==,kdbus_name_acquire(env->conn, invalid_name, NULL));
+
+       ASSERT_RETURN(-EINVAL,==,kdbus_name_acquire(env->conn, wildcard_name, NULL));
+
+       /* check that we can acquire a name */
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, name, NULL));
+
+       ASSERT_RETURN(true,==,conn_is_name_primary_owner(env->conn, name));
+
+       /* ... and release it again */
+       ASSERT_ZERO(kdbus_name_release(env->conn, name));
+
+       ASSERT_RETURN(false,==,conn_is_name_primary_owner(env->conn, name));
+
+       /* check that we can't release it again */
+       ASSERT_RETURN(-ESRCH,==,kdbus_name_release(env->conn, name));
+
+       /* check that we can't release a name that we don't own */
+       ASSERT_RETURN(-EADDRINUSE,==,kdbus_name_release(env->conn, "foo.bar.xxx"));
+
+       /* Name is not valid, must fail */
+       ASSERT_RETURN(-ESRCH,==,kdbus_name_release(env->conn, dot_name));
+
+       ASSERT_RETURN(-ESRCH,==,kdbus_name_release(env->conn, invalid_name));
+
+       ASSERT_RETURN(-ESRCH,==,kdbus_name_release(env->conn, wildcard_name));
+
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_name_conflict(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       char *name;
+
+       name = "foo.bla.blaz";
+
+       /* create a 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* allow the new connection to own the same name */
+       /* acquire name from the 1st connection */
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, name, NULL));
+
+       ASSERT_RETURN(true,==,conn_is_name_primary_owner(env->conn, name));
+
+       /* check that we also can't acquire it again from the 2nd connection */
+       ASSERT_RETURN(-EEXIST,==,kdbus_name_acquire(conn, name, NULL));
+
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_name_queue(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       struct test_name t[2];
+       const char *name;
+       uint64_t flags;
+
+       name = "foo.bla.blaz";
+
+       flags = 0;
+
+       /* create a 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* allow the new connection to own the same name */
+       /* acquire name from the 1st connection */
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, name, &flags));
+
+       ASSERT_RETURN(true,==,conn_is_name_primary_owner(env->conn, name));
+
+       /* queue the 2nd connection as waiting owner */
+       flags = KDBUS_NAME_QUEUE;
+       ASSERT_ZERO(kdbus_name_acquire(conn, name, &flags));
+       ASSERT_NONZERO(flags & KDBUS_NAME_IN_QUEUE);
+
+       t[0].name = name;
+       t[0].owner_id = env->conn->id;
+       t[0].flags = KDBUS_NAME_PRIMARY;
+       t[1].name = name;
+       t[1].owner_id = conn->id;
+       t[1].flags = KDBUS_NAME_QUEUE | KDBUS_NAME_IN_QUEUE;
+       ASSERT_RETURN(true,==,conn_test_names(conn, t, 2));
+
+       /* release name from 1st connection */
+       ASSERT_ZERO(kdbus_name_release(env->conn, name));
+
+       /* now the name should be owned by the 2nd connection */
+       t[0].name = name;
+       t[0].owner_id = conn->id;
+       t[0].flags = KDBUS_NAME_PRIMARY | KDBUS_NAME_QUEUE;
+       ASSERT_RETURN(true,==,conn_test_names(conn, t, 1));
+
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_name_takeover(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn;
+       struct test_name t;
+       const char *name;
+       uint64_t flags;
+
+       name = "foo.bla.blaz";
+
+       flags = KDBUS_NAME_ALLOW_REPLACEMENT;
+
+       /* create a 2nd connection */
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* acquire name for 1st connection */
+       ASSERT_ZERO(kdbus_name_acquire(env->conn, name, &flags));
+
+       t.name = name;
+       t.owner_id = env->conn->id;
+       t.flags = KDBUS_NAME_ALLOW_REPLACEMENT | KDBUS_NAME_PRIMARY;
+       ASSERT_RETURN(true,==,conn_test_names(conn, &t, 1));
+
+       /* now steal name with 2nd connection */
+       flags = KDBUS_NAME_REPLACE_EXISTING;
+       ASSERT_ZERO(kdbus_name_acquire(conn, name, &flags));
+       ASSERT_NONZERO(flags & KDBUS_NAME_ACQUIRED);
+
+       ASSERT_RETURN(true,==,conn_is_name_primary_owner(conn, name));
+
+       kdbus_conn_free(conn);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-policy-ns.c b/tests/kdbus/test-policy-ns.c
new file mode 100644 (file)
index 0000000..0a630b7
--- /dev/null
@@ -0,0 +1,590 @@
+/*
+ * Test metadata and policies in new namespaces. Even if our tests
+ * can run in a namespaced setup, this test is necessary so we can
+ * inspect policies on the same kdbusfs but between multiple
+ * namespaces.
+ *
+ * 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 <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <sched.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/prctl.h>
+#include <sys/eventfd.h>
+#include <sys/syscall.h>
+#include <sys/capability.h>
+#include <linux/sched.h>
+
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+#define MAX_CONN       64
+#define POLICY_NAME    "foo.test.policy-test"
+
+#define KDBUS_CONN_MAX_MSGS_PER_USER            16
+
+/**
+ * Note: this test can be used to inspect policy_db->talk_access_hash
+ *
+ * The purpose of these tests:
+ * 1) Check KDBUS_POLICY_TALK
+ * 2) Check the cache state: kdbus_policy_db->talk_access_hash
+ * Should be extended
+ */
+
+/**
+ * Check a list of connections against conn_db[0]
+ * conn_db[0] will own the name "foo.test.policy-test" and the
+ * policy holder connection for this name will update the policy
+ * entries, so different use cases can be tested.
+ */
+static struct kdbus_conn **conn_db;
+
+static wur void *kdbus_recv_echo(void *ptr)
+{
+       int ret;
+       struct kdbus_conn *conn = ptr;
+
+       ret = kdbus_msg_recv_poll(conn, 3009, NULL, NULL);
+
+       return (void *)(long)ret;
+}
+
+/* Trigger kdbus_policy_set() */
+static wur int kdbus_set_policy_talk(struct kdbus_conn *conn,
+                                const char *name,
+                                uid_t id, unsigned int type)
+{
+       struct kdbus_policy_access access = {
+               .type = type,
+               .id = id,
+               .access = KDBUS_POLICY_TALK,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn, name, &access, 1));
+
+       return TEST_OK;
+}
+
+/* return TEST_OK or TEST_ERR on failure */
+static wur int kdbus_register_same_activator(char *bus, const char *name,
+                                        struct kdbus_conn **c)
+{
+       int ret;
+       struct kdbus_conn *activator;
+
+       activator = kdbus_hello_activator(bus, name, NULL, 0);
+       if (activator) {
+               *c = activator;
+               print("--- error was able to register name twice '%s'.\n", name);
+               return TEST_ERR;
+       }
+
+       ret = -errno;
+       /* -EEXIST means test succeeded */
+       if (ret == -EEXIST)
+               return TEST_OK;
+
+       return TEST_ERR;
+}
+
+/* return TEST_OK or TEST_ERR on failure */
+static wur int kdbus_register_policy_holder(char *bus, const char *name,
+                                       struct kdbus_conn **conn)
+{
+       struct kdbus_conn *c;
+       struct kdbus_policy_access access[2];
+
+       access[0].type = KDBUS_POLICY_ACCESS_USER;
+       access[0].access = KDBUS_POLICY_OWN;
+       access[0].id = geteuid();
+
+       access[1].type = KDBUS_POLICY_ACCESS_WORLD;
+       access[1].access = KDBUS_POLICY_TALK;
+       access[1].id = geteuid();
+
+       ASSERT_NONZERO(c = kdbus_hello_registrar(bus, name, access, 2, KDBUS_HELLO_POLICY_HOLDER));
+
+       *conn = c;
+
+       return TEST_OK;
+}
+
+/**
+ * Create new threads for receiving from multiple senders,
+ * The 'conn_db' will be populated by newly created connections.
+ * Caller should free all allocated connections.
+ *
+ * return 0 on success, negative errno on failure.
+ */
+static wur int kdbus_recv_in_threads(const char *bus, const char *name,
+                                struct kdbus_conn **conn_db)
+{
+       int ret;
+       bool pool_full = false;
+       unsigned int sent_packets = 0;
+       unsigned int lost_packets = 0;
+       unsigned int i, tid;
+       unsigned long dst_id;
+       unsigned long cookie = 1;
+       unsigned int thread_nr = MAX_CONN - 1;
+       pthread_t thread_id[MAX_CONN - 1] = {'\0'};
+
+       dst_id = name ? KDBUS_DST_ID_NAME : conn_db[0]->id;
+
+       for (tid = 0, i = 1; tid < thread_nr; tid++, i++) {
+               ret = pthread_create(&thread_id[tid], NULL,
+                                    kdbus_recv_echo, (void *)conn_db[0]);
+               if (ret < 0) {
+                       ret = -errno;
+                       kdbus_printf("error pthread_create: %d (%m)\n",
+                                     ret);
+                       break;
+               }
+
+               /* just free before re-using */
+               kdbus_conn_free(conn_db[i]);
+               conn_db[i] = NULL;
+
+               /* We need to create connections here */
+               conn_db[i] = kdbus_hello(bus, 0, NULL, 0);
+               if (!conn_db[i]) {
+                       ret = -errno;
+                       break;
+               }
+
+               ret = kdbus_add_match_empty(conn_db[i]);
+               if (ret < 0)
+                       break;
+
+               ret = kdbus_msg_send(conn_db[i], name, cookie++,
+                                    0, 0, 0, dst_id);
+               if (ret < 0) {
+                       /*
+                        * Receivers are not reading their messages,
+                        * not scheduled ?!
+                        *
+                        * So set the pool full here, perhaps the
+                        * connection pool or queue was full, later
+                        * recheck receivers errors
+                        */
+                       if (ret == -ENOBUFS || ret == -EXFULL)
+                               pool_full = true;
+                       break;
+               }
+
+               sent_packets++;
+       }
+
+       for (tid = 0; tid < thread_nr; tid++) {
+               int thread_ret = 0;
+
+               if (thread_id[tid]) {
+                       pthread_join(thread_id[tid], (void *)&thread_ret);
+                       if (thread_ret < 0) {
+                               /* Update only if send did not fail */
+                               if (ret == 0)
+                                       ret = thread_ret;
+
+                               lost_packets++;
+                       }
+               }
+       }
+
+       /*
+        * When sending if we did fail with -ENOBUFS or -EXFULL
+        * then we should have set lost_packet and we should at
+        * least have sent_packets set to KDBUS_CONN_MAX_MSGS_PER_USER
+        */
+       if (pool_full) {
+               ASSERT_NONZERO(lost_packets);
+
+               /*
+                * We should at least send KDBUS_CONN_MAX_MSGS_PER_USER
+                *
+                * For every send operation we create a thread to
+                * recv the packet, so we keep the queue clean
+                */
+               ASSERT_RETURN(sent_packets,>=,(unsigned)KDBUS_CONN_MAX_MSGS_PER_USER);
+
+               /*
+                * Set ret to zero since we only failed due to
+                * the receiving threads that have not been
+                * scheduled
+                */
+               ret = 0;
+       }
+
+       return ret;
+}
+
+/* Return: TEST_OK or TEST_ERR on failure */
+static wur int kdbus_normal_test(const char *bus, const char *name,
+                            struct kdbus_conn **conn_db)
+{
+       ASSERT_RETURN(0,<=,kdbus_recv_in_threads(bus, name, conn_db));
+       return TEST_OK;
+}
+
+static wur int kdbus_fork_test_by_id(const char *bus,
+                                struct kdbus_conn **conn_db,
+                                int parent_status, int child_status)
+{
+       int ret;
+       pid_t pid;
+       uint64_t cookie = 0x9876ecba;
+       struct kdbus_msg *msg = NULL;
+       uint64_t offset = 0;
+       int status = 0;
+
+       bool parent_timedout;
+       bool child_timedout;
+
+#ifdef TIZEN
+       child_status = parent_status = 0;
+#endif
+
+       /*
+        * If the child_status is not EXIT_SUCCESS, then we expect
+        * that sending from the child will fail, thus receiving
+        * from parent must error with -ETIMEDOUT, and vice versa.
+        */
+       parent_timedout = !!child_status;
+       child_timedout = !!parent_status;
+
+       pid = fork();
+       ASSERT_RETURN_VAL(pid,>=,0, pid);
+
+       if (pid == 0) {
+               struct kdbus_conn *conn_src;
+
+               ASSERT_EXIT_ZERO(prctl(PR_SET_PDEATHSIG, SIGKILL));
+
+               ASSERT_EXIT_ZERO(drop_privileges(65534, 65534));
+
+               ASSERT_EXIT_NONZERO(conn_src = kdbus_hello(bus, 0, NULL, 0));
+
+               ASSERT_EXIT_ZERO(kdbus_add_match_empty(conn_src));
+
+               /*
+                * child_status is always checked against send
+                * operations, in case it fails always return
+                * EXIT_FAILURE.
+                */
+               ASSERT_EXIT(child_status,==,kdbus_msg_send(conn_src, NULL, cookie, 0, 0, 0, conn_db[0]->id));
+
+               ret = kdbus_msg_recv_poll(conn_src, 1009, NULL, NULL);
+
+               kdbus_conn_free(conn_src);
+
+               /*
+                * Child kdbus_msg_recv_poll() should timeout since
+                * the parent_status was set to a non EXIT_SUCCESS
+                * value.
+                */
+               if (child_timedout)
+                       exit(ret == -ETIMEDOUT ? EXIT_SUCCESS : EXIT_FAILURE);
+
+               exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+       }
+
+       ret = kdbus_msg_recv_poll(conn_db[0], 1009, &msg, &offset);
+       /*
+        * If parent_timedout is set then this should fail with
+        * -ETIMEDOUT since the child_status was set to a non
+        * EXIT_SUCCESS value. Otherwise, assume
+        * that kdbus_msg_recv_poll() has succeeded.
+        */
+       if (parent_timedout) {
+               ASSERT_RETURN_VAL(ret,==,-ETIMEDOUT, TEST_ERR);
+
+               /* timedout no need to continue, we don't have the
+                * child connection ID, so just terminate. */
+               goto out;
+       } else
+               ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       ret = kdbus_msg_send(conn_db[0], NULL, ++cookie, 0, 0, 0, msg->src_id);
+       /*
+        * parent_status is checked against send operations,
+        * on failures always return TEST_ERR.
+        */
+       ASSERT_RETURN_VAL(ret,==,parent_status, TEST_ERR);
+
+       kdbus_msg_free(msg);
+       ASSERT_ZERO(kdbus_free(conn_db[0], offset));
+
+out:
+       ret = waitpid(pid, &status, 0);
+       ASSERT_RETURN_VAL(ret,>=,0, ret);
+
+       return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR;
+}
+
+/*
+ * Return: TEST_OK, TEST_ERR or TEST_SKIP
+ * we return TEST_OK only if the children return with the expected
+ * 'expected_status' that is specified as an argument.
+ */
+static wur int kdbus_fork_test(const char *bus, const char *name,
+                          struct kdbus_conn **conn_db, int expected_status)
+{
+       pid_t pid;
+       int ret = 0;
+       int status = 0;
+
+       pid = fork();
+       ASSERT_RETURN_VAL(pid,>=,0, pid);
+
+       if (pid == 0) {
+               ASSERT_EXIT_ZERO(prctl(PR_SET_PDEATHSIG, SIGKILL));
+
+               ASSERT_EXIT_ZERO(drop_privileges(65534, 65534));
+
+               ret = kdbus_recv_in_threads(bus, name, conn_db);
+               exit(ret == expected_status ? EXIT_SUCCESS : EXIT_FAILURE);
+       }
+
+       ASSERT_RETURN(0,<=,waitpid(pid, &status, 0));
+
+       return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR;
+}
+
+/* Return EXIT_SUCCESS, EXIT_FAILURE or negative errno */
+static wur int __kdbus_clone_userns_test(const char *bus,
+                                    const char *name,
+                                    int expected_status)
+{
+       int efd;
+       pid_t pid;
+       int ret = 0;
+       unsigned int uid = 65534;
+       int status;
+
+       ret = drop_privileges(uid, uid);
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       /*
+        * Since we just dropped privileges, the dumpable flag was just
+        * cleared which makes the /proc/$clone_child/uid_map to be
+        * owned by root, hence any userns uid mapping will fail with
+        * -EPERM since the mapping will be done by uid 65534.
+        *
+        * To avoid this set the dumpable flag again which makes procfs
+        * update the /proc/$clone_child/ inodes owner to 65534.
+        *
+        * Using this we will be able write to /proc/$clone_child/uid_map
+        * as uid 65534 and map the uid 65534 to 0 inside the user
+        * namespace.
+        */
+       ret = prctl(PR_SET_DUMPABLE, SUID_DUMP_USER);
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       /* sync parent/child */
+       efd = eventfd(0, EFD_CLOEXEC);
+       ASSERT_RETURN_VAL(efd,>=,0, efd);
+
+       pid = syscall(__NR_clone, SIGCHLD|CLONE_NEWUSER, NULL);
+       if (pid < 0) {
+               ret = -errno;
+               kdbus_printf("error clone: %d (%m)\n", ret);
+               /*
+                * Normal user not allowed to create userns,
+                * so nothing to worry about ?
+                */
+               if (ret == -EPERM) {
+                       kdbus_printf("-- CLONE_NEWUSER TEST Failed for uid: %u\n"
+                               "-- Make sure that your kernel do not allow "
+                               "CLONE_NEWUSER for unprivileged users\n"
+                               "-- Upstream Commit: "
+                               "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=5eaf563e\n",
+                               uid);
+                       ret = 0;
+               }
+
+               return ret;
+       }
+
+       if (pid == 0) {
+               struct kdbus_conn *conn_src;
+               eventfd_t event_status = 0;
+
+               ASSERT_EXIT_ZERO(prctl(PR_SET_PDEATHSIG, SIGKILL));
+
+               ASSERT_EXIT(0,<=,eventfd_read(efd, &event_status));
+               ASSERT_EXIT(event_status,==,(eventfd_t)1);
+
+               /* ping connection from the new user namespace */
+               ASSERT_EXIT_NONZERO(conn_src = kdbus_hello(bus, 0, NULL, 0));
+
+               ASSERT_EXIT_ZERO(kdbus_add_match_empty(conn_src));
+
+               ret = kdbus_msg_send(conn_src, name, 0xabcd1234, 0, 0, 0, KDBUS_DST_ID_NAME);
+               kdbus_conn_free(conn_src);
+
+               exit(ret == expected_status ? EXIT_SUCCESS : EXIT_FAILURE);
+       }
+
+       ret = userns_map_uid_gid(pid, "0 65534 1", "0 65534 1");
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       /* Tell child we are ready */
+       ret = eventfd_write(efd, 1);
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       ret = waitpid(pid, &status, 0);
+       ASSERT_RETURN_VAL(ret,>=,0, ret);
+
+       CLOSE(efd);
+
+       return status == EXIT_SUCCESS ? TEST_OK : TEST_ERR;
+}
+
+static wur int kdbus_clone_userns_test(const char *bus,
+                                  const char *name,
+                                  struct kdbus_conn **conn_db,
+                                  int expected_status)
+{
+       pid_t pid;
+       int ret = 0;
+       int status;
+
+       pid = fork();
+       ASSERT_RETURN_VAL(pid,>=,0, -errno);
+
+       if (pid == 0) {
+               ret = prctl(PR_SET_PDEATHSIG, SIGKILL);
+               if (ret < 0)
+                       exit(EXIT_FAILURE);
+
+               exit(__kdbus_clone_userns_test(bus, name, expected_status));
+       }
+
+       /*
+        * Receive in the original (root privileged) user namespace,
+        * must fail with -ETIMEDOUT.
+        */
+       ret = kdbus_msg_recv_poll(conn_db[0], 1009, NULL, NULL);
+       ASSERT_RETURN(ret,==,expected_status?-ETIMEDOUT:0);
+
+       ret = waitpid(pid, &status, 0);
+       ASSERT_RETURN(ret,>=,0);
+
+       return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR;
+}
+
+wur int kdbus_test_policy_ns(struct kdbus_test_env *env)
+{
+       int i;
+       int ret;
+       struct kdbus_conn *activator = NULL;
+       struct kdbus_conn *policy_holder = NULL;
+       char *bus = env->buspath;
+
+       ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1);
+       ASSERT_RETURN(ret,>=,0);
+
+       /* no enough privileges, SKIP test */
+       if (!ret)
+               return TEST_SKIP;
+
+       /* we require user-namespaces */
+       if (access("/proc/self/uid_map", F_OK) != 0)
+               return TEST_SKIP;
+
+       /* uids/gids must be mapped */
+       if (!all_uids_gids_are_mapped())
+               return TEST_SKIP;
+
+       ASSERT_NONZERO(conn_db = calloc(MAX_CONN, sizeof(struct kdbus_conn *)));
+
+       memset(conn_db, 0, MAX_CONN * sizeof(struct kdbus_conn *));
+
+       ASSERT_NONZERO(conn_db[0] = kdbus_hello(bus, 0, NULL, 0));
+
+       ASSERT_ZERO(kdbus_add_match_empty(conn_db[0]));
+
+       ASSERT_EXIT_ZERO(kdbus_fork_test_by_id(bus, conn_db, -EPERM, -EPERM));
+
+       ASSERT_ZERO(kdbus_register_policy_holder(bus, POLICY_NAME, &policy_holder));
+
+       /* Try to register the same name with an activator */
+       ASSERT_ZERO(kdbus_register_same_activator(bus, POLICY_NAME, &activator));
+
+       /* Acquire POLICY_NAME */
+       ASSERT_ZERO(kdbus_name_acquire(conn_db[0], POLICY_NAME, NULL));
+
+       ASSERT_ZERO(kdbus_normal_test(bus, POLICY_NAME, conn_db));
+
+       ASSERT_ZERO(kdbus_list(conn_db[0], KDBUS_LIST_NAMES | KDBUS_LIST_UNIQUE | KDBUS_LIST_ACTIVATORS | KDBUS_LIST_QUEUED));
+
+       ASSERT_ZERO(kdbus_fork_test(bus, POLICY_NAME, conn_db, EXIT_SUCCESS));
+
+       /*
+        * children connections are able to talk to conn_db[0] since
+        * current POLICY_NAME TALK type is KDBUS_POLICY_ACCESS_WORLD,
+        * so expect EXIT_SUCCESS when sending from child. However,
+        * since the child's connection does not own any well-known
+        * name, The parent connection conn_db[0] should fail with
+        * -EPERM but since it is a privileged bus user the TALK is
+        *  allowed.
+        */
+       ASSERT_EXIT_ZERO(kdbus_fork_test_by_id(bus, conn_db, EXIT_SUCCESS, EXIT_SUCCESS));
+
+       /*
+        * Connections that can talk are perhaps being destroyed now.
+        * Restrict the policy and purge cache entries where the
+        * conn_db[0] is the destination.
+        *
+        * Now only connections with uid == 0 are allowed to talk.
+        */
+       ASSERT_ZERO(kdbus_set_policy_talk(policy_holder, POLICY_NAME, geteuid(), KDBUS_POLICY_ACCESS_USER));
+
+       /*
+        * Testing connections (FORK+DROP) again:
+        * After setting the policy re-check connections
+        * we expect the children to fail with -EPERM
+        */
+       ASSERT_ZERO(kdbus_fork_test(bus, POLICY_NAME, conn_db, ONTIZEN(0,-EPERM)));
+
+       /*
+        * Now expect that both parent and child to fail.
+        *
+        * Child should fail with -EPERM since we just restricted
+        * the POLICY_NAME TALK to uid 0 and its uid is 65534.
+        *
+        * Since the parent's connection will timeout when receiving
+        * from the child, we never continue. FWIW just put -EPERM.
+        */
+       ASSERT_EXIT_ZERO(kdbus_fork_test_by_id(bus, conn_db, -EPERM, -EPERM));
+
+       /* Check if the name can be reached in a new userns */
+       ASSERT_ZERO(kdbus_clone_userns_test(bus, POLICY_NAME, conn_db, ONTIZEN(0,-EPERM)));
+
+       for (i = 0; i < MAX_CONN; i++)
+               kdbus_conn_free(conn_db[i]);
+
+       kdbus_conn_free(activator);
+       kdbus_conn_free(policy_holder);
+
+       free(conn_db);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-policy-priv.c b/tests/kdbus/test-policy-priv.c
new file mode 100644 (file)
index 0000000..0d9d476
--- /dev/null
@@ -0,0 +1,1057 @@
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/capability.h>
+#include <sys/eventfd.h>
+#include <sys/wait.h>
+
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+static wur int test_policy_priv_by_id(const char *bus,
+                                 struct kdbus_conn *conn_dst,
+                                 int parent_status,
+                                 int child_status)
+{
+       uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef;
+
+#ifdef TIZEN
+       parent_status = child_status = 0;
+#endif
+
+       ASSERT_NONZERO(conn_dst);
+
+       RUN_UNPRIVILEGED_CONN(unpriv, bus, ({
+               ASSERT_EXIT(child_status,==,kdbus_msg_send(unpriv, NULL, expected_cookie, 0, 0, 0, conn_dst->id));
+       }));
+
+       ASSERT_RETURN(parent_status,==,kdbus_msg_recv_poll(conn_dst, 300, NULL, NULL));
+
+       return 0;
+}
+
+static wur int test_policy_priv_by_broadcast(const char *bus,
+                                        struct kdbus_conn *conn_dst,
+                                        int drop_second_user,
+                                        int parent_status,
+                                        int child_status)
+{
+       int efd;
+       int ret = 0;
+       eventfd_t event_status = 0;
+       struct kdbus_msg *msg = NULL;
+       uid_t second_uid = UNPRIV_UID;
+       gid_t second_gid = UNPRIV_GID;
+       struct kdbus_conn *child_2 = conn_dst;
+       uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef;
+
+#ifdef TIZEN
+       child_status = 0;
+       if (DO_NOT_DROP != drop_second_user)
+               parent_status = 0;
+#endif
+
+       /* Drop to another unprivileged user other than UNPRIV_UID */
+       if (drop_second_user == DROP_OTHER_UNPRIV) {
+               second_uid = UNPRIV_UID - 1;
+               second_gid = UNPRIV_GID - 1;
+       }
+
+       /* child will signal parent to send broadcast */
+       efd = eventfd(0, EFD_CLOEXEC);
+       ASSERT_RETURN_VAL(efd,>=,0, efd);
+
+       RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({
+               struct kdbus_conn *child;
+
+               ASSERT_EXIT_NONZERO(child = kdbus_hello(bus, 0, NULL, 0));
+
+               ASSERT_EXIT_ZERO(kdbus_add_match_empty(child));
+
+               /* signal parent */
+               ASSERT_EXIT_ZERO(eventfd_write(efd, 1));
+
+               /* Use a little bit high time */
+               ASSERT_EXIT(child_status,==,kdbus_msg_recv_poll(child, 500, &msg, NULL));
+
+               /*
+                * If we expect the child to get the broadcast
+                * message, then check the received cookie.
+                */
+               if (!child_status) {
+                       ASSERT_EXIT(expected_cookie,==,msg->cookie);
+                       kdbus_msg_free(msg);
+               }
+
+               /* Use expected_cookie since 'msg' might be NULL */
+               ASSERT_EXIT_ZERO(kdbus_msg_send(child, NULL, expected_cookie + 1, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+               kdbus_conn_free(child);
+       }),
+       ({
+               bool got_own_broadcast = 0;
+               #define RECEIVE_FROM_PARENT() for (;;) {\
+                       /* Use a little bit high time */\
+                       ret = kdbus_msg_recv_poll(child_2, 1000, &msg, NULL);\
+                       if (!ret && child_2->id == msg->src_id) {\
+                               ASSERT_ZERO(got_own_broadcast);\
+                               got_own_broadcast = 1;\
+                               kdbus_msg_free(msg);\
+                               continue;\
+                       }\
+                       ASSERT_RETURN(parent_status,==,ret);\
+                       /* Check returned cookie in case we expect success. */\
+                       if (!ret) {\
+                               ASSERT_RETURN(msg->cookie,==,expected_cookie + 1);\
+                               kdbus_msg_free(msg);\
+                       }\
+                       break;\
+               }
+
+               if (drop_second_user == DO_NOT_DROP) {
+                       ASSERT_NONZERO(child_2);
+
+                       ASSERT_RETURN(0,<=,eventfd_read(efd, &event_status));
+                       ASSERT_RETURN(event_status,==,(eventfd_t)1);
+
+                       ASSERT_ZERO(kdbus_msg_send(child_2, NULL, expected_cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+                       RECEIVE_FROM_PARENT();
+               } else {
+                       /*
+                        * Two unprivileged users will try to
+                        * communicate using broadcast.
+                        */
+                       RUN_UNPRIVILEGED(second_uid, second_gid, ({
+                               ASSERT_EXIT_NONZERO(child_2 = kdbus_hello(bus, 0, NULL, 0));
+
+                               ASSERT_EXIT_ZERO(kdbus_add_match_empty(child_2));
+
+                               ASSERT_EXIT(0,<=,eventfd_read(efd, &event_status));
+                               ASSERT_EXIT(event_status,==,(eventfd_t)1);
+
+                               ASSERT_EXIT_ZERO(kdbus_msg_send(child_2, NULL, expected_cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+                               RECEIVE_FROM_PARENT();
+                               kdbus_conn_free(child_2);
+                       }),
+                       ({}));
+               }
+       }));
+#undef RECEIVE_FROM_PARENT
+
+       CLOSE(efd);
+
+       return ret;
+}
+
+static void nosig(int sig)
+{
+       UNUSED(sig);
+}
+
+static wur int test_priv_before_policy_upload(struct kdbus_test_env *env)
+{
+       int ret = 0;
+       struct kdbus_conn *conn;
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /*
+        * Make sure unprivileged bus user cannot acquire names
+        * before registering any policy holder.
+        */
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT(0,ONTIZEN(==,>),kdbus_name_acquire(unpriv, "com.example.a", NULL));
+       }));
+
+       /*
+        * Make sure unprivileged bus users cannot talk by default
+        * to privileged ones, unless a policy holder that allows
+        * this was uploaded.
+        */
+
+       ASSERT_ZERO(test_policy_priv_by_id(env->buspath, conn, -ETIMEDOUT, -EPERM));
+
+       /* Activate matching for a privileged connection */
+       ASSERT_ZERO(kdbus_add_match_empty(conn));
+
+       /*
+        * First make sure that BROADCAST with msg flag
+        * KDBUS_MSG_EXPECT_REPLY will fail with -ENOTUNIQ
+        */
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT(-ENOTUNIQ,==,kdbus_msg_send(unpriv, NULL, 0xdeadbeef, KDBUS_MSG_EXPECT_REPLY, 5000000000ULL, 0, KDBUS_DST_ID_BROADCAST));
+       }));
+
+       /*
+        * Test broadcast with a privileged connection.
+        *
+        * The first unprivileged receiver should not get the
+        * broadcast message sent by the privileged connection,
+        * since there is no a TALK policy that allows the
+        * unprivileged to TALK to the privileged connection. It
+        * will fail with -ETIMEDOUT
+        *
+        * Then second case:
+        * The privileged connection should get the broadcast
+        * message from the unprivileged one. Since the receiver is
+        * a privileged bus user and it has default TALK access to
+        * all connections it will receive those.
+        */
+
+       ASSERT_ZERO(test_policy_priv_by_broadcast(env->buspath, conn, DO_NOT_DROP, 0, -ETIMEDOUT));
+
+
+       /*
+        * Test broadcast with two unprivileged connections running
+        * under the same user.
+        *
+        * Both connections should succeed.
+        */
+
+       ASSERT_ZERO(test_policy_priv_by_broadcast(env->buspath, NULL, DROP_SAME_UNPRIV, 0, 0));
+
+       /*
+        * Test broadcast with two unprivileged connections running
+        * under different users.
+        *
+        * Both connections will fail with -ETIMEDOUT.
+        */
+
+       ASSERT_ZERO(test_policy_priv_by_broadcast(env->buspath, NULL, DROP_OTHER_UNPRIV, -ETIMEDOUT, -ETIMEDOUT));
+
+       kdbus_conn_free(conn);
+
+       return ret;
+}
+
+static wur int test_broadcast_after_policy_upload(struct kdbus_test_env *env)
+{
+       int efd;
+       eventfd_t event_status = 0;
+       struct kdbus_msg *msg = NULL;
+       struct kdbus_conn *owner_a, *owner_b;
+       struct kdbus_conn *holder_a, *holder_b;
+       struct kdbus_policy_access access = {};
+       uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef;
+
+       ASSERT_NONZERO(owner_a = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ASSERT_EXIT_ZERO(kdbus_name_acquire(owner_a, "com.example.broadcastA", NULL));
+
+       /*
+        * Make sure unprivileged bus users cannot talk by default
+        * to privileged ones, unless a policy holder that allows
+        * this was uploaded.
+        */
+
+       ++expected_cookie;
+       ASSERT_ZERO(test_policy_priv_by_id(env->buspath, owner_a, -ETIMEDOUT, -EPERM));
+
+       /*
+        * Make sure that privileged won't receive broadcasts unless
+        * it installs a match. It will fail with -ETIMEDOUT
+        *
+        * At same time check that the unprivileged connection will
+        * not receive the broadcast message from the privileged one
+        * since the privileged one owns a name with a restricted
+        * policy TALK (actually the TALK policy is still not
+        * registered so we fail by default), thus the unprivileged
+        * receiver is not able to TALK to that name.
+        */
+       ASSERT_ZERO(test_policy_priv_by_broadcast(env->buspath, owner_a, DO_NOT_DROP, -ETIMEDOUT, -ETIMEDOUT));
+
+       /* Activate matching for a privileged connection */
+       ASSERT_ZERO(kdbus_add_match_empty(owner_a));
+
+       /*
+        * Redo the previous test. The privileged conn owner_a is
+        * able to TALK to any connection so it will receive the
+        * broadcast message now.
+        */
+
+       ASSERT_ZERO(test_policy_priv_by_broadcast(env->buspath, owner_a, DO_NOT_DROP, 0, -ETIMEDOUT));
+
+       /*
+        * Test that broadcast between two unprivileged users running
+        * under the same user still succeed.
+        */
+
+       ASSERT_ZERO(test_policy_priv_by_broadcast(env->buspath, NULL, DROP_SAME_UNPRIV, 0, 0));
+
+       /*
+        * Test broadcast with two unprivileged connections running
+        * under different users.
+        *
+        * Both connections will fail with -ETIMEDOUT.
+        */
+
+       ASSERT_ZERO(test_policy_priv_by_broadcast(env->buspath, NULL, DROP_OTHER_UNPRIV, -ETIMEDOUT, -ETIMEDOUT));
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_USER,
+               .id = geteuid(),
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       ASSERT_NONZERO(holder_a = kdbus_hello_registrar(env->buspath, "com.example.broadcastA", &access, 1, KDBUS_HELLO_POLICY_HOLDER));
+       ASSERT_NONZERO(holder_b = kdbus_hello_registrar(env->buspath, "com.example.broadcastB", &access, 1, KDBUS_HELLO_POLICY_HOLDER));
+
+       /* Free connections and their received messages and restart */
+       kdbus_conn_free(owner_a);
+
+       ASSERT_NONZERO(owner_a = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /* Activate matching for a privileged connection */
+       ASSERT_ZERO(kdbus_add_match_empty(owner_a));
+
+       ASSERT_EXIT_ZERO(kdbus_name_acquire(owner_a, "com.example.broadcastA", NULL));
+
+       ASSERT_NONZERO(owner_b = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ASSERT_EXIT_ZERO(kdbus_name_acquire(owner_b, "com.example.broadcastB", NULL));
+
+       /* Activate matching for a privileged connection */
+       ASSERT_ZERO(kdbus_add_match_empty(owner_b));
+
+       /*
+        * Test that even if "com.example.broadcastA" and
+        * "com.example.broadcastB" do have a TALK access by default
+        * they are able to signal each other using broadcast due to
+        * the fact they are privileged connections, they receive
+        * all broadcasts if the match allows it.
+        */
+
+       ++expected_cookie;
+       ASSERT_ZERO(kdbus_msg_send(owner_a, NULL, expected_cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+       ASSERT_ZERO(kdbus_msg_recv_poll(owner_b, 100, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,expected_cookie);
+       /* Check src ID */
+       ASSERT_RETURN(msg->src_id,==,owner_a->id);
+       kdbus_msg_free(msg);
+
+       /* purge self-broadcast */
+       ASSERT_ZERO(kdbus_msg_recv_poll(owner_a, 100, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,expected_cookie);
+       /* Check src ID */
+       ASSERT_RETURN(msg->src_id,==,owner_a->id);
+
+       /* Release name "com.example.broadcastB" */
+
+       ASSERT_EXIT_ZERO(kdbus_name_release(owner_b, "com.example.broadcastB"));
+
+       /* KDBUS_POLICY_OWN for unprivileged connections */
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_WORLD,
+               .id = geteuid(),
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       /* Update the policy so unprivileged will own the name */
+
+       ASSERT_ZERO(kdbus_conn_update_policy(holder_b, "com.example.broadcastB", &access, 1));
+
+       /*
+        * Send broadcasts from an unprivileged connection that
+        * owns a name "com.example.broadcastB".
+        *
+        * We'll have four destinations here:
+        *
+        * 1) destination owner_a: privileged connection that owns
+        * "com.example.broadcastA". It will receive the broadcast
+        * since it is a privileged has default TALK access to all
+        * connections, and it is subscribed to the match.
+        * Will succeed.
+        *
+        * owner_b: privileged connection (running under a different
+        * uid) that do not own names, but with an empty broadcast
+        * match, so it will receive broadcasts since it has default
+        * TALK access to all connection.
+        *
+        * unpriv_a: unpriv connection that do not own any name.
+        * It will receive the broadcast since it is running under
+        * the same user of the one broadcasting and did install
+        * matches. It should get the message.
+        *
+        * unpriv_b: unpriv connection is not interested in broadcast
+        * messages, so it did not install broadcast matches. Should
+        * fail with -ETIMEDOUT
+        */
+
+       ++expected_cookie;
+       efd = eventfd(0, EFD_CLOEXEC);
+       ASSERT_RETURN_VAL(efd,>=,0, efd);
+
+       RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_UID, ({
+               struct kdbus_conn *unpriv_owner;
+               struct kdbus_conn *unpriv_a, *unpriv_b;
+
+               ASSERT_EXIT_NONZERO(unpriv_owner = kdbus_hello(env->buspath, 0, NULL, 0));
+               ASSERT_EXIT_NONZERO(unpriv_a = kdbus_hello(env->buspath, 0, NULL, 0));
+               ASSERT_EXIT_NONZERO(unpriv_b = kdbus_hello(env->buspath, 0, NULL, 0));
+
+               ASSERT_EXIT_ZERO(kdbus_name_acquire(unpriv_owner, "com.example.broadcastB", NULL));
+
+               ASSERT_EXIT_ZERO(kdbus_add_match_empty(unpriv_a));
+
+               /* Signal that we are doing broadcasts */
+               ASSERT_EXIT_ZERO(eventfd_write(efd, 1));
+
+               /*
+                * Do broadcast from a connection that owns the
+                * names "com.example.broadcastB".
+                */
+               ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv_owner, NULL, expected_cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+               /*
+                * Unprivileged connection running under the same
+                * user. It should succeed.
+                */
+               ASSERT_EXIT_ZERO(kdbus_msg_recv_poll(unpriv_a, 300, &msg, NULL));
+               ASSERT_EXIT(msg->cookie,==,expected_cookie);
+
+               /*
+                * Did not install matches, not interested in
+                * broadcasts
+                */
+               ASSERT_EXIT(-ETIMEDOUT,==,kdbus_msg_recv_poll(unpriv_b, 300, NULL, NULL));
+       }),
+       ({
+               ASSERT_RETURN(0,<=,eventfd_read(efd, &event_status));
+               ASSERT_RETURN(event_status,==,(eventfd_t)1);
+
+               /*
+                * owner_a must fail with -ETIMEDOUT, since it owns
+                * name "com.example.broadcastA" and its TALK
+                * access is restriced.
+                */
+               ASSERT_ZERO(kdbus_msg_recv_poll(owner_a, 300, &msg, NULL));
+
+               /* confirm the received cookie */
+               ASSERT_RETURN(msg->cookie,==,expected_cookie);
+
+               kdbus_msg_free(msg);
+
+               /*
+                * owner_b got the broadcast from an unprivileged
+                * connection.
+                */
+               ASSERT_ZERO(kdbus_msg_recv_poll(owner_b, 300, &msg, NULL));
+
+               /* confirm the received cookie */
+               ASSERT_RETURN(msg->cookie,==,expected_cookie);
+
+               kdbus_msg_free(msg);
+
+       }));
+
+       CLOSE(efd);
+
+       /*
+        * Test broadcast with two unprivileged connections running
+        * under different users.
+        *
+        * Both connections will fail with -ETIMEDOUT.
+        */
+
+       ASSERT_ZERO(test_policy_priv_by_broadcast(env->buspath, NULL, DROP_OTHER_UNPRIV, -ETIMEDOUT, -ETIMEDOUT));
+
+       /* Drop received broadcasts by privileged */
+       ASSERT_ZERO(kdbus_msg_recv_poll(owner_a, 100, NULL, NULL));
+       ASSERT_ZERO(kdbus_msg_recv_poll(owner_a, 100, NULL, NULL));
+
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(owner_a, NULL, NULL));
+
+       ASSERT_ZERO(kdbus_msg_recv_poll(owner_b, 100, NULL, NULL));
+       ASSERT_ZERO(kdbus_msg_recv_poll(owner_b, 100, NULL, NULL));
+
+       ASSERT_RETURN(-EAGAIN,==,kdbus_msg_recv(owner_b, NULL, NULL));
+
+       /*
+        * Perform last tests, allow others to talk to name
+        * "com.example.broadcastA". So now receiving broadcasts
+        * from it should succeed since the TALK policy allow it.
+        */
+
+       /* KDBUS_POLICY_OWN for unprivileged connections */
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_WORLD,
+               .id = geteuid(),
+               .access = KDBUS_POLICY_TALK,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(holder_a, "com.example.broadcastA", &access, 1));
+
+       /*
+        * Unprivileged is able to TALK to "com.example.broadcastA"
+        * now so it will receive its broadcasts
+        */
+       ASSERT_ZERO(test_policy_priv_by_broadcast(env->buspath, owner_a, DO_NOT_DROP, 0, 0));
+
+       ++expected_cookie;
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_name_acquire(unpriv, "com.example.broadcastB", NULL));
+               ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv, NULL, expected_cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+       }));
+
+       /* owner_a is privileged it will get the broadcast now. */
+       ASSERT_ZERO(kdbus_msg_recv_poll(owner_a, 300, &msg, NULL));
+
+       /* confirm the received cookie */
+       ASSERT_RETURN(msg->cookie,==,expected_cookie);
+
+       kdbus_msg_free(msg);
+
+       /*
+        * owner_a released name "com.example.broadcastA". It should
+        * receive broadcasts since it is still privileged and has
+        * the right match.
+        *
+        * Unprivileged connection will own a name and will try to
+        * signal to the privileged connection.
+        */
+
+       ASSERT_EXIT_ZERO(kdbus_name_release(owner_a, "com.example.broadcastA"));
+
+       ++expected_cookie;
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_name_acquire(unpriv, "com.example.broadcastB", NULL));
+               ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv, NULL, expected_cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+       }));
+
+       /* owner_a will get the broadcast now. */
+       ASSERT_ZERO(kdbus_msg_recv_poll(owner_a, 300, &msg, NULL));
+
+       /* confirm the received cookie */
+       ASSERT_RETURN(msg->cookie,==,expected_cookie);
+
+       kdbus_msg_free(msg);
+
+       kdbus_conn_free(owner_a);
+       kdbus_conn_free(owner_b);
+       kdbus_conn_free(holder_a);
+       kdbus_conn_free(holder_b);
+
+       return 0;
+}
+
+static wur int test_policy_priv(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn_a, *conn_b, *conn, *owner;
+       struct kdbus_policy_access access, *acc;
+       sigset_t sset;
+       size_t num;
+       int ret;
+
+       /*
+        * Make sure we have CAP_SETUID/SETGID so we can drop privileges
+        */
+
+       ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1);
+       ASSERT_RETURN(ret,>=,0);
+
+       if (!ret)
+               return TEST_SKIP;
+
+       /* make sure that uids and gids are mapped */
+       if (!all_uids_gids_are_mapped())
+               return TEST_SKIP;
+
+       /*
+        * Setup:
+        *  conn_a: policy holder for com.example.a
+        *  conn_b: name holder of com.example.b
+        */
+
+       signal(SIGUSR1, nosig);
+       sigemptyset(&sset);
+       sigaddset(&sset, SIGUSR1);
+       sigprocmask(SIG_BLOCK, &sset, NULL);
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       /*
+        * Before registering any policy holder, make sure that the
+        * bus is secure by default. This test is necessary, it catches
+        * several cases where old D-Bus was vulnerable.
+        */
+
+       ASSERT_ZERO(test_priv_before_policy_upload(env));
+
+       /*
+        * Make sure unprivileged are not able to register policy
+        * holders
+        */
+
+       RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({
+               struct kdbus_conn *holder;
+
+               holder = kdbus_hello_registrar(env->buspath,
+                                              "com.example.a", NULL, 0,
+                                              KDBUS_HELLO_POLICY_HOLDER);
+               ASSERT_EXIT(errno,==,EPERM);
+               ASSERT_ZERO(holder);
+       }),
+       ({}));
+
+
+       /* Register policy holder */
+
+       ASSERT_NONZERO(conn_a = kdbus_hello_registrar(env->buspath, "com.example.a", NULL, 0, KDBUS_HELLO_POLICY_HOLDER));
+       ASSERT_NONZERO(conn_b = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ASSERT_EXIT_ZERO(kdbus_name_acquire(conn_b, "com.example.b", NULL));
+
+       /*
+        * Make sure bus-owners can always acquire names.
+        */
+       ASSERT_EXIT_ZERO(kdbus_name_acquire(conn, "com.example.a", NULL));
+
+       kdbus_conn_free(conn);
+
+       /*
+        * Make sure unprivileged users cannot acquire names with default
+        * policy assigned.
+        */
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT(0,ONTIZEN(==,!=),kdbus_name_acquire(unpriv, "com.example.a", NULL));
+       }));
+
+       /*
+        * Make sure unprivileged users can acquire names if we make them
+        * world-accessible.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_WORLD,
+               .id = 0,
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       /*
+        * Make sure unprivileged/normal connections are not able
+        * to update policies
+        */
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT(-EOPNOTSUPP,==,kdbus_conn_update_policy(unpriv, "com.example.a", &access, 1));
+       }));
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_name_acquire(unpriv, "com.example.a", NULL));
+       }));
+
+       /*
+        * Make sure unprivileged users can acquire names if we make them
+        * gid-accessible. But only if the gid matches.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_GROUP,
+               .id = UNPRIV_GID,
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_name_acquire(unpriv, "com.example.a", NULL));
+       }));
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_GROUP,
+               .id = 1,
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT(0,ONTIZEN(==,!=),kdbus_name_acquire(unpriv, "com.example.a", NULL));
+       }));
+
+       /*
+        * Make sure unprivileged users can acquire names if we make them
+        * uid-accessible. But only if the uid matches.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_USER,
+               .id = UNPRIV_UID,
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_name_acquire(unpriv, "com.example.a", NULL));
+       }));
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_USER,
+               .id = 1,
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT(0,ONTIZEN(==,!=),kdbus_name_acquire(unpriv, "com.example.a", NULL));
+       }));
+
+       /*
+        * Make sure unprivileged users cannot acquire names if no owner-policy
+        * matches, even if SEE/TALK policies match.
+        */
+
+       num = 4;
+       acc = (struct kdbus_policy_access[]){
+               {
+                       .type = KDBUS_POLICY_ACCESS_GROUP,
+                       .id = UNPRIV_GID,
+                       .access = KDBUS_POLICY_SEE,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = UNPRIV_UID,
+                       .access = KDBUS_POLICY_TALK,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_WORLD,
+                       .id = 0,
+                       .access = KDBUS_POLICY_TALK,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_WORLD,
+                       .id = 0,
+                       .access = KDBUS_POLICY_SEE,
+               },
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.a", acc, num));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT(0,ONTIZEN(==,!=),kdbus_name_acquire(unpriv, "com.example.a", NULL));
+       }));
+
+       /*
+        * Make sure unprivileged users can acquire names if the only matching
+        * policy is somewhere in the middle.
+        */
+
+       num = 5;
+       acc = (struct kdbus_policy_access[]){
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = 1,
+                       .access = KDBUS_POLICY_OWN,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = 2,
+                       .access = KDBUS_POLICY_OWN,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = UNPRIV_UID,
+                       .access = KDBUS_POLICY_OWN,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = 3,
+                       .access = KDBUS_POLICY_OWN,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = 4,
+                       .access = KDBUS_POLICY_OWN,
+               },
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.a", acc, num));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_name_acquire(unpriv, "com.example.a", NULL));
+       }));
+
+       /*
+        * Clear policies
+        */
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.a", NULL, 0));
+
+       /*
+        * Make sure privileged bus users can _always_ talk to others.
+        */
+
+       ASSERT_NONZERO(conn = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ASSERT_EXIT_ZERO(kdbus_msg_send(conn, "com.example.b", 0xdeadbeef, 0, 0, 0, 0));
+
+       ASSERT_EXIT(0,<=,kdbus_msg_recv_poll(conn_b, 300, NULL, NULL));
+
+       kdbus_conn_free(conn);
+
+       /*
+        * Make sure unprivileged bus users cannot talk by default.
+        */
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT(ONTIZEN(0,-EPERM),==,kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, 0, 0));
+       }));
+
+       /*
+        * Make sure unprivileged bus users can talk to equals, even without
+        * policy.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_USER,
+               .id = UNPRIV_UID,
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.c", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               struct kdbus_conn *owner;
+
+               ASSERT_NONZERO(owner = kdbus_hello(env->buspath, 0, NULL, 0));
+
+               ASSERT_EXIT_ZERO(kdbus_name_acquire(owner, "com.example.c", NULL));
+
+               ASSERT_ZERO(kdbus_msg_send(unpriv, "com.example.c", 0xdeadbeef, 0, 0, 0, 0));
+               ASSERT_EXIT(0,<=,kdbus_msg_recv_poll(owner, 100, NULL, NULL));
+
+               kdbus_conn_free(owner);
+       }));
+
+       /*
+        * Make sure unprivileged bus users can talk to privileged users if a
+        * suitable UID policy is set.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_USER,
+               .id = UNPRIV_UID,
+               .access = KDBUS_POLICY_TALK,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, 0, 0));
+       }));
+
+       ASSERT_EXIT(0,<=,kdbus_msg_recv_poll(conn_b, 100, NULL, NULL));
+
+       /*
+        * Make sure unprivileged bus users can talk to privileged users if a
+        * suitable GID policy is set.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_GROUP,
+               .id = UNPRIV_GID,
+               .access = KDBUS_POLICY_TALK,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, 0, 0));
+       }));
+
+       ASSERT_EXIT(0,<=,kdbus_msg_recv_poll(conn_b, 100, NULL, NULL));
+
+       /*
+        * Make sure unprivileged bus users can talk to privileged users if a
+        * suitable WORLD policy is set.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_WORLD,
+               .id = 0,
+               .access = KDBUS_POLICY_TALK,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, 0, 0));
+       }));
+
+       ASSERT_EXIT(0,<=,kdbus_msg_recv_poll(conn_b, 100, NULL, NULL));
+
+       /*
+        * Make sure unprivileged bus users cannot talk to privileged users if
+        * no suitable policy is set.
+        */
+
+       num = 5;
+       acc = (struct kdbus_policy_access[]){
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = 0,
+                       .access = KDBUS_POLICY_OWN,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = 1,
+                       .access = KDBUS_POLICY_TALK,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = UNPRIV_UID,
+                       .access = KDBUS_POLICY_SEE,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = 3,
+                       .access = KDBUS_POLICY_TALK,
+               },
+               {
+                       .type = KDBUS_POLICY_ACCESS_USER,
+                       .id = 4,
+                       .access = KDBUS_POLICY_TALK,
+               },
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.b", acc, num));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT(ONTIZEN(0,-EPERM),==,kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, 0, 0));
+       }));
+
+       /*
+        * Make sure unprivileged bus users can talk to privileged users if a
+        * suitable OWN privilege overwrites TALK.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_WORLD,
+               .id = 0,
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, 0, 0));
+       }));
+
+       ASSERT_EXIT(0,<=,kdbus_msg_recv_poll(conn_b, 100, NULL, NULL));
+
+       /*
+        * Make sure the TALK cache is reset correctly when policies are
+        * updated.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_WORLD,
+               .id = 0,
+               .access = KDBUS_POLICY_TALK,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1));
+
+       RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, 0, 0));
+
+               ASSERT_EXIT(0,<=,kdbus_msg_recv_poll(conn_b, 100, NULL, NULL));
+
+               ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.b", NULL, 0));
+
+               ASSERT_EXIT(ONTIZEN(0,-EPERM),==,kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, 0, 0));
+       }));
+
+       /*
+        * Make sure the TALK cache is reset correctly when policy holders
+        * disconnect.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_WORLD,
+               .id = 0,
+               .access = KDBUS_POLICY_OWN,
+       };
+
+       ASSERT_NONZERO(conn = kdbus_hello_registrar(env->buspath, "com.example.c", NULL, 0, KDBUS_HELLO_POLICY_HOLDER));
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn, "com.example.c", &access, 1));
+
+       ASSERT_NONZERO(owner = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       ASSERT_ZERO(kdbus_name_acquire(owner, "com.example.c", NULL));
+
+       RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({
+               struct kdbus_conn *unpriv;
+
+               /* wait for parent to be finished */
+               sigemptyset(&sset);
+               ret = sigsuspend(&sset);
+               ASSERT_RETURN(errno,==,EINTR);
+               ASSERT_RETURN(ret,==,-1);
+
+               ASSERT_NONZERO(unpriv = kdbus_hello(env->buspath, 0, NULL, 0));
+
+               ASSERT_EXIT_ZERO(kdbus_msg_send(unpriv, "com.example.c", 0xdeadbeef, 0, 0, 0, 0));
+
+               ASSERT_EXIT(0,<=,kdbus_msg_recv_poll(owner, 100, NULL, NULL));
+
+               /* free policy holder */
+               kdbus_conn_free(conn);
+
+               ASSERT_EXIT(ONTIZEN(0,-EPERM),==,kdbus_msg_send(unpriv, "com.example.c", 0xdeadbeef, 0, 0, 0, 0));
+
+               kdbus_conn_free(unpriv);
+       }), ({
+               /* make sure policy holder is only valid in child */
+               kdbus_conn_free(conn);
+               kill(pid, SIGUSR1);
+       }));
+
+       /*
+        * The following tests are necessary.
+        */
+
+       ASSERT_ZERO(test_broadcast_after_policy_upload(env));
+
+       kdbus_conn_free(owner);
+
+       /*
+        * cleanup resources
+        */
+
+       kdbus_conn_free(conn_b);
+       kdbus_conn_free(conn_a);
+
+       return TEST_OK;
+}
+
+wur int kdbus_test_policy_priv(struct kdbus_test_env *env)
+{
+       pid_t pid;
+       int ret;
+
+       /* make sure to exit() if a child returns from fork() */
+       pid = getpid();
+       ret = test_policy_priv(env);
+       if (pid != getpid())
+               exit(1);
+
+       return ret;
+}
diff --git a/tests/kdbus/test-policy.c b/tests/kdbus/test-policy.c
new file mode 100644 (file)
index 0000000..830440b
--- /dev/null
@@ -0,0 +1,60 @@
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+wur int kdbus_test_policy(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn_a, *conn_b;
+       struct kdbus_policy_access access;
+
+       /* Invalid name */
+       ASSERT_ZERO(kdbus_hello_registrar(env->buspath, ".example.a", NULL, 0, KDBUS_HELLO_POLICY_HOLDER));
+       ASSERT_ZERO(kdbus_hello_registrar(env->buspath, "example", NULL, 0, KDBUS_HELLO_POLICY_HOLDER));
+
+       ASSERT_NONZERO(conn_a = kdbus_hello_registrar(env->buspath, "com.example.a", NULL, 0, KDBUS_HELLO_POLICY_HOLDER));
+       ASSERT_NONZERO(conn_b = kdbus_hello_registrar(env->buspath, "com.example.b", NULL, 0, KDBUS_HELLO_POLICY_HOLDER));
+
+       /*
+        * Verify there cannot be any duplicate entries, except for specific vs.
+        * wildcard entries.
+        */
+
+       access = (struct kdbus_policy_access){
+               .type = KDBUS_POLICY_ACCESS_USER,
+               .id = geteuid(),
+               .access = KDBUS_POLICY_SEE,
+       };
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1));
+
+       ASSERT_RETURN(-EEXIST,==,kdbus_conn_update_policy(conn_b, "com.example.a", &access, 1));
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_b, "com.example.a.*", &access, 1));
+
+       ASSERT_RETURN(-EEXIST,==,kdbus_conn_update_policy(conn_a, "com.example.a.*", &access, 1));
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_a, "com.example.*", &access, 1));
+
+       ASSERT_ZERO(kdbus_conn_update_policy(conn_b, "com.example.a", &access, 1));
+
+       ASSERT_RETURN(-EEXIST,==,kdbus_conn_update_policy(conn_b, "com.example.*", &access, 1));
+
+       /* Invalid name */
+       ASSERT_RETURN(-EINVAL,==,kdbus_conn_update_policy(conn_b, ".example.*", &access, 1));
+
+       ASSERT_RETURN(-EINVAL,==,kdbus_conn_update_policy(conn_b, "example", &access, 1));
+
+       kdbus_conn_free(conn_b);
+       kdbus_conn_free(conn_a);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-sync.c b/tests/kdbus/test-sync.c
new file mode 100644 (file)
index 0000000..3e7212c
--- /dev/null
@@ -0,0 +1,325 @@
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/eventfd.h>
+
+#include "kdbus-api.h"
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+static struct kdbus_conn *conn_a, *conn_b;
+static unsigned int cookie = 0xdeadbeef;
+
+static void nop_handler(int sig) { UNUSED(sig); }
+
+static int interrupt_sync(struct kdbus_conn *conn_src,
+                         struct kdbus_conn *conn_dst)
+{
+       pid_t pid;
+       int ret, status;
+       struct kdbus_msg *msg = NULL;
+       struct sigaction sa = {
+               .sa_handler = nop_handler,
+               .sa_flags = SA_NOCLDSTOP|SA_RESTART,
+       };
+
+       cookie++;
+       pid = fork();
+       ASSERT_RETURN_VAL(pid,>=,0, pid);
+
+       if (pid == 0) {
+               ASSERT_EXIT_ZERO(sigaction(SIGINT, &sa, NULL));
+
+               ASSERT_EXIT(-ETIMEDOUT,==,kdbus_msg_send_sync(conn_dst, NULL, cookie, KDBUS_MSG_EXPECT_REPLY, 100000000ULL, 0, conn_src->id, -1));
+
+               exit(EXIT_SUCCESS);
+       }
+
+       ASSERT_ZERO(kdbus_msg_recv_poll(conn_src, 100, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       kdbus_msg_free(msg);
+
+       ret = kill(pid, SIGINT);
+       ASSERT_RETURN_VAL(ret,==,0, ret);
+
+       ret = waitpid(pid, &status, 0);
+       ASSERT_RETURN_VAL(ret,>=,0, ret);
+
+       if (WIFSIGNALED(status))
+               return TEST_ERR;
+
+       ASSERT_RETURN(-ETIMEDOUT,==,kdbus_msg_recv_poll(conn_src, 100, NULL, NULL));
+
+       return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR;
+}
+
+static int close_epipe_sync(const char *bus)
+{
+       pid_t pid;
+       int ret, status;
+       struct kdbus_conn *conn_src;
+       struct kdbus_conn *conn_dst;
+       struct kdbus_msg *msg = NULL;
+
+       ASSERT_NONZERO(conn_src = kdbus_hello(bus, 0, NULL, 0));
+
+       ASSERT_ZERO(kdbus_add_match_empty(conn_src));
+
+       ASSERT_NONZERO(conn_dst = kdbus_hello(bus, 0, NULL, 0));
+
+       cookie++;
+       pid = fork();
+       ASSERT_RETURN_VAL(pid,>=,0, pid);
+
+       if (pid == 0) {
+               uint64_t dst_id;
+
+               /* close our reference */
+               dst_id = conn_dst->id;
+               kdbus_conn_free(conn_dst);
+
+               ASSERT_EXIT_ZERO(kdbus_msg_recv_poll(conn_src, -1, &msg, NULL));
+               ASSERT_EXIT(msg->cookie,==,cookie);
+               ASSERT_EXIT(msg->src_id,==,dst_id);
+
+               cookie++;
+               /* it seems this test is inherently racy - EPIPE and ECONNRESET are pretty much indistinguishable - we should probably check for either of them */
+               ret = kdbus_msg_send_sync(conn_src, NULL, cookie, KDBUS_MSG_EXPECT_REPLY, 5000000000000ULL, 0, dst_id, -1);
+               if (-EPIPE != ret)
+                       ASSERT_EXIT(-ECONNRESET,==,ret); /* need big timeout because we're not synchronized wrt dst_id closing */
+
+               exit(EXIT_SUCCESS);
+       }
+
+       ASSERT_ZERO(kdbus_msg_send(conn_dst, NULL, cookie, 0, 0, 0, KDBUS_DST_ID_BROADCAST));
+
+       cookie++;
+       ASSERT_ZERO(kdbus_msg_recv_poll(conn_dst, -1, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       kdbus_msg_free(msg);
+
+       /* destroy connection */
+       kdbus_conn_free(conn_dst);
+       kdbus_conn_free(conn_src);
+
+       ret = waitpid(pid, &status, 0);
+       ASSERT_RETURN_VAL(ret,>=,0, ret);
+
+       if (!WIFEXITED(status))
+               return TEST_ERR;
+
+       return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR;
+}
+
+static int cancel_fd_sync(struct kdbus_conn *conn_src,
+                         struct kdbus_conn *conn_dst)
+{
+       pid_t pid;
+       int cancel_fd;
+       int ret, status;
+       uint64_t counter = 1;
+       struct kdbus_msg *msg = NULL;
+
+       cancel_fd = eventfd(0, 0);
+       ASSERT_RETURN_VAL(cancel_fd,>=,0, cancel_fd);
+
+       cookie++;
+       pid = fork();
+       ASSERT_RETURN_VAL(pid,>=,0, pid);
+
+       if (pid == 0) {
+               ASSERT_EXIT(-ECANCELED,==,kdbus_msg_send_sync(conn_dst, NULL, cookie, KDBUS_MSG_EXPECT_REPLY, 500000000000ULL, 0, conn_src->id, cancel_fd));
+               exit(EXIT_SUCCESS);
+       }
+
+       ASSERT_ZERO(kdbus_msg_recv_poll(conn_src, 100, &msg, NULL));
+       ASSERT_RETURN(msg->cookie,==,cookie);
+
+       kdbus_msg_free(msg);
+
+       ASSERT_RETURN((int)sizeof(counter),==,write(cancel_fd, &counter, sizeof(counter)));
+
+       ret = waitpid(pid, &status, 0);
+       ASSERT_RETURN_VAL(ret,>=,0, ret);
+
+       if (WIFSIGNALED(status))
+               return TEST_ERR;
+
+       return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR;
+}
+
+static int no_cancel_sync(struct kdbus_conn *conn_src,
+                         struct kdbus_conn *conn_dst)
+{
+       pid_t pid;
+       int cancel_fd;
+       int ret, status;
+       struct kdbus_msg *msg = NULL;
+
+       /* pass eventfd, but never signal it so it shouldn't have any effect */
+
+       cancel_fd = eventfd(0, 0);
+       ASSERT_RETURN_VAL(cancel_fd,>=,0, cancel_fd);
+
+       cookie++;
+       pid = fork();
+       ASSERT_RETURN_VAL(pid,>=,0, pid);
+
+       if (pid == 0) {
+               ASSERT_EXIT_ZERO(kdbus_msg_send_sync(conn_dst, NULL, cookie, KDBUS_MSG_EXPECT_REPLY, 500000000000ULL, 0, conn_src->id, cancel_fd));
+               exit(EXIT_SUCCESS);
+       }
+
+       ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL);
+       ASSERT_RETURN_VAL(ret,==,0, -1);
+       ASSERT_RETURN_VAL(msg->cookie,==,cookie, -1);
+
+       kdbus_msg_free(msg);
+
+       ret = kdbus_msg_send_reply(conn_src, cookie, conn_dst->id);
+       ASSERT_RETURN_VAL(ret,>=,0, ret);
+
+       ret = waitpid(pid, &status, 0);
+       ASSERT_RETURN_VAL(ret,>=,0, ret);
+
+       if (WIFSIGNALED(status))
+               return -1;
+
+       return (status == EXIT_SUCCESS) ? 0 : -1;
+}
+
+static void *run_thread_reply(void *data)
+{
+       unsigned long status = TEST_OK;
+       UNUSED(data);
+
+       if (0 > kdbus_msg_recv_poll(conn_a, 3000, NULL, NULL))
+               goto exit_thread;
+
+       kdbus_printf("Thread received message, sending reply ...\n");
+
+       /* using an unknown cookie must fail iff not TIZEN */
+       if (ONTIZEN(0,-EBADSLT) != kdbus_msg_send_reply(conn_a, ~cookie, conn_b->id))
+       {
+               status = TEST_ERR;
+               goto exit_thread;
+       }
+
+       if (kdbus_msg_send_reply(conn_a, cookie, conn_b->id)) {
+               status = TEST_ERR;
+               goto exit_thread;
+       }
+
+exit_thread:
+       pthread_exit(NULL);
+       return (void *) status;
+}
+
+int kdbus_test_sync_reply(struct kdbus_test_env *env)
+{
+       unsigned long status;
+       pthread_t thread;
+       int ret;
+
+       ASSERT_NONZERO(conn_a = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(conn_b = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       pthread_create(&thread, NULL, run_thread_reply, NULL);
+
+       ret = kdbus_msg_send_sync(conn_b, NULL, cookie,
+                                 KDBUS_MSG_EXPECT_REPLY,
+                                 KDBUS_TIMEOUT_INFINITE, 0, conn_a->id, -1);
+
+       pthread_join(thread, (void *) &status);
+       ASSERT_ZERO(status);
+       ASSERT_ZERO(ret);
+
+       ASSERT_ZERO(interrupt_sync(conn_a, conn_b));
+       ASSERT_ZERO(close_epipe_sync(env->buspath));
+       ASSERT_ZERO(cancel_fd_sync(conn_a, conn_b));
+       ASSERT_ZERO(no_cancel_sync(conn_a, conn_b));
+
+       kdbus_printf("-- closing bus connections\n");
+
+       kdbus_conn_free(conn_a);
+       kdbus_conn_free(conn_b);
+
+       return TEST_OK;
+}
+
+#define BYEBYE_ME ((void*)0L)
+#define BYEBYE_THEM ((void*)1L)
+
+static void *run_thread_byebye(void *data)
+{
+       struct kdbus_cmd cmd_byebye = { .size = sizeof(cmd_byebye) };
+
+       if (0 == kdbus_msg_recv_poll(conn_a, 3000, NULL, NULL)) {
+               kdbus_printf("Thread received message, invoking BYEBYE ...\n");
+               if (data == BYEBYE_ME)
+                       ASSERT_EXIT_ZERO(kdbus_cmd_byebye(conn_b->fd, &cmd_byebye));
+               else if (data == BYEBYE_THEM)
+                       ASSERT_EXIT_ZERO(kdbus_cmd_byebye(conn_a->fd, &cmd_byebye));
+       }
+
+       pthread_exit(NULL);
+       return NULL;
+}
+
+int kdbus_test_sync_byebye(struct kdbus_test_env *env)
+{
+       pthread_t thread;
+
+       /*
+        * This sends a synchronous message to a thread, which waits until it
+        * received the message and then invokes BYEBYE on the *ORIGINAL*
+        * connection. That is, on the same connection that synchronously waits
+        * for an reply.
+        * This should properly wake the connection up and cause ECONNRESET as
+        * the connection is disconnected now.
+        *
+        * The second time, we do the same but invoke BYEBYE on the *TARGET*
+        * connection. This should also wake up the synchronous sender as the
+        * reply cannot be sent by a disconnected target.
+        */
+
+       ASSERT_NONZERO(conn_a = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(conn_b = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       pthread_create(&thread, NULL, run_thread_byebye, BYEBYE_ME);
+
+       ASSERT_RETURN(-ECONNRESET,==,kdbus_msg_send_sync(conn_b, NULL, cookie, KDBUS_MSG_EXPECT_REPLY, 5000000000ULL, 0, conn_a->id, -1));
+
+       pthread_join(thread, NULL);
+
+       kdbus_conn_free(conn_a);
+       kdbus_conn_free(conn_b);
+
+       ASSERT_NONZERO(conn_a = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(conn_b = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       pthread_create(&thread, NULL, run_thread_byebye, BYEBYE_THEM);
+
+       ASSERT_RETURN(-EPIPE,==,kdbus_msg_send_sync(conn_b, NULL, cookie, KDBUS_MSG_EXPECT_REPLY, 5000000000ULL, 0, conn_a->id, -1));
+
+       pthread_join(thread, NULL);
+
+       kdbus_conn_free(conn_a);
+       kdbus_conn_free(conn_b);
+
+       return TEST_OK;
+}
diff --git a/tests/kdbus/test-timeout.c b/tests/kdbus/test-timeout.c
new file mode 100644 (file)
index 0000000..5e15dda
--- /dev/null
@@ -0,0 +1,120 @@
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <poll.h>
+#include <stdbool.h>
+
+#include "kdbus-api.h"
+#include "kdbus-test.h"
+#include "kdbus-util.h"
+#include "kdbus-enum.h"
+
+wur int timeout_msg_recv(struct kdbus_conn *conn, uint64_t type, uint64_t *cookie_reply, uint64_t *seqnum, uint64_t *monotonic_ns, uint64_t *realtime_ns)
+{
+       struct kdbus_cmd_recv recv = { .size = sizeof(recv) };
+       struct kdbus_msg *msg;
+       int ret;
+
+       ret = kdbus_cmd_recv(conn->fd, &recv);
+       if (ret < 0) {
+               kdbus_printf("error receiving message: %d (%m)\n", ret);
+               return ret;
+       }
+
+       msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset);
+
+       ASSERT_RETURN_VAL(msg->payload_type,==,(typeof(msg->payload_type))KDBUS_PAYLOAD_KERNEL, -EINVAL);
+       ASSERT_RETURN_VAL(msg->src_id,==,(typeof(msg->src_id))KDBUS_SRC_ID_KERNEL, -EINVAL);
+       ASSERT_RETURN_VAL(msg->dst_id,==,conn->id, -EINVAL);
+
+       {
+               const struct kdbus_item *item;
+               bool have_type = false;
+               bool have_timestamp = false;
+
+               KDBUS_ITEM_FOREACH(item, msg, items) {
+                       if (item->type == type) {
+                               ASSERT_ZERO(have_type);
+                               have_type = true;
+                               ASSERT_RETURN(item->size,==,2*sizeof(uint64_t));
+                               continue;
+                       }
+                       ASSERT_RETURN(item->type,==,(uint64_t)KDBUS_ITEM_TIMESTAMP);
+                       ASSERT_ZERO(have_timestamp);
+                       have_timestamp = true;
+                       ASSERT_RETURN(item->size,==,2*sizeof(uint64_t) + sizeof(struct kdbus_timestamp));
+                       #define S(P) if (P) *P = item->timestamp.P;
+                               S(seqnum)
+                               S(monotonic_ns)
+                               S(realtime_ns)
+                       #undef S
+               }
+       }
+
+       *cookie_reply = msg->cookie_reply;
+       kdbus_printf("Got message timeout for cookie %llu\n",
+                    msg->cookie_reply);
+
+       ret = kdbus_free(conn, recv.msg.offset);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+wur int kdbus_test_timeout(struct kdbus_test_env *env)
+{
+       struct kdbus_conn *conn_a, *conn_b;
+       struct pollfd fd;
+       int ret, i, n_msgs = 4;
+       uint64_t expected = 0;
+       uint64_t cookie = 0xdeadbeef;
+
+       ASSERT_NONZERO(conn_a = kdbus_hello(env->buspath, 0, NULL, 0));
+       ASSERT_NONZERO(conn_b = kdbus_hello(env->buspath, 0, NULL, 0));
+
+       fd.fd = conn_b->fd;
+
+       /*
+        * send messages that expect a reply (within 100 msec),
+        * but never answer it.
+        */
+       for (i = 0; i < n_msgs; i++, cookie++) {
+               kdbus_printf("Sending message with cookie %llu ...\n",
+                            (unsigned long long)cookie);
+               ASSERT_ZERO(kdbus_msg_send(conn_b, NULL, cookie, KDBUS_MSG_EXPECT_REPLY, (i + 1) * 100ULL * 1000000ULL, 0, conn_a->id));
+               expected |= 1ULL << cookie;
+       }
+
+       for (;;) {
+               uint64_t cookie_reply;
+               fd.events = POLLIN | POLLPRI | POLLHUP;
+               fd.revents = 0;
+
+               ret = poll(&fd, 1, (n_msgs + 1) * 100);
+               if (ret == 0)
+                       kdbus_printf("--- timeout\n");
+               if (ret <= 0)
+                       break;
+
+               if (fd.revents & POLLIN)
+                       ASSERT_ZERO(timeout_msg_recv(conn_b, KDBUS_ITEM_REPLY_TIMEOUT, &cookie_reply, NULL, NULL, NULL));
+
+               if (!(expected &= ~(1ULL << cookie_reply)))
+                       break;
+       }
+
+       ASSERT_ZERO(expected);
+
+       kdbus_conn_free(conn_a);
+       kdbus_conn_free(conn_b);
+
+       return TEST_OK;
+}