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