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 
match_disconnected(sd_bus_message * m,void * userdata,sd_bus_error * error)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 
match_job_removed(sd_bus_message * m,void * userdata,sd_bus_error * error)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 
bus_wait_for_jobs_free(BusWaitForJobs * d)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 
bus_wait_for_jobs_new(sd_bus * bus,BusWaitForJobs ** ret)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 
bus_process_wait(sd_bus * bus)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 
bus_job_get_service_result(BusWaitForJobs * d,char ** result)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 
log_job_error_with_service_result(const char * service,const char * result,const char * const * extra_args)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 
check_wait_response(BusWaitForJobs * d,bool quiet,const char * const * extra_args)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 
bus_wait_for_jobs(BusWaitForJobs * d,bool quiet,const char * const * extra_args)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 
bus_wait_for_jobs_add(BusWaitForJobs * d,const char * path)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 
bus_wait_for_jobs_one(BusWaitForJobs * d,const char * path,bool quiet,const char * const * extra_args)326 int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet, const char* const* extra_args) {
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, extra_args);
334 }
335