X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Flibsystemd-bus%2Fsd-bus.c;h=941b33ab695f62074f8f2a2e58fb6e0bf8524d78;hb=66f931b4c818a5f9f7f36b2a0f3b0422609a6e8d;hp=049bd356a06f10deac234bf0f60e8c2c7a0c3949;hpb=b9bf7e2be93fe25e0e96e06ad436d43a1e589ed5;p=elogind.git diff --git a/src/libsystemd-bus/sd-bus.c b/src/libsystemd-bus/sd-bus.c index 049bd356a..941b33ab6 100644 --- a/src/libsystemd-bus/sd-bus.c +++ b/src/libsystemd-bus/sd-bus.c @@ -29,6 +29,7 @@ #include "util.h" #include "macro.h" +#include "missing.h" #include "sd-bus.h" #include "bus-internal.h" @@ -39,6 +40,7 @@ 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); @@ -51,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); @@ -67,6 +72,13 @@ 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); } @@ -103,16 +115,19 @@ static int hello_callback(sd_bus *bus, int error, sd_bus_message *reply, void *u assert(reply); - bus->state = BUS_RUNNING; - r = sd_bus_message_read(reply, "s", &s); if (r < 0) return r; + if (!service_name_is_valid(s) || s[0] != ':') + return -EBADMSG; + bus->unique_name = strdup(s); if (!bus->unique_name) return -ENOMEM; + bus->state = BUS_RUNNING; + return 1; } @@ -517,6 +532,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; @@ -527,6 +544,24 @@ 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"; @@ -575,8 +610,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); @@ -585,13 +625,18 @@ 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; + } } } @@ -725,17 +770,29 @@ int sd_bus_open_fd(int fd, sd_bus **ret) { return -ENOMEM; b->fd = fd; - fd_nonblock(b->fd, true); + + r = fd_nonblock(b->fd, true); + if (r < 0) + goto fail; + fd_cloexec(b->fd, true); + if (r < 0) + goto fail; + + r = bus_setup_fd(b); + if (r < 0) + goto fail; r = bus_start_auth(b); - if (r < 0) { - bus_free(b); - return r; - } + if (r < 0) + goto fail; *ret = b; return 0; + +fail: + bus_free(b); + return r; } void sd_bus_close(sd_bus *bus) { @@ -778,27 +835,37 @@ int sd_bus_is_open(sd_bus *bus) { return bus->fd >= 0; } -int sd_bus_is_running(sd_bus *bus) { +int sd_bus_can_send(sd_bus *bus, char type) { + int r; + if (!bus) return -EINVAL; - if (bus->fd < 0) - return -ENOTCONN; + if (type == SD_BUS_TYPE_UNIX_FD) { + r = bus_ensure_running(bus); + if (r < 0) + return r; - return bus->state == BUS_RUNNING; + return bus->can_fds; + } + + return bus_type_is_valid(type); } -int sd_bus_can_send(sd_bus *bus, char type) { +int sd_bus_get_peer(sd_bus *bus, sd_id128_t *peer) { + int r; if (!bus) return -EINVAL; - if (bus->state != BUS_RUNNING && bus->state != BUS_HELLO) - return -EAGAIN; + if (!peer) + return -EINVAL; - if (type == SD_BUS_TYPE_UNIX_FD) - return bus->can_fds; + r = bus_ensure_running(bus); + if (r < 0) + return r; - return bus_type_is_valid(type); + *peer = bus->peer; + return 0; } static int bus_seal_message(sd_bus *b, sd_bus_message *m) { @@ -827,6 +894,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); @@ -835,7 +914,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; @@ -901,7 +979,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); @@ -910,14 +988,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; @@ -926,6 +1008,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; } @@ -937,6 +1022,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); @@ -962,13 +1054,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; @@ -1021,7 +1150,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); @@ -1066,6 +1195,8 @@ int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *serial) { return -ENOTCONN; if (!m) return -EINVAL; + if (m->n_fds > 0 && !bus->can_fds) + return -ENOTSUP; /* If the serial number isn't kept, then we know that no reply * is expected */ @@ -1238,27 +1369,20 @@ 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); - r = sd_bus_is_running(bus); - if (r != 0) - return r; + if (bus->state == BUS_RUNNING) + return 1; for (;;) { - int k; - r = sd_bus_process(bus, NULL); - if (r < 0) return r; - - k = sd_bus_is_running(bus); - if (k != 0) - return k; - + if (bus->state == BUS_RUNNING) + return 1; if (r > 0) continue; @@ -1293,7 +1417,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; @@ -1451,7 +1575,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; @@ -1475,6 +1598,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; @@ -1526,36 +1686,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; + + if (hashmap_isempty(bus->object_callbacks)) + return 0; + + c = hashmap_get(bus->object_callbacks, m->path); + if (c) { + r = c->callback(bus, 0, m, c->userdata); + if (r != 0) + return r; - 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); + found = true; + } - r = c->callback(bus, 0, m, c->userdata); - free(c); + /* 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 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; - return process_builtin(bus, m); + 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) { @@ -1740,7 +1954,7 @@ int sd_bus_flush(sd_bus *bus) { if (bus->fd < 0) return -ENOTCONN; - r = ensure_running(bus); + r = bus_ensure_running(bus); if (r < 0) return r; @@ -1797,3 +2011,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); +}