]>
Commit | Line | Data |
---|---|---|
34bbda18 LB |
1 | /* SPDX-License-Identifier: MIT-0 */ |
2 | ||
3 | /* Implements a D-Bus service that automatically reconnects when the system bus is restarted. | |
4 | * | |
5 | * Compile with 'cc sd_bus_service_reconnect.c $(pkg-config --libs --cflags libsystemd)' | |
6 | * | |
7 | * To allow the program to take ownership of the name 'org.freedesktop.ReconnectExample', | |
e07e7017 LB |
8 | * add the following as /etc/dbus-1/system.d/org.freedesktop.ReconnectExample.conf |
9 | * and then reload the broker with 'systemctl reload dbus': | |
34bbda18 LB |
10 | |
11 | <?xml version="1.0"?> <!--*-nxml-*--> | |
12 | <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" | |
13 | "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> | |
14 | <busconfig> | |
15 | <policy user="root"> | |
16 | <allow own="org.freedesktop.ReconnectExample"/> | |
17 | <allow send_destination="org.freedesktop.ReconnectExample"/> | |
18 | <allow receive_sender="org.freedesktop.ReconnectExample"/> | |
19 | </policy> | |
20 | ||
21 | <policy context="default"> | |
22 | <allow send_destination="org.freedesktop.ReconnectExample"/> | |
23 | <allow receive_sender="org.freedesktop.ReconnectExample"/> | |
24 | </policy> | |
25 | </busconfig> | |
26 | ||
27 | * | |
28 | * To get the property via busctl: | |
29 | * | |
30 | * $ busctl --user get-property org.freedesktop.ReconnectExample \ | |
31 | * /org/freedesktop/ReconnectExample \ | |
32 | * org.freedesktop.ReconnectExample \ | |
33 | * Example | |
34 | * s "example" | |
35 | */ | |
36 | ||
37 | #include <errno.h> | |
38 | #include <stdio.h> | |
39 | #include <stdlib.h> | |
40 | #include <systemd/sd-bus.h> | |
41 | ||
42 | #define _cleanup_(f) __attribute__((cleanup(f))) | |
43 | ||
44 | #define check(x) ({ \ | |
45 | int _r = (x); \ | |
46 | errno = _r < 0 ? -_r : 0; \ | |
47 | printf(#x ": %m\n"); \ | |
48 | if (_r < 0) \ | |
49 | return EXIT_FAILURE; \ | |
50 | }) | |
51 | ||
52 | typedef struct object { | |
53 | const char *example; | |
54 | sd_bus **bus; | |
55 | sd_event **event; | |
56 | } object; | |
57 | ||
58 | static int property_get( | |
59 | sd_bus *bus, | |
60 | const char *path, | |
61 | const char *interface, | |
62 | const char *property, | |
63 | sd_bus_message *reply, | |
64 | void *userdata, | |
65 | sd_bus_error *error) { | |
66 | ||
67 | object *o = userdata; | |
68 | ||
69 | if (strcmp(property, "Example") == 0) | |
70 | return sd_bus_message_append(reply, "s", o->example); | |
71 | ||
72 | return sd_bus_error_setf(error, | |
73 | SD_BUS_ERROR_UNKNOWN_PROPERTY, | |
74 | "Unknown property '%s'", | |
75 | property); | |
76 | } | |
77 | ||
78 | /* https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html */ | |
79 | static const sd_bus_vtable vtable[] = { | |
80 | SD_BUS_VTABLE_START(0), | |
81 | SD_BUS_PROPERTY( | |
82 | "Example", "s", | |
83 | property_get, | |
84 | 0, | |
85 | SD_BUS_VTABLE_PROPERTY_CONST), | |
86 | SD_BUS_VTABLE_END | |
87 | }; | |
88 | ||
89 | static int setup(object *o); | |
90 | ||
91 | static int on_disconnect(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { | |
92 | check(setup((object *)userdata)); | |
93 | return 0; | |
94 | } | |
95 | ||
e07e7017 LB |
96 | /* Ensure the event loop exits with a clear error if acquiring the well-known service name fails */ |
97 | static int request_name_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { | |
98 | if (!sd_bus_message_is_method_error(m, NULL)) | |
99 | return 1; | |
100 | ||
101 | const sd_bus_error *error = sd_bus_message_get_error(m); | |
102 | ||
103 | if (sd_bus_error_has_names(error, SD_BUS_ERROR_TIMEOUT, SD_BUS_ERROR_NO_REPLY)) | |
104 | return 1; /* The bus is not available, try again later */ | |
105 | ||
106 | printf("Failed to request name: %s\n", error->message); | |
107 | object *o = userdata; | |
108 | check(sd_event_exit(*o->event, -sd_bus_error_get_errno(error))); | |
109 | ||
110 | return 1; | |
111 | } | |
112 | ||
34bbda18 LB |
113 | static int setup(object *o) { |
114 | /* If we are reconnecting, then the bus object needs to be closed, detached from | |
115 | * the event loop and recreated. | |
116 | * https://www.freedesktop.org/software/systemd/man/sd_bus_detach_event.html | |
117 | * https://www.freedesktop.org/software/systemd/man/sd_bus_close_unref.html | |
118 | */ | |
119 | if (*o->bus) { | |
120 | check(sd_bus_detach_event(*o->bus)); | |
121 | *o->bus = sd_bus_close_unref(*o->bus); | |
122 | } | |
123 | ||
124 | /* Set up a new bus object for the system bus, configure it to wait for D-Bus to be available | |
49d6e3c8 | 125 | * instead of failing if it is not, and start it. All the following operations are asynchronous |
34bbda18 LB |
126 | * and will not block waiting for D-Bus to be available. |
127 | * https://www.freedesktop.org/software/systemd/man/sd_bus_new.html | |
128 | * https://www.freedesktop.org/software/systemd/man/sd_bus_set_address.html | |
129 | * https://www.freedesktop.org/software/systemd/man/sd_bus_set_bus_client.html | |
130 | * https://www.freedesktop.org/software/systemd/man/sd_bus_negotiate_creds.html | |
131 | * https://www.freedesktop.org/software/systemd/man/sd_bus_set_watch_bind.html | |
132 | * https://www.freedesktop.org/software/systemd/man/sd_bus_set_connected_signal.html | |
133 | * https://www.freedesktop.org/software/systemd/man/sd_bus_start.html | |
134 | */ | |
135 | check(sd_bus_new(o->bus)); | |
136 | check(sd_bus_set_address(*o->bus, "unix:path=/run/dbus/system_bus_socket")); | |
137 | check(sd_bus_set_bus_client(*o->bus, 1)); | |
138 | check(sd_bus_negotiate_creds(*o->bus, 1, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS)); | |
139 | check(sd_bus_set_watch_bind(*o->bus, 1)); | |
34bbda18 LB |
140 | check(sd_bus_start(*o->bus)); |
141 | ||
142 | /* Publish an interface on the bus, specifying our well-known object access | |
143 | * path and public interface name. | |
144 | * https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html | |
145 | * https://dbus.freedesktop.org/doc/dbus-tutorial.html | |
146 | */ | |
147 | check(sd_bus_add_object_vtable(*o->bus, | |
148 | NULL, | |
149 | "/org/freedesktop/ReconnectExample", | |
150 | "org.freedesktop.ReconnectExample", | |
151 | vtable, | |
152 | o)); | |
153 | /* By default the service is only assigned an ephemeral name. Also add a well-known | |
154 | * one, so that clients know whom to call. This needs to be asynchronous, as | |
e07e7017 LB |
155 | * D-Bus might not be yet available. The callback will check whether the error is |
156 | * expected or not, in case it fails. | |
34bbda18 LB |
157 | * https://www.freedesktop.org/software/systemd/man/sd_bus_request_name.html |
158 | */ | |
159 | check(sd_bus_request_name_async(*o->bus, | |
160 | NULL, | |
161 | "org.freedesktop.ReconnectExample", | |
162 | 0, | |
e07e7017 LB |
163 | request_name_callback, |
164 | o)); | |
34bbda18 LB |
165 | /* When D-Bus is disconnected this callback will be invoked, which will |
166 | * set up the connection again. This needs to be asynchronous, as D-Bus might not | |
167 | * yet be available. | |
168 | * https://www.freedesktop.org/software/systemd/man/sd_bus_match_signal_async.html | |
169 | */ | |
170 | check(sd_bus_match_signal_async(*o->bus, | |
171 | NULL, | |
172 | "org.freedesktop.DBus.Local", | |
173 | NULL, | |
174 | "org.freedesktop.DBus.Local", | |
175 | "Disconnected", | |
176 | on_disconnect, | |
177 | NULL, | |
178 | o)); | |
179 | /* Attach the bus object to the event loop so that calls and signals are processed. | |
180 | * https://www.freedesktop.org/software/systemd/man/sd_bus_attach_event.html | |
181 | */ | |
182 | check(sd_bus_attach_event(*o->bus, *o->event, 0)); | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | int main(int argc, char **argv) { | |
188 | /* The bus should be relinquished before the program terminates. The cleanup | |
189 | * attribute allows us to do it nicely and cleanly whenever we exit the | |
190 | * block. | |
191 | */ | |
192 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
193 | _cleanup_(sd_event_unrefp) sd_event *event = NULL; | |
194 | object o = { | |
195 | .example = "example", | |
196 | .bus = &bus, | |
197 | .event = &event, | |
198 | }; | |
199 | ||
200 | /* Create an event loop data structure, with default parameters. | |
201 | * https://www.freedesktop.org/software/systemd/man/sd_event_default.html | |
202 | */ | |
203 | check(sd_event_default(&event)); | |
204 | ||
205 | /* By default the event loop will terminate when all sources have disappeared, so | |
206 | * we have to keep it 'occupied'. Register signal handling to do so. | |
207 | * https://www.freedesktop.org/software/systemd/man/sd_event_add_signal.html | |
208 | */ | |
209 | check(sd_event_add_signal(event, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK, NULL, NULL)); | |
210 | check(sd_event_add_signal(event, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK, NULL, NULL)); | |
211 | ||
212 | check(setup(&o)); | |
213 | ||
214 | /* Enter the main loop, it will exit only on sigint/sigterm. | |
215 | * https://www.freedesktop.org/software/systemd/man/sd_event_loop.html | |
216 | */ | |
217 | check(sd_event_loop(event)); | |
218 | ||
219 | /* https://www.freedesktop.org/software/systemd/man/sd_bus_release_name.html */ | |
220 | check(sd_bus_release_name(bus, "org.freedesktop.ReconnectExample")); | |
221 | ||
222 | return 0; | |
223 | } |