From cc15cc5604fe6ef42079b1b315919b3428407f3a Mon Sep 17 00:00:00 2001 From: Mateusz Majewski Date: Mon, 6 Dec 2021 11:27:27 +0100 Subject: [PATCH] Import kdbus test MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Change-Id: I02d685dbb73055daebd5bed9640937ac05e33e35 Signed-off-by: Łukasz Stelmach --- samples/.gitignore | 1 + samples/Makefile | 9 + samples/kdbus-api.h | 114 +++ samples/kdbus-workers.c | 1326 ++++++++++++++++++++++++++++ tests/kdbus/.gitignore | 1 + tests/kdbus/Makefile | 45 + tests/kdbus/kdbus-api.h | 114 +++ tests/kdbus/kdbus-enum.c | 93 ++ tests/kdbus/kdbus-enum.h | 15 + tests/kdbus/kdbus-test.c | 1020 ++++++++++++++++++++++ tests/kdbus/kdbus-test.h | 113 +++ tests/kdbus/kdbus-util.c | 1856 ++++++++++++++++++++++++++++++++++++++++ tests/kdbus/kdbus-util.h | 270 ++++++ tests/kdbus/test-activator.c | 291 +++++++ tests/kdbus/test-benchmark.c | 429 ++++++++++ tests/kdbus/test-bus.c | 166 ++++ tests/kdbus/test-chat.c | 98 +++ tests/kdbus/test-connection.c | 527 ++++++++++++ tests/kdbus/test-daemon.c | 62 ++ tests/kdbus/test-endpoint.c | 313 +++++++ tests/kdbus/test-fd.c | 778 +++++++++++++++++ tests/kdbus/test-free.c | 111 +++ tests/kdbus/test-match.c | 580 +++++++++++++ tests/kdbus/test-message.c | 704 +++++++++++++++ tests/kdbus/test-metadata-ns.c | 519 +++++++++++ tests/kdbus/test-monitor.c | 154 ++++ tests/kdbus/test-names.c | 236 +++++ tests/kdbus/test-policy-ns.c | 590 +++++++++++++ tests/kdbus/test-policy-priv.c | 1057 +++++++++++++++++++++++ tests/kdbus/test-policy.c | 60 ++ tests/kdbus/test-sync.c | 325 +++++++ tests/kdbus/test-timeout.c | 120 +++ 32 files changed, 12097 insertions(+) create mode 100644 samples/.gitignore create mode 100644 samples/Makefile create mode 100644 samples/kdbus-api.h create mode 100644 samples/kdbus-workers.c create mode 100644 tests/kdbus/.gitignore create mode 100644 tests/kdbus/Makefile create mode 100644 tests/kdbus/kdbus-api.h create mode 100644 tests/kdbus/kdbus-enum.c create mode 100644 tests/kdbus/kdbus-enum.h create mode 100644 tests/kdbus/kdbus-test.c create mode 100644 tests/kdbus/kdbus-test.h create mode 100644 tests/kdbus/kdbus-util.c create mode 100644 tests/kdbus/kdbus-util.h create mode 100644 tests/kdbus/test-activator.c create mode 100644 tests/kdbus/test-benchmark.c create mode 100644 tests/kdbus/test-bus.c create mode 100644 tests/kdbus/test-chat.c create mode 100644 tests/kdbus/test-connection.c create mode 100644 tests/kdbus/test-daemon.c create mode 100644 tests/kdbus/test-endpoint.c create mode 100644 tests/kdbus/test-fd.c create mode 100644 tests/kdbus/test-free.c create mode 100644 tests/kdbus/test-match.c create mode 100644 tests/kdbus/test-message.c create mode 100644 tests/kdbus/test-metadata-ns.c create mode 100644 tests/kdbus/test-monitor.c create mode 100644 tests/kdbus/test-names.c create mode 100644 tests/kdbus/test-policy-ns.c create mode 100644 tests/kdbus/test-policy-priv.c create mode 100644 tests/kdbus/test-policy.c create mode 100644 tests/kdbus/test-sync.c create mode 100644 tests/kdbus/test-timeout.c diff --git a/samples/.gitignore b/samples/.gitignore new file mode 100644 index 0000000..ee07d98 --- /dev/null +++ b/samples/.gitignore @@ -0,0 +1 @@ +kdbus-workers diff --git a/samples/Makefile b/samples/Makefile new file mode 100644 index 0000000..137f842 --- /dev/null +++ b/samples/Makefile @@ -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 index 0000000..5ed5907 --- /dev/null +++ b/samples/kdbus-api.h @@ -0,0 +1,114 @@ +#ifndef KDBUS_API_H +#define KDBUS_API_H + +#include +#include + +#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 index 0000000..d331e01 --- /dev/null +++ b/samples/kdbus-workers.c @@ -0,0 +1,1326 @@ +/* + * Copyright (C) 2013-2015 David Herrmann + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 index 0000000..d3ef42f --- /dev/null +++ b/tests/kdbus/.gitignore @@ -0,0 +1 @@ +kdbus-test diff --git a/tests/kdbus/Makefile b/tests/kdbus/Makefile new file mode 100644 index 0000000..f60619c --- /dev/null +++ b/tests/kdbus/Makefile @@ -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 index 0000000..4189a6a --- /dev/null +++ b/tests/kdbus/kdbus-api.h @@ -0,0 +1,114 @@ +#ifndef KDBUS_API_H +#define KDBUS_API_H + +#include +#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 index 0000000..4b642c6 --- /dev/null +++ b/tests/kdbus/kdbus-enum.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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 index 0000000..ed28cca --- /dev/null +++ b/tests/kdbus/kdbus-enum.h @@ -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 index 0000000..f8f2fcc --- /dev/null +++ b/tests/kdbus/kdbus-test.c @@ -0,0 +1,1020 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 Toplevel of the kdbus hierarchy\n" + "\t-t, --test Run one specific test only, in verbose mode\n" + "\t-b, --bus Instead of generating a random bus name, take .\n" + "\t-w, --wait Wait 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 index 0000000..e0a6abc --- /dev/null +++ b/tests/kdbus/kdbus-test.h @@ -0,0 +1,113 @@ +#ifndef _TEST_KDBUS_H_ +#define _TEST_KDBUS_H_ + +#include +#include +#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 index 0000000..49aa4b7 --- /dev/null +++ b/tests/kdbus/kdbus-util.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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<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; itype) { + 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: + * --