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
);
404 _cleanup_free_
char *word
= NULL
;
406 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_CUNESCAPE
);
408 log_error("Failed to parse Environment value %s", eq
);
414 if (streq(field
, "Environment")) {
415 if (!env_assignment_is_valid(word
)) {
416 log_error("Invalid environment assignment: %s", word
);
419 } else { /* PassEnvironment */
420 if (!env_name_is_valid(word
)) {
421 log_error("Invalid environment variable name: %s", word
);
426 r
= sd_bus_message_append_basic(m
, 's', word
);
428 return bus_log_create_error(r
);
431 r
= sd_bus_message_close_container(m
);
433 return bus_log_create_error(r
);
435 r
= sd_bus_message_close_container(m
);
437 } else if (streq(field
, "KillSignal")) {
440 sig
= signal_from_string_try_harder(eq
);
442 log_error("Failed to parse %s value %s.", field
, eq
);
446 r
= sd_bus_message_append(m
, "v", "i", sig
);
448 } else if (streq(field
, "TimerSlackNSec")) {
451 r
= parse_nsec(eq
, &n
);
453 log_error("Failed to parse %s value %s", field
, eq
);
457 r
= sd_bus_message_append(m
, "v", "t", n
);
458 } else if (streq(field
, "OOMScoreAdjust")) {
461 r
= safe_atoi(eq
, &oa
);
463 log_error("Failed to parse %s value %s", field
, eq
);
467 if (!oom_score_adjust_is_valid(oa
)) {
468 log_error("OOM score adjust value out of range");
472 r
= sd_bus_message_append(m
, "v", "i", oa
);
473 } else if (STR_IN_SET(field
, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
474 "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
477 r
= sd_bus_message_open_container(m
, 'v', "as");
479 return bus_log_create_error(r
);
481 r
= sd_bus_message_open_container(m
, 'a', "s");
483 return bus_log_create_error(r
);
488 _cleanup_free_
char *word
= NULL
;
491 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
493 log_error("Failed to parse %s value %s", field
, eq
);
499 if (!utf8_is_valid(word
)) {
500 log_error("Failed to parse %s value %s", field
, eq
);
504 offset
= word
[0] == '-';
505 if (!path_is_absolute(word
+ offset
)) {
506 log_error("Failed to parse %s value %s", field
, eq
);
510 path_kill_slashes(word
+ offset
);
512 r
= sd_bus_message_append_basic(m
, 's', word
);
514 return bus_log_create_error(r
);
517 r
= sd_bus_message_close_container(m
);
519 return bus_log_create_error(r
);
521 r
= sd_bus_message_close_container(m
);
523 } else if (streq(field
, "RuntimeDirectory")) {
526 r
= sd_bus_message_open_container(m
, 'v', "as");
528 return bus_log_create_error(r
);
530 r
= sd_bus_message_open_container(m
, 'a', "s");
532 return bus_log_create_error(r
);
537 _cleanup_free_
char *word
= NULL
;
539 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
541 return log_error_errno(r
, "Failed to parse %s value %s", field
, eq
);
546 r
= sd_bus_message_append_basic(m
, 's', word
);
548 return bus_log_create_error(r
);
551 r
= sd_bus_message_close_container(m
);
553 return bus_log_create_error(r
);
555 r
= sd_bus_message_close_container(m
);
557 } else if (streq(field
, "RestrictNamespaces")) {
566 r
= parse_boolean(eq
);
570 flags
= NAMESPACE_FLAGS_ALL
;
572 r
= namespace_flag_from_string_many(eq
, &flags
);
574 return log_error_errno(r
, "Failed to parse %s value %s.", field
, eq
);
578 flags
= (~flags
) & NAMESPACE_FLAGS_ALL
;
580 r
= sd_bus_message_append(m
, "v", "t", flags
);
582 log_error("Unknown assignment %s.", assignment
);
588 return bus_log_create_error(r
);
590 r
= sd_bus_message_close_container(m
);
592 return bus_log_create_error(r
);
597 int bus_append_unit_property_assignment_many(sd_bus_message
*m
, char **l
) {
604 r
= bus_append_unit_property_assignment(m
, *i
);
612 typedef struct BusWaitForJobs
{
619 sd_bus_slot
*slot_job_removed
;
620 sd_bus_slot
*slot_disconnected
;
623 static int match_disconnected(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
626 log_error("Warning! D-Bus connection terminated.");
627 sd_bus_close(sd_bus_message_get_bus(m
));
632 static int match_job_removed(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
633 const char *path
, *unit
, *result
;
634 BusWaitForJobs
*d
= userdata
;
642 r
= sd_bus_message_read(m
, "uoss", &id
, &path
, &unit
, &result
);
644 bus_log_parse_error(r
);
648 found
= set_remove(d
->jobs
, (char*) path
);
654 if (!isempty(result
))
655 d
->result
= strdup(result
);
658 d
->name
= strdup(unit
);
663 void bus_wait_for_jobs_free(BusWaitForJobs
*d
) {
667 set_free_free(d
->jobs
);
669 sd_bus_slot_unref(d
->slot_disconnected
);
670 sd_bus_slot_unref(d
->slot_job_removed
);
672 sd_bus_unref(d
->bus
);
680 int bus_wait_for_jobs_new(sd_bus
*bus
, BusWaitForJobs
**ret
) {
681 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*d
= NULL
;
687 d
= new0(BusWaitForJobs
, 1);
691 d
->bus
= sd_bus_ref(bus
);
693 /* When we are a bus client we match by sender. Direct
694 * connections OTOH have no initialized sender field, and
695 * hence we ignore the sender then */
696 r
= sd_bus_add_match(
698 &d
->slot_job_removed
,
701 "sender='org.freedesktop.systemd1',"
702 "interface='org.freedesktop.systemd1.Manager',"
703 "member='JobRemoved',"
704 "path='/org/freedesktop/systemd1'" :
706 "interface='org.freedesktop.systemd1.Manager',"
707 "member='JobRemoved',"
708 "path='/org/freedesktop/systemd1'",
709 match_job_removed
, d
);
713 r
= sd_bus_add_match(
715 &d
->slot_disconnected
,
717 "sender='org.freedesktop.DBus.Local',"
718 "interface='org.freedesktop.DBus.Local',"
719 "member='Disconnected'",
720 match_disconnected
, d
);
730 static int bus_process_wait(sd_bus
*bus
) {
734 r
= sd_bus_process(bus
, NULL
);
740 r
= sd_bus_wait(bus
, (uint64_t) -1);
746 static int bus_job_get_service_result(BusWaitForJobs
*d
, char **result
) {
747 _cleanup_free_
char *dbus_path
= NULL
;
753 dbus_path
= unit_dbus_path_from_name(d
->name
);
757 return sd_bus_get_property_string(d
->bus
,
758 "org.freedesktop.systemd1",
760 "org.freedesktop.systemd1.Service",
766 static const struct {
767 const char *result
, *explanation
;
768 } explanations
[] = {
769 { "resources", "of unavailable resources or another system error" },
770 { "timeout", "a timeout was exceeded" },
771 { "exit-code", "the control process exited with error code" },
772 { "signal", "a fatal signal was delivered to the control process" },
773 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
774 { "watchdog", "the service failed to send watchdog ping" },
775 { "start-limit", "start of the service was attempted too often" }
778 static void log_job_error_with_service_result(const char* service
, const char *result
, const char* const* extra_args
) {
779 _cleanup_free_
char *service_shell_quoted
= NULL
;
780 const char *systemctl
= "systemctl", *journalctl
= "journalctl";
784 service_shell_quoted
= shell_maybe_quote(service
);
786 if (extra_args
&& extra_args
[1]) {
787 _cleanup_free_
char *t
;
789 t
= strv_join((char**) extra_args
, " ");
790 systemctl
= strjoina("systemctl ", t
? : "<args>");
791 journalctl
= strjoina("journalctl ", t
? : "<args>");
794 if (!isempty(result
)) {
797 for (i
= 0; i
< ELEMENTSOF(explanations
); ++i
)
798 if (streq(result
, explanations
[i
].result
))
801 if (i
< ELEMENTSOF(explanations
)) {
802 log_error("Job for %s failed because %s.\n"
803 "See \"%s status %s\" and \"%s -xe\" for details.\n",
805 explanations
[i
].explanation
,
807 service_shell_quoted
?: "<service>",
813 log_error("Job for %s failed.\n"
814 "See \"%s status %s\" and \"%s -xe\" for details.\n",
817 service_shell_quoted
?: "<service>",
821 /* For some results maybe additional explanation is required */
822 if (streq_ptr(result
, "start-limit"))
823 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
824 "followed by \"%1$s start %2$s\" again.",
826 service_shell_quoted
?: "<service>");
829 static int check_wait_response(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
835 if (streq(d
->result
, "canceled"))
836 log_error("Job for %s canceled.", strna(d
->name
));
837 else if (streq(d
->result
, "timeout"))
838 log_error("Job for %s timed out.", strna(d
->name
));
839 else if (streq(d
->result
, "dependency"))
840 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d
->name
));
841 else if (streq(d
->result
, "invalid"))
842 log_error("%s is not active, cannot reload.", strna(d
->name
));
843 else if (streq(d
->result
, "assert"))
844 log_error("Assertion failed on job for %s.", strna(d
->name
));
845 else if (streq(d
->result
, "unsupported"))
846 log_error("Operation on or unit type of %s not supported on this system.", strna(d
->name
));
847 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped")) {
850 _cleanup_free_
char *result
= NULL
;
852 q
= bus_job_get_service_result(d
, &result
);
854 log_debug_errno(q
, "Failed to get Result property of service %s: %m", d
->name
);
856 log_job_error_with_service_result(d
->name
, result
, extra_args
);
858 log_error("Job failed. See \"journalctl -xe\" for details.");
862 if (streq(d
->result
, "canceled"))
864 else if (streq(d
->result
, "timeout"))
866 else if (streq(d
->result
, "dependency"))
868 else if (streq(d
->result
, "invalid"))
870 else if (streq(d
->result
, "assert"))
872 else if (streq(d
->result
, "unsupported"))
874 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped"))
880 int bus_wait_for_jobs(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
885 while (!set_isempty(d
->jobs
)) {
888 q
= bus_process_wait(d
->bus
);
890 return log_error_errno(q
, "Failed to wait for response: %m");
893 q
= check_wait_response(d
, quiet
, extra_args
);
894 /* Return the first error as it is most likely to be
899 log_debug_errno(q
, "Got result %s/%m for job %s", strna(d
->result
), strna(d
->name
));
902 d
->name
= mfree(d
->name
);
903 d
->result
= mfree(d
->result
);
909 int bus_wait_for_jobs_add(BusWaitForJobs
*d
, const char *path
) {
914 r
= set_ensure_allocated(&d
->jobs
, &string_hash_ops
);
918 return set_put_strdup(d
->jobs
, path
);
921 int bus_wait_for_jobs_one(BusWaitForJobs
*d
, const char *path
, bool quiet
) {
924 r
= bus_wait_for_jobs_add(d
, path
);
928 return bus_wait_for_jobs(d
, quiet
, NULL
);
931 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message
*m
, bool quiet
, UnitFileChange
**changes
, unsigned *n_changes
) {
932 const char *type
, *path
, *source
;
935 /* changes is dereferenced when calling unit_file_dump_changes() later,
936 * so we have to make sure this is not NULL. */
940 r
= sd_bus_message_enter_container(m
, SD_BUS_TYPE_ARRAY
, "(sss)");
942 return bus_log_parse_error(r
);
944 while ((r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
)) > 0) {
945 /* We expect only "success" changes to be sent over the bus.
946 Hence, reject anything negative. */
947 UnitFileChangeType ch
= unit_file_change_type_from_string(type
);
950 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type
, path
);
954 r
= unit_file_changes_add(changes
, n_changes
, ch
, path
, source
);
959 return bus_log_parse_error(r
);
961 r
= sd_bus_message_exit_container(m
);
963 return bus_log_parse_error(r
);
965 unit_file_dump_changes(0, NULL
, *changes
, *n_changes
, false);
971 bool is_const
; /* If false, cgroup_path should be free()'d */
973 Hashmap
*pids
; /* PID → process name */
976 struct CGroupInfo
*parent
;
977 LIST_FIELDS(struct CGroupInfo
, siblings
);
978 LIST_HEAD(struct CGroupInfo
, children
);
982 static bool IS_ROOT(const char *p
) {
983 return isempty(p
) || streq(p
, "/");
986 static int add_cgroup(Hashmap
*cgroups
, const char *path
, bool is_const
, struct CGroupInfo
**ret
) {
987 struct CGroupInfo
*parent
= NULL
, *cg
;
996 cg
= hashmap_get(cgroups
, path
);
1002 if (!IS_ROOT(path
)) {
1005 e
= strrchr(path
, '/');
1009 pp
= strndupa(path
, e
- path
);
1013 r
= add_cgroup(cgroups
, pp
, false, &parent
);
1018 cg
= new0(struct CGroupInfo
, 1);
1023 cg
->cgroup_path
= (char*) path
;
1025 cg
->cgroup_path
= strdup(path
);
1026 if (!cg
->cgroup_path
) {
1032 cg
->is_const
= is_const
;
1033 cg
->parent
= parent
;
1035 r
= hashmap_put(cgroups
, cg
->cgroup_path
, cg
);
1038 free(cg
->cgroup_path
);
1044 LIST_PREPEND(siblings
, parent
->children
, cg
);
1045 parent
->n_children
++;
1052 static int add_process(
1058 struct CGroupInfo
*cg
;
1065 r
= add_cgroup(cgroups
, path
, true, &cg
);
1069 r
= hashmap_ensure_allocated(&cg
->pids
, &trivial_hash_ops
);
1073 return hashmap_put(cg
->pids
, PID_TO_PTR(pid
), (void*) name
);
1076 static void remove_cgroup(Hashmap
*cgroups
, struct CGroupInfo
*cg
) {
1080 while (cg
->children
)
1081 remove_cgroup(cgroups
, cg
->children
);
1083 hashmap_remove(cgroups
, cg
->cgroup_path
);
1086 free(cg
->cgroup_path
);
1088 hashmap_free(cg
->pids
);
1091 LIST_REMOVE(siblings
, cg
->parent
->children
, cg
);
1096 static int cgroup_info_compare_func(const void *a
, const void *b
) {
1097 const struct CGroupInfo
*x
= *(const struct CGroupInfo
* const*) a
, *y
= *(const struct CGroupInfo
* const*) b
;
1102 return strcmp(x
->cgroup_path
, y
->cgroup_path
);
1105 static int dump_processes(
1107 const char *cgroup_path
,
1110 OutputFlags flags
) {
1112 struct CGroupInfo
*cg
;
1117 if (IS_ROOT(cgroup_path
))
1120 cg
= hashmap_get(cgroups
, cgroup_path
);
1124 if (!hashmap_isempty(cg
->pids
)) {
1132 /* Order processes by their PID */
1133 pids
= newa(pid_t
, hashmap_size(cg
->pids
));
1135 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
)
1136 pids
[n
++] = PTR_TO_PID(pidp
);
1138 assert(n
== hashmap_size(cg
->pids
));
1139 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1141 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1143 for (i
= 0; i
< n
; i
++) {
1144 _cleanup_free_
char *e
= NULL
;
1145 const char *special
;
1148 name
= hashmap_get(cg
->pids
, PID_TO_PTR(pids
[i
]));
1151 if (n_columns
!= 0) {
1154 k
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1156 e
= ellipsize(name
, k
, 100);
1161 more
= i
+1 < n
|| cg
->children
;
1162 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1164 fprintf(stdout
, "%s%s%*"PID_PRI
" %s\n",
1173 struct CGroupInfo
**children
, *child
;
1176 /* Order subcgroups by their name */
1177 children
= newa(struct CGroupInfo
*, cg
->n_children
);
1178 LIST_FOREACH(siblings
, child
, cg
->children
)
1179 children
[n
++] = child
;
1180 assert(n
== cg
->n_children
);
1181 qsort_safe(children
, n
, sizeof(struct CGroupInfo
*), cgroup_info_compare_func
);
1184 n_columns
= MAX(LESS_BY(n_columns
, 2U), 20U);
1186 for (i
= 0; i
< n
; i
++) {
1187 _cleanup_free_
char *pp
= NULL
;
1188 const char *name
, *special
;
1191 child
= children
[i
];
1193 name
= strrchr(child
->cgroup_path
, '/');
1199 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1201 fputs(prefix
, stdout
);
1202 fputs(special
, stdout
);
1203 fputs(name
, stdout
);
1204 fputc('\n', stdout
);
1206 special
= special_glyph(more
? TREE_VERTICAL
: TREE_SPACE
);
1208 pp
= strappend(prefix
, special
);
1212 r
= dump_processes(cgroups
, child
->cgroup_path
, pp
, n_columns
, flags
);
1222 static int dump_extra_processes(
1226 OutputFlags flags
) {
1228 _cleanup_free_ pid_t
*pids
= NULL
;
1229 _cleanup_hashmap_free_ Hashmap
*names
= NULL
;
1230 struct CGroupInfo
*cg
;
1231 size_t n_allocated
= 0, n
= 0, k
;
1235 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1236 * combined, sorted, linear list. */
1238 HASHMAP_FOREACH(cg
, cgroups
, i
) {
1246 if (hashmap_isempty(cg
->pids
))
1249 r
= hashmap_ensure_allocated(&names
, &trivial_hash_ops
);
1253 if (!GREEDY_REALLOC(pids
, n_allocated
, n
+ hashmap_size(cg
->pids
)))
1256 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
) {
1257 pids
[n
++] = PTR_TO_PID(pidp
);
1259 r
= hashmap_put(names
, pidp
, (void*) name
);
1268 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1269 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1271 for (k
= 0; k
< n
; k
++) {
1272 _cleanup_free_
char *e
= NULL
;
1275 name
= hashmap_get(names
, PID_TO_PTR(pids
[k
]));
1278 if (n_columns
!= 0) {
1281 z
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1283 e
= ellipsize(name
, z
, 100);
1288 fprintf(stdout
, "%s%s %*" PID_PRI
" %s\n",
1290 special_glyph(TRIANGULAR_BULLET
),
1298 int unit_show_processes(
1301 const char *cgroup_path
,
1305 sd_bus_error
*error
) {
1307 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1308 Hashmap
*cgroups
= NULL
;
1309 struct CGroupInfo
*cg
;
1315 if (flags
& OUTPUT_FULL_WIDTH
)
1317 else if (n_columns
<= 0)
1318 n_columns
= columns();
1320 prefix
= strempty(prefix
);
1322 r
= sd_bus_call_method(
1324 "org.freedesktop.systemd1",
1325 "/org/freedesktop/systemd1",
1326 "org.freedesktop.systemd1.Manager",
1335 cgroups
= hashmap_new(&string_hash_ops
);
1339 r
= sd_bus_message_enter_container(reply
, 'a', "(sus)");
1344 const char *path
= NULL
, *name
= NULL
;
1347 r
= sd_bus_message_read(reply
, "(sus)", &path
, &pid
, &name
);
1353 r
= add_process(cgroups
, path
, pid
, name
);
1358 r
= sd_bus_message_exit_container(reply
);
1362 r
= dump_processes(cgroups
, cgroup_path
, prefix
, n_columns
, flags
);
1366 r
= dump_extra_processes(cgroups
, prefix
, n_columns
, flags
);
1369 while ((cg
= hashmap_first(cgroups
)))
1370 remove_cgroup(cgroups
, cg
);
1372 hashmap_free(cgroups
);