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