diff options
Diffstat (limited to 'src/libsystemd-bus')
30 files changed, 12634 insertions, 0 deletions
diff --git a/src/libsystemd-bus/Makefile b/src/libsystemd-bus/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/libsystemd-bus/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/libsystemd-bus/bus-bloom.c b/src/libsystemd-bus/bus-bloom.c new file mode 100644 index 0000000000..cb65e47b4c --- /dev/null +++ b/src/libsystemd-bus/bus-bloom.c @@ -0,0 +1,93 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" +#include "MurmurHash3.h" + +#include "bus-bloom.h" + +static inline void set_bit(uint64_t filter[], unsigned b) { + filter[b >> 6] |= 1ULL << (b & 63); +} + +void bloom_add_data(uint64_t filter[BLOOM_SIZE/8], const void *data, size_t n) { + uint16_t hash[8]; + unsigned k = 0; + + /* + * Our bloom filter has the following parameters: + * + * m=512 (bits in the filter) + * k=8 (hash functions) + * + * We calculate a single 128bit MurmurHash value of which we + * use 8 parts of 9 bits as individual hash functions. + * + */ + + MurmurHash3_x64_128(data, n, 0, hash); + + assert_cc(BLOOM_SIZE*8 == 512); + + for (k = 0; k < ELEMENTSOF(hash); k++) + set_bit(filter, hash[k] & 511); +} + +void bloom_add_pair(uint64_t filter[BLOOM_SIZE/8], const char *a, const char *b) { + size_t n; + char *c; + + assert(filter); + assert(a); + assert(b); + + n = strlen(a) + 1 + strlen(b); + c = alloca(n + 1); + strcpy(stpcpy(stpcpy(c, a), ":"), b); + + bloom_add_data(filter, c, n); +} + +void bloom_add_prefixes(uint64_t filter[BLOOM_SIZE/8], const char *a, const char *b, char sep) { + size_t n; + char *c, *p; + + assert(filter); + assert(a); + assert(b); + + n = strlen(a) + 1 + strlen(b); + c = alloca(n + 1); + + p = stpcpy(stpcpy(c, a), ":"); + strcpy(p, b); + + for (;;) { + char *e; + + e = strrchr(p, sep); + if (!e || e == p) + break; + + *e = 0; + bloom_add_data(filter, c, e - c); + } +} diff --git a/src/libsystemd-bus/bus-bloom.h b/src/libsystemd-bus/bus-bloom.h new file mode 100644 index 0000000000..1bf14a3331 --- /dev/null +++ b/src/libsystemd-bus/bus-bloom.h @@ -0,0 +1,30 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#define BLOOM_SIZE 64 + +void bloom_add_data(uint64_t filter[BLOOM_SIZE/8], const void *data, size_t n); +void bloom_add_pair(uint64_t filter[BLOOM_SIZE/8], const char *a, const char *b); +void bloom_add_prefixes(uint64_t filter[BLOOM_SIZE/8], const char *a, const char *b, char sep); diff --git a/src/libsystemd-bus/bus-control.c b/src/libsystemd-bus/bus-control.c new file mode 100644 index 0000000000..a4dc9bf511 --- /dev/null +++ b/src/libsystemd-bus/bus-control.c @@ -0,0 +1,378 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include <valgrind/memcheck.h> +#endif + +#include <stddef.h> +#include <errno.h> + +#include "strv.h" + +#include "sd-bus.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-control.h" + +int sd_bus_get_unique_name(sd_bus *bus, const char **unique) { + int r; + + if (!bus) + return -EINVAL; + if (!unique) + return -EINVAL; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + *unique = bus->unique_name; + return 0; +} + +int sd_bus_request_name(sd_bus *bus, const char *name, int flags) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + uint32_t ret; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + if (!bus->bus_client) + return -EINVAL; + + if (bus->is_kernel) { + struct kdbus_cmd_name *n; + size_t l; + + l = strlen(name); + n = alloca0(offsetof(struct kdbus_cmd_name, name) + l + 1); + n->size = offsetof(struct kdbus_cmd_name, name) + l + 1; + n->name_flags = flags; + memcpy(n->name, name, l+1); + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(n, n->size); +#endif + + r = ioctl(bus->input_fd, KDBUS_CMD_NAME_ACQUIRE, n); + if (r < 0) + return -errno; + + return n->name_flags; + } else { + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "RequestName", + NULL, + &reply, + "su", + name, + flags); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &ret); + if (r < 0) + return r; + + return ret; + } +} + +int sd_bus_release_name(sd_bus *bus, const char *name) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + uint32_t ret; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + if (!bus->bus_client) + return -EINVAL; + + if (bus->is_kernel) { + struct kdbus_cmd_name *n; + size_t l; + + l = strlen(name); + n = alloca0(offsetof(struct kdbus_cmd_name, name) + l + 1); + n->size = offsetof(struct kdbus_cmd_name, name) + l + 1; + memcpy(n->name, name, l+1); + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(n, n->size); +#endif + r = ioctl(bus->input_fd, KDBUS_CMD_NAME_RELEASE, n); + if (r < 0) + return -errno; + + return n->name_flags; + } else { + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "ReleaseName", + NULL, + &reply, + "s", + name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &ret); + if (r < 0) + return r; + } + + return ret; +} + +int sd_bus_list_names(sd_bus *bus, char ***l) { + _cleanup_bus_message_unref_ sd_bus_message *reply1 = NULL, *reply2 = NULL; + char **x = NULL; + int r; + + if (!bus) + return -EINVAL; + if (!l) + return -EINVAL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "ListNames", + NULL, + &reply1, + NULL); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "ListActivatableNames", + NULL, + &reply2, + NULL); + if (r < 0) + return r; + + r = bus_message_read_strv_extend(reply1, &x); + if (r < 0) { + strv_free(x); + return r; + } + + r = bus_message_read_strv_extend(reply2, &x); + if (r < 0) { + strv_free(x); + return r; + } + + *l = strv_uniq(x); + return 0; +} + +int sd_bus_get_owner(sd_bus *bus, const char *name, char **owner) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + const char *found; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "GetNameOwner", + NULL, + &reply, + "s", + name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "s", &found); + if (r < 0) + return r; + + if (owner) { + char *t; + + t = strdup(found); + if (!t) + return -ENOMEM; + + *owner = t; + } + + return 0; +} + +int sd_bus_get_owner_uid(sd_bus *bus, const char *name, uid_t *uid) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + uint32_t u; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + if (!uid) + return -EINVAL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "GetConnectionUnixUser", + NULL, + &reply, + "s", + name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &u); + if (r < 0) + return r; + + *uid = (uid_t) u; + return 0; +} + +int sd_bus_get_owner_pid(sd_bus *bus, const char *name, pid_t *pid) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + uint32_t u; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + if (!pid) + return -EINVAL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID", + NULL, + &reply, + "s", + name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &u); + if (r < 0) + return r; + + if (u == 0) + return -EIO; + + *pid = (uid_t) u; + return 0; +} + +int bus_add_match_internal(sd_bus *bus, const char *match) { + assert(bus); + assert(match); + + return sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "AddMatch", + NULL, + NULL, + "s", + match); +} + +int bus_remove_match_internal(sd_bus *bus, const char *match) { + assert(bus); + assert(match); + + return sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "RemoveMatch", + NULL, + NULL, + "s", + match); +} + +int sd_bus_get_owner_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + const char *mid; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + + if (streq_ptr(name, bus->unique_name)) + return sd_id128_get_machine(machine); + + r = sd_bus_call_method(bus, + name, + "/", + "org.freedesktop.DBus.Peer", + "GetMachineId", + NULL, + &reply, + NULL); + + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "s", &mid); + if (r < 0) + return r; + + return sd_id128_from_string(mid, machine); +} diff --git a/src/libsystemd-bus/bus-control.h b/src/libsystemd-bus/bus-control.h new file mode 100644 index 0000000000..34ecb260c3 --- /dev/null +++ b/src/libsystemd-bus/bus-control.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-bus.h" + +int bus_add_match_internal(sd_bus *bus, const char *match); +int bus_remove_match_internal(sd_bus *bus, const char *match); diff --git a/src/libsystemd-bus/bus-error.c b/src/libsystemd-bus/bus-error.c new file mode 100644 index 0000000000..5faa17384e --- /dev/null +++ b/src/libsystemd-bus/bus-error.c @@ -0,0 +1,177 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> + +#include "util.h" + +#include "sd-bus.h" +#include "bus-error.h" + +bool bus_error_is_dirty(sd_bus_error *e) { + if (!e) + return 0; + + return e->name || e->message || e->need_free; +} + +void sd_bus_error_free(sd_bus_error *e) { + if (!e) + return; + + if (e->need_free) { + free((void*) e->name); + free((void*) e->message); + } + + e->name = e->message = NULL; + e->need_free = false; +} + +int sd_bus_error_set(sd_bus_error *e, const char *name, const char *format, ...) { + char *n, *m = NULL; + va_list ap; + int r; + + if (!e) + return 0; + if (bus_error_is_dirty(e)) + return -EINVAL; + if (!name) + return -EINVAL; + + n = strdup(name); + if (!n) + return -ENOMEM; + + if (format) { + va_start(ap, format); + r = vasprintf(&m, format, ap); + va_end(ap); + + if (r < 0) { + free(n); + return -ENOMEM; + } + } + + e->name = n; + e->message = m; + e->need_free = true; + + return 0; +} + +int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e) { + char *x, *y = NULL; + + if (!dest) + return 0; + if (bus_error_is_dirty(dest)) + return -EINVAL; + if (!sd_bus_error_is_set(e)) + return 0; + + x = strdup(e->name); + if (!x) + return -ENOMEM; + + if (e->message) { + y = strdup(e->message); + if (!y) { + free(x); + return -ENOMEM; + } + } + + dest->name = x; + dest->message = y; + dest->need_free = true; + return 0; +} + +void sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message) { + if (!e) + return; + if (bus_error_is_dirty(e)) + return; + + e->name = name; + e->message = message; + e->need_free = false; +} + +int sd_bus_error_is_set(const sd_bus_error *e) { + if (!e) + return 0; + + return !!e->name; +} + +int sd_bus_error_has_name(const sd_bus_error *e, const char *name) { + if (!e) + return 0; + + return streq_ptr(e->name, name); +} + +int bus_error_to_errno(const sd_bus_error* e) { + + /* Better replce this with a gperf table */ + + if (!e->name) + return -EIO; + + if (streq(e->name, "org.freedesktop.DBus.Error.NoMemory")) + return -ENOMEM; + + if (streq(e->name, "org.freedesktop.DBus.Error.AuthFailed") || + streq(e->name, "org.freedesktop.DBus.Error.AccessDenied")) + return -EPERM; + + return -EIO; +} + +int bus_error_from_errno(sd_bus_error *e, int error) { + if (!e) + return error; + + if (error == -ENOMEM) + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.NoMemory", strerror(-error)); + else if (error == -EPERM || error == -EACCES) + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.AccessDenied", strerror(-error)); + else + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.Failed", "Operation failed"); + + return error; +} + +const char *bus_error_message(const sd_bus_error *e, int error) { + if (e && e->message) + return e->message; + + return strerror(error); +} diff --git a/src/libsystemd-bus/bus-error.h b/src/libsystemd-bus/bus-error.h new file mode 100644 index 0000000000..ceca7d678f --- /dev/null +++ b/src/libsystemd-bus/bus-error.h @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-bus.h" + +int bus_error_to_errno(const sd_bus_error *e); +int bus_error_from_errno(sd_bus_error *e, int error); + +bool bus_error_is_dirty(sd_bus_error *e); + +const char *bus_error_message(const sd_bus_error *e, int error); diff --git a/src/libsystemd-bus/bus-internal.c b/src/libsystemd-bus/bus-internal.c new file mode 100644 index 0000000000..0e66f3d355 --- /dev/null +++ b/src/libsystemd-bus/bus-internal.c @@ -0,0 +1,256 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "bus-internal.h" + +bool object_path_is_valid(const char *p) { + const char *q; + bool slash; + + if (!p) + return false; + + if (p[0] != '/') + return false; + + if (p[1] == 0) + return true; + + for (slash = true, q = p+1; *q; q++) + if (*q == '/') { + if (slash) + return false; + + slash = true; + } else { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + (*q >= '0' && *q <= '9') || + *q == '_'; + + if (!good) + return false; + + slash = false; + } + + if (slash) + return false; + + return true; +} + +bool interface_name_is_valid(const char *p) { + const char *q; + bool dot, found_dot; + + if (isempty(p)) + return false; + + for (dot = true, q = p; *q; q++) + if (*q == '.') { + if (dot) + return false; + + found_dot = dot = true; + } else { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + (!dot && *q >= '0' && *q <= '9') || + *q == '_'; + + if (!good) + return false; + + dot = false; + } + + if (q - p > 255) + return false; + + if (dot) + return false; + + if (!found_dot) + return false; + + return true; +} + +bool service_name_is_valid(const char *p) { + const char *q; + bool dot, found_dot, unique; + + if (isempty(p)) + return false; + + unique = p[0] == ':'; + + for (dot = true, q = unique ? p+1 : p; *q; q++) + if (*q == '.') { + if (dot) + return false; + + found_dot = dot = true; + } else { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + ((!dot || unique) && *q >= '0' && *q <= '9') || + *q == '_' || *q == '-'; + + if (!good) + return false; + + dot = false; + } + + if (q - p > 255) + return false; + + if (dot) + return false; + + if (!found_dot) + return false; + + return true; +} + +bool member_name_is_valid(const char *p) { + const char *q; + + if (isempty(p)) + return false; + + for (q = p; *q; q++) { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + (*q >= '0' && *q <= '9') || + *q == '_'; + + if (!good) + return false; + } + + if (q - p > 255) + return false; + + return true; +} + +static bool complex_pattern_check(char c, const char *a, const char *b) { + bool separator = false; + + if (!a && !b) + return true; + + if (!a || !b) + return false; + + for (;;) { + if (*a != *b) + return (separator && (*a == 0 || *b == 0)) || + (*a == 0 && *b == c && b[1] == 0) || + (*b == 0 && *a == c && a[1] == 0); + + if (*a == 0) + return true; + + separator = *a == c; + + a++, b++; + } +} + +bool namespace_complex_pattern(const char *pattern, const char *value) { + return complex_pattern_check('.', pattern, value); +} + +bool path_complex_pattern(const char *pattern, const char *value) { + return complex_pattern_check('/', pattern, value); +} + +static bool simple_pattern_check(char c, const char *a, const char *b) { + + if (!a && !b) + return true; + + if (!a || !b) + return false; + + for (;;) { + if (*a != *b) + return *a == 0 && *b == c; + + if (*a == 0) + return true; + + a++, b++; + } +} + +bool namespace_simple_pattern(const char *pattern, const char *value) { + return simple_pattern_check('.', pattern, value); +} + +bool path_simple_pattern(const char *pattern, const char *value) { + return simple_pattern_check('/', pattern, value); +} + +int bus_message_type_from_string(const char *s, uint8_t *u) { + if (streq(s, "signal")) + *u = SD_BUS_MESSAGE_TYPE_SIGNAL; + else if (streq(s, "method_call")) + *u = SD_BUS_MESSAGE_TYPE_METHOD_CALL; + else if (streq(s, "error")) + *u = SD_BUS_MESSAGE_TYPE_METHOD_ERROR; + else if (streq(s, "method_return")) + *u = SD_BUS_MESSAGE_TYPE_METHOD_RETURN; + else + return -EINVAL; + + return 0; +} + +const char *bus_message_type_to_string(uint8_t u) { + if (u == SD_BUS_MESSAGE_TYPE_SIGNAL) + return "signal"; + else if (u == SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return "method_call"; + else if (u == SD_BUS_MESSAGE_TYPE_METHOD_ERROR) + return "error"; + else if (u == SD_BUS_MESSAGE_TYPE_METHOD_RETURN) + return "method_return"; + else + return NULL; +} diff --git a/src/libsystemd-bus/bus-internal.h b/src/libsystemd-bus/bus-internal.h new file mode 100644 index 0000000000..4babfac86d --- /dev/null +++ b/src/libsystemd-bus/bus-internal.h @@ -0,0 +1,198 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> + +#include "hashmap.h" +#include "prioq.h" +#include "list.h" +#include "util.h" + +#include "sd-bus.h" +#include "bus-error.h" +#include "bus-match.h" + +struct reply_callback { + sd_bus_message_handler_t callback; + void *userdata; + usec_t timeout; + uint64_t serial; + unsigned prioq_idx; +}; + +struct filter_callback { + sd_bus_message_handler_t callback; + void *userdata; + + unsigned last_iteration; + + LIST_FIELDS(struct filter_callback, callbacks); +}; + +struct object_callback { + sd_bus_message_handler_t callback; + void *userdata; + + char *path; + bool is_fallback; + + unsigned last_iteration; +}; + +enum bus_state { + BUS_UNSET, + BUS_OPENING, + BUS_AUTHENTICATING, + BUS_HELLO, + BUS_RUNNING +}; + +enum bus_auth { + _BUS_AUTH_INVALID, + BUS_AUTH_EXTERNAL, + BUS_AUTH_ANONYMOUS +}; + +struct sd_bus { + unsigned n_ref; + enum bus_state state; + int input_fd, output_fd; + int message_version; + + bool is_kernel:1; + bool negotiate_fds:1; + bool can_fds:1; + bool bus_client:1; + bool ucred_valid:1; + bool is_server:1; + bool anonymous_auth:1; + bool prefer_readv:1; + bool prefer_writev:1; + bool processing:1; + bool match_callbacks_modified:1; + bool filter_callbacks_modified:1; + bool object_callbacks_modified:1; + + void *rbuffer; + size_t rbuffer_size; + + sd_bus_message **rqueue; + unsigned rqueue_size; + + sd_bus_message **wqueue; + unsigned wqueue_size; + size_t windex; + + uint64_t serial; + + char *unique_name; + + struct bus_match_node match_callbacks; + Prioq *reply_callbacks_prioq; + Hashmap *reply_callbacks; + LIST_HEAD(struct filter_callback, filter_callbacks); + Hashmap *object_callbacks; + + union { + struct sockaddr sa; + struct sockaddr_un un; + struct sockaddr_in in; + struct sockaddr_in6 in6; + } sockaddr; + socklen_t sockaddr_size; + + char *kernel; + + sd_id128_t server_id; + + char *address; + unsigned address_index; + + int last_connect_error; + + enum bus_auth auth; + size_t auth_rbegin; + struct iovec auth_iovec[3]; + unsigned auth_index; + char *auth_buffer; + usec_t auth_timeout; + + struct ucred ucred; + char label[NAME_MAX]; + + int *fds; + unsigned n_fds; + + char *exec_path; + char **exec_argv; + + uint64_t hello_serial; + unsigned iteration_counter; +}; + +static inline void bus_unrefp(sd_bus **b) { + sd_bus_unref(*b); +} + +#define _cleanup_bus_unref_ __attribute__((cleanup(bus_unrefp))) +#define _cleanup_bus_error_free_ __attribute__((cleanup(sd_bus_error_free))) + +#define BUS_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) + +#define BUS_WQUEUE_MAX 128 +#define BUS_RQUEUE_MAX 128 + +#define BUS_MESSAGE_SIZE_MAX (64*1024*1024) +#define BUS_AUTH_SIZE_MAX (64*1024) + +#define BUS_CONTAINER_DEPTH 128 + +/* Defined by the specification as maximum size of an array in + * bytes */ +#define BUS_ARRAY_MAX_SIZE 67108864 + +#define BUS_FDS_MAX 1024 + +#define BUS_EXEC_ARGV_MAX 256 + +bool object_path_is_valid(const char *p); +bool interface_name_is_valid(const char *p); +bool service_name_is_valid(const char *p); +bool member_name_is_valid(const char *p); + +bool namespace_complex_pattern(const char *pattern, const char *value); +bool path_complex_pattern(const char *pattern, const char *value); + +bool namespace_simple_pattern(const char *pattern, const char *value); +bool path_simple_pattern(const char *pattern, const char *value); + +int bus_message_type_from_string(const char *s, uint8_t *u); +const char *bus_message_type_to_string(uint8_t u); + +#define error_name_is_valid interface_name_is_valid + +int bus_ensure_running(sd_bus *bus); +int bus_start_running(sd_bus *bus); +int bus_next_address(sd_bus *bus); diff --git a/src/libsystemd-bus/bus-kernel.c b/src/libsystemd-bus/bus-kernel.c new file mode 100644 index 0000000000..0762b7836f --- /dev/null +++ b/src/libsystemd-bus/bus-kernel.c @@ -0,0 +1,618 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include <valgrind/memcheck.h> +#endif + +#include <fcntl.h> +#include <malloc.h> + +#include "util.h" + +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-kernel.h" +#include "bus-bloom.h" + +#define KDBUS_ITEM_NEXT(item) \ + (typeof(item))(((uint8_t *)item) + ALIGN8((item)->size)) + +#define KDBUS_ITEM_FOREACH(item, head) \ + for (item = (head)->items; \ + (uint8_t *)(item) < (uint8_t *)(head) + (head)->size; \ + item = KDBUS_ITEM_NEXT(item)) + +#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) +#define KDBUS_ITEM_SIZE(s) ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE) + +static int parse_unique_name(const char *s, uint64_t *id) { + int r; + + assert(s); + assert(id); + + if (!startswith(s, ":1.")) + return 0; + + r = safe_atou64(s + 3, id); + if (r < 0) + return r; + + return 1; +} + +static void append_payload_vec(struct kdbus_item **d, const void *p, size_t sz) { + assert(d); + assert(p); + assert(sz > 0); + + *d = ALIGN8_PTR(*d); + + (*d)->size = offsetof(struct kdbus_item, vec) + sizeof(struct kdbus_vec); + (*d)->type = KDBUS_MSG_PAYLOAD_VEC; + (*d)->vec.address = (intptr_t) p; + (*d)->vec.size = sz; + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static void append_destination(struct kdbus_item **d, const char *s, size_t length) { + assert(d); + assert(s); + + *d = ALIGN8_PTR(*d); + + (*d)->size = offsetof(struct kdbus_item, str) + length + 1; + (*d)->type = KDBUS_MSG_DST_NAME; + memcpy((*d)->str, s, length + 1); + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static void* append_bloom(struct kdbus_item **d, size_t length) { + void *r; + + assert(d); + + *d = ALIGN8_PTR(*d); + + (*d)->size = offsetof(struct kdbus_item, data) + length; + (*d)->type = KDBUS_MSG_BLOOM; + r = (*d)->data; + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); + + return r; +} + +static void append_fds(struct kdbus_item **d, const int fds[], unsigned n_fds) { + assert(d); + assert(fds); + assert(n_fds > 0); + + *d = ALIGN8_PTR(*d); + (*d)->size = offsetof(struct kdbus_item, fds) + sizeof(int) * n_fds; + (*d)->type = KDBUS_MSG_UNIX_FDS; + memcpy((*d)->fds, fds, sizeof(int) * n_fds); + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static int bus_message_setup_bloom(sd_bus_message *m, void *bloom) { + unsigned i; + int r; + + assert(m); + assert(bloom); + + memset(bloom, 0, BLOOM_SIZE); + + bloom_add_pair(bloom, "message-type", bus_message_type_to_string(m->header->type)); + + if (m->interface) + bloom_add_pair(bloom, "interface", m->interface); + if (m->member) + bloom_add_pair(bloom, "member", m->member); + if (m->path) { + bloom_add_pair(bloom, "path", m->path); + bloom_add_prefixes(bloom, "path-slash-prefix", m->path, '/'); + } + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + for (i = 0; i < 64; i++) { + char type; + const char *t; + char buf[sizeof("arg")-1 + 2 + sizeof("-slash-prefix")]; + char *e; + + r = sd_bus_message_peek_type(m, &type, NULL); + if (r < 0) + return r; + + if (type != SD_BUS_TYPE_STRING && + type != SD_BUS_TYPE_OBJECT_PATH && + type != SD_BUS_TYPE_SIGNATURE) + break; + + r = sd_bus_message_read_basic(m, type, &t); + if (r < 0) + return r; + + e = stpcpy(buf, "arg"); + if (i < 10) + *(e++) = '0' + i; + else { + *(e++) = '0' + (i / 10); + *(e++) = '0' + (i % 10); + } + + *e = 0; + bloom_add_pair(bloom, buf, t); + + strcpy(e, "-dot-prefix"); + bloom_add_prefixes(bloom, buf, t, '.'); + strcpy(e, "-slash-prefix"); + bloom_add_prefixes(bloom, buf, t, '/'); + } + + return 0; +} + +static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { + struct kdbus_item *d; + bool well_known; + uint64_t unique; + size_t sz, dl; + int r; + + assert(b); + assert(m); + assert(m->sealed); + + if (m->kdbus) + return 0; + + if (m->destination) { + r = parse_unique_name(m->destination, &unique); + if (r < 0) + return r; + + well_known = r == 0; + } else + well_known = false; + + sz = offsetof(struct kdbus_msg, items); + + /* Add in fixed header, fields header and payload */ + sz += 3 * ALIGN8(offsetof(struct kdbus_item, vec) + sizeof(struct kdbus_vec)); + + /* Add space for bloom filter */ + sz += ALIGN8(offsetof(struct kdbus_item, data) + BLOOM_SIZE); + + /* Add in well-known destination header */ + if (well_known) { + dl = strlen(m->destination); + sz += ALIGN8(offsetof(struct kdbus_item, str) + dl + 1); + } + + /* Add space for unix fds */ + if (m->n_fds > 0) + sz += ALIGN8(offsetof(struct kdbus_item, fds) + sizeof(int)*m->n_fds); + + m->kdbus = memalign(8, sz); + if (!m->kdbus) + return -ENOMEM; + + memset(m->kdbus, 0, sz); + + m->kdbus->flags = + ((m->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) ? 0 : KDBUS_MSG_FLAGS_EXPECT_REPLY) | + ((m->header->flags & SD_BUS_MESSAGE_NO_AUTO_START) ? KDBUS_MSG_FLAGS_NO_AUTO_START : 0); + m->kdbus->dst_id = + well_known ? 0 : + m->destination ? unique : KDBUS_DST_ID_BROADCAST; + m->kdbus->payload_type = KDBUS_PAYLOAD_DBUS1; + m->kdbus->cookie = m->header->serial; + + m->kdbus->timeout_ns = m->timeout * NSEC_PER_USEC; + + d = m->kdbus->items; + + if (well_known) + append_destination(&d, m->destination, dl); + + append_payload_vec(&d, m->header, sizeof(*m->header)); + + if (m->fields) + append_payload_vec(&d, m->fields, ALIGN8(m->header->fields_size)); + + if (m->body) + append_payload_vec(&d, m->body, m->header->body_size); + + if (m->kdbus->dst_id == KDBUS_DST_ID_BROADCAST) { + void *p; + + p = append_bloom(&d, BLOOM_SIZE); + r = bus_message_setup_bloom(m, p); + if (r < 0) { + free(m->kdbus); + m->kdbus = NULL; + return -r; + } + } + + if (m->n_fds > 0) + append_fds(&d, m->fds, m->n_fds); + + m->kdbus->size = (uint8_t*) d - (uint8_t*) m->kdbus; + assert(m->kdbus->size <= sz); + + m->free_kdbus = true; + + return 0; +} + +int bus_kernel_take_fd(sd_bus *b) { + struct kdbus_cmd_hello hello = { + .conn_flags = + KDBUS_HELLO_ACCEPT_FD| + KDBUS_HELLO_ATTACH_COMM| + KDBUS_HELLO_ATTACH_EXE| + KDBUS_HELLO_ATTACH_CMDLINE| + KDBUS_HELLO_ATTACH_CGROUP| + KDBUS_HELLO_ATTACH_CAPS| + KDBUS_HELLO_ATTACH_SECLABEL| + KDBUS_HELLO_ATTACH_AUDIT + }; + int r; + + assert(b); + + if (b->is_server) + return -EINVAL; + + r = ioctl(b->input_fd, KDBUS_CMD_HELLO, &hello); + if (r < 0) + return -errno; + + /* The higher 32bit of both flags fields are considered + * 'incompatible flags'. Refuse them all for now. */ + if (hello.bus_flags > 0xFFFFFFFFULL || + hello.conn_flags > 0xFFFFFFFFULL) + return -ENOTSUP; + + if (hello.bloom_size != BLOOM_SIZE) + return -ENOTSUP; + + if (asprintf(&b->unique_name, ":1.%llu", (unsigned long long) hello.id) < 0) + return -ENOMEM; + + b->is_kernel = true; + b->bus_client = true; + b->can_fds = true; + + r = bus_start_running(b); + if (r < 0) + return r; + + return 1; +} + +int bus_kernel_connect(sd_bus *b) { + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->kernel); + + if (b->is_server) + return -EINVAL; + + b->input_fd = open(b->kernel, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (b->input_fd < 0) + return -errno; + + b->output_fd = b->input_fd; + + return bus_kernel_take_fd(b); +} + +int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m) { + int r; + + assert(bus); + assert(m); + assert(bus->state == BUS_RUNNING); + + r = bus_message_setup_kmsg(bus, m); + if (r < 0) + return r; + + r = ioctl(bus->output_fd, KDBUS_CMD_MSG_SEND, m->kdbus); + if (r < 0) + return errno == EAGAIN ? 0 : -errno; + + return 1; +} + +static void close_kdbus_msg(struct kdbus_msg *k) { + struct kdbus_item *d; + + KDBUS_ITEM_FOREACH(d, k) { + + if (d->type != KDBUS_MSG_UNIX_FDS) + continue; + + close_many(d->fds, (d->size - offsetof(struct kdbus_item, fds)) / sizeof(int)); + } +} + +static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k, sd_bus_message **ret) { + sd_bus_message *m = NULL; + struct kdbus_item *d; + unsigned n_payload = 0, n_fds = 0; + _cleanup_free_ int *fds = NULL; + struct bus_header *h = NULL; + size_t total, n_bytes = 0, idx = 0; + const char *destination = NULL, *seclabel = NULL; + int r; + + assert(bus); + assert(k); + assert(ret); + + if (k->payload_type != KDBUS_PAYLOAD_DBUS1) + return 0; + + KDBUS_ITEM_FOREACH(d, k) { + size_t l; + + l = d->size - offsetof(struct kdbus_item, data); + + if (d->type == KDBUS_MSG_PAYLOAD) { + + if (!h) { + if (l < sizeof(struct bus_header)) + return -EBADMSG; + + h = (struct bus_header*) d->data; + } + + n_payload++; + n_bytes += l; + + } else if (d->type == KDBUS_MSG_UNIX_FDS) { + int *f; + unsigned j; + + j = l / sizeof(int); + f = realloc(fds, sizeof(int) * (n_fds + j)); + if (!f) + return -ENOMEM; + + fds = f; + memcpy(fds + n_fds, d->fds, sizeof(int) * j); + n_fds += j; + + } else if (d->type == KDBUS_MSG_DST_NAME) + destination = d->str; + else if (d->type == KDBUS_MSG_SRC_SECLABEL) + seclabel = d->str; + } + + if (!h) + return -EBADMSG; + + r = bus_header_size(h, &total); + if (r < 0) + return r; + + if (n_bytes != total) + return -EBADMSG; + + r = bus_message_from_header(h, sizeof(struct bus_header), fds, n_fds, NULL, seclabel, 0, &m); + if (r < 0) + return r; + + KDBUS_ITEM_FOREACH(d, k) { + size_t l; + + l = d->size - offsetof(struct kdbus_item, data); + + if (d->type == KDBUS_MSG_PAYLOAD) { + + if (idx == sizeof(struct bus_header) && + l == ALIGN8(BUS_MESSAGE_FIELDS_SIZE(m))) + m->fields = d->data; + else if (idx == sizeof(struct bus_header) + ALIGN8(BUS_MESSAGE_FIELDS_SIZE(m)) && + l == BUS_MESSAGE_BODY_SIZE(m)) + m->body = d->data; + else if (!(idx == 0 && l == sizeof(struct bus_header))) { + sd_bus_message_unref(m); + return -EBADMSG; + } + + idx += l; + } else if (d->type == KDBUS_MSG_SRC_CREDS) { + m->pid_starttime = d->creds.starttime / NSEC_PER_USEC; + m->uid = d->creds.uid; + m->gid = d->creds.gid; + m->pid = d->creds.pid; + m->tid = d->creds.tid; + m->uid_valid = m->gid_valid = true; + } else if (d->type == KDBUS_MSG_TIMESTAMP) { + m->realtime = d->timestamp.realtime_ns / NSEC_PER_USEC; + m->monotonic = d->timestamp.monotonic_ns / NSEC_PER_USEC; + } else if (d->type == KDBUS_MSG_SRC_PID_COMM) + m->comm = d->str; + else if (d->type == KDBUS_MSG_SRC_TID_COMM) + m->tid_comm = d->str; + else if (d->type == KDBUS_MSG_SRC_EXE) + m->exe = d->str; + else if (d->type == KDBUS_MSG_SRC_CMDLINE) { + m->cmdline = d->str; + m->cmdline_length = l; + } else if (d->type == KDBUS_MSG_SRC_CGROUP) + m->cgroup = d->str; + else if (d->type == KDBUS_MSG_SRC_AUDIT) + m->audit = &d->audit; + else if (d->type == KDBUS_MSG_SRC_CAPS) { + m->capability = d->data; + m->capability_size = l; + } else + log_debug("Got unknown field from kernel %llu", d->type); + } + + r = bus_message_parse_fields(m); + if (r < 0) { + sd_bus_message_unref(m); + return r; + } + + if (k->src_id == KDBUS_SRC_ID_KERNEL) + m->sender = "org.freedesktop.DBus"; + else { + snprintf(m->sender_buffer, sizeof(m->sender_buffer), ":1.%llu", (unsigned long long) k->src_id); + m->sender = m->sender_buffer; + } + + if (!m->destination) { + if (destination) + m->destination = destination; + else if (k->dst_id != KDBUS_DST_ID_WELL_KNOWN_NAME && + k->dst_id != KDBUS_DST_ID_BROADCAST) { + snprintf(m->destination_buffer, sizeof(m->destination_buffer), ":1.%llu", (unsigned long long) k->dst_id); + m->destination = m->destination_buffer; + } + } + + /* We take possession of the kmsg struct now */ + m->kdbus = k; + m->free_kdbus = true; + m->free_fds = true; + + fds = NULL; + + *ret = m; + return 1; +} + +int bus_kernel_read_message(sd_bus *bus, sd_bus_message **m) { + struct kdbus_msg *k; + size_t sz = 1024; + int r; + + assert(bus); + assert(m); + + for (;;) { + void *q; + + q = memalign(8, sz); + if (!q) + return -errno; + + free(bus->rbuffer); + k = bus->rbuffer = q; + k->size = sz; + + /* Let's tell valgrind that there's really no need to + * initialize this fully. This should be removed again + * when valgrind learned the kdbus ioctls natively. */ +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(k, sz); +#endif + + r = ioctl(bus->input_fd, KDBUS_CMD_MSG_RECV, bus->rbuffer); + if (r >= 0) + break; + + if (errno == EAGAIN) + return 0; + + if (errno != ENOBUFS) + return -errno; + + sz *= 2; + } + + r = bus_kernel_make_message(bus, k, m); + if (r > 0) + bus->rbuffer = NULL; + else + close_kdbus_msg(k); + + return r < 0 ? r : 1; +} + +int bus_kernel_create(const char *name, char **s) { + struct kdbus_cmd_bus_make *make; + struct kdbus_item *n, *cg; + size_t l; + int fd; + char *p; + + assert(name); + assert(s); + + fd = open("/dev/kdbus/control", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + l = strlen(name); + make = alloca0(offsetof(struct kdbus_cmd_bus_make, items) + + KDBUS_ITEM_HEADER_SIZE + sizeof(uint64_t) + + KDBUS_ITEM_HEADER_SIZE + DECIMAL_STR_MAX(uid_t) + 1 + l + 1); + + cg = make->items; + cg->type = KDBUS_MAKE_CGROUP; + cg->data64[0] = 1; + cg->size = KDBUS_ITEM_HEADER_SIZE + sizeof(uint64_t); + + n = KDBUS_ITEM_NEXT(cg); + n->type = KDBUS_MAKE_NAME; + sprintf(n->str, "%lu-%s", (unsigned long) getuid(), name); + n->size = KDBUS_ITEM_HEADER_SIZE + strlen(n->str) + 1; + + make->size = offsetof(struct kdbus_cmd_bus_make, items) + cg->size + n->size; + make->flags = KDBUS_MAKE_ACCESS_WORLD | KDBUS_MAKE_POLICY_OPEN; + make->bus_flags = 0; + make->bloom_size = BLOOM_SIZE; + assert_cc(BLOOM_SIZE % 8 == 0); + + p = strjoin("/dev/kdbus/", n->str, "/bus", NULL); + if (!p) + return -ENOMEM; + + if (ioctl(fd, KDBUS_CMD_BUS_MAKE, make) < 0) { + close_nointr_nofail(fd); + free(p); + return -errno; + } + + if (s) + *s = p; + + return fd; +} diff --git a/src/libsystemd-bus/bus-kernel.h b/src/libsystemd-bus/bus-kernel.h new file mode 100644 index 0000000000..ac746afe03 --- /dev/null +++ b/src/libsystemd-bus/bus-kernel.h @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-bus.h" + +int bus_kernel_connect(sd_bus *b); +int bus_kernel_take_fd(sd_bus *b); + +int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m); +int bus_kernel_read_message(sd_bus *bus, sd_bus_message **m); + +int bus_kernel_create(const char *name, char **s); diff --git a/src/libsystemd-bus/bus-match.c b/src/libsystemd-bus/bus-match.c new file mode 100644 index 0000000000..501a38df70 --- /dev/null +++ b/src/libsystemd-bus/bus-match.c @@ -0,0 +1,994 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-match.h" + +/* Example: + * + * A: type=signal,sender=foo,interface=bar + * B: type=signal,sender=quux,interface=fips + * C: type=signal,sender=quux,interface=waldo + * D: type=signal,member=test + * E: sender=miau + * F: type=signal + * G: type=signal + * + * results in this tree: + * + * BUS_MATCH_ROOT + * + BUS_MATCH_MESSAGE_TYPE + * | ` BUS_MATCH_VALUE: value == signal + * | + DBUS_MATCH_SENDER + * | | + BUS_MATCH_VALUE: value == foo + * | | | ` DBUS_MATCH_INTERFACE + * | | | ` BUS_MATCH_VALUE: value == bar + * | | | ` BUS_MATCH_LEAF: A + * | | ` BUS_MATCH_VALUE: value == quux + * | | ` DBUS_MATCH_INTERFACE + * | | | BUS_MATCH_VALUE: value == fips + * | | | ` BUS_MATCH_LEAF: B + * | | ` BUS_MATCH_VALUE: value == waldo + * | | ` BUS_MATCH_LEAF: C + * | + DBUS_MATCH_MEMBER + * | | ` BUS_MATCH_VALUE: value == test + * | | ` BUS_MATCH_LEAF: D + * | + BUS_MATCH_LEAF: F + * | ` BUS_MATCH_LEAF: G + * ` BUS_MATCH_SENDER + * ` BUS_MATCH_VALUE: value == miau + * ` BUS_MATCH_LEAF: E + */ + +static inline bool BUS_MATCH_IS_COMPARE(enum bus_match_node_type t) { + return t >= BUS_MATCH_MESSAGE_TYPE && t <= BUS_MATCH_ARG_NAMESPACE_LAST; +} + +static inline bool BUS_MATCH_CAN_HASH(enum bus_match_node_type t) { + return (t >= BUS_MATCH_MESSAGE_TYPE && t <= BUS_MATCH_PATH) || + (t >= BUS_MATCH_ARG && t <= BUS_MATCH_ARG_LAST); +} + +static void bus_match_node_free(struct bus_match_node *node) { + assert(node); + assert(node->parent); + assert(!node->child); + assert(node->type != BUS_MATCH_ROOT); + assert(node->type < _BUS_MATCH_NODE_TYPE_MAX); + + if (node->parent->child) { + /* We are apparently linked into the parent's child + * list. Let's remove us from there. */ + if (node->prev) { + assert(node->prev->next == node); + node->prev->next = node->next; + } else { + assert(node->parent->child == node); + node->parent->child = node->next; + } + + if (node->next) + node->next->prev = node->prev; + } + + if (node->type == BUS_MATCH_VALUE) { + /* We might be in the parent's hash table, so clean + * this up */ + + if (node->parent->type == BUS_MATCH_MESSAGE_TYPE) + hashmap_remove(node->parent->compare.children, UINT_TO_PTR(node->value.u8)); + else if (BUS_MATCH_CAN_HASH(node->parent->type) && node->value.str) + hashmap_remove(node->parent->compare.children, node->value.str); + + free(node->value.str); + } + + if (BUS_MATCH_IS_COMPARE(node->type)) { + assert(hashmap_isempty(node->compare.children)); + hashmap_free(node->compare.children); + } + + free(node); +} + +static bool bus_match_node_maybe_free(struct bus_match_node *node) { + assert(node); + + if (node->child) + return false; + + if (BUS_MATCH_IS_COMPARE(node->type) && !hashmap_isempty(node->compare.children)) + return true; + + bus_match_node_free(node); + return true; +} + +static bool value_node_test( + struct bus_match_node *node, + enum bus_match_node_type parent_type, + uint8_t value_u8, + const char *value_str) { + + assert(node); + assert(node->type == BUS_MATCH_VALUE); + + /* Tests parameters against this value node, doing prefix + * magic and stuff. */ + + switch (parent_type) { + + case BUS_MATCH_MESSAGE_TYPE: + return node->value.u8 == value_u8; + + case BUS_MATCH_SENDER: + case BUS_MATCH_DESTINATION: + case BUS_MATCH_INTERFACE: + case BUS_MATCH_MEMBER: + case BUS_MATCH_PATH: + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + return streq_ptr(node->value.str, value_str); + + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + return namespace_simple_pattern(node->value.str, value_str); + + case BUS_MATCH_PATH_NAMESPACE: + return path_simple_pattern(node->value.str, value_str); + + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + return path_complex_pattern(node->value.str, value_str); + + default: + assert_not_reached("Invalid node type"); + } +} + +static bool value_node_same( + struct bus_match_node *node, + enum bus_match_node_type parent_type, + uint8_t value_u8, + const char *value_str) { + + /* Tests parameters against this value node, not doing prefix + * magic and stuff, i.e. this one actually compares the match + * itself.*/ + + assert(node); + assert(node->type == BUS_MATCH_VALUE); + + switch (parent_type) { + + case BUS_MATCH_MESSAGE_TYPE: + return node->value.u8 == value_u8; + + case BUS_MATCH_SENDER: + case BUS_MATCH_DESTINATION: + case BUS_MATCH_INTERFACE: + case BUS_MATCH_MEMBER: + case BUS_MATCH_PATH: + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + case BUS_MATCH_PATH_NAMESPACE: + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + return streq(node->value.str, value_str); + + default: + assert_not_reached("Invalid node type"); + } +} + +int bus_match_run( + sd_bus *bus, + struct bus_match_node *node, + int ret, + sd_bus_message *m) { + + + const char *test_str = NULL; + uint8_t test_u8 = 0; + int r; + + assert(m); + + if (!node) + return 0; + + if (bus && bus->match_callbacks_modified) + return 0; + + /* Not these special semantics: when traversing the tree we + * usually let bus_match_run() when called for a node + * recursively invoke bus_match_run(). There's are two + * exceptions here though, which are BUS_NODE_ROOT (which + * cannot have a sibling), and BUS_NODE_VALUE (whose siblings + * are invoked anyway by its parent. */ + + switch (node->type) { + + case BUS_MATCH_ROOT: + + /* Run all children. Since we cannot have any siblings + * we won't call any. The children of the root node + * are compares or leaves, they will automatically + * call their siblings. */ + return bus_match_run(bus, node->child, ret, m); + + case BUS_MATCH_VALUE: + + /* Run all children. We don't execute any siblings, we + * assume our caller does that. The children of value + * nodes are compares or leaves, they will + * automatically call their siblings */ + + assert(node->child); + return bus_match_run(bus, node->child, ret, m); + + case BUS_MATCH_LEAF: + + if (bus) { + if (node->leaf.last_iteration == bus->iteration_counter) + return 0; + + node->leaf.last_iteration = bus->iteration_counter; + } + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + /* Run the callback. And then invoke siblings. */ + assert(node->leaf.callback); + r = node->leaf.callback(bus, ret, m, node->leaf.userdata); + if (r != 0) + return r; + + return bus_match_run(bus, node->next, ret, m); + + case BUS_MATCH_MESSAGE_TYPE: + test_u8 = m->header->type; + break; + + case BUS_MATCH_SENDER: + test_str = m->sender; + /* FIXME: resolve test_str from a well-known to a unique name first */ + break; + + case BUS_MATCH_DESTINATION: + test_str = m->destination; + break; + + case BUS_MATCH_INTERFACE: + test_str = m->interface; + break; + + case BUS_MATCH_MEMBER: + test_str = m->member; + break; + + case BUS_MATCH_PATH: + case BUS_MATCH_PATH_NAMESPACE: + test_str = m->path; + break; + + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + test_str = bus_message_get_arg(m, node->type - BUS_MATCH_ARG); + break; + + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + test_str = bus_message_get_arg(m, node->type - BUS_MATCH_ARG_PATH); + break; + + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + test_str = bus_message_get_arg(m, node->type - BUS_MATCH_ARG_NAMESPACE); + break; + + default: + assert_not_reached("Unknown match type."); + } + + if (BUS_MATCH_CAN_HASH(node->type)) { + struct bus_match_node *found; + + /* Lookup via hash table, nice! So let's jump directly. */ + + if (test_str) + found = hashmap_get(node->compare.children, test_str); + else if (node->type == BUS_MATCH_MESSAGE_TYPE) + found = hashmap_get(node->compare.children, UINT_TO_PTR(test_u8)); + else + found = NULL; + + if (found) { + r = bus_match_run(bus, found, ret, m); + if (r != 0) + return r; + } + } else { + struct bus_match_node *c; + + /* No hash table, so let's iterate manually... */ + + for (c = node->child; c; c = c->next) { + if (!value_node_test(c, node->type, test_u8, test_str)) + continue; + + r = bus_match_run(bus, c, ret, m); + if (r != 0) + return r; + } + } + + if (bus && bus->match_callbacks_modified) + return 0; + + /* And now, let's invoke our siblings */ + return bus_match_run(bus, node->next, ret, m); +} + +static int bus_match_add_compare_value( + struct bus_match_node *where, + enum bus_match_node_type t, + uint8_t value_u8, + const char *value_str, + struct bus_match_node **ret) { + + struct bus_match_node *c = NULL, *n = NULL; + int r; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(BUS_MATCH_IS_COMPARE(t)); + assert(ret); + + for (c = where->child; c && c->type != t; c = c->next) + ; + + if (c) { + /* Comparison node already exists? Then let's see if + * the value node exists too. */ + + if (t == BUS_MATCH_MESSAGE_TYPE) + n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8)); + else if (BUS_MATCH_CAN_HASH(t)) + n = hashmap_get(c->compare.children, value_str); + else { + for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next) + ; + } + + if (n) { + *ret = n; + return 0; + } + } else { + /* Comparison node, doesn't exist yet? Then let's + * create it. */ + + c = new0(struct bus_match_node, 1); + if (!c) { + r = -ENOMEM; + goto fail; + } + + c->type = t; + c->parent = where; + c->next = where->child; + if (c->next) + c->next->prev = c; + where->child = c; + + if (t == BUS_MATCH_MESSAGE_TYPE) { + c->compare.children = hashmap_new(trivial_hash_func, trivial_compare_func); + if (!c->compare.children) { + r = -ENOMEM; + goto fail; + } + } else if (BUS_MATCH_CAN_HASH(t)) { + c->compare.children = hashmap_new(string_hash_func, string_compare_func); + if (!c->compare.children) { + r = -ENOMEM; + goto fail; + } + } + } + + n = new0(struct bus_match_node, 1); + if (!n) { + r = -ENOMEM; + goto fail; + } + + n->type = BUS_MATCH_VALUE; + n->value.u8 = value_u8; + if (value_str) { + n->value.str = strdup(value_str); + if (!n->value.str) { + r = -ENOMEM; + goto fail; + } + } + + n->parent = c; + if (c->compare.children) { + + if (t == BUS_MATCH_MESSAGE_TYPE) + r = hashmap_put(c->compare.children, UINT_TO_PTR(value_u8), n); + else + r = hashmap_put(c->compare.children, n->value.str, n); + + if (r < 0) + goto fail; + } else { + n->next = c->child; + if (n->next) + n->next->prev = n; + c->child = n; + } + + *ret = n; + return 1; + +fail: + if (c) + bus_match_node_maybe_free(c); + + if (n) { + free(n->value.str); + free(n); + } + + return r; +} + +static int bus_match_find_compare_value( + struct bus_match_node *where, + enum bus_match_node_type t, + uint8_t value_u8, + const char *value_str, + struct bus_match_node **ret) { + + struct bus_match_node *c, *n; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(BUS_MATCH_IS_COMPARE(t)); + assert(ret); + + for (c = where->child; c && c->type != t; c = c->next) + ; + + if (!c) + return 0; + + if (t == BUS_MATCH_MESSAGE_TYPE) + n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8)); + else if (BUS_MATCH_CAN_HASH(t)) + n = hashmap_get(c->compare.children, value_str); + else { + for (n = c->child; !value_node_same(n, t, value_u8, value_str); n = n->next) + ; + } + + if (n) { + *ret = n; + return 1; + } + + return 0; +} + +static int bus_match_add_leaf( + struct bus_match_node *where, + sd_bus_message_handler_t callback, + void *userdata, + struct bus_match_node **ret) { + + struct bus_match_node *n; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(ret); + + n = new0(struct bus_match_node, 1); + if (!n) + return -ENOMEM; + + n->type = BUS_MATCH_LEAF; + n->parent = where; + n->next = where->child; + if (n->next) + n->next->prev = n; + n->leaf.callback = callback; + n->leaf.userdata = userdata; + + where->child = n; + + *ret = n; + return 1; +} + +static int bus_match_find_leaf( + struct bus_match_node *where, + sd_bus_message_handler_t callback, + void *userdata, + struct bus_match_node **ret) { + + struct bus_match_node *c; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(ret); + + for (c = where->child; c; c = c->next) { + if (c->type == BUS_MATCH_LEAF && + c->leaf.callback == callback && + c->leaf.userdata == userdata) { + *ret = c; + return 1; + } + } + + return 0; +} + +enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n) { + assert(k); + + if (n == 4 && memcmp(k, "type", 4) == 0) + return BUS_MATCH_MESSAGE_TYPE; + if (n == 6 && memcmp(k, "sender", 6) == 0) + return BUS_MATCH_SENDER; + if (n == 11 && memcmp(k, "destination", 11) == 0) + return BUS_MATCH_DESTINATION; + if (n == 9 && memcmp(k, "interface", 9) == 0) + return BUS_MATCH_INTERFACE; + if (n == 6 && memcmp(k, "member", 6) == 0) + return BUS_MATCH_MEMBER; + if (n == 4 && memcmp(k, "path", 4) == 0) + return BUS_MATCH_PATH; + if (n == 14 && memcmp(k, "path_namespace", 14) == 0) + return BUS_MATCH_PATH_NAMESPACE; + + if (n == 4 && memcmp(k, "arg", 3) == 0) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG + j; + } + + if (n == 5 && memcmp(k, "arg", 3) == 0) { + int a, b; + enum bus_match_node_type t; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG + a * 10 + b; + if (t > BUS_MATCH_ARG_LAST) + return -EINVAL; + + return t; + } + + if (n == 8 && memcmp(k, "arg", 3) == 0 && memcmp(k + 4, "path", 4) == 0) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG_PATH + j; + } + + if (n == 9 && memcmp(k, "arg", 3) == 0 && memcmp(k + 5, "path", 4) == 0) { + enum bus_match_node_type t; + int a, b; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG_PATH + a * 10 + b; + if (t > BUS_MATCH_ARG_PATH_LAST) + return -EINVAL; + + return t; + } + + if (n == 13 && memcmp(k, "arg", 3) == 0 && memcmp(k + 4, "namespace", 9) == 0) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG_NAMESPACE + j; + } + + if (n == 14 && memcmp(k, "arg", 3) == 0 && memcmp(k + 5, "namespace", 9) == 0) { + enum bus_match_node_type t; + int a, b; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG_NAMESPACE + a * 10 + b; + if (t > BUS_MATCH_ARG_NAMESPACE_LAST) + return -EINVAL; + + return t; + } + + return -EINVAL; +} + +struct match_component { + enum bus_match_node_type type; + uint8_t value_u8; + char *value_str; +}; + +static int match_component_compare(const void *a, const void *b) { + const struct match_component *x = a, *y = b; + + if (x->type < y->type) + return -1; + if (x->type > y->type) + return 1; + + return 0; +} + +static void free_components(struct match_component *components, unsigned n_components) { + unsigned i; + + for (i = 0; i < n_components; i++) + free(components[i].value_str); + + free(components); +} + +static int parse_match( + const char *match, + struct match_component **_components, + unsigned *_n_components) { + + const char *p = match; + struct match_component *components = NULL; + size_t components_allocated = 0; + unsigned n_components = 0, i; + _cleanup_free_ char *value = NULL; + int r; + + assert(match); + assert(_components); + assert(_n_components); + + while (*p != 0) { + const char *eq, *q; + enum bus_match_node_type t; + unsigned j = 0; + size_t value_allocated = 0; + bool escaped = false; + uint8_t u; + + eq = strchr(p, '='); + if (!eq) + return -EINVAL; + + if (eq[1] != '\'') + return -EINVAL; + + t = bus_match_node_type_from_string(p, eq - p); + if (t < 0) + return -EINVAL; + + for (q = eq + 2;; q++) { + + if (*q == 0) { + r = -EINVAL; + goto fail; + } + + if (!escaped) { + if (*q == '\\') { + escaped = true; + continue; + } + if (*q == '\'') { + if (value) + value[j] = 0; + break; + } + } + + if (!GREEDY_REALLOC(value, value_allocated, j + 2)) { + r = -ENOMEM; + goto fail; + } + + value[j++] = *q; + escaped = false; + } + + if (t == BUS_MATCH_MESSAGE_TYPE) { + r = bus_message_type_from_string(value, &u); + if (r < 0) + goto fail; + + free(value); + value = NULL; + } else + u = 0; + + if (!GREEDY_REALLOC(components, components_allocated, n_components + 1)) { + r = -ENOMEM; + goto fail; + } + + components[n_components].type = t; + components[n_components].value_str = value; + components[n_components].value_u8 = u; + n_components++; + + value = NULL; + + if (q[1] == 0) + break; + + if (q[1] != ',') { + r = -EINVAL; + goto fail; + } + + p = q + 2; + } + + /* Order the whole thing, so that we always generate the same tree */ + qsort(components, n_components, sizeof(struct match_component), match_component_compare); + + /* Check for duplicates */ + for (i = 0; i+1 < n_components; i++) + if (components[i].type == components[i+1].type) { + r = -EINVAL; + goto fail; + } + + *_components = components; + *_n_components = n_components; + + return 0; + +fail: + free_components(components, n_components); + return r; +} + +int bus_match_add( + struct bus_match_node *root, + const char *match, + sd_bus_message_handler_t callback, + void *userdata, + struct bus_match_node **ret) { + + struct match_component *components = NULL; + unsigned n_components = 0, i; + struct bus_match_node *n; + int r; + + assert(root); + assert(match); + assert(callback); + + r = parse_match(match, &components, &n_components); + if (r < 0) + return r; + + n = root; + for (i = 0; i < n_components; i++) { + r = bus_match_add_compare_value( + n, components[i].type, + components[i].value_u8, components[i].value_str, &n); + if (r < 0) + goto finish; + } + + r = bus_match_add_leaf(n, callback, userdata, &n); + if (r < 0) + goto finish; + + if (ret) + *ret = n; + +finish: + free_components(components, n_components); + return r; +} + +int bus_match_remove( + struct bus_match_node *root, + const char *match, + sd_bus_message_handler_t callback, + void *userdata) { + + struct match_component *components = NULL; + unsigned n_components = 0, i; + struct bus_match_node *n, **gc; + int r; + + assert(root); + assert(match); + + r = parse_match(match, &components, &n_components); + if (r < 0) + return r; + + gc = newa(struct bus_match_node*, n_components); + + n = root; + for (i = 0; i < n_components; i++) { + r = bus_match_find_compare_value( + n, components[i].type, + components[i].value_u8, components[i].value_str, + &n); + if (r <= 0) + goto finish; + + gc[i] = n; + } + + r = bus_match_find_leaf(n, callback, userdata, &n); + if (r <= 0) + goto finish; + + /* Free the leaf */ + bus_match_node_free(n); + + /* Prune the tree above */ + for (i = n_components; i > 0; i --) { + struct bus_match_node *p = gc[i-1]->parent; + + if (!bus_match_node_maybe_free(gc[i-1])) + break; + + if (!bus_match_node_maybe_free(p)) + break; + } + +finish: + free_components(components, n_components); + return r; +} + +void bus_match_free(struct bus_match_node *node) { + struct bus_match_node *c; + + if (!node) + return; + + if (BUS_MATCH_CAN_HASH(node->type)) { + Iterator i; + + HASHMAP_FOREACH(c, node->compare.children, i) + bus_match_free(c); + + assert(hashmap_isempty(node->compare.children)); + } + + while ((c = node->child)) + bus_match_free(c); + + if (node->type != BUS_MATCH_ROOT) + bus_match_node_free(node); +} + +const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l) { + switch (t) { + + case BUS_MATCH_ROOT: + return "root"; + + case BUS_MATCH_VALUE: + return "value"; + + case BUS_MATCH_LEAF: + return "leaf"; + + case BUS_MATCH_MESSAGE_TYPE: + return "type"; + + case BUS_MATCH_SENDER: + return "sender"; + + case BUS_MATCH_DESTINATION: + return "destination"; + + case BUS_MATCH_INTERFACE: + return "interface"; + + case BUS_MATCH_MEMBER: + return "member"; + + case BUS_MATCH_PATH: + return "path"; + + case BUS_MATCH_PATH_NAMESPACE: + return "path_namespace"; + + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + snprintf(buf, l, "arg%i", t - BUS_MATCH_ARG); + return buf; + + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + snprintf(buf, l, "arg%ipath", t - BUS_MATCH_ARG_PATH); + return buf; + + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + snprintf(buf, l, "arg%inamespace", t - BUS_MATCH_ARG_NAMESPACE); + return buf; + + default: + return NULL; + } +} + +void bus_match_dump(struct bus_match_node *node, unsigned level) { + struct bus_match_node *c; + _cleanup_free_ char *pfx = NULL; + char buf[32]; + + if (!node) + return; + + pfx = strrep(" ", level); + printf("%s[%s]", strempty(pfx), bus_match_node_type_to_string(node->type, buf, sizeof(buf))); + + if (node->type == BUS_MATCH_VALUE) { + if (node->parent->type == BUS_MATCH_MESSAGE_TYPE) + printf(" <%u>\n", node->value.u8); + else + printf(" <%s>\n", node->value.str); + } else if (node->type == BUS_MATCH_ROOT) + puts(" root"); + else if (node->type == BUS_MATCH_LEAF) + printf(" %p/%p\n", node->leaf.callback, node->leaf.userdata); + else + putchar('\n'); + + if (BUS_MATCH_CAN_HASH(node->type)) { + Iterator i; + + HASHMAP_FOREACH(c, node->compare.children, i) + bus_match_dump(c, level + 1); + } + + for (c = node->child; c; c = c->next) + bus_match_dump(c, level + 1); +} diff --git a/src/libsystemd-bus/bus-match.h b/src/libsystemd-bus/bus-match.h new file mode 100644 index 0000000000..075f1a9e3a --- /dev/null +++ b/src/libsystemd-bus/bus-match.h @@ -0,0 +1,82 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "hashmap.h" + +#include "sd-bus.h" + +enum bus_match_node_type { + BUS_MATCH_ROOT, + BUS_MATCH_VALUE, + BUS_MATCH_LEAF, + + /* The following are all different kinds of compare nodes */ + BUS_MATCH_MESSAGE_TYPE, + BUS_MATCH_SENDER, + BUS_MATCH_DESTINATION, + BUS_MATCH_INTERFACE, + BUS_MATCH_MEMBER, + BUS_MATCH_PATH, + BUS_MATCH_PATH_NAMESPACE, + BUS_MATCH_ARG, + BUS_MATCH_ARG_LAST = BUS_MATCH_ARG + 63, + BUS_MATCH_ARG_PATH, + BUS_MATCH_ARG_PATH_LAST = BUS_MATCH_ARG_PATH + 63, + BUS_MATCH_ARG_NAMESPACE, + BUS_MATCH_ARG_NAMESPACE_LAST = BUS_MATCH_ARG_NAMESPACE + 63, + _BUS_MATCH_NODE_TYPE_MAX, + _BUS_MATCH_NODE_TYPE_INVALID = -1 +}; + +struct bus_match_node { + enum bus_match_node_type type; + struct bus_match_node *parent, *next, *prev, *child; + + union { + struct { + char *str; + uint8_t u8; + } value; + struct { + sd_bus_message_handler_t callback; + void *userdata; + unsigned last_iteration; + } leaf; + struct { + /* If this is set, then the child is NULL */ + Hashmap *children; + } compare; + }; +}; + +int bus_match_run(sd_bus *bus, struct bus_match_node *root, int ret, sd_bus_message *m); + +int bus_match_add(struct bus_match_node *root, const char *match, sd_bus_message_handler_t callback, void *userdata, struct bus_match_node **ret); +int bus_match_remove(struct bus_match_node *root, const char *match, sd_bus_message_handler_t callback, void *userdata); + +void bus_match_free(struct bus_match_node *node); + +void bus_match_dump(struct bus_match_node *node, unsigned level); + +const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l); +enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n); diff --git a/src/libsystemd-bus/bus-message.c b/src/libsystemd-bus/bus-message.c new file mode 100644 index 0000000000..835a9f9a44 --- /dev/null +++ b/src/libsystemd-bus/bus-message.c @@ -0,0 +1,3494 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <fcntl.h> + +#include "util.h" +#include "utf8.h" +#include "strv.h" +#include "time-util.h" +#include "cgroup-util.h" + +#include "sd-bus.h" +#include "bus-message.h" +#include "bus-internal.h" +#include "bus-type.h" +#include "bus-signature.h" + +static int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored); + +static void reset_containers(sd_bus_message *m) { + unsigned i; + + assert(m); + + for (i = 0; i < m->n_containers; i++) + free(m->containers[i].signature); + + free(m->containers); + m->containers = NULL; + + m->n_containers = 0; + m->root_container.index = 0; +} + +static void message_free(sd_bus_message *m) { + assert(m); + + if (m->free_header) + free(m->header); + + if (m->free_fields) + free(m->fields); + + if (m->free_body) + free(m->body); + + if (m->free_kdbus) + free(m->kdbus); + + if (m->free_fds) { + close_many(m->fds, m->n_fds); + free(m->fds); + } + + free(m->cmdline_array); + + reset_containers(m); + free(m->root_container.signature); + + free(m->peeked_signature); + + free(m->unit); + free(m->user_unit); + free(m->session); + free(m); +} + +static void* buffer_extend(void **p, uint32_t *sz, size_t align, size_t extend) { + size_t start, n; + void *k; + + assert(p); + assert(sz); + assert(align > 0); + + start = ALIGN_TO((size_t) *sz, align); + n = start + extend; + + if (n == *sz) + return (uint8_t*) *p + start; + + if (n > (size_t) ((uint32_t) -1)) + return NULL; + + k = realloc(*p, n); + if (!k) + return NULL; + + /* Zero out padding */ + if (start > *sz) + memset((uint8_t*) k + *sz, 0, start - *sz); + + *p = k; + *sz = n; + + return (uint8_t*) k + start; +} + +static void *message_extend_fields(sd_bus_message *m, size_t align, size_t sz) { + void *p, *o; + + assert(m); + + o = m->fields; + p = buffer_extend(&m->fields, &m->header->fields_size, align, sz); + if (!p) + return NULL; + + if (o != m->fields) { + /* Adjust quick access pointers */ + + if (m->path) + m->path = (const char*) m->fields + (m->path - (const char*) o); + if (m->interface) + m->interface = (const char*) m->fields + (m->interface - (const char*) o); + if (m->member) + m->member = (const char*) m->fields + (m->member - (const char*) o); + if (m->destination) + m->destination = (const char*) m->fields + (m->destination - (const char*) o); + if (m->sender) + m->sender = (const char*) m->fields + (m->sender - (const char*) o); + if (m->error.name) + m->error.name = (const char*) m->fields + (m->error.name - (const char*) o); + } + + m->free_fields = true; + + return p; +} + +static int message_append_field_string( + sd_bus_message *m, + uint8_t h, + char type, + const char *s, + const char **ret) { + + size_t l; + uint8_t *p; + + assert(m); + + l = strlen(s); + if (l > (size_t) (uint32_t) -1) + return -EINVAL; + + /* field id byte + signature length + signature 's' + NUL + string length + string + NUL */ + p = message_extend_fields(m, 8, 4 + 4 + l + 1); + if (!p) + return -ENOMEM; + + p[0] = h; + p[1] = 1; + p[2] = type; + p[3] = 0; + + ((uint32_t*) p)[1] = l; + memcpy(p + 8, s, l + 1); + + if (ret) + *ret = (const char*) p + 8; + + return 0; +} + +static int message_append_field_signature( + sd_bus_message *m, + uint8_t h, + const char *s, + const char **ret) { + + size_t l; + uint8_t *p; + + assert(m); + + l = strlen(s); + if (l > 255) + return -EINVAL; + + /* field id byte + signature length + signature 'g' + NUL + string length + string + NUL */ + p = message_extend_fields(m, 8, 4 + 1 + l + 1); + if (!p) + return -ENOMEM; + + p[0] = h; + p[1] = 1; + p[2] = SD_BUS_TYPE_SIGNATURE; + p[3] = 0; + p[4] = l; + memcpy(p + 5, s, l + 1); + + if (ret) + *ret = (const char*) p + 5; + + return 0; +} + +static int message_append_field_uint32(sd_bus_message *m, uint8_t h, uint32_t x) { + uint8_t *p; + + assert(m); + + /* field id byte + signature length + signature 'u' + NUL + value */ + p = message_extend_fields(m, 8, 4 + 4); + if (!p) + return -ENOMEM; + + p[0] = h; + p[1] = 1; + p[2] = SD_BUS_TYPE_UINT32; + p[3] = 0; + + ((uint32_t*) p)[1] = x; + + return 0; +} + +int bus_message_from_header( + void *buffer, + size_t length, + int *fds, + unsigned n_fds, + const struct ucred *ucred, + const char *label, + size_t extra, + sd_bus_message **ret) { + + sd_bus_message *m; + struct bus_header *h; + size_t a, label_sz; + + assert(buffer || length <= 0); + assert(fds || n_fds <= 0); + assert(ret); + + if (length < sizeof(struct bus_header)) + return -EBADMSG; + + h = buffer; + if (h->version != 1) + return -EBADMSG; + + if (h->serial == 0) + return -EBADMSG; + + if (h->type == _SD_BUS_MESSAGE_TYPE_INVALID) + return -EBADMSG; + + if (h->endian != SD_BUS_LITTLE_ENDIAN && + h->endian != SD_BUS_BIG_ENDIAN) + return -EBADMSG; + + a = ALIGN(sizeof(sd_bus_message)) + ALIGN(extra); + + if (label) { + label_sz = strlen(label); + a += label_sz + 1; + } + + m = malloc0(a); + if (!m) + return -ENOMEM; + + m->n_ref = 1; + m->sealed = true; + m->header = h; + m->fds = fds; + m->n_fds = n_fds; + + if (ucred) { + m->uid = ucred->uid; + m->pid = ucred->pid; + m->gid = ucred->gid; + m->uid_valid = m->gid_valid = true; + } + + if (label) { + m->label = (char*) m + ALIGN(sizeof(sd_bus_message)) + ALIGN(extra); + memcpy(m->label, label, label_sz + 1); + } + + *ret = m; + return 0; +} + +int bus_message_from_malloc( + void *buffer, + size_t length, + int *fds, + unsigned n_fds, + const struct ucred *ucred, + const char *label, + sd_bus_message **ret) { + + sd_bus_message *m; + int r; + + r = bus_message_from_header(buffer, length, fds, n_fds, ucred, label, 0, &m); + if (r < 0) + return r; + + if (length != BUS_MESSAGE_SIZE(m)) { + r = -EBADMSG; + goto fail; + } + + m->fields = (uint8_t*) buffer + sizeof(struct bus_header); + m->body = (uint8_t*) buffer + sizeof(struct bus_header) + ALIGN8(BUS_MESSAGE_FIELDS_SIZE(m)); + + m->n_iovec = 1; + m->iovec[0].iov_base = buffer; + m->iovec[0].iov_len = length; + + r = bus_message_parse_fields(m); + if (r < 0) + goto fail; + + /* We take possession of the memory and fds now */ + m->free_header = true; + m->free_fds = true; + + *ret = m; + return 0; + +fail: + message_free(m); + return r; +} + +static sd_bus_message *message_new(sd_bus *bus, uint8_t type) { + sd_bus_message *m; + + m = malloc0(ALIGN(sizeof(sd_bus_message)) + sizeof(struct bus_header)); + if (!m) + return NULL; + + m->n_ref = 1; + m->header = (struct bus_header*) ((uint8_t*) m + ALIGN(sizeof(struct sd_bus_message))); + m->header->endian = SD_BUS_NATIVE_ENDIAN; + m->header->type = type; + m->header->version = bus ? bus->message_version : 1; + m->allow_fds = !bus || bus->can_fds || (bus->state != BUS_HELLO && bus->state != BUS_RUNNING); + + return m; +} + +int sd_bus_message_new_signal( + sd_bus *bus, + const char *path, + const char *interface, + const char *member, + sd_bus_message **m) { + + sd_bus_message *t; + int r; + + if (!path) + return -EINVAL; + if (!interface) + return -EINVAL; + if (!member) + return -EINVAL; + if (!m) + return -EINVAL; + if (bus && bus->state == BUS_UNSET) + return -ENOTCONN; + + t = message_new(bus, SD_BUS_MESSAGE_TYPE_SIGNAL); + if (!t) + return -ENOMEM; + + t->header->flags |= SD_BUS_MESSAGE_NO_REPLY_EXPECTED; + + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path); + if (r < 0) + goto fail; + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface); + if (r < 0) + goto fail; + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member); + if (r < 0) + goto fail; + + *m = t; + return 0; + +fail: + sd_bus_message_unref(t); + return r; +} + +int sd_bus_message_new_method_call( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_message **m) { + + sd_bus_message *t; + int r; + + if (!path) + return -EINVAL; + if (!member) + return -EINVAL; + if (!m) + return -EINVAL; + if (bus && bus->state == BUS_UNSET) + return -ENOTCONN; + + t = message_new(bus, SD_BUS_MESSAGE_TYPE_METHOD_CALL); + if (!t) + return -ENOMEM; + + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path); + if (r < 0) + goto fail; + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member); + if (r < 0) + goto fail; + + if (interface) { + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface); + if (r < 0) + goto fail; + } + + if (destination) { + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &t->destination); + if (r < 0) + goto fail; + } + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +static int message_new_reply( + sd_bus *bus, + sd_bus_message *call, + uint8_t type, + sd_bus_message **m) { + + sd_bus_message *t; + int r; + + if (!call) + return -EINVAL; + if (!call->sealed) + return -EPERM; + if (call->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + if (!m) + return -EINVAL; + if (bus && bus->state == BUS_UNSET) + return -ENOTCONN; + + t = message_new(bus, type); + if (!t) + return -ENOMEM; + + t->header->flags |= SD_BUS_MESSAGE_NO_REPLY_EXPECTED; + t->reply_serial = BUS_MESSAGE_SERIAL(call); + + r = message_append_field_uint32(t, SD_BUS_MESSAGE_HEADER_REPLY_SERIAL, t->reply_serial); + if (r < 0) + goto fail; + + if (call->sender) { + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, call->sender, &t->sender); + if (r < 0) + goto fail; + } + + t->dont_send = !!(call->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED); + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +int sd_bus_message_new_method_return( + sd_bus *bus, + sd_bus_message *call, + sd_bus_message **m) { + + return message_new_reply(bus, call, SD_BUS_MESSAGE_TYPE_METHOD_RETURN, m); +} + +int sd_bus_message_new_method_error( + sd_bus *bus, + sd_bus_message *call, + const sd_bus_error *e, + sd_bus_message **m) { + + sd_bus_message *t; + int r; + + if (!sd_bus_error_is_set(e)) + return -EINVAL; + if (!m) + return -EINVAL; + + r = message_new_reply(bus, call, SD_BUS_MESSAGE_TYPE_METHOD_ERROR, &t); + if (r < 0) + return r; + + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name); + if (r < 0) + goto fail; + + if (e->message) { + r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message); + if (r < 0) + goto fail; + } + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +sd_bus_message* sd_bus_message_ref(sd_bus_message *m) { + if (!m) + return NULL; + + assert(m->n_ref > 0); + m->n_ref++; + + return m; +} + +sd_bus_message* sd_bus_message_unref(sd_bus_message *m) { + if (!m) + return NULL; + + assert(m->n_ref > 0); + m->n_ref--; + + if (m->n_ref <= 0) + message_free(m); + + return NULL; +} + +int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type) { + if (!m) + return -EINVAL; + if (!type) + return -EINVAL; + + *type = m->header->type; + return 0; +} + +int sd_bus_message_get_serial(sd_bus_message *m, uint64_t *serial) { + if (!m) + return -EINVAL; + if (!serial) + return -EINVAL; + if (m->header->serial == 0) + return -ENOENT; + + *serial = BUS_MESSAGE_SERIAL(m); + return 0; +} + +int sd_bus_message_get_reply_serial(sd_bus_message *m, uint64_t *serial) { + if (!m) + return -EINVAL; + if (!serial) + return -EINVAL; + if (m->reply_serial == 0) + return -ENOENT; + + *serial = m->reply_serial; + return 0; +} + +int sd_bus_message_get_no_reply(sd_bus_message *m) { + if (!m) + return -EINVAL; + + return m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_CALL ? !!(m->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) : 0; +} + +const char *sd_bus_message_get_path(sd_bus_message *m) { + if (!m) + return NULL; + + return m->path; +} + +const char *sd_bus_message_get_interface(sd_bus_message *m) { + if (!m) + return NULL; + + return m->interface; +} + +const char *sd_bus_message_get_member(sd_bus_message *m) { + if (!m) + return NULL; + + return m->member; +} +const char *sd_bus_message_get_destination(sd_bus_message *m) { + if (!m) + return NULL; + + return m->destination; +} + +const char *sd_bus_message_get_sender(sd_bus_message *m) { + if (!m) + return NULL; + + return m->sender; +} + +const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m) { + if (!m) + return NULL; + + if (!sd_bus_error_is_set(&m->error)) + return NULL; + + return &m->error; +} + +int sd_bus_message_get_uid(sd_bus_message *m, uid_t *uid) { + if (!m) + return -EINVAL; + if (!uid) + return -EINVAL; + if (!m->uid_valid) + return -ESRCH; + + *uid = m->uid; + return 0; +} + +int sd_bus_message_get_gid(sd_bus_message *m, gid_t *gid) { + if (!m) + return -EINVAL; + if (!gid) + return -EINVAL; + if (!m->gid_valid) + return -ESRCH; + + *gid = m->gid; + return 0; +} + +int sd_bus_message_get_pid(sd_bus_message *m, pid_t *pid) { + if (!m) + return -EINVAL; + if (!pid) + return -EINVAL; + if (m->pid <= 0) + return -ESRCH; + + *pid = m->pid; + return 0; +} + +int sd_bus_message_get_tid(sd_bus_message *m, pid_t *tid) { + if (!m) + return -EINVAL; + if (!tid) + return -EINVAL; + if (m->tid <= 0) + return -ESRCH; + + *tid = m->tid; + return 0; +} + +int sd_bus_message_get_pid_starttime(sd_bus_message *m, uint64_t *usec) { + if (!m) + return -EINVAL; + if (!usec) + return -EINVAL; + if (m->pid_starttime <= 0) + return -ESRCH; + + *usec = m->pid_starttime; + return 0; +} + +int sd_bus_message_get_selinux_context(sd_bus_message *m, const char **ret) { + if (!m) + return -EINVAL; + if (!m->label) + return -ESRCH; + + *ret = m->label; + return 0; +} + +int sd_bus_message_get_monotonic_timestamp(sd_bus_message *m, uint64_t *usec) { + if (!m) + return -EINVAL; + if (!usec) + return -EINVAL; + if (m->monotonic <= 0) + return -ESRCH; + + *usec = m->monotonic; + return 0; +} + +int sd_bus_message_get_realtime_timestamp(sd_bus_message *m, uint64_t *usec) { + if (!m) + return -EINVAL; + if (!usec) + return -EINVAL; + if (m->realtime <= 0) + return -ESRCH; + + *usec = m->realtime; + return 0; +} + +int sd_bus_message_get_comm(sd_bus_message *m, const char **ret) { + if (!m) + return -EINVAL; + if (!ret) + return -EINVAL; + if (!m->comm) + return -ESRCH; + + *ret = m->comm; + return 0; +} + +int sd_bus_message_get_tid_comm(sd_bus_message *m, const char **ret) { + if (!m) + return -EINVAL; + if (!ret) + return -EINVAL; + if (!m->tid_comm) + return -ESRCH; + + *ret = m->tid_comm; + return 0; +} + +int sd_bus_message_get_exe(sd_bus_message *m, const char **ret) { + if (!m) + return -EINVAL; + if (!ret) + return -EINVAL; + if (!m->exe) + return -ESRCH; + + *ret = m->exe; + return 0; +} + +int sd_bus_message_get_cgroup(sd_bus_message *m, const char **ret) { + if (!m) + return -EINVAL; + if (!ret) + return -EINVAL; + if (!m->cgroup) + return -ESRCH; + + *ret = m->cgroup; + return 0; +} + +int sd_bus_message_get_unit(sd_bus_message *m, const char **ret) { + int r; + + if (!m) + return -EINVAL; + if (!ret) + return -EINVAL; + if (!m->cgroup) + return -ESRCH; + + if (!m->unit) { + r = cg_path_get_unit(m->cgroup, &m->unit); + if (r < 0) + return r; + } + + *ret = m->unit; + return 0; +} + +int sd_bus_message_get_user_unit(sd_bus_message *m, const char **ret) { + int r; + + if (!m) + return -EINVAL; + if (!ret) + return -EINVAL; + if (!m->cgroup) + return -ESRCH; + + if (!m->user_unit) { + r = cg_path_get_user_unit(m->cgroup, &m->user_unit); + if (r < 0) + return r; + } + + *ret = m->user_unit; + return 0; +} + +int sd_bus_message_get_session(sd_bus_message *m, const char **ret) { + int r; + + if (!m) + return -EINVAL; + if (!ret) + return -EINVAL; + if (!m->cgroup) + return -ESRCH; + + if (!m->session) { + r = cg_path_get_session(m->cgroup, &m->session); + if (r < 0) + return r; + } + + *ret = m->session; + return 0; +} + +int sd_bus_message_get_owner_uid(sd_bus_message *m, uid_t *uid) { + if (!m) + return -EINVAL; + if (!uid) + return -EINVAL; + if (!m->cgroup) + return -ESRCH; + + return cg_path_get_owner_uid(m->cgroup, uid); +} + +int sd_bus_message_get_cmdline(sd_bus_message *m, char ***cmdline) { + size_t n, i; + const char *p; + bool first; + + if (!m) + return -EINVAL; + + if (!m->cmdline) + return -ENOENT; + + for (p = m->cmdline, n = 0; p < m->cmdline + m->cmdline_length; p++) + if (*p == 0) + n++; + + m->cmdline_array = new(char*, n + 1); + if (!m->cmdline_array) + return -ENOMEM; + + for (p = m->cmdline, i = 0, first = true; p < m->cmdline + m->cmdline_length; p++) { + if (first) + m->cmdline_array[i++] = (char*) p; + + first = *p == 0; + } + + m->cmdline_array[i] = NULL; + *cmdline = m->cmdline_array; + + return 0; +} + +int sd_bus_message_get_audit_sessionid(sd_bus_message *m, uint32_t *sessionid) { + if (!m) + return -EINVAL; + if (!sessionid) + return -EINVAL; + if (!m->audit) + return -ESRCH; + + *sessionid = m->audit->sessionid; + return 0; +} + +int sd_bus_message_get_audit_loginuid(sd_bus_message *m, uid_t *uid) { + if (!m) + return -EINVAL; + if (!uid) + return -EINVAL; + if (!m->audit) + return -ESRCH; + + *uid = m->audit->loginuid; + return 0; +} + +int sd_bus_message_has_effective_cap(sd_bus_message *m, int capability) { + unsigned sz; + + if (!m) + return -EINVAL; + if (capability < 0) + return -EINVAL; + if (!m->capability) + return -ESRCH; + + sz = m->capability_size / 4; + if ((unsigned) capability >= sz*8) + return 0; + + return !!(m->capability[2 * sz + (capability / 8)] & (1 << (capability % 8))); +} + +int sd_bus_message_is_signal(sd_bus_message *m, const char *interface, const char *member) { + if (!m) + return -EINVAL; + + if (m->header->type != SD_BUS_MESSAGE_TYPE_SIGNAL) + return 0; + + if (interface && (!m->interface || !streq(m->interface, interface))) + return 0; + + if (member && (!m->member || !streq(m->member, member))) + return 0; + + return 1; +} + +int sd_bus_message_is_method_call(sd_bus_message *m, const char *interface, const char *member) { + if (!m) + return -EINVAL; + + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return 0; + + if (interface && (!m->interface || !streq(m->interface, interface))) + return 0; + + if (member && (!m->member || !streq(m->member, member))) + return 0; + + return 1; +} + +int sd_bus_message_is_method_error(sd_bus_message *m, const char *name) { + if (!m) + return -EINVAL; + + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_ERROR) + return 0; + + if (name && (!m->error.name || !streq(m->error.name, name))) + return 0; + + return 1; +} + +int sd_bus_message_set_no_reply(sd_bus_message *m, int b) { + if (!m) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EPERM; + + if (b) + m->header->flags |= SD_BUS_MESSAGE_NO_REPLY_EXPECTED; + else + m->header->flags &= ~SD_BUS_MESSAGE_NO_REPLY_EXPECTED; + + return 0; +} + +static struct bus_container *message_get_container(sd_bus_message *m) { + assert(m); + + if (m->n_containers == 0) + return &m->root_container; + + assert(m->containers); + return m->containers + m->n_containers - 1; +} + +static void *message_extend_body(sd_bus_message *m, size_t align, size_t sz) { + void *p, *o; + size_t added; + struct bus_container *c; + + assert(m); + assert(align > 0); + + o = m->body; + added = m->header->body_size; + + p = buffer_extend(&m->body, &m->header->body_size, align, sz); + if (!p) + return NULL; + + added = m->header->body_size - added; + + for (c = m->containers; c < m->containers + m->n_containers; c++) + if (c->array_size) { + c->array_size = (uint32_t*) ((uint8_t*) m->body + ((uint8_t*) c->array_size - (uint8_t*) o)); + *c->array_size += added; + } + + if (o != m->body) { + if (m->error.message) + m->error.message = (const char*) m->body + (m->error.message - (const char*) o); + } + + m->free_body = true; + + return p; +} + +int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored) { + struct bus_container *c; + ssize_t align, sz; + uint32_t k; + void *a; + char *e = NULL; + int fd = -1; + uint32_t fdi = 0; + int r; + + if (!m) + return -EINVAL; + if (!p) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (!bus_type_is_basic(type)) + return -EINVAL; + + c = message_get_container(m); + + if (c->signature && c->signature[c->index]) { + /* Container signature is already set */ + + if (c->signature[c->index] != type) + return -ENXIO; + } else { + /* Maybe we can append to the signature? But only if this is the top-level container*/ + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(type), NULL); + if (!e) + return -ENOMEM; + } + + switch (type) { + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + + align = 4; + sz = 4 + strlen(p) + 1; + break; + + case SD_BUS_TYPE_SIGNATURE: + + align = 1; + sz = 1 + strlen(p) + 1; + break; + + case SD_BUS_TYPE_BOOLEAN: + align = sz = 4; + + assert_cc(sizeof(int) == sizeof(uint32_t)); + memcpy(&k, p, 4); + k = !!k; + p = &k; + break; + + case SD_BUS_TYPE_UNIX_FD: { + int z, *f; + + if (!m->allow_fds) { + r = -ENOTSUP; + goto fail; + } + + align = sz = 4; + + z = *(int*) p; + if (z < 0) { + r = -EINVAL; + goto fail; + } + + fd = fcntl(z, F_DUPFD_CLOEXEC, 3); + if (fd < 0) { + r = -errno; + goto fail; + } + + f = realloc(m->fds, sizeof(int) * (m->n_fds + 1)); + if (!f) { + r = -ENOMEM; + goto fail; + } + + fdi = m->n_fds; + f[fdi] = fd; + m->fds = f; + m->free_fds = true; + break; + } + + default: + align = bus_type_get_alignment(type); + sz = bus_type_get_size(type); + break; + } + + assert(align > 0); + assert(sz > 0); + + a = message_extend_body(m, align, sz); + if (!a) { + r = -ENOMEM; + goto fail; + } + + if (type == SD_BUS_TYPE_STRING || type == SD_BUS_TYPE_OBJECT_PATH) { + *(uint32_t*) a = sz - 5; + memcpy((uint8_t*) a + 4, p, sz - 4); + + if (stored) + *stored = (const uint8_t*) a + 4; + + } else if (type == SD_BUS_TYPE_SIGNATURE) { + *(uint8_t*) a = sz - 1; + memcpy((uint8_t*) a + 1, p, sz - 1); + + if (stored) + *stored = (const uint8_t*) a + 1; + } else if (type == SD_BUS_TYPE_UNIX_FD) { + *(uint32_t*) a = fdi; + + if (stored) + *stored = a; + + m->n_fds ++; + + } else { + memcpy(a, p, sz); + + if (stored) + *stored = a; + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 0; + +fail: + /* Truncate extended signature again */ + if (e) + c->signature[c->index] = 0; + + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p) { + return message_append_basic(m, type, p, NULL); +} + +static int bus_message_open_array( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + uint32_t **array_size) { + + unsigned nindex; + char *e = NULL; + void *a, *b; + int alignment; + size_t saved; + + assert(m); + assert(c); + assert(contents); + assert(array_size); + + if (!signature_is_single(contents)) + return -EINVAL; + + alignment = bus_type_get_alignment(contents[0]); + if (alignment < 0) + return alignment; + + if (c->signature && c->signature[c->index]) { + + /* Verify the existing signature */ + + if (c->signature[c->index] != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (!startswith(c->signature + c->index + 1, contents)) + return -ENXIO; + + nindex = c->index + 1 + strlen(contents); + } else { + if (c->enclosing != 0) + return -ENXIO; + + /* Extend the existing signature */ + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_ARRAY), contents, NULL); + if (!e) + return -ENOMEM; + + nindex = e - c->signature; + } + + saved = m->header->body_size; + a = message_extend_body(m, 4, 4); + if (!a) { + /* Truncate extended signature again */ + if (e) + c->signature[c->index] = 0; + + return -ENOMEM; + } + b = m->body; + + if (!message_extend_body(m, alignment, 0)) { + /* Add alignment between size and first element */ + if (e) + c->signature[c->index] = 0; + + m->header->body_size = saved; + return -ENOMEM; + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index = nindex; + + /* m->body might have changed so let's readjust a */ + a = (uint8_t*) m->body + ((uint8_t*) a - (uint8_t*) b); + *(uint32_t*) a = 0; + + *array_size = a; + return 0; +} + +static int bus_message_open_variant( + sd_bus_message *m, + struct bus_container *c, + const char *contents) { + + char *e = NULL; + size_t l; + void *a; + + assert(m); + assert(c); + assert(contents); + + if (!signature_is_single(contents)) + return -EINVAL; + + if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) + return -EINVAL; + + if (c->signature && c->signature[c->index]) { + + if (c->signature[c->index] != SD_BUS_TYPE_VARIANT) + return -ENXIO; + + } else { + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_VARIANT), NULL); + if (!e) + return -ENOMEM; + } + + l = strlen(contents); + a = message_extend_body(m, 1, 1 + l + 1); + if (!a) { + /* Truncate extended signature again */ + if (e) + c->signature[c->index] = 0; + + return -ENOMEM; + } + + *(uint8_t*) a = l; + memcpy((uint8_t*) a + 1, contents, l + 1); + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 0; +} + +static int bus_message_open_struct( + sd_bus_message *m, + struct bus_container *c, + const char *contents) { + + size_t nindex; + char *e = NULL; + + assert(m); + assert(c); + assert(contents); + + if (!signature_is_valid(contents, false)) + return -EINVAL; + + if (c->signature && c->signature[c->index]) { + size_t l; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END) + return -ENXIO; + + nindex = c->index + 1 + l + 1; + } else { + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_END), NULL); + if (!e) + return -ENOMEM; + + nindex = e - c->signature; + } + + /* Align contents to 8 byte boundary */ + if (!message_extend_body(m, 8, 0)) { + if (e) + c->signature[c->index] = 0; + + return -ENOMEM; + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index = nindex; + + return 0; +} + +static int bus_message_open_dict_entry( + sd_bus_message *m, + struct bus_container *c, + const char *contents) { + + size_t nindex; + + assert(m); + assert(c); + assert(contents); + + if (!signature_is_pair(contents)) + return -EINVAL; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (c->signature && c->signature[c->index]) { + size_t l; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END) + return -ENXIO; + + nindex = c->index + 1 + l + 1; + } else + return -ENXIO; + + /* Align contents to 8 byte boundary */ + if (!message_extend_body(m, 8, 0)) + return -ENOMEM; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index = nindex; + + return 0; +} + +int sd_bus_message_open_container( + sd_bus_message *m, + char type, + const char *contents) { + + struct bus_container *c, *w; + uint32_t *array_size = NULL; + char *signature; + int r; + + if (!m) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (!contents) + return -EINVAL; + + /* Make sure we have space for one more container */ + w = realloc(m->containers, sizeof(struct bus_container) * (m->n_containers + 1)); + if (!w) + return -ENOMEM; + m->containers = w; + + c = message_get_container(m); + + signature = strdup(contents); + if (!signature) + return -ENOMEM; + + if (type == SD_BUS_TYPE_ARRAY) + r = bus_message_open_array(m, c, contents, &array_size); + else if (type == SD_BUS_TYPE_VARIANT) + r = bus_message_open_variant(m, c, contents); + else if (type == SD_BUS_TYPE_STRUCT) + r = bus_message_open_struct(m, c, contents); + else if (type == SD_BUS_TYPE_DICT_ENTRY) + r = bus_message_open_dict_entry(m, c, contents); + else + r = -EINVAL; + + if (r < 0) { + free(signature); + return r; + } + + /* OK, let's fill it in */ + w += m->n_containers++; + w->enclosing = type; + w->signature = signature; + w->index = 0; + w->array_size = array_size; + w->begin = 0; + + return 0; +} + +int sd_bus_message_close_container(sd_bus_message *m) { + struct bus_container *c; + + if (!m) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (m->n_containers <= 0) + return -EINVAL; + + c = message_get_container(m); + if (c->enclosing != SD_BUS_TYPE_ARRAY) + if (c->signature && c->signature[c->index] != 0) + return -EINVAL; + + free(c->signature); + m->n_containers--; + + return 0; +} + + +typedef struct { + const char *types; + unsigned n_struct; + unsigned n_array; +} TypeStack; + +static int type_stack_push(TypeStack *stack, unsigned max, unsigned *i, const char *types, unsigned n_struct, unsigned n_array) { + assert(stack); + assert(max > 0); + + if (*i >= max) + return -EINVAL; + + stack[*i].types = types; + stack[*i].n_struct = n_struct; + stack[*i].n_array = n_array; + (*i)++; + + return 0; +} + +static int type_stack_pop(TypeStack *stack, unsigned max, unsigned *i, const char **types, unsigned *n_struct, unsigned *n_array) { + assert(stack); + assert(max > 0); + assert(types); + assert(n_struct); + assert(n_array); + + if (*i <= 0) + return 0; + + (*i)--; + *types = stack[*i].types; + *n_struct = stack[*i].n_struct; + *n_array = stack[*i].n_array; + + return 1; +} + +int bus_message_append_ap( + sd_bus_message *m, + const char *types, + va_list ap) { + + unsigned n_array, n_struct; + TypeStack stack[BUS_CONTAINER_DEPTH]; + unsigned stack_ptr = 0; + int r; + + assert(m); + + if (!types) + return 0; + + n_array = (unsigned) -1; + n_struct = strlen(types); + + for (;;) { + const char *t; + + if (n_array == 0 || (n_array == (unsigned) -1 && n_struct == 0)) { + r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array); + if (r < 0) + return r; + if (r == 0) + break; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + continue; + } + + t = types; + if (n_array != (unsigned) -1) + n_array --; + else { + types ++; + n_struct--; + } + + switch (*t) { + + case SD_BUS_TYPE_BYTE: { + uint8_t x; + + x = (uint8_t) va_arg(ap, int); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: { + uint32_t x; + + /* We assume a boolean is the same as int32_t */ + assert_cc(sizeof(int32_t) == sizeof(int)); + + x = va_arg(ap, uint32_t); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: { + uint16_t x; + + x = (uint16_t) va_arg(ap, int); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: { + uint64_t x; + + x = va_arg(ap, uint64_t); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: { + const char *x; + + x = va_arg(ap, const char*); + r = sd_bus_message_append_basic(m, *t, x); + break; + } + + case SD_BUS_TYPE_ARRAY: { + size_t k; + + r = signature_element_length(t + 1, &k); + if (r < 0) + return r; + + { + char s[k + 1]; + memcpy(s, t + 1, k); + s[k] = 0; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); + if (r < 0) + return r; + } + + if (n_array == (unsigned) -1) { + types += k; + n_struct -= k; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k; + n_array = va_arg(ap, unsigned); + + break; + } + + case SD_BUS_TYPE_VARIANT: { + const char *s; + + s = va_arg(ap, const char*); + if (!s) + return -EINVAL; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, s); + if (r < 0) + return r; + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = s; + n_struct = strlen(s); + n_array = (unsigned) -1; + + break; + } + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + r = signature_element_length(t, &k); + if (r < 0) + return r; + + { + char s[k - 1]; + + memcpy(s, t + 1, k - 2); + s[k - 2] = 0; + + r = sd_bus_message_open_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); + if (r < 0) + return r; + } + + if (n_array == (unsigned) -1) { + types += k - 1; + n_struct -= k - 1; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k - 2; + n_array = (unsigned) -1; + + break; + } + + default: + r = -EINVAL; + } + + if (r < 0) + return r; + } + + return 0; +} + +int sd_bus_message_append(sd_bus_message *m, const char *types, ...) { + va_list ap; + int r; + + if (!m) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (!types) + return 0; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + + return r; +} + +static int buffer_peek(const void *p, uint32_t sz, size_t *rindex, size_t align, size_t nbytes, void **r) { + size_t k, start, n; + + assert(rindex); + assert(align > 0); + + start = ALIGN_TO((size_t) *rindex, align); + n = start + nbytes; + + if (n > sz) + return -EBADMSG; + + /* Verify that padding is 0 */ + for (k = *rindex; k < start; k++) + if (((const uint8_t*) p)[k] != 0) + return -EBADMSG; + + if (r) + *r = (uint8_t*) p + start; + + *rindex = n; + + return 1; +} + +static bool message_end_of_array(sd_bus_message *m, size_t index) { + struct bus_container *c; + + assert(m); + + c = message_get_container(m); + if (!c->array_size) + return false; + + return index >= c->begin + BUS_MESSAGE_BSWAP32(m, *c->array_size); +} + +static int message_peek_body(sd_bus_message *m, size_t *rindex, size_t align, size_t nbytes, void **ret) { + assert(m); + assert(rindex); + assert(align > 0); + + if (message_end_of_array(m, *rindex)) + return 0; + + return buffer_peek(m->body, BUS_MESSAGE_BODY_SIZE(m), rindex, align, nbytes, ret); +} + +static bool validate_nul(const char *s, size_t l) { + + /* Check for NUL chars in the string */ + if (memchr(s, 0, l)) + return false; + + /* Check for NUL termination */ + if (s[l] != 0) + return false; + + return true; +} + +static bool validate_string(const char *s, size_t l) { + + if (!validate_nul(s, l)) + return false; + + /* Check if valid UTF8 */ + if (!utf8_is_valid(s)) + return false; + + return true; +} + +static bool validate_signature(const char *s, size_t l) { + + if (!validate_nul(s, l)) + return false; + + /* Check if valid signature */ + if (!signature_is_valid(s, true)) + return false; + + return true; +} + +static bool validate_object_path(const char *s, size_t l) { + + if (!validate_nul(s, l)) + return false; + + if (!object_path_is_valid(s)) + return false; + + return true; +} + +int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p) { + struct bus_container *c; + int r; + void *q; + + if (!m) + return -EINVAL; + if (!m->sealed) + return -EPERM; + if (!bus_type_is_basic(type)) + return -EINVAL; + if (!p) + return -EINVAL; + + c = message_get_container(m); + + if (!c->signature || c->signature[c->index] == 0) + return 0; + + if (c->signature[c->index] != type) + return -ENXIO; + + switch (type) { + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: { + uint32_t l; + size_t rindex; + + rindex = m->rindex; + r = message_peek_body(m, &rindex, 4, 4, &q); + if (r <= 0) + return r; + + l = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + if (type == SD_BUS_TYPE_OBJECT_PATH) { + if (!validate_object_path(q, l)) + return -EBADMSG; + } else { + if (!validate_string(q, l)) + return -EBADMSG; + } + + m->rindex = rindex; + *(const char**) p = q; + break; + } + + case SD_BUS_TYPE_SIGNATURE: { + uint8_t l; + size_t rindex; + + rindex = m->rindex; + r = message_peek_body(m, &rindex, 1, 1, &q); + if (r <= 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + if (!validate_signature(q, l)) + return -EBADMSG; + + m->rindex = rindex; + *(const char**) p = q; + break; + } + + default: { + ssize_t sz, align; + size_t rindex; + + align = bus_type_get_alignment(type); + sz = bus_type_get_size(type); + assert(align > 0 && sz > 0); + + rindex = m->rindex; + r = message_peek_body(m, &rindex, align, sz, &q); + if (r <= 0) + return r; + + switch (type) { + + case SD_BUS_TYPE_BYTE: + *(uint8_t*) p = *(uint8_t*) q; + break; + + case SD_BUS_TYPE_BOOLEAN: + *(int*) p = !!*(uint32_t*) q; + break; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + *(uint16_t*) p = BUS_MESSAGE_BSWAP16(m, *(uint16_t*) q); + break; + + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + *(uint32_t*) p = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + break; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + *(uint64_t*) p = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); + break; + + case SD_BUS_TYPE_UNIX_FD: { + uint32_t j; + + j = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + if (j >= m->n_fds) + return -EBADMSG; + + *(int*) p = m->fds[j]; + break; + } + + default: + assert_not_reached("Unknown basic type..."); + } + + m->rindex = rindex; + + break; + } + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 1; +} + +static int bus_message_enter_array( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + uint32_t **array_size) { + + size_t rindex; + void *q; + int r, alignment; + + assert(m); + assert(c); + assert(contents); + assert(array_size); + + if (!signature_is_single(contents)) + return -EINVAL; + + alignment = bus_type_get_alignment(contents[0]); + if (alignment < 0) + return alignment; + + if (!c->signature || c->signature[c->index] == 0) + return 0; + + if (c->signature[c->index] != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (!startswith(c->signature + c->index + 1, contents)) + return -ENXIO; + + rindex = m->rindex; + r = message_peek_body(m, &rindex, 4, 4, &q); + if (r <= 0) + return r; + + if (BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q) > BUS_ARRAY_MAX_SIZE) + return -EBADMSG; + + r = message_peek_body(m, &rindex, alignment, 0, NULL); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index += 1 + strlen(contents); + + m->rindex = rindex; + + *array_size = (uint32_t*) q; + + return 1; +} + +static int bus_message_enter_variant( + sd_bus_message *m, + struct bus_container *c, + const char *contents) { + + size_t rindex; + uint8_t l; + void *q; + int r; + + assert(m); + assert(c); + assert(contents); + + if (!signature_is_single(contents)) + return -EINVAL; + + if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) + return -EINVAL; + + if (!c->signature || c->signature[c->index] == 0) + return 0; + + if (c->signature[c->index] != SD_BUS_TYPE_VARIANT) + return -ENXIO; + + rindex = m->rindex; + r = message_peek_body(m, &rindex, 1, 1, &q); + if (r <= 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + if (!validate_signature(q, l)) + return -EBADMSG; + + if (!streq(q, contents)) + return -ENXIO; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + m->rindex = rindex; + + return 1; +} + +static int bus_message_enter_struct( + sd_bus_message *m, + struct bus_container *c, + const char *contents) { + + size_t l; + int r; + + assert(m); + assert(c); + assert(contents); + + if (!signature_is_valid(contents, false)) + return -EINVAL; + + if (!c->signature || c->signature[c->index] == 0) + return 0; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END) + return -ENXIO; + + r = message_peek_body(m, &m->rindex, 8, 0, NULL); + if (r <= 0) + return r; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index += 1 + l + 1; + + return 1; +} + +static int bus_message_enter_dict_entry( + sd_bus_message *m, + struct bus_container *c, + const char *contents) { + + size_t l; + int r; + + assert(m); + assert(c); + assert(contents); + + if (!signature_is_pair(contents)) + return -EINVAL; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (!c->signature || c->signature[c->index] == 0) + return 0; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END) + return -ENXIO; + + r = message_peek_body(m, &m->rindex, 8, 0, NULL); + if (r <= 0) + return r; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index += 1 + l + 1; + + return 1; +} + +int sd_bus_message_enter_container(sd_bus_message *m, char type, const char *contents) { + struct bus_container *c, *w; + uint32_t *array_size = NULL; + char *signature; + int r; + + if (!m) + return -EINVAL; + if (!m->sealed) + return -EPERM; + if (!contents) + return -EINVAL; + + /* + * We enforce a global limit on container depth, that is much + * higher than the 32 structs and 32 arrays the specification + * mandates. This is simpler to implement for us, and we need + * this only to ensure our container array doesn't grow + * without bounds. We are happy to return any data from a + * message as long as the data itself is valid, even if the + * overall message might be not. + * + * Note that the message signature is validated when + * parsing the headers, and that validation does check the + * 32/32 limit. + * + * Note that the specification defines no limits on the depth + * of stacked variants, but we do. + */ + if (m->n_containers >= BUS_CONTAINER_DEPTH) + return -EBADMSG; + + w = realloc(m->containers, sizeof(struct bus_container) * (m->n_containers + 1)); + if (!w) + return -ENOMEM; + m->containers = w; + + c = message_get_container(m); + + if (!c->signature || c->signature[c->index] == 0) + return 0; + + signature = strdup(contents); + if (!signature) + return -ENOMEM; + + if (type == SD_BUS_TYPE_ARRAY) + r = bus_message_enter_array(m, c, contents, &array_size); + else if (type == SD_BUS_TYPE_VARIANT) + r = bus_message_enter_variant(m, c, contents); + else if (type == SD_BUS_TYPE_STRUCT) + r = bus_message_enter_struct(m, c, contents); + else if (type == SD_BUS_TYPE_DICT_ENTRY) + r = bus_message_enter_dict_entry(m, c, contents); + else + r = -EINVAL; + + if (r <= 0) { + free(signature); + return r; + } + + /* OK, let's fill it in */ + w += m->n_containers++; + w->enclosing = type; + w->signature = signature; + w->index = 0; + w->array_size = array_size; + w->begin = m->rindex; + + return 1; +} + +int sd_bus_message_exit_container(sd_bus_message *m) { + struct bus_container *c; + + if (!m) + return -EINVAL; + if (!m->sealed) + return -EPERM; + if (m->n_containers <= 0) + return -EINVAL; + + c = message_get_container(m); + if (c->enclosing == SD_BUS_TYPE_ARRAY) { + uint32_t l; + + l = BUS_MESSAGE_BSWAP32(m, *c->array_size); + if (c->begin + l != m->rindex) + return -EBUSY; + + } else { + if (c->signature && c->signature[c->index] != 0) + return -EINVAL; + } + + free(c->signature); + m->n_containers--; + + return 1; +} + +int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents) { + struct bus_container *c; + int r; + + if (!m) + return -EINVAL; + if (!m->sealed) + return -EPERM; + + c = message_get_container(m); + + if (!c->signature || c->signature[c->index] == 0) + goto eof; + + if (message_end_of_array(m, m->rindex)) + goto eof; + + if (bus_type_is_basic(c->signature[c->index])) { + if (contents) + *contents = NULL; + if (type) + *type = c->signature[c->index]; + return 1; + } + + if (c->signature[c->index] == SD_BUS_TYPE_ARRAY) { + + if (contents) { + size_t l; + char *sig; + + r = signature_element_length(c->signature+c->index+1, &l); + if (r < 0) + return r; + + assert(l >= 1); + + sig = strndup(c->signature + c->index + 1, l); + if (!sig) + return -ENOMEM; + + free(m->peeked_signature); + m->peeked_signature = sig; + + *contents = sig; + } + + if (type) + *type = SD_BUS_TYPE_ARRAY; + + return 1; + } + + if (c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN || + c->signature[c->index] == SD_BUS_TYPE_DICT_ENTRY_BEGIN) { + + if (contents) { + size_t l; + char *sig; + + r = signature_element_length(c->signature+c->index, &l); + if (r < 0) + return r; + + assert(l >= 2); + sig = strndup(c->signature + c->index + 1, l - 2); + if (!sig) + return -ENOMEM; + + free(m->peeked_signature); + m->peeked_signature = sig; + + *contents = sig; + } + + if (type) + *type = c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY; + + return 1; + } + + if (c->signature[c->index] == SD_BUS_TYPE_VARIANT) { + if (contents) { + size_t rindex, l; + void *q; + + rindex = m->rindex; + r = message_peek_body(m, &rindex, 1, 1, &q); + if (r < 0) + return r; + if (r == 0) + goto eof; + + l = *(uint8_t*) q; + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + if (!validate_signature(q, l)) + return -EBADMSG; + + *contents = q; + } + + if (type) + *type = SD_BUS_TYPE_VARIANT; + + return 1; + } + + return -EINVAL; + +eof: + if (type) + *type = c->enclosing; + if (contents) + *contents = NULL; + return 0; +} + +int sd_bus_message_rewind(sd_bus_message *m, int complete) { + struct bus_container *c; + + if (!m) + return -EINVAL; + if (!m->sealed) + return -EPERM; + + if (complete) { + reset_containers(m); + m->rindex = 0; + m->root_container.index = 0; + + c = message_get_container(m); + } else { + c = message_get_container(m); + + c->index = 0; + m->rindex = c->begin; + } + + return !isempty(c->signature); +} +static int message_read_ap( + sd_bus_message *m, + const char *types, + va_list ap) { + + unsigned n_array, n_struct; + TypeStack stack[BUS_CONTAINER_DEPTH]; + unsigned stack_ptr = 0; + int r; + + assert(m); + + if (!types) + return 0; + + /* Ideally, we'd just call ourselves recursively on every + * complex type. However, the state of a va_list that is + * passed to a function is undefined after that function + * returns. This means we need to docode the va_list linearly + * in a single stackframe. We hence implement our own + * home-grown stack in an array. */ + + n_array = (unsigned) -1; + n_struct = strlen(types); + + for (;;) { + const char *t; + + if (n_array == 0 || (n_array == (unsigned) -1 && n_struct == 0)) { + r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array); + if (r < 0) + return r; + if (r == 0) + break; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + continue; + } + + t = types; + if (n_array != (unsigned) -1) + n_array --; + else { + types ++; + n_struct--; + } + + switch (*t) { + + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_UNIX_FD: { + void *p; + + p = va_arg(ap, void*); + r = sd_bus_message_read_basic(m, *t, p); + if (r < 0) + return r; + if (r == 0) + return -ENXIO; + + break; + } + + case SD_BUS_TYPE_ARRAY: { + size_t k; + + r = signature_element_length(t + 1, &k); + if (r < 0) + return r; + + { + char s[k + 1]; + memcpy(s, t + 1, k); + s[k] = 0; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s); + if (r < 0) + return r; + if (r == 0) + return -ENXIO; + } + + if (n_array == (unsigned) -1) { + types += k; + n_struct -= k; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k; + n_array = va_arg(ap, unsigned); + + break; + } + + case SD_BUS_TYPE_VARIANT: { + const char *s; + + s = va_arg(ap, const char *); + if (!s) + return -EINVAL; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, s); + if (r < 0) + return r; + if (r == 0) + return -ENXIO; + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = s; + n_struct = strlen(s); + n_array = (unsigned) -1; + + break; + } + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + r = signature_element_length(t, &k); + if (r < 0) + return r; + + { + char s[k - 1]; + memcpy(s, t + 1, k - 2); + s[k - 2] = 0; + + r = sd_bus_message_enter_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); + if (r < 0) + return r; + if (r == 0) + return -ENXIO; + } + + if (n_array == (unsigned) -1) { + types += k - 1; + n_struct -= k - 1; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k - 2; + n_array = (unsigned) -1; + + break; + } + + default: + return -EINVAL; + } + } + + return 1; +} + +int sd_bus_message_read(sd_bus_message *m, const char *types, ...) { + va_list ap; + int r; + + if (!m) + return -EINVAL; + if (!m->sealed) + return -EPERM; + if (!types) + return -EINVAL; + + va_start(ap, types); + r = message_read_ap(m, types, ap); + va_end(ap); + + return r; +} + +static int message_peek_fields( + sd_bus_message *m, + size_t *rindex, + size_t align, + size_t nbytes, + void **ret) { + + assert(m); + assert(rindex); + assert(align > 0); + + return buffer_peek(m->fields, BUS_MESSAGE_FIELDS_SIZE(m), rindex, align, nbytes, ret); +} + +static int message_peek_field_uint32( + sd_bus_message *m, + size_t *ri, + uint32_t *ret) { + + int r; + void *q; + + assert(m); + assert(ri); + + r = message_peek_fields(m, ri, 4, 4, &q); + if (r < 0) + return r; + + if (ret) + *ret = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + + return 0; +} + +static int message_peek_field_string( + sd_bus_message *m, + bool (*validate)(const char *p), + size_t *ri, + const char **ret) { + + uint32_t l; + int r; + void *q; + + assert(m); + assert(ri); + + r = message_peek_field_uint32(m, ri, &l); + if (r < 0) + return r; + + r = message_peek_fields(m, ri, 1, l+1, &q); + if (r < 0) + return r; + + if (validate) { + if (!validate_nul(q, l)) + return -EBADMSG; + + if (!validate(q)) + return -EBADMSG; + } else { + if (!validate_string(q, l)) + return -EBADMSG; + } + + if (ret) + *ret = q; + + return 0; +} + +static int message_peek_field_signature( + sd_bus_message *m, + size_t *ri, + const char **ret) { + + size_t l; + int r; + void *q; + + assert(m); + assert(ri); + + r = message_peek_fields(m, ri, 1, 1, &q); + if (r < 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_fields(m, ri, 1, l+1, &q); + if (r < 0) + return r; + + if (!validate_signature(q, l)) + return -EBADMSG; + + if (ret) + *ret = q; + + return 0; +} + +static int message_skip_fields( + sd_bus_message *m, + size_t *ri, + uint32_t array_size, + const char **signature) { + + size_t original_index; + int r; + + assert(m); + assert(ri); + assert(signature); + + original_index = *ri; + + for (;;) { + char t; + size_t l; + + if (array_size != (uint32_t) -1 && + array_size <= *ri - original_index) + return 0; + + t = **signature; + if (!t) + return 0; + + if (t == SD_BUS_TYPE_STRING) { + + r = message_peek_field_string(m, NULL, ri, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_OBJECT_PATH) { + + r = message_peek_field_string(m, object_path_is_valid, ri, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_SIGNATURE) { + + r = message_peek_field_signature(m, ri, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (bus_type_is_basic(t)) { + ssize_t align, k; + + align = bus_type_get_alignment(t); + k = bus_type_get_size(t); + assert(align > 0 && k > 0); + + r = message_peek_fields(m, ri, align, k, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_ARRAY) { + + r = signature_element_length(*signature+1, &l); + if (r < 0) + return r; + + assert(l >= 1); + { + char sig[l-1], *s; + uint32_t nas; + int alignment; + + strncpy(sig, *signature + 1, l-1); + s = sig; + + alignment = bus_type_get_alignment(sig[0]); + if (alignment < 0) + return alignment; + + r = message_peek_field_uint32(m, ri, &nas); + if (r < 0) + return r; + if (nas > BUS_ARRAY_MAX_SIZE) + return -EBADMSG; + + r = message_peek_fields(m, ri, alignment, 0, NULL); + if (r < 0) + return r; + + r = message_skip_fields(m, ri, nas, (const char**) &s); + if (r < 0) + return r; + } + + (*signature) += 1 + l; + + } else if (t == SD_BUS_TYPE_VARIANT) { + const char *s; + + r = message_peek_field_signature(m, ri, &s); + if (r < 0) + return r; + + r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_STRUCT || + t == SD_BUS_TYPE_DICT_ENTRY) { + + r = signature_element_length(*signature, &l); + if (r < 0) + return r; + + assert(l >= 2); + { + char sig[l-1], *s; + strncpy(sig, *signature + 1, l-1); + s = sig; + + r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s); + if (r < 0) + return r; + } + + *signature += l; + } else + return -EINVAL; + } +} + +int bus_message_parse_fields(sd_bus_message *m) { + size_t ri; + int r; + uint32_t unix_fds = 0; + + assert(m); + + for (ri = 0; ri < BUS_MESSAGE_FIELDS_SIZE(m); ) { + const char *signature; + uint8_t *header; + + r = message_peek_fields(m, &ri, 8, 1, (void**) &header); + if (r < 0) + return r; + + r = message_peek_field_signature(m, &ri, &signature); + if (r < 0) + return r; + + switch (*header) { + case _SD_BUS_MESSAGE_HEADER_INVALID: + return -EBADMSG; + + case SD_BUS_MESSAGE_HEADER_PATH: + + if (m->path) + return -EBADMSG; + + if (!streq(signature, "o")) + return -EBADMSG; + + r = message_peek_field_string(m, object_path_is_valid, &ri, &m->path); + break; + + case SD_BUS_MESSAGE_HEADER_INTERFACE: + + if (m->interface) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, interface_name_is_valid, &ri, &m->interface); + break; + + case SD_BUS_MESSAGE_HEADER_MEMBER: + + if (m->member) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, member_name_is_valid, &ri, &m->member); + break; + + case SD_BUS_MESSAGE_HEADER_ERROR_NAME: + + if (m->error.name) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, error_name_is_valid, &ri, &m->error.name); + break; + + case SD_BUS_MESSAGE_HEADER_DESTINATION: + + if (m->destination) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, service_name_is_valid, &ri, &m->destination); + break; + + case SD_BUS_MESSAGE_HEADER_SENDER: + + if (m->sender) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, service_name_is_valid, &ri, &m->sender); + break; + + + case SD_BUS_MESSAGE_HEADER_SIGNATURE: { + const char *s; + char *c; + + if (m->root_container.signature) + return -EBADMSG; + + if (!streq(signature, "g")) + return -EBADMSG; + + r = message_peek_field_signature(m, &ri, &s); + if (r < 0) + return r; + + c = strdup(s); + if (!c) + return -ENOMEM; + + free(m->root_container.signature); + m->root_container.signature = c; + break; + } + + case SD_BUS_MESSAGE_HEADER_REPLY_SERIAL: + if (m->reply_serial != 0) + return -EBADMSG; + + if (!streq(signature, "u")) + return -EBADMSG; + + r = message_peek_field_uint32(m, &ri, &m->reply_serial); + if (r < 0) + return r; + + if (m->reply_serial == 0) + return -EBADMSG; + + break; + + case SD_BUS_MESSAGE_HEADER_UNIX_FDS: + if (unix_fds != 0) + return -EBADMSG; + + if (!streq(signature, "u")) + return -EBADMSG; + + r = message_peek_field_uint32(m, &ri, &unix_fds); + if (r < 0) + return -EBADMSG; + + if (unix_fds == 0) + return -EBADMSG; + + break; + + default: + r = message_skip_fields(m, &ri, (uint32_t) -1, (const char **) &signature); + } + + if (r < 0) + return r; + } + + if (m->n_fds != unix_fds) + return -EBADMSG; + + if (isempty(m->root_container.signature) != (BUS_MESSAGE_BODY_SIZE(m) == 0)) + return -EBADMSG; + + switch (m->header->type) { + + case SD_BUS_MESSAGE_TYPE_SIGNAL: + if (!m->path || !m->interface || !m->member) + return -EBADMSG; + break; + + case SD_BUS_MESSAGE_TYPE_METHOD_CALL: + + if (!m->path || !m->member) + return -EBADMSG; + + break; + + case SD_BUS_MESSAGE_TYPE_METHOD_RETURN: + + if (m->reply_serial == 0) + return -EBADMSG; + break; + + case SD_BUS_MESSAGE_TYPE_METHOD_ERROR: + + if (m->reply_serial == 0 || !m->error.name) + return -EBADMSG; + break; + } + + /* Try to read the error message, but if we can't it's a non-issue */ + if (m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_ERROR) + sd_bus_message_read(m, "s", &m->error.message); + + return 0; +} + +int bus_message_seal(sd_bus_message *m, uint64_t serial) { + int r; + size_t l, a; + + assert(m); + + if (m->sealed) + return -EPERM; + + if (m->n_containers > 0) + return -EBADMSG; + + /* If there's a non-trivial signature set, then add it in here */ + if (!isempty(m->root_container.signature)) { + r = message_append_field_signature(m, SD_BUS_MESSAGE_HEADER_SIGNATURE, m->root_container.signature, NULL); + if (r < 0) + return r; + } + + if (m->n_fds > 0) { + r = message_append_field_uint32(m, SD_BUS_MESSAGE_HEADER_UNIX_FDS, m->n_fds); + if (r < 0) + return r; + } + + l = BUS_MESSAGE_FIELDS_SIZE(m); + a = ALIGN8(l) - l; + + if (a > 0) { + /* Add padding at the end, since we know the body + * needs to start at an 8 byte alignment. */ + void *p; + + p = message_extend_fields(m, 1, a); + if (!p) + return -ENOMEM; + + memset(p, 0, a); + m->header->fields_size -= a; + } + + m->header->serial = serial; + m->sealed = true; + + return 0; +} + +int sd_bus_message_set_destination(sd_bus_message *m, const char *destination) { + if (!m) + return -EINVAL; + if (!destination) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (m->destination) + return -EEXIST; + + return message_append_field_string(m, SD_BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &m->destination); +} + +int bus_message_dump(sd_bus_message *m) { + const char *u = NULL, *uu = NULL, *s = NULL; + char **cmdline = NULL; + unsigned level = 1; + int r; + uid_t owner, audit_loginuid; + uint32_t audit_sessionid; + + assert(m); + + printf("Message %p\n" + "\tn_ref=%u\n" + "\tendian=%c\n" + "\ttype=%i\n" + "\tflags=%u\n" + "\tversion=%u\n" + "\tserial=%u\n" + "\tfields_size=%u\n" + "\tbody_size=%u\n" + "\tpath=%s\n" + "\tinterface=%s\n" + "\tmember=%s\n" + "\tdestination=%s\n" + "\tsender=%s\n" + "\tsignature=%s\n" + "\treply_serial=%u\n" + "\terror.name=%s\n" + "\terror.message=%s\n" + "\tsealed=%s\n", + m, + m->n_ref, + m->header->endian, + m->header->type, + m->header->flags, + m->header->version, + BUS_MESSAGE_SERIAL(m), + BUS_MESSAGE_FIELDS_SIZE(m), + BUS_MESSAGE_BODY_SIZE(m), + strna(m->path), + strna(m->interface), + strna(m->member), + strna(m->destination), + strna(m->sender), + strna(m->root_container.signature), + m->reply_serial, + strna(m->error.name), + strna(m->error.message), + yes_no(m->sealed)); + + if (m->pid != 0) + printf("\tpid=%lu\n", (unsigned long) m->pid); + if (m->tid != 0) + printf("\ttid=%lu\n", (unsigned long) m->tid); + if (m->uid_valid) + printf("\tuid=%lu\n", (unsigned long) m->uid); + if (m->gid_valid) + printf("\tgid=%lu\n", (unsigned long) m->gid); + if (m->pid_starttime != 0) + printf("\tpid_starttime=%llu\n", (unsigned long long) m->pid_starttime); + if (m->monotonic != 0) + printf("\tmonotonic=%llu\n", (unsigned long long) m->monotonic); + if (m->realtime != 0) + printf("\trealtime=%llu\n", (unsigned long long) m->realtime); + if (m->exe) + printf("\texe=[%s]\n", m->exe); + if (m->comm) + printf("\tcomm=[%s]\n", m->comm); + if (m->tid_comm) + printf("\ttid_comm=[%s]\n", m->tid_comm); + if (m->label) + printf("\tlabel=[%s]\n", m->label); + if (m->cgroup) + printf("\tcgroup=[%s]\n", m->cgroup); + + sd_bus_message_get_unit(m, &u); + if (u) + printf("\tunit=[%s]\n", u); + sd_bus_message_get_user_unit(m, &uu); + if (uu) + printf("\tuser_unit=[%s]\n", uu); + sd_bus_message_get_session(m, &s); + if (s) + printf("\tsession=[%s]\n", s); + if (sd_bus_message_get_owner_uid(m, &owner) >= 0) + printf("\towner_uid=%lu\n", (unsigned long) owner); + if (sd_bus_message_get_audit_loginuid(m, &audit_loginuid) >= 0) + printf("\taudit_loginuid=%lu\n", (unsigned long) audit_loginuid); + if (sd_bus_message_get_audit_sessionid(m, &audit_sessionid) >= 0) + printf("\taudit_sessionid=%lu\n", (unsigned long) audit_sessionid); + + printf("\tCAP_KILL=%i\n", sd_bus_message_has_effective_cap(m, 5)); + + if (sd_bus_message_get_cmdline(m, &cmdline) >= 0) { + char **c; + + fputs("\tcmdline=[", stdout); + STRV_FOREACH(c, cmdline) { + if (c != cmdline) + putchar(' '); + + fputs(*c, stdout); + } + + fputs("]\n", stdout); + } + + r = sd_bus_message_rewind(m, true); + if (r < 0) { + log_error("Failed to rewind: %s", strerror(-r)); + return r; + } + + printf("BEGIN_MESSAGE \"%s\" {\n", strempty(m->root_container.signature)); + + for(;;) { + _cleanup_free_ char *prefix = NULL; + const char *contents = NULL; + char type; + union { + uint8_t u8; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + uint64_t u64; + int64_t s64; + double d64; + const char *string; + int i; + } basic; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) { + log_error("Failed to peek type: %s", strerror(-r)); + return r; + } + if (r == 0) { + if (level <= 1) + break; + + r = sd_bus_message_exit_container(m); + if (r < 0) { + log_error("Failed to exit container: %s", strerror(-r)); + return r; + } + + level--; + + prefix = strrep("\t", level); + if (!prefix) + return log_oom(); + + if (type == SD_BUS_TYPE_ARRAY) + printf("%s} END_ARRAY \n", prefix); + else if (type == SD_BUS_TYPE_VARIANT) + printf("%s} END_VARIANT\n", prefix); + else if (type == SD_BUS_TYPE_STRUCT) + printf("%s} END_STRUCT\n", prefix); + else if (type == SD_BUS_TYPE_DICT_ENTRY) + printf("%s} END_DICT_ENTRY\n", prefix); + + continue; + } + + prefix = strrep("\t", level); + if (!prefix) + return log_oom(); + + if (bus_type_is_container(type) > 0) { + r = sd_bus_message_enter_container(m, type, contents); + if (r < 0) { + log_error("Failed to enter container: %s", strerror(-r)); + return r; + } + + if (type == SD_BUS_TYPE_ARRAY) + printf("%sBEGIN_ARRAY \"%s\" {\n", prefix, contents); + else if (type == SD_BUS_TYPE_VARIANT) + printf("%sBEGIN_VARIANT \"%s\" {\n", prefix, contents); + else if (type == SD_BUS_TYPE_STRUCT) + printf("%sBEGIN_STRUCT \"%s\" {\n", prefix, contents); + else if (type == SD_BUS_TYPE_DICT_ENTRY) + printf("%sBEGIN_DICT_ENTRY \"%s\" {\n", prefix, contents); + + level ++; + + continue; + } + + r = sd_bus_message_read_basic(m, type, &basic); + if (r < 0) { + log_error("Failed to get basic: %s", strerror(-r)); + return r; + } + + switch (type) { + + case SD_BUS_TYPE_BYTE: + printf("%sBYTE: %u\n", prefix, basic.u8); + break; + + case SD_BUS_TYPE_BOOLEAN: + printf("%sBOOLEAN: %s\n", prefix, yes_no(basic.i)); + break; + + case SD_BUS_TYPE_INT16: + printf("%sINT16: %i\n", prefix, basic.s16); + break; + + case SD_BUS_TYPE_UINT16: + printf("%sUINT16: %u\n", prefix, basic.u16); + break; + + case SD_BUS_TYPE_INT32: + printf("%sINT32: %i\n", prefix, basic.s32); + break; + + case SD_BUS_TYPE_UINT32: + printf("%sUINT32: %u\n", prefix, basic.u32); + break; + + case SD_BUS_TYPE_INT64: + printf("%sINT64: %lli\n", prefix, (long long) basic.s64); + break; + + case SD_BUS_TYPE_UINT64: + printf("%sUINT64: %llu\n", prefix, (unsigned long long) basic.u64); + break; + + case SD_BUS_TYPE_DOUBLE: + printf("%sDOUBLE: %g\n", prefix, basic.d64); + break; + + case SD_BUS_TYPE_STRING: + printf("%sSTRING: \"%s\"\n", prefix, basic.string); + break; + + case SD_BUS_TYPE_OBJECT_PATH: + printf("%sOBJECT_PATH: \"%s\"\n", prefix, basic.string); + break; + + case SD_BUS_TYPE_SIGNATURE: + printf("%sSIGNATURE: \"%s\"\n", prefix, basic.string); + break; + + case SD_BUS_TYPE_UNIX_FD: + printf("%sUNIX_FD: %i\n", prefix, basic.i); + break; + + default: + assert_not_reached("Unknown basic type."); + } + } + + printf("} END_MESSAGE\n"); + return 0; +} + +int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) { + size_t total; + void *p, *e; + + assert(m); + assert(buffer); + assert(sz); + + total = BUS_MESSAGE_SIZE(m); + + p = malloc(total); + if (!p) + return -ENOMEM; + + e = mempcpy(p, m->header, sizeof(*m->header)); + + if (m->fields) { + e = mempcpy(e, m->fields, m->header->fields_size); + + if (m->header->fields_size % 8 != 0) + e = mempset(e, 0, 8 - (m->header->fields_size % 8)); + } + + if (m->body) + e = mempcpy(e, m->body, m->header->body_size); + + assert(total == (size_t) ((uint8_t*) e - (uint8_t*) p)); + + *buffer = p; + *sz = total; + + return 0; +} + +int bus_message_read_strv_extend(sd_bus_message *m, char ***l) { + int r; + + assert(m); + assert(l); + + r = sd_bus_message_enter_container(m, 'a', "s"); + if (r < 0) + return r; + + for (;;) { + const char *s; + + r = sd_bus_message_read_basic(m, 's', &s); + if (r < 0) + return r; + if (r == 0) + break; + + r = strv_extend(l, s); + if (r < 0) + return r; + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return 0; +} + +const char* bus_message_get_arg(sd_bus_message *m, unsigned i) { + int r; + const char *t = NULL; + unsigned j; + + assert(m); + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return NULL; + + for (j = 0; j <= i; j++) { + char type; + + r = sd_bus_message_peek_type(m, &type, NULL); + if (r < 0) + return NULL; + + if (type != SD_BUS_TYPE_STRING && + type != SD_BUS_TYPE_OBJECT_PATH && + type != SD_BUS_TYPE_SIGNATURE) + return NULL; + + r = sd_bus_message_read_basic(m, type, &t); + if (r < 0) + return NULL; + } + + return t; +} + +int bus_header_size(struct bus_header *h, size_t *sum) { + size_t fs, bs; + + assert(h); + assert(sum); + + if (h->endian == SD_BUS_NATIVE_ENDIAN) { + fs = h->fields_size; + bs = h->body_size; + } else if (h->endian == SD_BUS_REVERSE_ENDIAN) { + fs = bswap_32(h->fields_size); + bs = bswap_32(h->body_size); + } else + return -EBADMSG; + + *sum = sizeof(struct bus_header) + ALIGN8(fs) + bs; + return 0; +} diff --git a/src/libsystemd-bus/bus-message.h b/src/libsystemd-bus/bus-message.h new file mode 100644 index 0000000000..9c0829c7fa --- /dev/null +++ b/src/libsystemd-bus/bus-message.h @@ -0,0 +1,198 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> +#include <byteswap.h> +#include <sys/socket.h> + +#include "macro.h" +#include "sd-bus.h" +#include "kdbus.h" +#include "time-util.h" + +struct bus_container { + char enclosing; + + char *signature; + unsigned index; + + uint32_t *array_size; + size_t begin; +}; + +struct bus_header { + uint8_t endian; + uint8_t type; + uint8_t flags; + uint8_t version; + uint32_t body_size; + uint32_t serial; + uint32_t fields_size; +} _packed_; + +struct sd_bus_message { + unsigned n_ref; + + uint32_t reply_serial; + + const char *path; + const char *interface; + const char *member; + const char *destination; + const char *sender; + + sd_bus_error error; + + uid_t uid; + gid_t gid; + pid_t pid; + pid_t tid; + usec_t pid_starttime; + usec_t monotonic; + usec_t realtime; + + bool sealed:1; + bool dont_send:1; + bool allow_fds:1; + bool uid_valid:1; + bool gid_valid:1; + bool free_header:1; + bool free_fields:1; + bool free_body:1; + bool free_kdbus:1; + bool free_fds:1; + + struct bus_header *header; + void *fields; + void *body; + struct kdbus_msg *kdbus; + + char *label; + + size_t rindex; + + uint32_t n_fds; + int *fds; + + struct bus_container root_container, *containers; + unsigned n_containers; + + struct iovec iovec[3]; + unsigned n_iovec; + + char *peeked_signature; + + usec_t timeout; + + char sender_buffer[3 + DECIMAL_STR_MAX(uint64_t) + 1]; + char destination_buffer[3 + DECIMAL_STR_MAX(uint64_t) + 1]; + + const char *exe; + const char *comm; + const char *tid_comm; + const char *cgroup; + + const char *cmdline; + size_t cmdline_length; + char **cmdline_array; + + char *session; + char *unit; + char *user_unit; + + struct kdbus_audit *audit; + + uint8_t *capability; + size_t capability_size; +}; + +#define BUS_MESSAGE_NEED_BSWAP(m) ((m)->header->endian != SD_BUS_NATIVE_ENDIAN) + +static inline uint16_t BUS_MESSAGE_BSWAP16(sd_bus_message *m, uint16_t u) { + return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_16(u) : u; +} + +static inline uint32_t BUS_MESSAGE_BSWAP32(sd_bus_message *m, uint32_t u) { + return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_32(u) : u; +} + +static inline uint64_t BUS_MESSAGE_BSWAP64(sd_bus_message *m, uint64_t u) { + return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_64(u) : u; +} + +static inline uint32_t BUS_MESSAGE_SERIAL(sd_bus_message *m) { + return BUS_MESSAGE_BSWAP32(m, m->header->serial); +} + +static inline uint32_t BUS_MESSAGE_BODY_SIZE(sd_bus_message *m) { + return BUS_MESSAGE_BSWAP32(m, m->header->body_size); +} + +static inline uint32_t BUS_MESSAGE_FIELDS_SIZE(sd_bus_message *m) { + return BUS_MESSAGE_BSWAP32(m, m->header->fields_size); +} + +static inline uint32_t BUS_MESSAGE_SIZE(sd_bus_message *m) { + return + sizeof(struct bus_header) + + ALIGN8(BUS_MESSAGE_FIELDS_SIZE(m)) + + BUS_MESSAGE_BODY_SIZE(m); +} + +static inline void bus_message_unrefp(sd_bus_message **m) { + sd_bus_message_unref(*m); +} + +#define _cleanup_bus_message_unref_ __attribute__((cleanup(bus_message_unrefp))) + +int bus_message_seal(sd_bus_message *m, uint64_t serial); +int bus_message_dump(sd_bus_message *m); +int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz); +int bus_message_read_strv_extend(sd_bus_message *m, char ***l); + +int bus_message_from_header( + void *header, + size_t length, + int *fds, + unsigned n_fds, + const struct ucred *ucred, + const char *label, + size_t extra, + sd_bus_message **ret); + +int bus_message_from_malloc( + void *buffer, + size_t length, + int *fds, + unsigned n_fds, + const struct ucred *ucred, + const char *label, + sd_bus_message **ret); + +const char* bus_message_get_arg(sd_bus_message *m, unsigned i); + +int bus_message_append_ap(sd_bus_message *m, const char *types, va_list ap); + +int bus_message_parse_fields(sd_bus_message *m); + +int bus_header_size(struct bus_header *h, size_t *sum); diff --git a/src/libsystemd-bus/bus-signature.c b/src/libsystemd-bus/bus-signature.c new file mode 100644 index 0000000000..a92b7124c3 --- /dev/null +++ b/src/libsystemd-bus/bus-signature.c @@ -0,0 +1,153 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <util.h> + +#include "bus-signature.h" +#include "bus-type.h" + +static int signature_element_length_internal( + const char *s, + bool allow_dict_entry, + unsigned array_depth, + unsigned struct_depth, + size_t *l) { + + int r; + + assert(s); + + if (bus_type_is_basic(*s) || *s == SD_BUS_TYPE_VARIANT) { + *l = 1; + return 0; + } + + if (*s == SD_BUS_TYPE_ARRAY) { + size_t t; + + if (array_depth >= 32) + return -EINVAL; + + r = signature_element_length_internal(s + 1, true, array_depth+1, struct_depth, &t); + if (r < 0) + return r; + + *l = t + 1; + return 0; + } + + if (*s == SD_BUS_TYPE_STRUCT_BEGIN) { + const char *p = s + 1; + + if (struct_depth >= 32) + return -EINVAL; + + while (*p != SD_BUS_TYPE_STRUCT_END) { + size_t t; + + r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t); + if (r < 0) + return r; + + p += t; + } + + *l = p - s + 1; + return 0; + } + + if (*s == SD_BUS_TYPE_DICT_ENTRY_BEGIN && allow_dict_entry) { + const char *p = s + 1; + unsigned n = 0; + + if (struct_depth >= 32) + return -EINVAL; + + while (*p != SD_BUS_TYPE_DICT_ENTRY_END) { + size_t t; + + if (n == 0 && !bus_type_is_basic(*p)) + return -EINVAL; + + r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t); + if (r < 0) + return r; + + p += t; + n++; + } + + if (n != 2) + return -EINVAL; + + *l = p - s + 1; + return 0; + } + + return -EINVAL; +} + + +int signature_element_length(const char *s, size_t *l) { + return signature_element_length_internal(s, true, 0, 0, l); +} + +bool signature_is_single(const char *s) { + int r; + size_t t; + + assert(s); + + r = signature_element_length(s, &t); + if (r < 0) + return false; + + return s[t] == 0; +} + +bool signature_is_pair(const char *s) { + assert(s); + + if (!bus_type_is_basic(*s)) + return false; + + return signature_is_single(s + 1); +} + +bool signature_is_valid(const char *s, bool allow_dict_entry) { + const char *p; + int r; + + assert(s); + + p = s; + while (*p) { + size_t t; + + r = signature_element_length_internal(p, allow_dict_entry, 0, 0, &t); + if (r < 0) + return false; + + p += t; + } + + return p - s <= 255; +} diff --git a/src/libsystemd-bus/bus-signature.h b/src/libsystemd-bus/bus-signature.h new file mode 100644 index 0000000000..4000e0612a --- /dev/null +++ b/src/libsystemd-bus/bus-signature.h @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> +#include <sys/types.h> + +bool signature_is_single(const char *s); +bool signature_is_pair(const char *s); +bool signature_is_valid(const char *s, bool allow_dict_entry); + +int signature_element_length(const char *s, size_t *l); diff --git a/src/libsystemd-bus/bus-socket.c b/src/libsystemd-bus/bus-socket.c new file mode 100644 index 0000000000..8a86b02c68 --- /dev/null +++ b/src/libsystemd-bus/bus-socket.c @@ -0,0 +1,1059 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <endian.h> +#include <assert.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/poll.h> +#include <byteswap.h> + +#include "util.h" +#include "macro.h" +#include "missing.h" +#include "strv.h" +#include "utf8.h" +#include "sd-daemon.h" + +#include "sd-bus.h" +#include "bus-socket.h" +#include "bus-internal.h" +#include "bus-message.h" + +static void iovec_advance(struct iovec iov[], unsigned *idx, size_t size) { + + while (size > 0) { + struct iovec *i = iov + *idx; + + if (i->iov_len > size) { + i->iov_base = (uint8_t*) i->iov_base + size; + i->iov_len -= size; + return; + } + + size -= i->iov_len; + + i->iov_base = NULL; + i->iov_len = 0; + + (*idx) ++; + } +} + +static void append_iovec(sd_bus_message *m, const void *p, size_t sz) { + assert(m); + assert(p); + assert(sz > 0); + + m->iovec[m->n_iovec].iov_base = (void*) p; + m->iovec[m->n_iovec].iov_len = sz; + m->n_iovec++; +} + +static void bus_message_setup_iovec(sd_bus_message *m) { + assert(m); + assert(m->sealed); + + if (m->n_iovec > 0) + return; + + append_iovec(m, m->header, sizeof(*m->header)); + + if (m->fields) + append_iovec(m, m->fields, ALIGN8(m->header->fields_size)); + + if (m->body) + append_iovec(m, m->body, m->header->body_size); +} + +bool bus_socket_auth_needs_write(sd_bus *b) { + + unsigned i; + + if (b->auth_index >= ELEMENTSOF(b->auth_iovec)) + return false; + + for (i = b->auth_index; i < ELEMENTSOF(b->auth_iovec); i++) { + struct iovec *j = b->auth_iovec + i; + + if (j->iov_len > 0) + return true; + } + + return false; +} + +static int bus_socket_write_auth(sd_bus *b) { + ssize_t k; + + assert(b); + assert(b->state == BUS_AUTHENTICATING); + + if (!bus_socket_auth_needs_write(b)) + return 0; + + if (b->prefer_writev) + k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); + else { + struct msghdr mh; + zero(mh); + + mh.msg_iov = b->auth_iovec + b->auth_index; + mh.msg_iovlen = ELEMENTSOF(b->auth_iovec) - b->auth_index; + + k = sendmsg(b->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0 && errno == ENOTSOCK) { + b->prefer_writev = true; + k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); + } + } + + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + + iovec_advance(b->auth_iovec, &b->auth_index, (size_t) k); + return 1; +} + +static int bus_socket_auth_verify_client(sd_bus *b) { + char *e, *f, *start; + sd_id128_t peer; + unsigned i; + int r; + + assert(b); + + /* We expect two response lines: "OK" and possibly + * "AGREE_UNIX_FD" */ + + e = memmem(b->rbuffer, b->rbuffer_size, "\r\n", 2); + if (!e) + return 0; + + if (b->negotiate_fds) { + f = memmem(e + 2, b->rbuffer_size - (e - (char*) b->rbuffer) - 2, "\r\n", 2); + if (!f) + return 0; + + start = f + 2; + } else { + f = NULL; + start = e + 2; + } + + /* Nice! We got all the lines we need. First check the OK + * line */ + + if (e - (char*) b->rbuffer != 3 + 32) + return -EPERM; + + if (memcmp(b->rbuffer, "OK ", 3)) + return -EPERM; + + b->auth = b->anonymous_auth ? BUS_AUTH_ANONYMOUS : BUS_AUTH_EXTERNAL; + + for (i = 0; i < 32; i += 2) { + int x, y; + + x = unhexchar(((char*) b->rbuffer)[3 + i]); + y = unhexchar(((char*) b->rbuffer)[3 + i + 1]); + + if (x < 0 || y < 0) + return -EINVAL; + + peer.bytes[i/2] = ((uint8_t) x << 4 | (uint8_t) y); + } + + if (!sd_id128_equal(b->server_id, SD_ID128_NULL) && + !sd_id128_equal(b->server_id, peer)) + return -EPERM; + + b->server_id = peer; + + /* And possibly check the second line, too */ + + if (f) + b->can_fds = + (f - e == sizeof("\r\nAGREE_UNIX_FD") - 1) && + memcmp(e + 2, "AGREE_UNIX_FD", sizeof("AGREE_UNIX_FD") - 1) == 0; + + b->rbuffer_size -= (start - (char*) b->rbuffer); + memmove(b->rbuffer, start, b->rbuffer_size); + + r = bus_start_running(b); + if (r < 0) + return r; + + return 1; +} + +static bool line_equals(const char *s, size_t m, const char *line) { + size_t l; + + l = strlen(line); + if (l != m) + return false; + + return memcmp(s, line, l) == 0; +} + +static bool line_begins(const char *s, size_t m, const char *word) { + size_t l; + + l = strlen(word); + if (m < l) + return false; + + if (memcmp(s, word, l) != 0) + return false; + + return m == l || (m > l && s[l] == ' '); +} + +static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) { + _cleanup_free_ char *token = NULL; + + if (!b->anonymous_auth) + return 0; + + if (l <= 0) + return 1; + + assert(p[0] == ' '); + p++; l--; + + if (l % 2 != 0) + return 0; + token = unhexmem(p, l); + if (!token) + return -ENOMEM; + + if (memchr(token, 0, l/2)) + return 0; + + return !!utf8_is_valid(token); +} + +static int verify_external_token(sd_bus *b, const char *p, size_t l) { + _cleanup_free_ char *token = NULL; + uid_t u; + int r; + + /* We don't do any real authentication here. Instead, we if + * the owner of this bus wanted authentication he should have + * checked SO_PEERCRED before even creating the bus object. */ + + if (!b->anonymous_auth && !b->ucred_valid) + return 0; + + if (l <= 0) + return 1; + + assert(p[0] == ' '); + p++; l--; + + if (l % 2 != 0) + return 0; + + token = unhexmem(p, l); + if (!token) + return -ENOMEM; + + if (memchr(token, 0, l/2)) + return 0; + + r = parse_uid(token, &u); + if (r < 0) + return 0; + + /* We ignore the passed value if anonymous authentication is + * on anyway. */ + if (!b->anonymous_auth && u != b->ucred.uid) + return 0; + + return 1; +} + +static int bus_socket_auth_write(sd_bus *b, const char *t) { + char *p; + size_t l; + + assert(b); + assert(t); + + /* We only make use of the first iovec */ + assert(b->auth_index == 0 || b->auth_index == 1); + + l = strlen(t); + p = malloc(b->auth_iovec[0].iov_len + l); + if (!p) + return -ENOMEM; + + memcpy(p, b->auth_iovec[0].iov_base, b->auth_iovec[0].iov_len); + memcpy(p + b->auth_iovec[0].iov_len, t, l); + + b->auth_iovec[0].iov_base = p; + b->auth_iovec[0].iov_len += l; + + free(b->auth_buffer); + b->auth_buffer = p; + b->auth_index = 0; + return 0; +} + +static int bus_socket_auth_write_ok(sd_bus *b) { + char t[3 + 32 + 2 + 1]; + + assert(b); + + snprintf(t, sizeof(t), "OK " SD_ID128_FORMAT_STR "\r\n", SD_ID128_FORMAT_VAL(b->server_id)); + char_array_0(t); + + return bus_socket_auth_write(b, t); +} + +static int bus_socket_auth_verify_server(sd_bus *b) { + char *e; + const char *line; + size_t l; + bool processed = false; + int r; + + assert(b); + + if (b->rbuffer_size < 1) + return 0; + + /* First char must be a NUL byte */ + if (*(char*) b->rbuffer != 0) + return -EIO; + + if (b->rbuffer_size < 3) + return 0; + + /* Begin with the first line */ + if (b->auth_rbegin <= 0) + b->auth_rbegin = 1; + + for (;;) { + /* Check if line is complete */ + line = (char*) b->rbuffer + b->auth_rbegin; + e = memmem(line, b->rbuffer_size - b->auth_rbegin, "\r\n", 2); + if (!e) + return processed; + + l = e - line; + + if (line_begins(line, l, "AUTH ANONYMOUS")) { + + r = verify_anonymous_token(b, line + 14, l - 14); + if (r < 0) + return r; + if (r == 0) + r = bus_socket_auth_write(b, "REJECTED\r\n"); + else { + b->auth = BUS_AUTH_ANONYMOUS; + r = bus_socket_auth_write_ok(b); + } + + } else if (line_begins(line, l, "AUTH EXTERNAL")) { + + r = verify_external_token(b, line + 13, l - 13); + if (r < 0) + return r; + if (r == 0) + r = bus_socket_auth_write(b, "REJECTED\r\n"); + else { + b->auth = BUS_AUTH_EXTERNAL; + r = bus_socket_auth_write_ok(b); + } + + } else if (line_begins(line, l, "AUTH")) + r = bus_socket_auth_write(b, "REJECTED EXTERNAL ANONYMOUS\r\n"); + else if (line_equals(line, l, "CANCEL") || + line_begins(line, l, "ERROR")) { + + b->auth = _BUS_AUTH_INVALID; + r = bus_socket_auth_write(b, "REJECTED\r\n"); + + } else if (line_equals(line, l, "BEGIN")) { + + if (b->auth == _BUS_AUTH_INVALID) + r = bus_socket_auth_write(b, "ERROR\r\n"); + else { + /* We can't leave from the auth phase + * before we haven't written + * everything queued, so let's check + * that */ + + if (bus_socket_auth_needs_write(b)) + return 1; + + b->rbuffer_size -= (e + 2 - (char*) b->rbuffer); + memmove(b->rbuffer, e + 2, b->rbuffer_size); + return bus_start_running(b); + } + + } else if (line_begins(line, l, "DATA")) { + + if (b->auth == _BUS_AUTH_INVALID) + r = bus_socket_auth_write(b, "ERROR\r\n"); + else { + if (b->auth == BUS_AUTH_ANONYMOUS) + r = verify_anonymous_token(b, line + 4, l - 4); + else + r = verify_external_token(b, line + 4, l - 4); + + if (r < 0) + return r; + if (r == 0) { + b->auth = _BUS_AUTH_INVALID; + r = bus_socket_auth_write(b, "REJECTED\r\n"); + } else + r = bus_socket_auth_write_ok(b); + } + } else if (line_equals(line, l, "NEGOTIATE_UNIX_FD")) { + if (b->auth == _BUS_AUTH_INVALID || !b->negotiate_fds) + r = bus_socket_auth_write(b, "ERROR\r\n"); + else { + b->can_fds = true; + r = bus_socket_auth_write(b, "AGREE_UNIX_FD\r\n"); + } + } else + r = bus_socket_auth_write(b, "ERROR\r\n"); + + if (r < 0) + return r; + + b->auth_rbegin = e + 2 - (char*) b->rbuffer; + + processed = true; + } +} + +static int bus_socket_auth_verify(sd_bus *b) { + assert(b); + + if (b->is_server) + return bus_socket_auth_verify_server(b); + else + return bus_socket_auth_verify_client(b); +} + +static int bus_socket_read_auth(sd_bus *b) { + struct msghdr mh; + struct iovec iov; + size_t n; + ssize_t k; + int r; + void *p; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX) + + CMSG_SPACE(sizeof(struct ucred)) + + CMSG_SPACE(NAME_MAX)]; /*selinux label */ + } control; + struct cmsghdr *cmsg; + bool handle_cmsg = false; + + assert(b); + assert(b->state == BUS_AUTHENTICATING); + + r = bus_socket_auth_verify(b); + if (r != 0) + return r; + + n = MAX(256u, b->rbuffer_size * 2); + + if (n > BUS_AUTH_SIZE_MAX) + n = BUS_AUTH_SIZE_MAX; + + if (b->rbuffer_size >= n) + return -ENOBUFS; + + p = realloc(b->rbuffer, n); + if (!p) + return -ENOMEM; + + b->rbuffer = p; + + zero(iov); + iov.iov_base = (uint8_t*) b->rbuffer + b->rbuffer_size; + iov.iov_len = n - b->rbuffer_size; + + if (b->prefer_readv) + k = readv(b->input_fd, &iov, 1); + else { + zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + k = recvmsg(b->input_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (k < 0 && errno == ENOTSOCK) { + b->prefer_readv = true; + k = readv(b->input_fd, &iov, 1); + } else + handle_cmsg = true; + } + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + if (k == 0) + return -ECONNRESET; + + b->rbuffer_size += k; + + if (handle_cmsg) { + for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + int j; + + /* Whut? We received fds during the auth + * protocol? Somebody is playing games with + * us. Close them all, and fail */ + j = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + close_many((int*) CMSG_DATA(cmsg), j); + return -EIO; + + } else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { + + /* Ignore bogus data, which we might + * get on socketpair() sockets */ + if (((struct ucred*) CMSG_DATA(cmsg))->pid != 0) { + memcpy(&b->ucred, CMSG_DATA(cmsg), sizeof(struct ucred)); + b->ucred_valid = true; + } + + } else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_SECURITY) { + + size_t l; + + l = cmsg->cmsg_len - CMSG_LEN(0); + if (l > 0) { + memcpy(&b->label, CMSG_DATA(cmsg), l); + b->label[l] = 0; + } + } + } + } + + r = bus_socket_auth_verify(b); + if (r != 0) + return r; + + return 1; +} + +static int bus_socket_setup(sd_bus *b) { + int enable; + socklen_t l; + + assert(b); + + /* Enable SO_PASSCRED + SO_PASSEC. We try this on any + * socket, just in case. */ + enable = !b->bus_client; + setsockopt(b->input_fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable)); + setsockopt(b->input_fd, SOL_SOCKET, SO_PASSSEC, &enable, sizeof(enable)); + + /* Increase the buffers to a MB */ + fd_inc_rcvbuf(b->input_fd, 1024*1024); + fd_inc_sndbuf(b->output_fd, 1024*1024); + + /* Get the peer for socketpair() sockets */ + l = sizeof(b->ucred); + if (getsockopt(b->input_fd, SOL_SOCKET, SO_PEERCRED, &b->ucred, &l) >= 0 && l >= sizeof(b->ucred)) + b->ucred_valid = b->ucred.pid > 0; + + return 0; +} + +static int bus_socket_start_auth_client(sd_bus *b) { + size_t l; + const char *auth_suffix, *auth_prefix; + + assert(b); + + if (b->anonymous_auth) { + auth_prefix = "\0AUTH ANONYMOUS "; + + /* For ANONYMOUS auth we send some arbitrary "trace" string */ + l = 9; + b->auth_buffer = hexmem("anonymous", l); + } else { + char text[20 + 1]; /* enough space for a 64bit integer plus NUL */ + + auth_prefix = "\0AUTH EXTERNAL "; + + snprintf(text, sizeof(text), "%lu", (unsigned long) geteuid()); + char_array_0(text); + + l = strlen(text); + b->auth_buffer = hexmem(text, l); + } + + if (!b->auth_buffer) + return -ENOMEM; + + if (b->negotiate_fds) + auth_suffix = "\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n"; + else + auth_suffix = "\r\nBEGIN\r\n"; + + b->auth_iovec[0].iov_base = (void*) auth_prefix; + b->auth_iovec[0].iov_len = 1 + strlen(auth_prefix + 1); + b->auth_iovec[1].iov_base = (void*) b->auth_buffer; + b->auth_iovec[1].iov_len = l * 2; + b->auth_iovec[2].iov_base = (void*) auth_suffix; + b->auth_iovec[2].iov_len = strlen(auth_suffix); + + return bus_socket_write_auth(b); +} + +static int bus_socket_start_auth(sd_bus *b) { + assert(b); + + b->state = BUS_AUTHENTICATING; + b->auth_timeout = now(CLOCK_MONOTONIC) + BUS_DEFAULT_TIMEOUT; + + if (sd_is_socket(b->input_fd, AF_UNIX, 0, 0) <= 0) + b->negotiate_fds = false; + + if (b->output_fd != b->input_fd) + if (sd_is_socket(b->output_fd, AF_UNIX, 0, 0) <= 0) + b->negotiate_fds = false; + + if (b->is_server) + return bus_socket_read_auth(b); + else + return bus_socket_start_auth_client(b); +} + +int bus_socket_connect(sd_bus *b) { + int r; + + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->sockaddr.sa.sa_family != AF_UNSPEC); + + b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (b->input_fd < 0) + return -errno; + + b->output_fd = b->input_fd; + + r = bus_socket_setup(b); + if (r < 0) + return r; + + r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size); + if (r < 0) { + if (errno == EINPROGRESS) + return 1; + + return -errno; + } + + return bus_socket_start_auth(b); +} + +int bus_socket_exec(sd_bus *b) { + int s[2], r; + pid_t pid; + + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->exec_path); + + r = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, s); + if (r < 0) + return -errno; + + pid = fork(); + if (pid < 0) { + close_pipe(s); + return -errno; + } + if (pid == 0) { + /* Child */ + + reset_all_signal_handlers(); + + close_all_fds(s+1, 1); + + assert_se(dup3(s[1], STDIN_FILENO, 0) == STDIN_FILENO); + assert_se(dup3(s[1], STDOUT_FILENO, 0) == STDOUT_FILENO); + + if (s[1] != STDIN_FILENO && s[1] != STDOUT_FILENO) + close_nointr_nofail(s[1]); + + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_nonblock(STDIN_FILENO, false); + fd_nonblock(STDOUT_FILENO, false); + + if (b->exec_argv) + execvp(b->exec_path, b->exec_argv); + else { + const char *argv[] = { b->exec_path, NULL }; + execvp(b->exec_path, (char**) argv); + } + + _exit(EXIT_FAILURE); + } + + close_nointr_nofail(s[1]); + b->output_fd = b->input_fd = s[0]; + + return bus_socket_start_auth(b); +} + +int bus_socket_take_fd(sd_bus *b) { + int r; + assert(b); + + r = bus_socket_setup(b); + if (r < 0) + return r; + + return bus_socket_start_auth(b); +} + +int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { + struct iovec *iov; + ssize_t k; + size_t n; + unsigned j; + + assert(bus); + assert(m); + assert(idx); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + if (*idx >= BUS_MESSAGE_SIZE(m)) + return 0; + + bus_message_setup_iovec(m); + + n = m->n_iovec * sizeof(struct iovec); + iov = alloca(n); + memcpy(iov, m->iovec, n); + + j = 0; + iovec_advance(iov, &j, *idx); + + if (bus->prefer_writev) + k = writev(bus->output_fd, iov, m->n_iovec); + else { + struct msghdr mh; + zero(mh); + + if (m->n_fds > 0) { + struct cmsghdr *control; + control = alloca(CMSG_SPACE(sizeof(int) * m->n_fds)); + + mh.msg_control = control; + control->cmsg_level = SOL_SOCKET; + control->cmsg_type = SCM_RIGHTS; + mh.msg_controllen = control->cmsg_len = CMSG_LEN(sizeof(int) * m->n_fds); + memcpy(CMSG_DATA(control), m->fds, sizeof(int) * m->n_fds); + } + + mh.msg_iov = iov; + mh.msg_iovlen = m->n_iovec; + + k = sendmsg(bus->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0 && errno == ENOTSOCK) { + bus->prefer_writev = true; + k = writev(bus->output_fd, iov, m->n_iovec); + } + } + + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + + *idx += (size_t) k; + return 1; +} + +static int bus_socket_read_message_need(sd_bus *bus, size_t *need) { + uint32_t a, b; + uint8_t e; + uint64_t sum; + + assert(bus); + assert(need); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + if (bus->rbuffer_size < sizeof(struct bus_header)) { + *need = sizeof(struct bus_header) + 8; + + /* Minimum message size: + * + * Header + + * + * Method Call: +2 string headers + * Signal: +3 string headers + * Method Error: +1 string headers + * +1 uint32 headers + * Method Reply: +1 uint32 headers + * + * A string header is at least 9 bytes + * A uint32 header is at least 8 bytes + * + * Hence the minimum message size of a valid message + * is header + 8 bytes */ + + return 0; + } + + a = ((const uint32_t*) bus->rbuffer)[1]; + b = ((const uint32_t*) bus->rbuffer)[3]; + + e = ((const uint8_t*) bus->rbuffer)[0]; + if (e == SD_BUS_LITTLE_ENDIAN) { + a = le32toh(a); + b = le32toh(b); + } else if (e == SD_BUS_BIG_ENDIAN) { + a = be32toh(a); + b = be32toh(b); + } else + return -EBADMSG; + + sum = (uint64_t) sizeof(struct bus_header) + (uint64_t) ALIGN_TO(b, 8) + (uint64_t) a; + if (sum >= BUS_MESSAGE_SIZE_MAX) + return -ENOBUFS; + + *need = (size_t) sum; + return 0; +} + +static int bus_socket_make_message(sd_bus *bus, size_t size, sd_bus_message **m) { + sd_bus_message *t; + void *b; + int r; + + assert(bus); + assert(m); + assert(bus->rbuffer_size >= size); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + if (bus->rbuffer_size > size) { + b = memdup((const uint8_t*) bus->rbuffer + size, + bus->rbuffer_size - size); + if (!b) + return -ENOMEM; + } else + b = NULL; + + r = bus_message_from_malloc(bus->rbuffer, size, + bus->fds, bus->n_fds, + bus->ucred_valid ? &bus->ucred : NULL, + bus->label[0] ? bus->label : NULL, + &t); + if (r < 0) { + free(b); + return r; + } + + bus->rbuffer = b; + bus->rbuffer_size -= size; + + bus->fds = NULL; + bus->n_fds = 0; + + *m = t; + return 1; +} + +int bus_socket_read_message(sd_bus *bus, sd_bus_message **m) { + struct msghdr mh; + struct iovec iov; + ssize_t k; + size_t need; + int r; + void *b; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX) + + CMSG_SPACE(sizeof(struct ucred)) + + CMSG_SPACE(NAME_MAX)]; /*selinux label */ + } control; + struct cmsghdr *cmsg; + bool handle_cmsg = false; + + assert(bus); + assert(m); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + r = bus_socket_read_message_need(bus, &need); + if (r < 0) + return r; + + if (bus->rbuffer_size >= need) + return bus_socket_make_message(bus, need, m); + + b = realloc(bus->rbuffer, need); + if (!b) + return -ENOMEM; + + bus->rbuffer = b; + + zero(iov); + iov.iov_base = (uint8_t*) bus->rbuffer + bus->rbuffer_size; + iov.iov_len = need - bus->rbuffer_size; + + if (bus->prefer_readv) + k = readv(bus->input_fd, &iov, 1); + else { + zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + k = recvmsg(bus->input_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (k < 0 && errno == ENOTSOCK) { + bus->prefer_readv = true; + k = readv(bus->input_fd, &iov, 1); + } else + handle_cmsg = true; + } + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + if (k == 0) + return -ECONNRESET; + + bus->rbuffer_size += k; + + if (handle_cmsg) { + for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + int n, *f; + + n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + if (!bus->can_fds) { + /* Whut? We received fds but this + * isn't actually enabled? Close them, + * and fail */ + + close_many((int*) CMSG_DATA(cmsg), n); + return -EIO; + } + + f = realloc(bus->fds, sizeof(int) + (bus->n_fds + n)); + if (!f) { + close_many((int*) CMSG_DATA(cmsg), n); + return -ENOMEM; + } + + memcpy(f + bus->n_fds, CMSG_DATA(cmsg), n * sizeof(int)); + bus->fds = f; + bus->n_fds += n; + } else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { + + /* Ignore bogus data, which we might + * get on socketpair() sockets */ + if (((struct ucred*) CMSG_DATA(cmsg))->pid != 0) { + memcpy(&bus->ucred, CMSG_DATA(cmsg), sizeof(struct ucred)); + bus->ucred_valid = true; + } + + } else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_SECURITY) { + + size_t l; + l = cmsg->cmsg_len - CMSG_LEN(0); + if (l > 0) { + memcpy(&bus->label, CMSG_DATA(cmsg), l); + bus->label[l] = 0; + } + } + } + } + + r = bus_socket_read_message_need(bus, &need); + if (r < 0) + return r; + + if (bus->rbuffer_size >= need) + return bus_socket_make_message(bus, need, m); + + return 1; +} + +int bus_socket_process_opening(sd_bus *b) { + int error = 0; + socklen_t slen = sizeof(error); + struct pollfd p = { + .fd = b->output_fd, + .events = POLLOUT, + }; + int r; + + assert(b->state == BUS_OPENING); + + r = poll(&p, 1, 0); + if (r < 0) + return -errno; + + if (!(p.revents & (POLLOUT|POLLERR|POLLHUP))) + return 0; + + r = getsockopt(b->output_fd, SOL_SOCKET, SO_ERROR, &error, &slen); + if (r < 0) + b->last_connect_error = errno; + else if (error != 0) + b->last_connect_error = error; + else if (p.revents & (POLLERR|POLLHUP)) + b->last_connect_error = ECONNREFUSED; + else + return bus_socket_start_auth(b); + + return bus_next_address(b); +} + +int bus_socket_process_authenticating(sd_bus *b) { + int r; + + assert(b); + assert(b->state == BUS_AUTHENTICATING); + + if (now(CLOCK_MONOTONIC) >= b->auth_timeout) + return -ETIMEDOUT; + + r = bus_socket_write_auth(b); + if (r != 0) + return r; + + return bus_socket_read_auth(b); +} diff --git a/src/libsystemd-bus/bus-socket.h b/src/libsystemd-bus/bus-socket.h new file mode 100644 index 0000000000..a9c43f82df --- /dev/null +++ b/src/libsystemd-bus/bus-socket.h @@ -0,0 +1,36 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-bus.h" + +int bus_socket_connect(sd_bus *b); +int bus_socket_exec(sd_bus *b); +int bus_socket_take_fd(sd_bus *b); + +int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx); +int bus_socket_read_message(sd_bus *bus, sd_bus_message **m); + +int bus_socket_process_opening(sd_bus *b); +int bus_socket_process_authenticating(sd_bus *b); + +bool bus_socket_auth_needs_write(sd_bus *b); diff --git a/src/libsystemd-bus/bus-type.c b/src/libsystemd-bus/bus-type.c new file mode 100644 index 0000000000..0557328085 --- /dev/null +++ b/src/libsystemd-bus/bus-type.c @@ -0,0 +1,163 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" +#include "bus-type.h" + +bool bus_type_is_valid(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE, + SD_BUS_TYPE_STRING, + SD_BUS_TYPE_OBJECT_PATH, + SD_BUS_TYPE_SIGNATURE, + SD_BUS_TYPE_ARRAY, + SD_BUS_TYPE_VARIANT, + SD_BUS_TYPE_STRUCT, + SD_BUS_TYPE_DICT_ENTRY, + SD_BUS_TYPE_UNIX_FD + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_valid_in_signature(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE, + SD_BUS_TYPE_STRING, + SD_BUS_TYPE_OBJECT_PATH, + SD_BUS_TYPE_SIGNATURE, + SD_BUS_TYPE_ARRAY, + SD_BUS_TYPE_VARIANT, + SD_BUS_TYPE_STRUCT_BEGIN, + SD_BUS_TYPE_STRUCT_END, + SD_BUS_TYPE_DICT_ENTRY_BEGIN, + SD_BUS_TYPE_DICT_ENTRY_END, + SD_BUS_TYPE_UNIX_FD + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_basic(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE, + SD_BUS_TYPE_STRING, + SD_BUS_TYPE_OBJECT_PATH, + SD_BUS_TYPE_SIGNATURE, + SD_BUS_TYPE_UNIX_FD + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_container(char c) { + static const char valid[] = { + SD_BUS_TYPE_ARRAY, + SD_BUS_TYPE_VARIANT, + SD_BUS_TYPE_STRUCT, + SD_BUS_TYPE_DICT_ENTRY + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +int bus_type_get_alignment(char c) { + + switch (c) { + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_VARIANT: + return 1; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + return 2; + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_ARRAY: + case SD_BUS_TYPE_UNIX_FD: + return 4; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_STRUCT: + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: + return 8; + } + + return -EINVAL; +} + +int bus_type_get_size(char c) { + + switch (c) { + case SD_BUS_TYPE_BYTE: + return 1; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + return 2; + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: + return 4; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + return 8; + } + + return -EINVAL; +} diff --git a/src/libsystemd-bus/bus-type.h b/src/libsystemd-bus/bus-type.h new file mode 100644 index 0000000000..e261136084 --- /dev/null +++ b/src/libsystemd-bus/bus-type.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +#include "sd-bus.h" +#include "sd-bus-protocol.h" + +bool bus_type_is_valid(char c); +bool bus_type_is_valid_in_signature(char c); +bool bus_type_is_basic(char c); +bool bus_type_is_container(char c); +int bus_type_get_alignment(char c); +int bus_type_get_size(char c); diff --git a/src/libsystemd-bus/busctl.c b/src/libsystemd-bus/busctl.c new file mode 100644 index 0000000000..220c1eb34e --- /dev/null +++ b/src/libsystemd-bus/busctl.c @@ -0,0 +1,340 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <getopt.h> + +#include "strv.h" +#include "util.h" +#include "log.h" +#include "build.h" +#include "pager.h" + +#include "sd-bus.h" +#include "bus-message.h" +#include "bus-internal.h" + +static bool arg_no_pager = false; +static char *arg_address = NULL; +static bool arg_user = false; +static bool arg_no_unique = false; +static char **arg_matches = NULL; + +static void pager_open_if_enabled(void) { + + /* Cache result before we open the pager */ + if (arg_no_pager) + return; + + pager_open(false); +} + +static int list_bus_names(sd_bus *bus, char **argv) { + _cleanup_strv_free_ char **l = NULL; + char **i; + int r; + size_t max_i = 0; + + assert(bus); + + r = sd_bus_list_names(bus, &l); + if (r < 0) { + log_error("Failed to list names: %s", strerror(-r)); + return r; + } + + pager_open_if_enabled(); + + strv_sort(l); + + STRV_FOREACH(i, l) + max_i = MAX(max_i, strlen(*i)); + + printf("%-*s %*s %-*s %-*s CONNECTION\n", + (int) max_i, "NAME", 10, "PID", 15, "PROCESS", 16, "USER"); + + STRV_FOREACH(i, l) { + _cleanup_free_ char *owner = NULL; + pid_t pid; + uid_t uid; + + if (arg_no_unique && (*i)[0] == ':') + continue; + + printf("%-*s", (int) max_i, *i); + + r = sd_bus_get_owner_pid(bus, *i, &pid); + if (r >= 0) { + _cleanup_free_ char *comm = NULL; + + printf(" %10lu", (unsigned long) pid); + + get_process_comm(pid, &comm); + printf(" %-15s", strna(comm)); + } else + printf(" - - "); + + r = sd_bus_get_owner_uid(bus, *i, &uid); + if (r >= 0) { + _cleanup_free_ char *u = NULL; + + u = uid_to_name(uid); + if (!u) + return log_oom(); + + if (strlen(u) > 16) + u[16] = 0; + + printf(" %-16s", u); + } else + printf(" - "); + + r = sd_bus_get_owner(bus, *i, &owner); + if (r >= 0) + printf(" %s\n", owner); + else + printf(" -\n"); + } + + return 0; +} + +static int monitor(sd_bus *bus, char *argv[]) { + char **i; + int r; + + STRV_FOREACH(i, argv+1) { + _cleanup_free_ char *m = NULL; + + if (!service_name_is_valid(*i)) { + log_error("Invalid service name '%s'", *i); + return -EINVAL; + } + + m = strjoin("sender='", *i, "'", NULL); + if (!m) + return log_oom(); + + r = sd_bus_add_match(bus, m, NULL, NULL); + if (r < 0) { + log_error("Failed to add match: %s", strerror(-r)); + return r; + } + } + + STRV_FOREACH(i, arg_matches) { + r = sd_bus_add_match(bus, *i, NULL, NULL); + if (r < 0) { + log_error("Failed to add match: %s", strerror(-r)); + return r; + } + } + + for (;;) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + + r = sd_bus_process(bus, &m); + if (r < 0) { + log_error("Failed to process bus: %s", strerror(-r)); + return r; + } + + if (m) { + bus_message_dump(m); + continue; + } + + if (r > 0) + continue; + + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error("Failed to wait for bus: %s", strerror(-r)); + return r; + } + } + + return -EINVAL; +} + +static int help(void) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Introspect the bus.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --system Connect to system bus\n" + " --user Connect to user bus\n" + " --address=ADDRESS Connect to bus specified by address\n" + " --no-unique Only show well-known names\n" + " --match=MATCH Only show matching messages\n" + " --no-pager Do not pipe output into a pager\n\n" + "Commands:\n" + " list List bus names\n" + " monitor [SERVICE...] Show bus traffic\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_SYSTEM, + ARG_USER, + ARG_ADDRESS, + ARG_MATCH, + ARG_NO_UNIQUE + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "address", required_argument, NULL, ARG_ADDRESS }, + { "no-unique", no_argument, NULL, ARG_NO_UNIQUE }, + { "match", required_argument, NULL, ARG_MATCH }, + { NULL, 0, NULL, 0 }, + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(SYSTEMD_FEATURES); + return 0; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case ARG_ADDRESS: + arg_address = optarg; + break; + + case ARG_NO_UNIQUE: + arg_no_unique = true; + break; + + case ARG_MATCH: + if (strv_extend(&arg_matches, optarg) < 0) + return log_oom(); + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + return 1; +} + +static int busctl_main(sd_bus *bus, int argc, char *argv[]) { + assert(bus); + + if (optind >= argc || + streq(argv[optind], "list")) + return list_bus_names(bus, argv + optind); + + if (streq(argv[optind], "monitor")) + return monitor(bus, argv + optind); + + if (streq(argv[optind], "help")) + return help(); + + log_error("Unknown command '%s'", argv[optind]); + return -EINVAL; +} + +int main(int argc, char *argv[]) { + _cleanup_bus_unref_ sd_bus *bus = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_address) { + r = sd_bus_new(&bus); + if (r < 0) { + log_error("Failed to allocate bus: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_set_address(bus, arg_address); + if (r < 0) { + log_error("Failed to set address: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_set_bus_client(bus, true); + if (r < 0) { + log_error("Failed to set bus client: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_start(bus); + } else if (arg_user) + r = sd_bus_open_user(&bus); + else + r = sd_bus_open_system(&bus); + + if (r < 0) { + log_error("Failed to connect to bus: %s", strerror(-r)); + goto finish; + } + + r = busctl_main(bus, argc, argv); + +finish: + pager_close(); + strv_free(arg_matches); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/libsystemd-bus/kdbus.h b/src/libsystemd-bus/kdbus.h new file mode 100644 index 0000000000..db5e243c17 --- /dev/null +++ b/src/libsystemd-bus/kdbus.h @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2013 Kay Sievers + * Copyright (C) 2013 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013 Linux Foundation + * Copyright (C) 2013 Lennart Poettering + * Copyright (C) 2013 Daniel Mack <daniel@zonque.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef _KDBUS_H_ +#define _KDBUS_H_ + +#ifndef __KERNEL__ +#include <sys/ioctl.h> +#include <sys/types.h> +#include <linux/types.h> +#endif + +#define KDBUS_IOC_MAGIC 0x95 + +/* Message sent from kernel to userspace, when the owner or starter of + * a well-known name changes */ +struct kdbus_manager_msg_name_change { + __u64 old_id; + __u64 new_id; + __u64 flags; /* 0 or (possibly?) KDBUS_NAME_IN_QUEUE */ + char name[0]; +}; + +struct kdbus_manager_msg_id_change { + __u64 id; + __u64 flags; /* The kernel flags field from KDBUS_HELLO */ +}; + +struct kdbus_creds { + __u64 uid; + __u64 gid; + __u64 pid; + __u64 tid; + + /* The starttime of the process PID. This is useful to detect + PID overruns from the client side. i.e. if you use the PID to + look something up in /proc/$PID/ you can afterwards check the + starttime field of it to ensure you didn't run into a PID + ovretun. */ + __u64 starttime; +}; + +struct kdbus_audit { + __u64 sessionid; + __u64 loginuid; +}; + +struct kdbus_timestamp { + __u64 monotonic_ns; + __u64 realtime_ns; +}; + +#define KDBUS_SRC_ID_KERNEL (0) +#define KDBUS_DST_ID_WELL_KNOWN_NAME (0) +#define KDBUS_MATCH_SRC_ID_ANY (~0ULL) +#define KDBUS_DST_ID_BROADCAST (~0ULL) + +/* Message Item Types */ +enum { + KDBUS_MSG_NULL, + + /* Filled in by userspace */ + KDBUS_MSG_PAYLOAD, /* .data, inline memory */ + KDBUS_MSG_PAYLOAD_VEC, /* .data_vec, reference to memory area */ + KDBUS_MSG_UNIX_FDS, /* .data_fds of file descriptors */ + KDBUS_MSG_BLOOM, /* for broadcasts, carries bloom filter blob in .data */ + KDBUS_MSG_DST_NAME, /* destination's well-known name, in .str */ + + /* Filled in by kernelspace */ + KDBUS_MSG_SRC_NAMES = 0x200,/* NUL separated string list with well-known names of source */ + KDBUS_MSG_TIMESTAMP, /* .timestamp */ + KDBUS_MSG_SRC_CREDS, /* .creds */ + KDBUS_MSG_SRC_PID_COMM, /* optional, in .str */ + KDBUS_MSG_SRC_TID_COMM, /* optional, in .str */ + KDBUS_MSG_SRC_EXE, /* optional, in .str */ + KDBUS_MSG_SRC_CMDLINE, /* optional, in .str (a chain of NUL str) */ + KDBUS_MSG_SRC_CGROUP, /* optional, in .str */ + KDBUS_MSG_SRC_CAPS, /* caps data blob, in .data */ + KDBUS_MSG_SRC_SECLABEL, /* NUL terminated string, in .str */ + KDBUS_MSG_SRC_AUDIT, /* .audit */ + + /* Special messages from kernel, consisting of one and only one of these data blocks */ + KDBUS_MSG_NAME_ADD = 0x400,/* .name_change */ + KDBUS_MSG_NAME_REMOVE, /* .name_change */ + KDBUS_MSG_NAME_CHANGE, /* .name_change */ + KDBUS_MSG_ID_ADD, /* .id_change */ + KDBUS_MSG_ID_REMOVE, /* .id_change */ + KDBUS_MSG_REPLY_TIMEOUT, /* empty, but .reply_cookie in .kdbus_msg is filled in */ + KDBUS_MSG_REPLY_DEAD, /* dito */ +}; + +enum { + KDBUS_VEC_ALIGNED = 1 << 0, +}; + +struct kdbus_vec { + __u64 address; + __u64 size; + __u64 flags; +}; + +/** + * struct kdbus_item - chain of data blocks + * + * size: overall data record size + * type: kdbus_item type of data + */ +struct kdbus_item { + __u64 size; + __u64 type; + union { + /* inline data */ + __u8 data[0]; + __u32 data32[0]; + __u64 data64[0]; + char str[0]; + + /* connection */ + __u64 id; + + /* data vector */ + struct kdbus_vec vec; + + /* process credentials and properties*/ + struct kdbus_creds creds; + struct kdbus_audit audit; + struct kdbus_timestamp timestamp; + + /* specific fields */ + int fds[0]; + struct kdbus_manager_msg_name_change name_change; + struct kdbus_manager_msg_id_change id_change; + }; +}; + +enum { + KDBUS_MSG_FLAGS_EXPECT_REPLY = 1 << 0, + KDBUS_MSG_FLAGS_NO_AUTO_START = 1 << 1, +}; + +enum { + KDBUS_PAYLOAD_NULL, + KDBUS_PAYLOAD_DBUS1 = 0x4442757356657231ULL, /* 'DBusVer1' */ + KDBUS_PAYLOAD_GVARIANT = 0x4756617269616e74ULL, /* 'GVariant' */ +}; + +/** + * struct kdbus_msg + * + * set by userspace: + * dst_id: destination id + * flags: KDBUS_MSG_FLAGS_* + * items: data records + * + * set by kernel: + * src_id: who sent the message + */ +struct kdbus_msg { + __u64 size; + __u64 flags; + __u64 dst_id; /* connection, 0 == name in data, ~0 broadcast */ + __u64 src_id; /* connection, 0 == kernel */ + __u64 payload_type; /* 'DBusVer1', 'GVariant', ... */ + __u64 cookie; /* userspace-supplied cookie */ + union { + __u64 cookie_reply; /* cookie we reply to */ + __u64 timeout_ns; /* timespan to wait for reply */ + }; + struct kdbus_item items[0]; +}; + +enum { + KDBUS_POLICY_NULL, + KDBUS_POLICY_NAME, + KDBUS_POLICY_ACCESS, +}; + +enum { + KDBUS_POLICY_ACCESS_NULL, + KDBUS_POLICY_ACCESS_USER, + KDBUS_POLICY_ACCESS_GROUP, + KDBUS_POLICY_ACCESS_WORLD, +}; + +enum { + KDBUS_POLICY_RECV = 1 << 2, + KDBUS_POLICY_SEND = 1 << 1, + KDBUS_POLICY_OWN = 1 << 0, +}; + +struct kdbus_policy { + __u64 size; + __u64 type; /* NAME or ACCESS */ + union { + char name[0]; + struct { + __u32 type; /* USER, GROUP, WORLD */ + __u32 bits; /* RECV, SEND, OWN */ + __u64 id; /* uid, gid, 0 */ + } access; + }; +}; + +struct kdbus_cmd_policy { + __u64 size; + __u8 buffer[0]; /* a series of KDBUS_POLICY_NAME plus one or + * more KDBUS_POLICY_ACCESS each. */ +}; + +/* Flags for struct kdbus_cmd_hello */ +enum { + KDBUS_HELLO_STARTER = 1 << 0, + KDBUS_HELLO_ACCEPT_FD = 1 << 1, + + /* The following have an effect on directed messages only -- + * not for broadcasts */ + KDBUS_HELLO_ATTACH_COMM = 1 << 10, + KDBUS_HELLO_ATTACH_EXE = 1 << 11, + KDBUS_HELLO_ATTACH_CMDLINE = 1 << 12, + KDBUS_HELLO_ATTACH_CGROUP = 1 << 13, + KDBUS_HELLO_ATTACH_CAPS = 1 << 14, + KDBUS_HELLO_ATTACH_SECLABEL = 1 << 15, + KDBUS_HELLO_ATTACH_AUDIT = 1 << 16, +}; + +/* Items to append to struct kdbus_cmd_hello */ +enum { + KDBUS_HELLO_NULL, +}; + +struct kdbus_cmd_hello { + __u64 size; + + /* userspace → kernel, kernel → userspace */ + __u64 conn_flags; /* userspace specifies its + * capabilities and more, kernel + * returns its capabilites and + * more. Kernel might refuse client's + * capabilities by returning an error + * from KDBUS_HELLO */ + + /* kernel → userspace */ + __u64 bus_flags; /* this is .flags copied verbatim from + * from original KDBUS_CMD_BUS_MAKE + * ioctl. It's intended to be useful + * to do negotiation of features of + * the payload that is transfreted. */ + __u64 id; /* id assigned to this connection */ + __u64 bloom_size; /* The bloom filter size chosen by the + * bus owner */ + struct kdbus_item items[0]; +}; + +/* Flags for kdbus_cmd_{bus,ep,ns}_make */ +enum { + KDBUS_MAKE_ACCESS_GROUP = 1 << 0, + KDBUS_MAKE_ACCESS_WORLD = 1 << 1, + KDBUS_MAKE_POLICY_OPEN = 1 << 2, +}; + +/* Items to append to kdbus_cmd_{bus,ep,ns}_make */ +enum { + KDBUS_MAKE_NULL, + KDBUS_MAKE_NAME, + KDBUS_MAKE_CGROUP, /* the cgroup hierarchy ID for which to attach + * cgroup membership paths * to messages. */ + KDBUS_MAKE_CRED, /* allow translator services which connect + * to the bus on behalf of somebody else, + * allow specifying the credentials of the + * client to connect on behalf on. Needs + * privileges */ +}; + +struct kdbus_cmd_bus_make { + __u64 size; + __u64 flags; /* userspace → kernel, kernel → userspace + * When creating a bus feature + * kernel negotiation. */ + __u64 bus_flags; /* userspace → kernel + * When a bus is created this value is + * copied verbatim into the bus + * structure and returned from + * KDBUS_CMD_HELLO, later */ + __u64 bloom_size; /* size of the bloom filter for this bus */ + struct kdbus_item items[0]; + +}; + +struct kdbus_cmd_ep_make { + __u64 size; + __u64 flags; /* userspace → kernel, kernel → userspace + * When creating an entry point + * feature kernel negotiation done the + * same way as for + * KDBUS_CMD_BUS_MAKE. Unused for + * now. */ + struct kdbus_item items[0]; +}; + +struct kdbus_cmd_ns_make { + __u64 size; + __u64 flags; /* userspace → kernel, kernel → userspace + * When creating an entry point + * feature kernel negotiation done the + * same way as for + * KDBUS_CMD_BUS_MAKE. Unused for + * now. */ + struct kdbus_item items[0]; +}; + +enum { + /* userspace → kernel */ + KDBUS_NAME_REPLACE_EXISTING = 1 << 0, + KDBUS_NAME_QUEUE = 1 << 1, + KDBUS_NAME_ALLOW_REPLACEMENT = 1 << 2, + + /* kernel → userspace */ + KDBUS_NAME_IN_QUEUE = 1 << 16, +}; + +struct kdbus_cmd_name { + __u64 size; + __u64 name_flags; + __u64 id; /* We allow registration/deregestration of names of other peers */ + __u64 conn_flags; + char name[0]; +}; + +struct kdbus_cmd_names { + __u64 size; + struct kdbus_cmd_name names[0]; +}; + +enum { + KDBUS_NAME_INFO_ITEM_NULL, + KDBUS_NAME_INFO_ITEM_NAME, /* userspace → kernel */ + KDBUS_NAME_INFO_ITEM_SECLABEL, /* kernel → userspace */ + KDBUS_NAME_INFO_ITEM_AUDIT, /* kernel → userspace */ +}; + +struct kdbus_cmd_name_info { + __u64 size; /* overall size of info */ + __u64 flags; + __u64 id; /* either ID, or 0 and _ITEM_NAME follows */ + struct kdbus_creds creds; + struct kdbus_item items[0]; /* list of item records */ +}; + +enum { + KDBUS_MATCH_NULL, + KDBUS_MATCH_BLOOM, /* Matches a mask blob against KDBUS_MSG_BLOOM */ + KDBUS_MATCH_SRC_NAME, /* Matches a name string against KDBUS_MSG_SRC_NAMES */ + KDBUS_MATCH_NAME_ADD, /* Matches a name string against KDBUS_MSG_NAME_ADD */ + KDBUS_MATCH_NAME_REMOVE, /* Matches a name string against KDBUS_MSG_NAME_REMOVE */ + KDBUS_MATCH_NAME_CHANGE, /* Matches a name string against KDBUS_MSG_NAME_CHANGE */ + KDBUS_MATCH_ID_ADD, /* Matches an ID against KDBUS_MSG_ID_ADD */ + KDBUS_MATCH_ID_REMOVE, /* Matches an ID against KDBUS_MSG_ID_REMOVE */ +}; + +struct kdbus_cmd_match { + __u64 size; + __u64 id; /* We allow registration/deregestration of matches for other peers */ + __u64 cookie; /* userspace supplied cookie; when removing; kernel deletes everything with same cookie */ + __u64 src_id; /* ~0: any. other: exact unique match */ + struct kdbus_item items[0]; +}; + +struct kdbus_cmd_monitor { + __u64 id; /* We allow setting the monitor flag of other peers */ + unsigned int enabled; /* A boolean to enable/disable monitoring */ +}; + +/* FD states: + * control nodes: unset + * bus owner (via KDBUS_CMD_BUS_MAKE) + * ns owner (via KDBUS_CMD_NS_MAKE) + * + * ep nodes: unset + * connected (via KDBUS_CMD_HELLO) + * starter (via KDBUS_CMD_HELLO with KDBUS_CMD_HELLO_STARTER) + * ep owner (via KDBUS_CMD_EP_MAKE) + */ +enum kdbus_cmd { + /* kdbus control node commands: require unset state */ + KDBUS_CMD_BUS_MAKE = _IOWR(KDBUS_IOC_MAGIC, 0x00, struct kdbus_cmd_bus_make), + KDBUS_CMD_NS_MAKE = _IOWR(KDBUS_IOC_MAGIC, 0x10, struct kdbus_cmd_ns_make), + + /* kdbus ep node commands: require unset state */ + KDBUS_CMD_EP_MAKE = _IOWR(KDBUS_IOC_MAGIC, 0x20, struct kdbus_cmd_ep_make), + KDBUS_CMD_HELLO = _IOWR(KDBUS_IOC_MAGIC, 0x30, struct kdbus_cmd_hello), + + /* kdbus ep node commands: require connected state */ + KDBUS_CMD_MSG_SEND = _IOWR(KDBUS_IOC_MAGIC, 0x40, struct kdbus_msg), + KDBUS_CMD_MSG_RECV = _IOWR(KDBUS_IOC_MAGIC, 0x41, struct kdbus_msg), + + KDBUS_CMD_NAME_ACQUIRE = _IOWR(KDBUS_IOC_MAGIC, 0x50, struct kdbus_cmd_name), + KDBUS_CMD_NAME_RELEASE = _IOWR(KDBUS_IOC_MAGIC, 0x51, struct kdbus_cmd_name), + KDBUS_CMD_NAME_LIST = _IOWR(KDBUS_IOC_MAGIC, 0x52, struct kdbus_cmd_names), + KDBUS_CMD_NAME_QUERY = _IOWR(KDBUS_IOC_MAGIC, 0x53, struct kdbus_cmd_name_info), + + KDBUS_CMD_MATCH_ADD = _IOWR(KDBUS_IOC_MAGIC, 0x60, struct kdbus_cmd_match), + KDBUS_CMD_MATCH_REMOVE = _IOWR(KDBUS_IOC_MAGIC, 0x61, struct kdbus_cmd_match), + KDBUS_CMD_MONITOR = _IOWR(KDBUS_IOC_MAGIC, 0x62, struct kdbus_cmd_monitor), + + /* kdbus ep node commands: require ep owner state */ + KDBUS_CMD_EP_POLICY_SET = _IOWR(KDBUS_IOC_MAGIC, 0x70, struct kdbus_cmd_policy), +}; +#endif diff --git a/src/libsystemd-bus/sd-bus.c b/src/libsystemd-bus/sd-bus.c new file mode 100644 index 0000000000..7d6d848ec5 --- /dev/null +++ b/src/libsystemd-bus/sd-bus.c @@ -0,0 +1,2425 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <endian.h> +#include <assert.h> +#include <stdlib.h> +#include <unistd.h> +#include <netdb.h> +#include <sys/poll.h> +#include <byteswap.h> + +#include "util.h" +#include "macro.h" +#include "strv.h" +#include "set.h" +#include "missing.h" + +#include "sd-bus.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-type.h" +#include "bus-socket.h" +#include "bus-kernel.h" +#include "bus-control.h" + +static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec); + +static void bus_free(sd_bus *b) { + struct filter_callback *f; + struct object_callback *c; + unsigned i; + + assert(b); + + sd_bus_close(b); + + free(b->rbuffer); + free(b->unique_name); + free(b->auth_buffer); + free(b->address); + free(b->kernel); + + free(b->exec_path); + strv_free(b->exec_argv); + + close_many(b->fds, b->n_fds); + free(b->fds); + + for (i = 0; i < b->rqueue_size; i++) + sd_bus_message_unref(b->rqueue[i]); + free(b->rqueue); + + for (i = 0; i < b->wqueue_size; i++) + sd_bus_message_unref(b->wqueue[i]); + free(b->wqueue); + + hashmap_free_free(b->reply_callbacks); + prioq_free(b->reply_callbacks_prioq); + + while ((f = b->filter_callbacks)) { + LIST_REMOVE(struct filter_callback, callbacks, b->filter_callbacks, f); + free(f); + } + + while ((c = hashmap_steal_first(b->object_callbacks))) { + free(c->path); + free(c); + } + + hashmap_free(b->object_callbacks); + + bus_match_free(&b->match_callbacks); + + free(b); +} + +int sd_bus_new(sd_bus **ret) { + sd_bus *r; + + if (!ret) + return -EINVAL; + + r = new0(sd_bus, 1); + if (!r) + return -ENOMEM; + + r->n_ref = 1; + r->input_fd = r->output_fd = -1; + r->message_version = 1; + r->negotiate_fds = true; + + /* We guarantee that wqueue always has space for at least one + * entry */ + r->wqueue = new(sd_bus_message*, 1); + if (!r->wqueue) { + free(r); + return -ENOMEM; + } + + *ret = r; + return 0; +} + +int sd_bus_set_address(sd_bus *bus, const char *address) { + char *a; + + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (!address) + return -EINVAL; + + a = strdup(address); + if (!a) + return -ENOMEM; + + free(bus->address); + bus->address = a; + + return 0; +} + +int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (input_fd < 0) + return -EINVAL; + if (output_fd < 0) + return -EINVAL; + + bus->input_fd = input_fd; + bus->output_fd = output_fd; + return 0; +} + +int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]) { + char *p, **a; + + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (!path) + return -EINVAL; + if (strv_isempty(argv)) + return -EINVAL; + + p = strdup(path); + if (!p) + return -ENOMEM; + + a = strv_copy(argv); + if (!a) { + free(p); + return -ENOMEM; + } + + free(bus->exec_path); + strv_free(bus->exec_argv); + + bus->exec_path = p; + bus->exec_argv = a; + + return 0; +} + +int sd_bus_set_bus_client(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + + bus->bus_client = !!b; + return 0; +} + +int sd_bus_set_negotiate_fds(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + + bus->negotiate_fds = !!b; + return 0; +} + +int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id) { + if (!bus) + return -EINVAL; + if (!b && !sd_id128_equal(server_id, SD_ID128_NULL)) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + + bus->is_server = !!b; + bus->server_id = server_id; + return 0; +} + +int sd_bus_set_anonymous(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + + bus->anonymous_auth = !!b; + return 0; +} + +static int hello_callback(sd_bus *bus, int error, sd_bus_message *reply, void *userdata) { + const char *s; + int r; + + assert(bus); + assert(bus->state == BUS_HELLO); + + if (error != 0) + return -error; + + assert(reply); + + r = sd_bus_message_read(reply, "s", &s); + if (r < 0) + return r; + + if (!service_name_is_valid(s) || s[0] != ':') + return -EBADMSG; + + bus->unique_name = strdup(s); + if (!bus->unique_name) + return -ENOMEM; + + bus->state = BUS_RUNNING; + + return 1; +} + +static int bus_send_hello(sd_bus *bus) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + int r; + + assert(bus); + + if (!bus->bus_client || bus->is_kernel) + return 0; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "Hello", + &m); + if (r < 0) + return r; + + return sd_bus_send_with_reply(bus, m, hello_callback, NULL, 0, &bus->hello_serial); +} + +int bus_start_running(sd_bus *bus) { + assert(bus); + + if (bus->bus_client && !bus->is_kernel) { + bus->state = BUS_HELLO; + return 1; + } + + bus->state = BUS_RUNNING; + return 1; +} + +static int parse_address_key(const char **p, const char *key, char **value) { + size_t l, n = 0; + const char *a; + char *r = NULL; + + assert(p); + assert(*p); + assert(value); + + if (key) { + l = strlen(key); + if (strncmp(*p, key, l) != 0) + return 0; + + if ((*p)[l] != '=') + return 0; + + if (*value) + return -EINVAL; + + a = *p + l + 1; + } else + a = *p; + + while (*a != ';' && *a != ',' && *a != 0) { + char c, *t; + + if (*a == '%') { + int x, y; + + x = unhexchar(a[1]); + if (x < 0) { + free(r); + return x; + } + + y = unhexchar(a[2]); + if (y < 0) { + free(r); + return y; + } + + c = (char) ((x << 4) | y); + a += 3; + } else { + c = *a; + a++; + } + + t = realloc(r, n + 2); + if (!t) { + free(r); + return -ENOMEM; + } + + r = t; + r[n++] = c; + } + + if (!r) { + r = strdup(""); + if (!r) + return -ENOMEM; + } else + r[n] = 0; + + if (*a == ',') + a++; + + *p = a; + + free(*value); + *value = r; + + return 1; +} + +static void skip_address_key(const char **p) { + assert(p); + assert(*p); + + *p += strcspn(*p, ","); + + if (**p == ',') + (*p) ++; +} + +static int parse_unix_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *path = NULL, *abstract = NULL; + size_t l; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "path", &path); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "abstract", &abstract); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!path && !abstract) + return -EINVAL; + + if (path && abstract) + return -EINVAL; + + if (path) { + l = strlen(path); + if (l > sizeof(b->sockaddr.un.sun_path)) + return -E2BIG; + + b->sockaddr.un.sun_family = AF_UNIX; + strncpy(b->sockaddr.un.sun_path, path, sizeof(b->sockaddr.un.sun_path)); + b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l; + } else if (abstract) { + l = strlen(abstract); + if (l > sizeof(b->sockaddr.un.sun_path) - 1) + return -E2BIG; + + b->sockaddr.un.sun_family = AF_UNIX; + b->sockaddr.un.sun_path[0] = 0; + strncpy(b->sockaddr.un.sun_path+1, abstract, sizeof(b->sockaddr.un.sun_path)-1); + b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l; + } + + return 0; +} + +static int parse_tcp_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *host = NULL, *port = NULL, *family = NULL; + int r; + struct addrinfo *result, hints = { + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_ADDRCONFIG, + }; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "host", &host); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "port", &port); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "family", &family); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!host || !port) + return -EINVAL; + + if (family) { + if (streq(family, "ipv4")) + hints.ai_family = AF_INET; + else if (streq(family, "ipv6")) + hints.ai_family = AF_INET6; + else + return -EINVAL; + } + + r = getaddrinfo(host, port, &hints, &result); + if (r == EAI_SYSTEM) + return -errno; + else if (r != 0) + return -EADDRNOTAVAIL; + + memcpy(&b->sockaddr, result->ai_addr, result->ai_addrlen); + b->sockaddr_size = result->ai_addrlen; + + freeaddrinfo(result); + + return 0; +} + +static int parse_exec_address(sd_bus *b, const char **p, char **guid) { + char *path = NULL; + unsigned n_argv = 0, j; + char **argv = NULL; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + goto fail; + else if (r > 0) + continue; + + r = parse_address_key(p, "path", &path); + if (r < 0) + goto fail; + else if (r > 0) + continue; + + if (startswith(*p, "argv")) { + unsigned ul; + + errno = 0; + ul = strtoul(*p + 4, (char**) p, 10); + if (errno > 0 || **p != '=' || ul > 256) { + r = -EINVAL; + goto fail; + } + + (*p) ++; + + if (ul >= n_argv) { + char **x; + + x = realloc(argv, sizeof(char*) * (ul + 2)); + if (!x) { + r = -ENOMEM; + goto fail; + } + + memset(x + n_argv, 0, sizeof(char*) * (ul - n_argv + 2)); + + argv = x; + n_argv = ul + 1; + } + + r = parse_address_key(p, NULL, argv + ul); + if (r < 0) + goto fail; + + continue; + } + + skip_address_key(p); + } + + if (!path) { + r = -EINVAL; + goto fail; + } + + /* Make sure there are no holes in the array, with the + * exception of argv[0] */ + for (j = 1; j < n_argv; j++) + if (!argv[j]) { + r = -EINVAL; + goto fail; + } + + if (argv && argv[0] == NULL) { + argv[0] = strdup(path); + if (!argv[0]) { + r = -ENOMEM; + goto fail; + } + } + + b->exec_path = path; + b->exec_argv = argv; + return 0; + +fail: + for (j = 0; j < n_argv; j++) + free(argv[j]); + + free(argv); + free(path); + return r; +} + +static int parse_kernel_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *path = NULL; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "path", &path); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!path) + return -EINVAL; + + free(b->kernel); + b->kernel = path; + path = NULL; + + return 0; +} + +static void bus_reset_parsed_address(sd_bus *b) { + assert(b); + + zero(b->sockaddr); + b->sockaddr_size = 0; + strv_free(b->exec_argv); + free(b->exec_path); + b->exec_path = NULL; + b->exec_argv = NULL; + b->server_id = SD_ID128_NULL; + free(b->kernel); + b->kernel = NULL; +} + +static int bus_parse_next_address(sd_bus *b) { + _cleanup_free_ char *guid = NULL; + const char *a; + int r; + + assert(b); + + if (!b->address) + return 0; + if (b->address[b->address_index] == 0) + return 0; + + bus_reset_parsed_address(b); + + a = b->address + b->address_index; + + while (*a != 0) { + + if (*a == ';') { + a++; + continue; + } + + if (startswith(a, "unix:")) { + a += 5; + + r = parse_unix_address(b, &a, &guid); + if (r < 0) + return r; + break; + + } else if (startswith(a, "tcp:")) { + + a += 4; + r = parse_tcp_address(b, &a, &guid); + if (r < 0) + return r; + + break; + + } else if (startswith(a, "unixexec:")) { + + a += 9; + r = parse_exec_address(b, &a, &guid); + if (r < 0) + return r; + + break; + + } else if (startswith(a, "kernel:")) { + + a += 7; + r = parse_kernel_address(b, &a, &guid); + if (r < 0) + return r; + + break; + } + + a = strchr(a, ';'); + if (!a) + return 0; + } + + if (guid) { + r = sd_id128_from_string(guid, &b->server_id); + if (r < 0) + return r; + } + + b->address_index = a - b->address; + return 1; +} + +static int bus_start_address(sd_bus *b) { + int r; + + assert(b); + + for (;;) { + sd_bus_close(b); + + if (b->sockaddr.sa.sa_family != AF_UNSPEC) { + + r = bus_socket_connect(b); + if (r >= 0) + return r; + + b->last_connect_error = -r; + + } else if (b->exec_path) { + + r = bus_socket_exec(b); + if (r >= 0) + return r; + + b->last_connect_error = -r; + } else if (b->kernel) { + + r = bus_kernel_connect(b); + if (r >= 0) + return r; + + b->last_connect_error = -r; + } + + r = bus_parse_next_address(b); + if (r < 0) + return r; + if (r == 0) + return b->last_connect_error ? -b->last_connect_error : -ECONNREFUSED; + } +} + +int bus_next_address(sd_bus *b) { + assert(b); + + bus_reset_parsed_address(b); + return bus_start_address(b); +} + +static int bus_start_fd(sd_bus *b) { + struct stat st; + int r; + + assert(b); + assert(b->input_fd >= 0); + assert(b->output_fd >= 0); + + r = fd_nonblock(b->input_fd, true); + if (r < 0) + return r; + + r = fd_cloexec(b->input_fd, true); + if (r < 0) + return r; + + if (b->input_fd != b->output_fd) { + r = fd_nonblock(b->output_fd, true); + if (r < 0) + return r; + + r = fd_cloexec(b->output_fd, true); + if (r < 0) + return r; + } + + if (fstat(b->input_fd, &st) < 0) + return -errno; + + if (S_ISCHR(b->input_fd)) + return bus_kernel_take_fd(b); + else + return bus_socket_take_fd(b); +} + +int sd_bus_start(sd_bus *bus) { + int r; + + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + + bus->state = BUS_OPENING; + + if (bus->is_server && bus->bus_client) + return -EINVAL; + + if (bus->input_fd >= 0) + r = bus_start_fd(bus); + else if (bus->address || bus->sockaddr.sa.sa_family != AF_UNSPEC || bus->exec_path || bus->kernel) + r = bus_start_address(bus); + else + return -EINVAL; + + if (r < 0) + return r; + + return bus_send_hello(bus); +} + +int sd_bus_open_system(sd_bus **ret) { + const char *e; + sd_bus *b; + int r; + + if (!ret) + return -EINVAL; + + r = sd_bus_new(&b); + if (r < 0) + return r; + + e = secure_getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (e) { + r = sd_bus_set_address(b, e); + if (r < 0) + goto fail; + } else { + b->sockaddr.un.sun_family = AF_UNIX; + strncpy(b->sockaddr.un.sun_path, "/run/dbus/system_bus_socket", sizeof(b->sockaddr.un.sun_path)); + b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + sizeof("/run/dbus/system_bus_socket") - 1; + } + + b->bus_client = true; + + r = sd_bus_start(b); + if (r < 0) + goto fail; + + *ret = b; + return 0; + +fail: + bus_free(b); + return r; +} + +int sd_bus_open_user(sd_bus **ret) { + const char *e; + sd_bus *b; + size_t l; + int r; + + if (!ret) + return -EINVAL; + + r = sd_bus_new(&b); + if (r < 0) + return r; + + e = secure_getenv("DBUS_SESSION_BUS_ADDRESS"); + if (e) { + r = sd_bus_set_address(b, e); + if (r < 0) + goto fail; + } else { + e = secure_getenv("XDG_RUNTIME_DIR"); + if (!e) { + r = -ENOENT; + goto fail; + } + + l = strlen(e); + if (l + 4 > sizeof(b->sockaddr.un.sun_path)) { + r = -E2BIG; + goto fail; + } + + b->sockaddr.un.sun_family = AF_UNIX; + memcpy(mempcpy(b->sockaddr.un.sun_path, e, l), "/bus", 4); + b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l + 4; + } + + b->bus_client = true; + + r = sd_bus_start(b); + if (r < 0) + goto fail; + + *ret = b; + return 0; + +fail: + bus_free(b); + return r; +} + +void sd_bus_close(sd_bus *bus) { + if (!bus) + return; + + if (bus->input_fd >= 0) + close_nointr_nofail(bus->input_fd); + if (bus->output_fd >= 0 && bus->output_fd != bus->input_fd) + close_nointr_nofail(bus->output_fd); + + bus->input_fd = bus->output_fd = -1; +} + +sd_bus *sd_bus_ref(sd_bus *bus) { + if (!bus) + return NULL; + + assert(bus->n_ref > 0); + + bus->n_ref++; + return bus; +} + +sd_bus *sd_bus_unref(sd_bus *bus) { + if (!bus) + return NULL; + + assert(bus->n_ref > 0); + bus->n_ref--; + + if (bus->n_ref <= 0) + bus_free(bus); + + return NULL; +} + +int sd_bus_is_open(sd_bus *bus) { + if (!bus) + return -EINVAL; + + return bus->state != BUS_UNSET && bus->input_fd >= 0; +} + +int sd_bus_can_send(sd_bus *bus, char type) { + int r; + + if (!bus) + return -EINVAL; + if (bus->output_fd < 0) + return -ENOTCONN; + + if (type == SD_BUS_TYPE_UNIX_FD) { + if (!bus->negotiate_fds) + return 0; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + return bus->can_fds; + } + + return bus_type_is_valid(type); +} + +int sd_bus_get_server_id(sd_bus *bus, sd_id128_t *server_id) { + int r; + + if (!bus) + return -EINVAL; + if (!server_id) + return -EINVAL; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + *server_id = bus->server_id; + return 0; +} + +static int bus_seal_message(sd_bus *b, sd_bus_message *m) { + assert(m); + + if (m->header->version > b->message_version) + return -EPERM; + + if (m->sealed) + return 0; + + return bus_message_seal(m, ++b->serial); +} + +static int dispatch_wqueue(sd_bus *bus) { + int r, ret = 0; + + assert(bus); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + if (bus->output_fd < 0) + return -ENOTCONN; + + while (bus->wqueue_size > 0) { + + if (bus->is_kernel) + r = bus_kernel_write_message(bus, bus->wqueue[0]); + else + r = bus_socket_write_message(bus, bus->wqueue[0], &bus->windex); + + if (r < 0) { + sd_bus_close(bus); + return r; + } else if (r == 0) + /* Didn't do anything this time */ + return ret; + else if (bus->is_kernel || bus->windex >= BUS_MESSAGE_SIZE(bus->wqueue[0])) { + /* Fully written. Let's drop the entry from + * the queue. + * + * This isn't particularly optimized, but + * well, this is supposed to be our worst-case + * buffer only, and the socket buffer is + * supposed to be our primary buffer, and if + * it got full, then all bets are off + * anyway. */ + + sd_bus_message_unref(bus->wqueue[0]); + bus->wqueue_size --; + memmove(bus->wqueue, bus->wqueue + 1, sizeof(sd_bus_message*) * bus->wqueue_size); + bus->windex = 0; + + ret = 1; + } + } + + return ret; +} + +static int dispatch_rqueue(sd_bus *bus, sd_bus_message **m) { + sd_bus_message *z = NULL; + int r, ret = 0; + + assert(bus); + assert(m); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + if (bus->input_fd < 0) + return -ENOTCONN; + + if (bus->rqueue_size > 0) { + /* Dispatch a queued message */ + + *m = bus->rqueue[0]; + bus->rqueue_size --; + memmove(bus->rqueue, bus->rqueue + 1, sizeof(sd_bus_message*) * bus->rqueue_size); + return 1; + } + + /* Try to read a new message */ + do { + if (bus->is_kernel) + r = bus_kernel_read_message(bus, &z); + else + r = bus_socket_read_message(bus, &z); + + if (r < 0) { + sd_bus_close(bus); + return r; + } + if (r == 0) + return ret; + + r = 1; + } while (!z); + + *m = z; + return 1; +} + +int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *serial) { + int r; + + if (!bus) + return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; + if (bus->output_fd < 0) + return -ENOTCONN; + if (!m) + return -EINVAL; + + if (m->n_fds > 0) { + r = sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD); + if (r < 0) + return r; + if (r == 0) + return -ENOTSUP; + } + + /* If the serial number isn't kept, then we know that no reply + * is expected */ + if (!serial && !m->sealed) + m->header->flags |= SD_BUS_MESSAGE_NO_REPLY_EXPECTED; + + r = bus_seal_message(bus, m); + if (r < 0) + return r; + + /* If this is a reply and no reply was requested, then let's + * suppress this, if we can */ + if (m->dont_send && !serial) + return 0; + + if ((bus->state == BUS_RUNNING || bus->state == BUS_HELLO) && bus->wqueue_size <= 0) { + size_t idx = 0; + + if (bus->is_kernel) + r = bus_kernel_write_message(bus, m); + else + r = bus_socket_write_message(bus, m, &idx); + + if (r < 0) { + sd_bus_close(bus); + return r; + } else if (!bus->is_kernel && idx < BUS_MESSAGE_SIZE(m)) { + /* Wasn't fully written. So let's remember how + * much was written. Note that the first entry + * of the wqueue array is always allocated so + * that we always can remember how much was + * written. */ + bus->wqueue[0] = sd_bus_message_ref(m); + bus->wqueue_size = 1; + bus->windex = idx; + } + } else { + sd_bus_message **q; + + /* Just append it to the queue. */ + + if (bus->wqueue_size >= BUS_WQUEUE_MAX) + return -ENOBUFS; + + q = realloc(bus->wqueue, sizeof(sd_bus_message*) * (bus->wqueue_size + 1)); + if (!q) + return -ENOMEM; + + bus->wqueue = q; + q[bus->wqueue_size ++] = sd_bus_message_ref(m); + } + + if (serial) + *serial = BUS_MESSAGE_SERIAL(m); + + return 0; +} + +static usec_t calc_elapse(uint64_t usec) { + if (usec == (uint64_t) -1) + return 0; + + if (usec == 0) + usec = BUS_DEFAULT_TIMEOUT; + + return now(CLOCK_MONOTONIC) + usec; +} + +static int timeout_compare(const void *a, const void *b) { + const struct reply_callback *x = a, *y = b; + + if (x->timeout != 0 && y->timeout == 0) + return -1; + + if (x->timeout == 0 && y->timeout != 0) + return 1; + + if (x->timeout < y->timeout) + return -1; + + if (x->timeout > y->timeout) + return 1; + + return 0; +} + +int sd_bus_send_with_reply( + sd_bus *bus, + sd_bus_message *m, + sd_bus_message_handler_t callback, + void *userdata, + uint64_t usec, + uint64_t *serial) { + + struct reply_callback *c; + int r; + + if (!bus) + return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; + if (bus->output_fd < 0) + return -ENOTCONN; + if (!m) + return -EINVAL; + if (!callback) + return -EINVAL; + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + if (m->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) + return -EINVAL; + + r = hashmap_ensure_allocated(&bus->reply_callbacks, uint64_hash_func, uint64_compare_func); + if (r < 0) + return r; + + if (usec != (uint64_t) -1) { + r = prioq_ensure_allocated(&bus->reply_callbacks_prioq, timeout_compare); + if (r < 0) + return r; + } + + r = bus_seal_message(bus, m); + if (r < 0) + return r; + + c = new0(struct reply_callback, 1); + if (!c) + return -ENOMEM; + + c->callback = callback; + c->userdata = userdata; + c->serial = BUS_MESSAGE_SERIAL(m); + c->timeout = calc_elapse(usec); + + r = hashmap_put(bus->reply_callbacks, &c->serial, c); + if (r < 0) { + free(c); + return r; + } + + if (c->timeout != 0) { + r = prioq_put(bus->reply_callbacks_prioq, c, &c->prioq_idx); + if (r < 0) { + c->timeout = 0; + sd_bus_send_with_reply_cancel(bus, c->serial); + return r; + } + } + + r = sd_bus_send(bus, m, serial); + if (r < 0) { + sd_bus_send_with_reply_cancel(bus, c->serial); + return r; + } + + return r; +} + +int sd_bus_send_with_reply_cancel(sd_bus *bus, uint64_t serial) { + struct reply_callback *c; + + if (!bus) + return -EINVAL; + if (serial == 0) + return -EINVAL; + + c = hashmap_remove(bus->reply_callbacks, &serial); + if (!c) + return 0; + + if (c->timeout != 0) + prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); + + free(c); + return 1; +} + +int bus_ensure_running(sd_bus *bus) { + int r; + + assert(bus); + + if (bus->input_fd < 0) + return -ENOTCONN; + if (bus->state == BUS_UNSET) + return -ENOTCONN; + + if (bus->state == BUS_RUNNING) + return 1; + + for (;;) { + r = sd_bus_process(bus, NULL); + if (r < 0) + return r; + if (bus->state == BUS_RUNNING) + return 1; + if (r > 0) + continue; + + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) + return r; + } +} + +int sd_bus_send_with_reply_and_block( + sd_bus *bus, + sd_bus_message *m, + uint64_t usec, + sd_bus_error *error, + sd_bus_message **reply) { + + int r; + usec_t timeout; + uint64_t serial; + bool room = false; + + if (!bus) + return -EINVAL; + if (bus->output_fd < 0) + return -ENOTCONN; + if (bus->state == BUS_UNSET) + return -ENOTCONN; + if (!m) + return -EINVAL; + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + if (m->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) + return -EINVAL; + if (bus_error_is_dirty(error)) + return -EINVAL; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + r = sd_bus_send(bus, m, &serial); + if (r < 0) + return r; + + timeout = calc_elapse(usec); + + for (;;) { + usec_t left; + sd_bus_message *incoming = NULL; + + if (!room) { + sd_bus_message **q; + + if (bus->rqueue_size >= BUS_RQUEUE_MAX) + return -ENOBUFS; + + /* Make sure there's room for queuing this + * locally, before we read the message */ + + q = realloc(bus->rqueue, (bus->rqueue_size + 1) * sizeof(sd_bus_message*)); + if (!q) + return -ENOMEM; + + bus->rqueue = q; + room = true; + } + + if (bus->is_kernel) + r = bus_kernel_read_message(bus, &incoming); + else + r = bus_socket_read_message(bus, &incoming); + if (r < 0) + return r; + if (incoming) { + + if (incoming->reply_serial == serial) { + /* Found a match! */ + + if (incoming->header->type == SD_BUS_MESSAGE_TYPE_METHOD_RETURN) { + + if (reply) + *reply = incoming; + else + sd_bus_message_unref(incoming); + + return 0; + } + + if (incoming->header->type == SD_BUS_MESSAGE_TYPE_METHOD_ERROR) { + int k; + + r = sd_bus_error_copy(error, &incoming->error); + if (r < 0) { + sd_bus_message_unref(incoming); + return r; + } + + k = bus_error_to_errno(&incoming->error); + sd_bus_message_unref(incoming); + return k; + } + + sd_bus_message_unref(incoming); + return -EIO; + } + + /* There's already guaranteed to be room for + * this, so need to resize things here */ + bus->rqueue[bus->rqueue_size ++] = incoming; + room = false; + + /* Try to read more, right-away */ + continue; + } + if (r != 0) + continue; + + if (timeout > 0) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (n >= timeout) + return -ETIMEDOUT; + + left = timeout - n; + } else + left = (uint64_t) -1; + + r = bus_poll(bus, true, left); + if (r < 0) + return r; + + r = dispatch_wqueue(bus); + if (r < 0) + return r; + } +} + +int sd_bus_get_fd(sd_bus *bus) { + if (!bus) + return -EINVAL; + if (bus->input_fd < 0) + return -ENOTCONN; + if (bus->input_fd != bus->output_fd) + return -EPERM; + + return bus->input_fd; +} + +int sd_bus_get_events(sd_bus *bus) { + int flags = 0; + + if (!bus) + return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; + if (bus->input_fd < 0) + return -ENOTCONN; + + if (bus->state == BUS_OPENING) + flags |= POLLOUT; + else if (bus->state == BUS_AUTHENTICATING) { + + if (bus_socket_auth_needs_write(bus)) + flags |= POLLOUT; + + flags |= POLLIN; + + } else if (bus->state == BUS_RUNNING || bus->state == BUS_HELLO) { + if (bus->rqueue_size <= 0) + flags |= POLLIN; + if (bus->wqueue_size > 0) + flags |= POLLOUT; + } + + return flags; +} + +int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) { + struct reply_callback *c; + + if (!bus) + return -EINVAL; + if (!timeout_usec) + return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; + if (bus->input_fd < 0) + return -ENOTCONN; + + if (bus->state == BUS_AUTHENTICATING) { + *timeout_usec = bus->auth_timeout; + return 1; + } + + if (bus->state != BUS_RUNNING && bus->state != BUS_HELLO) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + c = prioq_peek(bus->reply_callbacks_prioq); + if (!c) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + *timeout_usec = c->timeout; + return 1; +} + +static int process_timeout(sd_bus *bus) { + struct reply_callback *c; + usec_t n; + int r; + + assert(bus); + + c = prioq_peek(bus->reply_callbacks_prioq); + if (!c) + return 0; + + n = now(CLOCK_MONOTONIC); + if (c->timeout > n) + return 0; + + assert_se(prioq_pop(bus->reply_callbacks_prioq) == c); + hashmap_remove(bus->reply_callbacks, &c->serial); + + r = c->callback(bus, ETIMEDOUT, NULL, c->userdata); + free(c); + + return r < 0 ? r : 1; +} + +static int process_hello(sd_bus *bus, sd_bus_message *m) { + assert(bus); + assert(m); + + if (bus->state != BUS_HELLO) + return 0; + + /* Let's make sure the first message on the bus is the HELLO + * reply. But note that we don't actually parse the message + * here (we leave that to the usual handling), we just verify + * we don't let any earlier msg through. */ + + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_RETURN && + m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_ERROR) + return -EIO; + + if (m->reply_serial != bus->hello_serial) + return -EIO; + + return 0; +} + +static int process_reply(sd_bus *bus, sd_bus_message *m) { + struct reply_callback *c; + int r; + + assert(bus); + assert(m); + + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_RETURN && + m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_ERROR) + return 0; + + c = hashmap_remove(bus->reply_callbacks, &m->reply_serial); + if (!c) + return 0; + + if (c->timeout != 0) + prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + r = c->callback(bus, 0, m, c->userdata); + free(c); + + return r; +} + +static int process_filter(sd_bus *bus, sd_bus_message *m) { + struct filter_callback *l; + int r; + + assert(bus); + assert(m); + + do { + bus->filter_callbacks_modified = false; + + LIST_FOREACH(callbacks, l, bus->filter_callbacks) { + + if (bus->filter_callbacks_modified) + break; + + /* Don't run this more than once per iteration */ + if (l->last_iteration == bus->iteration_counter) + continue; + + l->last_iteration = bus->iteration_counter; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + r = l->callback(bus, 0, m, l->userdata); + if (r != 0) + return r; + + } + + } while (bus->filter_callbacks_modified); + + return 0; +} + +static int process_match(sd_bus *bus, sd_bus_message *m) { + int r; + + assert(bus); + assert(m); + + do { + bus->match_callbacks_modified = false; + + r = bus_match_run(bus, &bus->match_callbacks, 0, m); + if (r != 0) + return r; + + } while (bus->match_callbacks_modified); + + return 0; +} + +static int process_builtin(sd_bus *bus, sd_bus_message *m) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + int r; + + assert(bus); + assert(m); + + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return 0; + + if (!streq_ptr(m->interface, "org.freedesktop.DBus.Peer")) + return 0; + + if (m->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) + return 1; + + if (streq_ptr(m->member, "Ping")) + r = sd_bus_message_new_method_return(bus, m, &reply); + else if (streq_ptr(m->member, "GetMachineId")) { + sd_id128_t id; + char sid[33]; + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(bus, m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", sd_id128_to_string(id, sid)); + } else { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + + sd_bus_error_set(&error, + "org.freedesktop.DBus.Error.UnknownMethod", + "Unknown method '%s' on interface '%s'.", m->member, m->interface); + + r = sd_bus_message_new_method_error(bus, m, &error, &reply); + } + + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int process_object(sd_bus *bus, sd_bus_message *m) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + struct object_callback *c; + int r; + bool found = false; + size_t pl; + + assert(bus); + assert(m); + + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return 0; + + if (hashmap_isempty(bus->object_callbacks)) + return 0; + + pl = strlen(m->path); + + do { + char p[pl+1]; + + bus->object_callbacks_modified = false; + + c = hashmap_get(bus->object_callbacks, m->path); + if (c && c->last_iteration != bus->iteration_counter) { + + c->last_iteration = bus->iteration_counter; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + r = c->callback(bus, 0, m, c->userdata); + if (r != 0) + return r; + + found = true; + } + + /* Look for fallback prefixes */ + strcpy(p, m->path); + for (;;) { + char *e; + + if (bus->object_callbacks_modified) + break; + + e = strrchr(p, '/'); + if (e == p || !e) + break; + + *e = 0; + + c = hashmap_get(bus->object_callbacks, p); + if (c && c->last_iteration != bus->iteration_counter && c->is_fallback) { + + c->last_iteration = bus->iteration_counter; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + r = c->callback(bus, 0, m, c->userdata); + if (r != 0) + return r; + + found = true; + } + } + + } while (bus->object_callbacks_modified); + + /* We found some handlers but none wanted to take this, then + * return this -- with one exception, we can handle + * introspection minimally ourselves */ + if (!found || sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) + return 0; + + sd_bus_error_set(&error, + "org.freedesktop.DBus.Error.UnknownMethod", + "Unknown method '%s' or interface '%s'.", m->member, m->interface); + + r = sd_bus_message_new_method_error(bus, m, &error, &reply); + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int process_introspect(sd_bus *bus, sd_bus_message *m) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_free_ char *introspection = NULL; + _cleanup_set_free_free_ Set *s = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct object_callback *c; + Iterator i; + size_t size = 0; + char *node; + int r; + + assert(bus); + assert(m); + + if (!sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) + return 0; + + if (!m->path) + return 0; + + s = set_new(string_hash_func, string_compare_func); + if (!s) + return -ENOMEM; + + HASHMAP_FOREACH(c, bus->object_callbacks, i) { + const char *e; + char *a, *p; + + if (streq(c->path, "/")) + continue; + + if (streq(m->path, "/")) + e = c->path; + else { + e = startswith(c->path, m->path); + if (!e || *e != '/') + continue; + } + + a = strdup(e+1); + if (!a) + return -ENOMEM; + + p = strchr(a, '/'); + if (p) + *p = 0; + + r = set_consume(s, a); + if (r < 0 && r != -EEXIST) + return r; + } + + f = open_memstream(&introspection, &size); + if (!f) + return -ENOMEM; + + fputs(SD_BUS_INTROSPECT_DOCTYPE, f); + fputs("<node>\n", f); + fputs(SD_BUS_INTROSPECT_INTERFACE_PEER, f); + fputs(SD_BUS_INTROSPECT_INTERFACE_INTROSPECTABLE, f); + + while ((node = set_steal_first(s))) { + fprintf(f, " <node name=\"%s\"/>\n", node); + free(node); + } + + fputs("</node>\n", f); + + fflush(f); + + if (ferror(f)) + return -ENOMEM; + + r = sd_bus_message_new_method_return(bus, m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", introspection); + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int process_message(sd_bus *bus, sd_bus_message *m) { + int r; + + assert(bus); + assert(m); + + bus->iteration_counter++; + + r = process_hello(bus, m); + if (r != 0) + return r; + + r = process_reply(bus, m); + if (r != 0) + return r; + + r = process_filter(bus, m); + if (r != 0) + return r; + + r = process_match(bus, m); + if (r != 0) + return r; + + r = process_builtin(bus, m); + if (r != 0) + return r; + + r = process_object(bus, m); + if (r != 0) + return r; + + return process_introspect(bus, m); +} + +static int process_running(sd_bus *bus, sd_bus_message **ret) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + int r; + + assert(bus); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + r = process_timeout(bus); + if (r != 0) + goto null_message; + + r = dispatch_wqueue(bus); + if (r != 0) + goto null_message; + + r = dispatch_rqueue(bus, &m); + if (r < 0) + return r; + if (!m) + goto null_message; + + r = process_message(bus, m); + if (r != 0) + goto null_message; + + if (ret) { + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + *ret = m; + m = NULL; + return 1; + } + + if (m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_CALL) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + + sd_bus_error_set(&error, "org.freedesktop.DBus.Error.UnknownObject", "Unknown object '%s'.", m->path); + + r = sd_bus_message_new_method_error(bus, m, &error, &reply); + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + } + + return 1; + +null_message: + if (r >= 0 && ret) + *ret = NULL; + + return r; +} + +int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { + int r; + + /* Returns 0 when we didn't do anything. This should cause the + * caller to invoke sd_bus_wait() before returning the next + * time. Returns > 0 when we did something, which possibly + * means *ret is filled in with an unprocessed message. */ + + if (!bus) + return -EINVAL; + if (bus->input_fd < 0) + return -ENOTCONN; + + /* We don't allow recursively invoking sd_bus_process(). */ + if (bus->processing) + return -EBUSY; + + switch (bus->state) { + + case BUS_UNSET: + return -ENOTCONN; + + case BUS_OPENING: + r = bus_socket_process_opening(bus); + if (r < 0) + return r; + if (ret) + *ret = NULL; + return r; + + case BUS_AUTHENTICATING: + + r = bus_socket_process_authenticating(bus); + if (r < 0) + return r; + if (ret) + *ret = NULL; + return r; + + case BUS_RUNNING: + case BUS_HELLO: + + bus->processing = true; + r = process_running(bus, ret); + bus->processing = false; + + return r; + } + + assert_not_reached("Unknown state"); +} + +static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) { + struct pollfd p[2] = {}; + int r, e, n; + struct timespec ts; + usec_t until, m; + + assert(bus); + + if (bus->input_fd < 0) + return -ENOTCONN; + + e = sd_bus_get_events(bus); + if (e < 0) + return e; + + if (need_more) + e |= POLLIN; + + r = sd_bus_get_timeout(bus, &until); + if (r < 0) + return r; + if (r == 0) + m = (uint64_t) -1; + else { + usec_t nw; + nw = now(CLOCK_MONOTONIC); + m = until > nw ? until - nw : 0; + } + + if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) + m = timeout_usec; + + p[0].fd = bus->input_fd; + if (bus->output_fd == bus->input_fd) { + p[0].events = e; + n = 1; + } else { + p[0].events = e & POLLIN; + p[1].fd = bus->output_fd; + p[1].events = e & POLLOUT; + n = 2; + } + + r = ppoll(p, n, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); + if (r < 0) + return -errno; + + return r > 0 ? 1 : 0; +} + +int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) { + + if (!bus) + return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; + if (bus->input_fd < 0) + return -ENOTCONN; + if (bus->rqueue_size > 0) + return 0; + + return bus_poll(bus, false, timeout_usec); +} + +int sd_bus_flush(sd_bus *bus) { + int r; + + if (!bus) + return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; + if (bus->output_fd < 0) + return -ENOTCONN; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + if (bus->wqueue_size <= 0) + return 0; + + for (;;) { + r = dispatch_wqueue(bus); + if (r < 0) + return r; + + if (bus->wqueue_size <= 0) + return 0; + + r = bus_poll(bus, false, (uint64_t) -1); + if (r < 0) + return r; + } +} + +int sd_bus_add_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *userdata) { + struct filter_callback *f; + + if (!bus) + return -EINVAL; + if (!callback) + return -EINVAL; + + f = new0(struct filter_callback, 1); + if (!f) + return -ENOMEM; + f->callback = callback; + f->userdata = userdata; + + bus->filter_callbacks_modified = true; + LIST_PREPEND(struct filter_callback, callbacks, bus->filter_callbacks, f); + return 0; +} + +int sd_bus_remove_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *userdata) { + struct filter_callback *f; + + if (!bus) + return -EINVAL; + if (!callback) + return -EINVAL; + + LIST_FOREACH(callbacks, f, bus->filter_callbacks) { + if (f->callback == callback && f->userdata == userdata) { + bus->filter_callbacks_modified = true; + LIST_REMOVE(struct filter_callback, callbacks, bus->filter_callbacks, f); + free(f); + return 1; + } + } + + return 0; +} + +static int bus_add_object( + sd_bus *bus, + bool fallback, + const char *path, + sd_bus_message_handler_t callback, + void *userdata) { + + struct object_callback *c; + int r; + + if (!bus) + return -EINVAL; + if (!path) + return -EINVAL; + if (!callback) + return -EINVAL; + + r = hashmap_ensure_allocated(&bus->object_callbacks, string_hash_func, string_compare_func); + if (r < 0) + return r; + + c = new0(struct object_callback, 1); + if (!c) + return -ENOMEM; + + c->path = strdup(path); + if (!c->path) { + free(c); + return -ENOMEM; + } + + c->callback = callback; + c->userdata = userdata; + c->is_fallback = fallback; + + bus->object_callbacks_modified = true; + r = hashmap_put(bus->object_callbacks, c->path, c); + if (r < 0) { + free(c->path); + free(c); + return r; + } + + return 0; +} + +static int bus_remove_object( + sd_bus *bus, + bool fallback, + const char *path, + sd_bus_message_handler_t callback, + void *userdata) { + + struct object_callback *c; + + if (!bus) + return -EINVAL; + if (!path) + return -EINVAL; + if (!callback) + return -EINVAL; + + c = hashmap_get(bus->object_callbacks, path); + if (!c) + return 0; + + if (c->callback != callback || c->userdata != userdata || c->is_fallback != fallback) + return 0; + + bus->object_callbacks_modified = true; + assert_se(c == hashmap_remove(bus->object_callbacks, c->path)); + + free(c->path); + free(c); + + return 1; +} + +int sd_bus_add_object(sd_bus *bus, const char *path, sd_bus_message_handler_t callback, void *userdata) { + return bus_add_object(bus, false, path, callback, userdata); +} + +int sd_bus_remove_object(sd_bus *bus, const char *path, sd_bus_message_handler_t callback, void *userdata) { + return bus_remove_object(bus, false, path, callback, userdata); +} + +int sd_bus_add_fallback(sd_bus *bus, const char *prefix, sd_bus_message_handler_t callback, void *userdata) { + return bus_add_object(bus, true, prefix, callback, userdata); +} + +int sd_bus_remove_fallback(sd_bus *bus, const char *prefix, sd_bus_message_handler_t callback, void *userdata) { + return bus_remove_object(bus, true, prefix, callback, userdata); +} + +int sd_bus_add_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata) { + int r = 0; + + if (!bus) + return -EINVAL; + if (!match) + return -EINVAL; + + if (bus->bus_client) { + r = bus_add_match_internal(bus, match); + if (r < 0) + return r; + } + + if (callback) { + bus->match_callbacks_modified = true; + r = bus_match_add(&bus->match_callbacks, match, callback, userdata, NULL); + if (r < 0) { + + if (bus->bus_client) + bus_remove_match_internal(bus, match); + } + } + + return r; +} + +int sd_bus_remove_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata) { + int r = 0, q = 0; + + if (!bus) + return -EINVAL; + if (!match) + return -EINVAL; + + if (bus->bus_client) + r = bus_remove_match_internal(bus, match); + + if (callback) { + bus->match_callbacks_modified = true; + q = bus_match_remove(&bus->match_callbacks, match, callback, userdata); + } + + if (r < 0) + return r; + return q; +} + +int sd_bus_emit_signal( + sd_bus *bus, + const char *path, + const char *interface, + const char *member, + const char *types, ...) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + va_list ap; + int r; + + if (!bus) + return -EINVAL; + + r = sd_bus_message_new_signal(bus, path, interface, member, &m); + if (r < 0) + return r; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +int sd_bus_call_method( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + sd_bus_message **reply, + const char *types, ...) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + va_list ap; + int r; + + if (!bus) + return -EINVAL; + + r = sd_bus_message_new_method_call(bus, destination, path, interface, member, &m); + if (r < 0) + return r; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + + return sd_bus_send_with_reply_and_block(bus, m, 0, error, reply); +} + +int sd_bus_reply_method_return( + sd_bus *bus, + sd_bus_message *call, + const char *types, ...) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + va_list ap; + int r; + + if (!bus) + return -EINVAL; + if (!call) + return -EINVAL; + if (!call->sealed) + return -EPERM; + if (call->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + + if (call->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_return(bus, call, &m); + if (r < 0) + return r; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +int sd_bus_reply_method_error( + sd_bus *bus, + sd_bus_message *call, + const sd_bus_error *e) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + int r; + + if (!bus) + return -EINVAL; + if (!call) + return -EINVAL; + if (!call->sealed) + return -EPERM; + if (call->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + if (!sd_bus_error_is_set(e)) + return -EINVAL; + + if (call->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_error(bus, call, e, &m); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} diff --git a/src/libsystemd-bus/test-bus-chat.c b/src/libsystemd-bus/test-bus-chat.c new file mode 100644 index 0000000000..f457c8f88a --- /dev/null +++ b/src/libsystemd-bus/test-bus-chat.c @@ -0,0 +1,576 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <stdlib.h> +#include <pthread.h> +#include <unistd.h> +#include <fcntl.h> + +#include "log.h" +#include "util.h" +#include "macro.h" + +#include "sd-bus.h" +#include "bus-message.h" +#include "bus-error.h" +#include "bus-match.h" +#include "bus-internal.h" + +static int match_callback(sd_bus *bus, int error, sd_bus_message *m, void *userdata) { + log_info("Match triggered! interface=%s member=%s", strna(sd_bus_message_get_interface(m)), strna(sd_bus_message_get_member(m))); + return 0; +} + +static int object_callback(sd_bus *bus, int error, sd_bus_message *m, void *userdata) { + int r; + + assert(bus); + + if (error != 0) + return 0; + + if (sd_bus_message_is_method_call(m, "org.object.test", "Foobar")) { + log_info("Invoked Foobar() on %s", sd_bus_message_get_path(m)); + + r = sd_bus_reply_method_return(bus, m, NULL); + if (r < 0) { + log_error("Failed to send reply: %s", strerror(-r)); + return r; + } + + return 1; + } + + return 0; +} + +static int server_init(sd_bus **_bus) { + sd_bus *bus = NULL; + sd_id128_t id; + int r; + const char *unique; + + assert(_bus); + + r = sd_bus_open_user(&bus); + if (r < 0) { + log_error("Failed to connect to user bus: %s", strerror(-r)); + goto fail; + } + + r = sd_bus_get_server_id(bus, &id); + if (r < 0) { + log_error("Failed to get server ID: %s", strerror(-r)); + goto fail; + } + + r = sd_bus_get_unique_name(bus, &unique); + if (r < 0) { + log_error("Failed to get unique name: %s", strerror(-r)); + goto fail; + } + + log_info("Peer ID is " SD_ID128_FORMAT_STR ".", SD_ID128_FORMAT_VAL(id)); + log_info("Unique ID: %s", unique); + log_info("Can send file handles: %i", sd_bus_can_send(bus, 'h')); + + r = sd_bus_request_name(bus, "org.freedesktop.systemd.test", 0); + if (r < 0) { + log_error("Failed to acquire name: %s", strerror(-r)); + goto fail; + } + + r = sd_bus_add_fallback(bus, "/foo/bar", object_callback, NULL); + if (r < 0) { + log_error("Failed to add object: %s", strerror(-r)); + goto fail; + } + + r = sd_bus_add_match(bus, "type='signal',interface='foo.bar',member='Notify'", match_callback, NULL); + if (r < 0) { + log_error("Failed to add match: %s", strerror(-r)); + goto fail; + } + + r = sd_bus_add_match(bus, "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged'", match_callback, NULL); + if (r < 0) { + log_error("Failed to add match: %s", strerror(-r)); + goto fail; + } + + bus_match_dump(&bus->match_callbacks, 0); + + *_bus = bus; + return 0; + +fail: + if (bus) + sd_bus_unref(bus); + + return r; +} + +static int server(sd_bus *bus) { + int r; + bool client1_gone = false, client2_gone = false; + + while (!client1_gone || !client2_gone) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + pid_t pid = 0; + const char *label = NULL; + + r = sd_bus_process(bus, &m); + if (r < 0) { + log_error("Failed to process requests: %s", strerror(-r)); + goto fail; + } + + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error("Failed to wait: %s", strerror(-r)); + goto fail; + } + + continue; + } + + if (!m) + continue; + + sd_bus_message_get_pid(m, &pid); + sd_bus_message_get_selinux_context(m, &label); + log_info("Got message! member=%s pid=%lu label=%s", + strna(sd_bus_message_get_member(m)), + (unsigned long) pid, + strna(label)); + /* bus_message_dump(m); */ + /* sd_bus_message_rewind(m, true); */ + + if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "LowerCase")) { + const char *hello; + _cleanup_free_ char *lowercase = NULL; + + r = sd_bus_message_read(m, "s", &hello); + if (r < 0) { + log_error("Failed to get parameter: %s", strerror(-r)); + goto fail; + } + + lowercase = strdup(hello); + if (!lowercase) { + r = log_oom(); + goto fail; + } + + ascii_strlower(lowercase); + + r = sd_bus_reply_method_return(bus, m, "s", lowercase); + if (r < 0) { + log_error("Failed to send reply: %s", strerror(-r)); + goto fail; + } + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient1")) { + + r = sd_bus_reply_method_return(bus, m, NULL); + if (r < 0) { + log_error("Failed to send reply: %s", strerror(-r)); + goto fail; + } + + client1_gone = true; + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient2")) { + + r = sd_bus_reply_method_return(bus, m, NULL); + if (r < 0) { + log_error("Failed to send reply: %s", strerror(-r)); + goto fail; + } + + client2_gone = true; + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Slow")) { + + sleep(1); + + r = sd_bus_reply_method_return(bus, m, NULL); + if (r < 0) { + log_error("Failed to send reply: %s", strerror(-r)); + goto fail; + } + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "FileDescriptor")) { + int fd; + static const char x = 'X'; + + r = sd_bus_message_read(m, "h", &fd); + if (r < 0) { + log_error("Failed to get parameter: %s", strerror(-r)); + goto fail; + } + + if (write(fd, &x, 1) < 0) { + log_error("Failed to write to fd: %m"); + close_nointr_nofail(fd); + goto fail; + } + + r = sd_bus_reply_method_return(bus, m, NULL); + if (r < 0) { + log_error("Failed to send reply: %s", strerror(-r)); + goto fail; + } + + } else if (sd_bus_message_is_method_call(m, NULL, NULL)) { + + r = sd_bus_reply_method_error( + bus, m, + &SD_BUS_ERROR_MAKE("org.freedesktop.DBus.Error.UnknownMethod", "Unknown method.")); + if (r < 0) { + log_error("Failed to send reply: %s", strerror(-r)); + goto fail; + } + } + } + + r = 0; + +fail: + if (bus) { + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + return r; +} + +static void* client1(void*p) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + const char *hello; + int r; + int pp[2] = { -1, -1 }; + char x; + + r = sd_bus_open_user(&bus); + if (r < 0) { + log_error("Failed to connect to user bus: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "LowerCase", + &error, + &reply, + "s", + "HELLO"); + if (r < 0) { + log_error("Failed to issue method call: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_message_read(reply, "s", &hello); + if (r < 0) { + log_error("Failed to get string: %s", strerror(-r)); + goto finish; + } + + assert(streq(hello, "hello")); + + if (pipe2(pp, O_CLOEXEC|O_NONBLOCK) < 0) { + log_error("Failed to allocate pipe: %m"); + r = -errno; + goto finish; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "FileDescriptor", + &error, + NULL, + "h", + pp[1]); + if (r < 0) { + log_error("Failed to issue method call: %s", strerror(-r)); + goto finish; + } + + errno = 0; + if (read(pp[0], &x, 1) <= 0) { + log_error("Failed to read from pipe: %s", errno ? strerror(errno) : "early read"); + goto finish; + } + + r = 0; + +finish: + if (bus) { + _cleanup_bus_message_unref_ sd_bus_message *q; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "ExitClient1", + &q); + if (r < 0) + log_error("Failed to allocate method call: %s", strerror(-r)); + else + sd_bus_send(bus, q, NULL); + + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + sd_bus_error_free(&error); + + close_pipe(pp); + + return INT_TO_PTR(r); +} + +static int quit_callback(sd_bus *b, int ret, sd_bus_message *m, void *userdata) { + bool *x = userdata; + + log_error("Quit callback: %s", strerror(ret)); + + *x = 1; + return 1; +} + +static void* client2(void*p) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + bool quit = false; + const char *mid; + int r; + + r = sd_bus_open_user(&bus); + if (r < 0) { + log_error("Failed to connect to user bus: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.systemd.test", + "/foo/bar/waldo/piep", + "org.object.test", + "Foobar", + &m); + if (r < 0) { + log_error("Failed to allocate method call: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_send(bus, m, NULL); + if (r < 0) { + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + goto finish; + } + + sd_bus_message_unref(m); + m = NULL; + + r = sd_bus_message_new_signal( + bus, + "/foobar", + "foo.bar", + "Notify", + &m); + if (r < 0) { + log_error("Failed to allocate signal: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_send(bus, m, NULL); + if (r < 0) { + log_error("Failed to issue signal: %s", bus_error_message(&error, -r)); + goto finish; + } + + sd_bus_message_unref(m); + m = NULL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.DBus.Peer", + "GetMachineId", + &m); + if (r < 0) { + log_error("Failed to allocate method call: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_send_with_reply_and_block(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + goto finish; + } + + r = sd_bus_message_read(reply, "s", &mid); + if (r < 0) { + log_error("Failed to parse machine ID: %s", strerror(-r)); + goto finish; + } + + log_info("Machine ID is %s.", mid); + + sd_bus_message_unref(m); + m = NULL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "Slow", + &m); + if (r < 0) { + log_error("Failed to allocate method call: %s", strerror(-r)); + goto finish; + } + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_send_with_reply_and_block(bus, m, 200 * USEC_PER_MSEC, &error, &reply); + if (r < 0) + log_info("Failed to issue method call: %s", bus_error_message(&error, -r)); + else + log_info("Slow call succeed."); + + sd_bus_message_unref(m); + m = NULL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "Slow", + &m); + if (r < 0) { + log_error("Failed to allocate method call: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_send_with_reply(bus, m, quit_callback, &quit, 200 * USEC_PER_MSEC, NULL); + if (r < 0) { + log_info("Failed to issue method call: %s", bus_error_message(&error, -r)); + goto finish; + } + + while (!quit) { + r = sd_bus_process(bus, NULL); + if (r < 0) { + log_error("Failed to process requests: %s", strerror(-r)); + goto finish; + } + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error("Failed to wait: %s", strerror(-r)); + goto finish; + } + } + } + + r = 0; + +finish: + if (bus) { + _cleanup_bus_message_unref_ sd_bus_message *q; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "ExitClient2", + &q); + if (r < 0) { + log_error("Failed to allocate method call: %s", strerror(-r)); + goto finish; + } + + sd_bus_send(bus, q, NULL); + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + sd_bus_error_free(&error); + return INT_TO_PTR(r); +} + +int main(int argc, char *argv[]) { + pthread_t c1, c2; + sd_bus *bus; + void *p; + int q, r; + + r = server_init(&bus); + if (r < 0) { + log_info("Failed to connect to bus, skipping tests."); + return EXIT_TEST_SKIP; + } + + log_info("Initialized..."); + + r = pthread_create(&c1, NULL, client1, bus); + if (r != 0) + return EXIT_FAILURE; + + r = pthread_create(&c2, NULL, client2, bus); + if (r != 0) + return EXIT_FAILURE; + + r = server(bus); + + q = pthread_join(c1, &p); + if (q != 0) + return EXIT_FAILURE; + if (PTR_TO_INT(p) < 0) + return EXIT_FAILURE; + + q = pthread_join(c2, &p); + if (q != 0) + return EXIT_FAILURE; + if (PTR_TO_INT(p) < 0) + return EXIT_FAILURE; + + if (r < 0) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd-bus/test-bus-kernel.c b/src/libsystemd-bus/test-bus-kernel.c new file mode 100644 index 0000000000..1095e57e42 --- /dev/null +++ b/src/libsystemd-bus/test-bus-kernel.c @@ -0,0 +1,162 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <fcntl.h> + +#include "util.h" + +#include "sd-bus.h" +#include "bus-message.h" +#include "bus-error.h" +#include "bus-kernel.h" + +int main(int argc, char *argv[]) { + _cleanup_close_ int bus_ref = -1; + _cleanup_free_ char *bus_name = NULL, *address = NULL; + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + const char *ua = NULL, *ub = NULL, *the_string = NULL; + sd_bus *a, *b; + int r, pipe_fds[2]; + + bus_ref = bus_kernel_create("deine-mutter", &bus_name); + if (bus_ref == -ENOENT) + return EXIT_TEST_SKIP; + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(a, address); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + r = sd_bus_get_unique_name(a, &ua); + assert_se(r >= 0); + + printf("unique a: %s\n", ua); + + r = sd_bus_get_unique_name(b, &ub); + assert_se(r >= 0); + + printf("unique b: %s\n", ub); + + { + //FIXME: + struct kdbus_cmd_match cmd_match; + + cmd_match.size = sizeof(cmd_match); + cmd_match.src_id = KDBUS_MATCH_SRC_ID_ANY; + + r = ioctl(sd_bus_get_fd(a), KDBUS_CMD_MATCH_ADD, &cmd_match); + assert_se(r >= 0); + + r = ioctl(sd_bus_get_fd(b), KDBUS_CMD_MATCH_ADD, &cmd_match); + assert_se(r >= 0); + } + + r = sd_bus_emit_signal(a, "/foo/bar/waldo", "waldo.com", "Piep", "sss", "I am a string", "/this/is/a/path", "and.this.a.domain.name"); + assert_se(r >= 0); + + r = sd_bus_process(b, &m); + assert_se(r > 0); + assert_se(m); + + bus_message_dump(m); + assert_se(sd_bus_message_rewind(m, true) >= 0); + + r = sd_bus_message_read(m, "s", &the_string); + assert_se(r >= 0); + assert_se(streq(the_string, "I am a string")); + + sd_bus_message_unref(m); + m = NULL; + + r = sd_bus_request_name(a, "net.x0pointer.foobar", 0); + assert_se(r >= 0); + + r = sd_bus_message_new_method_call(b, "net.x0pointer.foobar", "/a/path", "an.inter.face", "AMethod", &m); + assert_se(r >= 0); + + assert_se(pipe2(pipe_fds, O_CLOEXEC) >= 0); + + assert_se(write(pipe_fds[1], "x", 1) == 1); + + close_nointr_nofail(pipe_fds[1]); + pipe_fds[1] = -1; + + r = sd_bus_message_append(m, "h", pipe_fds[0]); + assert_se(r >= 0); + + close_nointr_nofail(pipe_fds[0]); + pipe_fds[0] = -1; + + r = sd_bus_send(b, m, NULL); + assert_se(r >= 0); + + for (;;) { + sd_bus_message_unref(m); + m = NULL; + r = sd_bus_process(a, &m); + assert_se(r > 0); + assert_se(m); + + bus_message_dump(m); + assert_se(sd_bus_message_rewind(m, true) >= 0); + + if (sd_bus_message_is_method_call(m, "an.inter.face", "AMethod")) { + int fd; + char x; + + r = sd_bus_message_read(m, "h", &fd); + assert_se(r >= 0); + + assert_se(read(fd, &x, 1) == 1); + assert_se(x == 'x'); + break; + } + } + + r = sd_bus_release_name(a, "net.x0pointer.foobar"); + assert_se(r >= 0); + + r = sd_bus_release_name(a, "net.x0pointer.foobar"); + assert_se(r == -ESRCH); + + sd_bus_unref(a); + sd_bus_unref(b); + + return 0; +} diff --git a/src/libsystemd-bus/test-bus-marshal.c b/src/libsystemd-bus/test-bus-marshal.c new file mode 100644 index 0000000000..20ae723fbe --- /dev/null +++ b/src/libsystemd-bus/test-bus-marshal.c @@ -0,0 +1,175 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <stdlib.h> +#include <byteswap.h> + +#ifdef HAVE_GLIB +#include <gio/gio.h> +#endif + +#include <dbus.h> + +#include "log.h" +#include "util.h" + +#include "sd-bus.h" +#include "bus-message.h" + +int main(int argc, char *argv[]) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + int r, boolean; + const char *x, *y, *z, *a, *b, *c, *d; + uint8_t u, v; + void *buffer = NULL; + size_t sz; + char *h; + + r = sd_bus_message_new_method_call(NULL, "foobar.waldo", "/", "foobar.waldo", "Piep", &m); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "s", "a string"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "s", NULL); + assert_se(r < 0); + + r = sd_bus_message_append(m, "as", 2, "string #1", "string #2"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "sass", "foobar", 5, "foo", "bar", "waldo", "piep", "pap", "after"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "a{yv}", 2, 3, "s", "foo", 5, "s", "waldo"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "ba(ss)", 255, 3, "aaa", "1", "bbb", "2", "ccc", "3"); + assert_se(r >= 0); + + r = sd_bus_message_open_container(m, 'a', "s"); + assert_se(r >= 0); + + r = sd_bus_message_append_basic(m, 's', "foobar"); + assert_se(r >= 0); + + r = sd_bus_message_append_basic(m, 's', "waldo"); + assert_se(r >= 0); + + r = sd_bus_message_close_container(m); + assert_se(r >= 0); + + r = bus_message_seal(m, 4711); + assert_se(r >= 0); + + bus_message_dump(m); + + r = bus_message_get_blob(m, &buffer, &sz); + assert_se(r >= 0); + + h = hexmem(buffer, sz); + assert_se(h); + + log_info("message size = %lu, contents =\n%s", (unsigned long) sz, h); + free(h); + +#ifdef HAVE_GLIB + { + GDBusMessage *g; + char *p; + +#if !defined(GLIB_VERSION_2_36) + g_type_init(); +#endif + + g = g_dbus_message_new_from_blob(buffer, sz, 0, NULL); + p = g_dbus_message_print(g, 0); + log_info("%s", p); + g_free(p); + g_object_unref(g); + } +#endif + + { + DBusMessage *w; + DBusError error; + + dbus_error_init(&error); + + w = dbus_message_demarshal(buffer, sz, &error); + if (!w) { + log_error("%s", error.message); + } else + dbus_message_unref(w); + } + + m = sd_bus_message_unref(m); + + r = bus_message_from_malloc(buffer, sz, NULL, 0, NULL, NULL, &m); + assert_se(r >= 0); + + bus_message_dump(m); + + assert_se(sd_bus_message_rewind(m, true) >= 0); + + r = sd_bus_message_read(m, "sas", &x, 2, &y, &z); + assert_se(r > 0); + assert_se(streq(x, "a string")); + assert_se(streq(y, "string #1")); + assert_se(streq(z, "string #2")); + + r = sd_bus_message_read(m, "sass", &x, 5, &y, &z, &a, &b, &c, &d); + assert_se(r > 0); + assert_se(streq(x, "foobar")); + assert_se(streq(y, "foo")); + assert_se(streq(z, "bar")); + assert_se(streq(a, "waldo")); + assert_se(streq(b, "piep")); + assert_se(streq(c, "pap")); + assert_se(streq(d, "after")); + + r = sd_bus_message_read(m, "a{yv}", 2, &u, "s", &x, &v, "s", &y); + assert_se(r > 0); + assert_se(u == 3); + assert_se(streq(x, "foo")); + assert_se(v == 5); + assert_se(streq(y, "waldo")); + + r = sd_bus_message_read(m, "ba(ss)", &boolean, 3, &x, &y, &a, &b, &c, &d); + assert_se(r > 0); + assert_se(boolean); + assert_se(streq(x, "aaa")); + assert_se(streq(y, "1")); + assert_se(streq(a, "bbb")); + assert_se(streq(b, "2")); + assert_se(streq(c, "ccc")); + assert_se(streq(d, "3")); + + r = sd_bus_message_read(m, "as", 2, &x, &y); + assert_se(r > 0); + assert_se(streq(x, "foobar")); + assert_se(streq(y, "waldo")); + + r = sd_bus_message_peek_type(m, NULL, NULL); + assert_se(r == 0); + + return 0; +} diff --git a/src/libsystemd-bus/test-bus-match.c b/src/libsystemd-bus/test-bus-match.c new file mode 100644 index 0000000000..9cf994009d --- /dev/null +++ b/src/libsystemd-bus/test-bus-match.c @@ -0,0 +1,114 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> + +#include "log.h" +#include "util.h" +#include "macro.h" + +#include "bus-match.h" +#include "bus-message.h" + +static bool mask[32]; + +static int filter(sd_bus *b, int ret, sd_bus_message *m, void *userdata) { + log_info("Ran %i", PTR_TO_INT(userdata)); + mask[PTR_TO_INT(userdata)] = true; + return 0; +} + +static bool mask_contains(unsigned a[], unsigned n) { + unsigned i, j; + + for (i = 0; i < ELEMENTSOF(mask); i++) { + bool found = false; + + for (j = 0; j < n; j++) + if (a[j] == i) { + found = true; + break; + } + + if (found != mask[i]) + return false; + } + + return true; +} + +int main(int argc, char *argv[]) { + struct bus_match_node root; + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + enum bus_match_node_type i; + + zero(root); + root.type = BUS_MATCH_ROOT; + + assert_se(bus_match_add(&root, "arg2='wal\\'do',sender='foo',type='signal',interface='bar',", filter, INT_TO_PTR(1), NULL) >= 0); + assert_se(bus_match_add(&root, "arg2='wal\\'do2',sender='foo',type='signal',interface='bar',", filter, INT_TO_PTR(2), NULL) >= 0); + assert_se(bus_match_add(&root, "arg3='test',sender='foo',type='signal',interface='bar',", filter, INT_TO_PTR(3), NULL) >= 0); + assert_se(bus_match_add(&root, "arg3='test',sender='foo',type='method_call',interface='bar',", filter, INT_TO_PTR(4), NULL) >= 0); + assert_se(bus_match_add(&root, "", filter, INT_TO_PTR(5), NULL) >= 0); + assert_se(bus_match_add(&root, "interface='quux'", filter, INT_TO_PTR(6), NULL) >= 0); + assert_se(bus_match_add(&root, "interface='bar'", filter, INT_TO_PTR(7), NULL) >= 0); + assert_se(bus_match_add(&root, "member='waldo',path='/foo/bar'", filter, INT_TO_PTR(8), NULL) >= 0); + assert_se(bus_match_add(&root, "path='/foo/bar'", filter, INT_TO_PTR(9), NULL) >= 0); + assert_se(bus_match_add(&root, "path_namespace='/foo'", filter, INT_TO_PTR(10), NULL) >= 0); + assert_se(bus_match_add(&root, "path_namespace='/foo/quux'", filter, INT_TO_PTR(11), NULL) >= 0); + assert_se(bus_match_add(&root, "arg1='two'", filter, INT_TO_PTR(12), NULL) >= 0); + assert_se(bus_match_add(&root, "member='waldo',arg2path='/prefix/'", filter, INT_TO_PTR(13), NULL) >= 0); + assert_se(bus_match_add(&root, "member='waldo',path='/foo/bar',arg3namespace='prefix'", filter, INT_TO_PTR(14), NULL) >= 0); + + bus_match_dump(&root, 0); + + assert_se(sd_bus_message_new_signal(NULL, "/foo/bar", "bar", "waldo", &m) >= 0); + assert_se(sd_bus_message_append(m, "ssss", "one", "two", "/prefix/three", "prefix.four") >= 0); + assert_se(bus_message_seal(m, 1) >= 0); + + zero(mask); + assert_se(bus_match_run(NULL, &root, 0, m) == 0); + assert_se(mask_contains((unsigned[]) { 9, 8, 7, 5, 10, 12, 13, 14 }, 8)); + + assert_se(bus_match_remove(&root, "member='waldo',path='/foo/bar'", filter, INT_TO_PTR(8)) > 0); + assert_se(bus_match_remove(&root, "arg2path='/prefix/',member='waldo'", filter, INT_TO_PTR(13)) > 0); + assert_se(bus_match_remove(&root, "interface='barxx'", filter, INT_TO_PTR(7)) == 0); + + bus_match_dump(&root, 0); + + zero(mask); + assert_se(bus_match_run(NULL, &root, 0, m) == 0); + assert_se(mask_contains((unsigned[]) { 9, 5, 10, 12, 14, 7 }, 6)); + + for (i = 0; i < _BUS_MATCH_NODE_TYPE_MAX; i++) { + char buf[32]; + const char *x; + + assert_se(x = bus_match_node_type_to_string(i, buf, sizeof(buf))); + + if (i >= BUS_MATCH_MESSAGE_TYPE) + assert_se(bus_match_node_type_from_string(x, strlen(x)) == i); + } + + bus_match_free(&root); + + return 0; +} diff --git a/src/libsystemd-bus/test-bus-server.c b/src/libsystemd-bus/test-bus-server.c new file mode 100644 index 0000000000..a9772624f2 --- /dev/null +++ b/src/libsystemd-bus/test-bus-server.c @@ -0,0 +1,223 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <stdlib.h> +#include <pthread.h> +#include <unistd.h> +#include <fcntl.h> + +#include "log.h" +#include "util.h" +#include "macro.h" + +#include "sd-bus.h" +#include "bus-internal.h" +#include "bus-message.h" + +struct context { + int fds[2]; + + bool client_negotiate_unix_fds; + bool server_negotiate_unix_fds; + + bool client_anonymous_auth; + bool server_anonymous_auth; +}; + +static void *server(void *p) { + struct context *c = p; + sd_bus *bus = NULL; + sd_id128_t id; + bool quit = false; + int r; + + assert_se(sd_id128_randomize(&id) >= 0); + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); + assert_se(sd_bus_set_server(bus, 1, id) >= 0); + assert_se(sd_bus_set_negotiate_fds(bus, c->server_negotiate_unix_fds) >= 0); + assert_se(sd_bus_set_anonymous(bus, c->server_anonymous_auth) >= 0); + assert_se(sd_bus_start(bus) >= 0); + + while (!quit) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + + r = sd_bus_process(bus, &m); + if (r < 0) { + log_error("Failed to process requests: %s", strerror(-r)); + goto fail; + } + + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error("Failed to wait: %s", strerror(-r)); + goto fail; + } + + continue; + } + + if (!m) + continue; + + log_info("Got message! member=%s", strna(sd_bus_message_get_member(m))); + + if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Exit")) { + + assert_se((sd_bus_can_send(bus, 'h') >= 1) == (c->server_negotiate_unix_fds && c->client_negotiate_unix_fds)); + + r = sd_bus_message_new_method_return(bus, m, &reply); + if (r < 0) { + log_error("Failed to allocate return: %s", strerror(-r)); + goto fail; + } + + quit = true; + + } else if (sd_bus_message_is_method_call(m, NULL, NULL)) { + r = sd_bus_message_new_method_error( + bus, m, + &SD_BUS_ERROR_MAKE("org.freedesktop.DBus.Error.UnknownMethod", "Unknown method."), + &reply); + if (r < 0) { + log_error("Failed to allocate return: %s", strerror(-r)); + goto fail; + } + } + + if (reply) { + r = sd_bus_send(bus, reply, NULL); + if (r < 0) { + log_error("Failed to send reply: %s", strerror(-r)); + goto fail; + } + } + } + + r = 0; + +fail: + if (bus) { + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + return INT_TO_PTR(r); +} + +static int client(struct context *c) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + _cleanup_bus_unref_ sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); + assert_se(sd_bus_set_negotiate_fds(bus, c->client_negotiate_unix_fds) >= 0); + assert_se(sd_bus_set_anonymous(bus, c->client_anonymous_auth) >= 0); + assert_se(sd_bus_start(bus) >= 0); + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "Exit", + &m); + if (r < 0) { + log_error("Failed to allocate method call: %s", strerror(-r)); + return r; + } + + r = sd_bus_send_with_reply_and_block(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int test_one(bool client_negotiate_unix_fds, bool server_negotiate_unix_fds, + bool client_anonymous_auth, bool server_anonymous_auth) { + + struct context c; + pthread_t s; + void *p; + int r, q; + + zero(c); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0); + + c.client_negotiate_unix_fds = client_negotiate_unix_fds; + c.server_negotiate_unix_fds = server_negotiate_unix_fds; + c.client_anonymous_auth = client_anonymous_auth; + c.server_anonymous_auth = server_anonymous_auth; + + r = pthread_create(&s, NULL, server, &c); + if (r != 0) + return -r; + + r = client(&c); + + q = pthread_join(s, &p); + if (q != 0) + return -q; + + if (r < 0) + return r; + + if (PTR_TO_INT(p) < 0) + return PTR_TO_INT(p); + + return 0; +} + +int main(int argc, char *argv[]) { + int r; + + r = test_one(true, true, false, false); + assert_se(r >= 0); + + r = test_one(true, false, false, false); + assert_se(r >= 0); + + r = test_one(false, true, false, false); + assert_se(r >= 0); + + r = test_one(false, false, false, false); + assert_se(r >= 0); + + r = test_one(true, true, true, true); + assert_se(r >= 0); + + r = test_one(true, true, false, true); + assert_se(r >= 0); + + r = test_one(true, true, true, false); + assert_se(r == -EPERM); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd-bus/test-bus-signature.c b/src/libsystemd-bus/test-bus-signature.c new file mode 100644 index 0000000000..cab0daa77e --- /dev/null +++ b/src/libsystemd-bus/test-bus-signature.c @@ -0,0 +1,116 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <stdlib.h> + +#include "log.h" +#include "bus-signature.h" +#include "bus-internal.h" + +int main(int argc, char *argv[]) { + + assert_se(signature_is_single("y")); + assert_se(signature_is_single("u")); + assert_se(signature_is_single("v")); + assert_se(signature_is_single("as")); + assert_se(signature_is_single("(ss)")); + assert_se(signature_is_single("()")); + assert_se(signature_is_single("(()()()()())")); + assert_se(signature_is_single("(((())))")); + assert_se(signature_is_single("((((s))))")); + assert_se(signature_is_single("{ss}")); + assert_se(signature_is_single("a{ss}")); + assert_se(!signature_is_single("uu")); + assert_se(!signature_is_single("")); + assert_se(!signature_is_single("(")); + assert_se(!signature_is_single(")")); + assert_se(!signature_is_single("())")); + assert_se(!signature_is_single("((())")); + assert_se(!signature_is_single("{)")); + assert_se(!signature_is_single("{}")); + assert_se(!signature_is_single("{sss}")); + assert_se(!signature_is_single("{s}")); + assert_se(!signature_is_single("{ass}")); + assert_se(!signature_is_single("a}")); + + assert_se(signature_is_pair("yy")); + assert_se(signature_is_pair("ss")); + assert_se(signature_is_pair("sas")); + assert_se(signature_is_pair("sv")); + assert_se(signature_is_pair("sa(vs)")); + assert_se(!signature_is_pair("")); + assert_se(!signature_is_pair("va")); + assert_se(!signature_is_pair("sss")); + assert_se(!signature_is_pair("{s}ss")); + + assert_se(signature_is_valid("ssa{ss}sssub", true)); + assert_se(signature_is_valid("ssa{ss}sssub", false)); + assert_se(signature_is_valid("{ss}", true)); + assert_se(!signature_is_valid("{ss}", false)); + assert_se(signature_is_valid("", true)); + assert_se(signature_is_valid("", false)); + + assert_se(signature_is_valid("sssusa(uuubbba(uu)uuuu)a{u(uuuvas)}", false)); + + assert_se(!signature_is_valid("a", false)); + assert_se(signature_is_valid("as", false)); + assert_se(signature_is_valid("aas", false)); + assert_se(signature_is_valid("aaas", false)); + assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad", false)); + assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas", false)); + assert_se(!signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau", false)); + + assert_se(signature_is_valid("(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))", false)); + assert_se(!signature_is_valid("((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))", false)); + + assert_se(namespace_complex_pattern("", "")); + assert_se(namespace_complex_pattern("foobar", "foobar")); + assert_se(namespace_complex_pattern("foobar.waldo", "foobar.waldo")); + assert_se(namespace_complex_pattern("foobar.", "foobar.waldo")); + assert_se(namespace_complex_pattern("foobar.waldo", "foobar.")); + assert_se(!namespace_complex_pattern("foobar.waldo", "foobar")); + assert_se(!namespace_complex_pattern("foobar", "foobar.waldo")); + assert_se(!namespace_complex_pattern("", "foo")); + assert_se(!namespace_complex_pattern("foo", "")); + assert_se(!namespace_complex_pattern("foo.", "")); + + assert_se(path_complex_pattern("", "")); + assert_se(path_complex_pattern("", "/")); + assert_se(path_complex_pattern("/", "")); + assert_se(path_complex_pattern("/", "/")); + assert_se(path_complex_pattern("/foobar/", "/")); + assert_se(path_complex_pattern("/foobar/", "/foobar")); + assert_se(path_complex_pattern("/foobar", "/foobar")); + assert_se(path_complex_pattern("/foobar", "/foobar/")); + assert_se(!path_complex_pattern("/foobar", "/foobar/waldo")); + assert_se(path_complex_pattern("/foobar/", "/foobar/waldo")); + + assert_se(namespace_simple_pattern("", "")); + assert_se(namespace_simple_pattern("foobar", "foobar")); + assert_se(namespace_simple_pattern("foobar.waldo", "foobar.waldo")); + assert_se(namespace_simple_pattern("foobar", "foobar.waldo")); + assert_se(!namespace_simple_pattern("foobar.waldo", "foobar")); + assert_se(!namespace_simple_pattern("", "foo")); + assert_se(!namespace_simple_pattern("foo", "")); + + return 0; +} |