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