chiark / gitweb /
Merge patches from Hleb Valoshka
[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_list:1;    /* In bus->tracks? */
43         bool in_queue:1;   /* In bus->track_queue? */
44         bool modified:1;
45         bool recursive:1;
46
47         LIST_FIELDS(sd_bus_track, tracks);
48 };
49
50 #define MATCH_PREFIX                                        \
51         "type='signal',"                                    \
52         "sender='org.freedesktop.DBus',"                    \
53         "path='/org/freedesktop/DBus',"                     \
54         "interface='org.freedesktop.DBus',"                 \
55         "member='NameOwnerChanged',"                        \
56         "arg0='"
57
58 #define MATCH_SUFFIX \
59         "'"
60
61 #define MATCH_FOR_NAME(name)                                            \
62         ({                                                              \
63                 char *_x;                                               \
64                 size_t _l = strlen(name);                               \
65                 _x = alloca(strlen(MATCH_PREFIX)+_l+strlen(MATCH_SUFFIX)+1); \
66                 strcpy(stpcpy(stpcpy(_x, MATCH_PREFIX), name), MATCH_SUFFIX); \
67                 _x;                                                     \
68         })
69
70 static struct track_item* track_item_free(struct track_item *i) {
71
72         if (!i)
73                 return NULL;
74
75         sd_bus_slot_unref(i->slot);
76         free(i->name);
77         return mfree(i);
78 }
79
80 DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free);
81
82 static void bus_track_add_to_queue(sd_bus_track *track) {
83         assert(track);
84
85         /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of
86          * conditions. */
87
88         /* Already in the queue? */
89         if (track->in_queue)
90                 return;
91
92         /* if we are currently in the process of adding a new name, then let's not enqueue this just yet, let's wait
93          * until the addition is complete. */
94         if (track->n_adding > 0)
95                 return;
96
97         /* still referenced? */
98         if (hashmap_size(track->names) > 0)
99                 return;
100
101         /* Nothing to call? */
102         if (!track->handler)
103                 return;
104
105         /* Already closed? */
106         if (!track->in_list)
107                 return;
108
109         LIST_PREPEND(queue, track->bus->track_queue, track);
110         track->in_queue = true;
111 }
112
113 static void bus_track_remove_from_queue(sd_bus_track *track) {
114         assert(track);
115
116         if (!track->in_queue)
117                 return;
118
119         LIST_REMOVE(queue, track->bus->track_queue, track);
120         track->in_queue = false;
121 }
122
123 static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) {
124         struct track_item *i;
125
126         assert(track);
127         assert(name);
128
129         i = hashmap_remove(track->names, name);
130         if (!i)
131                 return 0;
132
133         track_item_free(i);
134
135         bus_track_add_to_queue(track);
136
137         track->modified = true;
138         return 1;
139 }
140
141 _public_ int sd_bus_track_new(
142                 sd_bus *bus,
143                 sd_bus_track **track,
144                 sd_bus_track_handler_t handler,
145                 void *userdata) {
146
147         sd_bus_track *t;
148
149         assert_return(bus, -EINVAL);
150         assert_return(track, -EINVAL);
151
152         if (!bus->bus_client)
153                 return -EINVAL;
154
155         t = new0(sd_bus_track, 1);
156         if (!t)
157                 return -ENOMEM;
158
159         t->n_ref = 1;
160         t->handler = handler;
161         t->userdata = userdata;
162         t->bus = sd_bus_ref(bus);
163
164         LIST_PREPEND(tracks, bus->tracks, t);
165         t->in_list = true;
166
167         bus_track_add_to_queue(t);
168
169         *track = t;
170         return 0;
171 }
172
173 _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) {
174
175         if (!track)
176                 return NULL;
177
178         assert(track->n_ref > 0);
179
180         track->n_ref++;
181
182         return track;
183 }
184
185 _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) {
186         struct track_item *i;
187
188         if (!track)
189                 return NULL;
190
191         assert(track->n_ref > 0);
192
193         if (track->n_ref > 1) {
194                 track->n_ref--;
195                 return NULL;
196         }
197
198         while ((i = hashmap_steal_first(track->names)))
199                 track_item_free(i);
200
201         if (track->in_list)
202                 LIST_REMOVE(tracks, track->bus->tracks, track);
203
204         bus_track_remove_from_queue(track);
205         hashmap_free(track->names);
206         sd_bus_unref(track->bus);
207         return mfree(track);
208 }
209
210 static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
211         sd_bus_track *track = userdata;
212         const char *name, *old, *new;
213         int r;
214
215         assert(message);
216         assert(track);
217
218         r = sd_bus_message_read(message, "sss", &name, &old, &new);
219         if (r < 0)
220                 return 0;
221
222         bus_track_remove_name_fully(track, name);
223         return 0;
224 }
225
226 _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
227         _cleanup_(track_item_freep) struct track_item *n = NULL;
228         struct track_item *i;
229         const char *match;
230         int r;
231
232         assert_return(track, -EINVAL);
233         assert_return(service_name_is_valid(name), -EINVAL);
234
235         i = hashmap_get(track->names, name);
236         if (i) {
237                 if (track->recursive) {
238                         unsigned k = track->n_ref + 1;
239
240                         if (k < track->n_ref) /* Check for overflow */
241                                 return -EOVERFLOW;
242
243                         track->n_ref = k;
244                 }
245
246                 bus_track_remove_from_queue(track);
247                 return 0;
248         }
249
250         r = hashmap_ensure_allocated(&track->names, &string_hash_ops);
251         if (r < 0)
252                 return r;
253
254         n = new0(struct track_item, 1);
255         if (!n)
256                 return -ENOMEM;
257         n->name = strdup(name);
258         if (!n->name)
259                 return -ENOMEM;
260
261         /* First, subscribe to this name */
262         match = MATCH_FOR_NAME(name);
263
264         bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */
265
266         track->n_adding++; /* make sure we aren't dispatched while we synchronously add this match */
267         r = sd_bus_add_match(track->bus, &n->slot, match, on_name_owner_changed, track);
268         track->n_adding--;
269         if (r < 0) {
270                 bus_track_add_to_queue(track);
271                 return r;
272         }
273
274         r = hashmap_put(track->names, n->name, n);
275         if (r < 0) {
276                 bus_track_add_to_queue(track);
277                 return r;
278         }
279
280         /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */
281         track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */
282         r = sd_bus_get_name_creds(track->bus, name, 0, NULL);
283         track->n_adding--;
284         if (r < 0) {
285                 hashmap_remove(track->names, name);
286                 bus_track_add_to_queue(track);
287                 return r;
288         }
289
290         n->n_ref = 1;
291         n = NULL;
292
293         bus_track_remove_from_queue(track);
294         track->modified = true;
295
296         return 1;
297 }
298
299 _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) {
300         struct track_item *i;
301
302         assert_return(name, -EINVAL);
303
304         if (!track) /* Treat a NULL track object as an empty track object */
305                 return 0;
306
307         if (!track->recursive)
308                 return bus_track_remove_name_fully(track, name);
309
310         i = hashmap_get(track->names, name);
311         if (!i)
312                 return -EUNATCH;
313         if (i->n_ref <= 0)
314                 return -EUNATCH;
315
316         i->n_ref--;
317
318         if (i->n_ref <= 0)
319                 return bus_track_remove_name_fully(track, name);
320
321         return 1;
322 }
323
324 _public_ unsigned sd_bus_track_count(sd_bus_track *track) {
325
326         if (!track) /* Let's consider a NULL object equivalent to an empty object */
327                 return 0;
328
329         /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note
330          * that this returns the number of names being watched, and multiple references to the same name are not
331          * counted. */
332
333         return hashmap_size(track->names);
334 }
335
336 _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
337         assert_return(name, NULL);
338
339         if (!track) /* Let's consider a NULL object equivalent to an empty object */
340                 return NULL;
341
342         return hashmap_get(track->names, (void*) name) ? name : NULL;
343 }
344
345 _public_ const char* sd_bus_track_first(sd_bus_track *track) {
346         const char *n = NULL;
347
348         if (!track)
349                 return NULL;
350
351         track->modified = false;
352         track->iterator = ITERATOR_FIRST;
353
354         hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
355         return n;
356 }
357
358 _public_ const char* sd_bus_track_next(sd_bus_track *track) {
359         const char *n = NULL;
360
361         if (!track)
362                 return NULL;
363
364         if (track->modified)
365                 return NULL;
366
367         hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
368         return n;
369 }
370
371 _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
372         const char *sender;
373
374         assert_return(track, -EINVAL);
375         assert_return(m, -EINVAL);
376
377         if (sd_bus_message_get_bus(m) != track->bus)
378                 return -EINVAL;
379
380         sender = sd_bus_message_get_sender(m);
381         if (!sender)
382                 return -EINVAL;
383
384         return sd_bus_track_add_name(track, sender);
385 }
386
387 _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) {
388         const char *sender;
389
390         assert_return(m, -EINVAL);
391
392         if (!track) /* Treat a NULL track object as an empty track object */
393                 return 0;
394
395         if (sd_bus_message_get_bus(m) != track->bus)
396                 return -EINVAL;
397
398         sender = sd_bus_message_get_sender(m);
399         if (!sender)
400                 return -EINVAL;
401
402         return sd_bus_track_remove_name(track, sender);
403 }
404
405 _public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) {
406         assert_return(track, NULL);
407
408         return track->bus;
409 }
410
411 void bus_track_dispatch(sd_bus_track *track) {
412         int r;
413
414         assert(track);
415         assert(track->handler);
416
417         bus_track_remove_from_queue(track);
418
419         sd_bus_track_ref(track);
420
421         r = track->handler(track, track->userdata);
422         if (r < 0)
423                 log_debug_errno(r, "Failed to process track handler: %m");
424         else if (r == 0)
425                 bus_track_add_to_queue(track);
426
427         sd_bus_track_unref(track);
428 }
429
430 void bus_track_close(sd_bus_track *track) {
431         struct track_item *i;
432
433         assert(track);
434
435         /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it
436          * immediately, as we are closing now, but first flush out all names. */
437
438         if (!track->in_list)
439                 return; /* We already closed this one, don't close it again. */
440
441         /* Remember that this one is closed now */
442         LIST_REMOVE(tracks, track->bus->tracks, track);
443         track->in_list = false;
444
445         /* If there's no name in this one anyway, we don't have to dispatch */
446         if (hashmap_isempty(track->names))
447                 return;
448
449         /* Let's flush out all names */
450         while ((i = hashmap_steal_first(track->names)))
451                 track_item_free(i);
452
453         /* Invoke handler */
454         if (track->handler)
455                 bus_track_dispatch(track);
456 }
457
458 _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
459         assert_return(track, NULL);
460
461         return track->userdata;
462 }
463
464 _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
465         void *ret;
466
467         assert_return(track, NULL);
468
469         ret = track->userdata;
470         track->userdata = userdata;
471
472         return ret;
473 }
474
475 _public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) {
476         assert_return(track, -EINVAL);
477
478         if (track->recursive == !!b)
479                 return 0;
480
481         if (!hashmap_isempty(track->names))
482                 return -EBUSY;
483
484         track->recursive = b;
485         return 0;
486 }
487
488 _public_ int sd_bus_track_get_recursive(sd_bus_track *track) {
489         assert_return(track, -EINVAL);
490
491         return track->recursive;
492 }
493
494 _public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) {
495         const char *sender;
496
497         assert_return(m, -EINVAL);
498
499         if (!track) /* Let's consider a NULL object equivalent to an empty object */
500                 return 0;
501
502         if (sd_bus_message_get_bus(m) != track->bus)
503                 return -EINVAL;
504
505         sender = sd_bus_message_get_sender(m);
506         if (!sender)
507                 return -EINVAL;
508
509         return sd_bus_track_count_name(track, sender);
510 }
511
512 _public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) {
513         struct track_item *i;
514
515         assert_return(service_name_is_valid(name), -EINVAL);
516
517         if (!track) /* Let's consider a NULL object equivalent to an empty object */
518                 return 0;
519
520         i = hashmap_get(track->names, name);
521         if (!i)
522                 return 0;
523
524         return i->n_ref;
525 }