1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
9 #include "alloc-util.h"
10 #include "bus-error.h"
12 #include "cgroup-show.h"
13 #include "cgroup-util.h"
17 #include "format-util.h"
18 #include "hostname-util.h"
19 #include "locale-util.h"
21 #include "nulstr-util.h"
22 #include "output-mode.h"
23 #include "parse-util.h"
24 #include "path-util.h"
25 #include "process-util.h"
26 #include "sort-util.h"
27 #include "string-util.h"
28 #include "terminal-util.h"
29 #include "unit-name.h"
30 #include "xattr-util.h"
32 static void show_pid_array(
41 unsigned i
, j
, pid_width
;
46 typesafe_qsort(pids
, n_pids
, pid_compare_func
);
48 /* Filter duplicates */
49 for (j
= 0, i
= 1; i
< n_pids
; i
++) {
50 if (pids
[i
] == pids
[j
])
55 pid_width
= DECIMAL_STR_WIDTH(pids
[j
]);
57 if (flags
& OUTPUT_FULL_WIDTH
)
60 if (n_columns
> pid_width
+ 3) /* something like "├─1114784 " */
61 n_columns
-= pid_width
+ 3;
65 for (i
= 0; i
< n_pids
; i
++) {
66 _cleanup_free_
char *t
= NULL
;
68 (void) get_process_cmdline(pids
[i
], n_columns
,
69 PROCESS_CMDLINE_COMM_FALLBACK
| PROCESS_CMDLINE_USE_LOCALE
,
73 printf("%s%s ", prefix
, special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET
));
75 printf("%s%s", prefix
, special_glyph(((more
|| i
< n_pids
-1) ? SPECIAL_GLYPH_TREE_BRANCH
: SPECIAL_GLYPH_TREE_RIGHT
)));
77 printf("%s%*"PID_PRI
" %s%s\n", ansi_grey(), (int) pid_width
, pids
[i
], strna(t
), ansi_normal());
81 static int show_cgroup_one_by_path(
88 _cleanup_free_ pid_t
*pids
= NULL
;
89 _cleanup_fclose_
FILE *f
= NULL
;
90 _cleanup_free_
char *p
= NULL
;
95 r
= cg_mangle_path(path
, &p
);
99 fn
= strjoina(p
, "/cgroup.procs");
107 /* libvirt / qemu uses threaded mode and cgroup.procs cannot be read at the lower levels.
108 * From https://docs.kernel.org/admin-guide/cgroup-v2.html#threads,
109 * “cgroup.procs” in a threaded domain cgroup contains the PIDs of all processes in
110 * the subtree and is not readable in the subtree proper. */
111 r
= cg_read_pid(f
, &pid
);
112 if (IN_SET(r
, 0, -EOPNOTSUPP
))
117 if (!(flags
& OUTPUT_KERNEL_THREADS
) && is_kernel_thread(pid
) > 0)
120 if (!GREEDY_REALLOC(pids
, n
+ 1))
126 show_pid_array(pids
, n
, prefix
, n_columns
, false, more
, flags
);
131 static int is_delegated(int cgfd
, const char *path
) {
132 _cleanup_free_
char *b
= NULL
;
135 assert(cgfd
>= 0 || path
);
137 r
= getxattr_malloc(cgfd
< 0 ? path
: FORMAT_PROC_FD_PATH(cgfd
), "trusted.delegate", &b
);
138 if (r
< 0 && ERRNO_IS_XATTR_ABSENT(r
)) {
139 /* If the trusted xattr isn't set (preferred), then check the untrusted one. Under the
140 * assumption that whoever is trusted enough to own the cgroup, is also trusted enough to
141 * decide if it is delegated or not this should be safe. */
142 r
= getxattr_malloc(cgfd
< 0 ? path
: FORMAT_PROC_FD_PATH(cgfd
), "user.delegate", &b
);
143 if (r
< 0 && ERRNO_IS_XATTR_ABSENT(r
))
147 return log_debug_errno(r
, "Failed to read delegate xattr, ignoring: %m");
149 r
= parse_boolean(b
);
151 return log_debug_errno(r
, "Failed to parse delegate xattr boolean value, ignoring: %m");
156 static int show_cgroup_name(
162 uint64_t cgroupid
= UINT64_MAX
;
163 _cleanup_free_
char *b
= NULL
;
164 _cleanup_close_
int fd
= -EBADF
;
168 if (FLAGS_SET(flags
, OUTPUT_CGROUP_XATTRS
) || FLAGS_SET(flags
, OUTPUT_CGROUP_ID
)) {
169 fd
= open(path
, O_PATH
|O_CLOEXEC
|O_NOFOLLOW
|O_DIRECTORY
, 0);
171 log_debug_errno(errno
, "Failed to open cgroup '%s', ignoring: %m", path
);
174 delegate
= is_delegated(fd
, path
) > 0;
176 if (FLAGS_SET(flags
, OUTPUT_CGROUP_ID
)) {
177 cg_file_handle fh
= CG_FILE_HANDLE_INIT
;
180 if (name_to_handle_at(
181 fd
< 0 ? AT_FDCWD
: fd
,
185 fd
< 0 ? 0 : AT_EMPTY_PATH
) < 0)
186 log_debug_errno(errno
, "Failed to determine cgroup ID of %s, ignoring: %m", path
);
188 cgroupid
= CG_FILE_HANDLE_CGROUPID(fh
);
191 r
= path_extract_filename(path
, &b
);
193 return log_error_errno(r
, "Failed to extract filename from cgroup path: %m");
196 prefix
, special_glyph(glyph
),
197 delegate
? ansi_underline() : "",
199 delegate
? ansi_normal() : "");
204 special_glyph(SPECIAL_GLYPH_ELLIPSIS
),
207 if (cgroupid
!= UINT64_MAX
)
208 printf(" %s(#%" PRIu64
")%s", ansi_grey(), cgroupid
, ansi_normal());
212 if (FLAGS_SET(flags
, OUTPUT_CGROUP_XATTRS
) && fd
>= 0) {
213 _cleanup_free_
char *nl
= NULL
;
215 r
= flistxattr_malloc(fd
, &nl
);
217 log_debug_errno(r
, "Failed to enumerate xattrs on '%s', ignoring: %m", path
);
219 NULSTR_FOREACH(xa
, nl
) {
220 _cleanup_free_
char *x
= NULL
, *y
= NULL
, *buf
= NULL
;
223 if (!STARTSWITH_SET(xa
, "user.", "trusted."))
226 n
= fgetxattr_malloc(fd
, xa
, &buf
);
228 log_debug_errno(r
, "Failed to read xattr '%s' off '%s', ignoring: %m", xa
, path
);
236 y
= cescape_length(buf
, n
);
240 printf("%s%s%s %s%s%s: %s\n",
242 glyph
== SPECIAL_GLYPH_TREE_BRANCH
? special_glyph(SPECIAL_GLYPH_TREE_VERTICAL
) : " ",
243 special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
),
244 ansi_blue(), x
, ansi_normal(),
252 int show_cgroup_by_path(
258 _cleanup_free_
char *fn
= NULL
, *p1
= NULL
, *last
= NULL
, *p2
= NULL
;
259 _cleanup_closedir_
DIR *d
= NULL
;
260 bool shown_pids
= false;
267 n_columns
= columns();
269 prefix
= strempty(prefix
);
271 r
= cg_mangle_path(path
, &fn
);
279 while ((r
= cg_read_subgroup(d
, &gn
)) > 0) {
280 _cleanup_free_
char *k
= NULL
;
282 k
= path_join(fn
, gn
);
287 if (!(flags
& OUTPUT_SHOW_ALL
) && cg_is_empty_recursive(NULL
, k
) > 0)
291 show_cgroup_one_by_path(path
, prefix
, n_columns
, true, flags
);
296 r
= show_cgroup_name(last
, prefix
, SPECIAL_GLYPH_TREE_BRANCH
, flags
);
301 p1
= strjoin(prefix
, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL
));
306 show_cgroup_by_path(last
, p1
, n_columns
-2, flags
);
317 show_cgroup_one_by_path(path
, prefix
, n_columns
, !!last
, flags
);
320 r
= show_cgroup_name(last
, prefix
, SPECIAL_GLYPH_TREE_RIGHT
, flags
);
325 p2
= strjoin(prefix
, " ");
330 show_cgroup_by_path(last
, p2
, n_columns
-2, flags
);
336 int show_cgroup(const char *controller
,
341 _cleanup_free_
char *p
= NULL
;
346 r
= cg_get_path(controller
, path
, NULL
, &p
);
350 return show_cgroup_by_path(p
, prefix
, n_columns
, flags
);
353 static int show_extra_pids(
354 const char *controller
,
362 _cleanup_free_ pid_t
*copy
= NULL
;
372 n_columns
= columns();
374 prefix
= strempty(prefix
);
376 copy
= new(pid_t
, n_pids
);
380 for (i
= 0, j
= 0; i
< n_pids
; i
++) {
381 _cleanup_free_
char *k
= NULL
;
383 r
= cg_pid_get_path(controller
, pids
[i
], &k
);
387 if (path_startswith(k
, path
))
393 show_pid_array(copy
, j
, prefix
, n_columns
, true, false, flags
);
398 int show_cgroup_and_extra(
399 const char *controller
,
403 const pid_t extra_pids
[],
404 unsigned n_extra_pids
,
411 r
= show_cgroup(controller
, path
, prefix
, n_columns
, flags
);
415 return show_extra_pids(controller
, path
, prefix
, n_columns
, extra_pids
, n_extra_pids
, flags
);
418 int show_cgroup_get_unit_path_and_warn(
423 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
424 _cleanup_free_
char *path
= NULL
;
427 path
= unit_dbus_path_from_name(unit
);
431 r
= sd_bus_get_property_string(
433 "org.freedesktop.systemd1",
435 unit_dbus_interface_from_name(unit
),
440 return log_error_errno(r
, "Failed to query unit control group path: %s",
441 bus_error_message(&error
, r
));
446 int show_cgroup_get_path_and_warn(
451 _cleanup_free_
char *root
= NULL
;
455 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
456 _cleanup_free_
char *unit
= NULL
;
459 if (!hostname_is_valid(machine
, 0))
460 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Machine name is not valid: %s", machine
);
462 m
= strjoina("/run/systemd/machines/", machine
);
463 r
= parse_env_file(NULL
, m
, "SCOPE", &unit
);
465 return log_error_errno(r
, "Failed to load machine data: %m");
467 r
= bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL
, NULL
, false, &bus
);
469 return bus_log_connect_error(r
, BUS_TRANSPORT_LOCAL
);
471 r
= show_cgroup_get_unit_path_and_warn(bus
, unit
, &root
);
475 r
= cg_get_root_path(&root
);
477 return log_error_errno(r
, "Failed to get root control group path.\n"
478 "No cgroup filesystem mounted on /sys/fs/cgroup");
480 return log_error_errno(r
, "Failed to get root control group path: %m");
486 t
= path_join(root
, prefix
);
492 *ret
= TAKE_PTR(root
);