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