]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/bus-unit-util.c
Merge pull request #3900 from keszybz/fix-3607
[thirdparty/systemd.git] / src / shared / bus-unit-util.c
CommitLineData
291d565a
LP
1/***
2 This file is part of systemd.
3
4 Copyright 2016 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18***/
19
20#include "alloc-util.h"
21#include "bus-internal.h"
22#include "bus-unit-util.h"
23#include "bus-util.h"
24#include "cgroup-util.h"
25#include "env-util.h"
26#include "escape.h"
27#include "hashmap.h"
28#include "list.h"
29#include "locale-util.h"
30#include "parse-util.h"
31#include "path-util.h"
32#include "process-util.h"
33#include "rlimit-util.h"
34#include "signal-util.h"
35#include "string-util.h"
36#include "syslog-util.h"
37#include "terminal-util.h"
38#include "utf8.h"
39#include "util.h"
40
20b16441
LP
41int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) {
42 assert(message);
43 assert(u);
44
45 u->machine = NULL;
46
47 return sd_bus_message_read(
48 message,
49 "(ssssssouso)",
50 &u->id,
51 &u->description,
52 &u->load_state,
53 &u->active_state,
54 &u->sub_state,
55 &u->following,
56 &u->unit_path,
57 &u->job_id,
58 &u->job_type,
59 &u->job_path);
60}
61
62int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) {
63 const char *eq, *field;
64 int r, rl;
65
66 assert(m);
67 assert(assignment);
68
69 eq = strchr(assignment, '=');
70 if (!eq) {
71 log_error("Not an assignment: %s", assignment);
72 return -EINVAL;
73 }
74
75 r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
76 if (r < 0)
77 return bus_log_create_error(r);
78
79 field = strndupa(assignment, eq - assignment);
80 eq++;
81
82 if (streq(field, "CPUQuota")) {
83
84 if (isempty(eq))
85 r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY);
9184ca48 86 else {
5124866d 87 r = parse_percent_unbounded(eq);
9184ca48
LP
88 if (r <= 0) {
89 log_error_errno(r, "CPU quota '%s' invalid.", eq);
20b16441
LP
90 return -EINVAL;
91 }
92
9184ca48 93 r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) r * USEC_PER_SEC / 100U);
20b16441
LP
94 }
95
96 goto finish;
97
98 } else if (streq(field, "EnvironmentFile")) {
99
100 r = sd_bus_message_append(m, "sv", "EnvironmentFiles", "a(sb)", 1,
101 eq[0] == '-' ? eq + 1 : eq,
102 eq[0] == '-');
103 goto finish;
104
105 } else if (STR_IN_SET(field, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) {
106 char *n;
107 usec_t t;
108 size_t l;
9184ca48 109
20b16441
LP
110 r = parse_sec(eq, &t);
111 if (r < 0)
112 return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq);
113
114 l = strlen(field);
115 n = newa(char, l + 2);
116 if (!n)
117 return log_oom();
118
119 /* Change suffix Sec → USec */
120 strcpy(mempcpy(n, field, l - 3), "USec");
121 r = sd_bus_message_append(m, "sv", n, "t", t);
122 goto finish;
d58d600e
LP
123
124 } else if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
125 uint64_t bytes;
126
127 if (isempty(eq) || streq(eq, "infinity"))
128 bytes = CGROUP_LIMIT_MAX;
129 else {
130 r = parse_percent(eq);
131 if (r >= 0) {
132 char *n;
133
134 /* When this is a percentage we'll convert this into a relative value in the range
f7903e8d 135 * 0…UINT32_MAX and pass it in the MemoryLowScale property (and related
d58d600e
LP
136 * ones). This way the physical memory size can be determined server-side */
137
f7903e8d 138 n = strjoina(field, "Scale");
d58d600e
LP
139 r = sd_bus_message_append(m, "sv", n, "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U));
140 goto finish;
141
142 } else {
143 r = parse_size(eq, 1024, &bytes);
144 if (r < 0)
145 return log_error_errno(r, "Failed to parse bytes specification %s", assignment);
146 }
147 }
148
149 r = sd_bus_message_append(m, "sv", field, "t", bytes);
150 goto finish;
83f8e808
LP
151 } else if (streq(field, "TasksMax")) {
152 uint64_t t;
153
154 if (isempty(eq) || streq(eq, "infinity"))
155 t = (uint64_t) -1;
156 else {
157 r = parse_percent(eq);
158 if (r >= 0) {
159 r = sd_bus_message_append(m, "sv", "TasksMaxScale", "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U));
160 goto finish;
161 } else {
162 r = safe_atou64(eq, &t);
163 if (r < 0)
164 return log_error_errno(r, "Failed to parse maximum tasks specification %s", assignment);
165 }
166
167 }
168
169 r = sd_bus_message_append(m, "sv", "TasksMax", "t", t);
170 goto finish;
20b16441
LP
171 }
172
173 r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
174 if (r < 0)
175 return bus_log_create_error(r);
176
177 rl = rlimit_from_string(field);
178 if (rl >= 0) {
179 const char *sn;
180 struct rlimit l;
181
182 r = rlimit_parse(rl, eq, &l);
183 if (r < 0)
184 return log_error_errno(r, "Failed to parse resource limit: %s", eq);
185
186 r = sd_bus_message_append(m, "v", "t", l.rlim_max);
187 if (r < 0)
188 return bus_log_create_error(r);
189
190 r = sd_bus_message_close_container(m);
191 if (r < 0)
192 return bus_log_create_error(r);
193
194 r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
195 if (r < 0)
196 return bus_log_create_error(r);
197
198 sn = strjoina(field, "Soft");
199 r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur);
200
201 } else if (STR_IN_SET(field,
29206d46
LP
202 "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
203 "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
204 "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
205 "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges",
206 "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute",
207 "RestrictRealtime", "DynamicUser")) {
20b16441
LP
208
209 r = parse_boolean(eq);
210 if (r < 0)
211 return log_error_errno(r, "Failed to parse boolean assignment %s.", assignment);
212
213 r = sd_bus_message_append(m, "v", "b", r);
214
20b16441
LP
215 } else if (STR_IN_SET(field, "CPUShares", "StartupCPUShares")) {
216 uint64_t u;
217
218 r = cg_cpu_shares_parse(eq, &u);
219 if (r < 0) {
220 log_error("Failed to parse %s value %s.", field, eq);
221 return -EINVAL;
222 }
223
224 r = sd_bus_message_append(m, "v", "t", u);
225
13c31542
TH
226 } else if (STR_IN_SET(field, "IOWeight", "StartupIOWeight")) {
227 uint64_t u;
228
229 r = cg_weight_parse(eq, &u);
230 if (r < 0) {
231 log_error("Failed to parse %s value %s.", field, eq);
232 return -EINVAL;
233 }
234
235 r = sd_bus_message_append(m, "v", "t", u);
236
20b16441
LP
237 } else if (STR_IN_SET(field, "BlockIOWeight", "StartupBlockIOWeight")) {
238 uint64_t u;
239
3fdf9ad7 240 r = cg_blkio_weight_parse(eq, &u);
20b16441
LP
241 if (r < 0) {
242 log_error("Failed to parse %s value %s.", field, eq);
243 return -EINVAL;
244 }
245
246 r = sd_bus_message_append(m, "v", "t", u);
247
248 } else if (STR_IN_SET(field,
249 "User", "Group", "DevicePolicy", "KillMode",
250 "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
251 "StandardInput", "StandardOutput", "StandardError",
252 "Description", "Slice", "Type", "WorkingDirectory",
253 "RootDirectory", "SyslogIdentifier", "ProtectSystem",
186ad4b1 254 "ProtectHome", "SELinuxContext"))
20b16441
LP
255 r = sd_bus_message_append(m, "v", "s", eq);
256
257 else if (streq(field, "SyslogLevel")) {
258 int level;
259
260 level = log_level_from_string(eq);
261 if (level < 0) {
262 log_error("Failed to parse %s value %s.", field, eq);
263 return -EINVAL;
264 }
265
266 r = sd_bus_message_append(m, "v", "i", level);
267
268 } else if (streq(field, "SyslogFacility")) {
269 int facility;
270
271 facility = log_facility_unshifted_from_string(eq);
272 if (facility < 0) {
273 log_error("Failed to parse %s value %s.", field, eq);
274 return -EINVAL;
275 }
276
277 r = sd_bus_message_append(m, "v", "i", facility);
278
279 } else if (streq(field, "DeviceAllow")) {
280
281 if (isempty(eq))
282 r = sd_bus_message_append(m, "v", "a(ss)", 0);
283 else {
284 const char *path, *rwm, *e;
285
286 e = strchr(eq, ' ');
287 if (e) {
288 path = strndupa(eq, e - eq);
289 rwm = e+1;
290 } else {
291 path = eq;
292 rwm = "";
293 }
294
295 if (!path_startswith(path, "/dev")) {
296 log_error("%s is not a device file in /dev.", path);
297 return -EINVAL;
298 }
299
300 r = sd_bus_message_append(m, "v", "a(ss)", 1, path, rwm);
301 }
302
9be57249 303 } else if (cgroup_io_limit_type_from_string(field) >= 0 || STR_IN_SET(field, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
20b16441
LP
304
305 if (isempty(eq))
306 r = sd_bus_message_append(m, "v", "a(st)", 0);
307 else {
308 const char *path, *bandwidth, *e;
309 uint64_t bytes;
310
311 e = strchr(eq, ' ');
312 if (e) {
313 path = strndupa(eq, e - eq);
314 bandwidth = e+1;
315 } else {
316 log_error("Failed to parse %s value %s.", field, eq);
317 return -EINVAL;
318 }
319
320 if (!path_startswith(path, "/dev")) {
321 log_error("%s is not a device file in /dev.", path);
322 return -EINVAL;
323 }
324
e57c9ce1 325 if (streq(bandwidth, "infinity")) {
13c31542
TH
326 bytes = CGROUP_LIMIT_MAX;
327 } else {
328 r = parse_size(bandwidth, 1000, &bytes);
329 if (r < 0) {
330 log_error("Failed to parse byte value %s.", bandwidth);
331 return -EINVAL;
332 }
20b16441
LP
333 }
334
335 r = sd_bus_message_append(m, "v", "a(st)", 1, path, bytes);
336 }
337
13c31542 338 } else if (STR_IN_SET(field, "IODeviceWeight", "BlockIODeviceWeight")) {
20b16441
LP
339
340 if (isempty(eq))
341 r = sd_bus_message_append(m, "v", "a(st)", 0);
342 else {
343 const char *path, *weight, *e;
344 uint64_t u;
345
346 e = strchr(eq, ' ');
347 if (e) {
348 path = strndupa(eq, e - eq);
349 weight = e+1;
350 } else {
351 log_error("Failed to parse %s value %s.", field, eq);
352 return -EINVAL;
353 }
354
355 if (!path_startswith(path, "/dev")) {
356 log_error("%s is not a device file in /dev.", path);
357 return -EINVAL;
358 }
359
360 r = safe_atou64(weight, &u);
361 if (r < 0) {
362 log_error("Failed to parse %s value %s.", field, weight);
363 return -EINVAL;
364 }
096a4d53 365 r = sd_bus_message_append(m, "v", "a(st)", 1, path, u);
20b16441
LP
366 }
367
368 } else if (streq(field, "Nice")) {
41bf0590 369 int n;
20b16441 370
41bf0590
LP
371 r = parse_nice(eq, &n);
372 if (r < 0)
373 return log_error_errno(r, "Failed to parse nice value: %s", eq);
20b16441 374
41bf0590 375 r = sd_bus_message_append(m, "v", "i", (int32_t) n);
20b16441
LP
376
377 } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) {
378 const char *p;
379
380 r = sd_bus_message_open_container(m, 'v', "as");
381 if (r < 0)
382 return bus_log_create_error(r);
383
384 r = sd_bus_message_open_container(m, 'a', "s");
385 if (r < 0)
386 return bus_log_create_error(r);
387
388 p = eq;
389
390 for (;;) {
391 _cleanup_free_ char *word = NULL;
392
393 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE);
394 if (r < 0) {
395 log_error("Failed to parse Environment value %s", eq);
396 return -EINVAL;
397 }
398 if (r == 0)
399 break;
400
401 if (streq(field, "Environment")) {
402 if (!env_assignment_is_valid(word)) {
403 log_error("Invalid environment assignment: %s", word);
404 return -EINVAL;
405 }
406 } else { /* PassEnvironment */
407 if (!env_name_is_valid(word)) {
408 log_error("Invalid environment variable name: %s", word);
409 return -EINVAL;
410 }
411 }
412
413 r = sd_bus_message_append_basic(m, 's', word);
414 if (r < 0)
415 return bus_log_create_error(r);
416 }
417
418 r = sd_bus_message_close_container(m);
419 if (r < 0)
420 return bus_log_create_error(r);
421
422 r = sd_bus_message_close_container(m);
423
424 } else if (streq(field, "KillSignal")) {
425 int sig;
426
427 sig = signal_from_string_try_harder(eq);
428 if (sig < 0) {
429 log_error("Failed to parse %s value %s.", field, eq);
430 return -EINVAL;
431 }
432
433 r = sd_bus_message_append(m, "v", "i", sig);
434
435 } else if (streq(field, "TimerSlackNSec")) {
436 nsec_t n;
437
438 r = parse_nsec(eq, &n);
439 if (r < 0) {
440 log_error("Failed to parse %s value %s", field, eq);
441 return -EINVAL;
442 }
443
444 r = sd_bus_message_append(m, "v", "t", n);
445 } else if (streq(field, "OOMScoreAdjust")) {
446 int oa;
447
448 r = safe_atoi(eq, &oa);
449 if (r < 0) {
450 log_error("Failed to parse %s value %s", field, eq);
451 return -EINVAL;
452 }
453
454 if (!oom_score_adjust_is_valid(oa)) {
455 log_error("OOM score adjust value out of range");
456 return -EINVAL;
457 }
458
459 r = sd_bus_message_append(m, "v", "i", oa);
2a624c36
AP
460 } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
461 "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
20b16441
LP
462 const char *p;
463
464 r = sd_bus_message_open_container(m, 'v', "as");
465 if (r < 0)
466 return bus_log_create_error(r);
467
468 r = sd_bus_message_open_container(m, 'a', "s");
469 if (r < 0)
470 return bus_log_create_error(r);
471
472 p = eq;
473
474 for (;;) {
475 _cleanup_free_ char *word = NULL;
476 int offset;
477
478 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
479 if (r < 0) {
480 log_error("Failed to parse %s value %s", field, eq);
481 return -EINVAL;
482 }
483 if (r == 0)
484 break;
485
486 if (!utf8_is_valid(word)) {
487 log_error("Failed to parse %s value %s", field, eq);
488 return -EINVAL;
489 }
490
491 offset = word[0] == '-';
492 if (!path_is_absolute(word + offset)) {
493 log_error("Failed to parse %s value %s", field, eq);
494 return -EINVAL;
495 }
496
497 path_kill_slashes(word + offset);
498
499 r = sd_bus_message_append_basic(m, 's', word);
500 if (r < 0)
501 return bus_log_create_error(r);
502 }
503
504 r = sd_bus_message_close_container(m);
505 if (r < 0)
506 return bus_log_create_error(r);
507
508 r = sd_bus_message_close_container(m);
509
510 } else if (streq(field, "RuntimeDirectory")) {
511 const char *p;
512
513 r = sd_bus_message_open_container(m, 'v', "as");
514 if (r < 0)
515 return bus_log_create_error(r);
516
517 r = sd_bus_message_open_container(m, 'a', "s");
518 if (r < 0)
519 return bus_log_create_error(r);
520
521 p = eq;
522
523 for (;;) {
524 _cleanup_free_ char *word = NULL;
525
526 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
527 if (r < 0)
528 return log_error_errno(r, "Failed to parse %s value %s", field, eq);
529
530 if (r == 0)
531 break;
532
533 r = sd_bus_message_append_basic(m, 's', word);
534 if (r < 0)
535 return bus_log_create_error(r);
536 }
537
538 r = sd_bus_message_close_container(m);
539 if (r < 0)
540 return bus_log_create_error(r);
541
542 r = sd_bus_message_close_container(m);
543
544 } else {
545 log_error("Unknown assignment %s.", assignment);
546 return -EINVAL;
547 }
548
549finish:
550 if (r < 0)
551 return bus_log_create_error(r);
552
553 r = sd_bus_message_close_container(m);
554 if (r < 0)
555 return bus_log_create_error(r);
556
557 return 0;
558}
559
560typedef struct BusWaitForJobs {
561 sd_bus *bus;
562 Set *jobs;
563
564 char *name;
565 char *result;
566
567 sd_bus_slot *slot_job_removed;
568 sd_bus_slot *slot_disconnected;
569} BusWaitForJobs;
570
571static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
572 assert(m);
573
574 log_error("Warning! D-Bus connection terminated.");
575 sd_bus_close(sd_bus_message_get_bus(m));
576
577 return 0;
578}
579
580static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
581 const char *path, *unit, *result;
582 BusWaitForJobs *d = userdata;
583 uint32_t id;
584 char *found;
585 int r;
586
587 assert(m);
588 assert(d);
589
590 r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result);
591 if (r < 0) {
592 bus_log_parse_error(r);
593 return 0;
594 }
595
596 found = set_remove(d->jobs, (char*) path);
597 if (!found)
598 return 0;
599
600 free(found);
601
602 if (!isempty(result))
603 d->result = strdup(result);
604
605 if (!isempty(unit))
606 d->name = strdup(unit);
607
608 return 0;
609}
610
611void bus_wait_for_jobs_free(BusWaitForJobs *d) {
612 if (!d)
613 return;
614
615 set_free_free(d->jobs);
616
617 sd_bus_slot_unref(d->slot_disconnected);
618 sd_bus_slot_unref(d->slot_job_removed);
619
620 sd_bus_unref(d->bus);
621
622 free(d->name);
623 free(d->result);
624
625 free(d);
626}
627
628int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) {
629 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL;
630 int r;
631
632 assert(bus);
633 assert(ret);
634
635 d = new0(BusWaitForJobs, 1);
636 if (!d)
637 return -ENOMEM;
638
639 d->bus = sd_bus_ref(bus);
640
641 /* When we are a bus client we match by sender. Direct
642 * connections OTOH have no initialized sender field, and
643 * hence we ignore the sender then */
644 r = sd_bus_add_match(
645 bus,
646 &d->slot_job_removed,
647 bus->bus_client ?
648 "type='signal',"
649 "sender='org.freedesktop.systemd1',"
650 "interface='org.freedesktop.systemd1.Manager',"
651 "member='JobRemoved',"
652 "path='/org/freedesktop/systemd1'" :
653 "type='signal',"
654 "interface='org.freedesktop.systemd1.Manager',"
655 "member='JobRemoved',"
656 "path='/org/freedesktop/systemd1'",
657 match_job_removed, d);
658 if (r < 0)
659 return r;
660
661 r = sd_bus_add_match(
662 bus,
663 &d->slot_disconnected,
664 "type='signal',"
665 "sender='org.freedesktop.DBus.Local',"
666 "interface='org.freedesktop.DBus.Local',"
667 "member='Disconnected'",
668 match_disconnected, d);
669 if (r < 0)
670 return r;
671
672 *ret = d;
673 d = NULL;
674
675 return 0;
676}
677
678static int bus_process_wait(sd_bus *bus) {
679 int r;
680
681 for (;;) {
682 r = sd_bus_process(bus, NULL);
683 if (r < 0)
684 return r;
685 if (r > 0)
686 return 0;
687
688 r = sd_bus_wait(bus, (uint64_t) -1);
689 if (r < 0)
690 return r;
691 }
692}
693
694static int bus_job_get_service_result(BusWaitForJobs *d, char **result) {
695 _cleanup_free_ char *dbus_path = NULL;
696
697 assert(d);
698 assert(d->name);
699 assert(result);
700
701 dbus_path = unit_dbus_path_from_name(d->name);
702 if (!dbus_path)
703 return -ENOMEM;
704
705 return sd_bus_get_property_string(d->bus,
706 "org.freedesktop.systemd1",
707 dbus_path,
708 "org.freedesktop.systemd1.Service",
709 "Result",
710 NULL,
711 result);
712}
713
714static const struct {
715 const char *result, *explanation;
716} explanations [] = {
0b2de9d9 717 { "resources", "of unavailable resources or another system error" },
20b16441
LP
718 { "timeout", "a timeout was exceeded" },
719 { "exit-code", "the control process exited with error code" },
720 { "signal", "a fatal signal was delivered to the control process" },
721 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
722 { "watchdog", "the service failed to send watchdog ping" },
723 { "start-limit", "start of the service was attempted too often" }
724};
725
726static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) {
727 _cleanup_free_ char *service_shell_quoted = NULL;
728 const char *systemctl = "systemctl", *journalctl = "journalctl";
729
730 assert(service);
731
732 service_shell_quoted = shell_maybe_quote(service);
733
734 if (extra_args && extra_args[1]) {
735 _cleanup_free_ char *t;
736
737 t = strv_join((char**) extra_args, " ");
738 systemctl = strjoina("systemctl ", t ? : "<args>");
739 journalctl = strjoina("journalctl ", t ? : "<args>");
740 }
741
742 if (!isempty(result)) {
743 unsigned i;
744
745 for (i = 0; i < ELEMENTSOF(explanations); ++i)
746 if (streq(result, explanations[i].result))
747 break;
748
749 if (i < ELEMENTSOF(explanations)) {
750 log_error("Job for %s failed because %s.\n"
751 "See \"%s status %s\" and \"%s -xe\" for details.\n",
752 service,
753 explanations[i].explanation,
754 systemctl,
755 service_shell_quoted ?: "<service>",
756 journalctl);
757 goto finish;
758 }
759 }
760
761 log_error("Job for %s failed.\n"
762 "See \"%s status %s\" and \"%s -xe\" for details.\n",
763 service,
764 systemctl,
765 service_shell_quoted ?: "<service>",
766 journalctl);
767
768finish:
769 /* For some results maybe additional explanation is required */
770 if (streq_ptr(result, "start-limit"))
771 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
772 "followed by \"%1$s start %2$s\" again.",
773 systemctl,
774 service_shell_quoted ?: "<service>");
775}
776
777static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
778 int r = 0;
779
780 assert(d->result);
781
782 if (!quiet) {
783 if (streq(d->result, "canceled"))
784 log_error("Job for %s canceled.", strna(d->name));
785 else if (streq(d->result, "timeout"))
786 log_error("Job for %s timed out.", strna(d->name));
787 else if (streq(d->result, "dependency"))
788 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name));
789 else if (streq(d->result, "invalid"))
790 log_error("%s is not active, cannot reload.", strna(d->name));
791 else if (streq(d->result, "assert"))
792 log_error("Assertion failed on job for %s.", strna(d->name));
793 else if (streq(d->result, "unsupported"))
794 log_error("Operation on or unit type of %s not supported on this system.", strna(d->name));
795 else if (!streq(d->result, "done") && !streq(d->result, "skipped")) {
796 if (d->name) {
797 int q;
798 _cleanup_free_ char *result = NULL;
799
800 q = bus_job_get_service_result(d, &result);
801 if (q < 0)
802 log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name);
803
804 log_job_error_with_service_result(d->name, result, extra_args);
805 } else
806 log_error("Job failed. See \"journalctl -xe\" for details.");
807 }
808 }
809
810 if (streq(d->result, "canceled"))
811 r = -ECANCELED;
812 else if (streq(d->result, "timeout"))
813 r = -ETIME;
814 else if (streq(d->result, "dependency"))
815 r = -EIO;
816 else if (streq(d->result, "invalid"))
817 r = -ENOEXEC;
818 else if (streq(d->result, "assert"))
819 r = -EPROTO;
820 else if (streq(d->result, "unsupported"))
821 r = -EOPNOTSUPP;
822 else if (!streq(d->result, "done") && !streq(d->result, "skipped"))
823 r = -EIO;
824
825 return r;
826}
827
828int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
829 int r = 0;
830
831 assert(d);
832
833 while (!set_isempty(d->jobs)) {
834 int q;
835
836 q = bus_process_wait(d->bus);
837 if (q < 0)
838 return log_error_errno(q, "Failed to wait for response: %m");
839
840 if (d->result) {
841 q = check_wait_response(d, quiet, extra_args);
842 /* Return the first error as it is most likely to be
843 * meaningful. */
844 if (q < 0 && r == 0)
845 r = q;
846
847 log_debug_errno(q, "Got result %s/%m for job %s", strna(d->result), strna(d->name));
848 }
849
850 d->name = mfree(d->name);
851 d->result = mfree(d->result);
852 }
853
854 return r;
855}
856
857int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) {
858 int r;
859
860 assert(d);
861
862 r = set_ensure_allocated(&d->jobs, &string_hash_ops);
863 if (r < 0)
864 return r;
865
866 return set_put_strdup(d->jobs, path);
867}
868
869int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) {
870 int r;
871
872 r = bus_wait_for_jobs_add(d, path);
873 if (r < 0)
874 return log_oom();
875
876 return bus_wait_for_jobs(d, quiet, NULL);
877}
878
879int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) {
880 const char *type, *path, *source;
881 int r;
882
acc0269c
CH
883 /* changes is dereferenced when calling unit_file_dump_changes() later,
884 * so we have to make sure this is not NULL. */
885 assert(changes);
886 assert(n_changes);
887
20b16441
LP
888 r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)");
889 if (r < 0)
890 return bus_log_parse_error(r);
891
892 while ((r = sd_bus_message_read(m, "(sss)", &type, &path, &source)) > 0) {
893 /* We expect only "success" changes to be sent over the bus.
894 Hence, reject anything negative. */
895 UnitFileChangeType ch = unit_file_change_type_from_string(type);
896
897 if (ch < 0) {
898 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type, path);
899 continue;
900 }
901
902 r = unit_file_changes_add(changes, n_changes, ch, path, source);
903 if (r < 0)
904 return r;
905 }
906 if (r < 0)
907 return bus_log_parse_error(r);
908
909 r = sd_bus_message_exit_container(m);
910 if (r < 0)
911 return bus_log_parse_error(r);
912
913 unit_file_dump_changes(0, NULL, *changes, *n_changes, false);
914 return 0;
915}
916
291d565a
LP
917struct CGroupInfo {
918 char *cgroup_path;
919 bool is_const; /* If false, cgroup_path should be free()'d */
920
921 Hashmap *pids; /* PID → process name */
922 bool done;
923
924 struct CGroupInfo *parent;
925 LIST_FIELDS(struct CGroupInfo, siblings);
926 LIST_HEAD(struct CGroupInfo, children);
927 size_t n_children;
928};
929
930static bool IS_ROOT(const char *p) {
931 return isempty(p) || streq(p, "/");
932}
933
934static int add_cgroup(Hashmap *cgroups, const char *path, bool is_const, struct CGroupInfo **ret) {
935 struct CGroupInfo *parent = NULL, *cg;
936 int r;
937
938 assert(cgroups);
939 assert(ret);
940
941 if (IS_ROOT(path))
942 path = "/";
943
944 cg = hashmap_get(cgroups, path);
945 if (cg) {
946 *ret = cg;
947 return 0;
948 }
949
950 if (!IS_ROOT(path)) {
951 const char *e, *pp;
952
953 e = strrchr(path, '/');
954 if (!e)
955 return -EINVAL;
956
957 pp = strndupa(path, e - path);
958 if (!pp)
959 return -ENOMEM;
960
961 r = add_cgroup(cgroups, pp, false, &parent);
962 if (r < 0)
963 return r;
964 }
965
966 cg = new0(struct CGroupInfo, 1);
967 if (!cg)
968 return -ENOMEM;
969
970 if (is_const)
971 cg->cgroup_path = (char*) path;
972 else {
973 cg->cgroup_path = strdup(path);
974 if (!cg->cgroup_path) {
975 free(cg);
976 return -ENOMEM;
977 }
978 }
979
980 cg->is_const = is_const;
981 cg->parent = parent;
982
983 r = hashmap_put(cgroups, cg->cgroup_path, cg);
984 if (r < 0) {
985 if (!is_const)
986 free(cg->cgroup_path);
987 free(cg);
988 return r;
989 }
990
991 if (parent) {
992 LIST_PREPEND(siblings, parent->children, cg);
993 parent->n_children++;
994 }
995
996 *ret = cg;
997 return 1;
998}
999
1000static int add_process(
1001 Hashmap *cgroups,
1002 const char *path,
1003 pid_t pid,
1004 const char *name) {
1005
1006 struct CGroupInfo *cg;
1007 int r;
1008
1009 assert(cgroups);
1010 assert(name);
1011 assert(pid > 0);
1012
1013 r = add_cgroup(cgroups, path, true, &cg);
1014 if (r < 0)
1015 return r;
1016
1017 r = hashmap_ensure_allocated(&cg->pids, &trivial_hash_ops);
1018 if (r < 0)
1019 return r;
1020
1021 return hashmap_put(cg->pids, PID_TO_PTR(pid), (void*) name);
1022}
1023
1024static void remove_cgroup(Hashmap *cgroups, struct CGroupInfo *cg) {
1025 assert(cgroups);
1026 assert(cg);
1027
1028 while (cg->children)
1029 remove_cgroup(cgroups, cg->children);
1030
1031 hashmap_remove(cgroups, cg->cgroup_path);
1032
1033 if (!cg->is_const)
1034 free(cg->cgroup_path);
1035
1036 hashmap_free(cg->pids);
1037
1038 if (cg->parent)
1039 LIST_REMOVE(siblings, cg->parent->children, cg);
1040
1041 free(cg);
1042}
1043
1044static int cgroup_info_compare_func(const void *a, const void *b) {
1045 const struct CGroupInfo *x = *(const struct CGroupInfo* const*) a, *y = *(const struct CGroupInfo* const*) b;
1046
1047 assert(x);
1048 assert(y);
1049
1050 return strcmp(x->cgroup_path, y->cgroup_path);
1051}
1052
1053static int dump_processes(
1054 Hashmap *cgroups,
1055 const char *cgroup_path,
1056 const char *prefix,
1057 unsigned n_columns,
1058 OutputFlags flags) {
1059
1060 struct CGroupInfo *cg;
1061 int r;
1062
1063 assert(prefix);
1064
1065 if (IS_ROOT(cgroup_path))
1066 cgroup_path = "/";
1067
1068 cg = hashmap_get(cgroups, cgroup_path);
1069 if (!cg)
1070 return 0;
1071
1072 if (!hashmap_isempty(cg->pids)) {
1073 const char *name;
1074 size_t n = 0, i;
1075 pid_t *pids;
1076 void *pidp;
1077 Iterator j;
1078 int width;
1079
1080 /* Order processes by their PID */
1081 pids = newa(pid_t, hashmap_size(cg->pids));
1082
1083 HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j)
1084 pids[n++] = PTR_TO_PID(pidp);
1085
1086 assert(n == hashmap_size(cg->pids));
1087 qsort_safe(pids, n, sizeof(pid_t), pid_compare_func);
1088
1089 width = DECIMAL_STR_WIDTH(pids[n-1]);
1090
1091 for (i = 0; i < n; i++) {
1092 _cleanup_free_ char *e = NULL;
1093 const char *special;
1094 bool more;
1095
1096 name = hashmap_get(cg->pids, PID_TO_PTR(pids[i]));
1097 assert(name);
1098
1099 if (n_columns != 0) {
1100 unsigned k;
1101
1102 k = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U);
1103
1104 e = ellipsize(name, k, 100);
1105 if (e)
1106 name = e;
1107 }
1108
1109 more = i+1 < n || cg->children;
323b7dc9 1110 special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT);
291d565a
LP
1111
1112 fprintf(stdout, "%s%s%*"PID_PRI" %s\n",
1113 prefix,
1114 special,
1115 width, pids[i],
1116 name);
1117 }
1118 }
1119
1120 if (cg->children) {
1121 struct CGroupInfo **children, *child;
1122 size_t n = 0, i;
1123
1124 /* Order subcgroups by their name */
1125 children = newa(struct CGroupInfo*, cg->n_children);
1126 LIST_FOREACH(siblings, child, cg->children)
1127 children[n++] = child;
1128 assert(n == cg->n_children);
1129 qsort_safe(children, n, sizeof(struct CGroupInfo*), cgroup_info_compare_func);
1130
7351ded5
IL
1131 if (n_columns != 0)
1132 n_columns = MAX(LESS_BY(n_columns, 2U), 20U);
291d565a
LP
1133
1134 for (i = 0; i < n; i++) {
1135 _cleanup_free_ char *pp = NULL;
1136 const char *name, *special;
1137 bool more;
1138
1139 child = children[i];
1140
1141 name = strrchr(child->cgroup_path, '/');
1142 if (!name)
1143 return -EINVAL;
1144 name++;
1145
1146 more = i+1 < n;
323b7dc9 1147 special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT);
291d565a
LP
1148
1149 fputs(prefix, stdout);
1150 fputs(special, stdout);
1151 fputs(name, stdout);
1152 fputc('\n', stdout);
1153
323b7dc9 1154 special = special_glyph(more ? TREE_VERTICAL : TREE_SPACE);
291d565a
LP
1155
1156 pp = strappend(prefix, special);
1157 if (!pp)
1158 return -ENOMEM;
1159
1160 r = dump_processes(cgroups, child->cgroup_path, pp, n_columns, flags);
1161 if (r < 0)
1162 return r;
1163 }
1164 }
1165
1166 cg->done = true;
1167 return 0;
1168}
1169
1170static int dump_extra_processes(
1171 Hashmap *cgroups,
1172 const char *prefix,
1173 unsigned n_columns,
1174 OutputFlags flags) {
1175
1176 _cleanup_free_ pid_t *pids = NULL;
1177 _cleanup_hashmap_free_ Hashmap *names = NULL;
1178 struct CGroupInfo *cg;
1179 size_t n_allocated = 0, n = 0, k;
1180 Iterator i;
1181 int width, r;
1182
1183 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1184 * combined, sorted, linear list. */
1185
1186 HASHMAP_FOREACH(cg, cgroups, i) {
1187 const char *name;
1188 void *pidp;
1189 Iterator j;
1190
1191 if (cg->done)
1192 continue;
1193
1194 if (hashmap_isempty(cg->pids))
1195 continue;
1196
1197 r = hashmap_ensure_allocated(&names, &trivial_hash_ops);
1198 if (r < 0)
1199 return r;
1200
1201 if (!GREEDY_REALLOC(pids, n_allocated, n + hashmap_size(cg->pids)))
1202 return -ENOMEM;
1203
1204 HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j) {
1205 pids[n++] = PTR_TO_PID(pidp);
1206
1207 r = hashmap_put(names, pidp, (void*) name);
1208 if (r < 0)
1209 return r;
1210 }
1211 }
1212
1213 if (n == 0)
1214 return 0;
1215
1216 qsort_safe(pids, n, sizeof(pid_t), pid_compare_func);
1217 width = DECIMAL_STR_WIDTH(pids[n-1]);
1218
1219 for (k = 0; k < n; k++) {
1220 _cleanup_free_ char *e = NULL;
1221 const char *name;
1222
1223 name = hashmap_get(names, PID_TO_PTR(pids[k]));
1224 assert(name);
1225
1226 if (n_columns != 0) {
1227 unsigned z;
1228
1229 z = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U);
1230
1231 e = ellipsize(name, z, 100);
1232 if (e)
1233 name = e;
1234 }
1235
1236 fprintf(stdout, "%s%s %*" PID_PRI " %s\n",
1237 prefix,
323b7dc9 1238 special_glyph(TRIANGULAR_BULLET),
291d565a
LP
1239 width, pids[k],
1240 name);
1241 }
1242
1243 return 0;
1244}
1245
1246int unit_show_processes(
1247 sd_bus *bus,
1248 const char *unit,
1249 const char *cgroup_path,
1250 const char *prefix,
1251 unsigned n_columns,
1252 OutputFlags flags,
1253 sd_bus_error *error) {
1254
1255 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1256 Hashmap *cgroups = NULL;
1257 struct CGroupInfo *cg;
1258 int r;
1259
1260 assert(bus);
1261 assert(unit);
1262
1263 if (flags & OUTPUT_FULL_WIDTH)
1264 n_columns = 0;
1265 else if (n_columns <= 0)
1266 n_columns = columns();
1267
1268 prefix = strempty(prefix);
1269
1270 r = sd_bus_call_method(
1271 bus,
1272 "org.freedesktop.systemd1",
1273 "/org/freedesktop/systemd1",
1274 "org.freedesktop.systemd1.Manager",
1275 "GetUnitProcesses",
1276 error,
1277 &reply,
1278 "s",
1279 unit);
1280 if (r < 0)
1281 return r;
1282
1283 cgroups = hashmap_new(&string_hash_ops);
1284 if (!cgroups)
1285 return -ENOMEM;
1286
1287 r = sd_bus_message_enter_container(reply, 'a', "(sus)");
1288 if (r < 0)
1289 goto finish;
1290
1291 for (;;) {
1292 const char *path = NULL, *name = NULL;
1293 uint32_t pid;
1294
1295 r = sd_bus_message_read(reply, "(sus)", &path, &pid, &name);
1296 if (r < 0)
1297 goto finish;
1298 if (r == 0)
1299 break;
1300
1301 r = add_process(cgroups, path, pid, name);
1302 if (r < 0)
1303 goto finish;
1304 }
1305
1306 r = sd_bus_message_exit_container(reply);
1307 if (r < 0)
1308 goto finish;
1309
1310 r = dump_processes(cgroups, cgroup_path, prefix, n_columns, flags);
1311 if (r < 0)
1312 goto finish;
1313
1314 r = dump_extra_processes(cgroups, prefix, n_columns, flags);
1315
1316finish:
1317 while ((cg = hashmap_first(cgroups)))
1318 remove_cgroup(cgroups, cg);
1319
1320 hashmap_free(cgroups);
1321
1322 return r;
1323}