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 "mount-util.h"
32 #include "parse-util.h"
33 #include "path-util.h"
34 #include "process-util.h"
35 #include "rlimit-util.h"
36 #include "signal-util.h"
37 #include "string-util.h"
38 #include "syslog-util.h"
39 #include "terminal-util.h"
43 int bus_parse_unit_info(sd_bus_message
*message
, UnitInfo
*u
) {
49 return sd_bus_message_read(
64 int bus_append_unit_property_assignment(sd_bus_message
*m
, const char *assignment
) {
65 const char *eq
, *field
;
72 eq
= strchr(assignment
, '=');
74 log_error("Not an assignment: %s", assignment
);
78 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
80 return bus_log_create_error(r
);
82 field
= strndupa(assignment
, eq
- assignment
);
85 if (streq(field
, "CPUQuota")) {
88 r
= sd_bus_message_append(m
, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY
);
90 r
= parse_percent_unbounded(eq
);
92 log_error_errno(r
, "CPU quota '%s' invalid.", eq
);
96 r
= sd_bus_message_append(m
, "sv", "CPUQuotaPerSecUSec", "t", (usec_t
) r
* USEC_PER_SEC
/ 100U);
101 } else if (streq(field
, "EnvironmentFile")) {
103 r
= sd_bus_message_append(m
, "sv", "EnvironmentFiles", "a(sb)", 1,
104 eq
[0] == '-' ? eq
+ 1 : eq
,
108 } else if (STR_IN_SET(field
, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) {
113 r
= parse_sec(eq
, &t
);
115 return log_error_errno(r
, "Failed to parse %s= parameter: %s", field
, eq
);
118 n
= newa(char, l
+ 2);
122 /* Change suffix Sec → USec */
123 strcpy(mempcpy(n
, field
, l
- 3), "USec");
124 r
= sd_bus_message_append(m
, "sv", n
, "t", t
);
127 } else if (STR_IN_SET(field
, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
130 if (isempty(eq
) || streq(eq
, "infinity"))
131 bytes
= CGROUP_LIMIT_MAX
;
133 r
= parse_percent(eq
);
137 /* When this is a percentage we'll convert this into a relative value in the range
138 * 0…UINT32_MAX and pass it in the MemoryLowScale property (and related
139 * ones). This way the physical memory size can be determined server-side */
141 n
= strjoina(field
, "Scale");
142 r
= sd_bus_message_append(m
, "sv", n
, "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
146 r
= parse_size(eq
, 1024, &bytes
);
148 return log_error_errno(r
, "Failed to parse bytes specification %s", assignment
);
152 r
= sd_bus_message_append(m
, "sv", field
, "t", bytes
);
154 } else if (streq(field
, "TasksMax")) {
157 if (isempty(eq
) || streq(eq
, "infinity"))
160 r
= parse_percent(eq
);
162 r
= sd_bus_message_append(m
, "sv", "TasksMaxScale", "u", (uint32_t) (((uint64_t) UINT32_MAX
* r
) / 100U));
165 r
= safe_atou64(eq
, &t
);
167 return log_error_errno(r
, "Failed to parse maximum tasks specification %s", assignment
);
172 r
= sd_bus_message_append(m
, "sv", "TasksMax", "t", t
);
176 r
= sd_bus_message_append_basic(m
, SD_BUS_TYPE_STRING
, field
);
178 return bus_log_create_error(r
);
180 rl
= rlimit_from_string(field
);
185 r
= rlimit_parse(rl
, eq
, &l
);
187 return log_error_errno(r
, "Failed to parse resource limit: %s", eq
);
189 r
= sd_bus_message_append(m
, "v", "t", l
.rlim_max
);
191 return bus_log_create_error(r
);
193 r
= sd_bus_message_close_container(m
);
195 return bus_log_create_error(r
);
197 r
= sd_bus_message_open_container(m
, SD_BUS_TYPE_STRUCT
, "sv");
199 return bus_log_create_error(r
);
201 sn
= strjoina(field
, "Soft");
202 r
= sd_bus_message_append(m
, "sv", sn
, "t", l
.rlim_cur
);
204 } else if (STR_IN_SET(field
,
205 "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
206 "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
207 "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
208 "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "NoNewPrivileges",
209 "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute",
210 "RestrictRealtime", "DynamicUser", "RemoveIPC", "ProtectKernelTunables",
211 "ProtectKernelModules", "ProtectControlGroups")) {
213 r
= parse_boolean(eq
);
215 return log_error_errno(r
, "Failed to parse boolean assignment %s.", assignment
);
217 r
= sd_bus_message_append(m
, "v", "b", r
);
219 } else if (STR_IN_SET(field
, "CPUWeight", "StartupCPUWeight")) {
222 r
= cg_weight_parse(eq
, &u
);
224 log_error("Failed to parse %s value %s.", field
, eq
);
228 r
= sd_bus_message_append(m
, "v", "t", u
);
230 } else if (STR_IN_SET(field
, "CPUShares", "StartupCPUShares")) {
233 r
= cg_cpu_shares_parse(eq
, &u
);
235 log_error("Failed to parse %s value %s.", field
, eq
);
239 r
= sd_bus_message_append(m
, "v", "t", u
);
241 } else if (STR_IN_SET(field
, "IOWeight", "StartupIOWeight")) {
244 r
= cg_weight_parse(eq
, &u
);
246 log_error("Failed to parse %s value %s.", field
, eq
);
250 r
= sd_bus_message_append(m
, "v", "t", u
);
252 } else if (STR_IN_SET(field
, "BlockIOWeight", "StartupBlockIOWeight")) {
255 r
= cg_blkio_weight_parse(eq
, &u
);
257 log_error("Failed to parse %s value %s.", field
, eq
);
261 r
= sd_bus_message_append(m
, "v", "t", u
);
263 } else if (STR_IN_SET(field
,
264 "User", "Group", "DevicePolicy", "KillMode",
265 "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
266 "StandardInput", "StandardOutput", "StandardError",
267 "Description", "Slice", "Type", "WorkingDirectory",
268 "RootDirectory", "SyslogIdentifier", "ProtectSystem",
269 "ProtectHome", "SELinuxContext"))
270 r
= sd_bus_message_append(m
, "v", "s", eq
);
272 else if (streq(field
, "SyslogLevel")) {
275 level
= log_level_from_string(eq
);
277 log_error("Failed to parse %s value %s.", field
, eq
);
281 r
= sd_bus_message_append(m
, "v", "i", level
);
283 } else if (streq(field
, "SyslogFacility")) {
286 facility
= log_facility_unshifted_from_string(eq
);
288 log_error("Failed to parse %s value %s.", field
, eq
);
292 r
= sd_bus_message_append(m
, "v", "i", facility
);
294 } else if (streq(field
, "DeviceAllow")) {
297 r
= sd_bus_message_append(m
, "v", "a(ss)", 0);
299 const char *path
, *rwm
, *e
;
303 path
= strndupa(eq
, e
- eq
);
310 if (!is_deviceallow_pattern(path
)) {
311 log_error("%s is not a device file in /dev.", path
);
315 r
= sd_bus_message_append(m
, "v", "a(ss)", 1, path
, rwm
);
318 } else if (cgroup_io_limit_type_from_string(field
) >= 0 || STR_IN_SET(field
, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
321 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
323 const char *path
, *bandwidth
, *e
;
328 path
= strndupa(eq
, e
- eq
);
331 log_error("Failed to parse %s value %s.", field
, eq
);
335 if (!path_startswith(path
, "/dev")) {
336 log_error("%s is not a device file in /dev.", path
);
340 if (streq(bandwidth
, "infinity")) {
341 bytes
= CGROUP_LIMIT_MAX
;
343 r
= parse_size(bandwidth
, 1000, &bytes
);
345 log_error("Failed to parse byte value %s.", bandwidth
);
350 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, bytes
);
353 } else if (STR_IN_SET(field
, "IODeviceWeight", "BlockIODeviceWeight")) {
356 r
= sd_bus_message_append(m
, "v", "a(st)", 0);
358 const char *path
, *weight
, *e
;
363 path
= strndupa(eq
, e
- eq
);
366 log_error("Failed to parse %s value %s.", field
, eq
);
370 if (!path_startswith(path
, "/dev")) {
371 log_error("%s is not a device file in /dev.", path
);
375 r
= safe_atou64(weight
, &u
);
377 log_error("Failed to parse %s value %s.", field
, weight
);
380 r
= sd_bus_message_append(m
, "v", "a(st)", 1, path
, u
);
383 } else if (streq(field
, "Nice")) {
386 r
= parse_nice(eq
, &n
);
388 return log_error_errno(r
, "Failed to parse nice value: %s", eq
);
390 r
= sd_bus_message_append(m
, "v", "i", (int32_t) n
);
392 } else if (STR_IN_SET(field
, "Environment", "PassEnvironment")) {
395 r
= sd_bus_message_open_container(m
, 'v', "as");
397 return bus_log_create_error(r
);
399 r
= sd_bus_message_open_container(m
, 'a', "s");
401 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
);
486 _cleanup_free_
char *word
= NULL
;
489 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
491 log_error("Failed to parse %s value %s", field
, eq
);
497 if (!utf8_is_valid(word
)) {
498 log_error("Failed to parse %s value %s", field
, eq
);
502 offset
= word
[0] == '-';
503 if (!path_is_absolute(word
+ offset
)) {
504 log_error("Failed to parse %s value %s", field
, eq
);
508 path_kill_slashes(word
+ offset
);
510 r
= sd_bus_message_append_basic(m
, 's', word
);
512 return bus_log_create_error(r
);
515 r
= sd_bus_message_close_container(m
);
517 return bus_log_create_error(r
);
519 r
= sd_bus_message_close_container(m
);
521 } else if (streq(field
, "RuntimeDirectory")) {
524 r
= sd_bus_message_open_container(m
, 'v', "as");
526 return bus_log_create_error(r
);
528 r
= sd_bus_message_open_container(m
, 'a', "s");
530 return bus_log_create_error(r
);
533 _cleanup_free_
char *word
= NULL
;
535 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_QUOTES
);
537 return log_error_errno(r
, "Failed to parse %s value %s", field
, eq
);
542 r
= sd_bus_message_append_basic(m
, 's', word
);
544 return bus_log_create_error(r
);
547 r
= sd_bus_message_close_container(m
);
549 return bus_log_create_error(r
);
551 r
= sd_bus_message_close_container(m
);
553 } else if (streq(field
, "RestrictNamespaces")) {
562 r
= parse_boolean(eq
);
566 flags
= NAMESPACE_FLAGS_ALL
;
568 r
= namespace_flag_from_string_many(eq
, &flags
);
570 return log_error_errno(r
, "Failed to parse %s value %s.", field
, eq
);
574 flags
= (~flags
) & NAMESPACE_FLAGS_ALL
;
576 r
= sd_bus_message_append(m
, "v", "t", flags
);
577 } else if ((dep
= unit_dependency_from_string(field
)) >= 0)
578 r
= sd_bus_message_append(m
, "v", "as", 1, eq
);
579 else if (streq(field
, "MountFlags")) {
585 f
= mount_propagation_flags_from_string(eq
);
587 log_error("Failed to parse mount propagation type: %s", eq
);
592 r
= sd_bus_message_append(m
, "v", "t", f
);
594 log_error("Unknown assignment %s.", assignment
);
600 return bus_log_create_error(r
);
602 r
= sd_bus_message_close_container(m
);
604 return bus_log_create_error(r
);
609 int bus_append_unit_property_assignment_many(sd_bus_message
*m
, char **l
) {
616 r
= bus_append_unit_property_assignment(m
, *i
);
624 typedef struct BusWaitForJobs
{
631 sd_bus_slot
*slot_job_removed
;
632 sd_bus_slot
*slot_disconnected
;
635 static int match_disconnected(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
638 log_error("Warning! D-Bus connection terminated.");
639 sd_bus_close(sd_bus_message_get_bus(m
));
644 static int match_job_removed(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
645 const char *path
, *unit
, *result
;
646 BusWaitForJobs
*d
= userdata
;
654 r
= sd_bus_message_read(m
, "uoss", &id
, &path
, &unit
, &result
);
656 bus_log_parse_error(r
);
660 found
= set_remove(d
->jobs
, (char*) path
);
666 if (!isempty(result
))
667 d
->result
= strdup(result
);
670 d
->name
= strdup(unit
);
675 void bus_wait_for_jobs_free(BusWaitForJobs
*d
) {
679 set_free_free(d
->jobs
);
681 sd_bus_slot_unref(d
->slot_disconnected
);
682 sd_bus_slot_unref(d
->slot_job_removed
);
684 sd_bus_unref(d
->bus
);
692 int bus_wait_for_jobs_new(sd_bus
*bus
, BusWaitForJobs
**ret
) {
693 _cleanup_(bus_wait_for_jobs_freep
) BusWaitForJobs
*d
= NULL
;
699 d
= new0(BusWaitForJobs
, 1);
703 d
->bus
= sd_bus_ref(bus
);
705 /* When we are a bus client we match by sender. Direct
706 * connections OTOH have no initialized sender field, and
707 * hence we ignore the sender then */
708 r
= sd_bus_add_match(
710 &d
->slot_job_removed
,
713 "sender='org.freedesktop.systemd1',"
714 "interface='org.freedesktop.systemd1.Manager',"
715 "member='JobRemoved',"
716 "path='/org/freedesktop/systemd1'" :
718 "interface='org.freedesktop.systemd1.Manager',"
719 "member='JobRemoved',"
720 "path='/org/freedesktop/systemd1'",
721 match_job_removed
, d
);
725 r
= sd_bus_add_match(
727 &d
->slot_disconnected
,
729 "sender='org.freedesktop.DBus.Local',"
730 "interface='org.freedesktop.DBus.Local',"
731 "member='Disconnected'",
732 match_disconnected
, d
);
742 static int bus_process_wait(sd_bus
*bus
) {
746 r
= sd_bus_process(bus
, NULL
);
752 r
= sd_bus_wait(bus
, (uint64_t) -1);
758 static int bus_job_get_service_result(BusWaitForJobs
*d
, char **result
) {
759 _cleanup_free_
char *dbus_path
= NULL
;
765 dbus_path
= unit_dbus_path_from_name(d
->name
);
769 return sd_bus_get_property_string(d
->bus
,
770 "org.freedesktop.systemd1",
772 "org.freedesktop.systemd1.Service",
778 static const struct {
779 const char *result
, *explanation
;
780 } explanations
[] = {
781 { "resources", "of unavailable resources or another system error" },
782 { "protocol", "the service did not take the steps required by its unit configuration" },
783 { "timeout", "a timeout was exceeded" },
784 { "exit-code", "the control process exited with error code" },
785 { "signal", "a fatal signal was delivered to the control process" },
786 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
787 { "watchdog", "the service failed to send watchdog ping" },
788 { "start-limit", "start of the service was attempted too often" }
791 static void log_job_error_with_service_result(const char* service
, const char *result
, const char* const* extra_args
) {
792 _cleanup_free_
char *service_shell_quoted
= NULL
;
793 const char *systemctl
= "systemctl", *journalctl
= "journalctl";
797 service_shell_quoted
= shell_maybe_quote(service
);
799 if (extra_args
&& extra_args
[1]) {
800 _cleanup_free_
char *t
;
802 t
= strv_join((char**) extra_args
, " ");
803 systemctl
= strjoina("systemctl ", t
? : "<args>");
804 journalctl
= strjoina("journalctl ", t
? : "<args>");
807 if (!isempty(result
)) {
810 for (i
= 0; i
< ELEMENTSOF(explanations
); ++i
)
811 if (streq(result
, explanations
[i
].result
))
814 if (i
< ELEMENTSOF(explanations
)) {
815 log_error("Job for %s failed because %s.\n"
816 "See \"%s status %s\" and \"%s -xe\" for details.\n",
818 explanations
[i
].explanation
,
820 service_shell_quoted
?: "<service>",
826 log_error("Job for %s failed.\n"
827 "See \"%s status %s\" and \"%s -xe\" for details.\n",
830 service_shell_quoted
?: "<service>",
834 /* For some results maybe additional explanation is required */
835 if (streq_ptr(result
, "start-limit"))
836 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
837 "followed by \"%1$s start %2$s\" again.",
839 service_shell_quoted
?: "<service>");
842 static int check_wait_response(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
848 if (streq(d
->result
, "canceled"))
849 log_error("Job for %s canceled.", strna(d
->name
));
850 else if (streq(d
->result
, "timeout"))
851 log_error("Job for %s timed out.", strna(d
->name
));
852 else if (streq(d
->result
, "dependency"))
853 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d
->name
));
854 else if (streq(d
->result
, "invalid"))
855 log_error("%s is not active, cannot reload.", strna(d
->name
));
856 else if (streq(d
->result
, "assert"))
857 log_error("Assertion failed on job for %s.", strna(d
->name
));
858 else if (streq(d
->result
, "unsupported"))
859 log_error("Operation on or unit type of %s not supported on this system.", strna(d
->name
));
860 else if (streq(d
->result
, "collected"))
861 log_error("Queued job for %s was garbage collected.", strna(d
->name
));
862 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped")) {
865 _cleanup_free_
char *result
= NULL
;
867 q
= bus_job_get_service_result(d
, &result
);
869 log_debug_errno(q
, "Failed to get Result property of service %s: %m", d
->name
);
871 log_job_error_with_service_result(d
->name
, result
, extra_args
);
873 log_error("Job failed. See \"journalctl -xe\" for details.");
877 if (STR_IN_SET(d
->result
, "canceled", "collected"))
879 else if (streq(d
->result
, "timeout"))
881 else if (streq(d
->result
, "dependency"))
883 else if (streq(d
->result
, "invalid"))
885 else if (streq(d
->result
, "assert"))
887 else if (streq(d
->result
, "unsupported"))
889 else if (!streq(d
->result
, "done") && !streq(d
->result
, "skipped"))
895 int bus_wait_for_jobs(BusWaitForJobs
*d
, bool quiet
, const char* const* extra_args
) {
900 while (!set_isempty(d
->jobs
)) {
903 q
= bus_process_wait(d
->bus
);
905 return log_error_errno(q
, "Failed to wait for response: %m");
908 q
= check_wait_response(d
, quiet
, extra_args
);
909 /* Return the first error as it is most likely to be
914 log_debug_errno(q
, "Got result %s/%m for job %s", strna(d
->result
), strna(d
->name
));
917 d
->name
= mfree(d
->name
);
918 d
->result
= mfree(d
->result
);
924 int bus_wait_for_jobs_add(BusWaitForJobs
*d
, const char *path
) {
929 r
= set_ensure_allocated(&d
->jobs
, &string_hash_ops
);
933 return set_put_strdup(d
->jobs
, path
);
936 int bus_wait_for_jobs_one(BusWaitForJobs
*d
, const char *path
, bool quiet
) {
939 r
= bus_wait_for_jobs_add(d
, path
);
943 return bus_wait_for_jobs(d
, quiet
, NULL
);
946 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message
*m
, bool quiet
, UnitFileChange
**changes
, unsigned *n_changes
) {
947 const char *type
, *path
, *source
;
950 /* changes is dereferenced when calling unit_file_dump_changes() later,
951 * so we have to make sure this is not NULL. */
955 r
= sd_bus_message_enter_container(m
, SD_BUS_TYPE_ARRAY
, "(sss)");
957 return bus_log_parse_error(r
);
959 while ((r
= sd_bus_message_read(m
, "(sss)", &type
, &path
, &source
)) > 0) {
960 /* We expect only "success" changes to be sent over the bus.
961 Hence, reject anything negative. */
962 UnitFileChangeType ch
= unit_file_change_type_from_string(type
);
965 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type
, path
);
969 r
= unit_file_changes_add(changes
, n_changes
, ch
, path
, source
);
974 return bus_log_parse_error(r
);
976 r
= sd_bus_message_exit_container(m
);
978 return bus_log_parse_error(r
);
980 unit_file_dump_changes(0, NULL
, *changes
, *n_changes
, false);
986 bool is_const
; /* If false, cgroup_path should be free()'d */
988 Hashmap
*pids
; /* PID → process name */
991 struct CGroupInfo
*parent
;
992 LIST_FIELDS(struct CGroupInfo
, siblings
);
993 LIST_HEAD(struct CGroupInfo
, children
);
997 static bool IS_ROOT(const char *p
) {
998 return isempty(p
) || streq(p
, "/");
1001 static int add_cgroup(Hashmap
*cgroups
, const char *path
, bool is_const
, struct CGroupInfo
**ret
) {
1002 struct CGroupInfo
*parent
= NULL
, *cg
;
1011 cg
= hashmap_get(cgroups
, path
);
1017 if (!IS_ROOT(path
)) {
1020 e
= strrchr(path
, '/');
1024 pp
= strndupa(path
, e
- path
);
1028 r
= add_cgroup(cgroups
, pp
, false, &parent
);
1033 cg
= new0(struct CGroupInfo
, 1);
1038 cg
->cgroup_path
= (char*) path
;
1040 cg
->cgroup_path
= strdup(path
);
1041 if (!cg
->cgroup_path
) {
1047 cg
->is_const
= is_const
;
1048 cg
->parent
= parent
;
1050 r
= hashmap_put(cgroups
, cg
->cgroup_path
, cg
);
1053 free(cg
->cgroup_path
);
1059 LIST_PREPEND(siblings
, parent
->children
, cg
);
1060 parent
->n_children
++;
1067 static int add_process(
1073 struct CGroupInfo
*cg
;
1080 r
= add_cgroup(cgroups
, path
, true, &cg
);
1084 r
= hashmap_ensure_allocated(&cg
->pids
, &trivial_hash_ops
);
1088 return hashmap_put(cg
->pids
, PID_TO_PTR(pid
), (void*) name
);
1091 static void remove_cgroup(Hashmap
*cgroups
, struct CGroupInfo
*cg
) {
1095 while (cg
->children
)
1096 remove_cgroup(cgroups
, cg
->children
);
1098 hashmap_remove(cgroups
, cg
->cgroup_path
);
1101 free(cg
->cgroup_path
);
1103 hashmap_free(cg
->pids
);
1106 LIST_REMOVE(siblings
, cg
->parent
->children
, cg
);
1111 static int cgroup_info_compare_func(const void *a
, const void *b
) {
1112 const struct CGroupInfo
*x
= *(const struct CGroupInfo
* const*) a
, *y
= *(const struct CGroupInfo
* const*) b
;
1117 return strcmp(x
->cgroup_path
, y
->cgroup_path
);
1120 static int dump_processes(
1122 const char *cgroup_path
,
1125 OutputFlags flags
) {
1127 struct CGroupInfo
*cg
;
1132 if (IS_ROOT(cgroup_path
))
1135 cg
= hashmap_get(cgroups
, cgroup_path
);
1139 if (!hashmap_isempty(cg
->pids
)) {
1147 /* Order processes by their PID */
1148 pids
= newa(pid_t
, hashmap_size(cg
->pids
));
1150 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
)
1151 pids
[n
++] = PTR_TO_PID(pidp
);
1153 assert(n
== hashmap_size(cg
->pids
));
1154 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1156 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1158 for (i
= 0; i
< n
; i
++) {
1159 _cleanup_free_
char *e
= NULL
;
1160 const char *special
;
1163 name
= hashmap_get(cg
->pids
, PID_TO_PTR(pids
[i
]));
1166 if (n_columns
!= 0) {
1169 k
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1171 e
= ellipsize(name
, k
, 100);
1176 more
= i
+1 < n
|| cg
->children
;
1177 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1179 fprintf(stdout
, "%s%s%*"PID_PRI
" %s\n",
1188 struct CGroupInfo
**children
, *child
;
1191 /* Order subcgroups by their name */
1192 children
= newa(struct CGroupInfo
*, cg
->n_children
);
1193 LIST_FOREACH(siblings
, child
, cg
->children
)
1194 children
[n
++] = child
;
1195 assert(n
== cg
->n_children
);
1196 qsort_safe(children
, n
, sizeof(struct CGroupInfo
*), cgroup_info_compare_func
);
1199 n_columns
= MAX(LESS_BY(n_columns
, 2U), 20U);
1201 for (i
= 0; i
< n
; i
++) {
1202 _cleanup_free_
char *pp
= NULL
;
1203 const char *name
, *special
;
1206 child
= children
[i
];
1208 name
= strrchr(child
->cgroup_path
, '/');
1214 special
= special_glyph(more
? TREE_BRANCH
: TREE_RIGHT
);
1216 fputs(prefix
, stdout
);
1217 fputs(special
, stdout
);
1218 fputs(name
, stdout
);
1219 fputc('\n', stdout
);
1221 special
= special_glyph(more
? TREE_VERTICAL
: TREE_SPACE
);
1223 pp
= strappend(prefix
, special
);
1227 r
= dump_processes(cgroups
, child
->cgroup_path
, pp
, n_columns
, flags
);
1237 static int dump_extra_processes(
1241 OutputFlags flags
) {
1243 _cleanup_free_ pid_t
*pids
= NULL
;
1244 _cleanup_hashmap_free_ Hashmap
*names
= NULL
;
1245 struct CGroupInfo
*cg
;
1246 size_t n_allocated
= 0, n
= 0, k
;
1250 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1251 * combined, sorted, linear list. */
1253 HASHMAP_FOREACH(cg
, cgroups
, i
) {
1261 if (hashmap_isempty(cg
->pids
))
1264 r
= hashmap_ensure_allocated(&names
, &trivial_hash_ops
);
1268 if (!GREEDY_REALLOC(pids
, n_allocated
, n
+ hashmap_size(cg
->pids
)))
1271 HASHMAP_FOREACH_KEY(name
, pidp
, cg
->pids
, j
) {
1272 pids
[n
++] = PTR_TO_PID(pidp
);
1274 r
= hashmap_put(names
, pidp
, (void*) name
);
1283 qsort_safe(pids
, n
, sizeof(pid_t
), pid_compare_func
);
1284 width
= DECIMAL_STR_WIDTH(pids
[n
-1]);
1286 for (k
= 0; k
< n
; k
++) {
1287 _cleanup_free_
char *e
= NULL
;
1290 name
= hashmap_get(names
, PID_TO_PTR(pids
[k
]));
1293 if (n_columns
!= 0) {
1296 z
= MAX(LESS_BY(n_columns
, 2U + width
+ 1U), 20U);
1298 e
= ellipsize(name
, z
, 100);
1303 fprintf(stdout
, "%s%s %*" PID_PRI
" %s\n",
1305 special_glyph(TRIANGULAR_BULLET
),
1313 int unit_show_processes(
1316 const char *cgroup_path
,
1320 sd_bus_error
*error
) {
1322 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1323 Hashmap
*cgroups
= NULL
;
1324 struct CGroupInfo
*cg
;
1330 if (flags
& OUTPUT_FULL_WIDTH
)
1332 else if (n_columns
<= 0)
1333 n_columns
= columns();
1335 prefix
= strempty(prefix
);
1337 r
= sd_bus_call_method(
1339 "org.freedesktop.systemd1",
1340 "/org/freedesktop/systemd1",
1341 "org.freedesktop.systemd1.Manager",
1350 cgroups
= hashmap_new(&string_hash_ops
);
1354 r
= sd_bus_message_enter_container(reply
, 'a', "(sus)");
1359 const char *path
= NULL
, *name
= NULL
;
1362 r
= sd_bus_message_read(reply
, "(sus)", &path
, &pid
, &name
);
1368 r
= add_process(cgroups
, path
, pid
, name
);
1373 r
= sd_bus_message_exit_container(reply
);
1377 r
= dump_processes(cgroups
, cgroup_path
, prefix
, n_columns
, flags
);
1381 r
= dump_extra_processes(cgroups
, prefix
, n_columns
, flags
);
1384 while ((cg
= hashmap_first(cgroups
)))
1385 remove_cgroup(cgroups
, cg
);
1387 hashmap_free(cgroups
);