63e3a7c3a24e59fed5231363788fad75d5d242b6
[framework/connectivity/connman.git] / gdbus / watch.c
1 /*
2  *
3  *  D-Bus helper library
4  *
5  *  Copyright (C) 2004-2008  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 guint listener_id = 0;
41 static GSList *name_listeners = NULL;
42
43 struct name_callback {
44         GDBusWatchFunction func;
45         void *user_data;
46         guint id;
47 };
48
49 struct name_data {
50         DBusConnection *connection;
51         char *name;
52         GSList *callbacks;
53 };
54
55 static struct name_data *name_data_find(DBusConnection *connection,
56                                                         const char *name)
57 {
58         GSList *current;
59
60         for (current = name_listeners;
61                         current != NULL; current = current->next) {
62                 struct name_data *data = current->data;
63
64                 if (name == NULL && data->name == NULL) {
65                         if (connection == data->connection)
66                                 return data;
67                 } else {
68                         if (strcmp(name, data->name) == 0)
69                                 return data;
70                 }
71         }
72
73         return NULL;
74 }
75
76 static struct name_callback *name_callback_find(GSList *callbacks,
77                                         GDBusWatchFunction func, void *user_data)
78 {
79         GSList *current;
80
81         for (current = callbacks; current != NULL; current = current->next) {
82                 struct name_callback *cb = current->data;
83                 if (cb->func == func && cb->user_data == user_data)
84                         return cb;
85         }
86
87         return NULL;
88 }
89
90 static void name_data_call_and_free(struct name_data *data)
91 {
92         GSList *l;
93
94         for (l = data->callbacks; l != NULL; l = l->next) {
95                 struct name_callback *cb = l->data;
96                 if (cb->func)
97                         cb->func(data->connection, cb->user_data);
98                 g_free(cb);
99         }
100
101         g_slist_free(data->callbacks);
102         g_free(data->name);
103         g_free(data);
104 }
105
106 static void name_data_free(struct name_data *data)
107 {
108         GSList *l;
109
110         for (l = data->callbacks; l != NULL; l = l->next)
111                 g_free(l->data);
112
113         g_slist_free(data->callbacks);
114         g_free(data->name);
115         g_free(data);
116 }
117
118 static int name_data_add(DBusConnection *connection, const char *name,
119                                 GDBusWatchFunction func, void *user_data, guint id)
120 {
121         int first = 1;
122         struct name_data *data = NULL;
123         struct name_callback *cb = NULL;
124
125         cb = g_new(struct name_callback, 1);
126
127         cb->func = func;
128         cb->user_data = user_data;
129         cb->id = id;
130
131         data = name_data_find(connection, name);
132         if (data) {
133                 first = 0;
134                 goto done;
135         }
136
137         data = g_new0(struct name_data, 1);
138
139         data->connection = connection;
140         data->name = g_strdup(name);
141
142         name_listeners = g_slist_append(name_listeners, data);
143
144 done:
145         data->callbacks = g_slist_append(data->callbacks, cb);
146         return first;
147 }
148
149 static void name_data_remove(DBusConnection *connection,
150                         const char *name, GDBusWatchFunction func, void *user_data)
151 {
152         struct name_data *data;
153         struct name_callback *cb = NULL;
154
155         data = name_data_find(connection, name);
156         if (!data)
157                 return;
158
159         cb = name_callback_find(data->callbacks, func, user_data);
160         if (cb) {
161                 data->callbacks = g_slist_remove(data->callbacks, cb);
162                 g_free(cb);
163         }
164
165         if (!data->callbacks) {
166                 name_listeners = g_slist_remove(name_listeners, data);
167                 name_data_free(data);
168         }
169 }
170
171 static gboolean add_match(DBusConnection *connection, const char *name)
172 {
173         DBusError err;
174         char match_string[128];
175
176         snprintf(match_string, sizeof(match_string),
177                         "interface=%s,member=NameOwnerChanged,arg0=%s",
178                         DBUS_INTERFACE_DBUS, name);
179
180         dbus_error_init(&err);
181
182         dbus_bus_add_match(connection, match_string, &err);
183
184         if (dbus_error_is_set(&err)) {
185                 error("Adding match rule \"%s\" failed: %s", match_string,
186                                 err.message);
187                 dbus_error_free(&err);
188                 return FALSE;
189         }
190
191         return TRUE;
192 }
193
194 static gboolean remove_match(DBusConnection *connection, const char *name)
195 {
196         DBusError err;
197         char match_string[128];
198
199         snprintf(match_string, sizeof(match_string),
200                         "interface=%s,member=NameOwnerChanged,arg0=%s",
201                         DBUS_INTERFACE_DBUS, name);
202
203         dbus_error_init(&err);
204
205         dbus_bus_remove_match(connection, match_string, &err);
206
207         if (dbus_error_is_set(&err)) {
208                 error("Removing owner match rule for %s failed: %s",
209                                 name, err.message);
210                 dbus_error_free(&err);
211                 return FALSE;
212         }
213
214         return TRUE;
215 }
216
217 static DBusHandlerResult name_exit_filter(DBusConnection *connection,
218                                         DBusMessage *message, void *user_data)
219 {
220         GSList *l;
221         struct name_data *data;
222         char *name, *old, *new;
223
224         if (!dbus_message_is_signal(message, DBUS_INTERFACE_DBUS,
225                                                         "NameOwnerChanged"))
226                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
227
228         if (!dbus_message_get_args(message, NULL,
229                                 DBUS_TYPE_STRING, &name,
230                                 DBUS_TYPE_STRING, &old,
231                                 DBUS_TYPE_STRING, &new,
232                                 DBUS_TYPE_INVALID)) {
233                 error("Invalid arguments for NameOwnerChanged signal");
234                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
235         }
236
237         /* We are not interested of service creations */
238         if (*new != '\0')
239                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
240
241         data = name_data_find(connection, name);
242         if (!data) {
243                 error("Got NameOwnerChanged signal for %s which has no listeners", name);
244                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
245         }
246
247         for (l = data->callbacks; l != NULL; l = l->next) {
248                 struct name_callback *cb = l->data;
249                 cb->func(connection, cb->user_data);
250         }
251
252         name_listeners = g_slist_remove(name_listeners, data);
253         name_data_free(data);
254
255         remove_match(connection, name);
256
257         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
258 }
259
260 guint g_dbus_add_disconnect_watch(DBusConnection *connection,
261                                 const char *name,
262                                 GDBusWatchFunction func,
263                                 void *user_data, GDBusDestroyFunction destroy)
264 {
265         int first;
266
267         if (!listener_id) {
268                 if (!dbus_connection_add_filter(connection,
269                                         name_exit_filter, NULL, NULL)) {
270                         error("dbus_connection_add_filter() failed");
271                         return 0;
272                 }
273         }
274
275         listener_id++;
276         first = name_data_add(connection, name, func, user_data, listener_id);
277         /* The filter is already added if this is not the first callback
278          * registration for the name */
279         if (!first)
280                 return listener_id;
281
282         if (name) {
283                 debug("name_listener_add(%s)", name);
284
285                 if (!add_match(connection, name)) {
286                         name_data_remove(connection, name, func, user_data);
287                         return 0;
288                 }
289         }
290
291         return listener_id;
292 }
293
294 guint g_dbus_add_signal_watch(DBusConnection *connection,
295                                 const char *rule, GDBusSignalFunction function,
296                                 void *user_data, GDBusDestroyFunction destroy)
297 {
298         return 0;
299 }
300
301 gboolean g_dbus_remove_watch(DBusConnection *connection, guint id)
302 {
303         struct name_data *data;
304         struct name_callback *cb;
305         GSList *ldata, *lcb;
306
307         if (id == 0)
308                 return FALSE;
309
310         for (ldata = name_listeners; ldata; ldata = ldata->next) {
311                 data = ldata->data;
312                 for (lcb = data->callbacks; lcb; lcb = lcb->next) {
313                         cb = lcb->data;
314                         if (cb->id == id)
315                                 goto remove;
316                 }
317         }
318
319         return FALSE;
320
321 remove:
322         data->callbacks = g_slist_remove(data->callbacks, cb);
323         g_free(cb);
324
325         /* Don't remove the filter if other callbacks exist */
326         if (data->callbacks)
327                 return TRUE;
328
329         if (data->name) {
330                 if (!remove_match(data->connection, data->name))
331                         return FALSE;
332         }
333
334         name_listeners = g_slist_remove(name_listeners, data);
335         name_data_free(data);
336
337         return TRUE;
338 }
339
340 void g_dbus_remove_all_watches(DBusConnection *connection)
341 {
342         struct name_data *data;
343
344         data = name_data_find(connection, NULL);
345         if (!data) {
346                 error("name_listener_indicate_disconnect: no listener found");
347                 return;
348         }
349
350         debug("name_listener_indicate_disconnect");
351
352         name_data_call_and_free(data);
353 }