Coverage Report

Created: 2019-06-19 13:33

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