/* SPDX-License-Identifier: MIT-0 */ /* Implements a D-Bus service that automatically reconnects when the system bus is restarted. * * Compile with 'cc sd_bus_service_reconnect.c $(pkg-config --libs --cflags libsystemd)' * * To allow the program to take ownership of the name 'org.freedesktop.ReconnectExample', * add the following as /etc/dbus-1/system.d/org.freedesktop.ReconnectExample.conf * and then reload the broker with 'systemctl reload dbus': * * To get the property via busctl: * * $ busctl --user get-property org.freedesktop.ReconnectExample \ * /org/freedesktop/ReconnectExample \ * org.freedesktop.ReconnectExample \ * Example * s "example" */ #include #include #include #include #define _cleanup_(f) __attribute__((cleanup(f))) #define check(x) ({ \ int _r = (x); \ errno = _r < 0 ? -_r : 0; \ printf(#x ": %m\n"); \ if (_r < 0) \ return EXIT_FAILURE; \ }) typedef struct object { const char *example; sd_bus **bus; sd_event **event; } object; static int property_get( sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { object *o = userdata; if (strcmp(property, "Example") == 0) return sd_bus_message_append(reply, "s", o->example); return sd_bus_error_setf(error, SD_BUS_ERROR_UNKNOWN_PROPERTY, "Unknown property '%s'", property); } /* https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html */ static const sd_bus_vtable vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY( "Example", "s", property_get, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END }; static int setup(object *o); static int on_disconnect(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { check(setup((object *)userdata)); return 0; } /* Ensure the event loop exits with a clear error if acquiring the well-known service name fails */ static int request_name_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { if (!sd_bus_message_is_method_error(m, NULL)) return 1; const sd_bus_error *error = sd_bus_message_get_error(m); if (sd_bus_error_has_names(error, SD_BUS_ERROR_TIMEOUT, SD_BUS_ERROR_NO_REPLY)) return 1; /* The bus is not available, try again later */ printf("Failed to request name: %s\n", error->message); object *o = userdata; check(sd_event_exit(*o->event, -sd_bus_error_get_errno(error))); return 1; } static int setup(object *o) { /* If we are reconnecting, then the bus object needs to be closed, detached from * the event loop and recreated. * https://www.freedesktop.org/software/systemd/man/sd_bus_detach_event.html * https://www.freedesktop.org/software/systemd/man/sd_bus_close_unref.html */ if (*o->bus) { check(sd_bus_detach_event(*o->bus)); *o->bus = sd_bus_close_unref(*o->bus); } /* Set up a new bus object for the system bus, configure it to wait for D-Bus to be available * instead of failing if it is not, and start it. All the following operations are asyncronous * and will not block waiting for D-Bus to be available. * https://www.freedesktop.org/software/systemd/man/sd_bus_new.html * https://www.freedesktop.org/software/systemd/man/sd_bus_set_address.html * https://www.freedesktop.org/software/systemd/man/sd_bus_set_bus_client.html * https://www.freedesktop.org/software/systemd/man/sd_bus_negotiate_creds.html * https://www.freedesktop.org/software/systemd/man/sd_bus_set_watch_bind.html * https://www.freedesktop.org/software/systemd/man/sd_bus_set_connected_signal.html * https://www.freedesktop.org/software/systemd/man/sd_bus_start.html */ check(sd_bus_new(o->bus)); check(sd_bus_set_address(*o->bus, "unix:path=/run/dbus/system_bus_socket")); check(sd_bus_set_bus_client(*o->bus, 1)); check(sd_bus_negotiate_creds(*o->bus, 1, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS)); check(sd_bus_set_watch_bind(*o->bus, 1)); check(sd_bus_start(*o->bus)); /* Publish an interface on the bus, specifying our well-known object access * path and public interface name. * https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html * https://dbus.freedesktop.org/doc/dbus-tutorial.html */ check(sd_bus_add_object_vtable(*o->bus, NULL, "/org/freedesktop/ReconnectExample", "org.freedesktop.ReconnectExample", vtable, o)); /* By default the service is only assigned an ephemeral name. Also add a well-known * one, so that clients know whom to call. This needs to be asynchronous, as * D-Bus might not be yet available. The callback will check whether the error is * expected or not, in case it fails. * https://www.freedesktop.org/software/systemd/man/sd_bus_request_name.html */ check(sd_bus_request_name_async(*o->bus, NULL, "org.freedesktop.ReconnectExample", 0, request_name_callback, o)); /* When D-Bus is disconnected this callback will be invoked, which will * set up the connection again. This needs to be asynchronous, as D-Bus might not * yet be available. * https://www.freedesktop.org/software/systemd/man/sd_bus_match_signal_async.html */ check(sd_bus_match_signal_async(*o->bus, NULL, "org.freedesktop.DBus.Local", NULL, "org.freedesktop.DBus.Local", "Disconnected", on_disconnect, NULL, o)); /* Attach the bus object to the event loop so that calls and signals are processed. * https://www.freedesktop.org/software/systemd/man/sd_bus_attach_event.html */ check(sd_bus_attach_event(*o->bus, *o->event, 0)); return 0; } int main(int argc, char **argv) { /* The bus should be relinquished before the program terminates. The cleanup * attribute allows us to do it nicely and cleanly whenever we exit the * block. */ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; object o = { .example = "example", .bus = &bus, .event = &event, }; /* Create an event loop data structure, with default parameters. * https://www.freedesktop.org/software/systemd/man/sd_event_default.html */ check(sd_event_default(&event)); /* By default the event loop will terminate when all sources have disappeared, so * we have to keep it 'occupied'. Register signal handling to do so. * https://www.freedesktop.org/software/systemd/man/sd_event_add_signal.html */ check(sd_event_add_signal(event, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK, NULL, NULL)); check(sd_event_add_signal(event, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK, NULL, NULL)); check(setup(&o)); /* Enter the main loop, it will exit only on sigint/sigterm. * https://www.freedesktop.org/software/systemd/man/sd_event_loop.html */ check(sd_event_loop(event)); /* https://www.freedesktop.org/software/systemd/man/sd_bus_release_name.html */ check(sd_bus_release_name(bus, "org.freedesktop.ReconnectExample")); return 0; }