| /* |
| * Embedded Linux library |
| * Copyright (C) 2011-2014 Intel Corporation |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <netdb.h> |
| #include <errno.h> |
| |
| #include "util.h" |
| #include "io.h" |
| #include "idle.h" |
| #include "queue.h" |
| #include "hashmap.h" |
| #include "dbus.h" |
| #include "private.h" |
| #include "useful.h" |
| #include "dbus-private.h" |
| |
| #define DEFAULT_SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" |
| |
| #define DBUS_SERVICE_DBUS "org.freedesktop.DBus" |
| |
| #define DBUS_PATH_DBUS "/org/freedesktop/DBus" |
| |
| #define DBUS_MAXIMUM_MATCH_RULE_LENGTH 1024 |
| |
| enum auth_state { |
| WAITING_FOR_OK, |
| WAITING_FOR_AGREE_UNIX_FD, |
| SETUP_DONE |
| }; |
| |
| struct l_dbus_ops { |
| char version; |
| bool (*send_message)(struct l_dbus *bus, |
| struct l_dbus_message *message); |
| struct l_dbus_message *(*recv_message)(struct l_dbus *bus); |
| void (*free)(struct l_dbus *bus); |
| struct _dbus_name_ops name_ops; |
| struct _dbus_filter_ops filter_ops; |
| uint32_t (*name_acquire)(struct l_dbus *dbus, const char *name, |
| bool allow_replacement, bool replace_existing, |
| bool queue, l_dbus_name_acquire_func_t callback, |
| void *user_data); |
| }; |
| |
| struct l_dbus { |
| struct l_io *io; |
| char *guid; |
| bool negotiate_unix_fd; |
| bool support_unix_fd; |
| bool is_ready; |
| char *unique_name; |
| unsigned int next_id; |
| uint32_t next_serial; |
| struct l_queue *message_queue; |
| struct l_hashmap *message_list; |
| struct l_hashmap *signal_list; |
| l_dbus_ready_func_t ready_handler; |
| l_dbus_destroy_func_t ready_destroy; |
| void *ready_data; |
| l_dbus_disconnect_func_t disconnect_handler; |
| l_dbus_destroy_func_t disconnect_destroy; |
| void *disconnect_data; |
| l_dbus_debug_func_t debug_handler; |
| l_dbus_destroy_func_t debug_destroy; |
| void *debug_data; |
| struct _dbus_object_tree *tree; |
| struct _dbus_name_cache *name_cache; |
| struct _dbus_filter *filter; |
| bool name_notify_enabled; |
| |
| const struct l_dbus_ops *driver; |
| }; |
| |
| struct l_dbus_classic { |
| struct l_dbus super; |
| void *auth_command; |
| enum auth_state auth_state; |
| bool skip_hello; |
| struct l_hashmap *match_strings; |
| int *fd_buf; |
| unsigned int num_fds; |
| }; |
| |
| struct message_callback { |
| uint32_t serial; |
| struct l_dbus_message *message; |
| l_dbus_message_func_t callback; |
| l_dbus_destroy_func_t destroy; |
| void *user_data; |
| }; |
| |
| struct signal_callback { |
| unsigned int id; |
| l_dbus_message_func_t callback; |
| l_dbus_destroy_func_t destroy; |
| void *user_data; |
| }; |
| |
| static void message_queue_destroy(void *data) |
| { |
| struct message_callback *callback = data; |
| |
| l_dbus_message_unref(callback->message); |
| |
| if (callback->destroy) |
| callback->destroy(callback->user_data); |
| |
| l_free(callback); |
| } |
| |
| static void message_list_destroy(void *value) |
| { |
| message_queue_destroy(value); |
| } |
| |
| static void signal_list_destroy(void *value) |
| { |
| struct signal_callback *callback = value; |
| |
| if (callback->destroy) |
| callback->destroy(callback->user_data); |
| |
| l_free(callback); |
| } |
| |
| static bool message_write_handler(struct l_io *io, void *user_data) |
| { |
| struct l_dbus *dbus = user_data; |
| struct l_dbus_message *message; |
| struct message_callback *callback; |
| const void *header, *body; |
| size_t header_size, body_size; |
| |
| callback = l_queue_pop_head(dbus->message_queue); |
| if (!callback) |
| return false; |
| |
| message = callback->message; |
| if (_dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL && |
| callback->callback == NULL) |
| l_dbus_message_set_no_reply(message, true); |
| |
| _dbus_message_set_serial(message, callback->serial); |
| |
| if (!dbus->driver->send_message(dbus, message)) { |
| message_queue_destroy(callback); |
| return false; |
| } |
| |
| header = _dbus_message_get_header(message, &header_size); |
| body = _dbus_message_get_body(message, &body_size); |
| l_util_hexdump_two(false, header, header_size, body, body_size, |
| dbus->debug_handler, dbus->debug_data); |
| |
| if (callback->callback == NULL) { |
| message_queue_destroy(callback); |
| goto done; |
| } |
| |
| l_hashmap_insert(dbus->message_list, |
| L_UINT_TO_PTR(callback->serial), callback); |
| |
| done: |
| if (l_queue_isempty(dbus->message_queue)) |
| return false; |
| |
| /* Only continue sending messges if the connection is ready */ |
| return dbus->is_ready; |
| } |
| |
| static void handle_method_return(struct l_dbus *dbus, |
| struct l_dbus_message *message) |
| { |
| struct message_callback *callback; |
| uint32_t reply_serial; |
| |
| reply_serial = _dbus_message_get_reply_serial(message); |
| if (reply_serial == 0) |
| return; |
| |
| callback = l_hashmap_remove(dbus->message_list, |
| L_UINT_TO_PTR(reply_serial)); |
| if (!callback) |
| return; |
| |
| if (callback->callback) |
| callback->callback(message, callback->user_data); |
| |
| message_queue_destroy(callback); |
| } |
| |
| static void handle_error(struct l_dbus *dbus, struct l_dbus_message *message) |
| { |
| struct message_callback *callback; |
| uint32_t reply_serial; |
| |
| reply_serial = _dbus_message_get_reply_serial(message); |
| if (reply_serial == 0) |
| return; |
| |
| callback = l_hashmap_remove(dbus->message_list, |
| L_UINT_TO_PTR(reply_serial)); |
| if (!callback) |
| return; |
| |
| if (callback->callback) |
| callback->callback(message, callback->user_data); |
| |
| message_queue_destroy(callback); |
| } |
| |
| static void process_signal(const void *key, void *value, void *user_data) |
| { |
| struct signal_callback *callback = value; |
| struct l_dbus_message *message = user_data; |
| |
| if (callback->callback) |
| callback->callback(message, callback->user_data); |
| } |
| |
| static void handle_signal(struct l_dbus *dbus, struct l_dbus_message *message) |
| { |
| l_hashmap_foreach(dbus->signal_list, process_signal, message); |
| } |
| |
| static bool message_read_handler(struct l_io *io, void *user_data) |
| { |
| struct l_dbus *dbus = user_data; |
| struct l_dbus_message *message; |
| const void *header, *body; |
| size_t header_size, body_size; |
| enum dbus_message_type msgtype; |
| |
| message = dbus->driver->recv_message(dbus); |
| if (!message) |
| return true; |
| |
| header = _dbus_message_get_header(message, &header_size); |
| body = _dbus_message_get_body(message, &body_size); |
| l_util_hexdump_two(true, header, header_size, body, body_size, |
| dbus->debug_handler, dbus->debug_data); |
| |
| msgtype = _dbus_message_get_type(message); |
| |
| switch (msgtype) { |
| case DBUS_MESSAGE_TYPE_METHOD_RETURN: |
| handle_method_return(dbus, message); |
| break; |
| case DBUS_MESSAGE_TYPE_ERROR: |
| handle_error(dbus, message); |
| break; |
| case DBUS_MESSAGE_TYPE_SIGNAL: |
| handle_signal(dbus, message); |
| break; |
| case DBUS_MESSAGE_TYPE_METHOD_CALL: |
| if (!_dbus_object_tree_dispatch(dbus->tree, dbus, message)) { |
| struct l_dbus_message *error; |
| |
| error = l_dbus_message_new_error(message, |
| "org.freedesktop.DBus.Error.NotFound", |
| "No matching method found"); |
| l_dbus_send(dbus, error); |
| } |
| |
| break; |
| } |
| |
| l_dbus_message_unref(message); |
| |
| return true; |
| } |
| |
| static uint32_t send_message(struct l_dbus *dbus, bool priority, |
| struct l_dbus_message *message, |
| l_dbus_message_func_t function, |
| void *user_data, l_dbus_destroy_func_t destroy) |
| { |
| struct message_callback *callback; |
| enum dbus_message_type type; |
| const char *path; |
| |
| type = _dbus_message_get_type(message); |
| |
| if ((type == DBUS_MESSAGE_TYPE_METHOD_RETURN || |
| type == DBUS_MESSAGE_TYPE_ERROR) && |
| _dbus_message_get_reply_serial(message) == 0) { |
| l_dbus_message_unref(message); |
| return 0; |
| } |
| |
| /* Default empty signature for method return messages */ |
| if (type == DBUS_MESSAGE_TYPE_METHOD_RETURN && |
| !l_dbus_message_get_signature(message)) |
| l_dbus_message_set_arguments(message, ""); |
| |
| callback = l_new(struct message_callback, 1); |
| |
| callback->serial = dbus->next_serial++; |
| callback->message = message; |
| callback->callback = function; |
| callback->destroy = destroy; |
| callback->user_data = user_data; |
| |
| if (priority) { |
| l_queue_push_head(dbus->message_queue, callback); |
| |
| l_io_set_write_handler(dbus->io, message_write_handler, |
| dbus, NULL); |
| |
| return callback->serial; |
| } |
| |
| path = l_dbus_message_get_path(message); |
| if (path) |
| _dbus_object_tree_signals_flush(dbus, path); |
| |
| l_queue_push_tail(dbus->message_queue, callback); |
| |
| if (dbus->is_ready) |
| l_io_set_write_handler(dbus->io, message_write_handler, |
| dbus, NULL); |
| |
| return callback->serial; |
| } |
| |
| static void bus_ready(struct l_dbus *dbus) |
| { |
| dbus->is_ready = true; |
| |
| if (dbus->ready_handler) |
| dbus->ready_handler(dbus->ready_data); |
| |
| l_io_set_read_handler(dbus->io, message_read_handler, dbus, NULL); |
| |
| /* Check for messages added before the connection was ready */ |
| if (l_queue_isempty(dbus->message_queue)) |
| return; |
| |
| l_io_set_write_handler(dbus->io, message_write_handler, dbus, NULL); |
| } |
| |
| static void hello_callback(struct l_dbus_message *message, void *user_data) |
| { |
| struct l_dbus *dbus = user_data; |
| const char *signature; |
| const char *unique_name; |
| |
| signature = l_dbus_message_get_signature(message); |
| if (!signature || strcmp(signature, "s")) { |
| close(l_io_get_fd(dbus->io)); |
| return; |
| } |
| |
| if (!l_dbus_message_get_arguments(message, "s", &unique_name)) { |
| close(l_io_get_fd(dbus->io)); |
| return; |
| } |
| |
| dbus->unique_name = l_strdup(unique_name); |
| |
| bus_ready(dbus); |
| } |
| |
| static bool auth_write_handler(struct l_io *io, void *user_data) |
| { |
| struct l_dbus_classic *classic = user_data; |
| struct l_dbus *dbus = &classic->super; |
| ssize_t written, len; |
| int fd; |
| |
| fd = l_io_get_fd(io); |
| |
| if (!classic->auth_command) |
| return false; |
| |
| len = strlen(classic->auth_command); |
| if (!len) |
| return false; |
| |
| written = L_TFR(send(fd, classic->auth_command, len, 0)); |
| if (written < 0) |
| return false; |
| |
| l_util_hexdump(false, classic->auth_command, written, |
| dbus->debug_handler, dbus->debug_data); |
| |
| if (written < len) { |
| memmove(classic->auth_command, classic->auth_command + written, |
| len + 1 - written); |
| return true; |
| } |
| |
| l_free(classic->auth_command); |
| classic->auth_command = NULL; |
| |
| if (classic->auth_state == SETUP_DONE) { |
| struct l_dbus_message *message; |
| |
| if (classic->skip_hello) { |
| bus_ready(dbus); |
| return true; |
| } |
| |
| l_io_set_read_handler(dbus->io, message_read_handler, |
| dbus, NULL); |
| |
| message = l_dbus_message_new_method_call(dbus, |
| DBUS_SERVICE_DBUS, |
| DBUS_PATH_DBUS, |
| L_DBUS_INTERFACE_DBUS, |
| "Hello"); |
| l_dbus_message_set_arguments(message, ""); |
| |
| send_message(dbus, true, message, hello_callback, dbus, NULL); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool auth_read_handler(struct l_io *io, void *user_data) |
| { |
| struct l_dbus_classic *classic = user_data; |
| struct l_dbus *dbus = &classic->super; |
| char buffer[64]; |
| char *ptr, *end; |
| ssize_t offset, len; |
| int fd; |
| |
| fd = l_io_get_fd(io); |
| |
| ptr = buffer; |
| offset = 0; |
| |
| while (1) { |
| len = L_TFR(recv(fd, ptr + offset, |
| sizeof(buffer) - offset, |
| MSG_DONTWAIT)); |
| if (len < 0) { |
| if (errno != EAGAIN) |
| return false; |
| |
| break; |
| } |
| |
| offset += len; |
| } |
| |
| ptr = buffer; |
| len = offset; |
| |
| if (!ptr || len < 3) |
| return true; |
| |
| end = strstr(ptr, "\r\n"); |
| if (!end) |
| return true; |
| |
| if (end - ptr + 2 != len) |
| return true; |
| |
| l_util_hexdump(true, ptr, len, dbus->debug_handler, dbus->debug_data); |
| |
| *end = '\0'; |
| |
| switch (classic->auth_state) { |
| case WAITING_FOR_OK: |
| if (!strncmp(ptr, "OK ", 3)) { |
| enum auth_state state; |
| const char *command; |
| |
| if (dbus->negotiate_unix_fd) { |
| command = "NEGOTIATE_UNIX_FD\r\n"; |
| state = WAITING_FOR_AGREE_UNIX_FD; |
| } else { |
| command = "BEGIN\r\n"; |
| state = SETUP_DONE; |
| } |
| |
| l_free(dbus->guid); |
| dbus->guid = l_strdup(ptr + 3); |
| |
| classic->auth_command = l_strdup(command); |
| classic->auth_state = state; |
| break; |
| } else if (!strncmp(ptr, "REJECTED ", 9)) { |
| static const char *command = "AUTH ANONYMOUS\r\n"; |
| |
| dbus->negotiate_unix_fd = true; |
| |
| classic->auth_command = l_strdup(command); |
| classic->auth_state = WAITING_FOR_OK; |
| } |
| break; |
| |
| case WAITING_FOR_AGREE_UNIX_FD: |
| if (!strncmp(ptr, "AGREE_UNIX_FD", 13)) { |
| static const char *command = "BEGIN\r\n"; |
| |
| dbus->support_unix_fd = true; |
| |
| classic->auth_command = l_strdup(command); |
| classic->auth_state = SETUP_DONE; |
| break; |
| } else if (!strncmp(ptr, "ERROR", 5)) { |
| static const char *command = "BEGIN\r\n"; |
| |
| dbus->support_unix_fd = false; |
| |
| classic->auth_command = l_strdup(command); |
| classic->auth_state = SETUP_DONE; |
| break; |
| } |
| break; |
| |
| case SETUP_DONE: |
| break; |
| } |
| |
| l_io_set_write_handler(io, auth_write_handler, dbus, NULL); |
| |
| return true; |
| } |
| |
| static void disconnect_handler(struct l_io *io, void *user_data) |
| { |
| struct l_dbus *dbus = user_data; |
| |
| dbus->is_ready = false; |
| |
| l_util_debug(dbus->debug_handler, dbus->debug_data, "disconnect"); |
| |
| if (dbus->disconnect_handler) |
| dbus->disconnect_handler(dbus->disconnect_data); |
| } |
| |
| static void dbus_init(struct l_dbus *dbus, int fd) |
| { |
| dbus->io = l_io_new(fd); |
| l_io_set_close_on_destroy(dbus->io, true); |
| l_io_set_disconnect_handler(dbus->io, disconnect_handler, dbus, NULL); |
| |
| dbus->is_ready = false; |
| dbus->next_id = 1; |
| dbus->next_serial = 1; |
| |
| dbus->message_queue = l_queue_new(); |
| dbus->message_list = l_hashmap_new(); |
| dbus->signal_list = l_hashmap_new(); |
| |
| dbus->tree = _dbus_object_tree_new(); |
| } |
| |
| static void classic_free(struct l_dbus *dbus) |
| { |
| struct l_dbus_classic *classic = |
| l_container_of(dbus, struct l_dbus_classic, super); |
| unsigned int i; |
| |
| for (i = 0; i < classic->num_fds; i++) |
| close(classic->fd_buf[i]); |
| l_free(classic->fd_buf); |
| |
| l_free(classic->auth_command); |
| l_hashmap_destroy(classic->match_strings, l_free); |
| l_free(classic); |
| } |
| |
| static bool classic_send_message(struct l_dbus *dbus, |
| struct l_dbus_message *message) |
| { |
| int fd = l_io_get_fd(dbus->io); |
| struct msghdr msg; |
| struct iovec iov[2], *iovpos; |
| ssize_t r; |
| int *fds = NULL; |
| uint32_t num_fds = 0; |
| struct cmsghdr *cmsg; |
| int iovlen; |
| |
| iov[0].iov_base = _dbus_message_get_header(message, &iov[0].iov_len); |
| iov[1].iov_base = _dbus_message_get_body(message, &iov[1].iov_len); |
| |
| if (dbus->support_unix_fd) |
| fds = _dbus_message_get_fds(message, &num_fds); |
| |
| iovpos = iov; |
| iovlen = 2; |
| |
| while (1) { |
| memset(&msg, 0, sizeof(msg)); |
| msg.msg_iov = iovpos; |
| msg.msg_iovlen = iovlen; |
| |
| if (num_fds) { |
| msg.msg_control = |
| alloca(CMSG_SPACE(num_fds * sizeof(int))); |
| msg.msg_controllen = CMSG_LEN(num_fds * sizeof(int)); |
| |
| cmsg = CMSG_FIRSTHDR(&msg); |
| cmsg->cmsg_len = msg.msg_controllen; |
| cmsg->cmsg_level = SOL_SOCKET; |
| cmsg->cmsg_type = SCM_RIGHTS; |
| memcpy(CMSG_DATA(cmsg), fds, num_fds * sizeof(int)); |
| } |
| |
| r = L_TFR(sendmsg(fd, &msg, 0)); |
| if (r < 0) |
| return false; |
| |
| while ((size_t) r >= iovpos->iov_len) { |
| r -= iovpos->iov_len; |
| iovpos++; |
| iovlen--; |
| |
| if (!iovlen) |
| break; |
| } |
| |
| if (!iovlen) |
| break; |
| |
| iovpos->iov_base += r; |
| iovpos->iov_len -= r; |
| |
| /* The FDs have been transmitted, don't retransmit */ |
| num_fds = 0; |
| } |
| |
| return true; |
| } |
| |
| static struct l_dbus_message *classic_recv_message(struct l_dbus *dbus) |
| { |
| struct l_dbus_classic *classic = |
| l_container_of(dbus, struct l_dbus_classic, super); |
| int fd = l_io_get_fd(dbus->io); |
| struct dbus_header hdr; |
| struct msghdr msg; |
| struct iovec iov[2], *iovpos; |
| struct cmsghdr *cmsg; |
| ssize_t len, r; |
| void *header, *body; |
| size_t header_size, body_size; |
| union { |
| uint8_t bytes[CMSG_SPACE(16 * sizeof(int))]; |
| struct cmsghdr align; |
| } fd_buf; |
| int *fds = NULL; |
| uint32_t num_fds = 0; |
| int iovlen; |
| struct l_dbus_message *message; |
| unsigned int i; |
| |
| len = recv(fd, &hdr, DBUS_HEADER_SIZE, MSG_PEEK | MSG_DONTWAIT); |
| if (len != DBUS_HEADER_SIZE) |
| return NULL; |
| |
| header_size = align_len(DBUS_HEADER_SIZE + hdr.dbus1.field_length, 8); |
| header = l_malloc(header_size); |
| |
| body_size = hdr.dbus1.body_length; |
| body = l_malloc(body_size); |
| |
| iov[0].iov_base = header; |
| iov[0].iov_len = header_size; |
| iov[1].iov_base = body; |
| iov[1].iov_len = body_size; |
| |
| iovpos = iov; |
| iovlen = 2; |
| |
| while (1) { |
| memset(&msg, 0, sizeof(msg)); |
| msg.msg_iov = iovpos; |
| msg.msg_iovlen = iovlen; |
| msg.msg_control = &fd_buf; |
| msg.msg_controllen = sizeof(fd_buf); |
| |
| r = L_TFR(recvmsg(fd, &msg, |
| MSG_CMSG_CLOEXEC | MSG_WAITALL)); |
| if (r < 0) |
| goto cmsg_fail; |
| |
| for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; |
| cmsg = CMSG_NXTHDR(&msg, cmsg)) { |
| if (cmsg->cmsg_level != SOL_SOCKET || |
| cmsg->cmsg_type != SCM_RIGHTS) |
| continue; |
| |
| num_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); |
| fds = (void *) CMSG_DATA(cmsg); |
| |
| /* Set FD_CLOEXEC on all file descriptors */ |
| for (i = 0; i < num_fds; i++) { |
| long flags; |
| |
| flags = fcntl(fds[i], F_GETFD, NULL); |
| if (flags < 0) |
| continue; |
| |
| if (!(flags & FD_CLOEXEC)) |
| fcntl(fds[i], F_SETFD, |
| flags | FD_CLOEXEC); |
| } |
| |
| classic->fd_buf = l_realloc(classic->fd_buf, |
| (classic->num_fds + num_fds) * |
| sizeof(int)); |
| memcpy(classic->fd_buf + classic->num_fds, fds, |
| num_fds * sizeof(int)); |
| classic->num_fds += num_fds; |
| } |
| |
| while ((size_t) r >= iovpos->iov_len) { |
| r -= iovpos->iov_len; |
| iovpos++; |
| iovlen--; |
| |
| if (!iovlen) |
| break; |
| } |
| |
| if (!iovlen) |
| break; |
| |
| iovpos->iov_base += r; |
| iovpos->iov_len -= r; |
| } |
| |
| if (hdr.endian != DBUS_NATIVE_ENDIAN) { |
| l_util_debug(dbus->debug_handler, |
| dbus->debug_data, "Endianness incorrect"); |
| goto bad_msg; |
| } |
| |
| if (hdr.version != 1) { |
| l_util_debug(dbus->debug_handler, |
| dbus->debug_data, "Protocol version incorrect"); |
| goto bad_msg; |
| } |
| |
| num_fds = _dbus_message_unix_fds_from_header(header, header_size); |
| if (num_fds > classic->num_fds) |
| goto bad_msg; |
| |
| message = dbus_message_build(header, header_size, body, body_size, |
| classic->fd_buf, num_fds); |
| |
| if (message && num_fds) { |
| if (classic->num_fds > num_fds) { |
| memmove(classic->fd_buf, classic->fd_buf + num_fds, |
| (classic->num_fds - num_fds) * sizeof(int)); |
| classic->num_fds -= num_fds; |
| } else { |
| l_free(classic->fd_buf); |
| |
| classic->fd_buf = NULL; |
| classic->num_fds = 0; |
| } |
| } |
| |
| if (message) |
| return message; |
| |
| bad_msg: |
| cmsg_fail: |
| for (i = 0; i < classic->num_fds; i++) |
| close(classic->fd_buf[i]); |
| |
| l_free(classic->fd_buf); |
| |
| classic->fd_buf = NULL; |
| classic->num_fds = 0; |
| |
| l_free(header); |
| l_free(body); |
| |
| return NULL; |
| } |
| |
| static bool classic_add_match(struct l_dbus *dbus, unsigned int id, |
| const struct _dbus_filter_condition *rule, |
| int rule_len) |
| { |
| struct l_dbus_classic *classic = |
| l_container_of(dbus, struct l_dbus_classic, super); |
| char *match_str; |
| struct l_dbus_message *message; |
| |
| match_str = _dbus_filter_rule_to_str(rule, rule_len); |
| |
| l_hashmap_insert(classic->match_strings, L_UINT_TO_PTR(id), match_str); |
| |
| message = l_dbus_message_new_method_call(dbus, |
| DBUS_SERVICE_DBUS, |
| DBUS_PATH_DBUS, |
| L_DBUS_INTERFACE_DBUS, |
| "AddMatch"); |
| |
| l_dbus_message_set_arguments(message, "s", match_str); |
| |
| send_message(dbus, false, message, NULL, NULL, NULL); |
| |
| return true; |
| } |
| |
| static bool classic_remove_match(struct l_dbus *dbus, unsigned int id) |
| { |
| struct l_dbus_classic *classic = |
| l_container_of(dbus, struct l_dbus_classic, super); |
| char *match_str = l_hashmap_remove(classic->match_strings, |
| L_UINT_TO_PTR(id)); |
| struct l_dbus_message *message; |
| |
| if (!match_str) |
| return false; |
| |
| message = l_dbus_message_new_method_call(dbus, |
| DBUS_SERVICE_DBUS, |
| DBUS_PATH_DBUS, |
| L_DBUS_INTERFACE_DBUS, |
| "RemoveMatch"); |
| |
| l_dbus_message_set_arguments(message, "s", match_str); |
| |
| send_message(dbus, false, message, NULL, NULL, NULL); |
| |
| l_free(match_str); |
| |
| return true; |
| } |
| |
| static void name_owner_changed_cb(struct l_dbus_message *message, |
| void *user_data) |
| { |
| struct l_dbus *dbus = user_data; |
| char *name, *old, *new; |
| |
| if (!l_dbus_message_get_arguments(message, "sss", &name, &old, &new)) |
| return; |
| |
| _dbus_name_cache_notify(dbus->name_cache, name, new); |
| } |
| |
| struct get_name_owner_request { |
| struct l_dbus_message *message; |
| struct l_dbus *dbus; |
| }; |
| |
| static void get_name_owner_reply_cb(struct l_dbus_message *reply, |
| void *user_data) |
| { |
| struct get_name_owner_request *req = user_data; |
| const char *name, *owner; |
| |
| /* No name owner yet */ |
| if (l_dbus_message_is_error(reply)) |
| return; |
| |
| /* Shouldn't happen */ |
| if (!l_dbus_message_get_arguments(reply, "s", &owner)) |
| return; |
| |
| /* Shouldn't happen */ |
| if (!l_dbus_message_get_arguments(req->message, "s", &name)) |
| return; |
| |
| _dbus_name_cache_notify(req->dbus->name_cache, name, owner); |
| } |
| |
| static bool classic_get_name_owner(struct l_dbus *bus, const char *name) |
| { |
| struct get_name_owner_request *req; |
| |
| /* Name resolution is not performed for DBUS_SERVICE_DBUS */ |
| if (!strcmp(name, DBUS_SERVICE_DBUS)) |
| return false; |
| |
| req = l_new(struct get_name_owner_request, 1); |
| req->dbus = bus; |
| req->message = l_dbus_message_new_method_call(bus, |
| DBUS_SERVICE_DBUS, |
| DBUS_PATH_DBUS, |
| L_DBUS_INTERFACE_DBUS, |
| "GetNameOwner"); |
| |
| l_dbus_message_set_arguments(req->message, "s", name); |
| |
| send_message(bus, false, req->message, get_name_owner_reply_cb, |
| req, l_free); |
| |
| if (!bus->name_notify_enabled) { |
| static struct _dbus_filter_condition rule[] = { |
| { L_DBUS_MATCH_TYPE, "signal" }, |
| { L_DBUS_MATCH_SENDER, DBUS_SERVICE_DBUS }, |
| { L_DBUS_MATCH_PATH, DBUS_PATH_DBUS }, |
| { L_DBUS_MATCH_INTERFACE, L_DBUS_INTERFACE_DBUS }, |
| { L_DBUS_MATCH_MEMBER, "NameOwnerChanged" }, |
| }; |
| |
| if (!bus->filter) |
| bus->filter = _dbus_filter_new(bus, |
| &bus->driver->filter_ops, |
| bus->name_cache); |
| |
| _dbus_filter_add_rule(bus->filter, rule, L_ARRAY_SIZE(rule), |
| name_owner_changed_cb, bus); |
| |
| bus->name_notify_enabled = true; |
| } |
| |
| return true; |
| } |
| |
| struct name_request { |
| l_dbus_name_acquire_func_t callback; |
| void *user_data; |
| struct l_dbus *dbus; |
| }; |
| |
| enum dbus_name_flag { |
| DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x1, |
| DBUS_NAME_FLAG_REPLACE_EXISTING = 0x2, |
| DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x4, |
| }; |
| |
| enum dbus_name_reply { |
| DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1, |
| DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 2, |
| DBUS_REQUEST_NAME_REPLY_EXISTS = 3, |
| DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 4, |
| }; |
| |
| static void request_name_reply_cb(struct l_dbus_message *reply, void *user_data) |
| { |
| struct name_request *req = user_data; |
| bool success = false, queued = false; |
| uint32_t retval; |
| |
| if (!req->callback) |
| return; |
| |
| /* No name owner yet */ |
| if (l_dbus_message_is_error(reply)) |
| goto call_back; |
| |
| /* Shouldn't happen */ |
| if (!l_dbus_message_get_arguments(reply, "u", &retval)) |
| goto call_back; |
| |
| success = (retval == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) || |
| (retval == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) || |
| (retval == DBUS_REQUEST_NAME_REPLY_IN_QUEUE); |
| queued = (retval == DBUS_REQUEST_NAME_REPLY_IN_QUEUE); |
| |
| call_back: |
| req->callback(req->dbus, success, queued, req->user_data); |
| } |
| |
| static uint32_t classic_name_acquire(struct l_dbus *dbus, const char *name, |
| bool allow_replacement, |
| bool replace_existing, bool queue, |
| l_dbus_name_acquire_func_t callback, |
| void *user_data) |
| { |
| struct name_request *req; |
| struct l_dbus_message *message; |
| uint32_t flags = 0; |
| |
| req = l_new(struct name_request, 1); |
| req->dbus = dbus; |
| req->user_data = user_data; |
| req->callback = callback; |
| |
| message = l_dbus_message_new_method_call(dbus, DBUS_SERVICE_DBUS, |
| DBUS_PATH_DBUS, |
| L_DBUS_INTERFACE_DBUS, |
| "RequestName"); |
| |
| if (allow_replacement) |
| flags |= DBUS_NAME_FLAG_ALLOW_REPLACEMENT; |
| |
| if (replace_existing) |
| flags |= DBUS_NAME_FLAG_REPLACE_EXISTING; |
| |
| if (!queue) |
| flags |= DBUS_NAME_FLAG_DO_NOT_QUEUE; |
| |
| l_dbus_message_set_arguments(message, "su", name, flags); |
| |
| return send_message(dbus, false, message, request_name_reply_cb, |
| req, free); |
| } |
| |
| static const struct l_dbus_ops classic_ops = { |
| .version = 1, |
| .send_message = classic_send_message, |
| .recv_message = classic_recv_message, |
| .free = classic_free, |
| .name_ops = { |
| .get_name_owner = classic_get_name_owner, |
| }, |
| .filter_ops = { |
| .add_match = classic_add_match, |
| .remove_match = classic_remove_match, |
| }, |
| .name_acquire = classic_name_acquire, |
| }; |
| |
| static struct l_dbus *setup_dbus1(int fd, const char *guid, bool skip_hello) |
| { |
| static const unsigned char creds = 0x00; |
| char uid[6], hexuid[12], *ptr = hexuid; |
| struct l_dbus *dbus; |
| struct l_dbus_classic *classic; |
| ssize_t written; |
| unsigned int i; |
| |
| if (snprintf(uid, sizeof(uid), "%d", geteuid()) < 1) { |
| close(fd); |
| return NULL; |
| } |
| |
| for (i = 0; i < strlen(uid); i++) |
| ptr += sprintf(ptr, "%02x", uid[i]); |
| |
| /* Send special credentials-passing nul byte */ |
| written = L_TFR(send(fd, &creds, 1, 0)); |
| if (written < 1) { |
| close(fd); |
| return NULL; |
| } |
| |
| classic = l_new(struct l_dbus_classic, 1); |
| dbus = &classic->super; |
| dbus->driver = &classic_ops; |
| |
| classic->match_strings = l_hashmap_new(); |
| |
| dbus_init(dbus, fd); |
| dbus->guid = l_strdup(guid); |
| |
| classic->auth_command = l_strdup_printf("AUTH EXTERNAL %s\r\n", hexuid); |
| classic->auth_state = WAITING_FOR_OK; |
| classic->skip_hello = skip_hello; |
| |
| dbus->negotiate_unix_fd = true; |
| dbus->support_unix_fd = false; |
| |
| l_io_set_read_handler(dbus->io, auth_read_handler, dbus, NULL); |
| l_io_set_write_handler(dbus->io, auth_write_handler, dbus, NULL); |
| |
| return dbus; |
| } |
| |
| static struct l_dbus *setup_unix(char *params) |
| { |
| char *path = NULL, *guid = NULL; |
| bool abstract = false; |
| struct sockaddr_un addr; |
| size_t len; |
| int fd; |
| |
| while (params) { |
| char *key = strsep(¶ms, ","); |
| char *value; |
| |
| if (!key) |
| break; |
| |
| value = strchr(key, '='); |
| if (!value) |
| continue; |
| |
| *value++ = '\0'; |
| |
| if (!strcmp(key, "path")) { |
| path = value; |
| abstract = false; |
| } else if (!strcmp(key, "abstract")) { |
| path = value; |
| abstract = true; |
| } else if (!strcmp(key, "guid")) |
| guid = value; |
| } |
| |
| if (!path) |
| return NULL; |
| |
| fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); |
| if (fd < 0) |
| return NULL; |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.sun_family = AF_UNIX; |
| |
| len = strlen(path); |
| |
| if (abstract) { |
| if (len > sizeof(addr.sun_path) - 1) { |
| close(fd); |
| return NULL; |
| } |
| |
| addr.sun_path[0] = '\0'; |
| strncpy(addr.sun_path + 1, path, sizeof(addr.sun_path) - 2); |
| len++; |
| } else { |
| if (len > sizeof(addr.sun_path)) { |
| close(fd); |
| return NULL; |
| } |
| |
| strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); |
| } |
| |
| if (connect(fd, (struct sockaddr *) &addr, |
| sizeof(addr.sun_family) + len) < 0) { |
| close(fd); |
| return NULL; |
| } |
| |
| return setup_dbus1(fd, guid, false); |
| } |
| |
| static bool setup_tcp_cb(struct l_io *io, void *user_data) |
| { |
| static const unsigned char creds = 0x00; |
| struct l_dbus *dbus = user_data; |
| struct l_dbus_classic *classic; |
| ssize_t written; |
| int fd = l_io_get_fd(io); |
| |
| /* Send special credentials-passing nul byte */ |
| written = L_TFR(send(fd, &creds, 1, 0)); |
| if (written < 1) { |
| l_util_debug(dbus->debug_handler, dbus->debug_handler, |
| "error writing NUL byte"); |
| close(fd); |
| return false; |
| } |
| |
| dbus->driver = &classic_ops; |
| dbus->negotiate_unix_fd = false; |
| dbus->support_unix_fd = false; |
| |
| classic = l_container_of(dbus, struct l_dbus_classic, super); |
| classic->match_strings = l_hashmap_new(); |
| classic->auth_command = l_strdup("AUTH ANONYMOUS\r\n"); |
| classic->auth_state = WAITING_FOR_OK; |
| |
| l_io_set_read_handler(dbus->io, auth_read_handler, dbus, NULL); |
| l_io_set_write_handler(dbus->io, auth_write_handler, dbus, NULL); |
| |
| return auth_write_handler(dbus->io, dbus); |
| } |
| |
| static struct l_dbus *setup_tcp(char *params) |
| { |
| char *host = NULL; |
| char *port = NULL; |
| char *family = NULL; |
| struct addrinfo hints = { 0 }; |
| struct addrinfo *res; |
| struct addrinfo *iter; |
| struct l_dbus *dbus = NULL; |
| |
| while (params) { |
| char *key = strsep(¶ms, ","); |
| char *value; |
| |
| value = strchr(key, '='); |
| if (!value) |
| continue; |
| |
| *value++ = '\0'; |
| |
| if (!strcmp(key, "host")) |
| host = value; |
| else if (!strcmp(key, "port")) |
| port = value; |
| else if (!strcmp(key, "family")) |
| family = value; |
| } |
| |
| if (!host || !port) |
| return NULL; |
| |
| if (!family) |
| hints.ai_family = AF_UNSPEC; |
| else if (!strcmp(family, "ipv4")) |
| hints.ai_family = AF_INET; |
| else if (!strcmp(family, "ipv6")) |
| hints.ai_family = AF_INET6; |
| else |
| return NULL; |
| |
| hints.ai_socktype = SOCK_STREAM; |
| hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; |
| hints.ai_protocol = IPPROTO_TCP; |
| |
| if (getaddrinfo(host, port, &hints, &res) != 0) |
| return NULL; |
| |
| for (iter = res; iter; iter = iter->ai_next) { |
| int fd; |
| struct l_dbus_classic *classic; |
| |
| fd = socket(iter->ai_family, iter->ai_socktype | SOCK_NONBLOCK, |
| iter->ai_protocol); |
| if (fd < 0) |
| continue; |
| |
| if (connect(fd, iter->ai_addr, iter->ai_addrlen) < 0) { |
| if (errno != EINPROGRESS) { |
| close(fd); |
| continue; |
| } |
| } |
| |
| classic = l_new(struct l_dbus_classic, 1); |
| dbus = &classic->super; |
| dbus_init(dbus, fd); |
| l_io_set_write_handler(dbus->io, setup_tcp_cb, dbus, NULL); |
| break; |
| } |
| |
| freeaddrinfo(res); |
| return dbus; |
| } |
| |
| static struct l_dbus *setup_address(const char *address) |
| { |
| struct l_dbus *dbus = NULL; |
| char *address_copy; |
| |
| address_copy = strdupa(address); |
| |
| while (address_copy) { |
| char *transport = strsep(&address_copy, ";"); |
| char *params; |
| |
| if (!transport) |
| break; |
| |
| params = strchr(transport, ':'); |
| if (params) |
| *params++ = '\0'; |
| |
| if (!strcmp(transport, "unix")) { |
| /* Function will modify params string */ |
| dbus = setup_unix(params); |
| break; |
| } else if (!strcmp(transport, "tcp")) { |
| dbus = setup_tcp(params); |
| break; |
| } |
| } |
| |
| return dbus; |
| } |
| |
| LIB_EXPORT struct l_dbus *l_dbus_new(const char *address) |
| { |
| if (unlikely(!address)) |
| return NULL; |
| |
| return setup_address(address); |
| } |
| |
| LIB_EXPORT struct l_dbus *l_dbus_new_default(enum l_dbus_bus bus) |
| { |
| const char *address; |
| |
| switch (bus) { |
| case L_DBUS_SYSTEM_BUS: |
| address = getenv("DBUS_SYSTEM_BUS_ADDRESS"); |
| if (!address) |
| address = DEFAULT_SYSTEM_BUS_ADDRESS; |
| break; |
| case L_DBUS_SESSION_BUS: |
| address = getenv("DBUS_SESSION_BUS_ADDRESS"); |
| if (!address) |
| return NULL; |
| break; |
| default: |
| return NULL; |
| } |
| |
| return setup_address(address); |
| } |
| |
| LIB_EXPORT struct l_dbus *l_dbus_new_private(int fd) |
| { |
| return setup_dbus1(fd, NULL, true); |
| } |
| |
| LIB_EXPORT void l_dbus_destroy(struct l_dbus *dbus) |
| { |
| if (unlikely(!dbus)) |
| return; |
| |
| if (dbus->ready_destroy) |
| dbus->ready_destroy(dbus->ready_data); |
| |
| _dbus_filter_free(dbus->filter); |
| |
| _dbus_name_cache_free(dbus->name_cache); |
| |
| l_hashmap_destroy(dbus->signal_list, signal_list_destroy); |
| l_hashmap_destroy(dbus->message_list, message_list_destroy); |
| l_queue_destroy(dbus->message_queue, message_queue_destroy); |
| |
| l_io_destroy(dbus->io); |
| |
| if (dbus->disconnect_destroy) |
| dbus->disconnect_destroy(dbus->disconnect_data); |
| |
| if (dbus->debug_destroy) |
| dbus->debug_destroy(dbus->debug_data); |
| |
| l_free(dbus->guid); |
| l_free(dbus->unique_name); |
| |
| _dbus_object_tree_free(dbus->tree); |
| |
| dbus->driver->free(dbus); |
| } |
| |
| LIB_EXPORT bool l_dbus_set_ready_handler(struct l_dbus *dbus, |
| l_dbus_ready_func_t function, |
| void *user_data, l_dbus_destroy_func_t destroy) |
| { |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (dbus->ready_destroy) |
| dbus->ready_destroy(dbus->ready_data); |
| |
| dbus->ready_handler = function; |
| dbus->ready_destroy = destroy; |
| dbus->ready_data = user_data; |
| |
| return true; |
| } |
| |
| LIB_EXPORT bool l_dbus_set_disconnect_handler(struct l_dbus *dbus, |
| l_dbus_disconnect_func_t function, |
| void *user_data, l_dbus_destroy_func_t destroy) |
| { |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (dbus->disconnect_destroy) |
| dbus->disconnect_destroy(dbus->disconnect_data); |
| |
| dbus->disconnect_handler = function; |
| dbus->disconnect_destroy = destroy; |
| dbus->disconnect_data = user_data; |
| |
| return true; |
| } |
| |
| LIB_EXPORT bool l_dbus_set_debug(struct l_dbus *dbus, |
| l_dbus_debug_func_t function, |
| void *user_data, l_dbus_destroy_func_t destroy) |
| { |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (dbus->debug_destroy) |
| dbus->debug_destroy(dbus->debug_data); |
| |
| dbus->debug_handler = function; |
| dbus->debug_destroy = destroy; |
| dbus->debug_data = user_data; |
| |
| /* l_io_set_debug(dbus->io, function, user_data, NULL); */ |
| |
| return true; |
| } |
| |
| LIB_EXPORT uint32_t l_dbus_send_with_reply(struct l_dbus *dbus, |
| struct l_dbus_message *message, |
| l_dbus_message_func_t function, |
| void *user_data, |
| l_dbus_destroy_func_t destroy) |
| { |
| if (unlikely(!dbus || !message)) |
| return 0; |
| |
| return send_message(dbus, false, message, function, user_data, destroy); |
| } |
| |
| LIB_EXPORT uint32_t l_dbus_send(struct l_dbus *dbus, |
| struct l_dbus_message *message) |
| { |
| if (unlikely(!dbus || !message)) |
| return 0; |
| |
| return send_message(dbus, false, message, NULL, NULL, NULL); |
| } |
| |
| static bool remove_entry(void *data, void *user_data) |
| { |
| struct message_callback *callback = data; |
| uint32_t serial = L_PTR_TO_UINT(user_data); |
| |
| if (callback->serial == serial) { |
| message_queue_destroy(callback); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_dbus_cancel(struct l_dbus *dbus, uint32_t serial) |
| { |
| struct message_callback *callback; |
| unsigned int count; |
| |
| if (unlikely(!dbus || !serial)) |
| return false; |
| |
| callback = l_hashmap_remove(dbus->message_list, L_UINT_TO_PTR(serial)); |
| if (callback) { |
| message_queue_destroy(callback); |
| return true; |
| } |
| |
| count = l_queue_foreach_remove(dbus->message_queue, remove_entry, |
| L_UINT_TO_PTR(serial)); |
| if (!count) |
| return false; |
| |
| return true; |
| } |
| |
| LIB_EXPORT unsigned int l_dbus_register(struct l_dbus *dbus, |
| l_dbus_message_func_t function, |
| void *user_data, l_dbus_destroy_func_t destroy) |
| { |
| struct signal_callback *callback; |
| |
| if (unlikely(!dbus)) |
| return 0; |
| |
| callback = l_new(struct signal_callback, 1); |
| |
| callback->id = dbus->next_id++; |
| callback->callback = function; |
| callback->destroy = destroy; |
| callback->user_data = user_data; |
| |
| l_hashmap_insert(dbus->signal_list, |
| L_UINT_TO_PTR(callback->id), callback); |
| |
| return callback->id; |
| } |
| |
| LIB_EXPORT bool l_dbus_unregister(struct l_dbus *dbus, unsigned int id) |
| { |
| struct signal_callback *callback; |
| |
| if (unlikely(!dbus || !id)) |
| return false; |
| |
| callback = l_hashmap_remove(dbus->signal_list, L_UINT_TO_PTR(id)); |
| if (!callback) |
| return false; |
| |
| signal_list_destroy(callback); |
| |
| return true; |
| } |
| |
| LIB_EXPORT uint32_t l_dbus_method_call(struct l_dbus *dbus, |
| const char *destination, const char *path, |
| const char *interface, const char *method, |
| l_dbus_message_func_t setup, |
| l_dbus_message_func_t function, |
| void *user_data, l_dbus_destroy_func_t destroy) |
| { |
| struct l_dbus_message *message; |
| |
| if (unlikely(!dbus)) |
| return 0; |
| |
| message = l_dbus_message_new_method_call(dbus, destination, path, |
| interface, method); |
| |
| if (setup) |
| setup(message, user_data); |
| else |
| l_dbus_message_set_arguments(message, ""); |
| |
| return send_message(dbus, false, message, function, user_data, destroy); |
| } |
| |
| uint8_t _dbus_get_version(struct l_dbus *dbus) |
| { |
| return dbus->driver->version; |
| } |
| |
| int _dbus_get_fd(struct l_dbus *dbus) |
| { |
| return l_io_get_fd(dbus->io); |
| } |
| |
| struct _dbus_object_tree *_dbus_get_tree(struct l_dbus *dbus) |
| { |
| return dbus->tree; |
| } |
| |
| /** |
| * l_dbus_register_interface: |
| * @dbus: D-Bus connection as returned by @l_dbus_new* |
| * @interface: interface name string |
| * @setup_func: function that sets up the methods, signals and properties by |
| * using the #dbus-service.h API. |
| * @destroy: optional destructor to be called every time an instance of this |
| * interface is being removed from an object on this bus. |
| * @handle_old_style_properties: whether to automatically handle SetProperty and |
| * GetProperties for any properties registered by |
| * @setup_func. |
| * |
| * Registers an interface. If successful the interface can then be added |
| * to any number of objects with @l_dbus_object_add_interface. |
| * |
| * Returns: whether the interface was successfully registered |
| **/ |
| LIB_EXPORT bool l_dbus_register_interface(struct l_dbus *dbus, |
| const char *interface, |
| l_dbus_interface_setup_func_t setup_func, |
| l_dbus_destroy_func_t destroy, |
| bool handle_old_style_properties) |
| { |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (unlikely(!dbus->tree)) |
| return false; |
| |
| return _dbus_object_tree_register_interface(dbus->tree, interface, |
| setup_func, destroy, |
| handle_old_style_properties); |
| } |
| |
| LIB_EXPORT bool l_dbus_unregister_interface(struct l_dbus *dbus, |
| const char *interface) |
| { |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (unlikely(!dbus->tree)) |
| return false; |
| |
| return _dbus_object_tree_unregister_interface(dbus->tree, interface); |
| } |
| |
| /** |
| * l_dbus_register_object: |
| * @dbus: D-Bus connection |
| * @path: new object path |
| * @user_data: user pointer to be passed to @destroy if any |
| * @destroy: optional destructor to be called when object dropped from the tree |
| * @...: NULL-terminated list of 0 or more interfaces to be present on the |
| * object from the moment of creation. For every interface the interface |
| * name string is expected followed by the @user_data pointer same as |
| * would be passed as @l_dbus_object_add_interface's last two parameters. |
| * |
| * Create a new D-Bus object on the tree visible to D-Bus peers. For example: |
| * success = l_dbus_register_object(bus, "/org/example/ExampleManager", |
| * NULL, NULL, |
| * "org.example.Manager", |
| * manager_data, |
| * NULL); |
| * |
| * Returns: whether the object path was successfully registered |
| **/ |
| LIB_EXPORT bool l_dbus_register_object(struct l_dbus *dbus, const char *path, |
| void *user_data, |
| l_dbus_destroy_func_t destroy, ...) |
| { |
| va_list args; |
| const char *interface; |
| void *if_user_data; |
| bool r = true; |
| |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (unlikely(!dbus->tree)) |
| return false; |
| |
| if (!_dbus_object_tree_new_object(dbus->tree, path, user_data, destroy)) |
| return false; |
| |
| va_start(args, destroy); |
| while ((interface = va_arg(args, const char *))) { |
| if_user_data = va_arg(args, void *); |
| |
| if (!_dbus_object_tree_add_interface(dbus->tree, path, |
| interface, |
| if_user_data)) { |
| _dbus_object_tree_object_destroy(dbus->tree, path); |
| r = false; |
| |
| break; |
| } |
| } |
| va_end(args); |
| |
| return r; |
| } |
| |
| LIB_EXPORT bool l_dbus_unregister_object(struct l_dbus *dbus, |
| const char *object) |
| { |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (unlikely(!dbus->tree)) |
| return false; |
| |
| return _dbus_object_tree_object_destroy(dbus->tree, object); |
| } |
| |
| /** |
| * l_dbus_object_add_interface: |
| * @dbus: D-Bus connection |
| * @object: object path as passed to @l_dbus_register_object |
| * @interface: interface name as passed to @l_dbus_register_interface |
| * @user_data: user data pointer to be passed to any method and property |
| * callbacks provided by the @setup_func and to the @destroy |
| * callback as passed to @l_dbus_register_interface |
| * |
| * Creates an instance of given interface at the given path in the |
| * connection's object tree. If no object was registered at this path |
| * before @l_dbus_register_object gets called automatically. |
| * |
| * The addition of an interface to the object may trigger a query of |
| * all the properties on this interface and |
| * #org.freedesktop.DBus.ObjectManager.InterfacesAdded signals. |
| * |
| * Returns: whether the interface was successfully added. |
| **/ |
| LIB_EXPORT bool l_dbus_object_add_interface(struct l_dbus *dbus, |
| const char *object, |
| const char *interface, |
| void *user_data) |
| { |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (unlikely(!dbus->tree)) |
| return false; |
| |
| return _dbus_object_tree_add_interface(dbus->tree, object, interface, |
| user_data); |
| } |
| |
| LIB_EXPORT bool l_dbus_object_remove_interface(struct l_dbus *dbus, |
| const char *object, |
| const char *interface) |
| { |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (unlikely(!dbus->tree)) |
| return false; |
| |
| return _dbus_object_tree_remove_interface(dbus->tree, object, |
| interface); |
| } |
| |
| LIB_EXPORT void *l_dbus_object_get_data(struct l_dbus *dbus, const char *object, |
| const char *interface) |
| { |
| if (unlikely(!dbus)) |
| return NULL; |
| |
| if (unlikely(!dbus->tree)) |
| return NULL; |
| |
| return _dbus_object_tree_get_interface_data(dbus->tree, object, |
| interface); |
| } |
| |
| LIB_EXPORT bool l_dbus_object_set_data(struct l_dbus *dbus, const char *object, |
| const char *interface, void *user_data) |
| { |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (unlikely(!dbus->tree)) |
| return false; |
| |
| return _dbus_object_tree_set_interface_data(dbus->tree, object, |
| interface, user_data); |
| } |
| |
| LIB_EXPORT bool l_dbus_object_manager_enable(struct l_dbus *dbus, |
| const char *root) |
| { |
| if (unlikely(!dbus)) |
| return false; |
| |
| if (unlikely(!dbus->tree)) |
| return false; |
| |
| return _dbus_object_tree_add_interface(dbus->tree, root, |
| L_DBUS_INTERFACE_OBJECT_MANAGER, |
| dbus); |
| } |
| |
| LIB_EXPORT unsigned int l_dbus_add_disconnect_watch(struct l_dbus *dbus, |
| const char *name, |
| l_dbus_watch_func_t disconnect_func, |
| void *user_data, |
| l_dbus_destroy_func_t destroy) |
| { |
| return l_dbus_add_service_watch(dbus, name, NULL, disconnect_func, |
| user_data, destroy); |
| } |
| |
| LIB_EXPORT unsigned int l_dbus_add_service_watch(struct l_dbus *dbus, |
| const char *name, |
| l_dbus_watch_func_t connect_func, |
| l_dbus_watch_func_t disconnect_func, |
| void *user_data, |
| l_dbus_destroy_func_t destroy) |
| { |
| if (!name) |
| return 0; |
| |
| if (!dbus->name_cache) |
| dbus->name_cache = _dbus_name_cache_new(dbus, |
| &dbus->driver->name_ops); |
| |
| return _dbus_name_cache_add_watch(dbus->name_cache, name, connect_func, |
| disconnect_func, user_data, |
| destroy); |
| } |
| |
| LIB_EXPORT bool l_dbus_remove_watch(struct l_dbus *dbus, unsigned int id) |
| { |
| if (!dbus->name_cache) |
| return false; |
| |
| return _dbus_name_cache_remove_watch(dbus->name_cache, id); |
| } |
| |
| /** |
| * l_dbus_add_signal_watch: |
| * @dbus: D-Bus connection |
| * @sender: bus name to match the signal sender against or NULL to |
| * match any sender |
| * @path: object path to match the signal path against or NULL to |
| * match any path |
| * @interface: interface name to match the signal interface against |
| * or NULL to match any interface |
| * @member: name to match the signal name against or NULL to match any |
| * signal |
| * @...: a list of further conditions to be met by the signal followed |
| * by three more mandatory parameters: |
| * enum l_dbus_match_type list_end_marker, |
| * l_dbus_message_func callback, |
| * void *user_data, |
| * The value L_DBUS_MATCH_NONE must be passed as the end of list |
| * marker, followed by the signal match callback and user_data. |
| * In the list, every condition is a pair of parameters: |
| * enum l_dbus_match_type match_type, const char *value. |
| * |
| * Subscribe to a group of signals based on a set of conditions that |
| * compare the signal's header fields and string arguments against given |
| * values. For example: |
| * signal_id = l_dbus_add_signal_watch(bus, "org.example", "/" |
| * "org.example.Manager", |
| * "PropertyChanged", |
| * L_DBUS_MATCH_ARGUMENT(0), |
| * "ExampleProperty", |
| * L_DBUS_MATCH_NONE |
| * manager_property_change_cb, |
| * NULL); |
| * |
| * Returns: a non-zero signal filter identifier that can be passed to |
| * l_dbus_remove_signal_watch to remove this filter rule, or |
| * zero on failure. |
| **/ |
| LIB_EXPORT unsigned int l_dbus_add_signal_watch(struct l_dbus *dbus, |
| const char *sender, |
| const char *path, |
| const char *interface, |
| const char *member, ...) |
| { |
| struct _dbus_filter_condition *rule; |
| int rule_len; |
| va_list args; |
| const char *value; |
| l_dbus_message_func_t signal_func; |
| enum l_dbus_match_type type; |
| void *user_data; |
| unsigned int id; |
| |
| va_start(args, member); |
| |
| rule_len = 0; |
| while (true) { |
| type = va_arg(args, enum l_dbus_match_type); |
| if (type == L_DBUS_MATCH_NONE) |
| break; |
| |
| va_arg(args, const char *); |
| rule_len++; |
| } |
| |
| va_end(args); |
| |
| rule = l_new(struct _dbus_filter_condition, rule_len + 5); |
| |
| rule_len = 0; |
| |
| rule[rule_len].type = L_DBUS_MATCH_TYPE; |
| rule[rule_len++].value = "signal"; |
| |
| if (sender) { |
| rule[rule_len].type = L_DBUS_MATCH_SENDER; |
| rule[rule_len++].value = sender; |
| } |
| |
| if (path) { |
| rule[rule_len].type = L_DBUS_MATCH_PATH; |
| rule[rule_len++].value = path; |
| } |
| |
| if (interface) { |
| rule[rule_len].type = L_DBUS_MATCH_INTERFACE; |
| rule[rule_len++].value = interface; |
| } |
| |
| if (member) { |
| rule[rule_len].type = L_DBUS_MATCH_MEMBER; |
| rule[rule_len++].value = member; |
| } |
| |
| va_start(args, member); |
| |
| while (true) { |
| type = va_arg(args, enum l_dbus_match_type); |
| if (type == L_DBUS_MATCH_NONE) |
| break; |
| |
| value = va_arg(args, const char *); |
| |
| rule[rule_len].type = type; |
| rule[rule_len++].value = value; |
| } |
| |
| signal_func = va_arg(args, l_dbus_message_func_t); |
| user_data = va_arg(args, void *); |
| |
| va_end(args); |
| |
| if (!dbus->filter) { |
| if (!dbus->name_cache) |
| dbus->name_cache = _dbus_name_cache_new(dbus, |
| &dbus->driver->name_ops); |
| |
| dbus->filter = _dbus_filter_new(dbus, |
| &dbus->driver->filter_ops, |
| dbus->name_cache); |
| } |
| |
| id = _dbus_filter_add_rule(dbus->filter, rule, rule_len, |
| signal_func, user_data); |
| |
| l_free(rule); |
| |
| return id; |
| } |
| |
| LIB_EXPORT bool l_dbus_remove_signal_watch(struct l_dbus *dbus, unsigned int id) |
| { |
| if (!dbus->filter) |
| return false; |
| |
| return _dbus_filter_remove_rule(dbus->filter, id); |
| } |
| |
| /** |
| * l_dbus_name_acquire: |
| * @dbus: D-Bus connection |
| * @name: Well-known bus name to be acquired |
| * @allow_replacement: Whether to allow another peer's name request to |
| * take the name ownership away from this connection |
| * @replace_existing: Whether to allow D-Bus to take the name's ownership |
| * away from another peer in case the name is already |
| * owned and allows replacement. Ignored if name is |
| * currently free. |
| * @queue: Whether to allow the name request to be queued by D-Bus in |
| * case it cannot be acquired now, rather than to return a failure. |
| * @callback: Callback to receive the request result when done. |
| * |
| * Acquire a well-known bus name (service name) on the bus. |
| * |
| * Returns: a non-zero request serial that can be passed to l_dbus_cancel |
| * while waiting for the callback or zero if the callback has |
| * has happened while l_dbus_name_acquire was running. |
| **/ |
| LIB_EXPORT uint32_t l_dbus_name_acquire(struct l_dbus *dbus, const char *name, |
| bool allow_replacement, bool replace_existing, |
| bool queue, l_dbus_name_acquire_func_t callback, |
| void *user_data) |
| { |
| return dbus->driver->name_acquire(dbus, name, allow_replacement, |
| replace_existing, queue, |
| callback, user_data); |
| } |