Fix handling of watch functions
[connman] / gdbus / watch.c
1 /*
2  *
3  *  D-Bus helper library
4  *
5  *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
6  *
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  *
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdio.h>
29 #include <string.h>
30
31 #include <glib.h>
32 #include <dbus/dbus.h>
33
34 #include "gdbus.h"
35
36 #define info(fmt...)
37 #define error(fmt...)
38 #define debug(fmt...)
39
40 static DBusHandlerResult name_exit_filter(DBusConnection *connection,
41                                         DBusMessage *message, void *user_data);
42
43 static guint listener_id = 0;
44 static GSList *name_listeners = NULL;
45
46 struct name_callback {
47         GDBusWatchFunction conn_func;
48         GDBusWatchFunction disc_func;
49         void *user_data;
50         guint id;
51 };
52
53 struct name_data {
54         DBusConnection *connection;
55         char *name;
56         GSList *callbacks;
57 };
58
59 static struct name_data *name_data_find(DBusConnection *connection,
60                                                         const char *name)
61 {
62         GSList *current;
63
64         for (current = name_listeners;
65                         current != NULL; current = current->next) {
66                 struct name_data *data = current->data;
67
68                 if (connection != data->connection)
69                         continue;
70
71                 if (name == NULL || g_str_equal(name, data->name))
72                         return data;
73         }
74
75         return NULL;
76 }
77
78 static struct name_callback *name_callback_find(GSList *callbacks, guint id)
79 {
80         GSList *current;
81
82         for (current = callbacks; current != NULL; current = current->next) {
83                 struct name_callback *cb = current->data;
84                 if (cb->id == id)
85                         return cb;
86         }
87
88         return NULL;
89 }
90
91 static void name_data_call_and_free(struct name_data *data)
92 {
93         GSList *l;
94
95         for (l = data->callbacks; l != NULL; l = l->next) {
96                 struct name_callback *cb = l->data;
97                 if (cb->disc_func)
98                         cb->disc_func(data->connection, cb->user_data);
99                 g_free(cb);
100         }
101
102         g_slist_free(data->callbacks);
103         g_free(data->name);
104         g_free(data);
105 }
106
107 static void name_data_free(struct name_data *data)
108 {
109         GSList *l;
110
111         for (l = data->callbacks; l != NULL; l = l->next)
112                 g_free(l->data);
113
114         g_slist_free(data->callbacks);
115         g_free(data->name);
116         g_free(data);
117 }
118
119 static int name_data_add(DBusConnection *connection, const char *name,
120                                                 GDBusWatchFunction connect,
121                                                 GDBusWatchFunction disconnect,
122                                                 void *user_data, guint id)
123 {
124         int first = 1;
125         struct name_data *data = NULL;
126         struct name_callback *cb = NULL;
127
128         cb = g_new(struct name_callback, 1);
129
130         cb->conn_func = connect;
131         cb->disc_func = disconnect;
132         cb->user_data = user_data;
133         cb->id = id;
134
135         data = name_data_find(connection, name);
136         if (data) {
137                 first = 0;
138                 goto done;
139         }
140
141         data = g_new0(struct name_data, 1);
142
143         data->connection = connection;
144         data->name = g_strdup(name);
145
146         name_listeners = g_slist_append(name_listeners, data);
147
148 done:
149         data->callbacks = g_slist_append(data->callbacks, cb);
150         return first;
151 }
152
153 static void name_data_remove(DBusConnection *connection,
154                                         const char *name, guint id)
155 {
156         struct name_data *data;
157         struct name_callback *cb = NULL;
158
159         data = name_data_find(connection, name);
160         if (!data)
161                 return;
162
163         cb = name_callback_find(data->callbacks, id);
164         if (cb) {
165                 data->callbacks = g_slist_remove(data->callbacks, cb);
166                 g_free(cb);
167         }
168
169         if (data->callbacks)
170                 return;
171
172         name_listeners = g_slist_remove(name_listeners, data);
173         name_data_free(data);
174
175         /* Remove filter if there are no listeners left for the connection */
176         data = name_data_find(connection, NULL);
177         if (!data)
178                 dbus_connection_remove_filter(connection,
179                                                 name_exit_filter,
180                                                 NULL);
181 }
182
183 static gboolean add_match(DBusConnection *connection, const char *name)
184 {
185         DBusError err;
186         char match_string[128];
187
188         snprintf(match_string, sizeof(match_string),
189                         "interface=%s,member=NameOwnerChanged,arg0=%s",
190                         DBUS_INTERFACE_DBUS, name);
191
192         dbus_error_init(&err);
193
194         dbus_bus_add_match(connection, match_string, &err);
195
196         if (dbus_error_is_set(&err)) {
197                 error("Adding match rule \"%s\" failed: %s", match_string,
198                                 err.message);
199                 dbus_error_free(&err);
200                 return FALSE;
201         }
202
203         return TRUE;
204 }
205
206 static gboolean remove_match(DBusConnection *connection, const char *name)
207 {
208         DBusError err;
209         char match_string[128];
210
211         snprintf(match_string, sizeof(match_string),
212                         "interface=%s,member=NameOwnerChanged,arg0=%s",
213                         DBUS_INTERFACE_DBUS, name);
214
215         dbus_error_init(&err);
216
217         dbus_bus_remove_match(connection, match_string, &err);
218
219         if (dbus_error_is_set(&err)) {
220                 error("Removing owner match rule for %s failed: %s",
221                                 name, err.message);
222                 dbus_error_free(&err);
223                 return FALSE;
224         }
225
226         return TRUE;
227 }
228
229 static DBusHandlerResult name_exit_filter(DBusConnection *connection,
230                                         DBusMessage *message, void *user_data)
231 {
232         GSList *l;
233         struct name_data *data;
234         char *name, *old, *new;
235         int keep = 0;
236
237         if (!dbus_message_is_signal(message, DBUS_INTERFACE_DBUS,
238                                                         "NameOwnerChanged"))
239                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
240
241         if (!dbus_message_get_args(message, NULL,
242                                 DBUS_TYPE_STRING, &name,
243                                 DBUS_TYPE_STRING, &old,
244                                 DBUS_TYPE_STRING, &new,
245                                 DBUS_TYPE_INVALID)) {
246                 error("Invalid arguments for NameOwnerChanged signal");
247                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
248         }
249
250         data = name_data_find(connection, name);
251         if (!data) {
252                 error("Got NameOwnerChanged signal for %s which has no listeners", name);
253                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
254         }
255
256         for (l = data->callbacks; l != NULL; l = l->next) {
257                 struct name_callback *cb = l->data;
258                 if (*new == '\0') {
259                         if (cb->disc_func)
260                                 cb->disc_func(connection, cb->user_data);
261                 } else {
262                         if (cb->conn_func)
263                                 cb->conn_func(connection, cb->user_data);
264                 }
265                 if (cb->conn_func && cb->disc_func)
266                         keep = 1;
267         }
268
269         if (keep)
270                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
271
272         name_listeners = g_slist_remove(name_listeners, data);
273         name_data_free(data);
274
275         /* Remove filter if there no listener left for the connection */
276         data = name_data_find(connection, NULL);
277         if (!data)
278                 dbus_connection_remove_filter(connection, name_exit_filter,
279                                                 NULL);
280
281         remove_match(connection, name);
282
283         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
284 }
285
286 guint g_dbus_add_service_watch(DBusConnection *connection, const char *name,
287                                 GDBusWatchFunction connect,
288                                 GDBusWatchFunction disconnect,
289                                 void *user_data, GDBusDestroyFunction destroy)
290 {
291         int first;
292
293         if (!name_data_find(connection, NULL)) {
294                 if (!dbus_connection_add_filter(connection,
295                                         name_exit_filter, NULL, NULL)) {
296                         error("dbus_connection_add_filter() failed");
297                         return 0;
298                 }
299         }
300
301         listener_id++;
302         first = name_data_add(connection, name, connect, disconnect,
303                                                 user_data, listener_id);
304         /* The filter is already added if this is not the first callback
305          * registration for the name */
306         if (!first)
307                 return listener_id;
308
309         if (name) {
310                 debug("name_listener_add(%s)", name);
311
312                 if (!add_match(connection, name)) {
313                         name_data_remove(connection, name, listener_id);
314                         return 0;
315                 }
316         }
317
318         return listener_id;
319 }
320
321 guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
322                                 GDBusWatchFunction func,
323                                 void *user_data, GDBusDestroyFunction destroy)
324 {
325         return g_dbus_add_service_watch(connection, name, NULL, func,
326                                                         user_data, destroy);
327 }
328
329 guint g_dbus_add_signal_watch(DBusConnection *connection,
330                                 const char *rule, GDBusSignalFunction function,
331                                 void *user_data, GDBusDestroyFunction destroy)
332 {
333         return 0;
334 }
335
336 gboolean g_dbus_remove_watch(DBusConnection *connection, guint id)
337 {
338         struct name_data *data;
339         struct name_callback *cb;
340         GSList *ldata, *lcb;
341
342         if (id == 0)
343                 return FALSE;
344
345         for (ldata = name_listeners; ldata; ldata = ldata->next) {
346                 data = ldata->data;
347                 for (lcb = data->callbacks; lcb; lcb = lcb->next) {
348                         cb = lcb->data;
349                         if (cb->id == id)
350                                 goto remove;
351                 }
352         }
353
354         return FALSE;
355
356 remove:
357         data->callbacks = g_slist_remove(data->callbacks, cb);
358         g_free(cb);
359
360         /* Don't remove the filter if other callbacks exist */
361         if (data->callbacks)
362                 return TRUE;
363
364         if (data->name) {
365                 if (!remove_match(data->connection, data->name))
366                         return FALSE;
367         }
368
369         name_listeners = g_slist_remove(name_listeners, data);
370         name_data_free(data);
371
372         /* Remove filter if there are no listeners left for the connection */
373         data = name_data_find(connection, NULL);
374         if (!data)
375                 dbus_connection_remove_filter(connection, name_exit_filter,
376                                                 NULL);
377
378         return TRUE;
379 }
380
381 void g_dbus_remove_all_watches(DBusConnection *connection)
382 {
383         struct name_data *data;
384
385         while ((data = name_data_find(connection, NULL))) {
386                 name_listeners = g_slist_remove(name_listeners, data);
387                 name_data_call_and_free(data);
388         }
389
390         dbus_connection_remove_filter(connection, name_exit_filter, NULL);
391 }