Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include "bus-util.h"
4 : #include "bus-wait-for-units.h"
5 : #include "hashmap.h"
6 : #include "string-util.h"
7 : #include "strv.h"
8 : #include "unit-def.h"
9 :
10 : typedef struct WaitForItem {
11 : BusWaitForUnits *parent;
12 :
13 : BusWaitForUnitsFlags flags;
14 :
15 : char *bus_path;
16 :
17 : sd_bus_slot *slot_get_all;
18 : sd_bus_slot *slot_properties_changed;
19 :
20 : bus_wait_for_units_unit_callback unit_callback;
21 : void *userdata;
22 :
23 : char *active_state;
24 : uint32_t job_id;
25 : char *clean_result;
26 : } WaitForItem;
27 :
28 : typedef struct BusWaitForUnits {
29 : sd_bus *bus;
30 : sd_bus_slot *slot_disconnected;
31 :
32 : Hashmap *items;
33 :
34 : bus_wait_for_units_ready_callback ready_callback;
35 : void *userdata;
36 :
37 : WaitForItem *current;
38 :
39 : BusWaitForUnitsState state;
40 : bool has_failed:1;
41 : } BusWaitForUnits;
42 :
43 0 : static WaitForItem *wait_for_item_free(WaitForItem *item) {
44 : int r;
45 :
46 0 : if (!item)
47 0 : return NULL;
48 :
49 0 : if (item->parent) {
50 0 : if (FLAGS_SET(item->flags, BUS_WAIT_REFFED) && item->bus_path && item->parent->bus) {
51 0 : r = sd_bus_call_method_async(
52 0 : item->parent->bus,
53 : NULL,
54 : "org.freedesktop.systemd1",
55 0 : item->bus_path,
56 : "org.freedesktop.systemd1.Unit",
57 : "Unref",
58 : NULL,
59 : NULL,
60 : NULL);
61 0 : if (r < 0)
62 0 : log_debug_errno(r, "Failed to drop reference to unit %s, ignoring: %m", item->bus_path);
63 : }
64 :
65 0 : assert_se(hashmap_remove(item->parent->items, item->bus_path) == item);
66 :
67 0 : if (item->parent->current == item)
68 0 : item->parent->current = NULL;
69 : }
70 :
71 0 : sd_bus_slot_unref(item->slot_properties_changed);
72 0 : sd_bus_slot_unref(item->slot_get_all);
73 :
74 0 : free(item->bus_path);
75 0 : free(item->active_state);
76 0 : free(item->clean_result);
77 :
78 0 : return mfree(item);
79 : }
80 :
81 0 : DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free);
82 :
83 0 : static void bus_wait_for_units_clear(BusWaitForUnits *d) {
84 : WaitForItem *item;
85 :
86 0 : assert(d);
87 :
88 0 : d->slot_disconnected = sd_bus_slot_unref(d->slot_disconnected);
89 0 : d->bus = sd_bus_unref(d->bus);
90 :
91 0 : while ((item = hashmap_first(d->items))) {
92 0 : d->current = item;
93 :
94 0 : item->unit_callback(d, item->bus_path, false, item->userdata);
95 0 : wait_for_item_free(item);
96 : }
97 :
98 0 : d->items = hashmap_free(d->items);
99 0 : }
100 :
101 0 : static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
102 0 : BusWaitForUnits *d = userdata;
103 :
104 0 : assert(m);
105 0 : assert(d);
106 :
107 0 : log_error("Warning! D-Bus connection terminated.");
108 :
109 0 : bus_wait_for_units_clear(d);
110 :
111 0 : if (d->ready_callback)
112 0 : d->ready_callback(d, false, d->userdata);
113 : else /* If no ready callback is specified close the connection so that the event loop exits */
114 0 : sd_bus_close(sd_bus_message_get_bus(m));
115 :
116 0 : return 0;
117 : }
118 :
119 0 : int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret) {
120 0 : _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *d = NULL;
121 : int r;
122 :
123 0 : assert(bus);
124 0 : assert(ret);
125 :
126 0 : d = new(BusWaitForUnits, 1);
127 0 : if (!d)
128 0 : return -ENOMEM;
129 :
130 0 : *d = (BusWaitForUnits) {
131 : .state = BUS_WAIT_SUCCESS,
132 0 : .bus = sd_bus_ref(bus),
133 : };
134 :
135 0 : r = sd_bus_match_signal_async(
136 : bus,
137 0 : &d->slot_disconnected,
138 : "org.freedesktop.DBus.Local",
139 : NULL,
140 : "org.freedesktop.DBus.Local",
141 : "Disconnected",
142 : match_disconnected, NULL, d);
143 0 : if (r < 0)
144 0 : return r;
145 :
146 0 : *ret = TAKE_PTR(d);
147 0 : return 0;
148 : }
149 :
150 0 : BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d) {
151 0 : if (!d)
152 0 : return NULL;
153 :
154 0 : bus_wait_for_units_clear(d);
155 0 : sd_bus_slot_unref(d->slot_disconnected);
156 0 : sd_bus_unref(d->bus);
157 :
158 0 : return mfree(d);
159 : }
160 :
161 0 : static bool bus_wait_for_units_is_ready(BusWaitForUnits *d) {
162 0 : assert(d);
163 :
164 0 : if (!d->bus) /* Disconnected? */
165 0 : return true;
166 :
167 0 : return hashmap_isempty(d->items);
168 : }
169 :
170 0 : void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) {
171 0 : assert(d);
172 :
173 0 : d->ready_callback = callback;
174 0 : d->userdata = userdata;
175 0 : }
176 :
177 0 : static void bus_wait_for_units_check_ready(BusWaitForUnits *d) {
178 0 : assert(d);
179 :
180 0 : if (!bus_wait_for_units_is_ready(d))
181 0 : return;
182 :
183 0 : d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS;
184 :
185 0 : if (d->ready_callback)
186 0 : d->ready_callback(d, d->state, d->userdata);
187 : }
188 :
189 0 : static void wait_for_item_check_ready(WaitForItem *item) {
190 : BusWaitForUnits *d;
191 :
192 0 : assert(item);
193 0 : assert_se(d = item->parent);
194 :
195 0 : if (FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END)) {
196 :
197 0 : if (item->clean_result && !streq(item->clean_result, "success"))
198 0 : d->has_failed = true;
199 :
200 0 : if (!item->active_state || streq(item->active_state, "maintenance"))
201 0 : return;
202 : }
203 :
204 0 : if (FLAGS_SET(item->flags, BUS_WAIT_NO_JOB) && item->job_id != 0)
205 0 : return;
206 :
207 0 : if (FLAGS_SET(item->flags, BUS_WAIT_FOR_INACTIVE)) {
208 :
209 0 : if (streq_ptr(item->active_state, "failed"))
210 0 : d->has_failed = true;
211 0 : else if (!streq_ptr(item->active_state, "inactive"))
212 0 : return;
213 : }
214 :
215 0 : if (item->unit_callback) {
216 0 : d->current = item;
217 0 : item->unit_callback(d, item->bus_path, true, item->userdata);
218 : }
219 :
220 0 : wait_for_item_free(item);
221 :
222 0 : bus_wait_for_units_check_ready(d);
223 : }
224 :
225 0 : static int property_map_job(
226 : sd_bus *bus,
227 : const char *member,
228 : sd_bus_message *m,
229 : sd_bus_error *error,
230 : void *userdata) {
231 :
232 0 : WaitForItem *item = userdata;
233 : const char *path;
234 : uint32_t id;
235 : int r;
236 :
237 0 : assert(item);
238 :
239 0 : r = sd_bus_message_read(m, "(uo)", &id, &path);
240 0 : if (r < 0)
241 0 : return r;
242 :
243 0 : item->job_id = id;
244 0 : return 0;
245 : }
246 :
247 0 : static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) {
248 :
249 : static const struct bus_properties_map map[] = {
250 : { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) },
251 : { "Job", "(uo)", property_map_job, 0 },
252 : { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) },
253 : {}
254 : };
255 :
256 : int r;
257 :
258 0 : assert(item);
259 0 : assert(m);
260 :
261 0 : r = bus_message_map_all_properties(m, map, BUS_MAP_STRDUP, NULL, item);
262 0 : if (r < 0)
263 0 : return r;
264 :
265 0 : wait_for_item_check_ready(item);
266 0 : return 0;
267 : }
268 :
269 0 : static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
270 0 : WaitForItem *item = userdata;
271 : const char *interface;
272 : int r;
273 :
274 0 : assert(item);
275 :
276 0 : r = sd_bus_message_read(m, "s", &interface);
277 0 : if (r < 0) {
278 0 : log_debug_errno(r, "Failed to parse PropertiesChanged signal: %m");
279 0 : return 0;
280 : }
281 :
282 0 : if (!streq(interface, "org.freedesktop.systemd1.Unit"))
283 0 : return 0;
284 :
285 0 : r = wait_for_item_parse_properties(item, m);
286 0 : if (r < 0)
287 0 : log_debug_errno(r, "Failed to process PropertiesChanged signal: %m");
288 :
289 0 : return 0;
290 : }
291 :
292 0 : static int on_get_all_properties(sd_bus_message *m, void *userdata, sd_bus_error *error) {
293 0 : WaitForItem *item = userdata;
294 : int r;
295 :
296 0 : assert(item);
297 :
298 0 : if (sd_bus_error_is_set(error)) {
299 0 : BusWaitForUnits *d = item->parent;
300 :
301 0 : d->has_failed = true;
302 :
303 0 : log_debug_errno(sd_bus_error_get_errno(error), "GetAll() failed for %s: %s",
304 : item->bus_path, error->message);
305 :
306 0 : d->current = item;
307 0 : item->unit_callback(d, item->bus_path, false, item->userdata);
308 0 : wait_for_item_free(item);
309 :
310 0 : bus_wait_for_units_check_ready(d);
311 0 : return 0;
312 : }
313 :
314 0 : r = wait_for_item_parse_properties(item, m);
315 0 : if (r < 0)
316 0 : log_debug_errno(r, "Failed to process GetAll method reply: %m");
317 :
318 0 : return 0;
319 : }
320 :
321 0 : int bus_wait_for_units_add_unit(
322 : BusWaitForUnits *d,
323 : const char *unit,
324 : BusWaitForUnitsFlags flags,
325 : bus_wait_for_units_unit_callback callback,
326 : void *userdata) {
327 :
328 0 : _cleanup_(wait_for_item_freep) WaitForItem *item = NULL;
329 : int r;
330 :
331 0 : assert(d);
332 0 : assert(unit);
333 :
334 0 : assert(flags != 0);
335 :
336 0 : r = hashmap_ensure_allocated(&d->items, &string_hash_ops);
337 0 : if (r < 0)
338 0 : return r;
339 :
340 0 : item = new(WaitForItem, 1);
341 0 : if (!item)
342 0 : return -ENOMEM;
343 :
344 0 : *item = (WaitForItem) {
345 : .flags = flags,
346 0 : .bus_path = unit_dbus_path_from_name(unit),
347 : .unit_callback = callback,
348 : .userdata = userdata,
349 : .job_id = UINT32_MAX,
350 : };
351 :
352 0 : if (!item->bus_path)
353 0 : return -ENOMEM;
354 :
355 0 : if (!FLAGS_SET(item->flags, BUS_WAIT_REFFED)) {
356 0 : r = sd_bus_call_method_async(
357 : d->bus,
358 : NULL,
359 : "org.freedesktop.systemd1",
360 0 : item->bus_path,
361 : "org.freedesktop.systemd1.Unit",
362 : "Ref",
363 : NULL,
364 : NULL,
365 : NULL);
366 0 : if (r < 0)
367 0 : return log_debug_errno(r, "Failed to add reference to unit %s: %m", unit);
368 :
369 0 : item->flags |= BUS_WAIT_REFFED;
370 : }
371 :
372 0 : r = sd_bus_match_signal_async(
373 : d->bus,
374 0 : &item->slot_properties_changed,
375 : "org.freedesktop.systemd1",
376 0 : item->bus_path,
377 : "org.freedesktop.DBus.Properties",
378 : "PropertiesChanged",
379 : on_properties_changed,
380 : NULL,
381 : item);
382 0 : if (r < 0)
383 0 : return log_debug_errno(r, "Failed to request match for PropertiesChanged signal: %m");
384 :
385 0 : r = sd_bus_call_method_async(
386 : d->bus,
387 0 : &item->slot_get_all,
388 : "org.freedesktop.systemd1",
389 0 : item->bus_path,
390 : "org.freedesktop.DBus.Properties",
391 : "GetAll",
392 : on_get_all_properties,
393 : item,
394 0 : "s", FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END) ? NULL : "org.freedesktop.systemd1.Unit");
395 0 : if (r < 0)
396 0 : return log_debug_errno(r, "Failed to request properties of unit %s: %m", unit);
397 :
398 0 : r = hashmap_put(d->items, item->bus_path, item);
399 0 : if (r < 0)
400 0 : return r;
401 :
402 0 : d->state = BUS_WAIT_RUNNING;
403 0 : item->parent = d;
404 0 : TAKE_PTR(item);
405 0 : return 0;
406 : }
407 :
408 0 : int bus_wait_for_units_run(BusWaitForUnits *d) {
409 : int r;
410 :
411 0 : assert(d);
412 :
413 0 : while (d->state == BUS_WAIT_RUNNING) {
414 :
415 0 : r = sd_bus_process(d->bus, NULL);
416 0 : if (r < 0)
417 0 : return r;
418 0 : if (r > 0)
419 0 : continue;
420 :
421 0 : r = sd_bus_wait(d->bus, (uint64_t) -1);
422 0 : if (r < 0)
423 0 : return r;
424 : }
425 :
426 0 : return d->state;
427 : }
428 :
429 0 : BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d) {
430 0 : assert(d);
431 :
432 0 : return d->state;
433 : }
|