X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Flibsystemd-bus%2Fsd-bus.c;h=df4010265f4aec97ecbadaa0a6553137f834b73d;hb=021a1e78d7621bcd844a9bf22efca88960a8e28b;hp=4bfbd4c651cdf694a997ede987a7f4da57038c7e;hpb=dafb75912a65e72c3989143f79367a652323a69f;p=elogind.git diff --git a/src/libsystemd-bus/sd-bus.c b/src/libsystemd-bus/sd-bus.c index 4bfbd4c65..df4010265 100644 --- a/src/libsystemd-bus/sd-bus.c +++ b/src/libsystemd-bus/sd-bus.c @@ -29,17 +29,18 @@ #include "util.h" #include "macro.h" +#include "missing.h" #include "sd-bus.h" #include "bus-internal.h" #include "bus-message.h" #include "bus-type.h" -static int ensure_running(sd_bus *bus); static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec); static void bus_free(sd_bus *b) { struct filter_callback *f; + struct object_callback *c; unsigned i; assert(b); @@ -52,6 +53,9 @@ static void bus_free(sd_bus *b) { free(b->auth_uid); free(b->address); + close_many(b->fds, b->n_fds); + free(b->fds); + for (i = 0; i < b->rqueue_size; i++) sd_bus_message_unref(b->rqueue[i]); free(b->rqueue); @@ -68,36 +72,101 @@ static void bus_free(sd_bus *b) { free(f); } + while ((c = hashmap_steal_first(b->object_callbacks))) { + free(c->path); + free(c); + } + + hashmap_free(b->object_callbacks); + free(b); } -static sd_bus* bus_new(void) { +int sd_bus_new(sd_bus **ret) { sd_bus *r; + if (!ret) + return -EINVAL; + r = new0(sd_bus, 1); if (!r) - return NULL; + return -ENOMEM; r->n_ref = 1; r->fd = -1; r->message_version = 1; + r->negotiate_fds = true; /* We guarantee that wqueue always has space for at least one * entry */ r->wqueue = new(sd_bus_message*, 1); if (!r->wqueue) { free(r); - return NULL; + return -ENOMEM; } - return r; -}; + *ret = r; + return 0; +} + +int sd_bus_set_address(sd_bus *bus, const char *address) { + char *a; + + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (!address) + return -EINVAL; + + a = strdup(address); + if (!a) + return -ENOMEM; + + free(bus->address); + bus->address = a; + + return 0; +} + +int sd_bus_set_fd(sd_bus *bus, int fd) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (fd < 0) + return -EINVAL; + + bus->fd = fd; + return 0; +} + +int sd_bus_set_hello(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + + bus->send_hello = !!b; + return 0; +} + +int sd_bus_set_negotiate_fds(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + + bus->negotiate_fds = !!b; + return 0; +} static int hello_callback(sd_bus *bus, int error, sd_bus_message *reply, void *userdata) { const char *s; int r; assert(bus); + assert(bus->state == BUS_HELLO); if (error != 0) return -error; @@ -126,6 +195,9 @@ static int bus_send_hello(sd_bus *bus) { assert(bus); + if (!bus->send_hello) + return 0; + r = sd_bus_message_new_method_call( bus, "org.freedesktop.DBus", @@ -140,14 +212,13 @@ static int bus_send_hello(sd_bus *bus) { if (r < 0) return r; - bus->sent_hello = true; return r; } static int bus_start_running(sd_bus *bus) { assert(bus); - if (bus->sent_hello) { + if (bus->send_hello) { bus->state = BUS_HELLO; return 1; } @@ -428,21 +499,31 @@ static int bus_write_auth(sd_bus *b) { } static int bus_auth_verify(sd_bus *b) { - char *e, *f; + char *e, *f, *start; sd_id128_t peer; unsigned i; int r; - /* We expect two response lines: "OK", "AGREE_UNIX_FD", and - * that's it */ + /* We expect two response lines: "OK" and possibly + * "AGREE_UNIX_FD" */ e = memmem(b->rbuffer, b->rbuffer_size, "\r\n", 2); if (!e) return 0; - f = memmem(e + 2, b->rbuffer_size - (e - (char*) b->rbuffer) - 2, "\r\n", 2); - if (!f) - return 0; + if (b->negotiate_fds) { + f = memmem(e + 2, b->rbuffer_size - (e - (char*) b->rbuffer) - 2, "\r\n", 2); + if (!f) + return 0; + + start = f + 2; + } else { + f = NULL; + start = e + 2; + } + + /* Nice! We got all the lines we need. First check the OK + * line */ if (e - (char*) b->rbuffer != 3 + 32) return -EPERM; @@ -468,12 +549,15 @@ static int bus_auth_verify(sd_bus *b) { 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; + /* And possibly check the second line, too */ + + if (f) + b->can_fds = + (f - e == sizeof("\r\nAGREE_UNIX_FD") - 1) && + memcmp(e + 2, "AGREE_UNIX_FD", sizeof("AGREE_UNIX_FD") - 1) == 0; - b->rbuffer_size -= (f + 2 - (char*) b->rbuffer); - memmove(b->rbuffer, f + 2, b->rbuffer_size); + b->rbuffer_size -= (start - (char*) b->rbuffer); + memmove(b->rbuffer, start, b->rbuffer_size); r = bus_start_running(b); if (r < 0) @@ -521,6 +605,8 @@ static int bus_read_auth(sd_bus *b) { k = recvmsg(b->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); if (k < 0) return errno == EAGAIN ? 0 : -errno; + if (k == 0) + return -ECONNRESET; b->rbuffer_size += k; @@ -531,12 +617,32 @@ static int bus_read_auth(sd_bus *b) { return 1; } +static int bus_setup_fd(sd_bus *b) { + int one; + + assert(b); + + /* Enable SO_PASSCRED + SO_PASSEC. We try this on any socket, + * just in case. This is actually irrelavant for */ + one = 1; + setsockopt(b->fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); + setsockopt(b->fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)); + + /* Increase the buffers to a MB */ + fd_inc_rcvbuf(b->fd, 1024*1024); + fd_inc_sndbuf(b->fd, 1024*1024); + + 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"; + static const char auth_suffix_with_unix_fd[] = "\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n"; + static const char auth_suffix_without_unix_fd[] = "\r\nBEGIN\r\n"; char text[20 + 1]; /* enough space for a 64bit integer plus NUL */ size_t l; + const char *auth_suffix; assert(b); @@ -550,12 +656,14 @@ static int bus_start_auth(sd_bus *b) { if (!b->auth_uid) return -ENOMEM; + auth_suffix = b->negotiate_fds ? auth_suffix_with_unix_fd : auth_suffix_without_unix_fd; + 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_iovec[2].iov_len = strlen(auth_suffix); b->auth_size = sizeof(auth_prefix) - 1 + l * 2 + sizeof(auth_suffix) - 1; return bus_write_auth(b); @@ -579,8 +687,13 @@ static int bus_start_connect(sd_bus *b) { 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; + goto try_again; + } + + r = bus_setup_fd(b); + if (r < 0) { + b->last_connect_error = errno; + goto try_again; } r = connect(b->fd, &b->sockaddr.sa, b->sockaddr_size); @@ -589,16 +702,64 @@ static int bus_start_connect(sd_bus *b) { return 1; b->last_connect_error = errno; - close_nointr_nofail(b->fd); - b->fd = -1; - zero(b->sockaddr); - continue; + goto try_again; } return bus_start_auth(b); + + try_again: + zero(b->sockaddr); + + if (b->fd >= 0) { + close_nointr_nofail(b->fd); + b->fd = -1; + } } } +static int bus_start_fd(sd_bus *b) { + int r; + + assert(b); + + r = fd_nonblock(b->fd, true); + if (r < 0) + return r; + + r = fd_cloexec(b->fd, true); + if (r < 0) + return r; + + r = bus_setup_fd(b); + if (r < 0) + return r; + + return bus_start_auth(b); +} + +int sd_bus_start(sd_bus *bus) { + int r; + + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + + bus->state = BUS_OPENING; + + if (bus->fd >= 0) + r = bus_start_fd(bus); + else if (bus->address) + r = bus_start_connect(bus); + else + return -EINVAL; + + if (r < 0) + return r; + + return bus_send_hello(bus); +} + int sd_bus_open_system(sd_bus **ret) { const char *e; sd_bus *b; @@ -607,35 +768,33 @@ int sd_bus_open_system(sd_bus **ret) { if (!ret) return -EINVAL; + r = sd_bus_new(&b); + if (r < 0) + return r; + e = getenv("DBUS_SYSTEM_BUS_ADDRESS"); if (e) { - r = sd_bus_open_address(e, &b); + r = sd_bus_set_address(b, e); if (r < 0) - return r; + goto fail; } else { - b = bus_new(); - if (!b) - return -ENOMEM; - 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; - } } - r = bus_send_hello(b); - if (r < 0) { - sd_bus_unref(b); - return r; - } + b->send_hello = true; + + r = sd_bus_start(b); + if (r < 0) + goto fail; *ret = b; return 0; + +fail: + bus_free(b); + return r; } int sd_bus_open_user(sd_bus **ret) { @@ -647,99 +806,45 @@ int sd_bus_open_user(sd_bus **ret) { if (!ret) return -EINVAL; + r = sd_bus_new(&b); + if (r < 0) + return r; + e = getenv("DBUS_SESSION_BUS_ADDRESS"); if (e) { - r = sd_bus_open_address(e, &b); + r = sd_bus_set_address(b, e); if (r < 0) - return r; + goto fail; } else { e = getenv("XDG_RUNTIME_DIR"); - if (!e) - return -ENOENT; + if (!e) { + r = -ENOENT; + goto fail; + } l = strlen(e); - if (l + 4 > sizeof(b->sockaddr.un.sun_path)) - return -E2BIG; - - b = bus_new(); - if (!b) - return -ENOMEM; + if (l + 4 > sizeof(b->sockaddr.un.sun_path)) { + r = -E2BIG; + goto fail; + } b->sockaddr.un.sun_family = AF_UNIX; memcpy(mempcpy(b->sockaddr.un.sun_path, e, l), "/bus", 4); b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l + 4; - - r = bus_start_connect(b); - if (r < 0) { - bus_free(b); - return r; - } - } - - r = bus_send_hello(b); - if (r < 0) { - sd_bus_unref(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; - } + b->send_hello = true; - r = bus_start_connect(b); - if (r < 0) { - bus_free(b); - return r; - } + r = sd_bus_start(b); + if (r < 0) + goto fail; *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; +fail: + bus_free(b); + return r; } void sd_bus_close(sd_bus *bus) { @@ -779,7 +884,7 @@ int sd_bus_is_open(sd_bus *bus) { if (!bus) return -EINVAL; - return bus->fd >= 0; + return bus->state != BUS_UNSET && bus->fd >= 0; } int sd_bus_can_send(sd_bus *bus, char type) { @@ -787,9 +892,14 @@ int sd_bus_can_send(sd_bus *bus, char type) { if (!bus) return -EINVAL; + if (bus->fd < 0) + return -ENOTCONN; if (type == SD_BUS_TYPE_UNIX_FD) { - r = ensure_running(bus); + if (!bus->negotiate_fds) + return 0; + + r = bus_ensure_running(bus); if (r < 0) return r; @@ -807,7 +917,7 @@ int sd_bus_get_peer(sd_bus *bus, sd_id128_t *peer) { if (!peer) return -EINVAL; - r = ensure_running(bus); + r = bus_ensure_running(bus); if (r < 0) return r; @@ -841,6 +951,18 @@ static int message_write(sd_bus *bus, sd_bus_message *m, size_t *idx) { if (*idx >= m->size) return 0; + zero(mh); + + if (m->n_fds > 0) { + struct cmsghdr *control; + control = alloca(CMSG_SPACE(sizeof(int) * m->n_fds)); + + mh.msg_control = control; + control->cmsg_level = SOL_SOCKET; + control->cmsg_type = SCM_RIGHTS; + mh.msg_controllen = control->cmsg_len = CMSG_LEN(sizeof(int) * m->n_fds); + memcpy(CMSG_DATA(control), m->fds, sizeof(int) * m->n_fds); + } n = m->n_iovec * sizeof(struct iovec); iov = alloca(n); @@ -849,7 +971,6 @@ static int message_write(sd_bus *bus, sd_bus_message *m, size_t *idx) { j = 0; iovec_advance(iov, &j, *idx); - zero(mh); mh.msg_iov = iov; mh.msg_iovlen = m->n_iovec; @@ -915,7 +1036,7 @@ static int message_read_need(sd_bus *bus, size_t *need) { static int message_make(sd_bus *bus, size_t size, sd_bus_message **m) { sd_bus_message *t; - void *b = NULL; + void *b; int r; assert(bus); @@ -924,14 +1045,18 @@ static int message_make(sd_bus *bus, size_t size, sd_bus_message **m) { assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); if (bus->rbuffer_size > size) { - b = memdup((const uint8_t*) bus->rbuffer + size, bus->rbuffer_size - size); - if (!b) { - free(t); + b = memdup((const uint8_t*) bus->rbuffer + size, + bus->rbuffer_size - size); + if (!b) return -ENOMEM; - } - } + } else + b = NULL; - r = bus_message_from_malloc(bus->rbuffer, size, &t); + r = bus_message_from_malloc(bus->rbuffer, size, + bus->fds, bus->n_fds, + bus->ucred_valid ? &bus->ucred : NULL, + bus->label[0] ? bus->label : NULL, + &t); if (r < 0) { free(b); return r; @@ -940,6 +1065,9 @@ static int message_make(sd_bus *bus, size_t size, sd_bus_message **m) { bus->rbuffer = b; bus->rbuffer_size -= size; + bus->fds = NULL; + bus->n_fds = 0; + *m = t; return 1; } @@ -951,6 +1079,13 @@ static int message_read(sd_bus *bus, sd_bus_message **m) { size_t need; int r; void *b; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX) + + CMSG_SPACE(sizeof(struct ucred)) + + CMSG_SPACE(NAME_MAX)]; /*selinux label */ + } control; + struct cmsghdr *cmsg; assert(bus); assert(m); @@ -976,13 +1111,50 @@ static int message_read(sd_bus *bus, sd_bus_message **m) { zero(mh); mh.msg_iov = &iov; mh.msg_iovlen = 1; + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); - k = recvmsg(bus->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + k = recvmsg(bus->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); if (k < 0) return errno == EAGAIN ? 0 : -errno; + if (k == 0) + return -ECONNRESET; bus->rbuffer_size += k; + for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + int n, *f; + + n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + f = realloc(bus->fds, sizeof(int) + (bus->n_fds + n)); + if (!f) { + close_many((int*) CMSG_DATA(cmsg), n); + return -ENOMEM; + } + + memcpy(f + bus->n_fds, CMSG_DATA(cmsg), n * sizeof(int)); + bus->fds = f; + bus->n_fds += n; + } else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { + + memcpy(&bus->ucred, CMSG_DATA(cmsg), sizeof(struct ucred)); + bus->ucred_valid = true; + + } else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_SECURITY) { + + size_t l; + l = cmsg->cmsg_len - CMSG_LEN(0); + memcpy(&bus->label, CMSG_DATA(cmsg), l); + bus->label[l] = 0; + } + } + r = message_read_need(bus, &need); if (r < 0) return r; @@ -1035,7 +1207,7 @@ static int dispatch_wqueue(sd_bus *bus) { } static int dispatch_rqueue(sd_bus *bus, sd_bus_message **m) { - sd_bus_message *z; + sd_bus_message *z = NULL; int r, ret = 0; assert(bus); @@ -1076,11 +1248,21 @@ int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *serial) { if (!bus) return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; if (bus->fd < 0) return -ENOTCONN; if (!m) return -EINVAL; + if (m->n_fds > 0) { + r = sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD); + if (r < 0) + return r; + if (r == 0) + return -ENOTSUP; + } + /* If the serial number isn't kept, then we know that no reply * is expected */ if (!serial && !m->sealed) @@ -1175,6 +1357,8 @@ int sd_bus_send_with_reply( if (!bus) return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; if (bus->fd < 0) return -ENOTCONN; if (!m) @@ -1252,11 +1436,16 @@ int sd_bus_send_with_reply_cancel(sd_bus *bus, uint64_t serial) { return 1; } -static int ensure_running(sd_bus *bus) { +int bus_ensure_running(sd_bus *bus) { int r; assert(bus); + if (bus->fd < 0) + return -ENOTCONN; + if (bus->state == BUS_UNSET) + return -ENOTCONN; + if (bus->state == BUS_RUNNING) return 1; @@ -1291,6 +1480,8 @@ int sd_bus_send_with_reply_and_block( return -EINVAL; if (bus->fd < 0) return -ENOTCONN; + if (bus->state == BUS_UNSET) + return -ENOTCONN; if (!m) return -EINVAL; if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) @@ -1300,7 +1491,7 @@ int sd_bus_send_with_reply_and_block( if (bus_error_is_dirty(error)) return -EINVAL; - r = ensure_running(bus); + r = bus_ensure_running(bus); if (r < 0) return r; @@ -1409,6 +1600,8 @@ int sd_bus_get_events(sd_bus *bus) { if (!bus) return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; if (bus->fd < 0) return -ENOTCONN; @@ -1438,6 +1631,8 @@ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) { return -EINVAL; if (!timeout_usec) return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; if (bus->fd < 0) return -ENOTCONN; @@ -1458,7 +1653,6 @@ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) { } static int process_timeout(sd_bus *bus) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; struct reply_callback *c; usec_t n; int r; @@ -1482,6 +1676,43 @@ static int process_timeout(sd_bus *bus) { return r < 0 ? r : 1; } +static int process_reply(sd_bus *bus, sd_bus_message *m) { + struct reply_callback *c; + int r; + + assert(bus); + assert(m); + + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_RETURN && + m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_ERROR) + return 0; + + c = hashmap_remove(bus->reply_callbacks, &m->reply_serial); + if (!c) + return 0; + + if (c->timeout != 0) + prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); + + r = c->callback(bus, 0, m, c->userdata); + free(c); + + return r; +} + +static int process_filter(sd_bus *bus, sd_bus_message *m) { + struct filter_callback *l; + int r; + + LIST_FOREACH(callbacks, l, bus->filter_callbacks) { + r = l->callback(bus, 0, m, l->userdata); + if (r != 0) + return r; + } + + return 0; +} + static int process_builtin(sd_bus *bus, sd_bus_message *m) { _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; int r; @@ -1533,36 +1764,90 @@ static int process_builtin(sd_bus *bus, sd_bus_message *m) { return 1; } -static int process_message(sd_bus *bus, sd_bus_message *m) { - struct filter_callback *l; +static int process_object(sd_bus *bus, sd_bus_message *m) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_INIT; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + struct object_callback *c; + char *p; int r; + bool found = false; assert(bus); assert(m); - if (m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_RETURN || m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_ERROR) { - struct reply_callback *c; + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return 0; - c = hashmap_remove(bus->reply_callbacks, &m->reply_serial); - if (c) { - if (c->timeout != 0) - prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); + if (hashmap_isempty(bus->object_callbacks)) + return 0; - r = c->callback(bus, 0, m, c->userdata); - free(c); + c = hashmap_get(bus->object_callbacks, m->path); + if (c) { + r = c->callback(bus, 0, m, c->userdata); + if (r != 0) + return r; + found = true; + } + + /* Look for fallback prefixes */ + p = strdupa(m->path); + for (;;) { + char *e; + + e = strrchr(p, '/'); + if (e == p || !e) + break; + + *e = 0; + + c = hashmap_get(bus->object_callbacks, p); + if (c && c->is_fallback) { + r = c->callback(bus, 0, m, c->userdata); if (r != 0) return r; + + found = true; } } - LIST_FOREACH(callbacks, l, bus->filter_callbacks) { - r = l->callback(bus, 0, m, l->userdata); - if (r != 0) - return r; - } + if (!found) + return 0; + + sd_bus_error_set(&error, + "org.freedesktop.DBus.Error.UnknownMethod", + "Unknown method '%s' or interface '%s'.", m->member, m->interface); + + r = sd_bus_message_new_method_error(bus, m, &error, &reply); + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; - return process_builtin(bus, m); + return 1; +} + +static int process_message(sd_bus *bus, sd_bus_message *m) { + int r; + + assert(bus); + assert(m); + + r = process_reply(bus, m); + if (r != 0) + return r; + + r = process_filter(bus, m); + if (r != 0) + return r; + + r = process_builtin(bus, m); + if (r != 0) + return r; + + return process_object(bus, m); } int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { @@ -1575,6 +1860,8 @@ int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { if (!bus) return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; if (bus->fd < 0) return -ENOTCONN; @@ -1731,6 +2018,8 @@ int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) { if (!bus) return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; if (bus->fd < 0) return -ENOTCONN; if (bus->rqueue_size > 0) @@ -1744,10 +2033,12 @@ int sd_bus_flush(sd_bus *bus) { if (!bus) return -EINVAL; + if (bus->state == BUS_UNSET) + return -ENOTCONN; if (bus->fd < 0) return -ENOTCONN; - r = ensure_running(bus); + r = bus_ensure_running(bus); if (r < 0) return r; @@ -1804,3 +2095,95 @@ int sd_bus_remove_filter(sd_bus *bus, sd_message_handler_t callback, void *userd return 0; } + +static int bus_add_object( + sd_bus *bus, + bool fallback, + const char *path, + sd_message_handler_t callback, + void *userdata) { + + struct object_callback *c; + int r; + + if (!bus) + return -EINVAL; + if (!path) + return -EINVAL; + if (!callback) + return -EINVAL; + + r = hashmap_ensure_allocated(&bus->object_callbacks, string_hash_func, string_compare_func); + if (r < 0) + return r; + + c = new(struct object_callback, 1); + if (!c) + return -ENOMEM; + + c->path = strdup(path); + if (!path) { + free(c); + return -ENOMEM; + } + + c->callback = callback; + c->userdata = userdata; + c->is_fallback = fallback; + + r = hashmap_put(bus->object_callbacks, c->path, c); + if (r < 0) { + free(c->path); + free(c); + return r; + } + + return 0; +} + +static int bus_remove_object( + sd_bus *bus, + bool fallback, + const char *path, + sd_message_handler_t callback, + void *userdata) { + + struct object_callback *c; + + if (!bus) + return -EINVAL; + if (!path) + return -EINVAL; + if (!callback) + return -EINVAL; + + c = hashmap_get(bus->object_callbacks, path); + if (!c) + return 0; + + if (c->callback != callback || c->userdata != userdata || c->is_fallback != fallback) + return 0; + + assert_se(c == hashmap_remove(bus->object_callbacks, c->path)); + + free(c->path); + free(c); + + return 1; +} + +int sd_bus_add_object(sd_bus *bus, const char *path, sd_message_handler_t callback, void *userdata) { + return bus_add_object(bus, false, path, callback, userdata); +} + +int sd_bus_remove_object(sd_bus *bus, const char *path, sd_message_handler_t callback, void *userdata) { + return bus_remove_object(bus, false, path, callback, userdata); +} + +int sd_bus_add_fallback(sd_bus *bus, const char *prefix, sd_message_handler_t callback, void *userdata) { + return bus_add_object(bus, true, prefix, callback, userdata); +} + +int sd_bus_remove_fallback(sd_bus *bus, const char *prefix, sd_message_handler_t callback, void *userdata) { + return bus_remove_object(bus, true, prefix, callback, userdata); +}