996 lines
26 KiB
C
996 lines
26 KiB
C
/*
|
|
* Copyright (C) 2025 Xiaomi Corporation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <execinfo.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <dbus/dbus-list.h>
|
|
#include <dbus/dbus.h>
|
|
#include <uv.h>
|
|
|
|
#include "gdbus-internal.h"
|
|
|
|
static DBusHandlerResult message_filter(DBusConnection* connection,
|
|
DBusMessage* message, void* user_data);
|
|
|
|
struct GDBusWatch {
|
|
DBusConnection* conn;
|
|
guint serial;
|
|
DBusList* list;
|
|
gboolean conn_closed;
|
|
};
|
|
|
|
struct service_data {
|
|
DBusConnection* conn;
|
|
DBusPendingCall* call;
|
|
char* name;
|
|
const char* owner;
|
|
struct filter_callback* callback;
|
|
uv_idle_t handle;
|
|
};
|
|
|
|
struct filter_callback {
|
|
GDBusWatchFunction conn_func;
|
|
GDBusWatchFunction disc_func;
|
|
GDBusSignalFunction signal_func;
|
|
GDBusDestroyFunction destroy_func;
|
|
struct service_data* data;
|
|
void* user_data;
|
|
guint id;
|
|
};
|
|
|
|
struct filter_data {
|
|
DBusConnection* connection;
|
|
DBusHandleMessageFunction handle_func;
|
|
char* name;
|
|
char* owner;
|
|
char* path;
|
|
char* interface;
|
|
char* member;
|
|
char* argument;
|
|
DBusList* callbacks;
|
|
DBusList* processed;
|
|
guint name_watch;
|
|
gboolean lock;
|
|
gboolean registered;
|
|
GDBusWatch* watcher;
|
|
};
|
|
|
|
static int strcmp0(const char* str1, const char* str2)
|
|
{
|
|
if (str1 == NULL)
|
|
return -(str1 != str2);
|
|
|
|
if (str2 == NULL)
|
|
return -(str1 != str2);
|
|
|
|
return strcmp(str1, str2);
|
|
}
|
|
|
|
static char* strdup0(const char* str)
|
|
{
|
|
if (str)
|
|
return strdup(str);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int get_watch_serial(GDBusWatch* watcher)
|
|
{
|
|
return ++watcher->serial;
|
|
}
|
|
|
|
static struct filter_data* filter_data_find_match(DBusConnection* connection,
|
|
DBusList* listener_list, const char* name, const char* owner,
|
|
const char* path, const char* interface, const char* member, const char* argument)
|
|
{
|
|
DBusList* current;
|
|
|
|
for (current = _dbus_list_get_first_link(&listener_list); current != NULL;
|
|
current = _dbus_list_get_next_link(&listener_list, current)) {
|
|
struct filter_data* data = current->data;
|
|
|
|
if (connection != data->connection)
|
|
continue;
|
|
|
|
if (strcmp0(name, data->name) != 0)
|
|
continue;
|
|
|
|
if (strcmp0(owner, data->owner) != 0)
|
|
continue;
|
|
|
|
if (strcmp0(path, data->path) != 0)
|
|
continue;
|
|
|
|
if (strcmp0(interface, data->interface) != 0)
|
|
continue;
|
|
|
|
if (strcmp0(member, data->member) != 0)
|
|
continue;
|
|
|
|
if (strcmp0(argument, data->argument) != 0)
|
|
continue;
|
|
|
|
return data;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct filter_data* filter_data_find(DBusConnection* connection, DBusList* listener_list)
|
|
{
|
|
DBusList* current;
|
|
|
|
for (current = _dbus_list_get_first_link(&listener_list); current != NULL;
|
|
current = _dbus_list_get_next_link(&listener_list, current)) {
|
|
struct filter_data* data = current->data;
|
|
|
|
if (connection != data->connection)
|
|
continue;
|
|
|
|
return data;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void format_rule(struct filter_data* data, char* rule, size_t size)
|
|
{
|
|
const char* sender;
|
|
int offset;
|
|
|
|
offset = snprintf(rule, size, "type='signal'");
|
|
sender = data->name ?: data->owner;
|
|
|
|
if (sender)
|
|
offset += snprintf(rule + offset, size - offset,
|
|
",sender='%s'", sender);
|
|
if (data->path)
|
|
offset += snprintf(rule + offset, size - offset,
|
|
",path='%s'", data->path);
|
|
if (data->interface)
|
|
offset += snprintf(rule + offset, size - offset,
|
|
",interface='%s'", data->interface);
|
|
if (data->member)
|
|
offset += snprintf(rule + offset, size - offset,
|
|
",member='%s'", data->member);
|
|
if (data->argument)
|
|
snprintf(rule + offset, size - offset,
|
|
",arg0='%s'", data->argument);
|
|
}
|
|
|
|
static gboolean add_match(struct filter_data* data,
|
|
DBusHandleMessageFunction filter)
|
|
{
|
|
DBusError err;
|
|
char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH];
|
|
|
|
format_rule(data, rule, sizeof(rule));
|
|
dbus_error_init(&err);
|
|
|
|
dbus_bus_add_match(data->connection, rule, &err);
|
|
if (dbus_error_is_set(&err)) {
|
|
error("Adding match rule \"%s\" failed: %s", rule,
|
|
err.message);
|
|
dbus_error_free(&err);
|
|
return FALSE;
|
|
}
|
|
|
|
data->handle_func = filter;
|
|
data->registered = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean remove_match(struct filter_data* data)
|
|
{
|
|
DBusError err;
|
|
char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH];
|
|
|
|
if (data->watcher->conn_closed) {
|
|
/* If the connection is disconnected, we don't need to remove the match */
|
|
return TRUE;
|
|
}
|
|
|
|
format_rule(data, rule, sizeof(rule));
|
|
|
|
dbus_error_init(&err);
|
|
|
|
dbus_bus_remove_match(data->connection, rule, &err);
|
|
if (dbus_error_is_set(&err)) {
|
|
error("GDBUS Removing owner match rule for %s failed: %s",
|
|
rule, err.message);
|
|
dump_stack();
|
|
dbus_error_free(&err);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void filter_data_free(struct filter_data* data, DBusList* listener_list)
|
|
{
|
|
DBusList* l;
|
|
|
|
/* Remove filter if there are no listeners left for the connection */
|
|
if (filter_data_find(data->connection, listener_list) == NULL) {
|
|
dbus_connection_remove_filter(data->connection, message_filter,
|
|
data->watcher);
|
|
}
|
|
|
|
for (l = _dbus_list_get_first_link(&data->callbacks); l != NULL;
|
|
l = _dbus_list_get_next_link(&data->callbacks, l))
|
|
free(l->data);
|
|
|
|
_dbus_list_clear(&data->callbacks);
|
|
dbus_remove_watch(data->watcher, data->name_watch);
|
|
free(data->name);
|
|
free(data->owner);
|
|
free(data->path);
|
|
free(data->interface);
|
|
free(data->member);
|
|
free(data->argument);
|
|
|
|
/* no listeners left for the connection */
|
|
if (_dbus_list_get_last(&listener_list) == NULL) {
|
|
free_dbus_watch(data->watcher);
|
|
}
|
|
dbus_connection_unref(data->connection);
|
|
free(data);
|
|
}
|
|
|
|
static struct filter_data* filter_data_get(GDBusWatch* watcher,
|
|
DBusHandleMessageFunction filter,
|
|
const char* sender, const char* path, const char* interface,
|
|
const char* member, const char* argument)
|
|
{
|
|
struct filter_data* data;
|
|
const char *name = NULL, *owner = NULL;
|
|
|
|
if (filter_data_find(watcher->conn, watcher->list) == NULL) {
|
|
if (!dbus_connection_add_filter(watcher->conn,
|
|
message_filter, watcher, NULL)) {
|
|
error("dbus_connection_add_filter() failed");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (sender != NULL) {
|
|
if (sender[0] == ':')
|
|
owner = sender;
|
|
else
|
|
name = sender;
|
|
}
|
|
|
|
data = filter_data_find_match(watcher->conn, watcher->list, name, owner, path,
|
|
interface, member, argument);
|
|
if (data)
|
|
return data;
|
|
|
|
data = calloc(1, sizeof(struct filter_data));
|
|
|
|
data->connection = dbus_connection_ref(watcher->conn);
|
|
data->name = strdup0(name);
|
|
data->owner = strdup0(owner);
|
|
data->path = strdup0(path);
|
|
data->interface = strdup0(interface);
|
|
data->member = strdup0(member);
|
|
data->argument = strdup0(argument);
|
|
data->watcher = watcher;
|
|
|
|
if (!add_match(data, filter)) {
|
|
dbus_connection_unref(data->connection);
|
|
free(data->name);
|
|
free(data->owner);
|
|
free(data->path);
|
|
free(data->interface);
|
|
free(data->member);
|
|
free(data->argument);
|
|
free(data);
|
|
return NULL;
|
|
}
|
|
|
|
_dbus_list_append(&watcher->list, data);
|
|
return data;
|
|
}
|
|
|
|
static struct filter_callback* filter_data_find_callback(
|
|
struct filter_data* data,
|
|
guint id)
|
|
{
|
|
DBusList* l;
|
|
|
|
for (l = _dbus_list_get_first_link(&data->callbacks); l != NULL;
|
|
l = _dbus_list_get_next_link(&data->callbacks, l)) {
|
|
struct filter_callback* cb = l->data;
|
|
if (cb->id == id)
|
|
return cb;
|
|
}
|
|
for (l = _dbus_list_get_first_link(&data->processed); l != NULL;
|
|
l = _dbus_list_get_next_link(&data->processed, l)) {
|
|
struct filter_callback* cb = l->data;
|
|
if (cb->id == id)
|
|
return cb;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void filter_data_call_and_free(struct filter_data* data, DBusList* listener_list)
|
|
{
|
|
DBusList* l;
|
|
|
|
for (l = _dbus_list_get_first_link(&data->callbacks); l != NULL;
|
|
l = _dbus_list_get_next_link(&data->callbacks, l)) {
|
|
struct filter_callback* cb = l->data;
|
|
if (cb->disc_func)
|
|
cb->disc_func(data->connection, cb->user_data);
|
|
if (cb->destroy_func)
|
|
cb->destroy_func(cb->user_data);
|
|
}
|
|
|
|
filter_data_free(data, listener_list);
|
|
}
|
|
|
|
static struct filter_callback* filter_data_add_callback(
|
|
struct filter_data* data,
|
|
GDBusWatchFunction connect,
|
|
GDBusWatchFunction disconnect,
|
|
GDBusSignalFunction signal,
|
|
GDBusDestroyFunction destroy,
|
|
guint watch_id,
|
|
void* user_data)
|
|
{
|
|
struct filter_callback* cb = NULL;
|
|
|
|
cb = calloc(1, sizeof(struct filter_callback));
|
|
|
|
cb->conn_func = connect;
|
|
cb->disc_func = disconnect;
|
|
cb->signal_func = signal;
|
|
cb->destroy_func = destroy;
|
|
cb->user_data = user_data;
|
|
cb->id = watch_id;
|
|
|
|
if (data->lock)
|
|
_dbus_list_append(&data->processed, cb);
|
|
else
|
|
_dbus_list_append(&data->callbacks, cb);
|
|
|
|
return cb;
|
|
}
|
|
|
|
static void close_cb(uv_handle_t* handle)
|
|
{
|
|
free(handle->data);
|
|
}
|
|
|
|
static void service_data_free(struct service_data* data)
|
|
{
|
|
struct filter_callback* callback = data->callback;
|
|
|
|
dbus_connection_unref(data->conn);
|
|
|
|
if (data->call)
|
|
dbus_pending_call_unref(data->call);
|
|
|
|
free(data->name);
|
|
callback->data = NULL;
|
|
|
|
uv_close((uv_handle_t*)&data->handle, close_cb);
|
|
}
|
|
|
|
/* Returns TRUE if data is freed */
|
|
static gboolean filter_data_remove_callback(struct filter_data* data,
|
|
struct filter_callback* cb)
|
|
{
|
|
_dbus_list_remove(&data->callbacks, cb);
|
|
_dbus_list_remove(&data->processed, cb);
|
|
|
|
/* Cancel pending operations */
|
|
if (cb->data) {
|
|
if (cb->data->call)
|
|
dbus_pending_call_cancel(cb->data->call);
|
|
service_data_free(cb->data);
|
|
}
|
|
|
|
if (cb->destroy_func)
|
|
cb->destroy_func(cb->user_data);
|
|
|
|
free(cb);
|
|
|
|
/* Don't remove the filter if other callbacks exist or data is lock
|
|
* processing callbacks */
|
|
if (data->callbacks || data->lock)
|
|
return FALSE;
|
|
|
|
if (data->registered && !remove_match(data))
|
|
return FALSE;
|
|
|
|
_dbus_list_remove(&data->watcher->list, data);
|
|
filter_data_free(data, data->watcher->list);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static DBusHandlerResult signal_filter(DBusConnection* connection,
|
|
DBusMessage* message, void* user_data)
|
|
{
|
|
struct filter_data* data = user_data;
|
|
struct filter_callback* cb;
|
|
|
|
while (data->callbacks) {
|
|
cb = data->callbacks->data;
|
|
|
|
if (cb->signal_func && !cb->signal_func(connection, message, cb->user_data)) {
|
|
if (filter_data_remove_callback(data, cb))
|
|
break;
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Check if the watch was removed/freed by the callback
|
|
* function */
|
|
if (_dbus_list_find_last(&data->callbacks, cb) == NULL)
|
|
continue;
|
|
|
|
_dbus_list_remove(&data->callbacks, cb);
|
|
_dbus_list_append(&data->processed, cb);
|
|
}
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
static void update_name_cache(DBusList* listener_list, const char* name, const char* owner)
|
|
{
|
|
DBusList* l;
|
|
|
|
for (l = _dbus_list_get_first_link(&listener_list); l != NULL;
|
|
l = _dbus_list_get_next_link(&listener_list, l)) {
|
|
struct filter_data* data = l->data;
|
|
|
|
if (strcmp0(data->name, name) != 0)
|
|
continue;
|
|
|
|
free(data->owner);
|
|
data->owner = strdup0(owner);
|
|
}
|
|
}
|
|
|
|
static const char* check_name_cache(DBusList* listener_list, const char* name)
|
|
{
|
|
DBusList* l;
|
|
|
|
for (l = _dbus_list_get_first_link(&listener_list); l != NULL;
|
|
l = _dbus_list_get_next_link(&listener_list, l)) {
|
|
struct filter_data* data = l->data;
|
|
|
|
if (strcmp0(data->name, name) != 0)
|
|
continue;
|
|
|
|
return data->owner;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DBusHandlerResult service_filter(DBusConnection* connection,
|
|
DBusMessage* message, void* user_data)
|
|
{
|
|
struct filter_data* data = user_data;
|
|
struct filter_callback* cb;
|
|
char *name, *old, *new;
|
|
|
|
if (!dbus_message_get_args(message, NULL,
|
|
DBUS_TYPE_STRING, &name,
|
|
DBUS_TYPE_STRING, &old,
|
|
DBUS_TYPE_STRING, &new,
|
|
DBUS_TYPE_INVALID)) {
|
|
error("Invalid arguments for NameOwnerChanged signal");
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
update_name_cache(data->watcher->list, name, new);
|
|
|
|
while (data->callbacks) {
|
|
cb = data->callbacks->data;
|
|
|
|
if (*new == '\0') {
|
|
if (cb->disc_func)
|
|
cb->disc_func(connection, cb->user_data);
|
|
} else {
|
|
if (cb->conn_func)
|
|
cb->conn_func(connection, cb->user_data);
|
|
}
|
|
|
|
/* Check if the watch was removed/freed by the callback
|
|
* function */
|
|
if (_dbus_list_find_last(&data->callbacks, cb) == NULL)
|
|
continue;
|
|
|
|
/* Only auto remove if it is a bus name watch */
|
|
if (data->argument[0] == ':' && (cb->conn_func == NULL || cb->disc_func == NULL)) {
|
|
if (filter_data_remove_callback(data, cb))
|
|
break;
|
|
|
|
continue;
|
|
}
|
|
|
|
_dbus_list_remove(&data->callbacks, cb);
|
|
_dbus_list_append(&data->processed, cb);
|
|
}
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
static bool process_signal_message(DBusMessage* message,
|
|
const char** sender, const char** path,
|
|
const char** iface, const char** member,
|
|
const char** arg)
|
|
{
|
|
if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
|
|
return false;
|
|
|
|
*sender = dbus_message_get_sender(message);
|
|
*path = dbus_message_get_path(message);
|
|
*iface = dbus_message_get_interface(message);
|
|
*member = dbus_message_get_member(message);
|
|
if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, arg, DBUS_TYPE_INVALID))
|
|
*arg = NULL;
|
|
|
|
return true;
|
|
}
|
|
static int filter_data_match(const char* str1, const char* data_param)
|
|
{
|
|
if (data_param == NULL)
|
|
return 0;
|
|
|
|
if (str1 == NULL)
|
|
return -1;
|
|
|
|
return strcmp(str1, data_param);
|
|
}
|
|
|
|
static gboolean match_listener_data(struct filter_data* data, DBusConnection* connection,
|
|
const char* sender, const char* path,
|
|
const char* iface, const char* member, const char* arg)
|
|
{
|
|
if (connection != data->connection)
|
|
return FALSE;
|
|
|
|
if (filter_data_match(sender, data->owner) != 0)
|
|
return FALSE;
|
|
|
|
if (filter_data_match(path, data->path) != 0)
|
|
return FALSE;
|
|
|
|
if (filter_data_match(iface, data->interface) != 0)
|
|
return FALSE;
|
|
|
|
if (filter_data_match(member, data->member) != 0)
|
|
return FALSE;
|
|
|
|
if (filter_data_match(arg, data->argument) != 0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void process_listener_callbacks(struct filter_data* data, DBusConnection* connection,
|
|
DBusMessage* message)
|
|
{
|
|
|
|
if (!data->handle_func)
|
|
return;
|
|
|
|
data->lock = TRUE;
|
|
data->handle_func(connection, message, data);
|
|
data->callbacks = data->processed;
|
|
data->processed = NULL;
|
|
data->lock = FALSE;
|
|
}
|
|
|
|
static void process_listeners(DBusList* listener_list, DBusConnection* connection,
|
|
DBusMessage* message, DBusList** delete_listener, const char* sender,
|
|
const char* path, const char* iface, const char* member, const char* arg)
|
|
{
|
|
DBusList* current;
|
|
struct filter_data* data;
|
|
|
|
for (current = _dbus_list_get_first_link(&listener_list); current;
|
|
current = _dbus_list_get_next_link(&listener_list, current)) {
|
|
data = current->data;
|
|
|
|
if (!match_listener_data(data, connection, sender,
|
|
path, iface, member, arg))
|
|
continue;
|
|
|
|
process_listener_callbacks(data, connection, message);
|
|
|
|
if (!data->callbacks)
|
|
_dbus_list_prepend(delete_listener, current);
|
|
}
|
|
}
|
|
|
|
static void cleanup_listeners(DBusList* delete_listener, GDBusWatch* watcher)
|
|
{
|
|
DBusList* current;
|
|
struct filter_data* data;
|
|
|
|
for (current = _dbus_list_get_first_link(&delete_listener); current;
|
|
current = _dbus_list_get_next_link(&delete_listener, current)) {
|
|
DBusList* l = current->data;
|
|
data = l->data;
|
|
|
|
/* Has any other callback added callbacks back to this data? */
|
|
if (data->callbacks)
|
|
continue;
|
|
|
|
remove_match(data);
|
|
_dbus_list_remove_link(&watcher->list, l);
|
|
filter_data_free(data, watcher->list);
|
|
}
|
|
_dbus_list_clear(&delete_listener);
|
|
}
|
|
|
|
static DBusHandlerResult message_filter(DBusConnection* connection,
|
|
DBusMessage* message, void* user_data)
|
|
{
|
|
const char *sender, *path, *iface, *member, *arg = NULL;
|
|
DBusList* delete_listener = NULL;
|
|
GDBusWatch* watcher = user_data;
|
|
|
|
if (!process_signal_message(message, &sender, &path,
|
|
&iface, &member, &arg))
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
process_listeners(watcher->list, connection, message, &delete_listener,
|
|
sender, path, iface, member, arg);
|
|
|
|
if (delete_listener)
|
|
cleanup_listeners(delete_listener, watcher);
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
static void update_service(uv_idle_t* handle)
|
|
{
|
|
struct service_data* data = handle->data;
|
|
struct filter_callback* cb = data->callback;
|
|
DBusConnection* conn;
|
|
|
|
conn = dbus_connection_ref(data->conn);
|
|
service_data_free(data);
|
|
|
|
if (cb->conn_func)
|
|
cb->conn_func(conn, cb->user_data);
|
|
|
|
dbus_connection_unref(conn);
|
|
}
|
|
|
|
static void service_reply(DBusPendingCall* call, void* user_data)
|
|
{
|
|
struct service_data* data = user_data;
|
|
DBusMessage* reply;
|
|
DBusError err;
|
|
|
|
reply = dbus_pending_call_steal_reply(call);
|
|
if (reply == NULL)
|
|
return;
|
|
|
|
dbus_error_init(&err);
|
|
|
|
if (dbus_set_error_from_message(&err, reply))
|
|
goto fail;
|
|
|
|
if (dbus_message_get_args(reply, &err, DBUS_TYPE_STRING,
|
|
&data->owner, DBUS_TYPE_INVALID)
|
|
== FALSE)
|
|
goto fail;
|
|
|
|
update_service(&data->handle);
|
|
|
|
goto done;
|
|
|
|
fail:
|
|
error("service_reply fail: %s", err.message);
|
|
dbus_error_free(&err);
|
|
service_data_free(data);
|
|
done:
|
|
dbus_message_unref(reply);
|
|
}
|
|
|
|
static struct service_data* create_service_data(DBusConnection* conn,
|
|
const char* name,
|
|
struct filter_callback* cb)
|
|
{
|
|
struct service_data* data = calloc(1, sizeof(*data));
|
|
if (!data) {
|
|
error("%s: malloc failed", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
data->conn = dbus_connection_ref(conn);
|
|
data->name = strdup0(name);
|
|
data->callback = cb;
|
|
cb->data = data;
|
|
return data;
|
|
}
|
|
|
|
static bool init_uv_handle(struct service_data* data)
|
|
{
|
|
if (uv_idle_init(uv_default_loop(), &data->handle) != 0) {
|
|
error("%s: uv_idle_init failed", __func__);
|
|
return false;
|
|
}
|
|
data->handle.data = data;
|
|
return true;
|
|
}
|
|
|
|
static gboolean send_name_owner_request(struct service_data* data, const char* name)
|
|
{
|
|
DBusMessage* msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS,
|
|
DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner");
|
|
if (!msg) {
|
|
error("%s: dbus_message_new_method_call failed", __func__);
|
|
return FALSE;
|
|
}
|
|
|
|
dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID);
|
|
if (!dbus_connection_send_with_reply(data->conn, msg, &data->call, -1)) {
|
|
error("%s: dbus_connection_send_with_reply failed", __func__);
|
|
dbus_message_unref(msg);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!data->call) {
|
|
error("%s: dbus reply failed with empty pendingcall", __func__);
|
|
dbus_message_unref(msg);
|
|
return FALSE;
|
|
}
|
|
|
|
if (dbus_pending_call_get_completed(data->call)) {
|
|
service_reply(data->call, data);
|
|
} else {
|
|
dbus_pending_call_set_notify(data->call, service_reply, data, NULL);
|
|
}
|
|
|
|
dbus_message_unref(msg);
|
|
return TRUE;
|
|
}
|
|
|
|
static void check_service(DBusConnection* conn, const char* name,
|
|
struct filter_callback* cb, DBusList* listener_list)
|
|
{
|
|
struct service_data* data = create_service_data(conn, name, cb);
|
|
if (!data)
|
|
return;
|
|
|
|
if (!init_uv_handle(data)) {
|
|
dbus_connection_unref(data->conn);
|
|
free(data->name);
|
|
free(data);
|
|
return;
|
|
}
|
|
|
|
data->owner = check_name_cache(listener_list, name);
|
|
if (data->owner) {
|
|
if (uv_idle_start(&data->handle, update_service) != 0) {
|
|
error("%s: uv_idle_start failed:", __func__);
|
|
uv_close((uv_handle_t*)&data->handle, NULL);
|
|
dbus_connection_unref(data->conn);
|
|
free(data->name);
|
|
free(data);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (send_name_owner_request(data, name) == FALSE) {
|
|
uv_close((uv_handle_t*)&data->handle, NULL);
|
|
dbus_connection_unref(data->conn);
|
|
free(data->name);
|
|
free(data);
|
|
return;
|
|
}
|
|
}
|
|
|
|
guint dbus_add_service_watch(GDBusWatch* watcher, const char* name,
|
|
GDBusWatchFunction connect,
|
|
GDBusWatchFunction disconnect,
|
|
void* user_data, GDBusDestroyFunction destroy)
|
|
{
|
|
struct filter_data* data;
|
|
struct filter_callback* cb;
|
|
|
|
if (name == NULL)
|
|
return 0;
|
|
|
|
data = filter_data_get(watcher, service_filter, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
|
|
DBUS_INTERFACE_DBUS, "NameOwnerChanged", name);
|
|
if (data == NULL)
|
|
return 0;
|
|
|
|
cb = filter_data_add_callback(data, connect, disconnect, NULL, destroy,
|
|
get_watch_serial(watcher), user_data);
|
|
if (cb == NULL)
|
|
return 0;
|
|
|
|
if (connect)
|
|
check_service(watcher->conn, name, cb, watcher->list);
|
|
|
|
return cb->id;
|
|
}
|
|
|
|
guint dbus_add_service_disconnect_watch(GDBusWatch* watcher, const char* name,
|
|
GDBusWatchFunction func, void* user_data, GDBusDestroyFunction destroy)
|
|
{
|
|
return dbus_add_service_watch(watcher, name, NULL, func, user_data, destroy);
|
|
}
|
|
|
|
guint dbus_add_signal_watch(GDBusWatch* watcher,
|
|
const char* sender, const char* path,
|
|
const char* interface, const char* member,
|
|
GDBusSignalFunction function, void* user_data,
|
|
GDBusDestroyFunction destroy)
|
|
{
|
|
struct filter_data* data;
|
|
struct filter_callback* cb;
|
|
|
|
data = filter_data_get(watcher, signal_filter, sender, path,
|
|
interface, member, NULL);
|
|
if (data == NULL)
|
|
return 0;
|
|
|
|
cb = filter_data_add_callback(data, NULL, NULL, function, destroy,
|
|
get_watch_serial(watcher), user_data);
|
|
if (cb == NULL)
|
|
return 0;
|
|
|
|
if (data->name != NULL && data->name_watch == 0)
|
|
data->name_watch = dbus_add_service_watch(watcher,
|
|
data->name, NULL,
|
|
NULL, NULL, NULL);
|
|
|
|
return cb->id;
|
|
}
|
|
|
|
guint dbus_add_properties_watch(GDBusWatch* watcher,
|
|
const char* sender, const char* path,
|
|
const char* interface,
|
|
GDBusSignalFunction function, void* user_data,
|
|
GDBusDestroyFunction destroy)
|
|
{
|
|
struct filter_data* data;
|
|
struct filter_callback* cb;
|
|
|
|
data = filter_data_get(watcher, signal_filter, sender, path,
|
|
DBUS_INTERFACE_PROPERTIES, "PropertiesChanged", interface);
|
|
if (data == NULL)
|
|
return 0;
|
|
|
|
cb = filter_data_add_callback(data, NULL, NULL, function, destroy,
|
|
get_watch_serial(watcher), user_data);
|
|
if (cb == NULL)
|
|
return 0;
|
|
|
|
if (data->name != NULL && data->name_watch == 0)
|
|
data->name_watch = dbus_add_service_watch(watcher,
|
|
data->name, NULL,
|
|
NULL, NULL, NULL);
|
|
|
|
return cb->id;
|
|
}
|
|
|
|
gboolean dbus_remove_watch(GDBusWatch* watcher, guint id)
|
|
{
|
|
struct filter_data* data;
|
|
struct filter_callback* cb;
|
|
DBusList* ldata;
|
|
|
|
if (id == 0)
|
|
return FALSE;
|
|
|
|
for (ldata = _dbus_list_get_first_link(&watcher->list); ldata != NULL;
|
|
ldata = _dbus_list_get_next_link(&watcher->list, ldata)) {
|
|
data = ldata->data;
|
|
|
|
cb = filter_data_find_callback(data, id);
|
|
if (cb) {
|
|
filter_data_remove_callback(data, cb);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void dbus_remove_all_watches(GDBusWatch* watcher)
|
|
{
|
|
struct filter_data* data;
|
|
|
|
while ((data = filter_data_find(watcher->conn, watcher->list))) {
|
|
_dbus_list_remove(&watcher->list, data);
|
|
filter_data_call_and_free(data, watcher->list);
|
|
}
|
|
}
|
|
|
|
GDBusWatch* new_dbus_watch(DBusConnection* connection)
|
|
{
|
|
GDBusWatch* watcher;
|
|
|
|
watcher = calloc(1, sizeof(GDBusWatch));
|
|
if (watcher == NULL)
|
|
return NULL;
|
|
|
|
watcher->conn = dbus_connection_ref(connection);
|
|
watcher->list = NULL;
|
|
watcher->serial = 0;
|
|
watcher->conn_closed = FALSE;
|
|
|
|
return watcher;
|
|
}
|
|
void free_dbus_watch(GDBusWatch* watcher)
|
|
{
|
|
dbus_connection_unref(watcher->conn);
|
|
free(watcher);
|
|
}
|
|
|
|
guint dbus_client_add_service_watch(GDBusClient* client, const char* name,
|
|
GDBusWatchFunction connect, GDBusWatchFunction disconnect,
|
|
void* user_data, GDBusDestroyFunction destroy)
|
|
{
|
|
return dbus_add_service_watch(client->watcher, name, connect, disconnect,
|
|
user_data, destroy);
|
|
}
|
|
|
|
guint dbus_client_add_service_disconnect_watch(GDBusClient* client, const char* name,
|
|
GDBusWatchFunction function, void* user_data, GDBusDestroyFunction destroy)
|
|
{
|
|
return dbus_add_service_disconnect_watch(client->watcher, name, function, user_data, destroy);
|
|
}
|
|
|
|
guint dbus_client_add_signal_watch(GDBusClient* client, const char* sender,
|
|
const char* path, const char* interface, const char* member,
|
|
GDBusSignalFunction function, void* user_data, GDBusDestroyFunction destroy)
|
|
{
|
|
return dbus_add_signal_watch(client->watcher, sender, path, interface,
|
|
member, function, user_data, destroy);
|
|
}
|
|
|
|
guint dbus_client_add_properties_watch(GDBusClient* client,
|
|
const char* sender, const char* path, const char* interface,
|
|
GDBusSignalFunction function, void* user_data, GDBusDestroyFunction destroy)
|
|
{
|
|
return dbus_add_properties_watch(client->watcher, sender, path, interface,
|
|
function, user_data, destroy);
|
|
}
|
|
|
|
gboolean dbus_client_remove_watch(GDBusClient* client, guint tag)
|
|
{
|
|
return dbus_remove_watch(client->watcher, tag);
|
|
}
|
|
|
|
void dbus_client_remove_all_watches(GDBusClient* client)
|
|
{
|
|
dbus_remove_all_watches(client->watcher);
|
|
}
|
|
|
|
void dbus_watch_set_connection_state(GDBusWatch* watcher, gboolean closed)
|
|
{
|
|
watcher->conn_closed = closed;
|
|
}
|