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