]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bus-wait-for-units.c
Merge pull request #13076 from keszybz/pr/13062
[thirdparty/systemd.git] / src / shared / bus-wait-for-units.c
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 static WaitForItem *wait_for_item_free(WaitForItem *item) {
44 int r;
45
46 if (!item)
47 return NULL;
48
49 if (item->parent) {
50 if (FLAGS_SET(item->flags, BUS_WAIT_REFFED) && item->bus_path && item->parent->bus) {
51 r = sd_bus_call_method_async(
52 item->parent->bus,
53 NULL,
54 "org.freedesktop.systemd1",
55 item->bus_path,
56 "org.freedesktop.systemd1.Unit",
57 "Unref",
58 NULL,
59 NULL,
60 NULL);
61 if (r < 0)
62 log_debug_errno(r, "Failed to drop reference to unit %s, ignoring: %m", item->bus_path);
63 }
64
65 assert_se(hashmap_remove(item->parent->items, item->bus_path) == item);
66
67 if (item->parent->current == item)
68 item->parent->current = NULL;
69 }
70
71 sd_bus_slot_unref(item->slot_properties_changed);
72 sd_bus_slot_unref(item->slot_get_all);
73
74 free(item->bus_path);
75 free(item->active_state);
76 free(item->clean_result);
77
78 return mfree(item);
79 }
80
81 DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free);
82
83 static void bus_wait_for_units_clear(BusWaitForUnits *d) {
84 WaitForItem *item;
85
86 assert(d);
87
88 d->slot_disconnected = sd_bus_slot_unref(d->slot_disconnected);
89 d->bus = sd_bus_unref(d->bus);
90
91 while ((item = hashmap_first(d->items))) {
92 d->current = item;
93
94 item->unit_callback(d, item->bus_path, false, item->userdata);
95 wait_for_item_free(item);
96 }
97
98 d->items = hashmap_free(d->items);
99 }
100
101 static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
102 BusWaitForUnits *d = userdata;
103
104 assert(m);
105 assert(d);
106
107 log_error("Warning! D-Bus connection terminated.");
108
109 bus_wait_for_units_clear(d);
110
111 if (d->ready_callback)
112 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 sd_bus_close(sd_bus_message_get_bus(m));
115
116 return 0;
117 }
118
119 int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret) {
120 _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *d = NULL;
121 int r;
122
123 assert(bus);
124 assert(ret);
125
126 d = new(BusWaitForUnits, 1);
127 if (!d)
128 return -ENOMEM;
129
130 *d = (BusWaitForUnits) {
131 .state = BUS_WAIT_SUCCESS,
132 .bus = sd_bus_ref(bus),
133 };
134
135 r = sd_bus_match_signal_async(
136 bus,
137 &d->slot_disconnected,
138 "org.freedesktop.DBus.Local",
139 NULL,
140 "org.freedesktop.DBus.Local",
141 "Disconnected",
142 match_disconnected, NULL, d);
143 if (r < 0)
144 return r;
145
146 *ret = TAKE_PTR(d);
147 return 0;
148 }
149
150 BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d) {
151 if (!d)
152 return NULL;
153
154 bus_wait_for_units_clear(d);
155 sd_bus_slot_unref(d->slot_disconnected);
156 sd_bus_unref(d->bus);
157
158 return mfree(d);
159 }
160
161 static bool bus_wait_for_units_is_ready(BusWaitForUnits *d) {
162 assert(d);
163
164 if (!d->bus) /* Disconnected? */
165 return true;
166
167 return hashmap_isempty(d->items);
168 }
169
170 void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) {
171 assert(d);
172
173 d->ready_callback = callback;
174 d->userdata = userdata;
175 }
176
177 static void bus_wait_for_units_check_ready(BusWaitForUnits *d) {
178 assert(d);
179
180 if (!bus_wait_for_units_is_ready(d))
181 return;
182
183 d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS;
184
185 if (d->ready_callback)
186 d->ready_callback(d, d->state, d->userdata);
187 }
188
189 static void wait_for_item_check_ready(WaitForItem *item) {
190 BusWaitForUnits *d;
191
192 assert(item);
193 assert(d = item->parent);
194
195 if (FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END)) {
196
197 if (item->clean_result && !streq(item->clean_result, "success"))
198 d->has_failed = true;
199
200 if (!item->active_state || streq(item->active_state, "maintenance"))
201 return;
202 }
203
204 if (FLAGS_SET(item->flags, BUS_WAIT_NO_JOB) && item->job_id != 0)
205 return;
206
207 if (FLAGS_SET(item->flags, BUS_WAIT_FOR_INACTIVE)) {
208
209 if (streq_ptr(item->active_state, "failed"))
210 d->has_failed = true;
211 else if (!streq_ptr(item->active_state, "inactive"))
212 return;
213 }
214
215 if (item->unit_callback) {
216 d->current = item;
217 item->unit_callback(d, item->bus_path, true, item->userdata);
218 }
219
220 wait_for_item_free(item);
221
222 bus_wait_for_units_check_ready(d);
223 }
224
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
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
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
292 static int on_get_all_properties(sd_bus_message *m, void *userdata, sd_bus_error *error) {
293 WaitForItem *item = userdata;
294 int r;
295
296 assert(item);
297
298 if (sd_bus_error_is_set(error)) {
299 BusWaitForUnits *d = item->parent;
300
301 d->has_failed = true;
302
303 log_debug_errno(sd_bus_error_get_errno(error), "GetAll() failed for %s: %s",
304 item->bus_path, error->message);
305
306 d->current = item;
307 item->unit_callback(d, item->bus_path, false, item->userdata);
308 wait_for_item_free(item);
309
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
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
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_t) -1);
422 if (r < 0)
423 return r;
424 }
425
426 return d->state;
427 }
428
429 BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d) {
430 assert(d);
431
432 return d->state;
433 }