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