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