Add support for sending flight mode indication
[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 guint listener_id = 0;
41 static GSList *name_listeners = NULL;
42
43 struct name_callback {
44         GDBusWatchFunction conn_func;
45         GDBusWatchFunction disc_func;
46         void *user_data;
47         guint id;
48 };
49
50 struct name_data {
51         DBusConnection *connection;
52         char *name;
53         GSList *callbacks;
54 };
55
56 static struct name_data *name_data_find(DBusConnection *connection,
57                                                         const char *name)
58 {
59         GSList *current;
60
61         for (current = name_listeners;
62                         current != NULL; current = current->next) {
63                 struct name_data *data = current->data;
64
65                 if (name == NULL && data->name == NULL) {
66                         if (connection == data->connection)
67                                 return data;
68                 } else {
69                         if (strcmp(name, data->name) == 0)
70                                 return data;
71                 }
72         }
73
74         return NULL;
75 }
76
77 static struct name_callback *name_callback_find(GSList *callbacks, guint id)
78 {
79         GSList *current;
80
81         for (current = callbacks; current != NULL; current = current->next) {
82                 struct name_callback *cb = current->data;
83                 if (cb->id == id)
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->disc_func)
97                         cb->disc_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 connect,
120                                                 GDBusWatchFunction disconnect,
121                                                 void *user_data, guint id)
122 {
123         int first = 1;
124         struct name_data *data = NULL;
125         struct name_callback *cb = NULL;
126
127         cb = g_new(struct name_callback, 1);
128
129         cb->conn_func = connect;
130         cb->disc_func = disconnect;
131         cb->user_data = user_data;
132         cb->id = id;
133
134         data = name_data_find(connection, name);
135         if (data) {
136                 first = 0;
137                 goto done;
138         }
139
140         data = g_new0(struct name_data, 1);
141
142         data->connection = connection;
143         data->name = g_strdup(name);
144
145         name_listeners = g_slist_append(name_listeners, data);
146
147 done:
148         data->callbacks = g_slist_append(data->callbacks, cb);
149         return first;
150 }
151
152 static void name_data_remove(DBusConnection *connection,
153                                         const char *name, guint id)
154 {
155         struct name_data *data;
156         struct name_callback *cb = NULL;
157
158         data = name_data_find(connection, name);
159         if (!data)
160                 return;
161
162         cb = name_callback_find(data->callbacks, id);
163         if (cb) {
164                 data->callbacks = g_slist_remove(data->callbacks, cb);
165                 g_free(cb);
166         }
167
168         if (!data->callbacks) {
169                 name_listeners = g_slist_remove(name_listeners, data);
170                 name_data_free(data);
171         }
172 }
173
174 static gboolean add_match(DBusConnection *connection, const char *name)
175 {
176         DBusError err;
177         char match_string[128];
178
179         snprintf(match_string, sizeof(match_string),
180                         "interface=%s,member=NameOwnerChanged,arg0=%s",
181                         DBUS_INTERFACE_DBUS, name);
182
183         dbus_error_init(&err);
184
185         dbus_bus_add_match(connection, match_string, &err);
186
187         if (dbus_error_is_set(&err)) {
188                 error("Adding match rule \"%s\" failed: %s", match_string,
189                                 err.message);
190                 dbus_error_free(&err);
191                 return FALSE;
192         }
193
194         return TRUE;
195 }
196
197 static gboolean remove_match(DBusConnection *connection, const char *name)
198 {
199         DBusError err;
200         char match_string[128];
201
202         snprintf(match_string, sizeof(match_string),
203                         "interface=%s,member=NameOwnerChanged,arg0=%s",
204                         DBUS_INTERFACE_DBUS, name);
205
206         dbus_error_init(&err);
207
208         dbus_bus_remove_match(connection, match_string, &err);
209
210         if (dbus_error_is_set(&err)) {
211                 error("Removing owner match rule for %s failed: %s",
212                                 name, err.message);
213                 dbus_error_free(&err);
214                 return FALSE;
215         }
216
217         return TRUE;
218 }
219
220 static DBusHandlerResult name_exit_filter(DBusConnection *connection,
221                                         DBusMessage *message, void *user_data)
222 {
223         GSList *l;
224         struct name_data *data;
225         char *name, *old, *new;
226         int keep = 0;
227
228         if (!dbus_message_is_signal(message, DBUS_INTERFACE_DBUS,
229                                                         "NameOwnerChanged"))
230                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
231
232         if (!dbus_message_get_args(message, NULL,
233                                 DBUS_TYPE_STRING, &name,
234                                 DBUS_TYPE_STRING, &old,
235                                 DBUS_TYPE_STRING, &new,
236                                 DBUS_TYPE_INVALID)) {
237                 error("Invalid arguments for NameOwnerChanged signal");
238                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
239         }
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                 if (*new == '\0') {
250                         if (cb->disc_func)
251                                 cb->disc_func(connection, cb->user_data);
252                 } else {
253                         if (cb->conn_func)
254                                 cb->conn_func(connection, cb->user_data);
255                 }
256                 if (cb->conn_func && cb->disc_func)
257                         keep = 1;
258         }
259
260         if (keep)
261                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
262
263         name_listeners = g_slist_remove(name_listeners, data);
264         name_data_free(data);
265
266         remove_match(connection, name);
267
268         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
269 }
270
271 guint g_dbus_add_service_watch(DBusConnection *connection, const char *name,
272                                 GDBusWatchFunction connect,
273                                 GDBusWatchFunction disconnect,
274                                 void *user_data, GDBusDestroyFunction destroy)
275 {
276         int first;
277
278         if (!listener_id) {
279                 if (!dbus_connection_add_filter(connection,
280                                         name_exit_filter, NULL, NULL)) {
281                         error("dbus_connection_add_filter() failed");
282                         return 0;
283                 }
284         }
285
286         listener_id++;
287         first = name_data_add(connection, name, connect, disconnect,
288                                                 user_data, listener_id);
289         /* The filter is already added if this is not the first callback
290          * registration for the name */
291         if (!first)
292                 return listener_id;
293
294         if (name) {
295                 debug("name_listener_add(%s)", name);
296
297                 if (!add_match(connection, name)) {
298                         name_data_remove(connection, name, listener_id);
299                         return 0;
300                 }
301         }
302
303         return listener_id;
304 }
305
306 guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
307                                 GDBusWatchFunction func,
308                                 void *user_data, GDBusDestroyFunction destroy)
309 {
310         return g_dbus_add_service_watch(connection, name, NULL, func,
311                                                         user_data, destroy);
312 }
313
314 guint g_dbus_add_signal_watch(DBusConnection *connection,
315                                 const char *rule, GDBusSignalFunction function,
316                                 void *user_data, GDBusDestroyFunction destroy)
317 {
318         return 0;
319 }
320
321 gboolean g_dbus_remove_watch(DBusConnection *connection, guint id)
322 {
323         struct name_data *data;
324         struct name_callback *cb;
325         GSList *ldata, *lcb;
326
327         if (id == 0)
328                 return FALSE;
329
330         for (ldata = name_listeners; ldata; ldata = ldata->next) {
331                 data = ldata->data;
332                 for (lcb = data->callbacks; lcb; lcb = lcb->next) {
333                         cb = lcb->data;
334                         if (cb->id == id)
335                                 goto remove;
336                 }
337         }
338
339         return FALSE;
340
341 remove:
342         data->callbacks = g_slist_remove(data->callbacks, cb);
343         g_free(cb);
344
345         /* Don't remove the filter if other callbacks exist */
346         if (data->callbacks)
347                 return TRUE;
348
349         if (data->name) {
350                 if (!remove_match(data->connection, data->name))
351                         return FALSE;
352         }
353
354         name_listeners = g_slist_remove(name_listeners, data);
355         name_data_free(data);
356
357         return TRUE;
358 }
359
360 void g_dbus_remove_all_watches(DBusConnection *connection)
361 {
362         struct name_data *data;
363
364         data = name_data_find(connection, NULL);
365         if (!data) {
366                 error("name_listener_indicate_disconnect: no listener found");
367                 return;
368         }
369
370         debug("name_listener_indicate_disconnect");
371
372         name_data_call_and_free(data);
373 }