diff options
Diffstat (limited to 'src/libsystemd/sd-bus')
-rw-r--r-- | src/libsystemd/sd-bus/bus-common-errors.c | 1 | ||||
-rw-r--r-- | src/libsystemd/sd-bus/bus-common-errors.h | 1 | ||||
-rw-r--r-- | src/libsystemd/sd-bus/bus-internal.h | 4 | ||||
-rw-r--r-- | src/libsystemd/sd-bus/bus-track.c | 260 | ||||
-rw-r--r-- | src/libsystemd/sd-bus/bus-track.h | 1 | ||||
-rw-r--r-- | src/libsystemd/sd-bus/sd-bus.c | 144 | ||||
-rw-r--r-- | src/libsystemd/sd-bus/test-bus-track.c | 113 |
7 files changed, 449 insertions, 75 deletions
diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 32be3cdc38..a69193aa32 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -45,6 +45,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_SHUTTING_DOWN, ECANCELED), SD_BUS_ERROR_MAP(BUS_ERROR_SCOPE_NOT_RUNNING, EHOSTDOWN), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DYNAMIC_USER, ESRCH), + SD_BUS_ERROR_MAP(BUS_ERROR_NOT_REFERENCED, EUNATCH), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index befb6fbfe0..5df21c8926 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -41,6 +41,7 @@ #define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown" #define BUS_ERROR_SCOPE_NOT_RUNNING "org.freedesktop.systemd1.ScopeNotRunning" #define BUS_ERROR_NO_SUCH_DYNAMIC_USER "org.freedesktop.systemd1.NoSuchDynamicUser" +#define BUS_ERROR_NOT_REFERENCED "org.freedesktop.systemd1.NotReferenced" #define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine" #define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage" diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h index 216d9f62bc..2608f5469c 100644 --- a/src/libsystemd/sd-bus/bus-internal.h +++ b/src/libsystemd/sd-bus/bus-internal.h @@ -209,6 +209,9 @@ struct sd_bus { bool is_system:1; bool is_user:1; bool allow_interactive_authorization:1; + bool exit_on_disconnect:1; + bool exited:1; + bool exit_triggered:1; int use_memfd; @@ -320,6 +323,7 @@ struct sd_bus { sd_bus_track *track_queue; LIST_HEAD(sd_bus_slot, slots); + LIST_HEAD(sd_bus_track, tracks); }; #define BUS_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) diff --git a/src/libsystemd/sd-bus/bus-track.c b/src/libsystemd/sd-bus/bus-track.c index 1f436fe560..00e93a215f 100644 --- a/src/libsystemd/sd-bus/bus-track.c +++ b/src/libsystemd/sd-bus/bus-track.c @@ -24,16 +24,27 @@ #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_queue; - bool modified; + bool in_list:1; /* In bus->tracks? */ + bool in_queue:1; /* In bus->track_queue? */ + bool modified:1; + bool recursive:1; + + LIST_FIELDS(sd_bus_track, tracks); }; #define MATCH_PREFIX \ @@ -56,15 +67,47 @@ struct sd_bus_track { _x; \ }) +static struct track_item* track_item_free(struct track_item *i) { + + if (!i) + return NULL; + + sd_bus_slot_unref(i->slot); + free(i->name); + free(i); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_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; } @@ -79,6 +122,24 @@ static void bus_track_remove_from_queue(sd_bus_track *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, @@ -102,6 +163,9 @@ _public_ int sd_bus_track_new( 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; @@ -121,7 +185,7 @@ _public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) { } _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) { - const char *n; + struct track_item *i; if (!track) return NULL; @@ -133,8 +197,11 @@ _public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) { return NULL; } - while ((n = hashmap_first_key(track->names))) - sd_bus_track_remove_name(track, n); + while ((i = hashmap_steal_first(track->names))) + track_item_free(i); + + if (track->in_list) + LIST_REMOVE(tracks, track->bus->tracks, track); bus_track_remove_from_queue(track); hashmap_free(track->names); @@ -156,49 +223,76 @@ static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus if (r < 0) return 0; - sd_bus_track_remove_name(track, name); + bus_track_remove_name_fully(track, name); return 0; } _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_free_ char *n = NULL; + _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, &string_hash_ops); if (r < 0) return r; - n = strdup(name); + 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(n); - r = sd_bus_add_match(track->bus, &slot, match, on_name_owner_changed, track); - if (r < 0) + match = MATCH_FOR_NAME(name); + + bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */ + + track->n_adding++; /* make sure we aren't dispatched while we synchronously add this match */ + r = sd_bus_add_match(track->bus, &n->slot, match, on_name_owner_changed, track); + track->n_adding--; + if (r < 0) { + bus_track_add_to_queue(track); return r; + } - r = hashmap_put(track->names, n, slot); - if (r == -EEXIST) - return 0; - if (r < 0) + 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. */ - r = sd_bus_get_name_creds(track->bus, n, 0, NULL); + /* 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, n); + hashmap_remove(track->names, name); + bus_track_add_to_queue(track); return r; } + n->n_ref = 1; n = NULL; - slot = NULL; bus_track_remove_from_queue(track); track->modified = true; @@ -207,37 +301,48 @@ _public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { } _public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_free_ char *n = NULL; + struct track_item *i; assert_return(name, -EINVAL); - if (!track) + if (!track) /* Treat a NULL track object as an empty track object */ return 0; - slot = hashmap_remove2(track->names, (char*) name, (void**) &n); - if (!slot) - return 0; + if (!track->recursive) + return bus_track_remove_name_fully(track, name); - if (hashmap_isempty(track->names)) - bus_track_add_to_queue(track); + i = hashmap_get(track->names, name); + if (!i) + return -EUNATCH; + if (i->n_ref <= 0) + return -EUNATCH; - track->modified = true; + 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) + + 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(track, NULL); 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; } @@ -273,6 +378,9 @@ _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { 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; @@ -283,9 +391,14 @@ _public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { _public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) { const char *sender; - assert_return(track, -EINVAL); 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; @@ -303,7 +416,6 @@ void bus_track_dispatch(sd_bus_track *track) { int r; assert(track); - assert(track->in_queue); assert(track->handler); bus_track_remove_from_queue(track); @@ -319,6 +431,34 @@ void bus_track_dispatch(sd_bus_track *track) { sd_bus_track_unref(track); } +void bus_track_close(sd_bus_track *track) { + struct track_item *i; + + 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 */ + while ((i = hashmap_steal_first(track->names))) + track_item_free(i); + + /* Invoke handler */ + if (track->handler) + bus_track_dispatch(track); +} + _public_ void *sd_bus_track_get_userdata(sd_bus_track *track) { assert_return(track, NULL); @@ -335,3 +475,55 @@ _public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) { return ret; } + +_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; +} diff --git a/src/libsystemd/sd-bus/bus-track.h b/src/libsystemd/sd-bus/bus-track.h index 7d93a727d6..26bd05f5c7 100644 --- a/src/libsystemd/sd-bus/bus-track.h +++ b/src/libsystemd/sd-bus/bus-track.h @@ -20,3 +20,4 @@ ***/ void bus_track_dispatch(sd_bus_track *track); +void bus_track_close(sd_bus_track *track); diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index ed5f94e136..d746348544 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -107,6 +107,7 @@ static void bus_free(sd_bus *b) { assert(b); assert(!b->track_queue); + assert(!b->tracks); b->state = BUS_CLOSED; @@ -2640,62 +2641,101 @@ null_message: return r; } -static int process_closing(sd_bus *bus, sd_bus_message **ret) { +static int bus_exit_now(sd_bus *bus) { + assert(bus); + + /* Exit due to close, if this is requested. If this is bus object is attached to an event source, invokes + * sd_event_exit(), otherwise invokes libc exit(). */ + + if (bus->exited) /* did we already exit? */ + return 0; + if (!bus->exit_triggered) /* was the exit condition triggered? */ + return 0; + if (!bus->exit_on_disconnect) /* Shall we actually exit on disconnection? */ + return 0; + + bus->exited = true; /* never exit more than once */ + + log_debug("Bus connection disconnected, exiting."); + + if (bus->event) + return sd_event_exit(bus->event, EXIT_FAILURE); + else + exit(EXIT_FAILURE); + + assert_not_reached("exit() didn't exit?"); +} + +static int process_closing_reply_callback(sd_bus *bus, struct reply_callback *c) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct reply_callback *c; + sd_bus_slot *slot; int r; assert(bus); - assert(bus->state == BUS_CLOSING); + assert(c); - c = ordered_hashmap_first(bus->reply_callbacks); - if (c) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - sd_bus_slot *slot; + r = bus_message_new_synthetic_error( + bus, + c->cookie, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Connection terminated"), + &m); + if (r < 0) + return r; - /* First, fail all outstanding method calls */ - r = bus_message_new_synthetic_error( - bus, - c->cookie, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Connection terminated"), - &m); - if (r < 0) - return r; + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; + if (c->timeout != 0) { + prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); + c->timeout = 0; + } - if (c->timeout != 0) { - prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); - c->timeout = 0; - } + ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); + c->cookie = 0; - ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); - c->cookie = 0; + slot = container_of(c, sd_bus_slot, reply_callback); - slot = container_of(c, sd_bus_slot, reply_callback); + bus->iteration_counter++; - bus->iteration_counter++; + bus->current_message = m; + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->callback; + bus->current_userdata = slot->userdata; + r = c->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = NULL; + bus->current_message = NULL; - bus->current_message = m; - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->callback; - bus->current_userdata = slot->userdata; - r = c->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = NULL; - bus->current_message = NULL; + if (slot->floating) { + bus_slot_disconnect(slot); + sd_bus_slot_unref(slot); + } - if (slot->floating) { - bus_slot_disconnect(slot); - sd_bus_slot_unref(slot); - } + sd_bus_slot_unref(slot); - sd_bus_slot_unref(slot); + return bus_maybe_reply_error(m, r, &error_buffer); +} - return bus_maybe_reply_error(m, r, &error_buffer); +static int process_closing(sd_bus *bus, sd_bus_message **ret) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct reply_callback *c; + int r; + + assert(bus); + assert(bus->state == BUS_CLOSING); + + /* First, fail all outstanding method calls */ + c = ordered_hashmap_first(bus->reply_callbacks); + if (c) + return process_closing_reply_callback(bus, c); + + /* Then, fake-drop all remaining bus tracking references */ + if (bus->tracks) { + bus_track_close(bus->tracks); + return 1; } /* Then, synthesize a Disconnected message */ @@ -2727,6 +2767,10 @@ static int process_closing(sd_bus *bus, sd_bus_message **ret) { if (r != 0) goto finish; + /* Nothing else to do, exit now, if the condition holds */ + bus->exit_triggered = true; + (void) bus_exit_now(bus); + if (ret) { *ret = m; m = NULL; @@ -3789,3 +3833,21 @@ _public_ void sd_bus_default_flush_close(void) { flush_close(default_user_bus); flush_close(default_system_bus); } + +_public_ int sd_bus_set_exit_on_disconnect(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + + /* Turns on exit-on-disconnect, and triggers it immediately if the bus connection was already + * disconnected. Note that this is triggered exclusively on disconnections triggered by the server side, never + * from the client side. */ + bus->exit_on_disconnect = b; + + /* If the exit condition was triggered already, exit immediately. */ + return bus_exit_now(bus); +} + +_public_ int sd_bus_get_exit_on_disconnect(sd_bus *bus) { + assert_return(bus, -EINVAL); + + return bus->exit_on_disconnect; +} diff --git a/src/libsystemd/sd-bus/test-bus-track.c b/src/libsystemd/sd-bus/test-bus-track.c new file mode 100644 index 0000000000..4beb61f05a --- /dev/null +++ b/src/libsystemd/sd-bus/test-bus-track.c @@ -0,0 +1,113 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sd-bus.h> + +#include "macro.h" + +static bool track_cb_called_x = false; +static bool track_cb_called_y = false; + +static int track_cb_x(sd_bus_track *t, void *userdata) { + + log_error("TRACK CB X"); + + assert_se(!track_cb_called_x); + track_cb_called_x = true; + + /* This means b's name disappeared. Let's now disconnect, to make sure the track handling on disconnect works + * as it should. */ + + assert_se(shutdown(sd_bus_get_fd(sd_bus_track_get_bus(t)), SHUT_RDWR) >= 0); + return 1; +} + +static int track_cb_y(sd_bus_track *t, void *userdata) { + int r; + + log_error("TRACK CB Y"); + + assert_se(!track_cb_called_y); + track_cb_called_y = true; + + /* We got disconnected, let's close everything */ + + r = sd_event_exit(sd_bus_get_event(sd_bus_track_get_bus(t)), EXIT_SUCCESS); + assert_se(r >= 0); + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_bus_track_unrefp) sd_bus_track *x = NULL, *y = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *a = NULL, *b = NULL; + const char *unique; + int r; + + r = sd_event_default(&event); + assert_se(r >= 0); + + r = sd_bus_open_system(&a); + if (IN_SET(r, -ECONNREFUSED, -ENOENT)) { + log_info("Failed to connect to bus, skipping tests."); + return EXIT_TEST_SKIP; + } + assert_se(r >= 0); + + r = sd_bus_attach_event(a, event, SD_EVENT_PRIORITY_NORMAL); + assert_se(r >= 0); + + r = sd_bus_open_system(&b); + assert_se(r >= 0); + + r = sd_bus_attach_event(b, event, SD_EVENT_PRIORITY_NORMAL); + assert_se(r >= 0); + + /* Watch b's name from a */ + r = sd_bus_track_new(a, &x, track_cb_x, NULL); + assert_se(r >= 0); + + r = sd_bus_get_unique_name(b, &unique); + assert_se(r >= 0); + + r = sd_bus_track_add_name(x, unique); + assert_se(r >= 0); + + /* Watch's a's own name from a */ + r = sd_bus_track_new(a, &y, track_cb_y, NULL); + assert_se(r >= 0); + + r = sd_bus_get_unique_name(a, &unique); + assert_se(r >= 0); + + r = sd_bus_track_add_name(y, unique); + assert_se(r >= 0); + + /* Now make b's name disappear */ + sd_bus_close(b); + + r = sd_event_loop(event); + assert_se(r >= 0); + + assert_se(track_cb_called_x); + assert_se(track_cb_called_y); + + return 0; +} |