1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2013 Lennart Poettering
10 #include "alloc-util.h"
11 #include "bus-internal.h"
12 #include "bus-track.h"
23 unsigned n_adding; /* are we in the process of adding a new name? */
25 sd_bus_track_handler_t handler;
28 LIST_FIELDS(sd_bus_track, queue);
30 bool in_list:1; /* In bus->tracks? */
31 bool in_queue:1; /* In bus->track_queue? */
35 LIST_FIELDS(sd_bus_track, tracks);
38 #define MATCH_FOR_NAME(name) \
39 strjoina("type='signal'," \
40 "sender='org.freedesktop.DBus'," \
41 "path='/org/freedesktop/DBus'," \
42 "interface='org.freedesktop.DBus'," \
43 "member='NameOwnerChanged'," \
46 static struct track_item* track_item_free(struct track_item *i) {
51 sd_bus_slot_unref(i->slot);
56 DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free);
58 static void bus_track_add_to_queue(sd_bus_track *track) {
61 /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of
64 /* Already in the queue? */
68 /* if we are currently in the process of adding a new name, then let's not enqueue this just yet, let's wait
69 * until the addition is complete. */
70 if (track->n_adding > 0)
73 /* still referenced? */
74 if (hashmap_size(track->names) > 0)
77 /* Nothing to call? */
85 LIST_PREPEND(queue, track->bus->track_queue, track);
86 track->in_queue = true;
89 static void bus_track_remove_from_queue(sd_bus_track *track) {
95 LIST_REMOVE(queue, track->bus->track_queue, track);
96 track->in_queue = false;
99 static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) {
100 struct track_item *i;
105 i = hashmap_remove(track->names, name);
111 bus_track_add_to_queue(track);
113 track->modified = true;
117 _public_ int sd_bus_track_new(
119 sd_bus_track **track,
120 sd_bus_track_handler_t handler,
125 assert_return(bus, -EINVAL);
126 assert_return(bus = bus_resolve(bus), -ENOPKG);
127 assert_return(track, -EINVAL);
129 if (!bus->bus_client)
132 t = new0(sd_bus_track, 1);
137 t->handler = handler;
138 t->userdata = userdata;
139 t->bus = sd_bus_ref(bus);
141 LIST_PREPEND(tracks, bus->tracks, t);
144 bus_track_add_to_queue(t);
150 _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) {
155 assert(track->n_ref > 0);
162 _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) {
166 assert(track->n_ref > 0);
168 if (track->n_ref > 1) {
174 LIST_REMOVE(tracks, track->bus->tracks, track);
176 bus_track_remove_from_queue(track);
177 hashmap_free_with_destructor(track->names, track_item_free);
178 sd_bus_unref(track->bus);
182 static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
183 sd_bus_track *track = userdata;
184 const char *name, *old, *new;
190 r = sd_bus_message_read(message, "sss", &name, &old, &new);
194 bus_track_remove_name_fully(track, name);
198 _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
199 _cleanup_(track_item_freep) struct track_item *n = NULL;
200 struct track_item *i;
204 assert_return(track, -EINVAL);
205 assert_return(service_name_is_valid(name), -EINVAL);
207 i = hashmap_get(track->names, name);
209 if (track->recursive) {
210 unsigned k = track->n_ref + 1;
212 if (k < track->n_ref) /* Check for overflow */
218 bus_track_remove_from_queue(track);
222 r = hashmap_ensure_allocated(&track->names, &string_hash_ops);
226 n = new0(struct track_item, 1);
229 n->name = strdup(name);
233 /* First, subscribe to this name */
234 match = MATCH_FOR_NAME(name);
236 bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */
238 r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, NULL, track);
240 bus_track_add_to_queue(track);
244 r = hashmap_put(track->names, n->name, n);
246 bus_track_add_to_queue(track);
250 /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */
251 track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */
252 r = sd_bus_get_name_creds(track->bus, name, 0, NULL);
255 hashmap_remove(track->names, name);
256 bus_track_add_to_queue(track);
263 bus_track_remove_from_queue(track);
264 track->modified = true;
269 _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) {
270 struct track_item *i;
272 assert_return(name, -EINVAL);
274 if (!track) /* Treat a NULL track object as an empty track object */
277 if (!track->recursive)
278 return bus_track_remove_name_fully(track, name);
280 i = hashmap_get(track->names, name);
289 return bus_track_remove_name_fully(track, name);
294 _public_ unsigned sd_bus_track_count(sd_bus_track *track) {
296 if (!track) /* Let's consider a NULL object equivalent to an empty object */
299 /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note
300 * that this returns the number of names being watched, and multiple references to the same name are not
303 return hashmap_size(track->names);
306 _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
307 assert_return(name, NULL);
309 if (!track) /* Let's consider a NULL object equivalent to an empty object */
312 return hashmap_get(track->names, (void*) name) ? name : NULL;
315 _public_ const char* sd_bus_track_first(sd_bus_track *track) {
316 const char *n = NULL;
321 track->modified = false;
322 track->iterator = ITERATOR_FIRST;
324 hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
328 _public_ const char* sd_bus_track_next(sd_bus_track *track) {
329 const char *n = NULL;
337 hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
341 _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
344 assert_return(track, -EINVAL);
345 assert_return(m, -EINVAL);
347 if (sd_bus_message_get_bus(m) != track->bus)
350 sender = sd_bus_message_get_sender(m);
354 return sd_bus_track_add_name(track, sender);
357 _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) {
360 assert_return(m, -EINVAL);
362 if (!track) /* Treat a NULL track object as an empty track object */
365 if (sd_bus_message_get_bus(m) != track->bus)
368 sender = sd_bus_message_get_sender(m);
372 return sd_bus_track_remove_name(track, sender);
375 _public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) {
376 assert_return(track, NULL);
381 void bus_track_dispatch(sd_bus_track *track) {
385 assert(track->handler);
387 bus_track_remove_from_queue(track);
389 sd_bus_track_ref(track);
391 r = track->handler(track, track->userdata);
393 log_debug_errno(r, "Failed to process track handler: %m");
395 bus_track_add_to_queue(track);
397 sd_bus_track_unref(track);
400 void bus_track_close(sd_bus_track *track) {
403 /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it
404 * immediately, as we are closing now, but first flush out all names. */
407 return; /* We already closed this one, don't close it again. */
409 /* Remember that this one is closed now */
410 LIST_REMOVE(tracks, track->bus->tracks, track);
411 track->in_list = false;
413 /* If there's no name in this one anyway, we don't have to dispatch */
414 if (hashmap_isempty(track->names))
417 /* Let's flush out all names */
418 hashmap_clear_with_destructor(track->names, track_item_free);
422 bus_track_dispatch(track);
425 _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
426 assert_return(track, NULL);
428 return track->userdata;
431 _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
434 assert_return(track, NULL);
436 ret = track->userdata;
437 track->userdata = userdata;
442 _public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) {
443 assert_return(track, -EINVAL);
445 if (track->recursive == !!b)
448 if (!hashmap_isempty(track->names))
451 track->recursive = b;
455 _public_ int sd_bus_track_get_recursive(sd_bus_track *track) {
456 assert_return(track, -EINVAL);
458 return track->recursive;
461 _public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) {
464 assert_return(m, -EINVAL);
466 if (!track) /* Let's consider a NULL object equivalent to an empty object */
469 if (sd_bus_message_get_bus(m) != track->bus)
472 sender = sd_bus_message_get_sender(m);
476 return sd_bus_track_count_name(track, sender);
479 _public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) {
480 struct track_item *i;
482 assert_return(service_name_is_valid(name), -EINVAL);
484 if (!track) /* Let's consider a NULL object equivalent to an empty object */
487 i = hashmap_get(track->names, name);