From: Lennart Poettering Date: Tue, 19 Mar 2013 19:03:16 +0000 (+0100) Subject: bus: add basic implementation of a native bus client library X-Git-Tag: v199~136 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=de1c301ed165eb4d04a0c9d4babe97912b5233bb bus: add basic implementation of a native bus client library --- diff --git a/.gitignore b/.gitignore index 738510789..917b0663b 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,8 @@ /systemd-user-sessions /systemd-vconsole-setup /tags +/test-bus-marshal +/test-bus-signature /test-calendarspec /test-catalog /test-cgroup diff --git a/Makefile.am b/Makefile.am index 37eeb01d5..c94c192ce 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1638,6 +1638,50 @@ EXTRA_DIST += \ src/libsystemd-daemon/libsystemd-daemon.pc.in \ src/libsystemd-daemon/libsystemd-daemon.sym +# ------------------------------------------------------------------------------ +libsystemd_bus_la_SOURCES = \ + src/libsystemd-bus/sd-bus.c \ + src/libsystemd-bus/sd-bus.h \ + src/libsystemd-bus/sd-bus-protocol.h \ + src/libsystemd-bus/bus-control.c \ + src/libsystemd-bus/bus-error.c \ + src/libsystemd-bus/bus-error.h \ + src/libsystemd-bus/bus-internal.c \ + src/libsystemd-bus/bus-internal.h \ + src/libsystemd-bus/bus-message.c \ + src/libsystemd-bus/bus-message.h \ + src/libsystemd-bus/bus-signature.c \ + src/libsystemd-bus/bus-type.c \ + src/libsystemd-bus/bus-type.h + +noinst_LTLIBRARIES += \ + libsystemd-bus.la + +noinst_tests += \ + test-bus-marshal \ + test-bus-signature + +test_bus_marshal_SOURCES = \ + src/libsystemd-bus/test-bus-marshal.c + +test_bus_marshal_LDADD = \ + libsystemd-shared.la \ + libsystemd-bus.la \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) + +test_bus_marshal_CFLAGS = \ + $(AM_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) + +test_bus_signature_SOURCES = \ + src/libsystemd-bus/test-bus-signature.c + +test_bus_signature_LDADD = \ + libsystemd-shared.la \ + libsystemd-bus.la + # ------------------------------------------------------------------------------ if ENABLE_GTK_DOC SUBDIRS += \ diff --git a/configure.ac b/configure.ac index 5d1be3f85..ae492cfe6 100644 --- a/configure.ac +++ b/configure.ac @@ -724,9 +724,11 @@ AM_CONDITIONAL(ENABLE_FIRMWARE, [test "x${FIRMWARE_PATH}" != "x"]) AC_ARG_ENABLE([gudev], AS_HELP_STRING([--disable-gudev], [disable Gobject libudev support @<:@default=enabled@:>@]), [], [enable_gudev=yes]) -AS_IF([test "x$enable_gudev" = "xyes"], [ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.22.0 gobject-2.0 >= 2.22.0]) ]) +AS_IF([test "x$enable_gudev" = "xyes"], [ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.22.0 gobject-2.0 >= 2.22.0 gio-2.0]) ]) AM_CONDITIONAL([ENABLE_GUDEV], [test "x$enable_gudev" = "xyes"]) +AS_IF([test "x$enable_gudev" = "xyes"], [ AC_DEFINE(HAVE_GLIB, 1, [Define if glib is available]) ]) + # ------------------------------------------------------------------------------ AC_ARG_ENABLE([keymap], AS_HELP_STRING([--disable-keymap], [disable keymap fixup support @<:@default=enabled@:>@]), diff --git a/src/libsystemd-bus/Makefile b/src/libsystemd-bus/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/libsystemd-bus/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/libsystemd-bus/bus-control.c b/src/libsystemd-bus/bus-control.c new file mode 100644 index 000000000..e4cc251a4 --- /dev/null +++ b/src/libsystemd-bus/bus-control.c @@ -0,0 +1,322 @@ +/*-*- 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 . +***/ + +#include +#include + +#include "strv.h" + +#include "sd-bus.h" +#include "bus-internal.h" +#include "bus-message.h" + +const char *sd_bus_get_unique_name(sd_bus *bus) { + if (!bus) + return NULL; + + return bus->unique_name; +} + +int sd_bus_request_name(sd_bus *bus, const char *name, int flags) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + uint32_t ret; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "RequestName", + &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "su", name, flags); + if (r < 0) + return r; + + r = sd_bus_send_with_reply_and_block(bus, m, (uint64_t) -1, NULL, &reply); + 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 *m = NULL, *reply = NULL; + uint32_t ret; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "ReleaseName", + &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", name); + if (r < 0) + return r; + + r = sd_bus_send_with_reply_and_block(bus, m, (uint64_t) -1, NULL, &reply); + 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 *m1 = NULL, *reply1 = NULL, *m2 = NULL, *reply2 = NULL; + _cleanup_strv_free_ char **a = NULL, **b = NULL; + char **x = NULL; + int r; + + if (!bus) + return -EINVAL; + if (!l) + return -EINVAL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "ListNames", + &m1); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "ListActivatableNames", + &m2); + if (r < 0) + return r; + + r = sd_bus_send_with_reply_and_block(bus, m1, (uint64_t) -1, NULL, &reply1); + if (r < 0) + return r; + + r = sd_bus_send_with_reply_and_block(bus, m2, (uint64_t) -1, NULL, &reply2); + if (r < 0) + return r; + + r = sd_bus_message_read(reply1, "as", &a); + if (r < 0) + return r; + + r = sd_bus_message_read(reply2, "as", &b); + if (r < 0) + return r; + + x = strv_merge(a, b); + if (!x) + return -ENOMEM; + + *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 *m = NULL, *reply = NULL; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "GetNameOwner", + &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", name); + if (r < 0) + return r; + + r = sd_bus_send_with_reply_and_block(bus, m, (uint64_t) -1, NULL, &reply); + if (r < 0) + return r; + + return sd_bus_message_read(reply, "s", owner); +} + +int sd_bus_get_owner_uid(sd_bus *bus, const char *name, uid_t *uid) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + uint32_t u; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + if (!uid) + return -EINVAL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "GetConnectionUnixUser", + &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", name); + if (r < 0) + return r; + + r = sd_bus_send_with_reply_and_block(bus, m, (uint64_t) -1, NULL, &reply); + 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 *m = NULL, *reply = NULL; + uint32_t u; + int r; + + if (!bus) + return -EINVAL; + if (!name) + return -EINVAL; + if (!pid) + return -EINVAL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "GetConnectionUnixUser", + &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", name); + if (r < 0) + return r; + + r = sd_bus_send_with_reply_and_block(bus, m, (uint64_t) -1, NULL, &reply); + 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 sd_bus_add_match(sd_bus *bus, const char *match) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + int r; + + if (!bus) + return -EINVAL; + if (!match) + return -EINVAL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "AddMatch", + &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", match); + if (r < 0) + return r; + + return sd_bus_send_with_reply_and_block(bus, m, (uint64_t) -1, NULL, &reply); +} + +int sd_bus_remove_match(sd_bus *bus, const char *match) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + int r; + + if (!bus) + return -EINVAL; + if (!match) + return -EINVAL; + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "RemoveMatch", + &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", match); + if (r < 0) + return r; + + return sd_bus_send_with_reply_and_block(bus, m, (uint64_t) -1, NULL, &reply); +} diff --git a/src/libsystemd-bus/bus-error.c b/src/libsystemd-bus/bus-error.c new file mode 100644 index 000000000..0832022ee --- /dev/null +++ b/src/libsystemd-bus/bus-error.c @@ -0,0 +1,166 @@ +/*-*- 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "util.h" + +#include "sd-bus.h" +#include "bus-error.h" + +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 (sd_bus_error_is_set(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) { + if (!dest) + return 0; + if (sd_bus_error_is_set(dest)) + return -EINVAL; + if (!sd_bus_error_is_set(e)) + return 0; + + if (e->need_free) { + char *x, *y = NULL; + + 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; + } else + *dest = *e; + + return 0; +} + +void sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message) { + if (!e) + return; + if (sd_bus_error_is_set(e)) + return; + + e->name = name; + e->message = message; +} + +int sd_bus_error_is_set(const sd_bus_error *e) { + if (!e) + return 0; + + return e->name || e->message || e->need_free; +} + +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; +} diff --git a/src/libsystemd-bus/bus-error.h b/src/libsystemd-bus/bus-error.h new file mode 100644 index 000000000..f49e6c739 --- /dev/null +++ b/src/libsystemd-bus/bus-error.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 . +***/ + +#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); diff --git a/src/libsystemd-bus/bus-internal.c b/src/libsystemd-bus/bus-internal.c new file mode 100644 index 000000000..19398af6c --- /dev/null +++ b/src/libsystemd-bus/bus-internal.c @@ -0,0 +1,22 @@ +/*-*- 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 . +***/ + +#include "bus-internal.h" diff --git a/src/libsystemd-bus/bus-internal.h b/src/libsystemd-bus/bus-internal.h new file mode 100644 index 000000000..d8cdc48ce --- /dev/null +++ b/src/libsystemd-bus/bus-internal.h @@ -0,0 +1,101 @@ +/*-*- 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 . +***/ + +#include +#include +#include + +#include "hashmap.h" +#include "list.h" +#include "util.h" + +#include "sd-bus.h" +#include "bus-error.h" + +struct reply_callback { + sd_message_handler_t callback; + void *userdata; + usec_t timeout; + uint64_t serial; +}; + +struct filter_callback { + sd_message_handler_t callback; + void *userdata; + + LIST_FIELDS(struct filter_callback, callbacks); +}; + +enum bus_state { + BUS_OPENING, + BUS_AUTHENTICATING, + BUS_HELLO, + BUS_RUNNING, + BUS_CLOSED +}; + +struct sd_bus { + unsigned n_ref; + enum bus_state state; + int fd; + int message_version; + bool can_fds:1; + bool send_hello: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; + + Hashmap *reply_callbacks; + LIST_HEAD(struct filter_callback, filter_callbacks); + + union { + struct sockaddr sa; + struct sockaddr_un un; + struct sockaddr_in in; + struct sockaddr_in6 in6; + } sockaddr; + socklen_t sockaddr_size; + + sd_id128_t peer; + + char *address; + unsigned address_index; + + int last_connect_error; + + struct iovec auth_iovec[3]; + unsigned auth_index; + size_t auth_size; + char *auth_uid; +}; diff --git a/src/libsystemd-bus/bus-message.c b/src/libsystemd-bus/bus-message.c new file mode 100644 index 000000000..4c5588cf8 --- /dev/null +++ b/src/libsystemd-bus/bus-message.c @@ -0,0 +1,1413 @@ +/*-*- 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 . +***/ + +#include + +#include "util.h" + +#include "bus-message.h" +#include "sd-bus.h" +#include "bus-internal.h" +#include "bus-type.h" +#include "bus-signature.h" + +int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored); + +static void message_free(sd_bus_message *m) { + unsigned i; + + assert(m); + + if (m->free_header) + free(m->header); + + if (m->free_fields) + free(m->fields); + + if (m->free_body) + free(m->body); + + for (i = 0; i < m->n_fds; i++) + close_nointr_nofail(m->fds[i]); + + for (i = 0; i < m->n_containers; i++) + free(m->sub_containers[i].signature); + + free(m->sub_containers); + 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->signature) + m->signature = (const char*) m->fields + (m->signature - (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; +} + +static sd_bus_message *message_new(sd_bus *bus, uint8_t type) { + sd_bus_message *m; + + m = malloc0(ALIGN(sizeof(struct 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))); + +#if __BYTE_ORDER == __BIG_ENDIAN + m->header->endian = SD_BUS_BIG_ENDIAN; +#else + m->header->endian = SD_BUS_LITTLE_ENDIAN; +#endif + m->header->type = type; + m->header->version = bus ? bus->message_version : 1; + + 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; + + t = message_new(bus, SD_BUS_MESSAGE_TYPE_SIGNAL); + 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_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; + + 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; +} + +int sd_bus_message_new_method_return( + sd_bus *bus, + sd_bus_message *call, + sd_bus_message **m) { + + sd_bus_message *t; + int r; + + if (!call) + return -EINVAL; + if (call->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + + t = message_new(bus, SD_BUS_MESSAGE_TYPE_METHOD_RETURN); + if (!t) + return -ENOMEM; + + 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; + } + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +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 (!call) + return -EINVAL; + if (call->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + if (!e) + return -EINVAL; + if (!e->name) + return -EINVAL; + + t = message_new(bus, SD_BUS_MESSAGE_TYPE_METHOD_ERROR); + if (!t) + return -ENOMEM; + + 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; + } + + 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 (!m->uid_valid) + return -ENOENT; + + *uid = m->uid; + return 0; +} + +int sd_bus_message_get_gid(sd_bus_message *m, gid_t *gid) { + if (!m) + return -EINVAL; + if (!m->gid_valid) + return -ENOENT; + + *gid = m->gid; + return 0; +} + +int sd_bus_message_get_pid(sd_bus_message *m, pid_t *pid) { + if (!m) + return -EINVAL; + if (m->pid <= 0) + return -ENOENT; + + *pid = m->pid; + return 0; +} + +int sd_bus_message_get_tid(sd_bus_message *m, pid_t *tid) { + if (!m) + return -EINVAL; + if (m->tid <= 0) + return -ENOENT; + + *tid = m->tid; + return 0; +} + +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->sub_containers); + return m->sub_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); + + 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->sub_containers; c < m->sub_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; + size_t sz, align, nindex; + uint32_t k; + void *a; + char *e = NULL; + + if (!m) + 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 -EINVAL; + } else { + /* Maybe we can append to the signature? But only if this is the top-level container*/ + if (c->enclosing != 0) + return -EINVAL; + + e = strextend(&c->signature, CHAR_TO_STR(type), NULL); + if (!e) + return -ENOMEM; + } + + nindex = c->index + 1; + + 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; + + 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) { + /* Truncate extended signature again */ + if (e) + c->signature[c->index] = 0; + + return -ENOMEM; + } + + 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 { + memcpy(a, p, sz); + + if (stored) + *stored = a; + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index = nindex; + + return 0; +} + +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) { + + char *e = NULL; + size_t nindex; + 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 -EINVAL; + + if (!startswith(c->signature + c->index + 1, contents)) + return -EINVAL; + + nindex = c->index + 1 + strlen(contents); + } else { + if (c->enclosing != 0) + return -EINVAL; + + /* 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, nindex; + 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 -EINVAL; + + } else { + if (c->enclosing != 0) + return -EINVAL; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_VARIANT), NULL); + if (!e) + return -ENOMEM; + } + + nindex = c->index + 1; + + 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 = nindex; + + 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 -EINVAL; + + nindex = c->index + 1 + l + 1; + } else { + if (c->enclosing != 0) + return -EINVAL; + + 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 -EINVAL; + + 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 -EINVAL; + + nindex = c->index + 1 + l + 1; + } else + return -EINVAL; + + /* 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, *sub; + char *signature; + uint32_t *array_size = NULL; + int r; + + if (!m) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (!contents) + return -EINVAL; + + /* Make sure we have space for one more container */ + sub = realloc(m->sub_containers, sizeof(struct bus_container) * (m->n_containers + 1)); + if (!sub) + return -ENOMEM; + + m->sub_containers = sub; + + 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 */ + sub += m->n_containers++; + + sub->enclosing = type; + sub->signature = signature; + sub->index = 0; + sub->array_size = array_size; + + 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->signature) + return -EINVAL; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + if (c->signature[c->index] != 0) + return -EINVAL; + + free(c->signature); + m->n_containers--; + + return 0; +} + +static int message_append_ap( + sd_bus_message *m, + const char *types, + va_list ap) { + + const char *t; + int r; + + assert(m); + assert(types); + + for (t = types; *t; t++) { + 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: { + uint32_t x; + + 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_UNIX_FD: { + int x; + + x = va_arg(ap, int); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_ARRAY: { + unsigned i, n; + 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; + t += k; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); + if (r < 0) + return r; + + n = va_arg(ap, unsigned); + + for (i = 0; i < n; i++) { + r = message_append_ap(m, s, ap); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + } + + 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 = message_append_ap(m, s, ap); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + 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; + + t += k - 1; + + r = message_append_ap(m, s, ap); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + } + + 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 -EINVAL; + + va_start(ap, types); + r = message_append_ap(m, types, ap); + va_end(ap); + + return r; +} + +int sd_bus_message_read_type(sd_bus_message *m, char *type, char *element, size_t *length) { + return -ENOTSUP; +} + +int sd_bus_message_read_basic(sd_bus_message *m, char type, char element, const void **p, size_t *length) { + return -ENOTSUP; +} + +int sd_bus_message_read(sd_bus_message *m, const char *types, ...) { + return -ENOTSUP; +} + +int message_parse(sd_bus_message *m) { + assert(m); + + if (m->header->version != 1) + return -EIO; + + if (m->header->endian != SD_BUS_BIG_ENDIAN && + m->header->endian != SD_BUS_LITTLE_ENDIAN) + return -EIO; + + return 0; +} + +static void setup_iovec(sd_bus_message *m) { + assert(m); + assert(m->sealed); + + m->n_iovec = 0; + + m->iovec[m->n_iovec].iov_base = m->header; + m->iovec[m->n_iovec].iov_len = sizeof(*m->header); + m->n_iovec++; + + if (m->fields) { + m->iovec[m->n_iovec].iov_base = m->fields; + m->iovec[m->n_iovec].iov_len = m->header->fields_size; + m->n_iovec++; + + if (m->header->fields_size % 8 != 0) { + static const uint8_t padding[7] = { 0, 0, 0, 0, 0, 0, 0 }; + + m->iovec[m->n_iovec].iov_base = (void*) padding; + m->iovec[m->n_iovec].iov_len = 8 - m->header->fields_size % 8; + m->n_iovec++; + } + } + + if (m->body) { + m->iovec[m->n_iovec].iov_base = m->body; + m->iovec[m->n_iovec].iov_len = m->header->body_size; + m->n_iovec++; + } +} + +int message_seal(sd_bus_message *m, uint64_t serial) { + int r; + + 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, &m->signature); + 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; + } + + m->header->serial = serial; + m->sealed = true; + + setup_iovec(m); + + 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); +} + +void message_dump(sd_bus_message *m) { + + log_info("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->signature), + m->reply_serial, + strna(m->error.name), + strna(m->error.message), + yes_no(m->sealed)); +} + +int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) { + size_t total; + unsigned i; + void *p, *e; + + assert(m); + assert(buffer); + assert(sz); + + for (i = 0, total = 0; i < m->n_iovec; i++) + total += m->iovec[i].iov_len; + + p = malloc(total); + if (!p) + return -ENOMEM; + + for (i = 0, e = p; i < m->n_iovec; i++) + e = mempcpy(e, m->iovec[i].iov_base, m->iovec[i].iov_len); + + *buffer = p; + *sz = total; + + return 0; +} diff --git a/src/libsystemd-bus/bus-message.h b/src/libsystemd-bus/bus-message.h new file mode 100644 index 000000000..c62659e42 --- /dev/null +++ b/src/libsystemd-bus/bus-message.h @@ -0,0 +1,120 @@ +/*-*- 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 . +***/ + +#include +#include + +#include "macro.h" +#include "sd-bus.h" + +struct bus_container { + char enclosing; + + char *signature; + unsigned index; + + uint32_t *array_size; +}; + +_packed_ 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; +}; + +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; + const char *signature; + + sd_bus_error error; + + uid_t uid; + gid_t gid; + pid_t pid; + pid_t tid; + + bool sealed:1; + bool uid_valid:1; + bool gid_valid:1; + bool free_header:1; + bool free_fields:1; + bool free_body:1; + + struct bus_header *header; + void *fields; + void *body; + + uint32_t n_fds; + int *fds; + + struct bus_container root_container, *sub_containers; + unsigned n_containers; + + struct iovec iovec[4]; + unsigned n_iovec; +}; + +#if __BYTE_ORDER == __BIG_ENDIAN +#define BUS_MESSAGE_NEED_BSWAP(m) ((m)->header->endian != SD_BUS_BIG_ENDIAN) +#else +#define BUS_MESSAGE_NEED_BSWAP(m) ((m)->header->endian != SD_BUS_LITTLE_ENDIAN) +#endif + +static inline uint32_t BUS_MESSAGE_BSWAP(sd_bus_message *m, uint32_t u) { + return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_32(u) : u; +} + +static inline uint32_t BUS_MESSAGE_SERIAL(sd_bus_message *m) { + return BUS_MESSAGE_BSWAP(m, m->header->serial); +} + +static inline uint32_t BUS_MESSAGE_BODY_SIZE(sd_bus_message *m) { + return BUS_MESSAGE_BSWAP(m, m->header->body_size); +} + +static inline uint32_t BUS_MESSAGE_FIELDS_SIZE(sd_bus_message *m) { + return BUS_MESSAGE_BSWAP(m, m->header->fields_size); +} + +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 message_parse(sd_bus_message *m); +int message_seal(sd_bus_message *m, uint64_t serial); +void message_dump(sd_bus_message *m); +int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz); diff --git a/src/libsystemd-bus/bus-signature.c b/src/libsystemd-bus/bus-signature.c new file mode 100644 index 000000000..db95c8870 --- /dev/null +++ b/src/libsystemd-bus/bus-signature.c @@ -0,0 +1,141 @@ +/*-*- 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 . +***/ + +#include + +#include "bus-signature.h" +#include "bus-type.h" + +static int signature_element_length_internal( + const char *s, + bool allow_dict_entry, + 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; + + r = signature_element_length_internal(s + 1, true, &t); + if (r < 0) + return r; + + *l = t + 1; + return 0; + } + + if (*s == SD_BUS_TYPE_STRUCT_BEGIN) { + const char *p = s + 1; + + while (*p != SD_BUS_TYPE_STRUCT_END) { + size_t t; + + r = signature_element_length_internal(p, false, &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; + + 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, &t); + if (r < 0) + return r; + + p += t; + n++; + } + + if (n != 2) + return -EINVAL; + + *l = p - s + 1; + return 0; + } + + return -EINVAL; +} + +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, &t); + if (r < 0) + return false; + + p += t; + } + + return p - s <= 255; +} + +int signature_element_length(const char *s, size_t *l) { + return signature_element_length_internal(s, true, l); +} diff --git a/src/libsystemd-bus/bus-signature.h b/src/libsystemd-bus/bus-signature.h new file mode 100644 index 000000000..4000e0612 --- /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 . +***/ + +#include +#include + +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-type.c b/src/libsystemd-bus/bus-type.c new file mode 100644 index 000000000..055732808 --- /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 . +***/ + +#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 000000000..e26113608 --- /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 . +***/ + +#include + +#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 000000000..88446fcc7 --- /dev/null +++ b/src/libsystemd-bus/busctl.c @@ -0,0 +1,68 @@ +/*-*- 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 . +***/ + +#include "sd-bus.h" + +int main(int argc, char *argv[]) { + _cleanup_bus_unref_ sd_bus *bus = NULL; + _cleanup_strv_free_ char **l = NULL; + char **i; + int r; + + r = bus_open_system(&bus); + if (r < 0) { + log_error("Failed to connect to bus: %s", strerror(-r)); + goto fail; + } + + r = sd_bus_list_names(bus, &l); + if (r < 0) { + log_error("Failed to list names: %s", strerror(-r)); + goto fail; + } + + STRV_FOREACH(i, l) { + _cleanup_free_ char *owner = NULL; + pid_t pid = 0; + uid_t uid; + bool uid_valid; + + r = sd_bus_get_owner(bus, *i, &owner); + if (r == -ENXIO) + continue; + + r = sd_get_owner_pid(bus, *i, &pid); + if (r == -ENXIO) + continue; + + r = sd_get_owner_uid(bus, *i, &pid); + if (r == -ENXIO) + continue; + uid_valid = r >= 0; + + printf("%s (%s) %llu %llu\n", *i, owner, (unsigned long long) pid, (unsigned long long) uid); + } + + r = 0; + +fail: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/libsystemd-bus/sd-bus-protocol.h b/src/libsystemd-bus/sd-bus-protocol.h new file mode 100644 index 000000000..9d7615ff0 --- /dev/null +++ b/src/libsystemd-bus/sd-bus-protocol.h @@ -0,0 +1,96 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdbusprotocolhfoo +#define foosdbusprotocolhfoo + +/*** + 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 . +***/ + +/* Types of message */ + +#define SD_BUS_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) + +enum { + _SD_BUS_MESSAGE_TYPE_INVALID = 0, + SD_BUS_MESSAGE_TYPE_METHOD_CALL, + SD_BUS_MESSAGE_TYPE_METHOD_RETURN, + SD_BUS_MESSAGE_TYPE_METHOD_ERROR, + SD_BUS_MESSAGE_TYPE_SIGNAL, + _SD_BUS_MESSAGE_TYPE_MAX +}; + +/* Primitive types */ + +enum { + _SD_BUS_TYPE_INVALID = 0, + SD_BUS_TYPE_BYTE = 'y', + SD_BUS_TYPE_BOOLEAN = 'b', + SD_BUS_TYPE_INT16 = 'n', + SD_BUS_TYPE_UINT16 = 'q', + SD_BUS_TYPE_INT32 = 'i', + SD_BUS_TYPE_UINT32 = 'u', + SD_BUS_TYPE_INT64 = 'x', + SD_BUS_TYPE_UINT64 = 't', + SD_BUS_TYPE_DOUBLE = 'd', + SD_BUS_TYPE_STRING = 's', + SD_BUS_TYPE_OBJECT_PATH = 'o', + SD_BUS_TYPE_SIGNATURE = 'g', + SD_BUS_TYPE_UNIX_FD = 'h', + SD_BUS_TYPE_ARRAY = 'a', + SD_BUS_TYPE_VARIANT = 'v', + SD_BUS_TYPE_STRUCT = 'r', /* not actually used in signatures */ + SD_BUS_TYPE_STRUCT_BEGIN = '(', + SD_BUS_TYPE_STRUCT_END = ')', + SD_BUS_TYPE_DICT_ENTRY = 'e', /* not actually used in signatures */ + SD_BUS_TYPE_DICT_ENTRY_BEGIN = '{', + SD_BUS_TYPE_DICT_ENTRY_END = '}', +}; + +/* Endianess */ + +enum { + _SD_BUS_INVALID_ENDIAN = 0, + SD_BUS_LITTLE_ENDIAN = 'l', + SD_BUS_BIG_ENDIAN = 'B' +}; + +/* Flags */ + +enum { + SD_BUS_MESSAGE_NO_REPLY_EXPECTED = 1, + SD_BUS_MESSAGE_NO_AUTO_START = 2 +}; + +/* Header fields */ + +enum { + _SD_BUS_MESSAGE_HEADER_INVALID = 0, + SD_BUS_MESSAGE_HEADER_PATH, + SD_BUS_MESSAGE_HEADER_INTERFACE, + SD_BUS_MESSAGE_HEADER_MEMBER, + SD_BUS_MESSAGE_HEADER_ERROR_NAME, + SD_BUS_MESSAGE_HEADER_REPLY_SERIAL, + SD_BUS_MESSAGE_HEADER_DESTINATION, + SD_BUS_MESSAGE_HEADER_SENDER, + SD_BUS_MESSAGE_HEADER_SIGNATURE, + SD_BUS_MESSAGE_HEADER_UNIX_FDS, + _SD_BUS_MESSAGE_HEADER_MAX +}; + +#endif diff --git a/src/libsystemd-bus/sd-bus.c b/src/libsystemd-bus/sd-bus.c new file mode 100644 index 000000000..d80f09793 --- /dev/null +++ b/src/libsystemd-bus/sd-bus.c @@ -0,0 +1,1437 @@ +/*-*- 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "macro.h" + +#include "sd-bus.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-type.h" + +#define WQUEUE_MAX 128 + +static void bus_free(sd_bus *b) { + struct filter_callback *f; + + assert(b); + + if (b->fd >= 0) + close_nointr_nofail(b->fd); + + free(b->rbuffer); + free(b->rqueue); + free(b->wqueue); + free(b->unique_name); + + hashmap_free_free(b->reply_callbacks); + + while ((f = b->filter_callbacks)) { + LIST_REMOVE(struct filter_callback, callbacks, b->filter_callbacks, f); + free(f); + } + + free(b); +} + +static sd_bus* bus_new(void) { + sd_bus *r; + + r = new0(sd_bus, 1); + if (!r) + return NULL; + + r->n_ref = 1; + r->fd = -1; + r->message_version = 1; + + /* 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 NULL; + } + + return r; +}; + +static int hello_callback(sd_bus *bus, sd_bus_message *reply, void *userdata) { + const char *s; + int r; + + assert(bus); + assert(reply); + + bus->state = BUS_RUNNING; + + r = sd_bus_message_read(reply, "s", &s); + if (r < 0) + return r; + + bus->unique_name = strdup(s); + if (!bus->unique_name) + return -ENOMEM; + + return 1; +} + +static int bus_send_hello(sd_bus *bus) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + int r; + + assert(bus); + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "Hello", + &m); + if (r < 0) + return r; + + r = sd_bus_send_with_reply(bus, m, hello_callback, NULL, (uint64_t) -1, NULL); + if (r < 0) + return r; + + return 0; +} + +static int bus_start_running(sd_bus *bus) { + int r; + + assert(bus); + + if (bus->send_hello) { + bus->state = BUS_HELLO; + + r = bus_send_hello(bus); + if (r < 0) + return r; + } + + bus->state = BUS_RUNNING; + + return 0; +} + +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(key); + assert(value); + + l = strlen(key); + if (!strncmp(*p, key, l) != 0) + return 0; + + if ((*p)[l] != '=') + return 0; + + if (*value) + return -EINVAL; + + a = *p + l + 1; + while (*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; + } + + a += 3; + c = (char) ((x << 4) | y); + } else + c = *a; + + t = realloc(r, n + 1); + if (!t) { + free(r); + return -ENOMEM; + } + + r = t; + r[n++] = c; + } + + *p = a; + *value = r; + return 1; +} + +static void skip_address_key(const char **p) { + assert(p); + assert(*p); + + *p += strcspn(*p, ";"); +} + +static int bus_parse_next_address(sd_bus *b) { + const char *a, *p; + _cleanup_free_ char *guid = NULL; + int r; + + assert(b); + + if (!b->address) + return 0; + if (b->address[b->address_index] == 0) + return 0; + + a = b->address + b->address_index; + + zero(b->sockaddr); + b->sockaddr_size = 0; + b->peer = SD_ID128_NULL; + + if (startswith(a, "unix:")) { + _cleanup_free_ char *path = NULL, *abstract = NULL; + + p = a + 5; + 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) { + size_t l; + + 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) { + size_t l; + + l = strlen(path); + 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, path, sizeof(b->sockaddr.un.sun_path)-1); + b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l; + } + + } else if (startswith(a, "tcp:")) { + _cleanup_free_ char *host = NULL, *port = NULL, *family = NULL; + struct addrinfo hints, *result; + + p = a + 4; + 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; + + zero(hints); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + + 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); + } + + if (guid) { + r = sd_id128_from_string(guid, &b->peer); + if (r < 0) + return r; + } + + b->address_index = p - b->address; + return 1; +} + +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 int bus_write_auth(sd_bus *b) { + struct msghdr mh; + ssize_t k; + + assert(b); + assert(b->state == BUS_AUTHENTICATING); + + if (b->auth_index >= ELEMENTSOF(b->auth_iovec)) + return 0; + + zero(mh); + mh.msg_iov = b->auth_iovec + b->auth_index; + mh.msg_iovlen = ELEMENTSOF(b->auth_iovec) - b->auth_index; + + k = sendmsg(b->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + + iovec_advance(b->auth_iovec, &b->auth_index, (size_t) k); + + return 1; +} + +static int bus_auth_verify(sd_bus *b) { + char *e, *f; + sd_id128_t peer; + unsigned i; + int r; + + /* We expect two response lines: "OK", "AGREE_UNIX_FD", and + * that's it */ + + e = memmem(b->rbuffer, b->rbuffer_size, "\r\n", 2); + if (!e) + return 0; + + f = memmem(e, b->rbuffer_size - (e - (char*) b->rbuffer), "\r\n", 2); + if (!f) + return 0; + + if (e - (char*) b->rbuffer != 3 + 32) + return -EPERM; + + if (memcmp(b->rbuffer, "OK ", 3)) + return -EPERM; + + for (i = 0; i < 32; i += 2) { + int x, y; + + x = unhexchar(((char*) b->rbuffer)[3 + i]); + y = unhexchar(((char*) b->rbuffer)[3 + i + 2]); + + if (x < 0 || y < 0) + return -EINVAL; + + peer.bytes[i/2] = ((uint8_t) x << 4 | (uint8_t) y); + } + + if (!sd_id128_equal(b->peer, SD_ID128_NULL) && + !sd_id128_equal(b->peer, peer)) + return -EPERM; + + b->peer = peer; + + b->can_fds = + (f - e == sizeof("\r\nAGREE_UNIX_FD") - 1) && + memcmp(e + 2, "AGREE_UNIX_FD", sizeof("AGREE_UNIX_FD") - 1) == 0; + + if (f + 2 > (char*) b->rbuffer + b->rbuffer_size) { + b->rbuffer_size -= (f - (char*) b->rbuffer); + memmove(b->rbuffer, f + 2, b->rbuffer_size); + } + + r = bus_start_running(b); + if (r < 0) + return r; + + return 1; +} + +static int bus_read_auth(sd_bus *b) { + struct msghdr mh; + struct iovec iov; + size_t n; + ssize_t k; + int r; + + assert(b); + + r = bus_auth_verify(b); + if (r != 0) + return r; + + n = MAX(3 + 32 + 2 + sizeof("AGREE_UNIX_FD") - 1 + 2, b->rbuffer_size * 2); + + zero(iov); + iov.iov_base = (uint8_t*) b->rbuffer + b->rbuffer_size; + iov.iov_len = n - b->rbuffer_size; + + zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + + k = recvmsg(b->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + + b->rbuffer_size += k; + + r = bus_auth_verify(b); + if (r != 0) + return r; + + return 0; +} + +static int bus_start_auth(sd_bus *b) { + static const char auth_prefix[] = "\0AUTH_EXTERNAL "; + static const char auth_suffix[] = "\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n"; + + char text[20 + 1]; /* enough space for a 64bit integer plus NUL */ + size_t l; + + assert(b); + + b->state = BUS_AUTHENTICATING; + + snprintf(text, sizeof(text), "%llu", (unsigned long long) geteuid()); + char_array_0(text); + + l = strlen(text); + b->auth_uid = hexmem(text, l); + if (!b->auth_uid) + return -ENOMEM; + + b->auth_iovec[0].iov_base = (void*) auth_prefix; + b->auth_iovec[0].iov_len = sizeof(auth_prefix) -1; + b->auth_iovec[1].iov_base = (void*) b->auth_uid; + b->auth_iovec[1].iov_len = l * 2; + b->auth_iovec[2].iov_base = (void*) auth_suffix; + b->auth_iovec[2].iov_len = sizeof(auth_suffix) -1; + b->auth_size = sizeof(auth_prefix) - 1 + l * 2 + sizeof(auth_suffix) - 1; + + return bus_write_auth(b); +} + +static int bus_start_connect(sd_bus *b) { + int r; + + assert(b); + assert(b->fd < 0); + + for (;;) { + if (b->sockaddr.sa.sa_family == AF_UNSPEC) { + r = bus_parse_next_address(b); + if (r < 0) + return r; + if (r == 0) + return b->last_connect_error ? b->last_connect_error : -ECONNREFUSED; + } + + b->fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (b->fd < 0) { + b->last_connect_error = -errno; + zero(b->sockaddr); + continue; + } + + r = connect(b->fd, &b->sockaddr.sa, b->sockaddr_size); + if (r < 0) { + if (errno == EINPROGRESS) + return 0; + + b->last_connect_error = -errno; + close_nointr_nofail(b->fd); + b->fd = -1; + zero(b->sockaddr); + continue; + } + + return bus_start_auth(b); + } +} + +int sd_bus_open_system(sd_bus **ret) { + const char *e; + sd_bus *b; + int r; + + if (!ret) + return -EINVAL; + + e = getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (e) { + r = sd_bus_open_address(e, &b); + if (r < 0) + return r; + + b->send_hello = true; + *ret = b; + return r; + } + + b = bus_new(); + if (!b) + return -ENOMEM; + + b->send_hello = true; + + 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; + + r = bus_start_connect(b); + if (r < 0) { + bus_free(b); + return r; + } + + *ret = b; + return 0; +} + +int sd_bus_open_user(sd_bus **ret) { + const char *e; + sd_bus *b; + size_t l; + int r; + + if (!ret) + return -EINVAL; + + e = getenv("DBUS_SESSION_BUS_ADDRESS"); + if (e) { + r = sd_bus_open_address(e, &b); + if (r < 0) + return r; + + b->send_hello = true; + *ret = b; + return r; + } + + e = getenv("XDG_RUNTIME_DIR"); + if (!e) + return -ENOENT; + + l = strlen(e); + if (l + 4 > sizeof(b->sockaddr.un.sun_path)) + return -E2BIG; + + b = bus_new(); + if (!b) + return -ENOMEM; + + b->send_hello = true; + + 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; + + r = bus_start_connect(b); + if (r < 0) { + bus_free(b); + return r; + } + + *ret = b; + return 0; +} + +int sd_bus_open_address(const char *address, sd_bus **ret) { + sd_bus *b; + int r; + + if (!address) + return -EINVAL; + if (!ret) + return -EINVAL; + + b = bus_new(); + if (!b) + return -ENOMEM; + + b->address = strdup(address); + if (!b->address) { + bus_free(b); + return -ENOMEM; + } + + r = bus_start_connect(b); + if (r < 0) { + bus_free(b); + return r; + } + + *ret = b; + return 0; +} + +int sd_bus_open_fd(int fd, sd_bus **ret) { + sd_bus *b; + int r; + + if (fd < 0) + return -EINVAL; + if (!ret) + return -EINVAL; + + b = bus_new(); + if (!b) + return -ENOMEM; + + b->fd = fd; + fd_nonblock(b->fd, true); + fd_cloexec(b->fd, true); + + r = bus_start_auth(b); + if (r < 0) { + bus_free(b); + return r; + } + + *ret = b; + return 0; +} + +void sd_bus_close(sd_bus *bus) { + if (!bus) + return; + if (bus->fd < 0) + return; + + close_nointr_nofail(bus->fd); + bus->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_running(sd_bus *bus) { + if (!bus) + return -EINVAL; + + if (bus->fd < 0) + return -ENOTCONN; + + return bus->state == BUS_RUNNING; +} + +int sd_bus_can_send(sd_bus *bus, char type) { + + if (!bus) + return -EINVAL; + + if (type == SD_BUS_TYPE_UNIX_FD) + return bus->can_fds; + + return bus_type_is_valid(type); +} + +static int bus_seal_message(sd_bus *b, sd_bus_message *m) { + assert(m); + + if (m->sealed) + return 0; + + return message_seal(m, ++b->serial); +} + +static int message_write(sd_bus *bus, sd_bus_message *m, size_t *idx) { + struct msghdr mh; + struct iovec *iov; + ssize_t k; + size_t n; + unsigned j; + + assert(bus); + assert(m); + assert(idx); + + n = m->n_iovec * sizeof(struct iovec); + iov = alloca(n); + memcpy(iov, m->iovec, n); + + j = 0; + iovec_advance(iov, &j, *idx); + + zero(mh); + mh.msg_iov = iov; + mh.msg_iovlen = m->n_iovec; + + k = sendmsg(bus->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0) + return -errno; + + *idx += (size_t) k; + iovec_advance(iov, &j, *idx); + + return j > m->n_iovec; +} + +static int message_read_need(sd_bus *bus, size_t *need) { + uint32_t a, b; + uint8_t e; + + assert(bus); + assert(need); + + if (bus->rbuffer_size <= sizeof(struct bus_header)) { + *need = sizeof(struct bus_header); + 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 -EIO; + + *need = sizeof(struct bus_header) + ALIGN_TO(a, 8) + b; + return 0; +} + +static int message_make(sd_bus *bus, size_t size, sd_bus_message **m) { + sd_bus_message *t; + void *b = NULL; + int r; + + assert(bus); + assert(m); + assert(bus->rbuffer_size >= size); + + t = new0(sd_bus_message, 1); + if (!t) + return -ENOMEM; + + if (bus->rbuffer_size > size) { + b = memdup((const uint8_t*) bus->rbuffer + size, bus->rbuffer_size - size); + if (!b) { + free(t); + return -ENOMEM; + } + } + + t->n_ref = 1; + + t->header = bus->rbuffer; + t->free_header = true; + + t->fields = (uint8_t*) bus->rbuffer + sizeof(struct bus_header); + t->body = (uint8_t*) bus->rbuffer + sizeof(struct bus_header) + ALIGN_TO(BUS_MESSAGE_BODY_SIZE(t), 8); + + bus->rbuffer = b; + bus->rbuffer_size -= size; + + r = message_parse(t); + if (r < 0) { + sd_bus_message_unref(t); + return r; + } + + *m = t; + return 1; +} + +static int message_read(sd_bus *bus, sd_bus_message **m) { + struct msghdr mh; + struct iovec iov; + ssize_t k; + size_t need; + int r; + void *b; + + assert(bus); + assert(m); + + r = message_read_need(bus, &need); + if (r < 0) + return r; + + if (bus->rbuffer_size >= need) + return message_make(bus, need, m); + + b = realloc(bus->rbuffer, need); + if (!b) + return -ENOMEM; + + zero(iov); + iov.iov_base = (uint8_t*) bus->rbuffer + bus->rbuffer_size; + iov.iov_len = need - bus->rbuffer_size; + + zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + + k = recvmsg(bus->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + + bus->rbuffer_size += k; + + r = message_read_need(bus, &need); + if (r < 0) + return r; + + if (bus->rbuffer_size >= need) + return message_make(bus, need, m); + + return 0; +} + +static int dispatch_wqueue(sd_bus *bus) { + int r, c = 0; + + assert(bus); + + if (bus->fd < 0) + return -ENOTCONN; + + while (bus->wqueue_size > 0) { + + r = message_write(bus, bus->wqueue[0], &bus->windex); + if (r < 0) { + sd_bus_close(bus); + return r; + } else if (r == 0) + /* Wasn't fully written yet... */ + break; + else { + /* 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; + + c++; + } + } + + return c; +} + +static int dispatch_rqueue(sd_bus *bus, sd_bus_message **m) { + int r; + + assert(bus); + assert(m); + + if (bus->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 */ + r = message_read(bus, m); + if (r < 0) { + sd_bus_close(bus); + return r; + } + + return r; +} + +int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *serial) { + int r; + + if (!bus) + return -EINVAL; + if (bus->fd < 0) + return -ENOTCONN; + if (!m) + return -EINVAL; + if (m->header->version > bus->message_version) + return -EPERM; + + r = bus_seal_message(bus, m); + if (r < 0) + return r; + + if (bus->wqueue_size <= 0) { + size_t idx = 0; + + r = message_write(bus, m, &idx); + if (r < 0) { + sd_bus_close(bus); + return r; + } else if (r == 0) { + /* 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 >= 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 = SD_BUS_DEFAULT_TIMEOUT; + + return now(CLOCK_MONOTONIC) + usec; +} + +int sd_bus_send_with_reply( + sd_bus *bus, + sd_bus_message *m, + sd_message_handler_t callback, + void *userdata, + uint64_t usec, + uint64_t *serial) { + + struct reply_callback *c; + int r; + + if (!bus) + return -EINVAL; + if (!bus->fd < 0) + return -ENOTCONN; + if (!m) + return -EINVAL; + if (!callback) + return -EINVAL; + if (!m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + + r = bus_seal_message(bus, m); + if (r < 0) + return r; + + c = new(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; + } + + r = sd_bus_send(bus, m, serial); + if (r < 0) { + hashmap_remove(bus->reply_callbacks, &c->serial); + free(c); + return r; + } + + return r; +} + +int sd_bus_send_with_reply_cancel(sd_bus *bus, uint64_t serial) { + struct reply_callbacks *c; + + if (!bus) + return -EINVAL; + if (serial == 0) + return -EINVAL; + + c = hashmap_remove(bus->reply_callbacks, &serial); + if (!c) + return 0; + + free(c); + return 1; +} + +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->fd < 0) + return -ENOTCONN; + if (!m) + return -EINVAL; + if (!m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + if (sd_bus_error_is_set(error)) + return -EINVAL; + + r = sd_bus_send(bus, m, &serial); + if (r < 0) + return r; + + timeout = calc_elapse(usec); + + for (;;) { + usec_t left; + sd_bus_message *incoming; + + if (!room) { + sd_bus_message **q; + + /* 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; + } + + r = message_read(bus, &incoming); + if (r < 0) + return r; + if (r > 0) { + if (incoming->reply_serial == serial) { + /* Found a match! */ + + if (incoming->header->type == SD_BUS_MESSAGE_TYPE_METHOD_RETURN) { + *reply = 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 (timeout > 0) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (n >= timeout) + return -ETIMEDOUT; + + left = timeout - n; + } else + left = (uint64_t) -1; + + r = sd_bus_wait(bus, 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->fd < 0) + return -EINVAL; + + return bus->fd; +} + +int sd_bus_get_events(sd_bus *bus) { + int flags = 0; + + if (!bus) + return -EINVAL; + + if (bus->fd < 0) + return -EINVAL; + + if (bus->state == BUS_OPENING) + flags |= POLLOUT; + 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_process(sd_bus *bus, sd_bus_message **ret) { + sd_bus_message *m; + int r; + + if (!bus) + return -EINVAL; + if (bus->fd < 0) + return -ENOTCONN; + + if (bus->state == BUS_OPENING) { + struct pollfd p; + + zero(p); + p.fd = bus->fd; + p.events = POLLOUT; + + r = poll(&p, 1, 0); + if (r < 0) + return -errno; + + if (p.revents & (POLLOUT|POLLERR|POLLHUP)) { + int error; + socklen_t slen = sizeof(error); + + r = getsockopt(bus->fd, SOL_SOCKET, SO_ERROR, &error, &slen); + if (r < 0) + return -errno; + + if (error != 0) + bus->last_connect_error = -error; + else if (p.revents & (POLLERR|POLLHUP)) + bus->last_connect_error = -ECONNREFUSED; + else + return bus_start_auth(bus); + + /* Try next address */ + return bus_start_connect(bus); + } + + return 0; + + } else if (bus->state == BUS_AUTHENTICATING) { + + r = bus_write_auth(bus); + if (r < 0) + return r; + + r = bus_read_auth(bus); + if (r <= 0) + return r; + + return bus_start_running(bus); + + } else if (bus->state == BUS_RUNNING || bus->state == BUS_HELLO) { + struct filter_callback *l; + + r = dispatch_wqueue(bus); + if (r < 0) + return r; + + r = dispatch_rqueue(bus, &m); + if (r <= 0) + return r; + + if (m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_CALL || m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_RETURN) { + struct reply_callback *c; + + c = hashmap_remove(bus->reply_callbacks, &m->reply_serial); + if (c) { + r = c->callback(bus, m, c->userdata); + free(c); + + if (r != 0) { + sd_bus_message_unref(m); + return r < 0 ? r : 0; + } + } + } + + LIST_FOREACH(callbacks, l, bus->filter_callbacks) { + r = l->callback(bus, m, l->userdata); + if (r != 0) { + sd_bus_message_unref(m); + return r < 0 ? r : 0; + } + } + + if (ret) { + *ret = m; + return 1; + } + + sd_bus_message_unref(m); + return 0; + } + + return -ENOTSUP; +} + +int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) { + struct pollfd p; + int r, e; + struct timespec ts; + + if (!bus) + return -EINVAL; + if (bus->fd < 0) + return -ECONNREFUSED; + + e = sd_bus_get_events(bus); + if (e < 0) + return e; + + zero(p); + p.fd = bus->fd; + p.events = e; + + r = ppoll(&p, 1, timeout_usec == (uint64_t) -1 ? NULL : timespec_store(&ts, timeout_usec), NULL); + if (r < 0) + return -EINVAL; + + return r; +} + +int sd_bus_flush(sd_bus *bus) { + int r; + + if (!bus) + return -EINVAL; + if (bus->fd < 0) + return -ENOTCONN; + + if (bus->state == BUS_RUNNING && bus->wqueue_size <= 0) + return 0; + + for (;;) { + r = dispatch_wqueue(bus); + if (r < 0) + return r; + + if (bus->state == BUS_RUNNING && bus->wqueue_size <= 0) + return 0; + + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) + return r; + } +} + +int sd_bus_add_filter(sd_bus *bus, sd_message_handler_t callback, void *userdata) { + struct filter_callback *f; + + if (!bus) + return -EINVAL; + if (!callback) + return -EINVAL; + + f = new(struct filter_callback, 1); + if (!f) + return -ENOMEM; + f->callback = callback; + f->userdata = userdata; + + LIST_PREPEND(struct filter_callback, callbacks, bus->filter_callbacks, f); + return 0; +} + +int sd_bus_remove_filter(sd_bus *bus, sd_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) { + LIST_REMOVE(struct filter_callback, callbacks, bus->filter_callbacks, f); + free(f); + return 1; + } + } + + return 0; +} diff --git a/src/libsystemd-bus/sd-bus.h b/src/libsystemd-bus/sd-bus.h new file mode 100644 index 000000000..6d53d5afd --- /dev/null +++ b/src/libsystemd-bus/sd-bus.h @@ -0,0 +1,135 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdbushfoo +#define foosdbushfoo + +/*** + 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 . +***/ + +#include +#include + +#include "sd-bus-protocol.h" + +typedef struct sd_bus sd_bus; +typedef struct sd_bus_message sd_bus_message; + +typedef struct { + const char *name; + const char *message; + int need_free; +} sd_bus_error; + +typedef int (*sd_message_handler_t)(sd_bus *bus, sd_bus_message *m, void *userdata); + +/* Connections */ + +int sd_bus_open_system(sd_bus **ret); +int sd_bus_open_user(sd_bus **ret); +int sd_bus_open_address(const char *address, sd_bus **ret); +int sd_bus_open_fd(int fd, sd_bus **ret); +void sd_bus_close(sd_bus *bus); + +sd_bus *sd_bus_ref(sd_bus *bus); +sd_bus *sd_bus_unref(sd_bus *bus); + +int sd_bus_is_running(sd_bus *bus); +int sd_bus_can_send(sd_bus *bus, char type); + +int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *serial); +int sd_bus_send_with_reply(sd_bus *bus, sd_bus_message *m, sd_message_handler_t callback, void *userdata, uint64_t usec, uint64_t *serial); +int sd_bus_send_with_reply_cancel(sd_bus *bus, uint64_t serial); +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 **r); + +int sd_bus_get_fd(sd_bus *bus); +int sd_bus_get_events(sd_bus *bus); +int sd_bus_process(sd_bus *bus, sd_bus_message **r); +int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec); +int sd_bus_flush(sd_bus *bus); + +int sd_bus_add_filter(sd_bus *bus, sd_message_handler_t callback, void *userdata); +int sd_bus_remove_filter(sd_bus *bus, sd_message_handler_t callback, void *userdata); + +/* Message object */ + +int sd_bus_message_new_signal(sd_bus *bus, const char *path, const char *interface, const char *member, sd_bus_message **m); +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); +int sd_bus_message_new_method_return(sd_bus *bus, sd_bus_message *call, sd_bus_message **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* sd_bus_message_ref(sd_bus_message *m); +sd_bus_message* sd_bus_message_unref(sd_bus_message *m); + +int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type); +int sd_bus_message_get_serial(sd_bus_message *m, uint64_t *serial); +int sd_bus_message_get_reply_serial(sd_bus_message *m, uint64_t *serial); +int sd_bus_message_get_no_reply(sd_bus_message *m); + +const char *sd_bus_message_get_path(sd_bus_message *m); +const char *sd_bus_message_get_interface(sd_bus_message *m); +const char *sd_bus_message_get_member(sd_bus_message *m); +const char *sd_bus_message_get_destination(sd_bus_message *m); +const char *sd_bus_message_get_sender(sd_bus_message *m); +const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m); + +int sd_bus_message_get_uid(sd_bus_message *m, uid_t *uid); +int sd_bus_message_get_gid(sd_bus_message *m, gid_t *gid); +int sd_bus_message_get_pid(sd_bus_message *m, pid_t *pid); +int sd_bus_message_get_tid(sd_bus_message *m, pid_t *tid); + +int sd_bus_message_is_signal(sd_bus_message *m, const char *interface, const char *member); +int sd_bus_message_is_method_call(sd_bus_message *m, const char *interface, const char *member); +int sd_bus_message_is_method_error(sd_bus_message *m, const char *name); + +int sd_bus_message_set_no_reply(sd_bus_message *m, int b); +int sd_bus_message_set_destination(sd_bus_message *m, const char *destination); + +int sd_bus_message_append(sd_bus_message *m, const char *types, ...); +int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p); +int sd_bus_message_open_container(sd_bus_message *m, char type, const char *contents); +int sd_bus_message_close_container(sd_bus_message *m); + +int sd_bus_message_read_type(sd_bus_message *m, char *type, char *element, size_t *length); +int sd_bus_message_read_basic(sd_bus_message *m, char type, char element, const void **p, size_t *length); +int sd_bus_message_read(sd_bus_message *m, const char *types, ...); + +/* Bus management */ + +const char *sd_bus_get_unique_name(sd_bus *bus); +int sd_bus_request_name(sd_bus *bus, const char *name, int flags); +int sd_bus_release_name(sd_bus *bus, const char *name); +int sd_bus_list_names(sd_bus *bus, char ***l); +int sd_bus_get_owner(sd_bus *bus, const char *name, char **owner); +int sd_bus_get_owner_uid(sd_bus *bus, const char *name, uid_t *uid); +int sd_bus_get_owner_pid(sd_bus *bus, const char *name, pid_t *pid); +int sd_bus_add_match(sd_bus *bus, const char *match); +int sd_bus_remove_match(sd_bus *bus, const char *match); + +/* Error objects */ + +#define SD_BUS_ERROR_INIT (NULL, NULL, false) + +void sd_bus_error_free(sd_bus_error *e); +int sd_bus_error_set(sd_bus_error *e, const char *name, const char *format, ...); +void sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message); +int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e); +int sd_bus_error_is_set(const sd_bus_error *e); +int sd_bus_error_has_name(const sd_bus_error *e, const char *name); + +#endif diff --git a/src/libsystemd-bus/test-bus-marshal.c b/src/libsystemd-bus/test-bus-marshal.c new file mode 100644 index 000000000..08364ef48 --- /dev/null +++ b/src/libsystemd-bus/test-bus-marshal.c @@ -0,0 +1,129 @@ +/*-*- 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 . +***/ + +#include +#include +#include + +#ifdef HAVE_GLIB +#include +#endif + +#include + +#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; + const char *x, *y, *z, *a, *b, *c; + 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, "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 = message_seal(m, 4711); + assert_se(r >= 0); + + 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; + + g_type_init(); + + 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); + } + + free(buffer); + + /* r = sd_bus_message_read(m, "sas", &x, 5, &y, &z, &a, &b, &c); */ + /* assert_se(r >= 0); */ + + /* r = sd_bus_message_read(m, "a{yv}", 2, */ + /* &u, "s", &x, */ + /* &v, "s", &y); */ + + return 0; +} diff --git a/src/libsystemd-bus/test-bus-signature.c b/src/libsystemd-bus/test-bus-signature.c new file mode 100644 index 000000000..4310d62e0 --- /dev/null +++ b/src/libsystemd-bus/test-bus-signature.c @@ -0,0 +1,74 @@ +/*-*- 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 . +***/ + +#include +#include + +#include "log.h" +#include "bus-signature.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)); + + return 0; +}