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
;
71 eq
= strchr(assignment
, '=');
73 log_error("Not an assignment: %s", assignment
);
77 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
79 return bus_log_create_error(r
);
81 field
= strndupa(assignment
, eq
- assignment
);
84 if (streq(field
, "CPUQuota")) {
87 r
= sd_bus_message_append(m
, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY
);
89 r
= parse_percent_unbounded(eq
);
91 log_error_errno(r
, "CPU quota '%s' invalid.", eq
);
95 r
= sd_bus_message_append(m
, "sv", "CPUQuotaPerSecUSec", "t", (usec_t
) r
* USEC_PER_SEC
/ 100U);
100 } else if (streq(field
, "EnvironmentFile")) {
102 r
= sd_bus_message_append(m
, "sv", "EnvironmentFiles", "a(sb)", 1,
103 eq
[0] == '-' ? eq
+ 1 : eq
,
107 } else if (STR_IN_SET(field
, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) {
112 r
= parse_sec(eq
, &t
);
114 return log_error_errno(r
, "Failed to parse %s= parameter: %s", field
, eq
);
117 n
= newa(char, l
+ 2);
121 /* Change suffix Sec → USec */
122 strcpy(mempcpy(n
, field
, l
- 3), "USec");
123 r
= sd_bus_message_append(m
, "sv", n
, "t", t
);
126 } else if (STR_IN_SET(field
, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
129 if (isempty(eq
) || streq(eq
, "infinity"))
130 bytes
= CGROUP_LIMIT_MAX
;
132 r
= parse_percent(eq
);
136 /* When this is a percentage we'll convert this into a relative value in the range
137 * 0…UINT32_MAX and pass it in the MemoryLowScale property (and related
138 * ones). This way the physical memory size can be determined server-side */
140 n
= strjoina(field
, "Scale");
141 r
= sd_bus_message_append(m
, "sv", n
, "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
145 r
= parse_size(eq
, 1024, &bytes
);
147 return log_error_errno(r
, "Failed to parse bytes specification %s", assignment
);
151 r
= sd_bus_message_append(m
, "sv", field
, "t", bytes
);
153 } else if (streq(field
, "TasksMax")) {
156 if (isempty(eq
) || streq(eq
, "infinity"))
159 r
= parse_percent(eq
);
161 r
= sd_bus_message_append(m
, "sv", "TasksMaxScale", "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
164 r
= safe_atou64(eq
, &t
);
166 return log_error_errno(r
, "Failed to parse maximum tasks specification %s", assignment
);
171 r
= sd_bus_message_append(m
, "sv", "TasksMax", "t", t
);
175 r
= sd_bus_message_append_basic(m
, SD_BUS_TYPE_STRING
, field
);
177 return bus_log_create_error(r
);
179 rl
= rlimit_from_string(field
);
184 r
= rlimit_parse(rl
, eq
, &l
);
186 return log_error_errno(r
, "Failed to parse resource limit: %s", eq
);
188 r
= sd_bus_message_append(m
, "v", "t", l
.rlim_max
);
190 return bus_log_create_error(r
);
192 r
= sd_bus_message_close_container(m
);
194 return bus_log_create_error(r
);
196 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
198 return bus_log_create_error(r
);
200 sn
= strjoina(field
, "Soft");
201 r
= sd_bus_message_append(m
, "sv", sn
, "t", l
.rlim_cur
);
203 } else if (STR_IN_SET(field
,
204 "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
205 "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
206 "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
207 "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "NoNewPrivileges",
208 "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute",
209 "RestrictRealtime", "DynamicUser", "RemoveIPC", "ProtectKernelTunables",
210 "ProtectKernelModules", "ProtectControlGroups")) {
212 r
= parse_boolean(eq
);
214 return log_error_errno(r
, "Failed to parse boolean assignment %s.", assignment
);
216 r
= sd_bus_message_append(m
, "v", "b", r
);
218 } else if (STR_IN_SET(field
, "CPUWeight", "StartupCPUWeight")) {
221 r
= cg_weight_parse(eq
, &u
);
223 log_error("Failed to parse %s value %s.", field
, eq
);
227 r
= sd_bus_message_append(m
, "v", "t", u
);
229 } else if (STR_IN_SET(field
, "CPUShares", "StartupCPUShares")) {
232 r
= cg_cpu_shares_parse(eq
, &u
);
234 log_error("Failed to parse %s value %s.", field
, eq
);
238 r
= sd_bus_message_append(m
, "v", "t", u
);
240 } else if (STR_IN_SET(field
, "IOWeight", "StartupIOWeight")) {
243 r
= cg_weight_parse(eq
, &u
);
245 log_error("Failed to parse %s value %s.", field
, eq
);
249 r
= sd_bus_message_append(m
, "v", "t", u
);
251 } else if (STR_IN_SET(field
, "BlockIOWeight", "StartupBlockIOWeight")) {
254 r
= cg_blkio_weight_parse(eq
, &u
);
256 log_error("Failed to parse %s value %s.", field
, eq
);
260 r
= sd_bus_message_append(m
, "v", "t", u
);
262 } else if (STR_IN_SET(field
,
263 "User", "Group", "DevicePolicy", "KillMode",
264 "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
265 "StandardInput", "StandardOutput", "StandardError",
266 "Description", "Slice", "Type", "WorkingDirectory",
267 "RootDirectory", "SyslogIdentifier", "ProtectSystem",
268 "ProtectHome", "SELinuxContext"))
269 r
= sd_bus_message_append(m
, "v", "s", eq
);
271 else if (streq(field
, "SyslogLevel")) {
274 level
= log_level_from_string(eq
);
276 log_error("Failed to parse %s value %s.", field
, eq
);
280 r
= sd_bus_message_append(m
, "v", "i", level
);
282 } else if (streq(field
, "SyslogFacility")) {
285 facility
= log_facility_unshifted_from_string(eq
);
287 log_error("Failed to parse %s value %s.", field
, eq
);
291 r
= sd_bus_message_append(m
, "v", "i", facility
);
293 } else if (streq(field
, "DeviceAllow")) {
296 r
= sd_bus_message_append(m
, "v", "a(ss)", 0);
298 const char *path
, *rwm
, *e
;
302 path
= strndupa(eq
, e
- eq
);
309 if (!is_deviceallow_pattern(path
)) {
310 log_error("%s is not a device file in /dev.", path
);
314 r
= sd_bus_message_append(m
, "v", "a(ss)", 1, path
, rwm
);
317 } else if (cgroup_io_limit_type_from_string(field
) >= 0 || STR_IN_SET(field
, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
320 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
322 const char *path
, *bandwidth
, *e
;
327 path
= strndupa(eq
, e
- eq
);
330 log_error("Failed to parse %s value %s.", field
, eq
);
334 if (!path_startswith(path
, "/dev")) {
335 log_error("%s is not a device file in /dev.", path
);
339 if (streq(bandwidth
, "infinity")) {
340 bytes
= CGROUP_LIMIT_MAX
;
342 r
= parse_size(bandwidth
, 1000, &bytes
);
344 log_error("Failed to parse byte value %s.", bandwidth
);
349 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, bytes
);
352 } else if (STR_IN_SET(field
, "IODeviceWeight", "BlockIODeviceWeight")) {
355 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
357 const char *path
, *weight
, *e
;
362 path
= strndupa(eq
, e
- eq
);
365 log_error("Failed to parse %s value %s.", field
, eq
);
369 if (!path_startswith(path
, "/dev")) {
370 log_error("%s is not a device file in /dev.", path
);
374 r
= safe_atou64(weight
, &u
);
376 log_error("Failed to parse %s value %s.", field
, weight
);
379 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, u
);
382 } else if (streq(field
, "Nice")) {
385 r
= parse_nice(eq
, &n
);
387 return log_error_errno(r
, "Failed to parse nice value: %s", eq
);
389 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
391 } else if (STR_IN_SET(field
, "Environment", "PassEnvironment")) {
394 r
= sd_bus_message_open_container(m
, 'v', "as");
396 return bus_log_create_error(r
);
398 r
= sd_bus_message_open_container(m
, 'a', "s");
400 return bus_log_create_error(r
);
403 _cleanup_free_
char *word
= NULL
;
405 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_CUNESCAPE
);
407 log_error("Failed to parse Environment value %s", eq
);
413 if (streq(field
, "Environment")) {
414 if (!env_assignment_is_valid(word
)) {
415 log_error("Invalid environment assignment: %s", word
);
418 } else { /* PassEnvironment */
419 if (!env_name_is_valid(word
)) {
420 log_error("Invalid environment variable name: %s", word
);
425 r
= sd_bus_message_append_basic(m
, 's', word
);
427 return bus_log_create_error(r
);
430 r
= sd_bus_message_close_container(m
);
432 return bus_log_create_error(r
);
434 r
= sd_bus_message_close_container(m
);
436 } else if (streq(field
, "KillSignal")) {
439 sig
= signal_from_string_try_harder(eq
);
441 log_error("Failed to parse %s value %s.", field
, eq
);
445 r
= sd_bus_message_append(m
, "v", "i", sig
);
447 } else if (streq(field
, "TimerSlackNSec")) {
450 r
= parse_nsec(eq
, &n
);
452 log_error("Failed to parse %s value %s", field
, eq
);
456 r
= sd_bus_message_append(m
, "v", "t", n
);
457 } else if (streq(field
, "OOMScoreAdjust")) {
460 r
= safe_atoi(eq
, &oa
);
462 log_error("Failed to parse %s value %s", field
, eq
);
466 if (!oom_score_adjust_is_valid(oa
)) {
467 log_error("OOM score adjust value out of range");
471 r
= sd_bus_message_append(m
, "v", "i", oa
);
472 } else if (STR_IN_SET(field
, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
473 "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
476 r
= sd_bus_message_open_container(m
, 'v', "as");
478 return bus_log_create_error(r
);
480 r
= sd_bus_message_open_container(m
, 'a', "s");
482 return bus_log_create_error(r
);
485 _cleanup_free_
char *word
= NULL
;
488 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
490 log_error("Failed to parse %s value %s", field
, eq
);
496 if (!utf8_is_valid(word
)) {
497 log_error("Failed to parse %s value %s", field
, eq
);
501 offset
= word
[0] == '-';
502 if (!path_is_absolute(word
+ offset
)) {
503 log_error("Failed to parse %s value %s", field
, eq
);
507 path_kill_slashes(word
+ offset
);
509 r
= sd_bus_message_append_basic(m
, 's', word
);
511 return bus_log_create_error(r
);
514 r
= sd_bus_message_close_container(m
);
516 return bus_log_create_error(r
);
518 r
= sd_bus_message_close_container(m
);
520 } else if (streq(field
, "RuntimeDirectory")) {
523 r
= sd_bus_message_open_container(m
, 'v', "as");
525 return bus_log_create_error(r
);
527 r
= sd_bus_message_open_container(m
, 'a', "s");
529 return bus_log_create_error(r
);
532 _cleanup_free_
char *word
= NULL
;
534 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
536 return log_error_errno(r
, "Failed to parse %s value %s", field
, eq
);
541 r
= sd_bus_message_append_basic(m
, 's', word
);
543 return bus_log_create_error(r
);
546 r
= sd_bus_message_close_container(m
);
548 return bus_log_create_error(r
);
550 r
= sd_bus_message_close_container(m
);
552 } else if (streq(field
, "RestrictNamespaces")) {
561 r
= parse_boolean(eq
);
565 flags
= NAMESPACE_FLAGS_ALL
;
567 r
= namespace_flag_from_string_many(eq
, &flags
);
569 return log_error_errno(r
, "Failed to parse %s value %s.", field
, eq
);
573 flags
= (~flags
) & NAMESPACE_FLAGS_ALL
;
575 r
= sd_bus_message_append(m
, "v", "t", flags
);
576 } else if ((dep
= unit_dependency_from_string(field
)) >= 0)
577 r
= sd_bus_message_append(m
, "v", "as", 1, eq
);
579 log_error("Unknown assignment %s.", assignment
);
585 return bus_log_create_error(r
);
587 r
= sd_bus_message_close_container(m
);
589 return bus_log_create_error(r
);
594 int bus_append_unit_property_assignment_many(sd_bus_message
*m
, char **l
) {
601 r
= bus_append_unit_property_assignment(m
, *i
);
609 typedef struct BusWaitForJobs
{
616 sd_bus_slot
*slot_job_removed
;
617 sd_bus_slot
*slot_disconnected
;
620 static int match_disconnected(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
623 log_error("Warning! D-Bus connection terminated.");
624 sd_bus_close(sd_bus_message_get_bus(m
));
629 static int match_job_removed(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
630 const char *path
, *unit
, *result
;
631 BusWaitForJobs
*d
= userdata
;
639 r
= sd_bus_message_read(m
, "uoss", &id
, &path
, &unit
, &result
);
641 bus_log_parse_error(r
);
645 found
= set_remove(d
->jobs
, (char*) path
);
651 if (!isempty(result
))
652 d
->result
= strdup(result
);
655 d
->name
= strdup(unit
);
660 void bus_wait_for_jobs_free(BusWaitForJobs
*d
) {
664 set_free_free(d
->jobs
);
666 sd_bus_slot_unref(d
->slot_disconnected
);
667 sd_bus_slot_unref(d
->slot_job_removed
);
669 sd_bus_unref(d
->bus
);
677 int bus_wait_for_jobs_new(sd_bus
*bus
, BusWaitForJobs
**ret
) {
678 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*d
= NULL
;
684 d
= new0(BusWaitForJobs
, 1);
688 d
->bus
= sd_bus_ref(bus
);
690 /* When we are a bus client we match by sender. Direct
691 * connections OTOH have no initialized sender field, and
692 * hence we ignore the sender then */
693 r
= sd_bus_add_match(
695 &d
->slot_job_removed
,
698 "sender='org.freedesktop.systemd1',"
699 "interface='org.freedesktop.systemd1.Manager',"
700 "member='JobRemoved',"
701 "path='/org/freedesktop/systemd1'" :
703 "interface='org.freedesktop.systemd1.Manager',"
704 "member='JobRemoved',"
705 "path='/org/freedesktop/systemd1'",
706 match_job_removed
, d
);
710 r
= sd_bus_add_match(
712 &d
->slot_disconnected
,
714 "sender='org.freedesktop.DBus.Local',"
715 "interface='org.freedesktop.DBus.Local',"
716 "member='Disconnected'",
717 match_disconnected
, d
);
727 static int bus_process_wait(sd_bus
*bus
) {
731 r
= sd_bus_process(bus
, NULL
);
737 r
= sd_bus_wait(bus
, (uint64_t) -1);
743 static int bus_job_get_service_result(BusWaitForJobs
*d
, char **result
) {
744 _cleanup_free_
char *dbus_path
= NULL
;
750 dbus_path
= unit_dbus_path_from_name(d
->name
);
754 return sd_bus_get_property_string(d
->bus
,
755 "org.freedesktop.systemd1",
757 "org.freedesktop.systemd1.Service",
763 static const struct {
764 const char *result
, *explanation
;
765 } explanations
[] = {
766 { "resources", "of unavailable resources or another system error" },
767 { "timeout", "a timeout was exceeded" },
768 { "exit-code", "the control process exited with error code" },
769 { "signal", "a fatal signal was delivered to the control process" },
770 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
771 { "watchdog", "the service failed to send watchdog ping" },
772 { "start-limit", "start of the service was attempted too often" }
775 static void log_job_error_with_service_result(const char* service
, const char *result
, const char* const* extra_args
) {
776 _cleanup_free_
char *service_shell_quoted
= NULL
;
777 const char *systemctl
= "systemctl", *journalctl
= "journalctl";
781 service_shell_quoted
= shell_maybe_quote(service
);
783 if (extra_args
&& extra_args
[1]) {
784 _cleanup_free_
char *t
;
786 t
= strv_join((char**) extra_args
, " ");
787 systemctl
= strjoina("systemctl ", t
? : "<args>");
788 journalctl
= strjoina("journalctl ", t
? : "<args>");
791 if (!isempty(result
)) {
794 for (i
= 0; i
< ELEMENTSOF(explanations
); ++i
)
795 if (streq(result
, explanations
[i
].result
))
798 if (i
< ELEMENTSOF(explanations
)) {
799 log_error("Job for %s failed because %s.\n"
800 "See \"%s status %s\" and \"%s -xe\" for details.\n",
802 explanations
[i
].explanation
,
804 service_shell_quoted
?: "<service>",
810 log_error("Job for %s failed.\n"
811 "See \"%s status %s\" and \"%s -xe\" for details.\n",
814 service_shell_quoted
?: "<service>",
818 /* For some results maybe additional explanation is required */
819 if (streq_ptr(result
, "start-limit"))
820 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
821 "followed by \"%1$s start %2$s\" again.",
823 service_shell_quoted
?: "<service>");
826 static int check_wait_response(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
832 if (streq(d
->result
, "canceled"))
833 log_error("Job for %s canceled.", strna(d
->name
));
834 else if (streq(d
->result
, "timeout"))
835 log_error("Job for %s timed out.", strna(d
->name
));
836 else if (streq(d
->result
, "dependency"))
837 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d
->name
));
838 else if (streq(d
->result
, "invalid"))
839 log_error("%s is not active, cannot reload.", strna(d
->name
));
840 else if (streq(d
->result
, "assert"))
841 log_error("Assertion failed on job for %s.", strna(d
->name
));
842 else if (streq(d
->result
, "unsupported"))
843 log_error("Operation on or unit type of %s not supported on this system.", strna(d
->name
));
844 else if (streq(d
->result
, "collected"))
845 log_error("Queued job for %s was garbage collected.", strna(d
->name
));
846 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped")) {
849 _cleanup_free_
char *result
= NULL
;
851 q
= bus_job_get_service_result(d
, &result
);
853 log_debug_errno(q
, "Failed to get Result property of service %s: %m", d
->name
);
855 log_job_error_with_service_result(d
->name
, result
, extra_args
);
857 log_error("Job failed. See \"journalctl -xe\" for details.");
861 if (STR_IN_SET(d
->result
, "canceled", "collected"))
863 else if (streq(d
->result
, "timeout"))
865 else if (streq(d
->result
, "dependency"))
867 else if (streq(d
->result
, "invalid"))
869 else if (streq(d
->result
, "assert"))
871 else if (streq(d
->result
, "unsupported"))
873 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped"))
879 int bus_wait_for_jobs(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
884 while (!set_isempty(d
->jobs
)) {
887 q
= bus_process_wait(d
->bus
);
889 return log_error_errno(q
, "Failed to wait for response: %m");
892 q
= check_wait_response(d
, quiet
, extra_args
);
893 /* Return the first error as it is most likely to be
898 log_debug_errno(q
, "Got result %s/%m for job %s", strna(d
->result
), strna(d
->name
));
901 d
->name
= mfree(d
->name
);
902 d
->result
= mfree(d
->result
);
908 int bus_wait_for_jobs_add(BusWaitForJobs
*d
, const char *path
) {
913 r
= set_ensure_allocated(&d
->jobs
, &string_hash_ops
);
917 return set_put_strdup(d
->jobs
, path
);
920 int bus_wait_for_jobs_one(BusWaitForJobs
*d
, const char *path
, bool quiet
) {
923 r
= bus_wait_for_jobs_add(d
, path
);
927 return bus_wait_for_jobs(d
, quiet
, NULL
);
930 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message
*m
, bool quiet
, UnitFileChange
**changes
, unsigned *n_changes
) {
931 const char *type
, *path
, *source
;
934 /* changes is dereferenced when calling unit_file_dump_changes() later,
935 * so we have to make sure this is not NULL. */
939 r
= sd_bus_message_enter_container(m
, SD_BUS_TYPE_ARRAY
, "(sss)");
941 return bus_log_parse_error(r
);
943 while ((r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
)) > 0) {
944 /* We expect only "success" changes to be sent over the bus.
945 Hence, reject anything negative. */
946 UnitFileChangeType ch
= unit_file_change_type_from_string(type
);
949 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type
, path
);
953 r
= unit_file_changes_add(changes
, n_changes
, ch
, path
, source
);
958 return bus_log_parse_error(r
);
960 r
= sd_bus_message_exit_container(m
);
962 return bus_log_parse_error(r
);
964 unit_file_dump_changes(0, NULL
, *changes
, *n_changes
, false);
970 bool is_const
; /* If false, cgroup_path should be free()'d */
972 Hashmap
*pids
; /* PID → process name */
975 struct CGroupInfo
*parent
;
976 LIST_FIELDS(struct CGroupInfo
, siblings
);
977 LIST_HEAD(struct CGroupInfo
, children
);
981 static bool IS_ROOT(const char *p
) {
982 return isempty(p
) || streq(p
, "/");
985 static int add_cgroup(Hashmap
*cgroups
, const char *path
, bool is_const
, struct CGroupInfo
**ret
) {
986 struct CGroupInfo
*parent
= NULL
, *cg
;
995 cg
= hashmap_get(cgroups
, path
);
1001 if (!IS_ROOT(path
)) {
1004 e
= strrchr(path
, '/');
1008 pp
= strndupa(path
, e
- path
);
1012 r
= add_cgroup(cgroups
, pp
, false, &parent
);
1017 cg
= new0(struct CGroupInfo
, 1);
1022 cg
->cgroup_path
= (char*) path
;
1024 cg
->cgroup_path
= strdup(path
);
1025 if (!cg
->cgroup_path
) {
1031 cg
->is_const
= is_const
;
1032 cg
->parent
= parent
;
1034 r
= hashmap_put(cgroups
, cg
->cgroup_path
, cg
);
1037 free(cg
->cgroup_path
);
1043 LIST_PREPEND(siblings
, parent
->children
, cg
);
1044 parent
->n_children
++;
1051 static int add_process(
1057 struct CGroupInfo
*cg
;
1064 r
= add_cgroup(cgroups
, path
, true, &cg
);
1068 r
= hashmap_ensure_allocated(&cg
->pids
, &trivial_hash_ops
);
1072 return hashmap_put(cg
->pids
, PID_TO_PTR(pid
), (void*) name
);
1075 static void remove_cgroup(Hashmap
*cgroups
, struct CGroupInfo
*cg
) {
1079 while (cg
->children
)
1080 remove_cgroup(cgroups
, cg
->children
);
1082 hashmap_remove(cgroups
, cg
->cgroup_path
);
1085 free(cg
->cgroup_path
);
1087 hashmap_free(cg
->pids
);
1090 LIST_REMOVE(siblings
, cg
->parent
->children
, cg
);
1095 static int cgroup_info_compare_func(const void *a
, const void *b
) {
1096 const struct CGroupInfo
*x
= *(const struct CGroupInfo
* const*) a
, *y
= *(const struct CGroupInfo
* const*) b
;
1101 return strcmp(x
->cgroup_path
, y
->cgroup_path
);
1104 static int dump_processes(
1106 const char *cgroup_path
,
1109 OutputFlags flags
) {
1111 struct CGroupInfo
*cg
;
1116 if (IS_ROOT(cgroup_path
))
1119 cg
= hashmap_get(cgroups
, cgroup_path
);
1123 if (!hashmap_isempty(cg
->pids
)) {
1131 /* Order processes by their PID */
1132 pids
= newa(pid_t
, hashmap_size(cg
->pids
));
1134 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
)
1135 pids
[n
++] = PTR_TO_PID(pidp
);
1137 assert(n
== hashmap_size(cg
->pids
));
1138 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1140 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1142 for (i
= 0; i
< n
; i
++) {
1143 _cleanup_free_
char *e
= NULL
;
1144 const char *special
;
1147 name
= hashmap_get(cg
->pids
, PID_TO_PTR(pids
[i
]));
1150 if (n_columns
!= 0) {
1153 k
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1155 e
= ellipsize(name
, k
, 100);
1160 more
= i
+1 < n
|| cg
->children
;
1161 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1163 fprintf(stdout
, "%s%s%*"PID_PRI
" %s\n",
1172 struct CGroupInfo
**children
, *child
;
1175 /* Order subcgroups by their name */
1176 children
= newa(struct CGroupInfo
*, cg
->n_children
);
1177 LIST_FOREACH(siblings
, child
, cg
->children
)
1178 children
[n
++] = child
;
1179 assert(n
== cg
->n_children
);
1180 qsort_safe(children
, n
, sizeof(struct CGroupInfo
*), cgroup_info_compare_func
);
1183 n_columns
= MAX(LESS_BY(n_columns
, 2U), 20U);
1185 for (i
= 0; i
< n
; i
++) {
1186 _cleanup_free_
char *pp
= NULL
;
1187 const char *name
, *special
;
1190 child
= children
[i
];
1192 name
= strrchr(child
->cgroup_path
, '/');
1198 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1200 fputs(prefix
, stdout
);
1201 fputs(special
, stdout
);
1202 fputs(name
, stdout
);
1203 fputc('\n', stdout
);
1205 special
= special_glyph(more
? TREE_VERTICAL
: TREE_SPACE
);
1207 pp
= strappend(prefix
, special
);
1211 r
= dump_processes(cgroups
, child
->cgroup_path
, pp
, n_columns
, flags
);
1221 static int dump_extra_processes(
1225 OutputFlags flags
) {
1227 _cleanup_free_ pid_t
*pids
= NULL
;
1228 _cleanup_hashmap_free_ Hashmap
*names
= NULL
;
1229 struct CGroupInfo
*cg
;
1230 size_t n_allocated
= 0, n
= 0, k
;
1234 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1235 * combined, sorted, linear list. */
1237 HASHMAP_FOREACH(cg
, cgroups
, i
) {
1245 if (hashmap_isempty(cg
->pids
))
1248 r
= hashmap_ensure_allocated(&names
, &trivial_hash_ops
);
1252 if (!GREEDY_REALLOC(pids
, n_allocated
, n
+ hashmap_size(cg
->pids
)))
1255 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
) {
1256 pids
[n
++] = PTR_TO_PID(pidp
);
1258 r
= hashmap_put(names
, pidp
, (void*) name
);
1267 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1268 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1270 for (k
= 0; k
< n
; k
++) {
1271 _cleanup_free_
char *e
= NULL
;
1274 name
= hashmap_get(names
, PID_TO_PTR(pids
[k
]));
1277 if (n_columns
!= 0) {
1280 z
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1282 e
= ellipsize(name
, z
, 100);
1287 fprintf(stdout
, "%s%s %*" PID_PRI
" %s\n",
1289 special_glyph(TRIANGULAR_BULLET
),
1297 int unit_show_processes(
1300 const char *cgroup_path
,
1304 sd_bus_error
*error
) {
1306 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1307 Hashmap
*cgroups
= NULL
;
1308 struct CGroupInfo
*cg
;
1314 if (flags
& OUTPUT_FULL_WIDTH
)
1316 else if (n_columns
<= 0)
1317 n_columns
= columns();
1319 prefix
= strempty(prefix
);
1321 r
= sd_bus_call_method(
1323 "org.freedesktop.systemd1",
1324 "/org/freedesktop/systemd1",
1325 "org.freedesktop.systemd1.Manager",
1334 cgroups
= hashmap_new(&string_hash_ops
);
1338 r
= sd_bus_message_enter_container(reply
, 'a', "(sus)");
1343 const char *path
= NULL
, *name
= NULL
;
1346 r
= sd_bus_message_read(reply
, "(sus)", &path
, &pid
, &name
);
1352 r
= add_process(cgroups
, path
, pid
, name
);
1357 r
= sd_bus_message_exit_container(reply
);
1361 r
= dump_processes(cgroups
, cgroup_path
, prefix
, n_columns
, flags
);
1365 r
= dump_extra_processes(cgroups
, prefix
, n_columns
, flags
);
1368 while ((cg
= hashmap_first(cgroups
)))
1369 remove_cgroup(cgroups
, cg
);
1371 hashmap_free(cgroups
);