diff options
Diffstat (limited to 'man/sd_bus_service_reconnect.c')
-rw-r--r-- | man/sd_bus_service_reconnect.c | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/man/sd_bus_service_reconnect.c b/man/sd_bus_service_reconnect.c new file mode 100644 index 0000000..fdb18c3 --- /dev/null +++ b/man/sd_bus_service_reconnect.c @@ -0,0 +1,223 @@ +/* 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': + +<?xml version="1.0"?> <!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <policy user="root"> + <allow own="org.freedesktop.ReconnectExample"/> + <allow send_destination="org.freedesktop.ReconnectExample"/> + <allow receive_sender="org.freedesktop.ReconnectExample"/> + </policy> + + <policy context="default"> + <allow send_destination="org.freedesktop.ReconnectExample"/> + <allow receive_sender="org.freedesktop.ReconnectExample"/> + </policy> +</busconfig> + + * + * To get the property via busctl: + * + * $ busctl --user get-property org.freedesktop.ReconnectExample \ + * /org/freedesktop/ReconnectExample \ + * org.freedesktop.ReconnectExample \ + * Example + * s "example" + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <systemd/sd-bus.h> + +#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; +} |