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