/src/systemd/src/shared/bus-wait-for-jobs.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | | |
3 | | #include "alloc-util.h" |
4 | | #include "bus-wait-for-jobs.h" |
5 | | #include "set.h" |
6 | | #include "bus-util.h" |
7 | | #include "bus-internal.h" |
8 | | #include "unit-def.h" |
9 | | #include "escape.h" |
10 | | #include "strv.h" |
11 | | |
12 | | typedef struct BusWaitForJobs { |
13 | | sd_bus *bus; |
14 | | |
15 | | /* The set of jobs to wait for, as bus object paths */ |
16 | | Set *jobs; |
17 | | |
18 | | /* The unit name and job result of the last Job message */ |
19 | | char *name; |
20 | | char *result; |
21 | | |
22 | | sd_bus_slot *slot_job_removed; |
23 | | sd_bus_slot *slot_disconnected; |
24 | | } BusWaitForJobs; |
25 | | |
26 | 0 | static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) { |
27 | 0 | assert(m); |
28 | 0 |
|
29 | 0 | log_error("Warning! D-Bus connection terminated."); |
30 | 0 | sd_bus_close(sd_bus_message_get_bus(m)); |
31 | 0 |
|
32 | 0 | return 0; |
33 | 0 | } |
34 | | |
35 | 0 | static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { |
36 | 0 | const char *path, *unit, *result; |
37 | 0 | BusWaitForJobs *d = userdata; |
38 | 0 | uint32_t id; |
39 | 0 | char *found; |
40 | 0 | int r; |
41 | 0 |
|
42 | 0 | assert(m); |
43 | 0 | assert(d); |
44 | 0 |
|
45 | 0 | r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result); |
46 | 0 | if (r < 0) { |
47 | 0 | bus_log_parse_error(r); |
48 | 0 | return 0; |
49 | 0 | } |
50 | 0 | |
51 | 0 | found = set_remove(d->jobs, (char*) path); |
52 | 0 | if (!found) |
53 | 0 | return 0; |
54 | 0 | |
55 | 0 | free(found); |
56 | 0 |
|
57 | 0 | (void) free_and_strdup(&d->result, empty_to_null(result)); |
58 | 0 |
|
59 | 0 | (void) free_and_strdup(&d->name, empty_to_null(unit)); |
60 | 0 |
|
61 | 0 | return 0; |
62 | 0 | } |
63 | | |
64 | 0 | void bus_wait_for_jobs_free(BusWaitForJobs *d) { |
65 | 0 | if (!d) |
66 | 0 | return; |
67 | 0 | |
68 | 0 | set_free_free(d->jobs); |
69 | 0 |
|
70 | 0 | sd_bus_slot_unref(d->slot_disconnected); |
71 | 0 | sd_bus_slot_unref(d->slot_job_removed); |
72 | 0 |
|
73 | 0 | sd_bus_unref(d->bus); |
74 | 0 |
|
75 | 0 | free(d->name); |
76 | 0 | free(d->result); |
77 | 0 |
|
78 | 0 | free(d); |
79 | 0 | } |
80 | | |
81 | 0 | int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) { |
82 | 0 | _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL; |
83 | 0 | int r; |
84 | 0 |
|
85 | 0 | assert(bus); |
86 | 0 | assert(ret); |
87 | 0 |
|
88 | 0 | d = new(BusWaitForJobs, 1); |
89 | 0 | if (!d) |
90 | 0 | return -ENOMEM; |
91 | 0 | |
92 | 0 | *d = (BusWaitForJobs) { |
93 | 0 | .bus = sd_bus_ref(bus), |
94 | 0 | }; |
95 | 0 |
|
96 | 0 | /* When we are a bus client we match by sender. Direct |
97 | 0 | * connections OTOH have no initialized sender field, and |
98 | 0 | * hence we ignore the sender then */ |
99 | 0 | r = sd_bus_match_signal_async( |
100 | 0 | bus, |
101 | 0 | &d->slot_job_removed, |
102 | 0 | bus->bus_client ? "org.freedesktop.systemd1" : NULL, |
103 | 0 | "/org/freedesktop/systemd1", |
104 | 0 | "org.freedesktop.systemd1.Manager", |
105 | 0 | "JobRemoved", |
106 | 0 | match_job_removed, NULL, d); |
107 | 0 | if (r < 0) |
108 | 0 | return r; |
109 | 0 | |
110 | 0 | r = sd_bus_match_signal_async( |
111 | 0 | bus, |
112 | 0 | &d->slot_disconnected, |
113 | 0 | "org.freedesktop.DBus.Local", |
114 | 0 | NULL, |
115 | 0 | "org.freedesktop.DBus.Local", |
116 | 0 | "Disconnected", |
117 | 0 | match_disconnected, NULL, d); |
118 | 0 | if (r < 0) |
119 | 0 | return r; |
120 | 0 | |
121 | 0 | *ret = TAKE_PTR(d); |
122 | 0 |
|
123 | 0 | return 0; |
124 | 0 | } |
125 | | |
126 | 0 | static int bus_process_wait(sd_bus *bus) { |
127 | 0 | int r; |
128 | 0 |
|
129 | 0 | for (;;) { |
130 | 0 | r = sd_bus_process(bus, NULL); |
131 | 0 | if (r < 0) |
132 | 0 | return r; |
133 | 0 | if (r > 0) |
134 | 0 | return 0; |
135 | 0 | |
136 | 0 | r = sd_bus_wait(bus, (uint64_t) -1); |
137 | 0 | if (r < 0) |
138 | 0 | return r; |
139 | 0 | } |
140 | 0 | } |
141 | | |
142 | 0 | static int bus_job_get_service_result(BusWaitForJobs *d, char **result) { |
143 | 0 | _cleanup_free_ char *dbus_path = NULL; |
144 | 0 |
|
145 | 0 | assert(d); |
146 | 0 | assert(d->name); |
147 | 0 | assert(result); |
148 | 0 |
|
149 | 0 | if (!endswith(d->name, ".service")) |
150 | 0 | return -EINVAL; |
151 | 0 | |
152 | 0 | dbus_path = unit_dbus_path_from_name(d->name); |
153 | 0 | if (!dbus_path) |
154 | 0 | return -ENOMEM; |
155 | 0 | |
156 | 0 | return sd_bus_get_property_string(d->bus, |
157 | 0 | "org.freedesktop.systemd1", |
158 | 0 | dbus_path, |
159 | 0 | "org.freedesktop.systemd1.Service", |
160 | 0 | "Result", |
161 | 0 | NULL, |
162 | 0 | result); |
163 | 0 | } |
164 | | |
165 | 0 | static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) { |
166 | 0 | _cleanup_free_ char *service_shell_quoted = NULL; |
167 | 0 | const char *systemctl = "systemctl", *journalctl = "journalctl"; |
168 | 0 |
|
169 | 0 | static const struct { |
170 | 0 | const char *result, *explanation; |
171 | 0 | } explanations[] = { |
172 | 0 | { "resources", "of unavailable resources or another system error" }, |
173 | 0 | { "protocol", "the service did not take the steps required by its unit configuration" }, |
174 | 0 | { "timeout", "a timeout was exceeded" }, |
175 | 0 | { "exit-code", "the control process exited with error code" }, |
176 | 0 | { "signal", "a fatal signal was delivered to the control process" }, |
177 | 0 | { "core-dump", "a fatal signal was delivered causing the control process to dump core" }, |
178 | 0 | { "watchdog", "the service failed to send watchdog ping" }, |
179 | 0 | { "start-limit", "start of the service was attempted too often" } |
180 | 0 | }; |
181 | 0 |
|
182 | 0 | assert(service); |
183 | 0 |
|
184 | 0 | service_shell_quoted = shell_maybe_quote(service, ESCAPE_BACKSLASH); |
185 | 0 |
|
186 | 0 | if (!strv_isempty((char**) extra_args)) { |
187 | 0 | _cleanup_free_ char *t; |
188 | 0 |
|
189 | 0 | t = strv_join((char**) extra_args, " "); |
190 | 0 | systemctl = strjoina("systemctl ", t ? : "<args>"); |
191 | 0 | journalctl = strjoina("journalctl ", t ? : "<args>"); |
192 | 0 | } |
193 | 0 |
|
194 | 0 | if (!isempty(result)) { |
195 | 0 | size_t i; |
196 | 0 |
|
197 | 0 | for (i = 0; i < ELEMENTSOF(explanations); ++i) |
198 | 0 | if (streq(result, explanations[i].result)) |
199 | 0 | break; |
200 | 0 |
|
201 | 0 | if (i < ELEMENTSOF(explanations)) { |
202 | 0 | log_error("Job for %s failed because %s.\n" |
203 | 0 | "See \"%s status %s\" and \"%s -xe\" for details.\n", |
204 | 0 | service, |
205 | 0 | explanations[i].explanation, |
206 | 0 | systemctl, |
207 | 0 | service_shell_quoted ?: "<service>", |
208 | 0 | journalctl); |
209 | 0 | goto finish; |
210 | 0 | } |
211 | 0 | } |
212 | 0 |
|
213 | 0 | log_error("Job for %s failed.\n" |
214 | 0 | "See \"%s status %s\" and \"%s -xe\" for details.\n", |
215 | 0 | service, |
216 | 0 | systemctl, |
217 | 0 | service_shell_quoted ?: "<service>", |
218 | 0 | journalctl); |
219 | 0 |
|
220 | 0 | finish: |
221 | 0 | /* For some results maybe additional explanation is required */ |
222 | 0 | if (streq_ptr(result, "start-limit")) |
223 | 0 | log_info("To force a start use \"%1$s reset-failed %2$s\"\n" |
224 | 0 | "followed by \"%1$s start %2$s\" again.", |
225 | 0 | systemctl, |
226 | 0 | service_shell_quoted ?: "<service>"); |
227 | 0 | } |
228 | | |
229 | | static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { |
230 | | assert(d); |
231 | | assert(d->name); |
232 | | assert(d->result); |
233 | | |
234 | | if (!quiet) { |
235 | | if (streq(d->result, "canceled")) |
236 | | log_error("Job for %s canceled.", strna(d->name)); |
237 | | else if (streq(d->result, "timeout")) |
238 | | log_error("Job for %s timed out.", strna(d->name)); |
239 | | else if (streq(d->result, "dependency")) |
240 | | log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name)); |
241 | | else if (streq(d->result, "invalid")) |
242 | | log_error("%s is not active, cannot reload.", strna(d->name)); |
243 | | else if (streq(d->result, "assert")) |
244 | | log_error("Assertion failed on job for %s.", strna(d->name)); |
245 | | else if (streq(d->result, "unsupported")) |
246 | | log_error("Operation on or unit type of %s not supported on this system.", strna(d->name)); |
247 | | else if (streq(d->result, "collected")) |
248 | | log_error("Queued job for %s was garbage collected.", strna(d->name)); |
249 | | else if (streq(d->result, "once")) |
250 | | log_error("Unit %s was started already once and can't be started again.", strna(d->name)); |
251 | | else if (!STR_IN_SET(d->result, "done", "skipped")) { |
252 | | |
253 | | if (d->name && endswith(d->name, ".service")) { |
254 | | _cleanup_free_ char *result = NULL; |
255 | | int q; |
256 | | |
257 | | q = bus_job_get_service_result(d, &result); |
258 | | if (q < 0) |
259 | | log_debug_errno(q, "Failed to get Result property of unit %s: %m", d->name); |
260 | | |
261 | | log_job_error_with_service_result(d->name, result, extra_args); |
262 | | } else |
263 | | log_error("Job failed. See \"journalctl -xe\" for details."); |
264 | | } |
265 | | } |
266 | | |
267 | | if (STR_IN_SET(d->result, "canceled", "collected")) |
268 | | return -ECANCELED; |
269 | | else if (streq(d->result, "timeout")) |
270 | | return -ETIME; |
271 | | else if (streq(d->result, "dependency")) |
272 | | return -EIO; |
273 | | else if (streq(d->result, "invalid")) |
274 | | return -ENOEXEC; |
275 | | else if (streq(d->result, "assert")) |
276 | | return -EPROTO; |
277 | | else if (streq(d->result, "unsupported")) |
278 | | return -EOPNOTSUPP; |
279 | | else if (streq(d->result, "once")) |
280 | | return -ESTALE; |
281 | | else if (STR_IN_SET(d->result, "done", "skipped")) |
282 | | return 0; |
283 | | |
284 | | return log_debug_errno(SYNTHETIC_ERRNO(EIO), |
285 | | "Unexpected job result, assuming server side newer than us: %s", d->result); |
286 | | } |
287 | | |
288 | 0 | int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { |
289 | 0 | int r = 0; |
290 | 0 |
|
291 | 0 | assert(d); |
292 | 0 |
|
293 | 0 | while (!set_isempty(d->jobs)) { |
294 | 0 | int q; |
295 | 0 |
|
296 | 0 | q = bus_process_wait(d->bus); |
297 | 0 | if (q < 0) |
298 | 0 | return log_error_errno(q, "Failed to wait for response: %m"); |
299 | 0 | |
300 | 0 | if (d->name && d->result) { |
301 | 0 | q = check_wait_response(d, quiet, extra_args); |
302 | 0 | /* Return the first error as it is most likely to be |
303 | 0 | * meaningful. */ |
304 | 0 | if (q < 0 && r == 0) |
305 | 0 | r = q; |
306 | 0 |
|
307 | 0 | log_debug_errno(q, "Got result %s/%m for job %s", d->result, d->name); |
308 | 0 | } |
309 | 0 |
|
310 | 0 | d->name = mfree(d->name); |
311 | 0 | d->result = mfree(d->result); |
312 | 0 | } |
313 | 0 |
|
314 | 0 | return r; |
315 | 0 | } |
316 | | |
317 | 0 | int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) { |
318 | 0 | int r; |
319 | 0 |
|
320 | 0 | assert(d); |
321 | 0 |
|
322 | 0 | r = set_ensure_allocated(&d->jobs, &string_hash_ops); |
323 | 0 | if (r < 0) |
324 | 0 | return r; |
325 | 0 | |
326 | 0 | return set_put_strdup(d->jobs, path); |
327 | 0 | } |
328 | | |
329 | 0 | int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) { |
330 | 0 | int r; |
331 | 0 |
|
332 | 0 | r = bus_wait_for_jobs_add(d, path); |
333 | 0 | if (r < 0) |
334 | 0 | return log_oom(); |
335 | 0 | |
336 | 0 | return bus_wait_for_jobs(d, quiet, NULL); |
337 | 0 | } |