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
, "collected"))
842 log_error("Queued job for %s was garbage collected.", strna(d
->name
));
843 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped")) {
846 _cleanup_free_
char *result
= NULL
;
848 q
= bus_job_get_service_result(d
, &result
);
850 log_debug_errno(q
, "Failed to get Result property of service %s: %m", d
->name
);
852 log_job_error_with_service_result(d
->name
, result
, extra_args
);
854 log_error("Job failed. See \"journalctl -xe\" for details.");
858 if (STR_IN_SET(d
->result
, "canceled", "collected"))
860 else if (streq(d
->result
, "timeout"))
862 else if (streq(d
->result
, "dependency"))
864 else if (streq(d
->result
, "invalid"))
866 else if (streq(d
->result
, "assert"))
868 else if (streq(d
->result
, "unsupported"))
870 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped"))
876 int bus_wait_for_jobs(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
881 while (!set_isempty(d
->jobs
)) {
884 q
= bus_process_wait(d
->bus
);
886 return log_error_errno(q
, "Failed to wait for response: %m");
889 q
= check_wait_response(d
, quiet
, extra_args
);
890 /* Return the first error as it is most likely to be
895 log_debug_errno(q
, "Got result %s/%m for job %s", strna(d
->result
), strna(d
->name
));
898 d
->name
= mfree(d
->name
);
899 d
->result
= mfree(d
->result
);
905 int bus_wait_for_jobs_add(BusWaitForJobs
*d
, const char *path
) {
910 r
= set_ensure_allocated(&d
->jobs
, &string_hash_ops
);
914 return set_put_strdup(d
->jobs
, path
);
917 int bus_wait_for_jobs_one(BusWaitForJobs
*d
, const char *path
, bool quiet
) {
920 r
= bus_wait_for_jobs_add(d
, path
);
924 return bus_wait_for_jobs(d
, quiet
, NULL
);
927 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message
*m
, bool quiet
, UnitFileChange
**changes
, unsigned *n_changes
) {
928 const char *type
, *path
, *source
;
931 /* changes is dereferenced when calling unit_file_dump_changes() later,
932 * so we have to make sure this is not NULL. */
936 r
= sd_bus_message_enter_container(m
, SD_BUS_TYPE_ARRAY
, "(sss)");
938 return bus_log_parse_error(r
);
940 while ((r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
)) > 0) {
941 /* We expect only "success" changes to be sent over the bus.
942 Hence, reject anything negative. */
943 UnitFileChangeType ch
= unit_file_change_type_from_string(type
);
946 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type
, path
);
950 r
= unit_file_changes_add(changes
, n_changes
, ch
, path
, source
);
955 return bus_log_parse_error(r
);
957 r
= sd_bus_message_exit_container(m
);
959 return bus_log_parse_error(r
);
961 unit_file_dump_changes(0, NULL
, *changes
, *n_changes
, false);
967 bool is_const
; /* If false, cgroup_path should be free()'d */
969 Hashmap
*pids
; /* PID → process name */
972 struct CGroupInfo
*parent
;
973 LIST_FIELDS(struct CGroupInfo
, siblings
);
974 LIST_HEAD(struct CGroupInfo
, children
);
978 static bool IS_ROOT(const char *p
) {
979 return isempty(p
) || streq(p
, "/");
982 static int add_cgroup(Hashmap
*cgroups
, const char *path
, bool is_const
, struct CGroupInfo
**ret
) {
983 struct CGroupInfo
*parent
= NULL
, *cg
;
992 cg
= hashmap_get(cgroups
, path
);
998 if (!IS_ROOT(path
)) {
1001 e
= strrchr(path
, '/');
1005 pp
= strndupa(path
, e
- path
);
1009 r
= add_cgroup(cgroups
, pp
, false, &parent
);
1014 cg
= new0(struct CGroupInfo
, 1);
1019 cg
->cgroup_path
= (char*) path
;
1021 cg
->cgroup_path
= strdup(path
);
1022 if (!cg
->cgroup_path
) {
1028 cg
->is_const
= is_const
;
1029 cg
->parent
= parent
;
1031 r
= hashmap_put(cgroups
, cg
->cgroup_path
, cg
);
1034 free(cg
->cgroup_path
);
1040 LIST_PREPEND(siblings
, parent
->children
, cg
);
1041 parent
->n_children
++;
1048 static int add_process(
1054 struct CGroupInfo
*cg
;
1061 r
= add_cgroup(cgroups
, path
, true, &cg
);
1065 r
= hashmap_ensure_allocated(&cg
->pids
, &trivial_hash_ops
);
1069 return hashmap_put(cg
->pids
, PID_TO_PTR(pid
), (void*) name
);
1072 static void remove_cgroup(Hashmap
*cgroups
, struct CGroupInfo
*cg
) {
1076 while (cg
->children
)
1077 remove_cgroup(cgroups
, cg
->children
);
1079 hashmap_remove(cgroups
, cg
->cgroup_path
);
1082 free(cg
->cgroup_path
);
1084 hashmap_free(cg
->pids
);
1087 LIST_REMOVE(siblings
, cg
->parent
->children
, cg
);
1092 static int cgroup_info_compare_func(const void *a
, const void *b
) {
1093 const struct CGroupInfo
*x
= *(const struct CGroupInfo
* const*) a
, *y
= *(const struct CGroupInfo
* const*) b
;
1098 return strcmp(x
->cgroup_path
, y
->cgroup_path
);
1101 static int dump_processes(
1103 const char *cgroup_path
,
1106 OutputFlags flags
) {
1108 struct CGroupInfo
*cg
;
1113 if (IS_ROOT(cgroup_path
))
1116 cg
= hashmap_get(cgroups
, cgroup_path
);
1120 if (!hashmap_isempty(cg
->pids
)) {
1128 /* Order processes by their PID */
1129 pids
= newa(pid_t
, hashmap_size(cg
->pids
));
1131 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
)
1132 pids
[n
++] = PTR_TO_PID(pidp
);
1134 assert(n
== hashmap_size(cg
->pids
));
1135 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1137 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1139 for (i
= 0; i
< n
; i
++) {
1140 _cleanup_free_
char *e
= NULL
;
1141 const char *special
;
1144 name
= hashmap_get(cg
->pids
, PID_TO_PTR(pids
[i
]));
1147 if (n_columns
!= 0) {
1150 k
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1152 e
= ellipsize(name
, k
, 100);
1157 more
= i
+1 < n
|| cg
->children
;
1158 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1160 fprintf(stdout
, "%s%s%*"PID_PRI
" %s\n",
1169 struct CGroupInfo
**children
, *child
;
1172 /* Order subcgroups by their name */
1173 children
= newa(struct CGroupInfo
*, cg
->n_children
);
1174 LIST_FOREACH(siblings
, child
, cg
->children
)
1175 children
[n
++] = child
;
1176 assert(n
== cg
->n_children
);
1177 qsort_safe(children
, n
, sizeof(struct CGroupInfo
*), cgroup_info_compare_func
);
1180 n_columns
= MAX(LESS_BY(n_columns
, 2U), 20U);
1182 for (i
= 0; i
< n
; i
++) {
1183 _cleanup_free_
char *pp
= NULL
;
1184 const char *name
, *special
;
1187 child
= children
[i
];
1189 name
= strrchr(child
->cgroup_path
, '/');
1195 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1197 fputs(prefix
, stdout
);
1198 fputs(special
, stdout
);
1199 fputs(name
, stdout
);
1200 fputc('\n', stdout
);
1202 special
= special_glyph(more
? TREE_VERTICAL
: TREE_SPACE
);
1204 pp
= strappend(prefix
, special
);
1208 r
= dump_processes(cgroups
, child
->cgroup_path
, pp
, n_columns
, flags
);
1218 static int dump_extra_processes(
1222 OutputFlags flags
) {
1224 _cleanup_free_ pid_t
*pids
= NULL
;
1225 _cleanup_hashmap_free_ Hashmap
*names
= NULL
;
1226 struct CGroupInfo
*cg
;
1227 size_t n_allocated
= 0, n
= 0, k
;
1231 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1232 * combined, sorted, linear list. */
1234 HASHMAP_FOREACH(cg
, cgroups
, i
) {
1242 if (hashmap_isempty(cg
->pids
))
1245 r
= hashmap_ensure_allocated(&names
, &trivial_hash_ops
);
1249 if (!GREEDY_REALLOC(pids
, n_allocated
, n
+ hashmap_size(cg
->pids
)))
1252 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
) {
1253 pids
[n
++] = PTR_TO_PID(pidp
);
1255 r
= hashmap_put(names
, pidp
, (void*) name
);
1264 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1265 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1267 for (k
= 0; k
< n
; k
++) {
1268 _cleanup_free_
char *e
= NULL
;
1271 name
= hashmap_get(names
, PID_TO_PTR(pids
[k
]));
1274 if (n_columns
!= 0) {
1277 z
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1279 e
= ellipsize(name
, z
, 100);
1284 fprintf(stdout
, "%s%s %*" PID_PRI
" %s\n",
1286 special_glyph(TRIANGULAR_BULLET
),
1294 int unit_show_processes(
1297 const char *cgroup_path
,
1301 sd_bus_error
*error
) {
1303 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1304 Hashmap
*cgroups
= NULL
;
1305 struct CGroupInfo
*cg
;
1311 if (flags
& OUTPUT_FULL_WIDTH
)
1313 else if (n_columns
<= 0)
1314 n_columns
= columns();
1316 prefix
= strempty(prefix
);
1318 r
= sd_bus_call_method(
1320 "org.freedesktop.systemd1",
1321 "/org/freedesktop/systemd1",
1322 "org.freedesktop.systemd1.Manager",
1331 cgroups
= hashmap_new(&string_hash_ops
);
1335 r
= sd_bus_message_enter_container(reply
, 'a', "(sus)");
1340 const char *path
= NULL
, *name
= NULL
;
1343 r
= sd_bus_message_read(reply
, "(sus)", &path
, &pid
, &name
);
1349 r
= add_process(cgroups
, path
, pid
, name
);
1354 r
= sd_bus_message_exit_container(reply
);
1358 r
= dump_processes(cgroups
, cgroup_path
, prefix
, n_columns
, flags
);
1362 r
= dump_extra_processes(cgroups
, prefix
, n_columns
, flags
);
1365 while ((cg
= hashmap_first(cgroups
)))
1366 remove_cgroup(cgroups
, cg
);
1368 hashmap_free(cgroups
);