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);
159 /* Change suffix Sec → USec */
160 strcpy(mempcpy(n
, field
, l
- 3), "USec");
161 r
= sd_bus_message_append(m
, "sv", n
, "t", t
);
164 } else if (streq(field
, "LogExtraFields")) {
166 r
= sd_bus_message_append(m
, "s", "LogExtraFields");
170 r
= sd_bus_message_open_container(m
, 'v', "aay");
174 r
= sd_bus_message_open_container(m
, 'a', "ay");
178 r
= sd_bus_message_append_array(m
, 'y', eq
, strlen(eq
));
182 r
= sd_bus_message_close_container(m
);
186 r
= sd_bus_message_close_container(m
);
189 } else if (STR_IN_SET(field
, "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit")) {
192 if (isempty(eq
) || streq(eq
, "infinity"))
193 bytes
= CGROUP_LIMIT_MAX
;
195 r
= parse_percent(eq
);
199 /* When this is a percentage we'll convert this into a relative value in the range
200 * 0…UINT32_MAX and pass it in the MemoryLowScale property (and related
201 * ones). This way the physical memory size can be determined server-side */
203 n
= strjoina(field
, "Scale");
204 r
= sd_bus_message_append(m
, "sv", n
, "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
208 r
= parse_size(eq
, 1024, &bytes
);
210 return log_error_errno(r
, "Failed to parse bytes specification %s", assignment
);
214 r
= sd_bus_message_append(m
, "sv", field
, "t", bytes
);
217 } else if (streq(field
, "Delegate")) {
219 r
= parse_boolean(eq
);
223 r
= sd_bus_message_append(m
, "s", "DelegateControllers");
227 r
= sd_bus_message_open_container(m
, 'v', "as");
231 r
= sd_bus_message_open_container(m
, 'a', "s");
236 _cleanup_free_
char *word
= NULL
;
238 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
244 return log_error_errno(r
, "Invalid syntax: %s", eq
);
246 r
= sd_bus_message_append(m
, "s", word
);
251 r
= sd_bus_message_close_container(m
);
255 r
= sd_bus_message_close_container(m
);
257 r
= sd_bus_message_append(m
, "sv", "Delegate", "b", r
);
261 } else if (streq(field
, "TasksMax")) {
264 if (isempty(eq
) || streq(eq
, "infinity"))
267 r
= parse_percent(eq
);
269 r
= sd_bus_message_append(m
, "sv", "TasksMaxScale", "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
272 r
= safe_atou64(eq
, &t
);
274 return log_error_errno(r
, "Failed to parse maximum tasks specification %s", assignment
);
279 r
= sd_bus_message_append(m
, "sv", "TasksMax", "t", t
);
282 } else if (STR_IN_SET(field
, "StandardInput", "StandardOutput", "StandardError")) {
283 const char *n
, *appended
;
285 n
= startswith(eq
, "fd:");
287 appended
= strjoina(field
, "FileDescriptorName");
288 r
= sd_bus_message_append(m
, "sv", appended
, "s", n
);
290 } else if ((n
= startswith(eq
, "file:"))) {
291 appended
= strjoina(field
, "File");
292 r
= sd_bus_message_append(m
, "sv", appended
, "s", n
);
294 r
= sd_bus_message_append(m
, "sv", field
, "s", eq
);
298 } else if (streq(field
, "StandardInputText")) {
299 _cleanup_free_
char *unescaped
= NULL
;
301 r
= cunescape(eq
, 0, &unescaped
);
303 return log_error_errno(r
, "Failed to unescape text '%s': %m", eq
);
305 if (!strextend(&unescaped
, "\n", NULL
))
308 /* Note that we don't expand specifiers here, but that should be OK, as this is a programmatic
309 * interface anyway */
311 r
= sd_bus_message_append(m
, "s", "StandardInputData");
313 return bus_log_create_error(r
);
315 r
= sd_bus_message_open_container(m
, 'v', "ay");
317 return bus_log_create_error(r
);
319 r
= sd_bus_message_append_array(m
, 'y', unescaped
, strlen(unescaped
));
321 return bus_log_create_error(r
);
323 r
= sd_bus_message_close_container(m
);
327 r
= sd_bus_message_append_basic(m
, SD_BUS_TYPE_STRING
, field
);
329 return bus_log_create_error(r
);
331 rl
= rlimit_from_string(field
);
336 r
= rlimit_parse(rl
, eq
, &l
);
338 return log_error_errno(r
, "Failed to parse resource limit: %s", eq
);
340 r
= sd_bus_message_append(m
, "v", "t", l
.rlim_max
);
342 return bus_log_create_error(r
);
344 r
= sd_bus_message_close_container(m
);
346 return bus_log_create_error(r
);
348 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
350 return bus_log_create_error(r
);
352 sn
= strjoina(field
, "Soft");
353 r
= sd_bus_message_append(m
, "sv", sn
, "t", l
.rlim_cur
);
355 } else if (STR_IN_SET(field
,
356 "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting",
357 "TasksAccounting", "IPAccounting", "SendSIGHUP", "SendSIGKILL", "WakeSystem",
358 "DefaultDependencies", "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "TTYVTDisallocate",
359 "RemainAfterExit", "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers",
360 "NoNewPrivileges", "SyslogLevelPrefix", "RemainAfterElapse", "Persistent",
361 "MemoryDenyWriteExecute", "RestrictRealtime", "DynamicUser", "RemoveIPC",
362 "ProtectKernelTunables", "ProtectKernelModules", "ProtectControlGroups", "MountAPIVFS",
363 "CPUSchedulingResetOnFork", "LockPersonality")) {
365 r
= parse_boolean(eq
);
367 return log_error_errno(r
, "Failed to parse boolean assignment %s.", assignment
);
369 r
= sd_bus_message_append(m
, "v", "b", r
);
371 } else if (STR_IN_SET(field
, "CPUWeight", "StartupCPUWeight")) {
374 r
= cg_weight_parse(eq
, &u
);
376 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
378 r
= sd_bus_message_append(m
, "v", "t", u
);
380 } else if (STR_IN_SET(field
, "CPUShares", "StartupCPUShares")) {
383 r
= cg_cpu_shares_parse(eq
, &u
);
385 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
387 r
= sd_bus_message_append(m
, "v", "t", u
);
389 } else if (STR_IN_SET(field
, "IOWeight", "StartupIOWeight")) {
392 r
= cg_weight_parse(eq
, &u
);
394 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
396 r
= sd_bus_message_append(m
, "v", "t", u
);
398 } else if (STR_IN_SET(field
, "BlockIOWeight", "StartupBlockIOWeight")) {
401 r
= cg_blkio_weight_parse(eq
, &u
);
403 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
405 r
= sd_bus_message_append(m
, "v", "t", u
);
407 } else if (STR_IN_SET(field
,
408 "User", "Group", "DevicePolicy", "KillMode",
409 "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
410 "Description", "Slice", "Type", "WorkingDirectory",
411 "RootDirectory", "SyslogIdentifier", "ProtectSystem",
412 "ProtectHome", "SELinuxContext", "Restart", "RootImage",
413 "NotifyAccess", "RuntimeDirectoryPreserve", "Personality",
414 "KeyringMode", "CollectMode", "FailureAction", "SuccessAction",
417 r
= sd_bus_message_append(m
, "v", "s", eq
);
419 else if (streq(field
, "StandardInputData")) {
420 _cleanup_free_
void *decoded
= NULL
;
423 r
= unbase64mem(eq
, (size_t) -1, &decoded
, &sz
);
425 return log_error_errno(r
, "Failed to decode base64 data '%s': %m", eq
);
427 r
= sd_bus_message_open_container(m
, 'v', "ay");
429 return bus_log_create_error(r
);
431 r
= sd_bus_message_append_array(m
, 'y', decoded
, sz
);
433 return bus_log_create_error(r
);
435 r
= sd_bus_message_close_container(m
);
437 } else if (STR_IN_SET(field
, "AppArmorProfile", "SmackProcessLabel")) {
449 r
= sd_bus_message_append(m
, "v", "(bs)", ignore
, s
);
451 } else if (STR_IN_SET(field
, "SyslogLevel", "LogLevelMax")) {
454 level
= log_level_from_string(eq
);
456 log_error("Failed to parse %s value %s.", field
, eq
);
460 r
= sd_bus_message_append(m
, "v", "i", level
);
462 } else if (streq(field
, "SyslogFacility")) {
465 facility
= log_facility_unshifted_from_string(eq
);
467 log_error("Failed to parse %s value %s.", field
, eq
);
471 r
= sd_bus_message_append(m
, "v", "i", facility
);
473 } else if (streq(field
, "SecureBits")) {
475 r
= secure_bits_from_string(eq
);
477 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
479 r
= sd_bus_message_append(m
, "v", "i", r
);
481 } else if (STR_IN_SET(field
, "CapabilityBoundingSet", "AmbientCapabilities")) {
492 r
= capability_set_from_string(p
, &sum
);
494 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
496 sum
= invert
? ~sum
: sum
;
498 r
= sd_bus_message_append(m
, "v", "t", sum
);
500 } else if (streq(field
, "DeviceAllow")) {
503 r
= sd_bus_message_append(m
, "v", "a(ss)", 0);
505 const char *path
, *rwm
, *e
;
509 path
= strndupa(eq
, e
- eq
);
516 if (!is_deviceallow_pattern(path
)) {
517 log_error("%s is not a device file in /dev.", path
);
521 r
= sd_bus_message_append(m
, "v", "a(ss)", 1, path
, rwm
);
524 } else if (cgroup_io_limit_type_from_string(field
) >= 0 || STR_IN_SET(field
, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
527 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
529 const char *path
, *bandwidth
, *e
;
534 path
= strndupa(eq
, e
- eq
);
537 log_error("Failed to parse %s value %s.", field
, eq
);
541 if (!path_startswith(path
, "/dev")) {
542 log_error("%s is not a device file in /dev.", path
);
546 if (streq(bandwidth
, "infinity")) {
547 bytes
= CGROUP_LIMIT_MAX
;
549 r
= parse_size(bandwidth
, 1000, &bytes
);
551 return log_error_errno(r
, "Failed to parse byte value %s: %m", bandwidth
);
554 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, bytes
);
557 } else if (STR_IN_SET(field
, "IODeviceWeight", "BlockIODeviceWeight")) {
560 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
562 const char *path
, *weight
, *e
;
567 path
= strndupa(eq
, e
- eq
);
570 log_error("Failed to parse %s value %s.", field
, eq
);
574 if (!path_startswith(path
, "/dev")) {
575 log_error("%s is not a device file in /dev.", path
);
579 r
= safe_atou64(weight
, &u
);
581 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, weight
);
583 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, u
);
586 } else if (STR_IN_SET(field
, "IPAddressAllow", "IPAddressDeny")) {
589 r
= sd_bus_message_append(m
, "v", "a(iayu)", 0);
591 unsigned char prefixlen
;
592 union in_addr_union prefix
= {};
595 r
= sd_bus_message_open_container(m
, 'v', "a(iayu)");
597 return bus_log_create_error(r
);
599 r
= sd_bus_message_open_container(m
, 'a', "(iayu)");
601 return bus_log_create_error(r
);
603 if (streq(eq
, "any")) {
604 /* "any" is a shortcut for 0.0.0.0/0 and ::/0 */
606 r
= bus_append_ip_address_access(m
, AF_INET
, &prefix
, 0);
608 return bus_log_create_error(r
);
610 r
= bus_append_ip_address_access(m
, AF_INET6
, &prefix
, 0);
612 return bus_log_create_error(r
);
614 } else if (is_localhost(eq
)) {
615 /* "localhost" is a shortcut for 127.0.0.0/8 and ::1/128 */
617 prefix
.in
.s_addr
= htobe32(0x7f000000);
618 r
= bus_append_ip_address_access(m
, AF_INET
, &prefix
, 8);
620 return bus_log_create_error(r
);
622 prefix
.in6
= (struct in6_addr
) IN6ADDR_LOOPBACK_INIT
;
623 r
= bus_append_ip_address_access(m
, AF_INET6
, &prefix
, 128);
627 } else if (streq(eq
, "link-local")) {
629 /* "link-local" is a shortcut for 169.254.0.0/16 and fe80::/64 */
631 prefix
.in
.s_addr
= htobe32((UINT32_C(169) << 24 | UINT32_C(254) << 16));
632 r
= bus_append_ip_address_access(m
, AF_INET
, &prefix
, 16);
634 return bus_log_create_error(r
);
636 prefix
.in6
= (struct in6_addr
) {
637 .s6_addr32
[0] = htobe32(0xfe800000)
639 r
= bus_append_ip_address_access(m
, AF_INET6
, &prefix
, 64);
641 return bus_log_create_error(r
);
643 } else if (streq(eq
, "multicast")) {
645 /* "multicast" is a shortcut for 224.0.0.0/4 and ff00::/8 */
647 prefix
.in
.s_addr
= htobe32((UINT32_C(224) << 24));
648 r
= bus_append_ip_address_access(m
, AF_INET
, &prefix
, 4);
650 return bus_log_create_error(r
);
652 prefix
.in6
= (struct in6_addr
) {
653 .s6_addr32
[0] = htobe32(0xff000000)
655 r
= bus_append_ip_address_access(m
, AF_INET6
, &prefix
, 8);
657 return bus_log_create_error(r
);
660 r
= in_addr_prefix_from_string_auto(eq
, &family
, &prefix
, &prefixlen
);
662 return log_error_errno(r
, "Failed to parse IP address prefix: %s", eq
);
664 r
= bus_append_ip_address_access(m
, family
, &prefix
, prefixlen
);
666 return bus_log_create_error(r
);
669 r
= sd_bus_message_close_container(m
);
671 return bus_log_create_error(r
);
673 r
= sd_bus_message_close_container(m
);
675 return bus_log_create_error(r
);
678 } else if (streq(field
, "CPUSchedulingPolicy")) {
681 n
= sched_policy_from_string(eq
);
683 return log_error_errno(r
, "Failed to parse CPUSchedulingPolicy: %s", eq
);
685 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
687 } else if (streq(field
, "CPUSchedulingPriority")) {
690 r
= safe_atoi(eq
, &n
);
692 return log_error_errno(r
, "Failed to parse CPUSchedulingPriority: %s", eq
);
693 if (!sched_priority_is_valid(n
))
694 return log_error_errno(r
, "Invalid CPUSchedulingPriority: %s", eq
);
696 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
698 } else if (streq(field
, "CPUAffinity")) {
699 _cleanup_cpu_free_ cpu_set_t
*cpuset
= NULL
;
702 ncpus
= parse_cpu_set(eq
, &cpuset
);
704 return log_error_errno(r
, "Failed to parse %s value: %s", field
, eq
);
706 r
= sd_bus_message_open_container(m
, 'v', "ay");
708 return bus_log_create_error(r
);
710 r
= sd_bus_message_append_array(m
, 'y', cpuset
, CPU_ALLOC_SIZE(ncpus
));
712 return bus_log_create_error(r
);
714 r
= sd_bus_message_close_container(m
);
716 } else if (streq(field
, "Nice")) {
719 r
= parse_nice(eq
, &n
);
721 return log_error_errno(r
, "Failed to parse nice value: %s", eq
);
723 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
725 } else if (streq(field
, "SystemCallFilter")) {
727 _cleanup_strv_free_
char **l
= NULL
;
737 if (whitelist
!= 0) {
738 r
= strv_extend(&l
, "@default");
744 _cleanup_free_
char *word
= NULL
;
746 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
748 return log_error_errno(r
, "Failed to parse %s value: %s", field
, eq
);
752 r
= strv_extend(&l
, word
);
757 r
= sd_bus_message_open_container(m
, 'v', "(bas)");
759 return bus_log_create_error(r
);
761 r
= sd_bus_message_open_container(m
, 'r', "bas");
763 return bus_log_create_error(r
);
765 r
= sd_bus_message_append_basic(m
, 'b', &whitelist
);
767 return bus_log_create_error(r
);
769 r
= sd_bus_message_append_strv(m
, l
);
771 return bus_log_create_error(r
);
773 r
= sd_bus_message_close_container(m
);
775 return bus_log_create_error(r
);
777 r
= sd_bus_message_close_container(m
);
779 return bus_log_create_error(r
);
781 } else if (streq(field
, "SystemCallArchitectures")) {
784 r
= sd_bus_message_open_container(m
, 'v', "as");
786 return bus_log_create_error(r
);
788 r
= sd_bus_message_open_container(m
, 'a', "s");
790 return bus_log_create_error(r
);
793 _cleanup_free_
char *word
= NULL
;
795 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
797 return log_error_errno(r
, "Failed to parse %s value: %s", field
, eq
);
801 r
= sd_bus_message_append_basic(m
, 's', word
);
803 return bus_log_create_error(r
);
806 r
= sd_bus_message_close_container(m
);
808 return bus_log_create_error(r
);
810 r
= sd_bus_message_close_container(m
);
812 } else if (streq(field
, "SystemCallErrorNumber")) {
817 return log_error_errno(r
, "Failed to parse %s value: %s", field
, eq
);
819 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
821 } else if (streq(field
, "RestrictAddressFamilies")) {
823 _cleanup_strv_free_
char **l
= NULL
;
833 _cleanup_free_
char *word
= NULL
;
835 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
837 return log_error_errno(r
, "Failed to parse %s value: %s", field
, eq
);
841 r
= strv_extend(&l
, word
);
846 r
= sd_bus_message_open_container(m
, 'v', "(bas)");
848 return bus_log_create_error(r
);
850 r
= sd_bus_message_open_container(m
, 'r', "bas");
852 return bus_log_create_error(r
);
854 r
= sd_bus_message_append_basic(m
, 'b', &whitelist
);
856 return bus_log_create_error(r
);
858 r
= sd_bus_message_append_strv(m
, l
);
860 return bus_log_create_error(r
);
862 r
= sd_bus_message_close_container(m
);
864 return bus_log_create_error(r
);
866 r
= sd_bus_message_close_container(m
);
868 return bus_log_create_error(r
);
870 } else if (streq(field
, "FileDescriptorStoreMax")) {
873 r
= safe_atou(eq
, &u
);
875 return log_error_errno(r
, "Failed to parse file descriptor store limit: %s", eq
);
877 r
= sd_bus_message_append(m
, "v", "u", (uint32_t) u
);
879 } else if (streq(field
, "IOSchedulingClass")) {
882 c
= ioprio_class_from_string(eq
);
884 return log_error_errno(r
, "Failed to parse IO scheduling class: %s", eq
);
886 r
= sd_bus_message_append(m
, "v", "i", (int32_t) c
);
888 } else if (streq(field
, "IOSchedulingPriority")) {
891 r
= ioprio_parse_priority(eq
, &q
);
893 return log_error_errno(r
, "Failed to parse IO scheduling priority: %s", eq
);
895 r
= sd_bus_message_append(m
, "v", "i", (int32_t) q
);
897 } else if (STR_IN_SET(field
, "Environment", "UnsetEnvironment", "PassEnvironment")) {
900 r
= sd_bus_message_open_container(m
, 'v', "as");
902 return bus_log_create_error(r
);
904 r
= sd_bus_message_open_container(m
, 'a', "s");
906 return bus_log_create_error(r
);
909 _cleanup_free_
char *word
= NULL
;
911 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_CUNESCAPE
);
913 return log_error_errno(r
, "Failed to parse Environment value %s: %m", eq
);
917 if (streq(field
, "Environment")) {
918 if (!env_assignment_is_valid(word
)) {
919 log_error("Invalid environment assignment: %s", word
);
922 } else if (streq(field
, "UnsetEnvironment")) {
923 if (!env_assignment_is_valid(word
) && !env_name_is_valid(word
)) {
924 log_error("Invalid environment name or assignment: %s", word
);
927 } else { /* PassEnvironment */
928 if (!env_name_is_valid(word
)) {
929 log_error("Invalid environment variable name: %s", word
);
934 r
= sd_bus_message_append_basic(m
, 's', word
);
936 return bus_log_create_error(r
);
939 r
= sd_bus_message_close_container(m
);
941 return bus_log_create_error(r
);
943 r
= sd_bus_message_close_container(m
);
945 } else if (streq(field
, "KillSignal")) {
948 sig
= signal_from_string_try_harder(eq
);
950 log_error("Failed to parse %s value %s.", field
, eq
);
954 r
= sd_bus_message_append(m
, "v", "i", sig
);
956 } else if (streq(field
, "TimerSlackNSec")) {
959 r
= parse_nsec(eq
, &n
);
961 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
963 r
= sd_bus_message_append(m
, "v", "t", n
);
964 } else if (streq(field
, "OOMScoreAdjust")) {
967 r
= safe_atoi(eq
, &oa
);
969 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
971 if (!oom_score_adjust_is_valid(oa
)) {
972 log_error("OOM score adjust value out of range");
976 r
= sd_bus_message_append(m
, "v", "i", oa
);
977 } else if (STR_IN_SET(field
, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
978 "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
981 r
= sd_bus_message_open_container(m
, 'v', "as");
983 return bus_log_create_error(r
);
985 r
= sd_bus_message_open_container(m
, 'a', "s");
987 return bus_log_create_error(r
);
990 _cleanup_free_
char *word
= NULL
;
993 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
995 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
999 if (!utf8_is_valid(word
)) {
1000 log_error("Failed to parse %s value %s", field
, eq
);
1004 offset
= word
[0] == '-';
1005 offset
+= word
[offset
] == '+';
1007 if (!path_is_absolute(word
+ offset
)) {
1008 log_error("Failed to parse %s value %s", field
, eq
);
1012 path_kill_slashes(word
+ offset
);
1014 r
= sd_bus_message_append_basic(m
, 's', word
);
1016 return bus_log_create_error(r
);
1019 r
= sd_bus_message_close_container(m
);
1021 return bus_log_create_error(r
);
1023 r
= sd_bus_message_close_container(m
);
1025 } else if (streq(field
, "SupplementaryGroups")) {
1028 r
= sd_bus_message_open_container(m
, 'v', "as");
1030 return bus_log_create_error(r
);
1032 r
= sd_bus_message_open_container(m
, 'a', "s");
1034 return bus_log_create_error(r
);
1037 _cleanup_free_
char *word
= NULL
;
1039 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
1041 return log_error_errno(r
, "Failed to parse %s value %s: %m", field
, eq
);
1045 if (!valid_user_group_name_or_id(word
)) {
1046 log_error("Failed to parse %s value %s", field
, eq
);
1050 r
= sd_bus_message_append_basic(m
, 's', word
);
1052 return bus_log_create_error(r
);
1055 r
= sd_bus_message_close_container(m
);
1057 return bus_log_create_error(r
);
1059 r
= sd_bus_message_close_container(m
);
1061 } else if (STR_IN_SET(field
, "RuntimeDirectoryMode", "StateDirectoryMode", "CacheDirectoryMode", "LogsDirectoryMode", "ConfigurationDirectoryMode", "UMask")) {
1064 r
= parse_mode(eq
, &mode
);
1066 return log_error_errno(r
, "Failed to parse %s value %s", field
, eq
);
1068 r
= sd_bus_message_append(m
, "v", "u", mode
);
1070 } else if (STR_IN_SET(field
, "RuntimeDirectory", "StateDirectory", "CacheDirectory", "LogsDirectory", "ConfigurationDirectory")) {
1073 r
= sd_bus_message_open_container(m
, 'v', "as");
1075 return bus_log_create_error(r
);
1077 r
= sd_bus_message_open_container(m
, 'a', "s");
1079 return bus_log_create_error(r
);
1082 _cleanup_free_
char *word
= NULL
;
1084 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
1088 return log_error_errno(r
, "Failed to parse %s value %s", field
, eq
);
1092 r
= sd_bus_message_append_basic(m
, 's', word
);
1094 return bus_log_create_error(r
);
1097 r
= sd_bus_message_close_container(m
);
1099 return bus_log_create_error(r
);
1101 r
= sd_bus_message_close_container(m
);
1103 } else if (streq(field
, "RestrictNamespaces")) {
1104 bool invert
= false;
1105 unsigned long flags
= 0;
1112 r
= parse_boolean(eq
);
1116 flags
= NAMESPACE_FLAGS_ALL
;
1118 r
= namespace_flag_from_string_many(eq
, &flags
);
1120 return log_error_errno(r
, "Failed to parse %s value %s.", field
, eq
);
1124 flags
= (~flags
) & NAMESPACE_FLAGS_ALL
;
1126 r
= sd_bus_message_append(m
, "v", "t", (uint64_t) flags
);
1127 } else if ((dep
= unit_dependency_from_string(field
)) >= 0)
1128 r
= sd_bus_message_append(m
, "v", "as", 1, eq
);
1129 else if (streq(field
, "MountFlags")) {
1132 r
= mount_propagation_flags_from_string(eq
, &f
);
1134 return log_error_errno(r
, "Failed to parse mount propagation flags: %s", eq
);
1136 r
= sd_bus_message_append(m
, "v", "t", (uint64_t) f
);
1137 } else if (STR_IN_SET(field
, "BindPaths", "BindReadOnlyPaths")) {
1140 r
= sd_bus_message_open_container(m
, 'v', "a(ssbt)");
1144 r
= sd_bus_message_open_container(m
, 'a', "(ssbt)");
1149 _cleanup_free_
char *source
= NULL
, *destination
= NULL
;
1150 char *s
= NULL
, *d
= NULL
;
1151 bool ignore_enoent
= false;
1152 uint64_t flags
= MS_REC
;
1154 r
= extract_first_word(&p
, &source
, ":" WHITESPACE
, EXTRACT_QUOTES
|EXTRACT_DONT_COALESCE_SEPARATORS
);
1156 return log_error_errno(r
, "Failed to parse argument: %m");
1162 ignore_enoent
= true;
1166 if (p
&& p
[-1] == ':') {
1167 r
= extract_first_word(&p
, &destination
, ":" WHITESPACE
, EXTRACT_QUOTES
|EXTRACT_DONT_COALESCE_SEPARATORS
);
1169 return log_error_errno(r
, "Failed to parse argument: %m");
1171 log_error("Missing argument after ':': %s", eq
);
1177 if (p
&& p
[-1] == ':') {
1178 _cleanup_free_
char *options
= NULL
;
1180 r
= extract_first_word(&p
, &options
, NULL
, EXTRACT_QUOTES
);
1182 return log_error_errno(r
, "Failed to parse argument: %m");
1184 if (isempty(options
) || streq(options
, "rbind"))
1186 else if (streq(options
, "norbind"))
1189 log_error("Unknown options: %s", eq
);
1197 r
= sd_bus_message_append(m
, "(ssbt)", s
, d
, ignore_enoent
, flags
);
1202 r
= sd_bus_message_close_container(m
);
1206 r
= sd_bus_message_close_container(m
);
1208 } else if (STR_IN_SET(field
, "ExecStartPre", "ExecStart", "ExecStartPost",
1209 "ExecReload", "ExecStop", "ExecStopPost")) {
1211 bool ignore_failure
= false, explicit_path
= false, done
= false;
1212 _cleanup_strv_free_
char **l
= NULL
;
1213 _cleanup_free_
char *path
= NULL
;
1222 ignore_failure
= true;
1231 explicit_path
= true;
1238 /* The bus API doesn't support +, ! and !! currently, unfortunately. :-( */
1239 log_error("Sorry, but +, ! and !! are currently not supported for transient services.");
1248 if (explicit_path
) {
1249 r
= extract_first_word(&eq
, &path
, NULL
, EXTRACT_QUOTES
|EXTRACT_CUNESCAPE
);
1251 return log_error_errno(r
, "Failed to parse path: %m");
1254 r
= strv_split_extract(&l
, eq
, NULL
, EXTRACT_QUOTES
|EXTRACT_CUNESCAPE
);
1256 return log_error_errno(r
, "Failed to parse command line: %m");
1258 r
= sd_bus_message_open_container(m
, 'v', "a(sasb)");
1262 r
= sd_bus_message_open_container(m
, 'a', "(sasb)");
1266 if (strv_length(l
) > 0) {
1268 r
= sd_bus_message_open_container(m
, 'r', "sasb");
1272 r
= sd_bus_message_append(m
, "s", path
?: l
[0]);
1276 r
= sd_bus_message_append_strv(m
, l
);
1280 r
= sd_bus_message_append(m
, "b", ignore_failure
);
1284 r
= sd_bus_message_close_container(m
);
1289 r
= sd_bus_message_close_container(m
);
1293 r
= sd_bus_message_close_container(m
);
1295 } else if (STR_IN_SET(field
,
1296 "OnActiveSec", "OnBootSec", "OnStartupSec",
1297 "OnUnitActiveSec","OnUnitInactiveSec")) {
1300 r
= parse_sec(eq
, &t
);
1302 return log_error_errno(r
, "Failed to parse %s= parameter: %s", field
, eq
);
1304 r
= sd_bus_message_append(m
, "v", "t", t
);
1307 log_error("Unknown assignment: %s", assignment
);
1313 return bus_log_create_error(r
);
1315 r
= sd_bus_message_close_container(m
);
1317 return bus_log_create_error(r
);
1322 int bus_append_unit_property_assignment_many(sd_bus_message
*m
, char **l
) {
1328 STRV_FOREACH(i
, l
) {
1329 r
= bus_append_unit_property_assignment(m
, *i
);
1337 typedef struct BusWaitForJobs
{
1344 sd_bus_slot
*slot_job_removed
;
1345 sd_bus_slot
*slot_disconnected
;
1348 static int match_disconnected(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1351 log_error("Warning! D-Bus connection terminated.");
1352 sd_bus_close(sd_bus_message_get_bus(m
));
1357 static int match_job_removed(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1358 const char *path
, *unit
, *result
;
1359 BusWaitForJobs
*d
= userdata
;
1367 r
= sd_bus_message_read(m
, "uoss", &id
, &path
, &unit
, &result
);
1369 bus_log_parse_error(r
);
1373 found
= set_remove(d
->jobs
, (char*) path
);
1379 if (!isempty(result
))
1380 d
->result
= strdup(result
);
1383 d
->name
= strdup(unit
);
1388 void bus_wait_for_jobs_free(BusWaitForJobs
*d
) {
1392 set_free_free(d
->jobs
);
1394 sd_bus_slot_unref(d
->slot_disconnected
);
1395 sd_bus_slot_unref(d
->slot_job_removed
);
1397 sd_bus_unref(d
->bus
);
1405 int bus_wait_for_jobs_new(sd_bus
*bus
, BusWaitForJobs
**ret
) {
1406 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*d
= NULL
;
1412 d
= new0(BusWaitForJobs
, 1);
1416 d
->bus
= sd_bus_ref(bus
);
1418 /* When we are a bus client we match by sender. Direct
1419 * connections OTOH have no initialized sender field, and
1420 * hence we ignore the sender then */
1421 r
= sd_bus_add_match(
1423 &d
->slot_job_removed
,
1426 "sender='org.freedesktop.systemd1',"
1427 "interface='org.freedesktop.systemd1.Manager',"
1428 "member='JobRemoved',"
1429 "path='/org/freedesktop/systemd1'" :
1431 "interface='org.freedesktop.systemd1.Manager',"
1432 "member='JobRemoved',"
1433 "path='/org/freedesktop/systemd1'",
1434 match_job_removed
, d
);
1438 r
= sd_bus_add_match(
1440 &d
->slot_disconnected
,
1442 "sender='org.freedesktop.DBus.Local',"
1443 "interface='org.freedesktop.DBus.Local',"
1444 "member='Disconnected'",
1445 match_disconnected
, d
);
1455 static int bus_process_wait(sd_bus
*bus
) {
1459 r
= sd_bus_process(bus
, NULL
);
1465 r
= sd_bus_wait(bus
, (uint64_t) -1);
1471 static int bus_job_get_service_result(BusWaitForJobs
*d
, char **result
) {
1472 _cleanup_free_
char *dbus_path
= NULL
;
1478 if (!endswith(d
->name
, ".service"))
1481 dbus_path
= unit_dbus_path_from_name(d
->name
);
1485 return sd_bus_get_property_string(d
->bus
,
1486 "org.freedesktop.systemd1",
1488 "org.freedesktop.systemd1.Service",
1494 static const struct {
1495 const char *result
, *explanation
;
1496 } explanations
[] = {
1497 { "resources", "of unavailable resources or another system error" },
1498 { "protocol", "the service did not take the steps required by its unit configuration" },
1499 { "timeout", "a timeout was exceeded" },
1500 { "exit-code", "the control process exited with error code" },
1501 { "signal", "a fatal signal was delivered to the control process" },
1502 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
1503 { "watchdog", "the service failed to send watchdog ping" },
1504 { "start-limit", "start of the service was attempted too often" }
1507 static void log_job_error_with_service_result(const char* service
, const char *result
, const char* const* extra_args
) {
1508 _cleanup_free_
char *service_shell_quoted
= NULL
;
1509 const char *systemctl
= "systemctl", *journalctl
= "journalctl";
1513 service_shell_quoted
= shell_maybe_quote(service
, ESCAPE_BACKSLASH
);
1515 if (!strv_isempty((char**) extra_args
)) {
1516 _cleanup_free_
char *t
;
1518 t
= strv_join((char**) extra_args
, " ");
1519 systemctl
= strjoina("systemctl ", t
? : "<args>");
1520 journalctl
= strjoina("journalctl ", t
? : "<args>");
1523 if (!isempty(result
)) {
1526 for (i
= 0; i
< ELEMENTSOF(explanations
); ++i
)
1527 if (streq(result
, explanations
[i
].result
))
1530 if (i
< ELEMENTSOF(explanations
)) {
1531 log_error("Job for %s failed because %s.\n"
1532 "See \"%s status %s\" and \"%s -xe\" for details.\n",
1534 explanations
[i
].explanation
,
1536 service_shell_quoted
?: "<service>",
1542 log_error("Job for %s failed.\n"
1543 "See \"%s status %s\" and \"%s -xe\" for details.\n",
1546 service_shell_quoted
?: "<service>",
1550 /* For some results maybe additional explanation is required */
1551 if (streq_ptr(result
, "start-limit"))
1552 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
1553 "followed by \"%1$s start %2$s\" again.",
1555 service_shell_quoted
?: "<service>");
1558 static int check_wait_response(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
1564 if (streq(d
->result
, "canceled"))
1565 log_error("Job for %s canceled.", strna(d
->name
));
1566 else if (streq(d
->result
, "timeout"))
1567 log_error("Job for %s timed out.", strna(d
->name
));
1568 else if (streq(d
->result
, "dependency"))
1569 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d
->name
));
1570 else if (streq(d
->result
, "invalid"))
1571 log_error("%s is not active, cannot reload.", strna(d
->name
));
1572 else if (streq(d
->result
, "assert"))
1573 log_error("Assertion failed on job for %s.", strna(d
->name
));
1574 else if (streq(d
->result
, "unsupported"))
1575 log_error("Operation on or unit type of %s not supported on this system.", strna(d
->name
));
1576 else if (streq(d
->result
, "collected"))
1577 log_error("Queued job for %s was garbage collected.", strna(d
->name
));
1578 else if (!STR_IN_SET(d
->result
, "done", "skipped")) {
1580 _cleanup_free_
char *result
= NULL
;
1583 q
= bus_job_get_service_result(d
, &result
);
1585 log_debug_errno(q
, "Failed to get Result property of unit %s: %m", d
->name
);
1587 log_job_error_with_service_result(d
->name
, result
, extra_args
);
1589 log_error("Job failed. See \"journalctl -xe\" for details.");
1593 if (STR_IN_SET(d
->result
, "canceled", "collected"))
1595 else if (streq(d
->result
, "timeout"))
1597 else if (streq(d
->result
, "dependency"))
1599 else if (streq(d
->result
, "invalid"))
1601 else if (streq(d
->result
, "assert"))
1603 else if (streq(d
->result
, "unsupported"))
1605 else if (!STR_IN_SET(d
->result
, "done", "skipped"))
1611 int bus_wait_for_jobs(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
1616 while (!set_isempty(d
->jobs
)) {
1619 q
= bus_process_wait(d
->bus
);
1621 return log_error_errno(q
, "Failed to wait for response: %m");
1624 q
= check_wait_response(d
, quiet
, extra_args
);
1625 /* Return the first error as it is most likely to be
1627 if (q
< 0 && r
== 0)
1630 log_debug_errno(q
, "Got result %s/%m for job %s", strna(d
->result
), strna(d
->name
));
1633 d
->name
= mfree(d
->name
);
1634 d
->result
= mfree(d
->result
);
1640 int bus_wait_for_jobs_add(BusWaitForJobs
*d
, const char *path
) {
1645 r
= set_ensure_allocated(&d
->jobs
, &string_hash_ops
);
1649 return set_put_strdup(d
->jobs
, path
);
1652 int bus_wait_for_jobs_one(BusWaitForJobs
*d
, const char *path
, bool quiet
) {
1655 r
= bus_wait_for_jobs_add(d
, path
);
1659 return bus_wait_for_jobs(d
, quiet
, NULL
);
1662 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message
*m
, bool quiet
, UnitFileChange
**changes
, unsigned *n_changes
) {
1663 const char *type
, *path
, *source
;
1666 /* changes is dereferenced when calling unit_file_dump_changes() later,
1667 * so we have to make sure this is not NULL. */
1671 r
= sd_bus_message_enter_container(m
, SD_BUS_TYPE_ARRAY
, "(sss)");
1673 return bus_log_parse_error(r
);
1675 while ((r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
)) > 0) {
1676 /* We expect only "success" changes to be sent over the bus.
1677 Hence, reject anything negative. */
1678 UnitFileChangeType ch
= unit_file_change_type_from_string(type
);
1681 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type
, path
);
1685 r
= unit_file_changes_add(changes
, n_changes
, ch
, path
, source
);
1690 return bus_log_parse_error(r
);
1692 r
= sd_bus_message_exit_container(m
);
1694 return bus_log_parse_error(r
);
1696 unit_file_dump_changes(0, NULL
, *changes
, *n_changes
, quiet
);
1702 bool is_const
; /* If false, cgroup_path should be free()'d */
1704 Hashmap
*pids
; /* PID → process name */
1707 struct CGroupInfo
*parent
;
1708 LIST_FIELDS(struct CGroupInfo
, siblings
);
1709 LIST_HEAD(struct CGroupInfo
, children
);
1713 static bool IS_ROOT(const char *p
) {
1714 return isempty(p
) || streq(p
, "/");
1717 static int add_cgroup(Hashmap
*cgroups
, const char *path
, bool is_const
, struct CGroupInfo
**ret
) {
1718 struct CGroupInfo
*parent
= NULL
, *cg
;
1727 cg
= hashmap_get(cgroups
, path
);
1733 if (!IS_ROOT(path
)) {
1736 e
= strrchr(path
, '/');
1740 pp
= strndupa(path
, e
- path
);
1744 r
= add_cgroup(cgroups
, pp
, false, &parent
);
1749 cg
= new0(struct CGroupInfo
, 1);
1754 cg
->cgroup_path
= (char*) path
;
1756 cg
->cgroup_path
= strdup(path
);
1757 if (!cg
->cgroup_path
) {
1763 cg
->is_const
= is_const
;
1764 cg
->parent
= parent
;
1766 r
= hashmap_put(cgroups
, cg
->cgroup_path
, cg
);
1769 free(cg
->cgroup_path
);
1775 LIST_PREPEND(siblings
, parent
->children
, cg
);
1776 parent
->n_children
++;
1783 static int add_process(
1789 struct CGroupInfo
*cg
;
1796 r
= add_cgroup(cgroups
, path
, true, &cg
);
1800 r
= hashmap_ensure_allocated(&cg
->pids
, &trivial_hash_ops
);
1804 return hashmap_put(cg
->pids
, PID_TO_PTR(pid
), (void*) name
);
1807 static void remove_cgroup(Hashmap
*cgroups
, struct CGroupInfo
*cg
) {
1811 while (cg
->children
)
1812 remove_cgroup(cgroups
, cg
->children
);
1814 hashmap_remove(cgroups
, cg
->cgroup_path
);
1817 free(cg
->cgroup_path
);
1819 hashmap_free(cg
->pids
);
1822 LIST_REMOVE(siblings
, cg
->parent
->children
, cg
);
1827 static int cgroup_info_compare_func(const void *a
, const void *b
) {
1828 const struct CGroupInfo
*x
= *(const struct CGroupInfo
* const*) a
, *y
= *(const struct CGroupInfo
* const*) b
;
1833 return strcmp(x
->cgroup_path
, y
->cgroup_path
);
1836 static int dump_processes(
1838 const char *cgroup_path
,
1841 OutputFlags flags
) {
1843 struct CGroupInfo
*cg
;
1848 if (IS_ROOT(cgroup_path
))
1851 cg
= hashmap_get(cgroups
, cgroup_path
);
1855 if (!hashmap_isempty(cg
->pids
)) {
1863 /* Order processes by their PID */
1864 pids
= newa(pid_t
, hashmap_size(cg
->pids
));
1866 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
)
1867 pids
[n
++] = PTR_TO_PID(pidp
);
1869 assert(n
== hashmap_size(cg
->pids
));
1870 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1872 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1874 for (i
= 0; i
< n
; i
++) {
1875 _cleanup_free_
char *e
= NULL
;
1876 const char *special
;
1879 name
= hashmap_get(cg
->pids
, PID_TO_PTR(pids
[i
]));
1882 if (n_columns
!= 0) {
1885 k
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1887 e
= ellipsize(name
, k
, 100);
1892 more
= i
+1 < n
|| cg
->children
;
1893 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1895 fprintf(stdout
, "%s%s%*"PID_PRI
" %s\n",
1904 struct CGroupInfo
**children
, *child
;
1907 /* Order subcgroups by their name */
1908 children
= newa(struct CGroupInfo
*, cg
->n_children
);
1909 LIST_FOREACH(siblings
, child
, cg
->children
)
1910 children
[n
++] = child
;
1911 assert(n
== cg
->n_children
);
1912 qsort_safe(children
, n
, sizeof(struct CGroupInfo
*), cgroup_info_compare_func
);
1915 n_columns
= MAX(LESS_BY(n_columns
, 2U), 20U);
1917 for (i
= 0; i
< n
; i
++) {
1918 _cleanup_free_
char *pp
= NULL
;
1919 const char *name
, *special
;
1922 child
= children
[i
];
1924 name
= strrchr(child
->cgroup_path
, '/');
1930 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1932 fputs(prefix
, stdout
);
1933 fputs(special
, stdout
);
1934 fputs(name
, stdout
);
1935 fputc('\n', stdout
);
1937 special
= special_glyph(more
? TREE_VERTICAL
: TREE_SPACE
);
1939 pp
= strappend(prefix
, special
);
1943 r
= dump_processes(cgroups
, child
->cgroup_path
, pp
, n_columns
, flags
);
1953 static int dump_extra_processes(
1957 OutputFlags flags
) {
1959 _cleanup_free_ pid_t
*pids
= NULL
;
1960 _cleanup_hashmap_free_ Hashmap
*names
= NULL
;
1961 struct CGroupInfo
*cg
;
1962 size_t n_allocated
= 0, n
= 0, k
;
1966 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1967 * combined, sorted, linear list. */
1969 HASHMAP_FOREACH(cg
, cgroups
, i
) {
1977 if (hashmap_isempty(cg
->pids
))
1980 r
= hashmap_ensure_allocated(&names
, &trivial_hash_ops
);
1984 if (!GREEDY_REALLOC(pids
, n_allocated
, n
+ hashmap_size(cg
->pids
)))
1987 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
) {
1988 pids
[n
++] = PTR_TO_PID(pidp
);
1990 r
= hashmap_put(names
, pidp
, (void*) name
);
1999 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
2000 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
2002 for (k
= 0; k
< n
; k
++) {
2003 _cleanup_free_
char *e
= NULL
;
2006 name
= hashmap_get(names
, PID_TO_PTR(pids
[k
]));
2009 if (n_columns
!= 0) {
2012 z
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
2014 e
= ellipsize(name
, z
, 100);
2019 fprintf(stdout
, "%s%s %*" PID_PRI
" %s\n",
2021 special_glyph(TRIANGULAR_BULLET
),
2029 int unit_show_processes(
2032 const char *cgroup_path
,
2036 sd_bus_error
*error
) {
2038 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
2039 Hashmap
*cgroups
= NULL
;
2040 struct CGroupInfo
*cg
;
2046 if (flags
& OUTPUT_FULL_WIDTH
)
2048 else if (n_columns
<= 0)
2049 n_columns
= columns();
2051 prefix
= strempty(prefix
);
2053 r
= sd_bus_call_method(
2055 "org.freedesktop.systemd1",
2056 "/org/freedesktop/systemd1",
2057 "org.freedesktop.systemd1.Manager",
2066 cgroups
= hashmap_new(&string_hash_ops
);
2070 r
= sd_bus_message_enter_container(reply
, 'a', "(sus)");
2075 const char *path
= NULL
, *name
= NULL
;
2078 r
= sd_bus_message_read(reply
, "(sus)", &path
, &pid
, &name
);
2084 r
= add_process(cgroups
, path
, pid
, name
);
2089 r
= sd_bus_message_exit_container(reply
);
2093 r
= dump_processes(cgroups
, cgroup_path
, prefix
, n_columns
, flags
);
2097 r
= dump_extra_processes(cgroups
, prefix
, n_columns
, flags
);
2100 while ((cg
= hashmap_first(cgroups
)))
2101 remove_cgroup(cgroups
, cg
);
2103 hashmap_free(cgroups
);