/* * * D-Bus helper library * * Copyright (C) 2004-2009 Marcel Holtmann * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; 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 #include #include #include "gdbus.h" #define info(fmt...) #define error(fmt...) #define debug(fmt...) static DBusHandlerResult name_exit_filter(DBusConnection *connection, DBusMessage *message, void *user_data); static guint listener_id = 0; static GSList *name_listeners = NULL; struct name_callback { GDBusWatchFunction conn_func; GDBusWatchFunction disc_func; void *user_data; guint id; }; struct name_data { DBusConnection *connection; char *name; GSList *callbacks; GSList *processed; gboolean lock; }; static struct name_data *name_data_find(DBusConnection *connection, const char *name) { GSList *current; for (current = name_listeners; current != NULL; current = current->next) { struct name_data *data = current->data; if (connection != data->connection) continue; if (name == NULL || g_str_equal(name, data->name)) return data; } return NULL; } static struct name_callback *name_callback_find(GSList *callbacks, guint id) { GSList *current; for (current = callbacks; current != NULL; current = current->next) { struct name_callback *cb = current->data; if (cb->id == id) return cb; } return NULL; } static void name_data_call_and_free(struct name_data *data) { GSList *l; for (l = data->callbacks; l != NULL; l = l->next) { struct name_callback *cb = l->data; if (cb->disc_func) cb->disc_func(data->connection, cb->user_data); g_free(cb); } g_slist_free(data->callbacks); g_free(data->name); g_free(data); } static void name_data_free(struct name_data *data) { GSList *l; for (l = data->callbacks; l != NULL; l = l->next) g_free(l->data); g_slist_free(data->callbacks); g_free(data->name); g_free(data); } static int name_data_add(DBusConnection *connection, const char *name, GDBusWatchFunction connect, GDBusWatchFunction disconnect, void *user_data, guint id) { int first = 1; struct name_data *data = NULL; struct name_callback *cb = NULL; cb = g_new(struct name_callback, 1); cb->conn_func = connect; cb->disc_func = disconnect; cb->user_data = user_data; cb->id = id; data = name_data_find(connection, name); if (data) { first = 0; goto done; } data = g_new0(struct name_data, 1); data->connection = connection; data->name = g_strdup(name); name_listeners = g_slist_append(name_listeners, data); done: if (data->lock) data->processed = g_slist_append(data->processed, cb); else data->callbacks = g_slist_append(data->callbacks, cb); return first; } static void name_data_remove(DBusConnection *connection, const char *name, guint id) { struct name_data *data; struct name_callback *cb = NULL; data = name_data_find(connection, name); if (!data) return; cb = name_callback_find(data->callbacks, id); if (cb) { data->callbacks = g_slist_remove(data->callbacks, cb); g_free(cb); } if (data->callbacks) return; name_listeners = g_slist_remove(name_listeners, data); name_data_free(data); /* Remove filter if there are no listeners left for the connection */ data = name_data_find(connection, NULL); if (!data) dbus_connection_remove_filter(connection, name_exit_filter, NULL); } static gboolean add_match(DBusConnection *connection, const char *name) { DBusError err; char match_string[128]; snprintf(match_string, sizeof(match_string), "interface=%s,member=NameOwnerChanged,arg0=%s", DBUS_INTERFACE_DBUS, name); dbus_error_init(&err); dbus_bus_add_match(connection, match_string, &err); if (dbus_error_is_set(&err)) { error("Adding match rule \"%s\" failed: %s", match_string, err.message); dbus_error_free(&err); return FALSE; } return TRUE; } static gboolean remove_match(DBusConnection *connection, const char *name) { DBusError err; char match_string[128]; snprintf(match_string, sizeof(match_string), "interface=%s,member=NameOwnerChanged,arg0=%s", DBUS_INTERFACE_DBUS, name); dbus_error_init(&err); dbus_bus_remove_match(connection, match_string, &err); if (dbus_error_is_set(&err)) { error("Removing owner match rule for %s failed: %s", name, err.message); dbus_error_free(&err); return FALSE; } return TRUE; } static DBusHandlerResult name_exit_filter(DBusConnection *connection, DBusMessage *message, void *user_data) { struct name_data *data; struct name_callback *cb; char *name, *old, *new; if (!dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 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; } data = name_data_find(connection, name); if (!data) { error("Got NameOwnerChanged signal for %s which has no listeners", name); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } data->lock = TRUE; 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 (!g_slist_find(data->callbacks, cb)) continue; data->callbacks = g_slist_remove(data->callbacks, cb); if (!cb->conn_func || !cb->disc_func) { g_free(cb); continue; } data->processed = g_slist_append(data->processed, cb); } data->callbacks = data->processed; data->processed = NULL; data->lock = FALSE; if (data->callbacks) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; name_listeners = g_slist_remove(name_listeners, data); name_data_free(data); /* Remove filter if there no listener left for the connection */ data = name_data_find(connection, NULL); if (!data) dbus_connection_remove_filter(connection, name_exit_filter, NULL); remove_match(connection, name); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } guint g_dbus_add_service_watch(DBusConnection *connection, const char *name, GDBusWatchFunction connect, GDBusWatchFunction disconnect, void *user_data, GDBusDestroyFunction destroy) { int first; if (!name_data_find(connection, NULL)) { if (!dbus_connection_add_filter(connection, name_exit_filter, NULL, NULL)) { error("dbus_connection_add_filter() failed"); return 0; } } listener_id++; first = name_data_add(connection, name, connect, disconnect, user_data, listener_id); /* The filter is already added if this is not the first callback * registration for the name */ if (!first) return listener_id; if (name) { debug("name_listener_add(%s)", name); if (!add_match(connection, name)) { name_data_remove(connection, name, listener_id); return 0; } } return listener_id; } guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name, GDBusWatchFunction func, void *user_data, GDBusDestroyFunction destroy) { return g_dbus_add_service_watch(connection, name, NULL, func, user_data, destroy); } guint g_dbus_add_signal_watch(DBusConnection *connection, const char *rule, GDBusSignalFunction function, void *user_data, GDBusDestroyFunction destroy) { return 0; } gboolean g_dbus_remove_watch(DBusConnection *connection, guint id) { struct name_data *data; struct name_callback *cb; GSList *ldata, *lcb; if (id == 0) return FALSE; for (ldata = name_listeners; ldata; ldata = ldata->next) { data = ldata->data; for (lcb = data->callbacks; lcb; lcb = lcb->next) { cb = lcb->data; if (cb->id == id) goto remove; } for (lcb = data->processed; lcb; lcb = lcb->next) { cb = lcb->data; if (cb->id == id) goto remove; } } return FALSE; remove: data->callbacks = g_slist_remove(data->callbacks, cb); data->processed = g_slist_remove(data->processed, cb); g_free(cb); /* Don't remove the filter if other callbacks exist or data is lock * processing callbacks */ if (data->callbacks || data->lock) return TRUE; if (data->name) { if (!remove_match(data->connection, data->name)) return FALSE; } name_listeners = g_slist_remove(name_listeners, data); name_data_free(data); /* Remove filter if there are no listeners left for the connection */ data = name_data_find(connection, NULL); if (!data) dbus_connection_remove_filter(connection, name_exit_filter, NULL); return TRUE; } void g_dbus_remove_all_watches(DBusConnection *connection) { struct name_data *data; while ((data = name_data_find(connection, NULL))) { name_listeners = g_slist_remove(name_listeners, data); name_data_call_and_free(data); } dbus_connection_remove_filter(connection, name_exit_filter, NULL); }