dnsproxy: Only one copy of the relevant buffers will be made to a TCP request
[framework/connectivity/connman.git] / gdbus / watch.c
index 38bf3d7..9a716b0 100644 (file)
@@ -2,7 +2,7 @@
  *
  *  D-Bus helper library
  *
- *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2011  Marcel Holtmann <marcel@holtmann.org>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
 #define error(fmt...)
 #define debug(fmt...)
 
+static DBusHandlerResult message_filter(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data);
+
 static guint listener_id = 0;
-static GSList *name_listeners = NULL;
+static GSList *listeners = NULL;
+
+struct service_data {
+       DBusConnection *conn;
+       DBusPendingCall *call;
+       char *name;
+       const char *owner;
+       guint id;
+       struct filter_callback *callback;
+};
 
-struct name_callback {
+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 name_data {
+struct filter_data {
        DBusConnection *connection;
+       DBusHandleMessageFunction handle_func;
        char *name;
+       char *owner;
+       char *path;
+       char *interface;
+       char *member;
+       char *argument;
        GSList *callbacks;
+       GSList *processed;
+       guint name_watch;
+       gboolean lock;
+       gboolean registered;
 };
 
-static struct name_data *name_data_find(DBusConnection *connection,
-                                                       const char *name)
+static struct filter_data *filter_data_find(DBusConnection *connection,
+                                                       const char *name,
+                                                       const char *owner,
+                                                       const char *path,
+                                                       const char *interface,
+                                                       const char *member,
+                                                       const char *argument)
 {
        GSList *current;
 
-       for (current = name_listeners;
+       for (current = listeners;
                        current != NULL; current = current->next) {
-               struct name_data *data = current->data;
+               struct filter_data *data = current->data;
 
-               if (name == NULL && data->name == NULL) {
-                       if (connection == data->connection)
-                               return data;
-               } else {
-                       if (strcmp(name, data->name) == 0)
-                               return data;
-               }
+               if (connection != data->connection)
+                       continue;
+
+               if (name && data->name &&
+                               g_str_equal(name, data->name) == FALSE)
+                       continue;
+
+               if (owner && data->owner &&
+                               g_str_equal(owner, data->owner) == FALSE)
+                       continue;
+
+               if (path && data->path &&
+                               g_str_equal(path, data->path) == FALSE)
+                       continue;
+
+               if (interface && data->interface &&
+                               g_str_equal(interface, data->interface) == FALSE)
+                       continue;
+
+               if (member && data->member &&
+                               g_str_equal(member, data->member) == FALSE)
+                       continue;
+
+               if (argument && data->argument &&
+                               g_str_equal(argument, data->argument) == FALSE)
+                       continue;
+
+               return data;
        }
 
        return NULL;
 }
 
-static struct name_callback *name_callback_find(GSList *callbacks, guint id)
+static void format_rule(struct filter_data *data, char *rule, size_t size)
 {
-       GSList *current;
+       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);
+}
 
-       for (current = callbacks; current != NULL; current = current->next) {
-               struct name_callback *cb = current->data;
-               if (cb->id == id)
-                       return cb;
+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;
        }
 
-       return NULL;
+       data->handle_func = filter;
+       data->registered = TRUE;
+
+       return TRUE;
 }
 
-static void name_data_call_and_free(struct name_data *data)
+static gboolean remove_match(struct filter_data *data)
+{
+       DBusError err;
+       char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH];
+
+       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("Removing owner match rule for %s failed: %s",
+                               rule, err.message);
+               dbus_error_free(&err);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static struct filter_data *filter_data_get(DBusConnection *connection,
+                                       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(connection, NULL, NULL, NULL, NULL, NULL, NULL) == NULL) {
+               if (!dbus_connection_add_filter(connection,
+                                       message_filter, NULL, NULL)) {
+                       error("dbus_connection_add_filter() failed");
+                       return NULL;
+               }
+       }
+
+       if (sender == NULL)
+               goto proceed;
+
+       if (sender[0] == ':')
+               owner = sender;
+       else
+               name = sender;
+
+proceed:
+       data = filter_data_find(connection, name, owner, path, interface,
+                                       member, argument);
+       if (data)
+               return data;
+
+       data = g_new0(struct filter_data, 1);
+
+       data->connection = dbus_connection_ref(connection);
+       data->name = name ? g_strdup(name) : NULL;
+       data->owner = owner ? g_strdup(owner) : NULL;
+       data->path = g_strdup(path);
+       data->interface = g_strdup(interface);
+       data->member = g_strdup(member);
+       data->argument = g_strdup(argument);
+
+       if (!add_match(data, filter)) {
+               g_free(data);
+               return NULL;
+       }
+
+       listeners = g_slist_append(listeners, data);
+
+       return data;
+}
+
+static struct filter_callback *filter_data_find_callback(
+                                               struct filter_data *data,
+                                               guint id)
 {
        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);
+       for (l = data->callbacks; l; l = l->next) {
+               struct filter_callback *cb = l->data;
+               if (cb->id == id)
+                       return cb;
+       }
+       for (l = data->processed; l; l = l->next) {
+               struct filter_callback *cb = l->data;
+               if (cb->id == id)
+                       return cb;
        }
 
-       g_slist_free(data->callbacks);
-       g_free(data->name);
-       g_free(data);
+       return NULL;
 }
 
-static void name_data_free(struct name_data *data)
+static void filter_data_free(struct filter_data *data)
 {
        GSList *l;
 
@@ -111,123 +274,186 @@ static void name_data_free(struct name_data *data)
                g_free(l->data);
 
        g_slist_free(data->callbacks);
+       g_dbus_remove_watch(data->connection, data->name_watch);
        g_free(data->name);
+       g_free(data->owner);
+       g_free(data->path);
+       g_free(data->interface);
+       g_free(data->member);
+       g_free(data->argument);
+       dbus_connection_unref(data->connection);
        g_free(data);
 }
 
-static int name_data_add(DBusConnection *connection, const char *name,
+static void filter_data_call_and_free(struct filter_data *data)
+{
+       GSList *l;
+
+       for (l = data->callbacks; l != NULL; l = l->next) {
+               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);
+               g_free(cb);
+       }
+
+       filter_data_free(data);
+}
+
+static struct filter_callback *filter_data_add_callback(
+                                               struct filter_data *data,
                                                GDBusWatchFunction connect,
                                                GDBusWatchFunction disconnect,
-                                               void *user_data, guint id)
+                                               GDBusSignalFunction signal,
+                                               GDBusDestroyFunction destroy,
+                                               void *user_data)
 {
-       int first = 1;
-       struct name_data *data = NULL;
-       struct name_callback *cb = NULL;
+       struct filter_callback *cb = NULL;
 
-       cb = g_new(struct name_callback, 1);
+       cb = g_new0(struct filter_callback, 1);
 
        cb->conn_func = connect;
        cb->disc_func = disconnect;
+       cb->signal_func = signal;
+       cb->destroy_func = destroy;
        cb->user_data = user_data;
-       cb->id = id;
+       cb->id = ++listener_id;
 
-       data = name_data_find(connection, name);
-       if (data) {
-               first = 0;
-               goto done;
-       }
+       if (data->lock)
+               data->processed = g_slist_append(data->processed, cb);
+       else
+               data->callbacks = g_slist_append(data->callbacks, cb);
 
-       data = g_new0(struct name_data, 1);
+       return cb;
+}
 
-       data->connection = connection;
-       data->name = g_strdup(name);
+static void service_data_free(struct service_data *data)
+{
+       struct filter_callback *callback = data->callback;
 
-       name_listeners = g_slist_append(name_listeners, data);
+       dbus_connection_unref(data->conn);
 
-done:
-       data->callbacks = g_slist_append(data->callbacks, cb);
-       return first;
+       if (data->call)
+               dbus_pending_call_unref(data->call);
+
+       if (data->id)
+               g_source_remove(data->id);
+
+       g_free(data->name);
+       g_free(data);
+
+       callback->data = NULL;
 }
 
-static void name_data_remove(DBusConnection *connection,
-                                       const char *name, guint id)
+static gboolean filter_data_remove_callback(struct filter_data *data,
+                                               struct filter_callback *cb)
 {
-       struct name_data *data;
-       struct name_callback *cb = NULL;
+       DBusConnection *connection;
 
-       data = name_data_find(connection, name);
-       if (!data)
-               return;
+       data->callbacks = g_slist_remove(data->callbacks, cb);
+       data->processed = g_slist_remove(data->processed, cb);
 
-       cb = name_callback_find(data->callbacks, id);
-       if (cb) {
-               data->callbacks = g_slist_remove(data->callbacks, cb);
-               g_free(cb);
+       /* Cancel pending operations */
+       if (cb->data) {
+               if (cb->data->call)
+                       dbus_pending_call_cancel(cb->data->call);
+               service_data_free(cb->data);
        }
 
-       if (!data->callbacks) {
-               name_listeners = g_slist_remove(name_listeners, data);
-               name_data_free(data);
-       }
+       if (cb->destroy_func)
+               cb->destroy_func(cb->user_data);
+
+       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->registered && !remove_match(data))
+               return FALSE;
+
+       connection = dbus_connection_ref(data->connection);
+       listeners = g_slist_remove(listeners, data);
+       filter_data_free(data);
+
+       /* Remove filter if there are no listeners left for the connection */
+       data = filter_data_find(connection, NULL, NULL, NULL, NULL, NULL,
+                                       NULL);
+       if (data == NULL)
+               dbus_connection_remove_filter(connection, message_filter,
+                                               NULL);
+
+       dbus_connection_unref(connection);
+
+       return TRUE;
 }
 
-static gboolean add_match(DBusConnection *connection, const char *name)
+static DBusHandlerResult signal_filter(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data)
 {
-       DBusError err;
-       char match_string[128];
+       struct filter_data *data = user_data;
+       struct filter_callback *cb;
 
-       snprintf(match_string, sizeof(match_string),
-                       "interface=%s,member=NameOwnerChanged,arg0=%s",
-                       DBUS_INTERFACE_DBUS, name);
+       while (data->callbacks) {
+               cb = data->callbacks->data;
 
-       dbus_error_init(&err);
+               if (cb->signal_func && !cb->signal_func(connection, message,
+                                                       cb->user_data)) {
+                       filter_data_remove_callback(data, cb);
+                       continue;
+               }
 
-       dbus_bus_add_match(connection, match_string, &err);
+               /* Check if the watch was removed/freed by the callback
+                * function */
+               if (!g_slist_find(data->callbacks, cb))
+                       continue;
 
-       if (dbus_error_is_set(&err)) {
-               error("Adding match rule \"%s\" failed: %s", match_string,
-                               err.message);
-               dbus_error_free(&err);
-               return FALSE;
+               data->callbacks = g_slist_remove(data->callbacks, cb);
+               data->processed = g_slist_append(data->processed, cb);
        }
 
-       return TRUE;
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
-static gboolean remove_match(DBusConnection *connection, const char *name)
+static void update_name_cache(const char *name, const char *owner)
 {
-       DBusError err;
-       char match_string[128];
+       GSList *l;
 
-       snprintf(match_string, sizeof(match_string),
-                       "interface=%s,member=NameOwnerChanged,arg0=%s",
-                       DBUS_INTERFACE_DBUS, name);
+       for (l = listeners; l != NULL; l = l->next) {
+               struct filter_data *data = l->data;
 
-       dbus_error_init(&err);
+               if (g_strcmp0(data->name, name) != 0)
+                       continue;
 
-       dbus_bus_remove_match(connection, match_string, &err);
+               g_free(data->owner);
+               data->owner = g_strdup(owner);
+       }
+}
 
-       if (dbus_error_is_set(&err)) {
-               error("Removing owner match rule for %s failed: %s",
-                               name, err.message);
-               dbus_error_free(&err);
-               return FALSE;
+static const char *check_name_cache(const char *name)
+{
+       GSList *l;
+
+       for (l = listeners; l != NULL; l = l->next) {
+               struct filter_data *data = l->data;
+
+               if (g_strcmp0(data->name, name) != 0)
+                       continue;
+
+               return data->owner;
        }
 
-       return TRUE;
+       return NULL;
 }
 
-static DBusHandlerResult name_exit_filter(DBusConnection *connection,
+static DBusHandlerResult service_filter(DBusConnection *connection,
                                        DBusMessage *message, void *user_data)
 {
-       GSList *l;
-       struct name_data *data;
+       struct filter_data *data = user_data;
+       struct filter_callback *cb;
        char *name, *old, *new;
-       int keep = 0;
-
-       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,
@@ -238,14 +464,11 @@ static DBusHandlerResult name_exit_filter(DBusConnection *connection,
                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;
-       }
+       update_name_cache(name, new);
+
+       while (data->callbacks) {
+               cb = data->callbacks->data;
 
-       for (l = data->callbacks; l != NULL; l = l->next) {
-               struct name_callback *cb = l->data;
                if (*new == '\0') {
                        if (cb->disc_func)
                                cb->disc_func(connection, cb->user_data);
@@ -253,54 +476,205 @@ static DBusHandlerResult name_exit_filter(DBusConnection *connection,
                        if (cb->conn_func)
                                cb->conn_func(connection, cb->user_data);
                }
-               if (cb->conn_func && cb->disc_func)
-                       keep = 1;
+
+               /* Check if the watch was removed/freed by the callback
+                * function */
+               if (!g_slist_find(data->callbacks, cb))
+                       continue;
+
+               /* Only auto remove if it is a bus name watch */
+               if (data->argument[0] == ':' &&
+                               (cb->conn_func == NULL || cb->disc_func == NULL)) {
+                       filter_data_remove_callback(data, cb);
+                       continue;
+               }
+
+               data->callbacks = g_slist_remove(data->callbacks, cb);
+               data->processed = g_slist_append(data->processed, cb);
        }
 
-       if (keep)
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+
+static DBusHandlerResult message_filter(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data)
+{
+       struct filter_data *data;
+       const char *sender, *path, *iface, *member, *arg = NULL;
+
+       /* Only filter signals */
+       if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 
-       name_listeners = g_slist_remove(name_listeners, data);
-       name_data_free(data);
+       sender = dbus_message_get_sender(message);
+       path = dbus_message_get_path(message);
+       iface = dbus_message_get_interface(message);
+       member = dbus_message_get_member(message);
+       dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID);
+
+       /* Sender is always bus name */
+       data = filter_data_find(connection, NULL, sender, path, iface, member,
+                                       arg);
+       if (data == NULL) {
+               error("Got %s.%s signal which has no listeners", iface, member);
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       if (data->handle_func) {
+               data->lock = TRUE;
 
-       remove_match(connection, name);
+               data->handle_func(connection, message, data);
+
+               data->callbacks = data->processed;
+               data->processed = NULL;
+               data->lock = FALSE;
+       }
+
+       if (data->callbacks)
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       remove_match(data);
+
+       listeners = g_slist_remove(listeners, data);
+       filter_data_free(data);
+
+       /* Remove filter if there no listener left for the connection */
+       data = filter_data_find(connection, NULL, NULL, NULL, NULL, NULL,
+                                       NULL);
+       if (data == NULL)
+               dbus_connection_remove_filter(connection, message_filter,
+                                               NULL);
 
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
+static gboolean update_service(void *user_data)
+{
+       struct service_data *data = user_data;
+       struct filter_callback *cb = data->callback;
+
+       update_name_cache(data->name, data->owner);
+       if (cb->conn_func)
+               cb->conn_func(data->conn, cb->user_data);
+
+       service_data_free(data);
+
+       return FALSE;
+}
+
+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);
+
+       goto done;
+
+fail:
+       error("%s", err.message);
+       dbus_error_free(&err);
+       service_data_free(data);
+done:
+       dbus_message_unref(reply);
+}
+
+static void check_service(DBusConnection *connection,
+                                       const char *name,
+                                       struct filter_callback *callback)
+{
+       DBusMessage *message;
+       struct service_data *data;
+
+       data = g_try_malloc0(sizeof(*data));
+       if (data == NULL) {
+               error("Can't allocate data structure");
+               return;
+       }
+
+       data->conn = dbus_connection_ref(connection);
+       data->name = g_strdup(name);
+       data->callback = callback;
+       callback->data = data;
+
+       data->owner = check_name_cache(name);
+       if (data->owner != NULL) {
+               data->id = g_idle_add(update_service, data);
+               return;
+       }
+
+       message = dbus_message_new_method_call(DBUS_SERVICE_DBUS,
+                       DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner");
+       if (message == NULL) {
+               error("Can't allocate new message");
+               g_free(data);
+               return;
+       }
+
+       dbus_message_append_args(message, DBUS_TYPE_STRING, &name,
+                                                       DBUS_TYPE_INVALID);
+
+       if (dbus_connection_send_with_reply(connection, message,
+                                                       &data->call, -1) == FALSE) {
+               error("Failed to execute method call");
+               g_free(data);
+               goto done;
+       }
+
+       if (data->call == NULL) {
+               error("D-Bus connection not available");
+               g_free(data);
+               goto done;
+       }
+
+       dbus_pending_call_set_notify(data->call, service_reply, data, NULL);
+
+done:
+       dbus_message_unref(message);
+}
+
 guint g_dbus_add_service_watch(DBusConnection *connection, const char *name,
                                GDBusWatchFunction connect,
                                GDBusWatchFunction disconnect,
                                void *user_data, GDBusDestroyFunction destroy)
 {
-       int first;
+       struct filter_data *data;
+       struct filter_callback *cb;
 
-       if (!listener_id) {
-               if (!dbus_connection_add_filter(connection,
-                                       name_exit_filter, NULL, NULL)) {
-                       error("dbus_connection_add_filter() failed");
-                       return 0;
-               }
-       }
+       if (name == NULL)
+               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;
+       data = filter_data_get(connection, service_filter, NULL, NULL,
+                               DBUS_INTERFACE_DBUS, "NameOwnerChanged",
+                               name);
+       if (data == NULL)
+               return 0;
 
-       if (name) {
-               debug("name_listener_add(%s)", name);
+       cb = filter_data_add_callback(data, connect, disconnect, NULL, destroy,
+                                       user_data);
+       if (cb == NULL)
+               return 0;
 
-               if (!add_match(connection, name)) {
-                       name_data_remove(connection, name, listener_id);
-                       return 0;
-               }
-       }
+       if (connect)
+               check_service(connection, name, cb);
 
-       return listener_id;
+       return cb->id;
 }
 
 guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
@@ -312,62 +686,63 @@ guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
 }
 
 guint g_dbus_add_signal_watch(DBusConnection *connection,
-                               const char *rule, GDBusSignalFunction function,
-                               void *user_data, GDBusDestroyFunction destroy)
+                               const char *sender, const char *path,
+                               const char *interface, const char *member,
+                               GDBusSignalFunction function, void *user_data,
+                               GDBusDestroyFunction destroy)
 {
-       return 0;
+       struct filter_data *data;
+       struct filter_callback *cb;
+
+       data = filter_data_get(connection, signal_filter, sender, path,
+                               interface, member, NULL);
+       if (data == NULL)
+               return 0;
+
+       cb = filter_data_add_callback(data, NULL, NULL, function, destroy,
+                                       user_data);
+       if (cb == NULL)
+               return 0;
+
+       if (data->name != NULL && data->name_watch == 0)
+               data->name_watch = g_dbus_add_service_watch(connection,
+                                                       data->name, NULL,
+                                                       NULL, NULL, NULL);
+
+       return cb->id;
 }
 
 gboolean g_dbus_remove_watch(DBusConnection *connection, guint id)
 {
-       struct name_data *data;
-       struct name_callback *cb;
-       GSList *ldata, *lcb;
+       struct filter_data *data;
+       struct filter_callback *cb;
+       GSList *ldata;
 
        if (id == 0)
                return FALSE;
 
-       for (ldata = name_listeners; ldata; ldata = ldata->next) {
+       for (ldata = 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;
+
+               cb = filter_data_find_callback(data, id);
+               if (cb) {
+                       filter_data_remove_callback(data, cb);
+                       return TRUE;
                }
        }
 
        return FALSE;
-
-remove:
-       data->callbacks = g_slist_remove(data->callbacks, cb);
-       g_free(cb);
-
-       /* Don't remove the filter if other callbacks exist */
-       if (data->callbacks)
-               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);
-
-       return TRUE;
 }
 
 void g_dbus_remove_all_watches(DBusConnection *connection)
 {
-       struct name_data *data;
+       struct filter_data *data;
 
-       data = name_data_find(connection, NULL);
-       if (!data) {
-               error("name_listener_indicate_disconnect: no listener found");
-               return;
+       while ((data = filter_data_find(connection, NULL, NULL, NULL, NULL,
+                                       NULL, NULL))) {
+               listeners = g_slist_remove(listeners, data);
+               filter_data_call_and_free(data);
        }
 
-       debug("name_listener_indicate_disconnect");
-
-       name_data_call_and_free(data);
+       dbus_connection_remove_filter(connection, message_filter, NULL);
 }