/* * * Library for simple D-Bus integration with GLib * * Copyright (C) 2007-2008 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 version 2.1 as published by the Free Software Foundation. * * 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 #endif #include #include "gdbus.h" #include "debug.h" static dbus_int32_t connection_slot = -1; typedef struct { GSList *watches; GSList *handlers; guint next_id; } ConnectionData; typedef struct { guint id; char *name; void *user_data; char *match; BDBusWatchFunction connect; BDBusWatchFunction disconn; BDBusDestroyFunction destroy; } WatchData; typedef struct { guint id; void *user_data; BDBusWatchFunction function; BDBusDestroyFunction destroy; } DisconnectData; typedef struct { guint id; void *user_data; char *match; BDBusSignalFunction function; BDBusDestroyFunction destroy; } SignalData; static DBusHandlerResult signal_function(DBusConnection *connection, DBusMessage *message, void *user_data) { ConnectionData *data = user_data; GSList *list; DBG("connection %p message %p", connection, message); for (list = data->handlers; list; list = list->next) { SignalData *signal = list->data; gboolean result; if (signal->function == NULL) continue; result = signal->function(connection, message, signal->user_data); if (result == TRUE) continue; if (signal->destroy != NULL) signal->destroy(signal->user_data); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusHandlerResult owner_function(DBusConnection *connection, DBusMessage *message, void *user_data) { ConnectionData *data = user_data; GSList *list; const char *name, *old, *new; DBG("connection %p message %p", connection, message); if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old, DBUS_TYPE_STRING, &new, DBUS_TYPE_INVALID) == FALSE) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; DBG("name %s \"%s\" => \"%s\"", name, old, new); /* * Allow the watch to remove itself. That'll make the "list" pointer * invalid, need to read it before calling the watch. */ list = data->watches; while (list) { WatchData *watch = list->data; list = list->next; if (strcmp(name, watch->name) != 0) continue; if (watch->connect != NULL && *old == '\0' && *new != '\0') watch->connect(connection, watch->user_data); if (watch->disconn != NULL && *old != '\0' && *new == '\0') watch->disconn(connection, watch->user_data); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusHandlerResult filter_function(DBusConnection *connection, DBusMessage *message, void *user_data) { if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged") == TRUE) return owner_function(connection, message, user_data); if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) return signal_function(connection, message, user_data); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static ConnectionData *get_connection_data(DBusConnection *connection) { ConnectionData *data; /* dbus_bool_t result; */ DBG("connection %p", connection); if (dbus_connection_allocate_data_slot(&connection_slot) == FALSE) return NULL; DBG("connection slot %d", connection_slot); data = dbus_connection_get_data(connection, connection_slot); if (data == NULL) { data = g_try_new0(ConnectionData, 1); if (data == NULL) { dbus_connection_free_data_slot(&connection_slot); return NULL; } data->next_id = 1; if (dbus_connection_set_data(connection, connection_slot, data, NULL) == FALSE) { dbus_connection_free_data_slot(&connection_slot); g_free(data); return NULL; } /* result = */ dbus_connection_add_filter(connection, filter_function, data, NULL); } return data; } static void put_connection_data(DBusConnection *connection) { dbus_connection_free_data_slot(&connection_slot); } /** * b_dbus_add_service_watch: * @connection: the connection * @name: unique or well known name * @connect: function called on name connect * @disconnect: function called on name disconnect * @user_data: user data to pass to the function * @destroy: function called to destroy user_data * * Add new watch to listen for connects and/or disconnects * of a client for the given connection. * * Returns: identifier of the watch */ guint b_dbus_add_service_watch(DBusConnection *connection, const char *name, BDBusWatchFunction connect, BDBusWatchFunction disconnect, void *user_data, BDBusDestroyFunction destroy) { ConnectionData *data; WatchData *watch; DBusError error; DBG("connection %p name %s", connection, name); data = get_connection_data(connection); if (data == NULL) return 0; DBG("connection data %p", data); watch = g_try_new0(WatchData, 1); if (watch == NULL) goto error; watch->name = g_strdup(name); if (watch->name == NULL) goto error; watch->user_data = user_data; watch->connect = connect; watch->disconn = disconnect; watch->destroy = destroy; watch->match = g_strdup_printf("interface=%s,member=NameOwnerChanged,arg0=%s", DBUS_INTERFACE_DBUS, name); if (watch->match == NULL) goto error; dbus_error_init(&error); dbus_bus_add_match(connection, watch->match, &error); if (dbus_error_is_set(&error) == TRUE) { dbus_error_free(&error); goto error; } watch->id = data->next_id++; data->watches = g_slist_append(data->watches, watch); DBG("tag %d", watch->id); return watch->id; error: // Doesn't look valid: Possible null pointer dereference: watch - otherwise it is redundant to check it against null. // cppcheck-suppress nullPointer if (watch != NULL) { g_free(watch->name); g_free(watch->match); } g_free(watch); put_connection_data(connection); return 0; } /** * b_dbus_remove_watch: * @connection: the connection * @tag: watch identifier * * Removes the watch for the given identifier. * * Returns: #TRUE on success */ gboolean b_dbus_remove_watch(DBusConnection *connection, guint tag) { ConnectionData *data; GSList *list; DBG("connection %p tag %d", connection, tag); if (connection_slot < 0) return FALSE; data = dbus_connection_get_data(connection, connection_slot); if (data == NULL) return FALSE; for (list = data->watches; list; list = list->next) { WatchData *watch = list->data; if (watch->id == tag) { data->watches = g_slist_remove(data->watches, watch); if (watch->destroy != NULL) watch->destroy(watch->user_data); dbus_bus_remove_match(connection, watch->match, NULL); g_free(watch->name); g_free(watch->match); g_free(watch); goto done; } } for (list = data->handlers; list; list = list->next) { SignalData *signal = list->data; if (signal->id == tag) { data->handlers = g_slist_remove(data->handlers, signal); if (signal->destroy != NULL) signal->destroy(signal->user_data); dbus_bus_remove_match(connection, signal->match, NULL); g_free(signal->match); g_free(signal); goto done; } } return FALSE; done: if (!data->watches && !data->handlers) { dbus_connection_remove_filter(connection, filter_function, data); dbus_connection_set_data(connection, connection_slot, NULL, NULL); g_free(data); } dbus_connection_free_data_slot(&connection_slot); DBG("connection slot %d", connection_slot); return TRUE; } /** * b_dbus_remove_all_watches: * @connection: the connection * * Removes all registered watches. */ void b_dbus_remove_all_watches(DBusConnection *connection) { ConnectionData *data; GSList *list; DBG("connection %p slot %d", connection, connection_slot); if (connection_slot < 0) return; data = dbus_connection_get_data(connection, connection_slot); if (data == NULL) return; DBG("connection data %p", data); for (list = data->watches; list; list = list->next) { WatchData *watch = list->data; DBG("watch data %p tag %d", watch, watch->id); if (watch->destroy != NULL) watch->destroy(watch->user_data); dbus_bus_remove_match(connection, watch->match, NULL); g_free(watch->match); g_free(watch->name); g_free(watch); dbus_connection_free_data_slot(&connection_slot); DBG("connection slot %d", connection_slot); } g_slist_free(data->watches); for (list = data->handlers; list; list = list->next) { SignalData *signal = list->data; DBG("signal data %p tag %d", signal, signal->id); if (signal->destroy != NULL) signal->destroy(signal->user_data); dbus_bus_remove_match(connection, signal->match, NULL); g_free(signal->match); g_free(signal); dbus_connection_free_data_slot(&connection_slot); DBG("connection slot %d", connection_slot); } g_slist_free(data->handlers); dbus_connection_remove_filter(connection, filter_function, data); g_free(data); } static void disconnect_function(DBusConnection *connection, void *user_data) { DisconnectData *data = user_data; // The callback function might remove the watch, // which invalidates the data pointer. Remember // the ID. guint id = data->id; data->function(connection, data->user_data); b_dbus_remove_watch(connection, id); } static void disconnect_release(void *user_data) { g_free(user_data); } /** * b_dbus_add_disconnect_watch: * @connection: the connection * @name: unique or well known name * @function: function called on name disconnect * @user_data: user data to pass to the function * @destroy: function called to destroy user_data * * Add new watch to listen for disconnect of a client * for the given connection. * * After the callback has been called, this watch will be * automatically removed. * * Returns: identifier of the watch */ guint b_dbus_add_disconnect_watch(DBusConnection *connection, const char *name, BDBusWatchFunction function, void *user_data, BDBusDestroyFunction destroy) { DisconnectData *data; data = g_try_new0(DisconnectData, 1); if (data == NULL) return 0; data->user_data = user_data; data->function = function; data->destroy = destroy; data->id = b_dbus_add_service_watch(connection, name, NULL, disconnect_function, data, disconnect_release); if (data->id == 0) { g_free(data); return 0; } return data->id; } /** * b_dbus_add_signal_watch: * @connection: the connection * @rule: matching rule for this signal * @function: function called when signal arrives * @user_data: user data to pass to the function * @destroy: function called to destroy user_data * @is_bus_conn: whether the connection is with the bus * * Add new watch to listen for specific signals of * a client for the given connection. * * If the callback returns #FALSE this watch will be * automatically removed. * * Returns: identifier of the watch */ guint b_dbus_add_signal_watch(DBusConnection *connection, const char *rule, BDBusSignalFunction function, void *user_data, BDBusDestroyFunction destroy, gboolean is_bus_conn) { ConnectionData *data; SignalData *signal; DBusError error; DBG("connection %p rule %s", connection, rule); data = get_connection_data(connection); if (data == NULL) return 0; DBG("connection data %p", data); signal = g_try_new0(SignalData, 1); if (signal == NULL) goto error; signal->match = g_strdup(rule); if (!signal->match) goto error; signal->user_data = user_data; signal->function = function; signal->destroy = destroy; if (is_bus_conn) { dbus_error_init(&error); dbus_bus_add_match(connection, rule, &error); if (dbus_error_is_set(&error) == TRUE) { dbus_error_free(&error); goto error; } } signal->id = data->next_id++; data->handlers = g_slist_append(data->handlers, signal); DBG("tag %d", signal->id); return signal->id; error: // Doesn't look valid: Possible null pointer dereference: signal - otherwise it is redundant to check it against null. // cppcheck-suppress nullPointer if (signal) g_free(signal->match); g_free(signal); put_connection_data(connection); return 0; }