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"
30 #include "parse-util.h"
31 #include "path-util.h"
32 #include "process-util.h"
33 #include "rlimit-util.h"
34 #include "signal-util.h"
35 #include "string-util.h"
36 #include "syslog-util.h"
37 #include "terminal-util.h"
41 int bus_parse_unit_info(sd_bus_message
*message
, UnitInfo
*u
) {
47 return sd_bus_message_read(
62 int bus_append_unit_property_assignment(sd_bus_message
*m
, const char *assignment
) {
63 const char *eq
, *field
;
69 eq
= strchr(assignment
, '=');
71 log_error("Not an assignment: %s", assignment
);
75 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
77 return bus_log_create_error(r
);
79 field
= strndupa(assignment
, eq
- assignment
);
82 if (streq(field
, "CPUQuota")) {
85 r
= sd_bus_message_append(m
, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY
);
87 r
= parse_percent(eq
);
89 log_error_errno(r
, "CPU quota '%s' invalid.", eq
);
93 r
= sd_bus_message_append(m
, "sv", "CPUQuotaPerSecUSec", "t", (usec_t
) r
* USEC_PER_SEC
/ 100U);
98 } else if (streq(field
, "EnvironmentFile")) {
100 r
= sd_bus_message_append(m
, "sv", "EnvironmentFiles", "a(sb)", 1,
101 eq
[0] == '-' ? eq
+ 1 : eq
,
105 } else if (STR_IN_SET(field
, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) {
110 r
= parse_sec(eq
, &t
);
112 return log_error_errno(r
, "Failed to parse %s= parameter: %s", field
, eq
);
115 n
= newa(char, l
+ 2);
119 /* Change suffix Sec → USec */
120 strcpy(mempcpy(n
, field
, l
- 3), "USec");
121 r
= sd_bus_message_append(m
, "sv", n
, "t", t
);
124 } else if (STR_IN_SET(field
, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
127 if (isempty(eq
) || streq(eq
, "infinity"))
128 bytes
= CGROUP_LIMIT_MAX
;
130 r
= parse_percent(eq
);
134 /* When this is a percentage we'll convert this into a relative value in the range
135 * 0…UINT32_MAX and pass it in the MemoryLowByPhysicalMemory property (and related
136 * ones). This way the physical memory size can be determined server-side */
138 n
= strjoina(field
, "ByPhysicalMemory");
139 r
= sd_bus_message_append(m
, "sv", n
, "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
143 r
= parse_size(eq
, 1024, &bytes
);
145 return log_error_errno(r
, "Failed to parse bytes specification %s", assignment
);
149 r
= sd_bus_message_append(m
, "sv", field
, "t", bytes
);
153 r
= sd_bus_message_append_basic(m
, SD_BUS_TYPE_STRING
, field
);
155 return bus_log_create_error(r
);
157 rl
= rlimit_from_string(field
);
162 r
= rlimit_parse(rl
, eq
, &l
);
164 return log_error_errno(r
, "Failed to parse resource limit: %s", eq
);
166 r
= sd_bus_message_append(m
, "v", "t", l
.rlim_max
);
168 return bus_log_create_error(r
);
170 r
= sd_bus_message_close_container(m
);
172 return bus_log_create_error(r
);
174 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
176 return bus_log_create_error(r
);
178 sn
= strjoina(field
, "Soft");
179 r
= sd_bus_message_append(m
, "sv", sn
, "t", l
.rlim_cur
);
181 } else if (STR_IN_SET(field
,
182 "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
183 "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
184 "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
185 "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges",
186 "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute",
187 "RestrictRealtime", "DynamicUser")) {
189 r
= parse_boolean(eq
);
191 return log_error_errno(r
, "Failed to parse boolean assignment %s.", assignment
);
193 r
= sd_bus_message_append(m
, "v", "b", r
);
195 } else if (streq(field
, "TasksMax")) {
198 if (isempty(eq
) || streq(eq
, "infinity"))
201 r
= safe_atou64(eq
, &n
);
203 log_error("Failed to parse maximum tasks specification %s", assignment
);
208 r
= sd_bus_message_append(m
, "v", "t", n
);
210 } else if (STR_IN_SET(field
, "CPUShares", "StartupCPUShares")) {
213 r
= cg_cpu_shares_parse(eq
, &u
);
215 log_error("Failed to parse %s value %s.", field
, eq
);
219 r
= sd_bus_message_append(m
, "v", "t", u
);
221 } else if (STR_IN_SET(field
, "IOWeight", "StartupIOWeight")) {
224 r
= cg_weight_parse(eq
, &u
);
226 log_error("Failed to parse %s value %s.", field
, eq
);
230 r
= sd_bus_message_append(m
, "v", "t", u
);
232 } else if (STR_IN_SET(field
, "BlockIOWeight", "StartupBlockIOWeight")) {
235 r
= cg_blkio_weight_parse(eq
, &u
);
237 log_error("Failed to parse %s value %s.", field
, eq
);
241 r
= sd_bus_message_append(m
, "v", "t", u
);
243 } else if (STR_IN_SET(field
,
244 "User", "Group", "DevicePolicy", "KillMode",
245 "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
246 "StandardInput", "StandardOutput", "StandardError",
247 "Description", "Slice", "Type", "WorkingDirectory",
248 "RootDirectory", "SyslogIdentifier", "ProtectSystem",
249 "ProtectHome", "SELinuxContext"))
250 r
= sd_bus_message_append(m
, "v", "s", eq
);
252 else if (streq(field
, "SyslogLevel")) {
255 level
= log_level_from_string(eq
);
257 log_error("Failed to parse %s value %s.", field
, eq
);
261 r
= sd_bus_message_append(m
, "v", "i", level
);
263 } else if (streq(field
, "SyslogFacility")) {
266 facility
= log_facility_unshifted_from_string(eq
);
268 log_error("Failed to parse %s value %s.", field
, eq
);
272 r
= sd_bus_message_append(m
, "v", "i", facility
);
274 } else if (streq(field
, "DeviceAllow")) {
277 r
= sd_bus_message_append(m
, "v", "a(ss)", 0);
279 const char *path
, *rwm
, *e
;
283 path
= strndupa(eq
, e
- eq
);
290 if (!path_startswith(path
, "/dev")) {
291 log_error("%s is not a device file in /dev.", path
);
295 r
= sd_bus_message_append(m
, "v", "a(ss)", 1, path
, rwm
);
298 } else if (cgroup_io_limit_type_from_string(field
) >= 0 || STR_IN_SET(field
, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
301 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
303 const char *path
, *bandwidth
, *e
;
308 path
= strndupa(eq
, e
- eq
);
311 log_error("Failed to parse %s value %s.", field
, eq
);
315 if (!path_startswith(path
, "/dev")) {
316 log_error("%s is not a device file in /dev.", path
);
320 if (streq(bandwidth
, "infinity")) {
321 bytes
= CGROUP_LIMIT_MAX
;
323 r
= parse_size(bandwidth
, 1000, &bytes
);
325 log_error("Failed to parse byte value %s.", bandwidth
);
330 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, bytes
);
333 } else if (STR_IN_SET(field
, "IODeviceWeight", "BlockIODeviceWeight")) {
336 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
338 const char *path
, *weight
, *e
;
343 path
= strndupa(eq
, e
- eq
);
346 log_error("Failed to parse %s value %s.", field
, eq
);
350 if (!path_startswith(path
, "/dev")) {
351 log_error("%s is not a device file in /dev.", path
);
355 r
= safe_atou64(weight
, &u
);
357 log_error("Failed to parse %s value %s.", field
, weight
);
360 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, u
);
363 } else if (streq(field
, "Nice")) {
366 r
= safe_atoi32(eq
, &i
);
368 log_error("Failed to parse %s value %s.", field
, eq
);
372 r
= sd_bus_message_append(m
, "v", "i", i
);
374 } else if (STR_IN_SET(field
, "Environment", "PassEnvironment")) {
377 r
= sd_bus_message_open_container(m
, 'v', "as");
379 return bus_log_create_error(r
);
381 r
= sd_bus_message_open_container(m
, 'a', "s");
383 return bus_log_create_error(r
);
388 _cleanup_free_
char *word
= NULL
;
390 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
|EXTRACT_CUNESCAPE
);
392 log_error("Failed to parse Environment value %s", eq
);
398 if (streq(field
, "Environment")) {
399 if (!env_assignment_is_valid(word
)) {
400 log_error("Invalid environment assignment: %s", word
);
403 } else { /* PassEnvironment */
404 if (!env_name_is_valid(word
)) {
405 log_error("Invalid environment variable name: %s", word
);
410 r
= sd_bus_message_append_basic(m
, 's', word
);
412 return bus_log_create_error(r
);
415 r
= sd_bus_message_close_container(m
);
417 return bus_log_create_error(r
);
419 r
= sd_bus_message_close_container(m
);
421 } else if (streq(field
, "KillSignal")) {
424 sig
= signal_from_string_try_harder(eq
);
426 log_error("Failed to parse %s value %s.", field
, eq
);
430 r
= sd_bus_message_append(m
, "v", "i", sig
);
432 } else if (streq(field
, "TimerSlackNSec")) {
435 r
= parse_nsec(eq
, &n
);
437 log_error("Failed to parse %s value %s", field
, eq
);
441 r
= sd_bus_message_append(m
, "v", "t", n
);
442 } else if (streq(field
, "OOMScoreAdjust")) {
445 r
= safe_atoi(eq
, &oa
);
447 log_error("Failed to parse %s value %s", field
, eq
);
451 if (!oom_score_adjust_is_valid(oa
)) {
452 log_error("OOM score adjust value out of range");
456 r
= sd_bus_message_append(m
, "v", "i", oa
);
457 } else if (STR_IN_SET(field
, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
458 "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
461 r
= sd_bus_message_open_container(m
, 'v', "as");
463 return bus_log_create_error(r
);
465 r
= sd_bus_message_open_container(m
, 'a', "s");
467 return bus_log_create_error(r
);
472 _cleanup_free_
char *word
= NULL
;
475 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
477 log_error("Failed to parse %s value %s", field
, eq
);
483 if (!utf8_is_valid(word
)) {
484 log_error("Failed to parse %s value %s", field
, eq
);
488 offset
= word
[0] == '-';
489 if (!path_is_absolute(word
+ offset
)) {
490 log_error("Failed to parse %s value %s", field
, eq
);
494 path_kill_slashes(word
+ offset
);
496 r
= sd_bus_message_append_basic(m
, 's', word
);
498 return bus_log_create_error(r
);
501 r
= sd_bus_message_close_container(m
);
503 return bus_log_create_error(r
);
505 r
= sd_bus_message_close_container(m
);
507 } else if (streq(field
, "RuntimeDirectory")) {
510 r
= sd_bus_message_open_container(m
, 'v', "as");
512 return bus_log_create_error(r
);
514 r
= sd_bus_message_open_container(m
, 'a', "s");
516 return bus_log_create_error(r
);
521 _cleanup_free_
char *word
= NULL
;
523 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
525 return log_error_errno(r
, "Failed to parse %s value %s", field
, eq
);
530 r
= sd_bus_message_append_basic(m
, 's', word
);
532 return bus_log_create_error(r
);
535 r
= sd_bus_message_close_container(m
);
537 return bus_log_create_error(r
);
539 r
= sd_bus_message_close_container(m
);
542 log_error("Unknown assignment %s.", assignment
);
548 return bus_log_create_error(r
);
550 r
= sd_bus_message_close_container(m
);
552 return bus_log_create_error(r
);
557 typedef struct BusWaitForJobs
{
564 sd_bus_slot
*slot_job_removed
;
565 sd_bus_slot
*slot_disconnected
;
568 static int match_disconnected(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
571 log_error("Warning! D-Bus connection terminated.");
572 sd_bus_close(sd_bus_message_get_bus(m
));
577 static int match_job_removed(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
578 const char *path
, *unit
, *result
;
579 BusWaitForJobs
*d
= userdata
;
587 r
= sd_bus_message_read(m
, "uoss", &id
, &path
, &unit
, &result
);
589 bus_log_parse_error(r
);
593 found
= set_remove(d
->jobs
, (char*) path
);
599 if (!isempty(result
))
600 d
->result
= strdup(result
);
603 d
->name
= strdup(unit
);
608 void bus_wait_for_jobs_free(BusWaitForJobs
*d
) {
612 set_free_free(d
->jobs
);
614 sd_bus_slot_unref(d
->slot_disconnected
);
615 sd_bus_slot_unref(d
->slot_job_removed
);
617 sd_bus_unref(d
->bus
);
625 int bus_wait_for_jobs_new(sd_bus
*bus
, BusWaitForJobs
**ret
) {
626 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*d
= NULL
;
632 d
= new0(BusWaitForJobs
, 1);
636 d
->bus
= sd_bus_ref(bus
);
638 /* When we are a bus client we match by sender. Direct
639 * connections OTOH have no initialized sender field, and
640 * hence we ignore the sender then */
641 r
= sd_bus_add_match(
643 &d
->slot_job_removed
,
646 "sender='org.freedesktop.systemd1',"
647 "interface='org.freedesktop.systemd1.Manager',"
648 "member='JobRemoved',"
649 "path='/org/freedesktop/systemd1'" :
651 "interface='org.freedesktop.systemd1.Manager',"
652 "member='JobRemoved',"
653 "path='/org/freedesktop/systemd1'",
654 match_job_removed
, d
);
658 r
= sd_bus_add_match(
660 &d
->slot_disconnected
,
662 "sender='org.freedesktop.DBus.Local',"
663 "interface='org.freedesktop.DBus.Local',"
664 "member='Disconnected'",
665 match_disconnected
, d
);
675 static int bus_process_wait(sd_bus
*bus
) {
679 r
= sd_bus_process(bus
, NULL
);
685 r
= sd_bus_wait(bus
, (uint64_t) -1);
691 static int bus_job_get_service_result(BusWaitForJobs
*d
, char **result
) {
692 _cleanup_free_
char *dbus_path
= NULL
;
698 dbus_path
= unit_dbus_path_from_name(d
->name
);
702 return sd_bus_get_property_string(d
->bus
,
703 "org.freedesktop.systemd1",
705 "org.freedesktop.systemd1.Service",
711 static const struct {
712 const char *result
, *explanation
;
713 } explanations
[] = {
714 { "resources", "of unavailable resources or another system error" },
715 { "timeout", "a timeout was exceeded" },
716 { "exit-code", "the control process exited with error code" },
717 { "signal", "a fatal signal was delivered to the control process" },
718 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
719 { "watchdog", "the service failed to send watchdog ping" },
720 { "start-limit", "start of the service was attempted too often" }
723 static void log_job_error_with_service_result(const char* service
, const char *result
, const char* const* extra_args
) {
724 _cleanup_free_
char *service_shell_quoted
= NULL
;
725 const char *systemctl
= "systemctl", *journalctl
= "journalctl";
729 service_shell_quoted
= shell_maybe_quote(service
);
731 if (extra_args
&& extra_args
[1]) {
732 _cleanup_free_
char *t
;
734 t
= strv_join((char**) extra_args
, " ");
735 systemctl
= strjoina("systemctl ", t
? : "<args>");
736 journalctl
= strjoina("journalctl ", t
? : "<args>");
739 if (!isempty(result
)) {
742 for (i
= 0; i
< ELEMENTSOF(explanations
); ++i
)
743 if (streq(result
, explanations
[i
].result
))
746 if (i
< ELEMENTSOF(explanations
)) {
747 log_error("Job for %s failed because %s.\n"
748 "See \"%s status %s\" and \"%s -xe\" for details.\n",
750 explanations
[i
].explanation
,
752 service_shell_quoted
?: "<service>",
758 log_error("Job for %s failed.\n"
759 "See \"%s status %s\" and \"%s -xe\" for details.\n",
762 service_shell_quoted
?: "<service>",
766 /* For some results maybe additional explanation is required */
767 if (streq_ptr(result
, "start-limit"))
768 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
769 "followed by \"%1$s start %2$s\" again.",
771 service_shell_quoted
?: "<service>");
774 static int check_wait_response(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
780 if (streq(d
->result
, "canceled"))
781 log_error("Job for %s canceled.", strna(d
->name
));
782 else if (streq(d
->result
, "timeout"))
783 log_error("Job for %s timed out.", strna(d
->name
));
784 else if (streq(d
->result
, "dependency"))
785 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d
->name
));
786 else if (streq(d
->result
, "invalid"))
787 log_error("%s is not active, cannot reload.", strna(d
->name
));
788 else if (streq(d
->result
, "assert"))
789 log_error("Assertion failed on job for %s.", strna(d
->name
));
790 else if (streq(d
->result
, "unsupported"))
791 log_error("Operation on or unit type of %s not supported on this system.", strna(d
->name
));
792 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped")) {
795 _cleanup_free_
char *result
= NULL
;
797 q
= bus_job_get_service_result(d
, &result
);
799 log_debug_errno(q
, "Failed to get Result property of service %s: %m", d
->name
);
801 log_job_error_with_service_result(d
->name
, result
, extra_args
);
803 log_error("Job failed. See \"journalctl -xe\" for details.");
807 if (streq(d
->result
, "canceled"))
809 else if (streq(d
->result
, "timeout"))
811 else if (streq(d
->result
, "dependency"))
813 else if (streq(d
->result
, "invalid"))
815 else if (streq(d
->result
, "assert"))
817 else if (streq(d
->result
, "unsupported"))
819 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped"))
825 int bus_wait_for_jobs(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
830 while (!set_isempty(d
->jobs
)) {
833 q
= bus_process_wait(d
->bus
);
835 return log_error_errno(q
, "Failed to wait for response: %m");
838 q
= check_wait_response(d
, quiet
, extra_args
);
839 /* Return the first error as it is most likely to be
844 log_debug_errno(q
, "Got result %s/%m for job %s", strna(d
->result
), strna(d
->name
));
847 d
->name
= mfree(d
->name
);
848 d
->result
= mfree(d
->result
);
854 int bus_wait_for_jobs_add(BusWaitForJobs
*d
, const char *path
) {
859 r
= set_ensure_allocated(&d
->jobs
, &string_hash_ops
);
863 return set_put_strdup(d
->jobs
, path
);
866 int bus_wait_for_jobs_one(BusWaitForJobs
*d
, const char *path
, bool quiet
) {
869 r
= bus_wait_for_jobs_add(d
, path
);
873 return bus_wait_for_jobs(d
, quiet
, NULL
);
876 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message
*m
, bool quiet
, UnitFileChange
**changes
, unsigned *n_changes
) {
877 const char *type
, *path
, *source
;
880 /* changes is dereferenced when calling unit_file_dump_changes() later,
881 * so we have to make sure this is not NULL. */
885 r
= sd_bus_message_enter_container(m
, SD_BUS_TYPE_ARRAY
, "(sss)");
887 return bus_log_parse_error(r
);
889 while ((r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
)) > 0) {
890 /* We expect only "success" changes to be sent over the bus.
891 Hence, reject anything negative. */
892 UnitFileChangeType ch
= unit_file_change_type_from_string(type
);
895 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type
, path
);
899 r
= unit_file_changes_add(changes
, n_changes
, ch
, path
, source
);
904 return bus_log_parse_error(r
);
906 r
= sd_bus_message_exit_container(m
);
908 return bus_log_parse_error(r
);
910 unit_file_dump_changes(0, NULL
, *changes
, *n_changes
, false);
916 bool is_const
; /* If false, cgroup_path should be free()'d */
918 Hashmap
*pids
; /* PID → process name */
921 struct CGroupInfo
*parent
;
922 LIST_FIELDS(struct CGroupInfo
, siblings
);
923 LIST_HEAD(struct CGroupInfo
, children
);
927 static bool IS_ROOT(const char *p
) {
928 return isempty(p
) || streq(p
, "/");
931 static int add_cgroup(Hashmap
*cgroups
, const char *path
, bool is_const
, struct CGroupInfo
**ret
) {
932 struct CGroupInfo
*parent
= NULL
, *cg
;
941 cg
= hashmap_get(cgroups
, path
);
947 if (!IS_ROOT(path
)) {
950 e
= strrchr(path
, '/');
954 pp
= strndupa(path
, e
- path
);
958 r
= add_cgroup(cgroups
, pp
, false, &parent
);
963 cg
= new0(struct CGroupInfo
, 1);
968 cg
->cgroup_path
= (char*) path
;
970 cg
->cgroup_path
= strdup(path
);
971 if (!cg
->cgroup_path
) {
977 cg
->is_const
= is_const
;
980 r
= hashmap_put(cgroups
, cg
->cgroup_path
, cg
);
983 free(cg
->cgroup_path
);
989 LIST_PREPEND(siblings
, parent
->children
, cg
);
990 parent
->n_children
++;
997 static int add_process(
1003 struct CGroupInfo
*cg
;
1010 r
= add_cgroup(cgroups
, path
, true, &cg
);
1014 r
= hashmap_ensure_allocated(&cg
->pids
, &trivial_hash_ops
);
1018 return hashmap_put(cg
->pids
, PID_TO_PTR(pid
), (void*) name
);
1021 static void remove_cgroup(Hashmap
*cgroups
, struct CGroupInfo
*cg
) {
1025 while (cg
->children
)
1026 remove_cgroup(cgroups
, cg
->children
);
1028 hashmap_remove(cgroups
, cg
->cgroup_path
);
1031 free(cg
->cgroup_path
);
1033 hashmap_free(cg
->pids
);
1036 LIST_REMOVE(siblings
, cg
->parent
->children
, cg
);
1041 static int cgroup_info_compare_func(const void *a
, const void *b
) {
1042 const struct CGroupInfo
*x
= *(const struct CGroupInfo
* const*) a
, *y
= *(const struct CGroupInfo
* const*) b
;
1047 return strcmp(x
->cgroup_path
, y
->cgroup_path
);
1050 static int dump_processes(
1052 const char *cgroup_path
,
1055 OutputFlags flags
) {
1057 struct CGroupInfo
*cg
;
1062 if (IS_ROOT(cgroup_path
))
1065 cg
= hashmap_get(cgroups
, cgroup_path
);
1069 if (!hashmap_isempty(cg
->pids
)) {
1077 /* Order processes by their PID */
1078 pids
= newa(pid_t
, hashmap_size(cg
->pids
));
1080 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
)
1081 pids
[n
++] = PTR_TO_PID(pidp
);
1083 assert(n
== hashmap_size(cg
->pids
));
1084 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1086 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1088 for (i
= 0; i
< n
; i
++) {
1089 _cleanup_free_
char *e
= NULL
;
1090 const char *special
;
1093 name
= hashmap_get(cg
->pids
, PID_TO_PTR(pids
[i
]));
1096 if (n_columns
!= 0) {
1099 k
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1101 e
= ellipsize(name
, k
, 100);
1106 more
= i
+1 < n
|| cg
->children
;
1107 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1109 fprintf(stdout
, "%s%s%*"PID_PRI
" %s\n",
1118 struct CGroupInfo
**children
, *child
;
1121 /* Order subcgroups by their name */
1122 children
= newa(struct CGroupInfo
*, cg
->n_children
);
1123 LIST_FOREACH(siblings
, child
, cg
->children
)
1124 children
[n
++] = child
;
1125 assert(n
== cg
->n_children
);
1126 qsort_safe(children
, n
, sizeof(struct CGroupInfo
*), cgroup_info_compare_func
);
1129 n_columns
= MAX(LESS_BY(n_columns
, 2U), 20U);
1131 for (i
= 0; i
< n
; i
++) {
1132 _cleanup_free_
char *pp
= NULL
;
1133 const char *name
, *special
;
1136 child
= children
[i
];
1138 name
= strrchr(child
->cgroup_path
, '/');
1144 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1146 fputs(prefix
, stdout
);
1147 fputs(special
, stdout
);
1148 fputs(name
, stdout
);
1149 fputc('\n', stdout
);
1151 special
= special_glyph(more
? TREE_VERTICAL
: TREE_SPACE
);
1153 pp
= strappend(prefix
, special
);
1157 r
= dump_processes(cgroups
, child
->cgroup_path
, pp
, n_columns
, flags
);
1167 static int dump_extra_processes(
1171 OutputFlags flags
) {
1173 _cleanup_free_ pid_t
*pids
= NULL
;
1174 _cleanup_hashmap_free_ Hashmap
*names
= NULL
;
1175 struct CGroupInfo
*cg
;
1176 size_t n_allocated
= 0, n
= 0, k
;
1180 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1181 * combined, sorted, linear list. */
1183 HASHMAP_FOREACH(cg
, cgroups
, i
) {
1191 if (hashmap_isempty(cg
->pids
))
1194 r
= hashmap_ensure_allocated(&names
, &trivial_hash_ops
);
1198 if (!GREEDY_REALLOC(pids
, n_allocated
, n
+ hashmap_size(cg
->pids
)))
1201 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
) {
1202 pids
[n
++] = PTR_TO_PID(pidp
);
1204 r
= hashmap_put(names
, pidp
, (void*) name
);
1213 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1214 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1216 for (k
= 0; k
< n
; k
++) {
1217 _cleanup_free_
char *e
= NULL
;
1220 name
= hashmap_get(names
, PID_TO_PTR(pids
[k
]));
1223 if (n_columns
!= 0) {
1226 z
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1228 e
= ellipsize(name
, z
, 100);
1233 fprintf(stdout
, "%s%s %*" PID_PRI
" %s\n",
1235 special_glyph(TRIANGULAR_BULLET
),
1243 int unit_show_processes(
1246 const char *cgroup_path
,
1250 sd_bus_error
*error
) {
1252 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1253 Hashmap
*cgroups
= NULL
;
1254 struct CGroupInfo
*cg
;
1260 if (flags
& OUTPUT_FULL_WIDTH
)
1262 else if (n_columns
<= 0)
1263 n_columns
= columns();
1265 prefix
= strempty(prefix
);
1267 r
= sd_bus_call_method(
1269 "org.freedesktop.systemd1",
1270 "/org/freedesktop/systemd1",
1271 "org.freedesktop.systemd1.Manager",
1280 cgroups
= hashmap_new(&string_hash_ops
);
1284 r
= sd_bus_message_enter_container(reply
, 'a', "(sus)");
1289 const char *path
= NULL
, *name
= NULL
;
1292 r
= sd_bus_message_read(reply
, "(sus)", &path
, &pid
, &name
);
1298 r
= add_process(cgroups
, path
, pid
, name
);
1303 r
= sd_bus_message_exit_container(reply
);
1307 r
= dump_processes(cgroups
, cgroup_path
, prefix
, n_columns
, flags
);
1311 r
= dump_extra_processes(cgroups
, prefix
, n_columns
, flags
);
1314 while ((cg
= hashmap_first(cgroups
)))
1315 remove_cgroup(cgroups
, cg
);
1317 hashmap_free(cgroups
);