]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bus-wait-for-jobs.c
Merge pull request #18863 from keszybz/cmdline-escaping
[thirdparty/systemd.git] / src / shared / bus-wait-for-jobs.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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
64 BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d) {
65 if (!d)
66 return NULL;
67
68 set_free(d->jobs);
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
78 return mfree(d);
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
136 r = sd_bus_wait(bus, UINT64_MAX);
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
184 service_shell_quoted = shell_maybe_quote(service, 0);
185
186 if (!strv_isempty((char**) extra_args)) {
187 _cleanup_free_ char *t = NULL;
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"
203 "See \"%s status %s\" and \"%s -xeu %s\" for details.\n",
204 service,
205 explanations[i].explanation,
206 systemctl,
207 service_shell_quoted ?: "<service>",
208 journalctl,
209 service_shell_quoted ?: "<service>");
210 goto finish;
211 }
212 }
213
214 log_error("Job for %s failed.\n"
215 "See \"%s status %s\" and \"%s -xeu %s\" for details.\n",
216 service,
217 systemctl,
218 service_shell_quoted ?: "<service>",
219 journalctl,
220 service_shell_quoted ?: "<service>");
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
309 log_full_errno_zerook(LOG_DEBUG, q,
310 "Got result %s/%m for job %s", d->result, d->name);
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) {
321 assert(d);
322
323 return set_put_strdup(&d->jobs, path);
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 }