/* SPDX-License-Identifier: LGPL-2.1+ */ #include "sd-bus.h" #include "alloc-util.h" #include "bus-internal.h" #include "bus-track.h" #include "bus-util.h" struct track_item { unsigned n_ref; char *name; sd_bus_slot *slot; }; struct sd_bus_track { unsigned n_ref; unsigned n_adding; /* are we in the process of adding a new name? */ sd_bus *bus; sd_bus_track_handler_t handler; void *userdata; Hashmap *names; LIST_FIELDS(sd_bus_track, queue); Iterator iterator; bool in_list:1; /* In bus->tracks? */ bool in_queue:1; /* In bus->track_queue? */ bool modified:1; bool recursive:1; sd_bus_destroy_t destroy_callback; LIST_FIELDS(sd_bus_track, tracks); }; #define MATCH_FOR_NAME(name) \ strjoina("type='signal'," \ "sender='org.freedesktop.DBus'," \ "path='/org/freedesktop/DBus'," \ "interface='org.freedesktop.DBus'," \ "member='NameOwnerChanged'," \ "arg0='", name, "'") static struct track_item* track_item_free(struct track_item *i) { if (!i) return NULL; sd_bus_slot_unref(i->slot); free(i->name); return mfree(i); } DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_free); DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(track_item_hash_ops, char, string_hash_func, string_compare_func, struct track_item, track_item_free); static void bus_track_add_to_queue(sd_bus_track *track) { assert(track); /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of * conditions. */ /* Already in the queue? */ if (track->in_queue) return; /* if we are currently in the process of adding a new name, then let's not enqueue this just yet, let's wait * until the addition is complete. */ if (track->n_adding > 0) return; /* still referenced? */ if (hashmap_size(track->names) > 0) return; /* Nothing to call? */ if (!track->handler) return; /* Already closed? */ if (!track->in_list) return; LIST_PREPEND(queue, track->bus->track_queue, track); track->in_queue = true; } static void bus_track_remove_from_queue(sd_bus_track *track) { assert(track); if (!track->in_queue) return; LIST_REMOVE(queue, track->bus->track_queue, track); track->in_queue = false; } static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) { struct track_item *i; assert(track); assert(name); i = hashmap_remove(track->names, name); if (!i) return 0; track_item_free(i); bus_track_add_to_queue(track); track->modified = true; return 1; } _public_ int sd_bus_track_new( sd_bus *bus, sd_bus_track **track, sd_bus_track_handler_t handler, void *userdata) { sd_bus_track *t; assert_return(bus, -EINVAL); assert_return(bus = bus_resolve(bus), -ENOPKG); assert_return(track, -EINVAL); if (!bus->bus_client) return -EINVAL; t = new0(sd_bus_track, 1); if (!t) return -ENOMEM; t->n_ref = 1; t->handler = handler; t->userdata = userdata; t->bus = sd_bus_ref(bus); LIST_PREPEND(tracks, bus->tracks, t); t->in_list = true; bus_track_add_to_queue(t); *track = t; return 0; } static sd_bus_track *track_free(sd_bus_track *track) { assert(track); if (track->in_list) LIST_REMOVE(tracks, track->bus->tracks, track); bus_track_remove_from_queue(track); track->names = hashmap_free(track->names); track->bus = sd_bus_unref(track->bus); if (track->destroy_callback) track->destroy_callback(track->userdata); return mfree(track); } DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_bus_track, sd_bus_track, track_free); static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { sd_bus_track *track = userdata; const char *name, *old, *new; int r; assert(message); assert(track); r = sd_bus_message_read(message, "sss", &name, &old, &new); if (r < 0) return 0; bus_track_remove_name_fully(track, name); return 0; } _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { _cleanup_(track_item_freep) struct track_item *n = NULL; struct track_item *i; const char *match; int r; assert_return(track, -EINVAL); assert_return(service_name_is_valid(name), -EINVAL); i = hashmap_get(track->names, name); if (i) { if (track->recursive) { unsigned k = track->n_ref + 1; if (k < track->n_ref) /* Check for overflow */ return -EOVERFLOW; track->n_ref = k; } bus_track_remove_from_queue(track); return 0; } r = hashmap_ensure_allocated(&track->names, &track_item_hash_ops); if (r < 0) return r; n = new0(struct track_item, 1); if (!n) return -ENOMEM; n->name = strdup(name); if (!n->name) return -ENOMEM; /* First, subscribe to this name */ match = MATCH_FOR_NAME(name); bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */ r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, NULL, track); if (r < 0) { bus_track_add_to_queue(track); return r; } r = hashmap_put(track->names, n->name, n); if (r < 0) { bus_track_add_to_queue(track); return r; } /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */ track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */ r = sd_bus_get_name_creds(track->bus, name, 0, NULL); track->n_adding--; if (r < 0) { hashmap_remove(track->names, name); bus_track_add_to_queue(track); return r; } n->n_ref = 1; n = NULL; bus_track_remove_from_queue(track); track->modified = true; return 1; } _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) { struct track_item *i; assert_return(name, -EINVAL); if (!track) /* Treat a NULL track object as an empty track object */ return 0; if (!track->recursive) return bus_track_remove_name_fully(track, name); i = hashmap_get(track->names, name); if (!i) return -EUNATCH; if (i->n_ref <= 0) return -EUNATCH; i->n_ref--; if (i->n_ref <= 0) return bus_track_remove_name_fully(track, name); return 1; } _public_ unsigned sd_bus_track_count(sd_bus_track *track) { if (!track) /* Let's consider a NULL object equivalent to an empty object */ return 0; /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note * that this returns the number of names being watched, and multiple references to the same name are not * counted. */ return hashmap_size(track->names); } _public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) { assert_return(name, NULL); if (!track) /* Let's consider a NULL object equivalent to an empty object */ return NULL; return hashmap_get(track->names, (void*) name) ? name : NULL; } _public_ const char* sd_bus_track_first(sd_bus_track *track) { const char *n = NULL; if (!track) return NULL; track->modified = false; track->iterator = ITERATOR_FIRST; (void) hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); return n; } _public_ const char* sd_bus_track_next(sd_bus_track *track) { const char *n = NULL; if (!track) return NULL; if (track->modified) return NULL; (void) hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); return n; } _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { const char *sender; assert_return(track, -EINVAL); assert_return(m, -EINVAL); if (sd_bus_message_get_bus(m) != track->bus) return -EINVAL; sender = sd_bus_message_get_sender(m); if (!sender) return -EINVAL; return sd_bus_track_add_name(track, sender); } _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) { const char *sender; assert_return(m, -EINVAL); if (!track) /* Treat a NULL track object as an empty track object */ return 0; if (sd_bus_message_get_bus(m) != track->bus) return -EINVAL; sender = sd_bus_message_get_sender(m); if (!sender) return -EINVAL; return sd_bus_track_remove_name(track, sender); } _public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) { assert_return(track, NULL); return track->bus; } void bus_track_dispatch(sd_bus_track *track) { int r; assert(track); assert(track->handler); bus_track_remove_from_queue(track); sd_bus_track_ref(track); r = track->handler(track, track->userdata); if (r < 0) log_debug_errno(r, "Failed to process track handler: %m"); else if (r == 0) bus_track_add_to_queue(track); sd_bus_track_unref(track); } void bus_track_close(sd_bus_track *track) { assert(track); /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it * immediately, as we are closing now, but first flush out all names. */ if (!track->in_list) return; /* We already closed this one, don't close it again. */ /* Remember that this one is closed now */ LIST_REMOVE(tracks, track->bus->tracks, track); track->in_list = false; /* If there's no name in this one anyway, we don't have to dispatch */ if (hashmap_isempty(track->names)) return; /* Let's flush out all names */ hashmap_clear(track->names); /* Invoke handler */ if (track->handler) bus_track_dispatch(track); } _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) { assert_return(track, NULL); return track->userdata; } _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) { void *ret; assert_return(track, NULL); ret = track->userdata; track->userdata = userdata; return ret; } _public_ int sd_bus_track_set_destroy_callback(sd_bus_track *track, sd_bus_destroy_t callback) { assert_return(track, -EINVAL); track->destroy_callback = callback; return 0; } _public_ int sd_bus_track_get_destroy_callback(sd_bus_track *track, sd_bus_destroy_t *ret) { assert_return(track, -EINVAL); if (ret) *ret = track->destroy_callback; return !!track->destroy_callback; } _public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) { assert_return(track, -EINVAL); if (track->recursive == !!b) return 0; if (!hashmap_isempty(track->names)) return -EBUSY; track->recursive = b; return 0; } _public_ int sd_bus_track_get_recursive(sd_bus_track *track) { assert_return(track, -EINVAL); return track->recursive; } _public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) { const char *sender; assert_return(m, -EINVAL); if (!track) /* Let's consider a NULL object equivalent to an empty object */ return 0; if (sd_bus_message_get_bus(m) != track->bus) return -EINVAL; sender = sd_bus_message_get_sender(m); if (!sender) return -EINVAL; return sd_bus_track_count_name(track, sender); } _public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) { struct track_item *i; assert_return(service_name_is_valid(name), -EINVAL); if (!track) /* Let's consider a NULL object equivalent to an empty object */ return 0; i = hashmap_get(track->names, name); if (!i) return 0; return i->n_ref; }