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