1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2016 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 #include "alloc-util.h"
22 #include "bus-internal.h"
23 #include "bus-unit-util.h"
26 #include "cgroup-util.h"
27 #include "cpu-set-util.h"
29 #include "errno-list.h"
32 #include "hexdecoct.h"
33 #include "hostname-util.h"
34 #include "in-addr-util.h"
36 #include "locale-util.h"
37 #include "mount-util.h"
39 #include "parse-util.h"
40 #include "path-util.h"
41 #include "process-util.h"
42 #include "rlimit-util.h"
43 #include "securebits-util.h"
44 #include "signal-util.h"
45 #include "string-util.h"
46 #include "syslog-util.h"
47 #include "terminal-util.h"
48 #include "user-util.h"
52 int bus_parse_unit_info(sd_bus_message
*message
, UnitInfo
*u
) {
58 return sd_bus_message_read(
73 static int bus_append_ip_address_access(sd_bus_message
*m
, int family
, const union in_addr_union
*prefix
, unsigned char prefixlen
) {
79 r
= sd_bus_message_open_container(m
, 'r', "iayu");
83 r
= sd_bus_message_append(m
, "i", family
);
87 r
= sd_bus_message_append_array(m
, 'y', prefix
, FAMILY_ADDRESS_SIZE(family
));
91 r
= sd_bus_message_append(m
, "u", prefixlen
);
95 return sd_bus_message_close_container(m
);
98 int bus_append_unit_property_assignment(sd_bus_message
*m
, const char *assignment
) {
99 const char *eq
, *field
;
106 eq
= strchr(assignment
, '=');
108 log_error("Not an assignment: %s", assignment
);
112 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
114 return bus_log_create_error(r
);
116 field
= strndupa(assignment
, eq
- assignment
);
119 if (streq(field
, "CPUQuota")) {
122 r
= sd_bus_message_append(m
, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY
);
124 r
= parse_percent_unbounded(eq
);
126 log_error_errno(r
, "CPU quota '%s' invalid.", eq
);
130 r
= sd_bus_message_append(m
, "sv", "CPUQuotaPerSecUSec", "t", (usec_t
) r
* USEC_PER_SEC
/ 100U);
135 } else if (streq(field
, "EnvironmentFile")) {
138 r
= sd_bus_message_append(m
, "sv", "EnvironmentFiles", "a(sb)", 0);
140 r
= sd_bus_message_append(m
, "sv", "EnvironmentFiles", "a(sb)", 1,
141 eq
[0] == '-' ? eq
+ 1 : eq
,
145 } else if (STR_IN_SET(field
, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) {
150 r
= parse_sec(eq
, &t
);
152 return log_error_errno(r
, "Failed to parse %s= parameter: %s", field
, eq
);
155 n
= newa(char, l
+ 2);
157 /* Change suffix Sec → USec */
158 strcpy(mempcpy(n
, field
, l
- 3), "USec");
159 r
= sd_bus_message_append(m
, "sv", n
, "t", t
);
162 } else if (streq(field
, "LogExtraFields")) {
164 r
= sd_bus_message_append(m
, "s", "LogExtraFields");
168 r
= sd_bus_message_open_container(m
, 'v', "aay");
172 r
= sd_bus_message_open_container(m
, 'a', "ay");
176 r
= sd_bus_message_append_array(m
, 'y', eq
, strlen(eq
));
180 r
= sd_bus_message_close_container(m
);
184 r
= sd_bus_message_close_container(m
);
187 } else if (STR_IN_SET(field
, "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit")) {
190 if (isempty(eq
) || streq(eq
, "infinity"))
191 bytes
= CGROUP_LIMIT_MAX
;
193 r
= parse_percent(eq
);
197 /* When this is a percentage we'll convert this into a relative value in the range
198 * 0…UINT32_MAX and pass it in the MemoryLowScale property (and related
199 * ones). This way the physical memory size can be determined server-side */
201 n
= strjoina(field
, "Scale");
202 r
= sd_bus_message_append(m
, "sv", n
, "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
206 r
= parse_size(eq
, 1024, &bytes
);
208 return log_error_errno(r
, "Failed to parse bytes specification %s", assignment
);
212 r
= sd_bus_message_append(m
, "sv", field
, "t", bytes
);
215 } else if (streq(field
, "Delegate")) {
217 r
= parse_boolean(eq
);
221 r
= sd_bus_message_append(m
, "s", "DelegateControllers");
225 r
= sd_bus_message_open_container(m
, 'v', "as");
229 r
= sd_bus_message_open_container(m
, 'a', "s");
234 _cleanup_free_
char *word
= NULL
;
236 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
242 return log_error_errno(r
, "Invalid syntax: %s", eq
);
244 r
= sd_bus_message_append(m
, "s", word
);
249 r
= sd_bus_message_close_container(m
);
253 r
= sd_bus_message_close_container(m
);
255 r
= sd_bus_message_append(m
, "sv", "Delegate", "b", r
);
259 } else if (streq(field
, "TasksMax")) {
262 if (isempty(eq
) || streq(eq
, "infinity"))
265 r
= parse_percent(eq
);
267 r
= sd_bus_message_append(m
, "sv", "TasksMaxScale", "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
270 r
= safe_atou64(eq
, &t
);
272 return log_error_errno(r
, "Failed to parse maximum tasks specification %s", assignment
);
277 r
= sd_bus_message_append(m
, "sv", "TasksMax", "t", t
);
280 } else if (STR_IN_SET(field
, "StandardInput", "StandardOutput", "StandardError")) {
281 const char *n
, *appended
;
283 n
= startswith(eq
, "fd:");
285 appended
= strjoina(field
, "FileDescriptorName");
286 r
= sd_bus_message_append(m
, "sv", appended
, "s", n
);
288 } else if ((n
= startswith(eq
, "file:"))) {
289 appended
= strjoina(field
, "File");
290 r
= sd_bus_message_append(m
, "sv", appended
, "s", n
);
292 r
= sd_bus_message_append(m
, "sv", field
, "s", eq
);
296 } else if (streq(field
, "StandardInputText")) {
297 _cleanup_free_
char *unescaped
= NULL
;
299 r
= cunescape(eq
, 0, &unescaped
);
301 return log_error_errno(r
, "Failed to unescape text '%s': %m", eq
);
303 if (!strextend(&unescaped
, "\n", NULL
))
306 /* Note that we don't expand specifiers here, but that should be OK, as this is a programmatic
307 * interface anyway */
309 r
= sd_bus_message_append(m
, "s", "StandardInputData");
311 return bus_log_create_error(r
);
313 r
= sd_bus_message_open_container(m
, 'v', "ay");
315 return bus_log_create_error(r
);
317 r
= sd_bus_message_append_array(m
, 'y', unescaped
, strlen(unescaped
));
319 return bus_log_create_error(r
);
321 r
= sd_bus_message_close_container(m
);
325 r
= sd_bus_message_append_basic(m
, SD_BUS_TYPE_STRING
, field
);
327 return bus_log_create_error(r
);
329 rl
= rlimit_from_string(field
);
334 r
= rlimit_parse(rl
, eq
, &l
);
336 return log_error_errno(r
, "Failed to parse resource limit: %s", eq
);
338 r
= sd_bus_message_append(m
, "v", "t", l
.rlim_max
);
340 return bus_log_create_error(r
);
342 r
= sd_bus_message_close_container(m
);
344 return bus_log_create_error(r
);
346 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
348 return bus_log_create_error(r
);
350 sn
= strjoina(field
, "Soft");
351 r
= sd_bus_message_append(m
, "sv", sn
, "t", l
.rlim_cur
);
353 } else if (STR_IN_SET(field
,
354 "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting",
355 "TasksAccounting", "IPAccounting", "SendSIGHUP", "SendSIGKILL", "WakeSystem",
356 "DefaultDependencies", "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "TTYVTDisallocate",
357 "RemainAfterExit", "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers",
358 "NoNewPrivileges", "SyslogLevelPrefix", "RemainAfterElapse", "Persistent",
359 "MemoryDenyWriteExecute", "RestrictRealtime", "DynamicUser", "RemoveIPC",
360 "ProtectKernelTunables", "ProtectKernelModules", "ProtectControlGroups", "MountAPIVFS",
361 "CPUSchedulingResetOnFork", "LockPersonality")) {
363 r
= parse_boolean(eq
);
365 return log_error_errno(r
, "Failed to parse boolean assignment %s.", assignment
);
367 r
= sd_bus_message_append(m
, "v", "b", r
);
369 } else if (STR_IN_SET(field
, "CPUWeight", "StartupCPUWeight")) {
372 r
= cg_weight_parse(eq
, &u
);
374 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
376 r
= sd_bus_message_append(m
, "v", "t", u
);
378 } else if (STR_IN_SET(field
, "CPUShares", "StartupCPUShares")) {
381 r
= cg_cpu_shares_parse(eq
, &u
);
383 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
385 r
= sd_bus_message_append(m
, "v", "t", u
);
387 } else if (STR_IN_SET(field
, "IOWeight", "StartupIOWeight")) {
390 r
= cg_weight_parse(eq
, &u
);
392 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
394 r
= sd_bus_message_append(m
, "v", "t", u
);
396 } else if (STR_IN_SET(field
, "BlockIOWeight", "StartupBlockIOWeight")) {
399 r
= cg_blkio_weight_parse(eq
, &u
);
401 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
403 r
= sd_bus_message_append(m
, "v", "t", u
);
405 } else if (STR_IN_SET(field
,
406 "User", "Group", "DevicePolicy", "KillMode",
407 "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
408 "Description", "Slice", "Type", "WorkingDirectory",
409 "RootDirectory", "SyslogIdentifier", "ProtectSystem",
410 "ProtectHome", "SELinuxContext", "Restart", "RootImage",
411 "NotifyAccess", "RuntimeDirectoryPreserve", "Personality",
412 "KeyringMode", "CollectMode", "FailureAction", "SuccessAction",
415 r
= sd_bus_message_append(m
, "v", "s", eq
);
417 else if (streq(field
, "StandardInputData")) {
418 _cleanup_free_
void *decoded
= NULL
;
421 r
= unbase64mem(eq
, (size_t) -1, &decoded
, &sz
);
423 return log_error_errno(r
, "Failed to decode base64 data '%s': %m", eq
);
425 r
= sd_bus_message_open_container(m
, 'v', "ay");
427 return bus_log_create_error(r
);
429 r
= sd_bus_message_append_array(m
, 'y', decoded
, sz
);
431 return bus_log_create_error(r
);
433 r
= sd_bus_message_close_container(m
);
435 } else if (STR_IN_SET(field
, "AppArmorProfile", "SmackProcessLabel")) {
447 r
= sd_bus_message_append(m
, "v", "(bs)", ignore
, s
);
449 } else if (STR_IN_SET(field
, "SyslogLevel", "LogLevelMax")) {
452 level
= log_level_from_string(eq
);
454 log_error("Failed to parse %s value %s.", field
, eq
);
458 r
= sd_bus_message_append(m
, "v", "i", level
);
460 } else if (streq(field
, "SyslogFacility")) {
463 facility
= log_facility_unshifted_from_string(eq
);
465 log_error("Failed to parse %s value %s.", field
, eq
);
469 r
= sd_bus_message_append(m
, "v", "i", facility
);
471 } else if (streq(field
, "SecureBits")) {
473 r
= secure_bits_from_string(eq
);
475 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
477 r
= sd_bus_message_append(m
, "v", "i", r
);
479 } else if (STR_IN_SET(field
, "CapabilityBoundingSet", "AmbientCapabilities")) {
490 r
= capability_set_from_string(p
, &sum
);
492 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
494 sum
= invert
? ~sum
: sum
;
496 r
= sd_bus_message_append(m
, "v", "t", sum
);
498 } else if (streq(field
, "DeviceAllow")) {
501 r
= sd_bus_message_append(m
, "v", "a(ss)", 0);
503 const char *path
, *rwm
, *e
;
507 path
= strndupa(eq
, e
- eq
);
514 if (!is_deviceallow_pattern(path
)) {
515 log_error("%s is not a device file in /dev.", path
);
519 r
= sd_bus_message_append(m
, "v", "a(ss)", 1, path
, rwm
);
522 } else if (cgroup_io_limit_type_from_string(field
) >= 0 || STR_IN_SET(field
, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
525 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
527 const char *path
, *bandwidth
, *e
;
532 path
= strndupa(eq
, e
- eq
);
535 log_error("Failed to parse %s value %s.", field
, eq
);
539 if (!path_startswith(path
, "/dev")) {
540 log_error("%s is not a device file in /dev.", path
);
544 if (streq(bandwidth
, "infinity")) {
545 bytes
= CGROUP_LIMIT_MAX
;
547 r
= parse_size(bandwidth
, 1000, &bytes
);
549 return log_error_errno(r
, "Failed to parse byte value %s: %m", bandwidth
);
552 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, bytes
);
555 } else if (STR_IN_SET(field
, "IODeviceWeight", "BlockIODeviceWeight")) {
558 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
560 const char *path
, *weight
, *e
;
565 path
= strndupa(eq
, e
- eq
);
568 log_error("Failed to parse %s value %s.", field
, eq
);
572 if (!path_startswith(path
, "/dev")) {
573 log_error("%s is not a device file in /dev.", path
);
577 r
= safe_atou64(weight
, &u
);
579 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, weight
);
581 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, u
);
584 } else if (STR_IN_SET(field
, "IPAddressAllow", "IPAddressDeny")) {
587 r
= sd_bus_message_append(m
, "v", "a(iayu)", 0);
589 unsigned char prefixlen
;
590 union in_addr_union prefix
= {};
593 r
= sd_bus_message_open_container(m
, 'v', "a(iayu)");
595 return bus_log_create_error(r
);
597 r
= sd_bus_message_open_container(m
, 'a', "(iayu)");
599 return bus_log_create_error(r
);
601 if (streq(eq
, "any")) {
602 /* "any" is a shortcut for 0.0.0.0/0 and ::/0 */
604 r
= bus_append_ip_address_access(m
, AF_INET
, &prefix
, 0);
606 return bus_log_create_error(r
);
608 r
= bus_append_ip_address_access(m
, AF_INET6
, &prefix
, 0);
610 return bus_log_create_error(r
);
612 } else if (is_localhost(eq
)) {
613 /* "localhost" is a shortcut for 127.0.0.0/8 and ::1/128 */
615 prefix
.in
.s_addr
= htobe32(0x7f000000);
616 r
= bus_append_ip_address_access(m
, AF_INET
, &prefix
, 8);
618 return bus_log_create_error(r
);
620 prefix
.in6
= (struct in6_addr
) IN6ADDR_LOOPBACK_INIT
;
621 r
= bus_append_ip_address_access(m
, AF_INET6
, &prefix
, 128);
625 } else if (streq(eq
, "link-local")) {
627 /* "link-local" is a shortcut for 169.254.0.0/16 and fe80::/64 */
629 prefix
.in
.s_addr
= htobe32((UINT32_C(169) << 24 | UINT32_C(254) << 16));
630 r
= bus_append_ip_address_access(m
, AF_INET
, &prefix
, 16);
632 return bus_log_create_error(r
);
634 prefix
.in6
= (struct in6_addr
) {
635 .s6_addr32
[0] = htobe32(0xfe800000)
637 r
= bus_append_ip_address_access(m
, AF_INET6
, &prefix
, 64);
639 return bus_log_create_error(r
);
641 } else if (streq(eq
, "multicast")) {
643 /* "multicast" is a shortcut for 224.0.0.0/4 and ff00::/8 */
645 prefix
.in
.s_addr
= htobe32((UINT32_C(224) << 24));
646 r
= bus_append_ip_address_access(m
, AF_INET
, &prefix
, 4);
648 return bus_log_create_error(r
);
650 prefix
.in6
= (struct in6_addr
) {
651 .s6_addr32
[0] = htobe32(0xff000000)
653 r
= bus_append_ip_address_access(m
, AF_INET6
, &prefix
, 8);
655 return bus_log_create_error(r
);
658 r
= in_addr_prefix_from_string_auto(eq
, &family
, &prefix
, &prefixlen
);
660 return log_error_errno(r
, "Failed to parse IP address prefix: %s", eq
);
662 r
= bus_append_ip_address_access(m
, family
, &prefix
, prefixlen
);
664 return bus_log_create_error(r
);
667 r
= sd_bus_message_close_container(m
);
669 return bus_log_create_error(r
);
671 r
= sd_bus_message_close_container(m
);
673 return bus_log_create_error(r
);
676 } else if (streq(field
, "CPUSchedulingPolicy")) {
679 n
= sched_policy_from_string(eq
);
681 return log_error_errno(r
, "Failed to parse CPUSchedulingPolicy: %s", eq
);
683 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
685 } else if (streq(field
, "CPUSchedulingPriority")) {
688 r
= safe_atoi(eq
, &n
);
690 return log_error_errno(r
, "Failed to parse CPUSchedulingPriority: %s", eq
);
691 if (!sched_priority_is_valid(n
))
692 return log_error_errno(r
, "Invalid CPUSchedulingPriority: %s", eq
);
694 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
696 } else if (streq(field
, "CPUAffinity")) {
697 _cleanup_cpu_free_ cpu_set_t
*cpuset
= NULL
;
700 ncpus
= parse_cpu_set(eq
, &cpuset
);
702 return log_error_errno(r
, "Failed to parse %s value: %s", field
, eq
);
704 r
= sd_bus_message_open_container(m
, 'v', "ay");
706 return bus_log_create_error(r
);
708 r
= sd_bus_message_append_array(m
, 'y', cpuset
, CPU_ALLOC_SIZE(ncpus
));
710 return bus_log_create_error(r
);
712 r
= sd_bus_message_close_container(m
);
714 } else if (streq(field
, "Nice")) {
717 r
= parse_nice(eq
, &n
);
719 return log_error_errno(r
, "Failed to parse nice value: %s", eq
);
721 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
723 } else if (streq(field
, "SystemCallFilter")) {
725 _cleanup_strv_free_
char **l
= NULL
;
735 if (whitelist
!= 0) {
736 r
= strv_extend(&l
, "@default");
742 _cleanup_free_
char *word
= NULL
;
744 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
746 return log_error_errno(r
, "Failed to parse %s value: %s", field
, eq
);
750 r
= strv_extend(&l
, word
);
755 r
= sd_bus_message_open_container(m
, 'v', "(bas)");
757 return bus_log_create_error(r
);
759 r
= sd_bus_message_open_container(m
, 'r', "bas");
761 return bus_log_create_error(r
);
763 r
= sd_bus_message_append_basic(m
, 'b', &whitelist
);
765 return bus_log_create_error(r
);
767 r
= sd_bus_message_append_strv(m
, l
);
769 return bus_log_create_error(r
);
771 r
= sd_bus_message_close_container(m
);
773 return bus_log_create_error(r
);
775 r
= sd_bus_message_close_container(m
);
777 return bus_log_create_error(r
);
779 } else if (streq(field
, "SystemCallArchitectures")) {
782 r
= sd_bus_message_open_container(m
, 'v', "as");
784 return bus_log_create_error(r
);
786 r
= sd_bus_message_open_container(m
, 'a', "s");
788 return bus_log_create_error(r
);
791 _cleanup_free_
char *word
= NULL
;
793 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
795 return log_error_errno(r
, "Failed to parse %s value: %s", field
, eq
);
799 r
= sd_bus_message_append_basic(m
, 's', word
);
801 return bus_log_create_error(r
);
804 r
= sd_bus_message_close_container(m
);
806 return bus_log_create_error(r
);
808 r
= sd_bus_message_close_container(m
);
810 } else if (streq(field
, "SystemCallErrorNumber")) {
815 return log_error_errno(r
, "Failed to parse %s value: %s", field
, eq
);
817 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
819 } else if (streq(field
, "RestrictAddressFamilies")) {
821 _cleanup_strv_free_
char **l
= NULL
;
831 _cleanup_free_
char *word
= NULL
;
833 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
835 return log_error_errno(r
, "Failed to parse %s value: %s", field
, eq
);
839 r
= strv_extend(&l
, word
);
844 r
= sd_bus_message_open_container(m
, 'v', "(bas)");
846 return bus_log_create_error(r
);
848 r
= sd_bus_message_open_container(m
, 'r', "bas");
850 return bus_log_create_error(r
);
852 r
= sd_bus_message_append_basic(m
, 'b', &whitelist
);
854 return bus_log_create_error(r
);
856 r
= sd_bus_message_append_strv(m
, l
);
858 return bus_log_create_error(r
);
860 r
= sd_bus_message_close_container(m
);
862 return bus_log_create_error(r
);
864 r
= sd_bus_message_close_container(m
);
866 return bus_log_create_error(r
);
868 } else if (streq(field
, "FileDescriptorStoreMax")) {
871 r
= safe_atou(eq
, &u
);
873 return log_error_errno(r
, "Failed to parse file descriptor store limit: %s", eq
);
875 r
= sd_bus_message_append(m
, "v", "u", (uint32_t) u
);
877 } else if (streq(field
, "IOSchedulingClass")) {
880 c
= ioprio_class_from_string(eq
);
882 return log_error_errno(r
, "Failed to parse IO scheduling class: %s", eq
);
884 r
= sd_bus_message_append(m
, "v", "i", (int32_t) c
);
886 } else if (streq(field
, "IOSchedulingPriority")) {
889 r
= ioprio_parse_priority(eq
, &q
);
891 return log_error_errno(r
, "Failed to parse IO scheduling priority: %s", eq
);
893 r
= sd_bus_message_append(m
, "v", "i", (int32_t) q
);
895 } else if (STR_IN_SET(field
, "Environment", "UnsetEnvironment", "PassEnvironment")) {
898 r
= sd_bus_message_open_container(m
, 'v', "as");
900 return bus_log_create_error(r
);
902 r
= sd_bus_message_open_container(m
, 'a', "s");
904 return bus_log_create_error(r
);
907 _cleanup_free_
char *word
= NULL
;
909 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_CUNESCAPE
);
911 return log_error_errno(r
, "Failed to parse Environment value %s: %m", eq
);
915 if (streq(field
, "Environment")) {
916 if (!env_assignment_is_valid(word
)) {
917 log_error("Invalid environment assignment: %s", word
);
920 } else if (streq(field
, "UnsetEnvironment")) {
921 if (!env_assignment_is_valid(word
) && !env_name_is_valid(word
)) {
922 log_error("Invalid environment name or assignment: %s", word
);
925 } else { /* PassEnvironment */
926 if (!env_name_is_valid(word
)) {
927 log_error("Invalid environment variable name: %s", word
);
932 r
= sd_bus_message_append_basic(m
, 's', word
);
934 return bus_log_create_error(r
);
937 r
= sd_bus_message_close_container(m
);
939 return bus_log_create_error(r
);
941 r
= sd_bus_message_close_container(m
);
943 } else if (streq(field
, "KillSignal")) {
946 sig
= signal_from_string_try_harder(eq
);
948 log_error("Failed to parse %s value %s.", field
, eq
);
952 r
= sd_bus_message_append(m
, "v", "i", sig
);
954 } else if (streq(field
, "TimerSlackNSec")) {
957 r
= parse_nsec(eq
, &n
);
959 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
961 r
= sd_bus_message_append(m
, "v", "t", n
);
962 } else if (streq(field
, "OOMScoreAdjust")) {
965 r
= safe_atoi(eq
, &oa
);
967 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
969 if (!oom_score_adjust_is_valid(oa
)) {
970 log_error("OOM score adjust value out of range");
974 r
= sd_bus_message_append(m
, "v", "i", oa
);
975 } else if (STR_IN_SET(field
, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
976 "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
979 r
= sd_bus_message_open_container(m
, 'v', "as");
981 return bus_log_create_error(r
);
983 r
= sd_bus_message_open_container(m
, 'a', "s");
985 return bus_log_create_error(r
);
988 _cleanup_free_
char *word
= NULL
;
991 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
993 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
997 if (!utf8_is_valid(word
)) {
998 log_error("Failed to parse %s value %s", field
, eq
);
1002 offset
= word
[0] == '-';
1003 offset
+= word
[offset
] == '+';
1005 if (!path_is_absolute(word
+ offset
)) {
1006 log_error("Failed to parse %s value %s", field
, eq
);
1010 path_kill_slashes(word
+ offset
);
1012 r
= sd_bus_message_append_basic(m
, 's', word
);
1014 return bus_log_create_error(r
);
1017 r
= sd_bus_message_close_container(m
);
1019 return bus_log_create_error(r
);
1021 r
= sd_bus_message_close_container(m
);
1023 } else if (streq(field
, "SupplementaryGroups")) {
1026 r
= sd_bus_message_open_container(m
, 'v', "as");
1028 return bus_log_create_error(r
);
1030 r
= sd_bus_message_open_container(m
, 'a', "s");
1032 return bus_log_create_error(r
);
1035 _cleanup_free_
char *word
= NULL
;
1037 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
1039 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
1043 if (!valid_user_group_name_or_id(word
)) {
1044 log_error("Failed to parse %s value %s", field
, eq
);
1048 r
= sd_bus_message_append_basic(m
, 's', word
);
1050 return bus_log_create_error(r
);
1053 r
= sd_bus_message_close_container(m
);
1055 return bus_log_create_error(r
);
1057 r
= sd_bus_message_close_container(m
);
1059 } else if (STR_IN_SET(field
, "RuntimeDirectoryMode", "StateDirectoryMode", "CacheDirectoryMode", "LogsDirectoryMode", "ConfigurationDirectoryMode", "UMask")) {
1062 r
= parse_mode(eq
, &mode
);
1064 return log_error_errno(r
, "Failed to parse %s value %s", field
, eq
);
1066 r
= sd_bus_message_append(m
, "v", "u", mode
);
1068 } else if (STR_IN_SET(field
, "RuntimeDirectory", "StateDirectory", "CacheDirectory", "LogsDirectory", "ConfigurationDirectory")) {
1071 r
= sd_bus_message_open_container(m
, 'v', "as");
1073 return bus_log_create_error(r
);
1075 r
= sd_bus_message_open_container(m
, 'a', "s");
1077 return bus_log_create_error(r
);
1080 _cleanup_free_
char *word
= NULL
;
1082 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
1086 return log_error_errno(r
, "Failed to parse %s value %s", field
, eq
);
1090 r
= sd_bus_message_append_basic(m
, 's', word
);
1092 return bus_log_create_error(r
);
1095 r
= sd_bus_message_close_container(m
);
1097 return bus_log_create_error(r
);
1099 r
= sd_bus_message_close_container(m
);
1101 } else if (streq(field
, "RestrictNamespaces")) {
1102 bool invert
= false;
1103 unsigned long flags
= 0;
1110 r
= parse_boolean(eq
);
1114 flags
= NAMESPACE_FLAGS_ALL
;
1116 r
= namespace_flag_from_string_many(eq
, &flags
);
1118 return log_error_errno(r
, "Failed to parse %s value %s.", field
, eq
);
1122 flags
= (~flags
) & NAMESPACE_FLAGS_ALL
;
1124 r
= sd_bus_message_append(m
, "v", "t", (uint64_t) flags
);
1125 } else if ((dep
= unit_dependency_from_string(field
)) >= 0)
1126 r
= sd_bus_message_append(m
, "v", "as", 1, eq
);
1127 else if (streq(field
, "MountFlags")) {
1130 r
= mount_propagation_flags_from_string(eq
, &f
);
1132 return log_error_errno(r
, "Failed to parse mount propagation flags: %s", eq
);
1134 r
= sd_bus_message_append(m
, "v", "t", (uint64_t) f
);
1135 } else if (STR_IN_SET(field
, "BindPaths", "BindReadOnlyPaths")) {
1138 r
= sd_bus_message_open_container(m
, 'v', "a(ssbt)");
1142 r
= sd_bus_message_open_container(m
, 'a', "(ssbt)");
1147 _cleanup_free_
char *source
= NULL
, *destination
= NULL
;
1148 char *s
= NULL
, *d
= NULL
;
1149 bool ignore_enoent
= false;
1150 uint64_t flags
= MS_REC
;
1152 r
= extract_first_word(&p
, &source
, ":" WHITESPACE
, EXTRACT_QUOTES
|EXTRACT_DONT_COALESCE_SEPARATORS
);
1154 return log_error_errno(r
, "Failed to parse argument: %m");
1160 ignore_enoent
= true;
1164 if (p
&& p
[-1] == ':') {
1165 r
= extract_first_word(&p
, &destination
, ":" WHITESPACE
, EXTRACT_QUOTES
|EXTRACT_DONT_COALESCE_SEPARATORS
);
1167 return log_error_errno(r
, "Failed to parse argument: %m");
1169 log_error("Missing argument after ':': %s", eq
);
1175 if (p
&& p
[-1] == ':') {
1176 _cleanup_free_
char *options
= NULL
;
1178 r
= extract_first_word(&p
, &options
, NULL
, EXTRACT_QUOTES
);
1180 return log_error_errno(r
, "Failed to parse argument: %m");
1182 if (isempty(options
) || streq(options
, "rbind"))
1184 else if (streq(options
, "norbind"))
1187 log_error("Unknown options: %s", eq
);
1195 r
= sd_bus_message_append(m
, "(ssbt)", s
, d
, ignore_enoent
, flags
);
1200 r
= sd_bus_message_close_container(m
);
1204 r
= sd_bus_message_close_container(m
);
1206 } else if (STR_IN_SET(field
, "ExecStartPre", "ExecStart", "ExecStartPost",
1207 "ExecReload", "ExecStop", "ExecStopPost")) {
1209 bool ignore_failure
= false, explicit_path
= false, done
= false;
1210 _cleanup_strv_free_
char **l
= NULL
;
1211 _cleanup_free_
char *path
= NULL
;
1220 ignore_failure
= true;
1229 explicit_path
= true;
1236 /* The bus API doesn't support +, ! and !! currently, unfortunately. :-( */
1237 log_error("Sorry, but +, ! and !! are currently not supported for transient services.");
1246 if (explicit_path
) {
1247 r
= extract_first_word(&eq
, &path
, NULL
, EXTRACT_QUOTES
|EXTRACT_CUNESCAPE
);
1249 return log_error_errno(r
, "Failed to parse path: %m");
1252 r
= strv_split_extract(&l
, eq
, NULL
, EXTRACT_QUOTES
|EXTRACT_CUNESCAPE
);
1254 return log_error_errno(r
, "Failed to parse command line: %m");
1256 r
= sd_bus_message_open_container(m
, 'v', "a(sasb)");
1260 r
= sd_bus_message_open_container(m
, 'a', "(sasb)");
1264 if (strv_length(l
) > 0) {
1266 r
= sd_bus_message_open_container(m
, 'r', "sasb");
1270 r
= sd_bus_message_append(m
, "s", path
?: l
[0]);
1274 r
= sd_bus_message_append_strv(m
, l
);
1278 r
= sd_bus_message_append(m
, "b", ignore_failure
);
1282 r
= sd_bus_message_close_container(m
);
1287 r
= sd_bus_message_close_container(m
);
1291 r
= sd_bus_message_close_container(m
);
1293 } else if (STR_IN_SET(field
,
1294 "OnActiveSec", "OnBootSec", "OnStartupSec",
1295 "OnUnitActiveSec","OnUnitInactiveSec")) {
1298 r
= parse_sec(eq
, &t
);
1300 return log_error_errno(r
, "Failed to parse %s= parameter: %s", field
, eq
);
1302 r
= sd_bus_message_append(m
, "v", "t", t
);
1305 log_error("Unknown assignment: %s", assignment
);
1311 return bus_log_create_error(r
);
1313 r
= sd_bus_message_close_container(m
);
1315 return bus_log_create_error(r
);
1320 int bus_append_unit_property_assignment_many(sd_bus_message
*m
, char **l
) {
1326 STRV_FOREACH(i
, l
) {
1327 r
= bus_append_unit_property_assignment(m
, *i
);
1335 typedef struct BusWaitForJobs
{
1342 sd_bus_slot
*slot_job_removed
;
1343 sd_bus_slot
*slot_disconnected
;
1346 static int match_disconnected(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1349 log_error("Warning! D-Bus connection terminated.");
1350 sd_bus_close(sd_bus_message_get_bus(m
));
1355 static int match_job_removed(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1356 const char *path
, *unit
, *result
;
1357 BusWaitForJobs
*d
= userdata
;
1365 r
= sd_bus_message_read(m
, "uoss", &id
, &path
, &unit
, &result
);
1367 bus_log_parse_error(r
);
1371 found
= set_remove(d
->jobs
, (char*) path
);
1377 if (!isempty(result
))
1378 d
->result
= strdup(result
);
1381 d
->name
= strdup(unit
);
1386 void bus_wait_for_jobs_free(BusWaitForJobs
*d
) {
1390 set_free_free(d
->jobs
);
1392 sd_bus_slot_unref(d
->slot_disconnected
);
1393 sd_bus_slot_unref(d
->slot_job_removed
);
1395 sd_bus_unref(d
->bus
);
1403 int bus_wait_for_jobs_new(sd_bus
*bus
, BusWaitForJobs
**ret
) {
1404 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*d
= NULL
;
1410 d
= new0(BusWaitForJobs
, 1);
1414 d
->bus
= sd_bus_ref(bus
);
1416 /* When we are a bus client we match by sender. Direct
1417 * connections OTOH have no initialized sender field, and
1418 * hence we ignore the sender then */
1419 r
= sd_bus_add_match(
1421 &d
->slot_job_removed
,
1424 "sender='org.freedesktop.systemd1',"
1425 "interface='org.freedesktop.systemd1.Manager',"
1426 "member='JobRemoved',"
1427 "path='/org/freedesktop/systemd1'" :
1429 "interface='org.freedesktop.systemd1.Manager',"
1430 "member='JobRemoved',"
1431 "path='/org/freedesktop/systemd1'",
1432 match_job_removed
, d
);
1436 r
= sd_bus_add_match(
1438 &d
->slot_disconnected
,
1440 "sender='org.freedesktop.DBus.Local',"
1441 "interface='org.freedesktop.DBus.Local',"
1442 "member='Disconnected'",
1443 match_disconnected
, d
);
1453 static int bus_process_wait(sd_bus
*bus
) {
1457 r
= sd_bus_process(bus
, NULL
);
1463 r
= sd_bus_wait(bus
, (uint64_t) -1);
1469 static int bus_job_get_service_result(BusWaitForJobs
*d
, char **result
) {
1470 _cleanup_free_
char *dbus_path
= NULL
;
1476 if (!endswith(d
->name
, ".service"))
1479 dbus_path
= unit_dbus_path_from_name(d
->name
);
1483 return sd_bus_get_property_string(d
->bus
,
1484 "org.freedesktop.systemd1",
1486 "org.freedesktop.systemd1.Service",
1492 static const struct {
1493 const char *result
, *explanation
;
1494 } explanations
[] = {
1495 { "resources", "of unavailable resources or another system error" },
1496 { "protocol", "the service did not take the steps required by its unit configuration" },
1497 { "timeout", "a timeout was exceeded" },
1498 { "exit-code", "the control process exited with error code" },
1499 { "signal", "a fatal signal was delivered to the control process" },
1500 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
1501 { "watchdog", "the service failed to send watchdog ping" },
1502 { "start-limit", "start of the service was attempted too often" }
1505 static void log_job_error_with_service_result(const char* service
, const char *result
, const char* const* extra_args
) {
1506 _cleanup_free_
char *service_shell_quoted
= NULL
;
1507 const char *systemctl
= "systemctl", *journalctl
= "journalctl";
1511 service_shell_quoted
= shell_maybe_quote(service
, ESCAPE_BACKSLASH
);
1513 if (!strv_isempty((char**) extra_args
)) {
1514 _cleanup_free_
char *t
;
1516 t
= strv_join((char**) extra_args
, " ");
1517 systemctl
= strjoina("systemctl ", t
? : "<args>");
1518 journalctl
= strjoina("journalctl ", t
? : "<args>");
1521 if (!isempty(result
)) {
1524 for (i
= 0; i
< ELEMENTSOF(explanations
); ++i
)
1525 if (streq(result
, explanations
[i
].result
))
1528 if (i
< ELEMENTSOF(explanations
)) {
1529 log_error("Job for %s failed because %s.\n"
1530 "See \"%s status %s\" and \"%s -xe\" for details.\n",
1532 explanations
[i
].explanation
,
1534 service_shell_quoted
?: "<service>",
1540 log_error("Job for %s failed.\n"
1541 "See \"%s status %s\" and \"%s -xe\" for details.\n",
1544 service_shell_quoted
?: "<service>",
1548 /* For some results maybe additional explanation is required */
1549 if (streq_ptr(result
, "start-limit"))
1550 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
1551 "followed by \"%1$s start %2$s\" again.",
1553 service_shell_quoted
?: "<service>");
1556 static int check_wait_response(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
1562 if (streq(d
->result
, "canceled"))
1563 log_error("Job for %s canceled.", strna(d
->name
));
1564 else if (streq(d
->result
, "timeout"))
1565 log_error("Job for %s timed out.", strna(d
->name
));
1566 else if (streq(d
->result
, "dependency"))
1567 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d
->name
));
1568 else if (streq(d
->result
, "invalid"))
1569 log_error("%s is not active, cannot reload.", strna(d
->name
));
1570 else if (streq(d
->result
, "assert"))
1571 log_error("Assertion failed on job for %s.", strna(d
->name
));
1572 else if (streq(d
->result
, "unsupported"))
1573 log_error("Operation on or unit type of %s not supported on this system.", strna(d
->name
));
1574 else if (streq(d
->result
, "collected"))
1575 log_error("Queued job for %s was garbage collected.", strna(d
->name
));
1576 else if (!STR_IN_SET(d
->result
, "done", "skipped")) {
1578 _cleanup_free_
char *result
= NULL
;
1581 q
= bus_job_get_service_result(d
, &result
);
1583 log_debug_errno(q
, "Failed to get Result property of unit %s: %m", d
->name
);
1585 log_job_error_with_service_result(d
->name
, result
, extra_args
);
1587 log_error("Job failed. See \"journalctl -xe\" for details.");
1591 if (STR_IN_SET(d
->result
, "canceled", "collected"))
1593 else if (streq(d
->result
, "timeout"))
1595 else if (streq(d
->result
, "dependency"))
1597 else if (streq(d
->result
, "invalid"))
1599 else if (streq(d
->result
, "assert"))
1601 else if (streq(d
->result
, "unsupported"))
1603 else if (!STR_IN_SET(d
->result
, "done", "skipped"))
1609 int bus_wait_for_jobs(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
1614 while (!set_isempty(d
->jobs
)) {
1617 q
= bus_process_wait(d
->bus
);
1619 return log_error_errno(q
, "Failed to wait for response: %m");
1622 q
= check_wait_response(d
, quiet
, extra_args
);
1623 /* Return the first error as it is most likely to be
1625 if (q
< 0 && r
== 0)
1628 log_debug_errno(q
, "Got result %s/%m for job %s", strna(d
->result
), strna(d
->name
));
1631 d
->name
= mfree(d
->name
);
1632 d
->result
= mfree(d
->result
);
1638 int bus_wait_for_jobs_add(BusWaitForJobs
*d
, const char *path
) {
1643 r
= set_ensure_allocated(&d
->jobs
, &string_hash_ops
);
1647 return set_put_strdup(d
->jobs
, path
);
1650 int bus_wait_for_jobs_one(BusWaitForJobs
*d
, const char *path
, bool quiet
) {
1653 r
= bus_wait_for_jobs_add(d
, path
);
1657 return bus_wait_for_jobs(d
, quiet
, NULL
);
1660 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message
*m
, bool quiet
, UnitFileChange
**changes
, unsigned *n_changes
) {
1661 const char *type
, *path
, *source
;
1664 /* changes is dereferenced when calling unit_file_dump_changes() later,
1665 * so we have to make sure this is not NULL. */
1669 r
= sd_bus_message_enter_container(m
, SD_BUS_TYPE_ARRAY
, "(sss)");
1671 return bus_log_parse_error(r
);
1673 while ((r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
)) > 0) {
1674 /* We expect only "success" changes to be sent over the bus.
1675 Hence, reject anything negative. */
1676 UnitFileChangeType ch
= unit_file_change_type_from_string(type
);
1679 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type
, path
);
1683 r
= unit_file_changes_add(changes
, n_changes
, ch
, path
, source
);
1688 return bus_log_parse_error(r
);
1690 r
= sd_bus_message_exit_container(m
);
1692 return bus_log_parse_error(r
);
1694 unit_file_dump_changes(0, NULL
, *changes
, *n_changes
, quiet
);
1700 bool is_const
; /* If false, cgroup_path should be free()'d */
1702 Hashmap
*pids
; /* PID → process name */
1705 struct CGroupInfo
*parent
;
1706 LIST_FIELDS(struct CGroupInfo
, siblings
);
1707 LIST_HEAD(struct CGroupInfo
, children
);
1711 static bool IS_ROOT(const char *p
) {
1712 return isempty(p
) || streq(p
, "/");
1715 static int add_cgroup(Hashmap
*cgroups
, const char *path
, bool is_const
, struct CGroupInfo
**ret
) {
1716 struct CGroupInfo
*parent
= NULL
, *cg
;
1725 cg
= hashmap_get(cgroups
, path
);
1731 if (!IS_ROOT(path
)) {
1734 e
= strrchr(path
, '/');
1738 pp
= strndupa(path
, e
- path
);
1742 r
= add_cgroup(cgroups
, pp
, false, &parent
);
1747 cg
= new0(struct CGroupInfo
, 1);
1752 cg
->cgroup_path
= (char*) path
;
1754 cg
->cgroup_path
= strdup(path
);
1755 if (!cg
->cgroup_path
) {
1761 cg
->is_const
= is_const
;
1762 cg
->parent
= parent
;
1764 r
= hashmap_put(cgroups
, cg
->cgroup_path
, cg
);
1767 free(cg
->cgroup_path
);
1773 LIST_PREPEND(siblings
, parent
->children
, cg
);
1774 parent
->n_children
++;
1781 static int add_process(
1787 struct CGroupInfo
*cg
;
1794 r
= add_cgroup(cgroups
, path
, true, &cg
);
1798 r
= hashmap_ensure_allocated(&cg
->pids
, &trivial_hash_ops
);
1802 return hashmap_put(cg
->pids
, PID_TO_PTR(pid
), (void*) name
);
1805 static void remove_cgroup(Hashmap
*cgroups
, struct CGroupInfo
*cg
) {
1809 while (cg
->children
)
1810 remove_cgroup(cgroups
, cg
->children
);
1812 hashmap_remove(cgroups
, cg
->cgroup_path
);
1815 free(cg
->cgroup_path
);
1817 hashmap_free(cg
->pids
);
1820 LIST_REMOVE(siblings
, cg
->parent
->children
, cg
);
1825 static int cgroup_info_compare_func(const void *a
, const void *b
) {
1826 const struct CGroupInfo
*x
= *(const struct CGroupInfo
* const*) a
, *y
= *(const struct CGroupInfo
* const*) b
;
1831 return strcmp(x
->cgroup_path
, y
->cgroup_path
);
1834 static int dump_processes(
1836 const char *cgroup_path
,
1839 OutputFlags flags
) {
1841 struct CGroupInfo
*cg
;
1846 if (IS_ROOT(cgroup_path
))
1849 cg
= hashmap_get(cgroups
, cgroup_path
);
1853 if (!hashmap_isempty(cg
->pids
)) {
1861 /* Order processes by their PID */
1862 pids
= newa(pid_t
, hashmap_size(cg
->pids
));
1864 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
)
1865 pids
[n
++] = PTR_TO_PID(pidp
);
1867 assert(n
== hashmap_size(cg
->pids
));
1868 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1870 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1872 for (i
= 0; i
< n
; i
++) {
1873 _cleanup_free_
char *e
= NULL
;
1874 const char *special
;
1877 name
= hashmap_get(cg
->pids
, PID_TO_PTR(pids
[i
]));
1880 if (n_columns
!= 0) {
1883 k
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1885 e
= ellipsize(name
, k
, 100);
1890 more
= i
+1 < n
|| cg
->children
;
1891 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1893 fprintf(stdout
, "%s%s%*"PID_PRI
" %s\n",
1902 struct CGroupInfo
**children
, *child
;
1905 /* Order subcgroups by their name */
1906 children
= newa(struct CGroupInfo
*, cg
->n_children
);
1907 LIST_FOREACH(siblings
, child
, cg
->children
)
1908 children
[n
++] = child
;
1909 assert(n
== cg
->n_children
);
1910 qsort_safe(children
, n
, sizeof(struct CGroupInfo
*), cgroup_info_compare_func
);
1913 n_columns
= MAX(LESS_BY(n_columns
, 2U), 20U);
1915 for (i
= 0; i
< n
; i
++) {
1916 _cleanup_free_
char *pp
= NULL
;
1917 const char *name
, *special
;
1920 child
= children
[i
];
1922 name
= strrchr(child
->cgroup_path
, '/');
1928 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1930 fputs(prefix
, stdout
);
1931 fputs(special
, stdout
);
1932 fputs(name
, stdout
);
1933 fputc('\n', stdout
);
1935 special
= special_glyph(more
? TREE_VERTICAL
: TREE_SPACE
);
1937 pp
= strappend(prefix
, special
);
1941 r
= dump_processes(cgroups
, child
->cgroup_path
, pp
, n_columns
, flags
);
1951 static int dump_extra_processes(
1955 OutputFlags flags
) {
1957 _cleanup_free_ pid_t
*pids
= NULL
;
1958 _cleanup_hashmap_free_ Hashmap
*names
= NULL
;
1959 struct CGroupInfo
*cg
;
1960 size_t n_allocated
= 0, n
= 0, k
;
1964 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1965 * combined, sorted, linear list. */
1967 HASHMAP_FOREACH(cg
, cgroups
, i
) {
1975 if (hashmap_isempty(cg
->pids
))
1978 r
= hashmap_ensure_allocated(&names
, &trivial_hash_ops
);
1982 if (!GREEDY_REALLOC(pids
, n_allocated
, n
+ hashmap_size(cg
->pids
)))
1985 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
) {
1986 pids
[n
++] = PTR_TO_PID(pidp
);
1988 r
= hashmap_put(names
, pidp
, (void*) name
);
1997 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1998 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
2000 for (k
= 0; k
< n
; k
++) {
2001 _cleanup_free_
char *e
= NULL
;
2004 name
= hashmap_get(names
, PID_TO_PTR(pids
[k
]));
2007 if (n_columns
!= 0) {
2010 z
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
2012 e
= ellipsize(name
, z
, 100);
2017 fprintf(stdout
, "%s%s %*" PID_PRI
" %s\n",
2019 special_glyph(TRIANGULAR_BULLET
),
2027 int unit_show_processes(
2030 const char *cgroup_path
,
2034 sd_bus_error
*error
) {
2036 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
2037 Hashmap
*cgroups
= NULL
;
2038 struct CGroupInfo
*cg
;
2044 if (flags
& OUTPUT_FULL_WIDTH
)
2046 else if (n_columns
<= 0)
2047 n_columns
= columns();
2049 prefix
= strempty(prefix
);
2051 r
= sd_bus_call_method(
2053 "org.freedesktop.systemd1",
2054 "/org/freedesktop/systemd1",
2055 "org.freedesktop.systemd1.Manager",
2064 cgroups
= hashmap_new(&string_hash_ops
);
2068 r
= sd_bus_message_enter_container(reply
, 'a', "(sus)");
2073 const char *path
= NULL
, *name
= NULL
;
2076 r
= sd_bus_message_read(reply
, "(sus)", &path
, &pid
, &name
);
2082 r
= add_process(cgroups
, path
, pid
, name
);
2087 r
= sd_bus_message_exit_container(reply
);
2091 r
= dump_processes(cgroups
, cgroup_path
, prefix
, n_columns
, flags
);
2095 r
= dump_extra_processes(cgroups
, prefix
, n_columns
, flags
);
2098 while ((cg
= hashmap_first(cgroups
)))
2099 remove_cgroup(cgroups
, cg
);
2101 hashmap_free(cgroups
);