]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/cgroup-show.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / shared / cgroup-show.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <dirent.h>
4 #include <errno.h>
5 #include <stddef.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8
9 #include "alloc-util.h"
10 #include "bus-error.h"
11 #include "bus-util.h"
12 #include "cgroup-show.h"
13 #include "cgroup-util.h"
14 #include "env-file.h"
15 #include "escape.h"
16 #include "fd-util.h"
17 #include "format-util.h"
18 #include "hostname-util.h"
19 #include "locale-util.h"
20 #include "macro.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"
31
32 static void show_pid_array(
33 pid_t pids[],
34 unsigned n_pids,
35 const char *prefix,
36 size_t n_columns,
37 bool extra,
38 bool more,
39 OutputFlags flags) {
40
41 unsigned i, j, pid_width;
42
43 if (n_pids == 0)
44 return;
45
46 typesafe_qsort(pids, n_pids, pid_compare_func);
47
48 /* Filter duplicates */
49 for (j = 0, i = 1; i < n_pids; i++) {
50 if (pids[i] == pids[j])
51 continue;
52 pids[++j] = pids[i];
53 }
54 n_pids = j + 1;
55 pid_width = DECIMAL_STR_WIDTH(pids[j]);
56
57 if (flags & OUTPUT_FULL_WIDTH)
58 n_columns = SIZE_MAX;
59 else {
60 if (n_columns > pid_width + 3) /* something like "├─1114784 " */
61 n_columns -= pid_width + 3;
62 else
63 n_columns = 20;
64 }
65 for (i = 0; i < n_pids; i++) {
66 _cleanup_free_ char *t = NULL;
67
68 (void) get_process_cmdline(pids[i], n_columns,
69 PROCESS_CMDLINE_COMM_FALLBACK | PROCESS_CMDLINE_USE_LOCALE,
70 &t);
71
72 if (extra)
73 printf("%s%s ", prefix, special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET));
74 else
75 printf("%s%s", prefix, special_glyph(((more || i < n_pids-1) ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT)));
76
77 printf("%s%*"PID_PRI" %s%s\n", ansi_grey(), (int) pid_width, pids[i], strna(t), ansi_normal());
78 }
79 }
80
81 static int show_cgroup_one_by_path(
82 const char *path,
83 const char *prefix,
84 size_t n_columns,
85 bool more,
86 OutputFlags flags) {
87
88 _cleanup_free_ pid_t *pids = NULL;
89 _cleanup_fclose_ FILE *f = NULL;
90 _cleanup_free_ char *p = NULL;
91 size_t n = 0;
92 char *fn;
93 int r;
94
95 r = cg_mangle_path(path, &p);
96 if (r < 0)
97 return r;
98
99 fn = strjoina(p, "/cgroup.procs");
100 f = fopen(fn, "re");
101 if (!f)
102 return -errno;
103
104 for (;;) {
105 pid_t pid;
106
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))
113 break;
114 if (r < 0)
115 return r;
116
117 if (!(flags & OUTPUT_KERNEL_THREADS) && is_kernel_thread(pid) > 0)
118 continue;
119
120 if (!GREEDY_REALLOC(pids, n + 1))
121 return -ENOMEM;
122
123 pids[n++] = pid;
124 }
125
126 show_pid_array(pids, n, prefix, n_columns, false, more, flags);
127
128 return 0;
129 }
130
131 static int is_delegated(int cgfd, const char *path) {
132 _cleanup_free_ char *b = NULL;
133 int r;
134
135 assert(cgfd >= 0 || path);
136
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))
144 return false;
145 }
146 if (r < 0)
147 return log_debug_errno(r, "Failed to read delegate xattr, ignoring: %m");
148
149 r = parse_boolean(b);
150 if (r < 0)
151 return log_debug_errno(r, "Failed to parse delegate xattr boolean value, ignoring: %m");
152
153 return r;
154 }
155
156 static int show_cgroup_name(
157 const char *path,
158 const char *prefix,
159 SpecialGlyph glyph,
160 OutputFlags flags) {
161
162 uint64_t cgroupid = UINT64_MAX;
163 _cleanup_free_ char *b = NULL;
164 _cleanup_close_ int fd = -EBADF;
165 bool delegate;
166 int r;
167
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);
170 if (fd < 0)
171 log_debug_errno(errno, "Failed to open cgroup '%s', ignoring: %m", path);
172 }
173
174 delegate = is_delegated(fd, path) > 0;
175
176 if (FLAGS_SET(flags, OUTPUT_CGROUP_ID)) {
177 cg_file_handle fh = CG_FILE_HANDLE_INIT;
178 int mnt_id = -1;
179
180 if (name_to_handle_at(
181 fd < 0 ? AT_FDCWD : fd,
182 fd < 0 ? path : "",
183 &fh.file_handle,
184 &mnt_id,
185 fd < 0 ? 0 : AT_EMPTY_PATH) < 0)
186 log_debug_errno(errno, "Failed to determine cgroup ID of %s, ignoring: %m", path);
187 else
188 cgroupid = CG_FILE_HANDLE_CGROUPID(fh);
189 }
190
191 r = path_extract_filename(path, &b);
192 if (r < 0)
193 return log_error_errno(r, "Failed to extract filename from cgroup path: %m");
194
195 printf("%s%s%s%s%s",
196 prefix, special_glyph(glyph),
197 delegate ? ansi_underline() : "",
198 cg_unescape(b),
199 delegate ? ansi_normal() : "");
200
201 if (delegate)
202 printf(" %s%s%s",
203 ansi_highlight(),
204 special_glyph(SPECIAL_GLYPH_ELLIPSIS),
205 ansi_normal());
206
207 if (cgroupid != UINT64_MAX)
208 printf(" %s(#%" PRIu64 ")%s", ansi_grey(), cgroupid, ansi_normal());
209
210 printf("\n");
211
212 if (FLAGS_SET(flags, OUTPUT_CGROUP_XATTRS) && fd >= 0) {
213 _cleanup_free_ char *nl = NULL;
214
215 r = flistxattr_malloc(fd, &nl);
216 if (r < 0)
217 log_debug_errno(r, "Failed to enumerate xattrs on '%s', ignoring: %m", path);
218
219 NULSTR_FOREACH(xa, nl) {
220 _cleanup_free_ char *x = NULL, *y = NULL, *buf = NULL;
221 int n;
222
223 if (!STARTSWITH_SET(xa, "user.", "trusted."))
224 continue;
225
226 n = fgetxattr_malloc(fd, xa, &buf);
227 if (n < 0) {
228 log_debug_errno(r, "Failed to read xattr '%s' off '%s', ignoring: %m", xa, path);
229 continue;
230 }
231
232 x = cescape(xa);
233 if (!x)
234 return -ENOMEM;
235
236 y = cescape_length(buf, n);
237 if (!y)
238 return -ENOMEM;
239
240 printf("%s%s%s %s%s%s: %s\n",
241 prefix,
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(),
245 y);
246 }
247 }
248
249 return 0;
250 }
251
252 int show_cgroup_by_path(
253 const char *path,
254 const char *prefix,
255 size_t n_columns,
256 OutputFlags flags) {
257
258 _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL;
259 _cleanup_closedir_ DIR *d = NULL;
260 bool shown_pids = false;
261 char *gn = NULL;
262 int r;
263
264 assert(path);
265
266 if (n_columns <= 0)
267 n_columns = columns();
268
269 prefix = strempty(prefix);
270
271 r = cg_mangle_path(path, &fn);
272 if (r < 0)
273 return r;
274
275 d = opendir(fn);
276 if (!d)
277 return -errno;
278
279 while ((r = cg_read_subgroup(d, &gn)) > 0) {
280 _cleanup_free_ char *k = NULL;
281
282 k = path_join(fn, gn);
283 free(gn);
284 if (!k)
285 return -ENOMEM;
286
287 if (!(flags & OUTPUT_SHOW_ALL) && cg_is_empty_recursive(NULL, k) > 0)
288 continue;
289
290 if (!shown_pids) {
291 show_cgroup_one_by_path(path, prefix, n_columns, true, flags);
292 shown_pids = true;
293 }
294
295 if (last) {
296 r = show_cgroup_name(last, prefix, SPECIAL_GLYPH_TREE_BRANCH, flags);
297 if (r < 0)
298 return r;
299
300 if (!p1) {
301 p1 = strjoin(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL));
302 if (!p1)
303 return -ENOMEM;
304 }
305
306 show_cgroup_by_path(last, p1, n_columns-2, flags);
307 free(last);
308 }
309
310 last = TAKE_PTR(k);
311 }
312
313 if (r < 0)
314 return r;
315
316 if (!shown_pids)
317 show_cgroup_one_by_path(path, prefix, n_columns, !!last, flags);
318
319 if (last) {
320 r = show_cgroup_name(last, prefix, SPECIAL_GLYPH_TREE_RIGHT, flags);
321 if (r < 0)
322 return r;
323
324 if (!p2) {
325 p2 = strjoin(prefix, " ");
326 if (!p2)
327 return -ENOMEM;
328 }
329
330 show_cgroup_by_path(last, p2, n_columns-2, flags);
331 }
332
333 return 0;
334 }
335
336 int show_cgroup(const char *controller,
337 const char *path,
338 const char *prefix,
339 size_t n_columns,
340 OutputFlags flags) {
341 _cleanup_free_ char *p = NULL;
342 int r;
343
344 assert(path);
345
346 r = cg_get_path(controller, path, NULL, &p);
347 if (r < 0)
348 return r;
349
350 return show_cgroup_by_path(p, prefix, n_columns, flags);
351 }
352
353 static int show_extra_pids(
354 const char *controller,
355 const char *path,
356 const char *prefix,
357 size_t n_columns,
358 const pid_t pids[],
359 unsigned n_pids,
360 OutputFlags flags) {
361
362 _cleanup_free_ pid_t *copy = NULL;
363 unsigned i, j;
364 int r;
365
366 assert(path);
367
368 if (n_pids <= 0)
369 return 0;
370
371 if (n_columns <= 0)
372 n_columns = columns();
373
374 prefix = strempty(prefix);
375
376 copy = new(pid_t, n_pids);
377 if (!copy)
378 return -ENOMEM;
379
380 for (i = 0, j = 0; i < n_pids; i++) {
381 _cleanup_free_ char *k = NULL;
382
383 r = cg_pid_get_path(controller, pids[i], &k);
384 if (r < 0)
385 return r;
386
387 if (path_startswith(k, path))
388 continue;
389
390 copy[j++] = pids[i];
391 }
392
393 show_pid_array(copy, j, prefix, n_columns, true, false, flags);
394
395 return 0;
396 }
397
398 int show_cgroup_and_extra(
399 const char *controller,
400 const char *path,
401 const char *prefix,
402 size_t n_columns,
403 const pid_t extra_pids[],
404 unsigned n_extra_pids,
405 OutputFlags flags) {
406
407 int r;
408
409 assert(path);
410
411 r = show_cgroup(controller, path, prefix, n_columns, flags);
412 if (r < 0)
413 return r;
414
415 return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags);
416 }
417
418 int show_cgroup_get_unit_path_and_warn(
419 sd_bus *bus,
420 const char *unit,
421 char **ret) {
422
423 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
424 _cleanup_free_ char *path = NULL;
425 int r;
426
427 path = unit_dbus_path_from_name(unit);
428 if (!path)
429 return log_oom();
430
431 r = sd_bus_get_property_string(
432 bus,
433 "org.freedesktop.systemd1",
434 path,
435 unit_dbus_interface_from_name(unit),
436 "ControlGroup",
437 &error,
438 ret);
439 if (r < 0)
440 return log_error_errno(r, "Failed to query unit control group path: %s",
441 bus_error_message(&error, r));
442
443 return 0;
444 }
445
446 int show_cgroup_get_path_and_warn(
447 const char *machine,
448 const char *prefix,
449 char **ret) {
450
451 _cleanup_free_ char *root = NULL;
452 int r;
453
454 if (machine) {
455 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
456 _cleanup_free_ char *unit = NULL;
457 const char *m;
458
459 if (!hostname_is_valid(machine, 0))
460 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine name is not valid: %s", machine);
461
462 m = strjoina("/run/systemd/machines/", machine);
463 r = parse_env_file(NULL, m, "SCOPE", &unit);
464 if (r < 0)
465 return log_error_errno(r, "Failed to load machine data: %m");
466
467 r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus);
468 if (r < 0)
469 return bus_log_connect_error(r, BUS_TRANSPORT_LOCAL);
470
471 r = show_cgroup_get_unit_path_and_warn(bus, unit, &root);
472 if (r < 0)
473 return r;
474 } else {
475 r = cg_get_root_path(&root);
476 if (r == -ENOMEDIUM)
477 return log_error_errno(r, "Failed to get root control group path.\n"
478 "No cgroup filesystem mounted on /sys/fs/cgroup");
479 if (r < 0)
480 return log_error_errno(r, "Failed to get root control group path: %m");
481 }
482
483 if (prefix) {
484 char *t;
485
486 t = path_join(root, prefix);
487 if (!t)
488 return log_oom();
489
490 *ret = t;
491 } else
492 *ret = TAKE_PTR(root);
493
494 return 0;
495 }