1 /* SPDX-License-Identifier: LGPL-2.1+ */
7 #include "alloc-util.h"
8 #include "bus-internal.h"
20 unsigned n_adding; /* are we in the process of adding a new name? */
22 sd_bus_track_handler_t handler;
25 LIST_FIELDS(sd_bus_track, queue);
27 bool in_list:1; /* In bus->tracks? */
28 bool in_queue:1; /* In bus->track_queue? */
31 sd_bus_destroy_t destroy_callback;
33 LIST_FIELDS(sd_bus_track, tracks);
36 #define MATCH_FOR_NAME(name) \
37 strjoina("type='signal'," \
38 "sender='org.freedesktop.DBus'," \
39 "path='/org/freedesktop/DBus'," \
40 "interface='org.freedesktop.DBus'," \
41 "member='NameOwnerChanged'," \
44 static struct track_item* track_item_free(struct track_item *i) {
49 sd_bus_slot_unref(i->slot);
54 DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free);
56 static void bus_track_add_to_queue(sd_bus_track *track) {
59 /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of
62 /* Already in the queue? */
66 /* if we are currently in the process of adding a new name, then let's not enqueue this just yet, let's wait
67 * until the addition is complete. */
68 if (track->n_adding > 0)
71 /* still referenced? */
72 if (hashmap_size(track->names) > 0)
75 /* Nothing to call? */
83 LIST_PREPEND(queue, track->bus->track_queue, track);
84 track->in_queue = true;
87 static void bus_track_remove_from_queue(sd_bus_track *track) {
93 LIST_REMOVE(queue, track->bus->track_queue, track);
94 track->in_queue = false;
97 static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) {
103 i = hashmap_remove(track->names, name);
109 bus_track_add_to_queue(track);
111 track->modified = true;
115 _public_ int sd_bus_track_new(
117 sd_bus_track **track,
118 sd_bus_track_handler_t handler,
123 assert_return(bus, -EINVAL);
124 assert_return(bus = bus_resolve(bus), -ENOPKG);
125 assert_return(track, -EINVAL);
127 if (!bus->bus_client)
130 t = new0(sd_bus_track, 1);
135 t->handler = handler;
136 t->userdata = userdata;
137 t->bus = sd_bus_ref(bus);
139 LIST_PREPEND(tracks, bus->tracks, t);
142 bus_track_add_to_queue(t);
148 _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) {
153 assert(track->n_ref > 0);
160 _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) {
164 assert(track->n_ref > 0);
167 if (track->n_ref > 0)
171 LIST_REMOVE(tracks, track->bus->tracks, track);
173 bus_track_remove_from_queue(track);
174 track->names = hashmap_free_with_destructor(track->names, track_item_free);
175 track->bus = sd_bus_unref(track->bus);
177 if (track->destroy_callback)
178 track->destroy_callback(track->userdata);
183 static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
184 sd_bus_track *track = userdata;
185 const char *name, *old, *new;
191 r = sd_bus_message_read(message, "sss", &name, &old, &new);
195 bus_track_remove_name_fully(track, name);
199 _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
200 _cleanup_(track_item_freep) struct track_item *n = NULL;
201 struct track_item *i;
205 assert_return(track, -EINVAL);
206 assert_return(service_name_is_valid(name), -EINVAL);
208 i = hashmap_get(track->names, name);
210 if (track->recursive) {
211 unsigned k = track->n_ref + 1;
213 if (k < track->n_ref) /* Check for overflow */
219 bus_track_remove_from_queue(track);
223 r = hashmap_ensure_allocated(&track->names, &string_hash_ops);
227 n = new0(struct track_item, 1);
230 n->name = strdup(name);
234 /* First, subscribe to this name */
235 match = MATCH_FOR_NAME(name);
237 bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */
239 r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, NULL, track);
241 bus_track_add_to_queue(track);
245 r = hashmap_put(track->names, n->name, n);
247 bus_track_add_to_queue(track);
251 /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */
252 track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */
253 r = sd_bus_get_name_creds(track->bus, name, 0, NULL);
256 hashmap_remove(track->names, name);
257 bus_track_add_to_queue(track);
264 bus_track_remove_from_queue(track);
265 track->modified = true;
270 _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) {
271 struct track_item *i;
273 assert_return(name, -EINVAL);
275 if (!track) /* Treat a NULL track object as an empty track object */
278 if (!track->recursive)
279 return bus_track_remove_name_fully(track, name);
281 i = hashmap_get(track->names, name);
290 return bus_track_remove_name_fully(track, name);
295 _public_ unsigned sd_bus_track_count(sd_bus_track *track) {
297 if (!track) /* Let's consider a NULL object equivalent to an empty object */
300 /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note
301 * that this returns the number of names being watched, and multiple references to the same name are not
304 return hashmap_size(track->names);
307 _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
308 assert_return(name, NULL);
310 if (!track) /* Let's consider a NULL object equivalent to an empty object */
313 return hashmap_get(track->names, (void*) name) ? name : NULL;
316 _public_ const char* sd_bus_track_first(sd_bus_track *track) {
317 const char *n = NULL;
322 track->modified = false;
323 track->iterator = ITERATOR_FIRST;
325 hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
329 _public_ const char* sd_bus_track_next(sd_bus_track *track) {
330 const char *n = NULL;
338 hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
342 _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
345 assert_return(track, -EINVAL);
346 assert_return(m, -EINVAL);
348 if (sd_bus_message_get_bus(m) != track->bus)
351 sender = sd_bus_message_get_sender(m);
355 return sd_bus_track_add_name(track, sender);
358 _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) {
361 assert_return(m, -EINVAL);
363 if (!track) /* Treat a NULL track object as an empty track object */
366 if (sd_bus_message_get_bus(m) != track->bus)
369 sender = sd_bus_message_get_sender(m);
373 return sd_bus_track_remove_name(track, sender);
376 _public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) {
377 assert_return(track, NULL);
382 void bus_track_dispatch(sd_bus_track *track) {
386 assert(track->handler);
388 bus_track_remove_from_queue(track);
390 sd_bus_track_ref(track);
392 r = track->handler(track, track->userdata);
394 log_debug_errno(r, "Failed to process track handler: %m");
396 bus_track_add_to_queue(track);
398 sd_bus_track_unref(track);
401 void bus_track_close(sd_bus_track *track) {
404 /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it
405 * immediately, as we are closing now, but first flush out all names. */
408 return; /* We already closed this one, don't close it again. */
410 /* Remember that this one is closed now */
411 LIST_REMOVE(tracks, track->bus->tracks, track);
412 track->in_list = false;
414 /* If there's no name in this one anyway, we don't have to dispatch */
415 if (hashmap_isempty(track->names))
418 /* Let's flush out all names */
419 hashmap_clear_with_destructor(track->names, track_item_free);
423 bus_track_dispatch(track);
426 _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
427 assert_return(track, NULL);
429 return track->userdata;
432 _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
435 assert_return(track, NULL);
437 ret = track->userdata;
438 track->userdata = userdata;
443 _public_ int sd_bus_track_set_destroy_callback(sd_bus_track *track, sd_bus_destroy_t callback) {
444 assert_return(track, -EINVAL);
446 track->destroy_callback = callback;
450 _public_ int sd_bus_track_get_destroy_callback(sd_bus_track *track, sd_bus_destroy_t *ret) {
451 assert_return(track, -EINVAL);
454 *ret = track->destroy_callback;
456 return !!track->destroy_callback;
459 _public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) {
460 assert_return(track, -EINVAL);
462 if (track->recursive == !!b)
465 if (!hashmap_isempty(track->names))
468 track->recursive = b;
472 _public_ int sd_bus_track_get_recursive(sd_bus_track *track) {
473 assert_return(track, -EINVAL);
475 return track->recursive;
478 _public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) {
481 assert_return(m, -EINVAL);
483 if (!track) /* Let's consider a NULL object equivalent to an empty object */
486 if (sd_bus_message_get_bus(m) != track->bus)
489 sender = sd_bus_message_get_sender(m);
493 return sd_bus_track_count_name(track, sender);
496 _public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) {
497 struct track_item *i;
499 assert_return(service_name_is_valid(name), -EINVAL);
501 if (!track) /* Let's consider a NULL object equivalent to an empty object */
504 i = hashmap_get(track->names, name);