Branch data Line data Source code
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 : :
29 [ # # ]: 0 : log_error("Warning! D-Bus connection terminated.");
30 : 0 : sd_bus_close(sd_bus_message_get_bus(m));
31 : :
32 : 0 : return 0;
33 : : }
34 : :
35 : 0 : static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
36 : : const char *path, *unit, *result;
37 : 0 : BusWaitForJobs *d = userdata;
38 : : uint32_t id;
39 : : char *found;
40 : : int r;
41 : :
42 [ # # ]: 0 : assert(m);
43 [ # # ]: 0 : assert(d);
44 : :
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 : : }
50 : :
51 : 0 : found = set_remove(d->jobs, (char*) path);
52 [ # # ]: 0 : if (!found)
53 : 0 : return 0;
54 : :
55 : 0 : free(found);
56 : :
57 : 0 : (void) free_and_strdup(&d->result, empty_to_null(result));
58 : :
59 : 0 : (void) free_and_strdup(&d->name, empty_to_null(unit));
60 : :
61 : 0 : return 0;
62 : : }
63 : :
64 : 0 : void bus_wait_for_jobs_free(BusWaitForJobs *d) {
65 [ # # ]: 0 : if (!d)
66 : 0 : return;
67 : :
68 : 0 : set_free_free(d->jobs);
69 : :
70 : 0 : sd_bus_slot_unref(d->slot_disconnected);
71 : 0 : sd_bus_slot_unref(d->slot_job_removed);
72 : :
73 : 0 : sd_bus_unref(d->bus);
74 : :
75 : 0 : free(d->name);
76 : 0 : free(d->result);
77 : :
78 : 0 : free(d);
79 : : }
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 : : int r;
84 : :
85 [ # # ]: 0 : assert(bus);
86 [ # # ]: 0 : assert(ret);
87 : :
88 : 0 : d = new(BusWaitForJobs, 1);
89 [ # # ]: 0 : if (!d)
90 : 0 : return -ENOMEM;
91 : :
92 : 0 : *d = (BusWaitForJobs) {
93 : 0 : .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 : 0 : r = sd_bus_match_signal_async(
100 : : bus,
101 : 0 : &d->slot_job_removed,
102 [ # # ]: 0 : 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 [ # # ]: 0 : if (r < 0)
108 : 0 : return r;
109 : :
110 : 0 : r = sd_bus_match_signal_async(
111 : : bus,
112 : 0 : &d->slot_disconnected,
113 : : "org.freedesktop.DBus.Local",
114 : : NULL,
115 : : "org.freedesktop.DBus.Local",
116 : : "Disconnected",
117 : : match_disconnected, NULL, d);
118 [ # # ]: 0 : if (r < 0)
119 : 0 : return r;
120 : :
121 : 0 : *ret = TAKE_PTR(d);
122 : :
123 : 0 : return 0;
124 : : }
125 : :
126 : 0 : static int bus_process_wait(sd_bus *bus) {
127 : : int r;
128 : :
129 : : 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 : :
136 : 0 : r = sd_bus_wait(bus, (uint64_t) -1);
137 [ # # ]: 0 : if (r < 0)
138 : 0 : return r;
139 : : }
140 : : }
141 : :
142 : 0 : static int bus_job_get_service_result(BusWaitForJobs *d, char **result) {
143 : 0 : _cleanup_free_ char *dbus_path = NULL;
144 : :
145 [ # # ]: 0 : assert(d);
146 [ # # ]: 0 : assert(d->name);
147 [ # # ]: 0 : assert(result);
148 : :
149 [ # # ]: 0 : if (!endswith(d->name, ".service"))
150 : 0 : return -EINVAL;
151 : :
152 : 0 : dbus_path = unit_dbus_path_from_name(d->name);
153 [ # # ]: 0 : if (!dbus_path)
154 : 0 : return -ENOMEM;
155 : :
156 : 0 : 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 : 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 : :
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 [ # # ]: 0 : assert(service);
183 : :
184 : 0 : service_shell_quoted = shell_maybe_quote(service, ESCAPE_BACKSLASH);
185 : :
186 [ # # ]: 0 : if (!strv_isempty((char**) extra_args)) {
187 : 0 : _cleanup_free_ char *t;
188 : :
189 : 0 : t = strv_join((char**) extra_args, " ");
190 [ # # # # : 0 : systemctl = strjoina("systemctl ", t ? : "<args>");
# # # # #
# # # #
# ]
191 [ # # # # : 0 : journalctl = strjoina("journalctl ", t ? : "<args>");
# # # # #
# # # #
# ]
192 : : }
193 : :
194 [ # # ]: 0 : if (!isempty(result)) {
195 : : size_t i;
196 : :
197 [ # # ]: 0 : for (i = 0; i < ELEMENTSOF(explanations); ++i)
198 [ # # ]: 0 : if (streq(result, explanations[i].result))
199 : 0 : break;
200 : :
201 [ # # ]: 0 : if (i < ELEMENTSOF(explanations)) {
202 [ # # # # ]: 0 : log_error("Job for %s failed because %s.\n"
203 : : "See \"%s status %s\" and \"%s -xe\" for details.\n",
204 : : service,
205 : : explanations[i].explanation,
206 : : systemctl,
207 : : service_shell_quoted ?: "<service>",
208 : : journalctl);
209 : 0 : goto finish;
210 : : }
211 : : }
212 : :
213 [ # # # # ]: 0 : log_error("Job for %s failed.\n"
214 : : "See \"%s status %s\" and \"%s -xe\" for details.\n",
215 : : service,
216 : : systemctl,
217 : : service_shell_quoted ?: "<service>",
218 : : journalctl);
219 : :
220 : 0 : finish:
221 : : /* 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 : : "followed by \"%1$s start %2$s\" again.",
225 : : systemctl,
226 : : service_shell_quoted ?: "<service>");
227 : 0 : }
228 : :
229 : 0 : static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
230 [ # # ]: 0 : assert(d);
231 [ # # ]: 0 : assert(d->name);
232 [ # # ]: 0 : assert(d->result);
233 : :
234 [ # # ]: 0 : if (!quiet) {
235 [ # # ]: 0 : if (streq(d->result, "canceled"))
236 [ # # ]: 0 : log_error("Job for %s canceled.", strna(d->name));
237 [ # # ]: 0 : else if (streq(d->result, "timeout"))
238 [ # # ]: 0 : log_error("Job for %s timed out.", strna(d->name));
239 [ # # ]: 0 : else if (streq(d->result, "dependency"))
240 [ # # ]: 0 : log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name));
241 [ # # ]: 0 : else if (streq(d->result, "invalid"))
242 [ # # ]: 0 : log_error("%s is not active, cannot reload.", strna(d->name));
243 [ # # ]: 0 : else if (streq(d->result, "assert"))
244 [ # # ]: 0 : log_error("Assertion failed on job for %s.", strna(d->name));
245 [ # # ]: 0 : else if (streq(d->result, "unsupported"))
246 [ # # ]: 0 : log_error("Operation on or unit type of %s not supported on this system.", strna(d->name));
247 [ # # ]: 0 : else if (streq(d->result, "collected"))
248 [ # # ]: 0 : log_error("Queued job for %s was garbage collected.", strna(d->name));
249 [ # # ]: 0 : else if (streq(d->result, "once"))
250 [ # # ]: 0 : log_error("Unit %s was started already once and can't be started again.", strna(d->name));
251 [ # # ]: 0 : else if (!STR_IN_SET(d->result, "done", "skipped")) {
252 : :
253 [ # # # # ]: 0 : if (d->name && endswith(d->name, ".service")) {
254 : 0 : _cleanup_free_ char *result = NULL;
255 : : int q;
256 : :
257 : 0 : q = bus_job_get_service_result(d, &result);
258 [ # # ]: 0 : if (q < 0)
259 [ # # ]: 0 : log_debug_errno(q, "Failed to get Result property of unit %s: %m", d->name);
260 : :
261 : 0 : log_job_error_with_service_result(d->name, result, extra_args);
262 : : } else
263 [ # # ]: 0 : log_error("Job failed. See \"journalctl -xe\" for details.");
264 : : }
265 : : }
266 : :
267 [ # # ]: 0 : if (STR_IN_SET(d->result, "canceled", "collected"))
268 : 0 : return -ECANCELED;
269 [ # # ]: 0 : else if (streq(d->result, "timeout"))
270 : 0 : return -ETIME;
271 [ # # ]: 0 : else if (streq(d->result, "dependency"))
272 : 0 : return -EIO;
273 [ # # ]: 0 : else if (streq(d->result, "invalid"))
274 : 0 : return -ENOEXEC;
275 [ # # ]: 0 : else if (streq(d->result, "assert"))
276 : 0 : return -EPROTO;
277 [ # # ]: 0 : else if (streq(d->result, "unsupported"))
278 : 0 : return -EOPNOTSUPP;
279 [ # # ]: 0 : else if (streq(d->result, "once"))
280 : 0 : return -ESTALE;
281 [ # # ]: 0 : else if (STR_IN_SET(d->result, "done", "skipped"))
282 : 0 : return 0;
283 : :
284 [ # # ]: 0 : 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 : :
291 [ # # ]: 0 : assert(d);
292 : :
293 [ # # ]: 0 : while (!set_isempty(d->jobs)) {
294 : : int q;
295 : :
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 : :
300 [ # # # # ]: 0 : if (d->name && d->result) {
301 : 0 : q = check_wait_response(d, quiet, extra_args);
302 : : /* Return the first error as it is most likely to be
303 : : * meaningful. */
304 [ # # # # ]: 0 : if (q < 0 && r == 0)
305 : 0 : r = q;
306 : :
307 [ # # ]: 0 : log_debug_errno(q, "Got result %s/%m for job %s", d->result, d->name);
308 : : }
309 : :
310 : 0 : d->name = mfree(d->name);
311 : 0 : d->result = mfree(d->result);
312 : : }
313 : :
314 : 0 : return r;
315 : : }
316 : :
317 : 0 : int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) {
318 : : int r;
319 : :
320 [ # # ]: 0 : assert(d);
321 : :
322 : 0 : r = set_ensure_allocated(&d->jobs, &string_hash_ops);
323 [ # # ]: 0 : if (r < 0)
324 : 0 : return r;
325 : :
326 : 0 : return set_put_strdup(d->jobs, path);
327 : : }
328 : :
329 : 0 : int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) {
330 : : int r;
331 : :
332 : 0 : r = bus_wait_for_jobs_add(d, path);
333 [ # # ]: 0 : if (r < 0)
334 : 0 : return log_oom();
335 : :
336 : 0 : return bus_wait_for_jobs(d, quiet, NULL);
337 : : }
|