Merge "Fix SIGSEV on freeing server domains list" into tizen
[platform/upstream/connman.git] / src / peer_service.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2014  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27
28 #include <gdbus.h>
29
30 #include "connman.h"
31
32 static DBusConnection *connection;
33
34 struct _peer_service {
35         bool registered;
36         const char *owner;
37         DBusMessage *pending;
38
39         GBytes *specification;
40         GBytes *query;
41         int version;
42
43         bool master;
44 };
45
46 struct _peer_service_owner {
47         char *owner;
48         guint watch;
49         GList *services;
50 };
51
52 static struct connman_peer_driver *peer_driver;
53
54 static GHashTable *owners_map;
55 static GHashTable *services_map;
56 static int peer_master;
57
58 static void reply_pending(struct _peer_service *service, int error)
59 {
60         if (!service->pending)
61                 return;
62
63         connman_dbus_reply_pending(service->pending, error, NULL);
64         service->pending = NULL;
65 }
66
67 static struct _peer_service *find_peer_service(GBytes *specification,
68                                         GBytes *query, int version,
69                                         const char *owner, bool remove)
70 {
71         struct _peer_service *service = NULL;
72         struct _peer_service_owner *ps_owner;
73         GList *list;
74
75         ps_owner = g_hash_table_lookup(services_map, specification);
76         if (!ps_owner)
77                 return NULL;
78
79         if (owner && g_strcmp0(owner, ps_owner->owner) != 0)
80                 return NULL;
81
82         for (list = ps_owner->services; list; list = list->next) {
83                 service = list->data;
84
85                 if (service->specification == specification)
86                         break;
87
88                 if (version) {
89                         if (!service->version)
90                                 continue;
91                         if (version != service->version)
92                                 continue;
93                 }
94
95                 if (query) {
96                         if (!service->query)
97                                 continue;
98                         if (g_bytes_equal(service->query, query))
99                                 continue;
100                 }
101
102                 if (g_bytes_equal(service->specification, specification))
103                         break;
104         }
105
106         if (!service)
107                 return NULL;
108
109         if (owner && remove)
110                 ps_owner->services = g_list_delete_link(ps_owner->services,
111                                                                         list);
112
113         return service;
114 }
115
116 static void unregister_peer_service(struct _peer_service *service)
117 {
118         gsize spec_length, query_length = 0;
119         const void *spec, *query = NULL;
120
121         if (!peer_driver || !service->specification)
122                 return;
123
124         spec = g_bytes_get_data(service->specification, &spec_length);
125         if (service->query)
126                 query = g_bytes_get_data(service->query, &query_length);
127
128         peer_driver->unregister_service(spec, spec_length, query,
129                                         query_length, service->version);
130 }
131
132 static void remove_peer_service(gpointer user_data)
133 {
134         struct _peer_service *service = user_data;
135
136         reply_pending(service, ECONNABORTED);
137
138         if (service->registered)
139                 unregister_peer_service(service);
140
141         if (service->specification) {
142                 if (service->owner) {
143                         find_peer_service(service->specification,
144                                         service->query, service->version,
145                                         service->owner, true);
146                 }
147
148                 g_hash_table_remove(services_map, service->specification);
149                 g_bytes_unref(service->specification);
150         }
151
152         if (service->query)
153                 g_bytes_unref(service->query);
154
155         if (service->master)
156                 peer_master--;
157
158         g_free(service);
159 }
160
161 static void apply_peer_service_removal(gpointer user_data)
162 {
163         struct _peer_service *service = user_data;
164
165         service->owner = NULL;
166         remove_peer_service(user_data);
167 }
168
169 static void remove_peer_service_owner(gpointer user_data)
170 {
171         struct _peer_service_owner *ps_owner = user_data;
172
173         DBG("owner %s", ps_owner->owner);
174
175         if (ps_owner->watch > 0)
176                 g_dbus_remove_watch(connection, ps_owner->watch);
177
178         if (ps_owner->services) {
179                 g_list_free_full(ps_owner->services,
180                                         apply_peer_service_removal);
181         }
182
183         g_free(ps_owner->owner);
184         g_free(ps_owner);
185 }
186
187 static void owner_disconnect(DBusConnection *conn, void *user_data)
188 {
189         struct _peer_service_owner *ps_owner = user_data;
190
191         ps_owner->watch = 0;
192         g_hash_table_remove(owners_map, ps_owner->owner);
193 }
194
195 static void service_registration_result(int result, void *user_data)
196 {
197         struct _peer_service *service = user_data;
198
199         reply_pending(service, -result);
200
201         if (service->registered)
202                 return;
203
204         if (result == 0) {
205                 service->registered = true;
206                 if (service->master)
207                         peer_master++;
208                 return;
209         }
210
211         remove_peer_service(service);
212 }
213
214 static int register_peer_service(struct _peer_service *service)
215 {
216         gsize spec_length, query_length = 0;
217         const void *spec, *query = NULL;
218
219         if (!peer_driver)
220                 return 0;
221
222         spec = g_bytes_get_data(service->specification, &spec_length);
223         if (service->query)
224                 query = g_bytes_get_data(service->query, &query_length);
225
226         return peer_driver->register_service(spec, spec_length, query,
227                                         query_length, service->version,
228                                         service_registration_result, service);
229 }
230
231 static void register_all_services(gpointer key, gpointer value,
232                                                 gpointer user_data)
233 {
234         struct _peer_service_owner *ps_owner = value;
235         GList *list;
236
237         for (list = ps_owner->services; list; list = list->next) {
238                 struct _peer_service *service = list->data;
239
240                 if (service->registered)
241                         register_peer_service(service);
242         }
243 }
244
245 void __connman_peer_service_set_driver(struct connman_peer_driver *driver)
246 {
247         peer_driver = driver;
248         if (!peer_driver)
249                 return;
250
251         g_hash_table_foreach(owners_map, register_all_services, NULL);
252 }
253
254 int __connman_peer_service_register(const char *owner, DBusMessage *msg,
255                                         const unsigned char *specification,
256                                         int specification_length,
257                                         const unsigned char *query,
258                                         int query_length, int version,
259                                         bool master)
260 {
261         struct _peer_service_owner *ps_owner;
262         GBytes *spec, *query_spec = NULL;
263         struct _peer_service *service;
264         bool new = false;
265         int ret = 0;
266
267         DBG("owner %s - spec %p/length %d - query %p/length %d - version %d",
268                                 owner,specification, specification_length,
269                                 query, query_length, version);
270
271         if (!specification || specification_length == 0)
272                 return -EINVAL;
273
274         ps_owner = g_hash_table_lookup(owners_map, owner);
275         if (!ps_owner) {
276                 ps_owner = g_try_new0(struct _peer_service_owner, 1);
277                 if (!ps_owner)
278                         return -ENOMEM;
279
280                 ps_owner->owner = g_strdup(owner);
281                 ps_owner->watch = g_dbus_add_disconnect_watch(connection,
282                                                 owner, owner_disconnect,
283                                                 ps_owner, NULL);
284                 g_hash_table_insert(owners_map, ps_owner->owner, ps_owner);
285                 new = true;
286         }
287
288         spec = g_bytes_new(specification, specification_length);
289         if (query)
290                 query_spec = g_bytes_new(query, query_length);
291
292         service = find_peer_service(spec, query_spec, version, NULL, false);
293         if (service) {
294                 DBG("Found one existing service %p", service);
295
296                 if (service->pending)
297                         ret = -EINPROGRESS;
298                 else
299                         ret = -EEXIST;
300
301                 service = NULL;
302                 goto error;
303         }
304
305         service = g_try_new0(struct _peer_service, 1);
306         if (!service) {
307                 ret = -ENOMEM;
308                 goto error;
309         }
310
311         service->owner = ps_owner->owner;
312         service->specification = spec;
313         service->query = query_spec;
314         service->version = version;
315         service->master = master;
316
317         g_hash_table_insert(services_map, spec, ps_owner);
318         spec = query_spec = NULL;
319
320         ret = register_peer_service(service);
321         if (ret != 0 && ret != -EINPROGRESS)
322                 goto error;
323         else if (ret == -EINPROGRESS)
324                 service->pending = dbus_message_ref(msg);
325         else {
326                 service->registered = true;
327                 if (master)
328                         peer_master++;
329         }
330
331         ps_owner->services = g_list_prepend(ps_owner->services, service);
332
333         return ret;
334 error:
335         if (spec)
336                 g_bytes_unref(spec);
337         if (query_spec)
338                 g_bytes_unref(query_spec);
339
340         if (service)
341                 remove_peer_service(service);
342
343         if (new)
344                 g_hash_table_remove(owners_map, ps_owner->owner);
345
346         return ret;
347 }
348
349 int __connman_peer_service_unregister(const char *owner,
350                                         const unsigned char *specification,
351                                         int specification_length,
352                                         const unsigned char *query,
353                                         int query_length, int version)
354 {
355         struct _peer_service_owner *ps_owner;
356         GBytes *spec, *query_spec = NULL;
357         struct _peer_service *service;
358
359         DBG("owner %s - spec %p/length %d - query %p/length %d - version %d",
360                                 owner,specification, specification_length,
361                                 query, query_length, version);
362
363         ps_owner = g_hash_table_lookup(owners_map, owner);
364         if (!ps_owner)
365                 return -ESRCH;
366
367         spec = g_bytes_new(specification, specification_length);
368         if (query)
369                 query_spec = g_bytes_new(query, query_length);
370
371         service = find_peer_service(spec, query_spec, version, owner, true);
372
373         g_bytes_unref(spec);
374         g_bytes_unref(query_spec);
375
376         if (!service)
377                 return -ESRCH;
378
379         remove_peer_service(service);
380
381         if (!ps_owner->services)
382                 g_hash_table_remove(owners_map, ps_owner->owner);
383
384         return 0;
385 }
386
387 bool connman_peer_service_is_master(void)
388 {
389         if (!peer_master || !peer_driver)
390                 return false;
391
392         return true;
393 }
394
395 int __connman_peer_service_init(void)
396 {
397         DBG("");
398         connection = connman_dbus_get_connection();
399         if (!connection)
400                 return -1;
401
402         peer_driver = NULL;
403
404         owners_map = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
405                                                 remove_peer_service_owner);
406         services_map = g_hash_table_new_full(g_bytes_hash, g_bytes_equal,
407                                                                 NULL, NULL);
408         peer_master = 0;
409
410         return 0;
411 }
412
413 void __connman_peer_service_cleanup(void)
414 {
415         DBG("");
416
417         if (!connection)
418                 return;
419
420         g_hash_table_destroy(owners_map);
421         g_hash_table_destroy(services_map);
422         peer_master = 0;
423
424         dbus_connection_unref(connection);
425         connection = NULL;
426 }