chiark / gitweb /
sd-bus: ensure we don't dispatch track objects while we are adding names to them
[elogind.git] / src / libelogind / sd-bus / bus-track.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2013 Lennart Poettering
5
6   systemd is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   systemd is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include "sd-bus.h"
21
22 #include "alloc-util.h"
23 #include "bus-internal.h"
24 #include "bus-track.h"
25 #include "bus-util.h"
26
27 struct track_item {
28         unsigned n_ref;
29         char *name;
30         sd_bus_slot *slot;
31 };
32
33 struct sd_bus_track {
34         unsigned n_ref;
35         unsigned n_adding; /* are we in the process of adding a new name? */
36         sd_bus *bus;
37         sd_bus_track_handler_t handler;
38         void *userdata;
39         Hashmap *names;
40         LIST_FIELDS(sd_bus_track, queue);
41         Iterator iterator;
42         bool in_queue:1;
43         bool modified:1;
44         bool recursive:1;
45 };
46
47 #define MATCH_PREFIX                                        \
48         "type='signal',"                                    \
49         "sender='org.freedesktop.DBus',"                    \
50         "path='/org/freedesktop/DBus',"                     \
51         "interface='org.freedesktop.DBus',"                 \
52         "member='NameOwnerChanged',"                        \
53         "arg0='"
54
55 #define MATCH_SUFFIX \
56         "'"
57
58 #define MATCH_FOR_NAME(name)                                            \
59         ({                                                              \
60                 char *_x;                                               \
61                 size_t _l = strlen(name);                               \
62                 _x = alloca(strlen(MATCH_PREFIX)+_l+strlen(MATCH_SUFFIX)+1); \
63                 strcpy(stpcpy(stpcpy(_x, MATCH_PREFIX), name), MATCH_SUFFIX); \
64                 _x;                                                     \
65         })
66
67 static struct track_item* track_item_free(struct track_item *i) {
68
69         if (!i)
70                 return NULL;
71
72         sd_bus_slot_unref(i->slot);
73         free(i->name);
74         free(i);
75
76         return NULL;
77 }
78
79 DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free);
80
81 static void bus_track_add_to_queue(sd_bus_track *track) {
82         assert(track);
83
84         /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of
85          * conditions. */
86
87         /* Already in the queue? */
88         if (track->in_queue)
89                 return;
90
91         /* if we are currently in the process of adding a new name, then let's not enqueue this just yet, let's wait
92          * until the addition is complete. */
93         if (track->n_adding > 0)
94                 return;
95
96         /* still referenced? */
97         if (hashmap_size(track->names) > 0)
98                 return;
99
100         /* Nothing to call? */
101         if (!track->handler)
102                 return;
103
104         LIST_PREPEND(queue, track->bus->track_queue, track);
105         track->in_queue = true;
106 }
107
108 static void bus_track_remove_from_queue(sd_bus_track *track) {
109         assert(track);
110
111         if (!track->in_queue)
112                 return;
113
114         LIST_REMOVE(queue, track->bus->track_queue, track);
115         track->in_queue = false;
116 }
117
118 static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) {
119         struct track_item *i;
120
121         assert(track);
122         assert(name);
123
124         i = hashmap_remove(track->names, name);
125         if (!i)
126                 return 0;
127
128         track_item_free(i);
129
130         bus_track_add_to_queue(track);
131
132         track->modified = true;
133         return 1;
134 }
135
136 _public_ int sd_bus_track_new(
137                 sd_bus *bus,
138                 sd_bus_track **track,
139                 sd_bus_track_handler_t handler,
140                 void *userdata) {
141
142         sd_bus_track *t;
143
144         assert_return(bus, -EINVAL);
145         assert_return(track, -EINVAL);
146
147         if (!bus->bus_client)
148                 return -EINVAL;
149
150         t = new0(sd_bus_track, 1);
151         if (!t)
152                 return -ENOMEM;
153
154         t->n_ref = 1;
155         t->handler = handler;
156         t->userdata = userdata;
157         t->bus = sd_bus_ref(bus);
158
159         bus_track_add_to_queue(t);
160
161         *track = t;
162         return 0;
163 }
164
165 _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) {
166
167         if (!track)
168                 return NULL;
169
170         assert(track->n_ref > 0);
171
172         track->n_ref++;
173
174         return track;
175 }
176
177 _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) {
178         struct track_item *i;
179
180         if (!track)
181                 return NULL;
182
183         assert(track->n_ref > 0);
184
185         if (track->n_ref > 1) {
186                 track->n_ref--;
187                 return NULL;
188         }
189
190         while ((i = hashmap_steal_first(track->names)))
191                 track_item_free(i);
192
193         bus_track_remove_from_queue(track);
194         hashmap_free(track->names);
195         sd_bus_unref(track->bus);
196         free(track);
197
198         return NULL;
199 }
200
201 static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
202         sd_bus_track *track = userdata;
203         const char *name, *old, *new;
204         int r;
205
206         assert(message);
207         assert(track);
208
209         r = sd_bus_message_read(message, "sss", &name, &old, &new);
210         if (r < 0)
211                 return 0;
212
213         bus_track_remove_name_fully(track, name);
214         return 0;
215 }
216
217 _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
218         _cleanup_(track_item_freep) struct track_item *n = NULL;
219         struct track_item *i;
220         const char *match;
221         int r;
222
223         assert_return(track, -EINVAL);
224         assert_return(service_name_is_valid(name), -EINVAL);
225
226         i = hashmap_get(track->names, name);
227         if (i) {
228                 if (track->recursive) {
229                         unsigned k = track->n_ref + 1;
230
231                         if (k < track->n_ref) /* Check for overflow */
232                                 return -EOVERFLOW;
233
234                         track->n_ref = k;
235                 }
236
237                 bus_track_remove_from_queue(track);
238                 return 0;
239         }
240
241         r = hashmap_ensure_allocated(&track->names, &string_hash_ops);
242         if (r < 0)
243                 return r;
244
245         n = new0(struct track_item, 1);
246         if (!n)
247                 return -ENOMEM;
248         n->name = strdup(name);
249         if (!n->name)
250                 return -ENOMEM;
251
252         /* First, subscribe to this name */
253         match = MATCH_FOR_NAME(name);
254
255         bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */
256
257         track->n_adding++; /* make sure we aren't dispatched while we synchronously add this match */
258         r = sd_bus_add_match(track->bus, &n->slot, match, on_name_owner_changed, track);
259         track->n_adding--;
260         if (r < 0) {
261                 bus_track_add_to_queue(track);
262                 return r;
263         }
264
265         r = hashmap_put(track->names, n->name, n);
266         if (r < 0) {
267                 bus_track_add_to_queue(track);
268                 return r;
269         }
270
271         /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */
272         track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */
273         r = sd_bus_get_name_creds(track->bus, name, 0, NULL);
274         track->n_adding--;
275         if (r < 0) {
276                 hashmap_remove(track->names, name);
277                 bus_track_add_to_queue(track);
278                 return r;
279         }
280
281         n->n_ref = 1;
282         n = NULL;
283
284         bus_track_remove_from_queue(track);
285         track->modified = true;
286
287         return 1;
288 }
289
290 _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) {
291         struct track_item *i;
292
293         assert_return(name, -EINVAL);
294
295         if (!track) /* Treat a NULL track object as an empty track object */
296                 return 0;
297
298         if (!track->recursive)
299                 return bus_track_remove_name_fully(track, name);
300
301         i = hashmap_get(track->names, name);
302         if (!i)
303                 return -EUNATCH;
304         if (i->n_ref <= 0)
305                 return -EUNATCH;
306
307         i->n_ref--;
308
309         if (i->n_ref <= 0)
310                 return bus_track_remove_name_fully(track, name);
311
312         return 1;
313 }
314
315 _public_ unsigned sd_bus_track_count(sd_bus_track *track) {
316
317         if (!track) /* Let's consider a NULL object equivalent to an empty object */
318                 return 0;
319
320         /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note
321          * that this returns the number of names being watched, and multiple references to the same name are not
322          * counted. */
323
324         return hashmap_size(track->names);
325 }
326
327 _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
328         assert_return(name, NULL);
329
330         if (!track) /* Let's consider a NULL object equivalent to an empty object */
331                 return NULL;
332
333         return hashmap_get(track->names, (void*) name) ? name : NULL;
334 }
335
336 _public_ const char* sd_bus_track_first(sd_bus_track *track) {
337         const char *n = NULL;
338
339         if (!track)
340                 return NULL;
341
342         track->modified = false;
343         track->iterator = ITERATOR_FIRST;
344
345         hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
346         return n;
347 }
348
349 _public_ const char* sd_bus_track_next(sd_bus_track *track) {
350         const char *n = NULL;
351
352         if (!track)
353                 return NULL;
354
355         if (track->modified)
356                 return NULL;
357
358         hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
359         return n;
360 }
361
362 _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
363         const char *sender;
364
365         assert_return(track, -EINVAL);
366         assert_return(m, -EINVAL);
367
368         if (sd_bus_message_get_bus(m) != track->bus)
369                 return -EINVAL;
370
371         sender = sd_bus_message_get_sender(m);
372         if (!sender)
373                 return -EINVAL;
374
375         return sd_bus_track_add_name(track, sender);
376 }
377
378 _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) {
379         const char *sender;
380
381         assert_return(m, -EINVAL);
382
383         if (!track) /* Treat a NULL track object as an empty track object */
384                 return 0;
385
386         if (sd_bus_message_get_bus(m) != track->bus)
387                 return -EINVAL;
388
389         sender = sd_bus_message_get_sender(m);
390         if (!sender)
391                 return -EINVAL;
392
393         return sd_bus_track_remove_name(track, sender);
394 }
395
396 _public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) {
397         assert_return(track, NULL);
398
399         return track->bus;
400 }
401
402 void bus_track_dispatch(sd_bus_track *track) {
403         int r;
404
405         assert(track);
406         assert(track->in_queue);
407         assert(track->handler);
408
409         bus_track_remove_from_queue(track);
410
411         sd_bus_track_ref(track);
412
413         r = track->handler(track, track->userdata);
414         if (r < 0)
415                 log_debug_errno(r, "Failed to process track handler: %m");
416         else if (r == 0)
417                 bus_track_add_to_queue(track);
418
419         sd_bus_track_unref(track);
420 }
421
422 #if 0 /// UNNEEDED by elogind
423 _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
424         assert_return(track, NULL);
425
426         return track->userdata;
427 }
428
429 _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
430         void *ret;
431
432         assert_return(track, NULL);
433
434         ret = track->userdata;
435         track->userdata = userdata;
436
437         return ret;
438 }
439 #endif // 0
440
441 _public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) {
442         assert_return(track, -EINVAL);
443
444         if (track->recursive == !!b)
445                 return 0;
446
447         if (!hashmap_isempty(track->names))
448                 return -EBUSY;
449
450         track->recursive = b;
451         return 0;
452 }
453
454 _public_ int sd_bus_track_get_recursive(sd_bus_track *track) {
455         assert_return(track, -EINVAL);
456
457         return track->recursive;
458 }
459
460 _public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) {
461         const char *sender;
462
463         assert_return(m, -EINVAL);
464
465         if (!track) /* Let's consider a NULL object equivalent to an empty object */
466                 return 0;
467
468         if (sd_bus_message_get_bus(m) != track->bus)
469                 return -EINVAL;
470
471         sender = sd_bus_message_get_sender(m);
472         if (!sender)
473                 return -EINVAL;
474
475         return sd_bus_track_count_name(track, sender);
476 }
477
478 _public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) {
479         struct track_item *i;
480
481         assert_return(service_name_is_valid(name), -EINVAL);
482
483         if (!track) /* Let's consider a NULL object equivalent to an empty object */
484                 return 0;
485
486         i = hashmap_get(track->names, name);
487         if (!i)
488                 return 0;
489
490         return i->n_ref;
491 }