mirror of https://gitee.com/openkylin/bluez.git
1813 lines
43 KiB
C
1813 lines
43 KiB
C
/*
|
|
*
|
|
* Embedded Linux library
|
|
*
|
|
* Copyright (C) 2011-2014 Intel Corporation. All rights reserved.
|
|
*
|
|
* This library 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.
|
|
*
|
|
* This library 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 this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#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/socket.h>
|
|
#include <sys/un.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;
|
|
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;
|
|
|
|
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)
|
|
{
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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 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 false;
|
|
|
|
if (unlikely(!dbus->tree))
|
|
return false;
|
|
|
|
return _dbus_object_tree_get_interface_data(dbus->tree, object,
|
|
interface);
|
|
}
|
|
|
|
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 ((type = va_arg(args, enum l_dbus_match_type)) !=
|
|
L_DBUS_MATCH_NONE)
|
|
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);
|
|
}
|