2 This file is part of systemd.
4 Copyright 2016 Lennart Poettering
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.
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.
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/>.
20 #include "alloc-util.h"
21 #include "bus-internal.h"
22 #include "bus-unit-util.h"
24 #include "cgroup-util.h"
29 #include "locale-util.h"
31 #include "parse-util.h"
32 #include "path-util.h"
33 #include "process-util.h"
34 #include "rlimit-util.h"
35 #include "signal-util.h"
36 #include "string-util.h"
37 #include "syslog-util.h"
38 #include "terminal-util.h"
42 int bus_parse_unit_info(sd_bus_message
*message
, UnitInfo
*u
) {
48 return sd_bus_message_read(
63 int bus_append_unit_property_assignment(sd_bus_message
*m
, const char *assignment
) {
64 const char *eq
, *field
;
70 eq
= strchr(assignment
, '=');
72 log_error("Not an assignment: %s", assignment
);
76 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
78 return bus_log_create_error(r
);
80 field
= strndupa(assignment
, eq
- assignment
);
83 if (streq(field
, "CPUQuota")) {
86 r
= sd_bus_message_append(m
, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY
);
88 r
= parse_percent_unbounded(eq
);
90 log_error_errno(r
, "CPU quota '%s' invalid.", eq
);
94 r
= sd_bus_message_append(m
, "sv", "CPUQuotaPerSecUSec", "t", (usec_t
) r
* USEC_PER_SEC
/ 100U);
99 } else if (streq(field
, "EnvironmentFile")) {
101 r
= sd_bus_message_append(m
, "sv", "EnvironmentFiles", "a(sb)", 1,
102 eq
[0] == '-' ? eq
+ 1 : eq
,
106 } else if (STR_IN_SET(field
, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) {
111 r
= parse_sec(eq
, &t
);
113 return log_error_errno(r
, "Failed to parse %s= parameter: %s", field
, eq
);
116 n
= newa(char, l
+ 2);
120 /* Change suffix Sec → USec */
121 strcpy(mempcpy(n
, field
, l
- 3), "USec");
122 r
= sd_bus_message_append(m
, "sv", n
, "t", t
);
125 } else if (STR_IN_SET(field
, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
128 if (isempty(eq
) || streq(eq
, "infinity"))
129 bytes
= CGROUP_LIMIT_MAX
;
131 r
= parse_percent(eq
);
135 /* When this is a percentage we'll convert this into a relative value in the range
136 * 0…UINT32_MAX and pass it in the MemoryLowScale property (and related
137 * ones). This way the physical memory size can be determined server-side */
139 n
= strjoina(field
, "Scale");
140 r
= sd_bus_message_append(m
, "sv", n
, "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
144 r
= parse_size(eq
, 1024, &bytes
);
146 return log_error_errno(r
, "Failed to parse bytes specification %s", assignment
);
150 r
= sd_bus_message_append(m
, "sv", field
, "t", bytes
);
152 } else if (streq(field
, "TasksMax")) {
155 if (isempty(eq
) || streq(eq
, "infinity"))
158 r
= parse_percent(eq
);
160 r
= sd_bus_message_append(m
, "sv", "TasksMaxScale", "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
163 r
= safe_atou64(eq
, &t
);
165 return log_error_errno(r
, "Failed to parse maximum tasks specification %s", assignment
);
170 r
= sd_bus_message_append(m
, "sv", "TasksMax", "t", t
);
174 r
= sd_bus_message_append_basic(m
, SD_BUS_TYPE_STRING
, field
);
176 return bus_log_create_error(r
);
178 rl
= rlimit_from_string(field
);
183 r
= rlimit_parse(rl
, eq
, &l
);
185 return log_error_errno(r
, "Failed to parse resource limit: %s", eq
);
187 r
= sd_bus_message_append(m
, "v", "t", l
.rlim_max
);
189 return bus_log_create_error(r
);
191 r
= sd_bus_message_close_container(m
);
193 return bus_log_create_error(r
);
195 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
197 return bus_log_create_error(r
);
199 sn
= strjoina(field
, "Soft");
200 r
= sd_bus_message_append(m
, "sv", sn
, "t", l
.rlim_cur
);
202 } else if (STR_IN_SET(field
,
203 "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
204 "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
205 "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
206 "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "NoNewPrivileges",
207 "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute",
208 "RestrictRealtime", "DynamicUser", "RemoveIPC", "ProtectKernelTunables",
209 "ProtectKernelModules", "ProtectControlGroups")) {
211 r
= parse_boolean(eq
);
213 return log_error_errno(r
, "Failed to parse boolean assignment %s.", assignment
);
215 r
= sd_bus_message_append(m
, "v", "b", r
);
217 } else if (STR_IN_SET(field
, "CPUWeight", "StartupCPUWeight")) {
220 r
= cg_weight_parse(eq
, &u
);
222 log_error("Failed to parse %s value %s.", field
, eq
);
226 r
= sd_bus_message_append(m
, "v", "t", u
);
228 } else if (STR_IN_SET(field
, "CPUShares", "StartupCPUShares")) {
231 r
= cg_cpu_shares_parse(eq
, &u
);
233 log_error("Failed to parse %s value %s.", field
, eq
);
237 r
= sd_bus_message_append(m
, "v", "t", u
);
239 } else if (STR_IN_SET(field
, "IOWeight", "StartupIOWeight")) {
242 r
= cg_weight_parse(eq
, &u
);
244 log_error("Failed to parse %s value %s.", field
, eq
);
248 r
= sd_bus_message_append(m
, "v", "t", u
);
250 } else if (STR_IN_SET(field
, "BlockIOWeight", "StartupBlockIOWeight")) {
253 r
= cg_blkio_weight_parse(eq
, &u
);
255 log_error("Failed to parse %s value %s.", field
, eq
);
259 r
= sd_bus_message_append(m
, "v", "t", u
);
261 } else if (STR_IN_SET(field
,
262 "User", "Group", "DevicePolicy", "KillMode",
263 "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
264 "StandardInput", "StandardOutput", "StandardError",
265 "Description", "Slice", "Type", "WorkingDirectory",
266 "RootDirectory", "SyslogIdentifier", "ProtectSystem",
267 "ProtectHome", "SELinuxContext"))
268 r
= sd_bus_message_append(m
, "v", "s", eq
);
270 else if (streq(field
, "SyslogLevel")) {
273 level
= log_level_from_string(eq
);
275 log_error("Failed to parse %s value %s.", field
, eq
);
279 r
= sd_bus_message_append(m
, "v", "i", level
);
281 } else if (streq(field
, "SyslogFacility")) {
284 facility
= log_facility_unshifted_from_string(eq
);
286 log_error("Failed to parse %s value %s.", field
, eq
);
290 r
= sd_bus_message_append(m
, "v", "i", facility
);
292 } else if (streq(field
, "DeviceAllow")) {
295 r
= sd_bus_message_append(m
, "v", "a(ss)", 0);
297 const char *path
, *rwm
, *e
;
301 path
= strndupa(eq
, e
- eq
);
308 if (!is_deviceallow_pattern(path
)) {
309 log_error("%s is not a device file in /dev.", path
);
313 r
= sd_bus_message_append(m
, "v", "a(ss)", 1, path
, rwm
);
316 } else if (cgroup_io_limit_type_from_string(field
) >= 0 || STR_IN_SET(field
, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
319 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
321 const char *path
, *bandwidth
, *e
;
326 path
= strndupa(eq
, e
- eq
);
329 log_error("Failed to parse %s value %s.", field
, eq
);
333 if (!path_startswith(path
, "/dev")) {
334 log_error("%s is not a device file in /dev.", path
);
338 if (streq(bandwidth
, "infinity")) {
339 bytes
= CGROUP_LIMIT_MAX
;
341 r
= parse_size(bandwidth
, 1000, &bytes
);
343 log_error("Failed to parse byte value %s.", bandwidth
);
348 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, bytes
);
351 } else if (STR_IN_SET(field
, "IODeviceWeight", "BlockIODeviceWeight")) {
354 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
356 const char *path
, *weight
, *e
;
361 path
= strndupa(eq
, e
- eq
);
364 log_error("Failed to parse %s value %s.", field
, eq
);
368 if (!path_startswith(path
, "/dev")) {
369 log_error("%s is not a device file in /dev.", path
);
373 r
= safe_atou64(weight
, &u
);
375 log_error("Failed to parse %s value %s.", field
, weight
);
378 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, u
);
381 } else if (streq(field
, "Nice")) {
384 r
= parse_nice(eq
, &n
);
386 return log_error_errno(r
, "Failed to parse nice value: %s", eq
);
388 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
390 } else if (STR_IN_SET(field
, "Environment", "PassEnvironment")) {
393 r
= sd_bus_message_open_container(m
, 'v', "as");
395 return bus_log_create_error(r
);
397 r
= sd_bus_message_open_container(m
, 'a', "s");
399 return bus_log_create_error(r
);
402 _cleanup_free_
char *word
= NULL
;
404 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_CUNESCAPE
);
406 log_error("Failed to parse Environment value %s", eq
);
412 if (streq(field
, "Environment")) {
413 if (!env_assignment_is_valid(word
)) {
414 log_error("Invalid environment assignment: %s", word
);
417 } else { /* PassEnvironment */
418 if (!env_name_is_valid(word
)) {
419 log_error("Invalid environment variable name: %s", word
);
424 r
= sd_bus_message_append_basic(m
, 's', word
);
426 return bus_log_create_error(r
);
429 r
= sd_bus_message_close_container(m
);
431 return bus_log_create_error(r
);
433 r
= sd_bus_message_close_container(m
);
435 } else if (streq(field
, "KillSignal")) {
438 sig
= signal_from_string_try_harder(eq
);
440 log_error("Failed to parse %s value %s.", field
, eq
);
444 r
= sd_bus_message_append(m
, "v", "i", sig
);
446 } else if (streq(field
, "TimerSlackNSec")) {
449 r
= parse_nsec(eq
, &n
);
451 log_error("Failed to parse %s value %s", field
, eq
);
455 r
= sd_bus_message_append(m
, "v", "t", n
);
456 } else if (streq(field
, "OOMScoreAdjust")) {
459 r
= safe_atoi(eq
, &oa
);
461 log_error("Failed to parse %s value %s", field
, eq
);
465 if (!oom_score_adjust_is_valid(oa
)) {
466 log_error("OOM score adjust value out of range");
470 r
= sd_bus_message_append(m
, "v", "i", oa
);
471 } else if (STR_IN_SET(field
, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
472 "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
475 r
= sd_bus_message_open_container(m
, 'v', "as");
477 return bus_log_create_error(r
);
479 r
= sd_bus_message_open_container(m
, 'a', "s");
481 return bus_log_create_error(r
);
484 _cleanup_free_
char *word
= NULL
;
487 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
489 log_error("Failed to parse %s value %s", field
, eq
);
495 if (!utf8_is_valid(word
)) {
496 log_error("Failed to parse %s value %s", field
, eq
);
500 offset
= word
[0] == '-';
501 if (!path_is_absolute(word
+ offset
)) {
502 log_error("Failed to parse %s value %s", field
, eq
);
506 path_kill_slashes(word
+ offset
);
508 r
= sd_bus_message_append_basic(m
, 's', word
);
510 return bus_log_create_error(r
);
513 r
= sd_bus_message_close_container(m
);
515 return bus_log_create_error(r
);
517 r
= sd_bus_message_close_container(m
);
519 } else if (streq(field
, "RuntimeDirectory")) {
522 r
= sd_bus_message_open_container(m
, 'v', "as");
524 return bus_log_create_error(r
);
526 r
= sd_bus_message_open_container(m
, 'a', "s");
528 return bus_log_create_error(r
);
531 _cleanup_free_
char *word
= NULL
;
533 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
535 return log_error_errno(r
, "Failed to parse %s value %s", field
, eq
);
540 r
= sd_bus_message_append_basic(m
, 's', word
);
542 return bus_log_create_error(r
);
545 r
= sd_bus_message_close_container(m
);
547 return bus_log_create_error(r
);
549 r
= sd_bus_message_close_container(m
);
551 } else if (streq(field
, "RestrictNamespaces")) {
560 r
= parse_boolean(eq
);
564 flags
= NAMESPACE_FLAGS_ALL
;
566 r
= namespace_flag_from_string_many(eq
, &flags
);
568 return log_error_errno(r
, "Failed to parse %s value %s.", field
, eq
);
572 flags
= (~flags
) & NAMESPACE_FLAGS_ALL
;
574 r
= sd_bus_message_append(m
, "v", "t", flags
);
576 log_error("Unknown assignment %s.", assignment
);
582 return bus_log_create_error(r
);
584 r
= sd_bus_message_close_container(m
);
586 return bus_log_create_error(r
);
591 int bus_append_unit_property_assignment_many(sd_bus_message
*m
, char **l
) {
598 r
= bus_append_unit_property_assignment(m
, *i
);
606 typedef struct BusWaitForJobs
{
613 sd_bus_slot
*slot_job_removed
;
614 sd_bus_slot
*slot_disconnected
;
617 static int match_disconnected(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
620 log_error("Warning! D-Bus connection terminated.");
621 sd_bus_close(sd_bus_message_get_bus(m
));
626 static int match_job_removed(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
627 const char *path
, *unit
, *result
;
628 BusWaitForJobs
*d
= userdata
;
636 r
= sd_bus_message_read(m
, "uoss", &id
, &path
, &unit
, &result
);
638 bus_log_parse_error(r
);
642 found
= set_remove(d
->jobs
, (char*) path
);
648 if (!isempty(result
))
649 d
->result
= strdup(result
);
652 d
->name
= strdup(unit
);
657 void bus_wait_for_jobs_free(BusWaitForJobs
*d
) {
661 set_free_free(d
->jobs
);
663 sd_bus_slot_unref(d
->slot_disconnected
);
664 sd_bus_slot_unref(d
->slot_job_removed
);
666 sd_bus_unref(d
->bus
);
674 int bus_wait_for_jobs_new(sd_bus
*bus
, BusWaitForJobs
**ret
) {
675 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*d
= NULL
;
681 d
= new0(BusWaitForJobs
, 1);
685 d
->bus
= sd_bus_ref(bus
);
687 /* When we are a bus client we match by sender. Direct
688 * connections OTOH have no initialized sender field, and
689 * hence we ignore the sender then */
690 r
= sd_bus_add_match(
692 &d
->slot_job_removed
,
695 "sender='org.freedesktop.systemd1',"
696 "interface='org.freedesktop.systemd1.Manager',"
697 "member='JobRemoved',"
698 "path='/org/freedesktop/systemd1'" :
700 "interface='org.freedesktop.systemd1.Manager',"
701 "member='JobRemoved',"
702 "path='/org/freedesktop/systemd1'",
703 match_job_removed
, d
);
707 r
= sd_bus_add_match(
709 &d
->slot_disconnected
,
711 "sender='org.freedesktop.DBus.Local',"
712 "interface='org.freedesktop.DBus.Local',"
713 "member='Disconnected'",
714 match_disconnected
, d
);
724 static int bus_process_wait(sd_bus
*bus
) {
728 r
= sd_bus_process(bus
, NULL
);
734 r
= sd_bus_wait(bus
, (uint64_t) -1);
740 static int bus_job_get_service_result(BusWaitForJobs
*d
, char **result
) {
741 _cleanup_free_
char *dbus_path
= NULL
;
747 dbus_path
= unit_dbus_path_from_name(d
->name
);
751 return sd_bus_get_property_string(d
->bus
,
752 "org.freedesktop.systemd1",
754 "org.freedesktop.systemd1.Service",
760 static const struct {
761 const char *result
, *explanation
;
762 } explanations
[] = {
763 { "resources", "of unavailable resources or another system error" },
764 { "timeout", "a timeout was exceeded" },
765 { "exit-code", "the control process exited with error code" },
766 { "signal", "a fatal signal was delivered to the control process" },
767 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
768 { "watchdog", "the service failed to send watchdog ping" },
769 { "start-limit", "start of the service was attempted too often" }
772 static void log_job_error_with_service_result(const char* service
, const char *result
, const char* const* extra_args
) {
773 _cleanup_free_
char *service_shell_quoted
= NULL
;
774 const char *systemctl
= "systemctl", *journalctl
= "journalctl";
778 service_shell_quoted
= shell_maybe_quote(service
);
780 if (extra_args
&& extra_args
[1]) {
781 _cleanup_free_
char *t
;
783 t
= strv_join((char**) extra_args
, " ");
784 systemctl
= strjoina("systemctl ", t
? : "<args>");
785 journalctl
= strjoina("journalctl ", t
? : "<args>");
788 if (!isempty(result
)) {
791 for (i
= 0; i
< ELEMENTSOF(explanations
); ++i
)
792 if (streq(result
, explanations
[i
].result
))
795 if (i
< ELEMENTSOF(explanations
)) {
796 log_error("Job for %s failed because %s.\n"
797 "See \"%s status %s\" and \"%s -xe\" for details.\n",
799 explanations
[i
].explanation
,
801 service_shell_quoted
?: "<service>",
807 log_error("Job for %s failed.\n"
808 "See \"%s status %s\" and \"%s -xe\" for details.\n",
811 service_shell_quoted
?: "<service>",
815 /* For some results maybe additional explanation is required */
816 if (streq_ptr(result
, "start-limit"))
817 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
818 "followed by \"%1$s start %2$s\" again.",
820 service_shell_quoted
?: "<service>");
823 static int check_wait_response(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
829 if (streq(d
->result
, "canceled"))
830 log_error("Job for %s canceled.", strna(d
->name
));
831 else if (streq(d
->result
, "timeout"))
832 log_error("Job for %s timed out.", strna(d
->name
));
833 else if (streq(d
->result
, "dependency"))
834 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d
->name
));
835 else if (streq(d
->result
, "invalid"))
836 log_error("%s is not active, cannot reload.", strna(d
->name
));
837 else if (streq(d
->result
, "assert"))
838 log_error("Assertion failed on job for %s.", strna(d
->name
));
839 else if (streq(d
->result
, "unsupported"))
840 log_error("Operation on or unit type of %s not supported on this system.", strna(d
->name
));
841 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped")) {
844 _cleanup_free_
char *result
= NULL
;
846 q
= bus_job_get_service_result(d
, &result
);
848 log_debug_errno(q
, "Failed to get Result property of service %s: %m", d
->name
);
850 log_job_error_with_service_result(d
->name
, result
, extra_args
);
852 log_error("Job failed. See \"journalctl -xe\" for details.");
856 if (streq(d
->result
, "canceled"))
858 else if (streq(d
->result
, "timeout"))
860 else if (streq(d
->result
, "dependency"))
862 else if (streq(d
->result
, "invalid"))
864 else if (streq(d
->result
, "assert"))
866 else if (streq(d
->result
, "unsupported"))
868 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped"))
874 int bus_wait_for_jobs(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
879 while (!set_isempty(d
->jobs
)) {
882 q
= bus_process_wait(d
->bus
);
884 return log_error_errno(q
, "Failed to wait for response: %m");
887 q
= check_wait_response(d
, quiet
, extra_args
);
888 /* Return the first error as it is most likely to be
893 log_debug_errno(q
, "Got result %s/%m for job %s", strna(d
->result
), strna(d
->name
));
896 d
->name
= mfree(d
->name
);
897 d
->result
= mfree(d
->result
);
903 int bus_wait_for_jobs_add(BusWaitForJobs
*d
, const char *path
) {
908 r
= set_ensure_allocated(&d
->jobs
, &string_hash_ops
);
912 return set_put_strdup(d
->jobs
, path
);
915 int bus_wait_for_jobs_one(BusWaitForJobs
*d
, const char *path
, bool quiet
) {
918 r
= bus_wait_for_jobs_add(d
, path
);
922 return bus_wait_for_jobs(d
, quiet
, NULL
);
925 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message
*m
, bool quiet
, UnitFileChange
**changes
, unsigned *n_changes
) {
926 const char *type
, *path
, *source
;
929 /* changes is dereferenced when calling unit_file_dump_changes() later,
930 * so we have to make sure this is not NULL. */
934 r
= sd_bus_message_enter_container(m
, SD_BUS_TYPE_ARRAY
, "(sss)");
936 return bus_log_parse_error(r
);
938 while ((r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
)) > 0) {
939 /* We expect only "success" changes to be sent over the bus.
940 Hence, reject anything negative. */
941 UnitFileChangeType ch
= unit_file_change_type_from_string(type
);
944 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type
, path
);
948 r
= unit_file_changes_add(changes
, n_changes
, ch
, path
, source
);
953 return bus_log_parse_error(r
);
955 r
= sd_bus_message_exit_container(m
);
957 return bus_log_parse_error(r
);
959 unit_file_dump_changes(0, NULL
, *changes
, *n_changes
, false);
965 bool is_const
; /* If false, cgroup_path should be free()'d */
967 Hashmap
*pids
; /* PID → process name */
970 struct CGroupInfo
*parent
;
971 LIST_FIELDS(struct CGroupInfo
, siblings
);
972 LIST_HEAD(struct CGroupInfo
, children
);
976 static bool IS_ROOT(const char *p
) {
977 return isempty(p
) || streq(p
, "/");
980 static int add_cgroup(Hashmap
*cgroups
, const char *path
, bool is_const
, struct CGroupInfo
**ret
) {
981 struct CGroupInfo
*parent
= NULL
, *cg
;
990 cg
= hashmap_get(cgroups
, path
);
996 if (!IS_ROOT(path
)) {
999 e
= strrchr(path
, '/');
1003 pp
= strndupa(path
, e
- path
);
1007 r
= add_cgroup(cgroups
, pp
, false, &parent
);
1012 cg
= new0(struct CGroupInfo
, 1);
1017 cg
->cgroup_path
= (char*) path
;
1019 cg
->cgroup_path
= strdup(path
);
1020 if (!cg
->cgroup_path
) {
1026 cg
->is_const
= is_const
;
1027 cg
->parent
= parent
;
1029 r
= hashmap_put(cgroups
, cg
->cgroup_path
, cg
);
1032 free(cg
->cgroup_path
);
1038 LIST_PREPEND(siblings
, parent
->children
, cg
);
1039 parent
->n_children
++;
1046 static int add_process(
1052 struct CGroupInfo
*cg
;
1059 r
= add_cgroup(cgroups
, path
, true, &cg
);
1063 r
= hashmap_ensure_allocated(&cg
->pids
, &trivial_hash_ops
);
1067 return hashmap_put(cg
->pids
, PID_TO_PTR(pid
), (void*) name
);
1070 static void remove_cgroup(Hashmap
*cgroups
, struct CGroupInfo
*cg
) {
1074 while (cg
->children
)
1075 remove_cgroup(cgroups
, cg
->children
);
1077 hashmap_remove(cgroups
, cg
->cgroup_path
);
1080 free(cg
->cgroup_path
);
1082 hashmap_free(cg
->pids
);
1085 LIST_REMOVE(siblings
, cg
->parent
->children
, cg
);
1090 static int cgroup_info_compare_func(const void *a
, const void *b
) {
1091 const struct CGroupInfo
*x
= *(const struct CGroupInfo
* const*) a
, *y
= *(const struct CGroupInfo
* const*) b
;
1096 return strcmp(x
->cgroup_path
, y
->cgroup_path
);
1099 static int dump_processes(
1101 const char *cgroup_path
,
1104 OutputFlags flags
) {
1106 struct CGroupInfo
*cg
;
1111 if (IS_ROOT(cgroup_path
))
1114 cg
= hashmap_get(cgroups
, cgroup_path
);
1118 if (!hashmap_isempty(cg
->pids
)) {
1126 /* Order processes by their PID */
1127 pids
= newa(pid_t
, hashmap_size(cg
->pids
));
1129 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
)
1130 pids
[n
++] = PTR_TO_PID(pidp
);
1132 assert(n
== hashmap_size(cg
->pids
));
1133 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1135 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1137 for (i
= 0; i
< n
; i
++) {
1138 _cleanup_free_
char *e
= NULL
;
1139 const char *special
;
1142 name
= hashmap_get(cg
->pids
, PID_TO_PTR(pids
[i
]));
1145 if (n_columns
!= 0) {
1148 k
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1150 e
= ellipsize(name
, k
, 100);
1155 more
= i
+1 < n
|| cg
->children
;
1156 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1158 fprintf(stdout
, "%s%s%*"PID_PRI
" %s\n",
1167 struct CGroupInfo
**children
, *child
;
1170 /* Order subcgroups by their name */
1171 children
= newa(struct CGroupInfo
*, cg
->n_children
);
1172 LIST_FOREACH(siblings
, child
, cg
->children
)
1173 children
[n
++] = child
;
1174 assert(n
== cg
->n_children
);
1175 qsort_safe(children
, n
, sizeof(struct CGroupInfo
*), cgroup_info_compare_func
);
1178 n_columns
= MAX(LESS_BY(n_columns
, 2U), 20U);
1180 for (i
= 0; i
< n
; i
++) {
1181 _cleanup_free_
char *pp
= NULL
;
1182 const char *name
, *special
;
1185 child
= children
[i
];
1187 name
= strrchr(child
->cgroup_path
, '/');
1193 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1195 fputs(prefix
, stdout
);
1196 fputs(special
, stdout
);
1197 fputs(name
, stdout
);
1198 fputc('\n', stdout
);
1200 special
= special_glyph(more
? TREE_VERTICAL
: TREE_SPACE
);
1202 pp
= strappend(prefix
, special
);
1206 r
= dump_processes(cgroups
, child
->cgroup_path
, pp
, n_columns
, flags
);
1216 static int dump_extra_processes(
1220 OutputFlags flags
) {
1222 _cleanup_free_ pid_t
*pids
= NULL
;
1223 _cleanup_hashmap_free_ Hashmap
*names
= NULL
;
1224 struct CGroupInfo
*cg
;
1225 size_t n_allocated
= 0, n
= 0, k
;
1229 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1230 * combined, sorted, linear list. */
1232 HASHMAP_FOREACH(cg
, cgroups
, i
) {
1240 if (hashmap_isempty(cg
->pids
))
1243 r
= hashmap_ensure_allocated(&names
, &trivial_hash_ops
);
1247 if (!GREEDY_REALLOC(pids
, n_allocated
, n
+ hashmap_size(cg
->pids
)))
1250 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
) {
1251 pids
[n
++] = PTR_TO_PID(pidp
);
1253 r
= hashmap_put(names
, pidp
, (void*) name
);
1262 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1263 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1265 for (k
= 0; k
< n
; k
++) {
1266 _cleanup_free_
char *e
= NULL
;
1269 name
= hashmap_get(names
, PID_TO_PTR(pids
[k
]));
1272 if (n_columns
!= 0) {
1275 z
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1277 e
= ellipsize(name
, z
, 100);
1282 fprintf(stdout
, "%s%s %*" PID_PRI
" %s\n",
1284 special_glyph(TRIANGULAR_BULLET
),
1292 int unit_show_processes(
1295 const char *cgroup_path
,
1299 sd_bus_error
*error
) {
1301 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1302 Hashmap
*cgroups
= NULL
;
1303 struct CGroupInfo
*cg
;
1309 if (flags
& OUTPUT_FULL_WIDTH
)
1311 else if (n_columns
<= 0)
1312 n_columns
= columns();
1314 prefix
= strempty(prefix
);
1316 r
= sd_bus_call_method(
1318 "org.freedesktop.systemd1",
1319 "/org/freedesktop/systemd1",
1320 "org.freedesktop.systemd1.Manager",
1329 cgroups
= hashmap_new(&string_hash_ops
);
1333 r
= sd_bus_message_enter_container(reply
, 'a', "(sus)");
1338 const char *path
= NULL
, *name
= NULL
;
1341 r
= sd_bus_message_read(reply
, "(sus)", &path
, &pid
, &name
);
1347 r
= add_process(cgroups
, path
, pid
, name
);
1352 r
= sd_bus_message_exit_container(reply
);
1356 r
= dump_processes(cgroups
, cgroup_path
, prefix
, n_columns
, flags
);
1360 r
= dump_extra_processes(cgroups
, prefix
, n_columns
, flags
);
1363 while ((cg
= hashmap_first(cgroups
)))
1364 remove_cgroup(cgroups
, cg
);
1366 hashmap_free(cgroups
);