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