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