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
show_pid_array(pid_t pids[],unsigned n_pids,const char * prefix,size_t n_columns,bool extra,bool more,OutputFlags flags)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(), pid_width, pids[i], strna(t), ansi_normal());
78 }
79 }
80
show_cgroup_one_by_path(const char * path,const char * prefix,size_t n_columns,bool more,OutputFlags flags)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://www.kernel.org/doc/html/latest/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
is_delegated(int cgfd,const char * path)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 == -ENODATA) {
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 == -ENODATA)
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
show_cgroup_name(const char * path,const char * prefix,SpecialGlyph glyph,OutputFlags flags)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 = -1;
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 char *xa;
215
216 r = flistxattr_malloc(fd, &nl);
217 if (r < 0)
218 log_debug_errno(r, "Failed to enumerate xattrs on '%s', ignoring: %m", path);
219
220 NULSTR_FOREACH(xa, nl) {
221 _cleanup_free_ char *x = NULL, *y = NULL, *buf = NULL;
222 int n;
223
224 if (!STARTSWITH_SET(xa, "user.", "trusted."))
225 continue;
226
227 n = fgetxattr_malloc(fd, xa, &buf);
228 if (n < 0) {
229 log_debug_errno(r, "Failed to read xattr '%s' off '%s', ignoring: %m", xa, path);
230 continue;
231 }
232
233 x = cescape(xa);
234 if (!x)
235 return -ENOMEM;
236
237 y = cescape_length(buf, n);
238 if (!y)
239 return -ENOMEM;
240
241 printf("%s%s%s %s%s%s: %s\n",
242 prefix,
243 glyph == SPECIAL_GLYPH_TREE_BRANCH ? special_glyph(SPECIAL_GLYPH_TREE_VERTICAL) : " ",
244 special_glyph(SPECIAL_GLYPH_ARROW_RIGHT),
245 ansi_blue(), x, ansi_normal(),
246 y);
247 }
248 }
249
250 return 0;
251 }
252
show_cgroup_by_path(const char * path,const char * prefix,size_t n_columns,OutputFlags flags)253 int show_cgroup_by_path(
254 const char *path,
255 const char *prefix,
256 size_t n_columns,
257 OutputFlags flags) {
258
259 _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL;
260 _cleanup_closedir_ DIR *d = NULL;
261 bool shown_pids = false;
262 char *gn = NULL;
263 int r;
264
265 assert(path);
266
267 if (n_columns <= 0)
268 n_columns = columns();
269
270 prefix = strempty(prefix);
271
272 r = cg_mangle_path(path, &fn);
273 if (r < 0)
274 return r;
275
276 d = opendir(fn);
277 if (!d)
278 return -errno;
279
280 while ((r = cg_read_subgroup(d, &gn)) > 0) {
281 _cleanup_free_ char *k = NULL;
282
283 k = path_join(fn, gn);
284 free(gn);
285 if (!k)
286 return -ENOMEM;
287
288 if (!(flags & OUTPUT_SHOW_ALL) && cg_is_empty_recursive(NULL, k) > 0)
289 continue;
290
291 if (!shown_pids) {
292 show_cgroup_one_by_path(path, prefix, n_columns, true, flags);
293 shown_pids = true;
294 }
295
296 if (last) {
297 r = show_cgroup_name(last, prefix, SPECIAL_GLYPH_TREE_BRANCH, flags);
298 if (r < 0)
299 return r;
300
301 if (!p1) {
302 p1 = strjoin(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL));
303 if (!p1)
304 return -ENOMEM;
305 }
306
307 show_cgroup_by_path(last, p1, n_columns-2, flags);
308 free(last);
309 }
310
311 last = TAKE_PTR(k);
312 }
313
314 if (r < 0)
315 return r;
316
317 if (!shown_pids)
318 show_cgroup_one_by_path(path, prefix, n_columns, !!last, flags);
319
320 if (last) {
321 r = show_cgroup_name(last, prefix, SPECIAL_GLYPH_TREE_RIGHT, flags);
322 if (r < 0)
323 return r;
324
325 if (!p2) {
326 p2 = strjoin(prefix, " ");
327 if (!p2)
328 return -ENOMEM;
329 }
330
331 show_cgroup_by_path(last, p2, n_columns-2, flags);
332 }
333
334 return 0;
335 }
336
show_cgroup(const char * controller,const char * path,const char * prefix,size_t n_columns,OutputFlags flags)337 int show_cgroup(const char *controller,
338 const char *path,
339 const char *prefix,
340 size_t n_columns,
341 OutputFlags flags) {
342 _cleanup_free_ char *p = NULL;
343 int r;
344
345 assert(path);
346
347 r = cg_get_path(controller, path, NULL, &p);
348 if (r < 0)
349 return r;
350
351 return show_cgroup_by_path(p, prefix, n_columns, flags);
352 }
353
show_extra_pids(const char * controller,const char * path,const char * prefix,size_t n_columns,const pid_t pids[],unsigned n_pids,OutputFlags flags)354 static int show_extra_pids(
355 const char *controller,
356 const char *path,
357 const char *prefix,
358 size_t n_columns,
359 const pid_t pids[],
360 unsigned n_pids,
361 OutputFlags flags) {
362
363 _cleanup_free_ pid_t *copy = NULL;
364 unsigned i, j;
365 int r;
366
367 assert(path);
368
369 if (n_pids <= 0)
370 return 0;
371
372 if (n_columns <= 0)
373 n_columns = columns();
374
375 prefix = strempty(prefix);
376
377 copy = new(pid_t, n_pids);
378 if (!copy)
379 return -ENOMEM;
380
381 for (i = 0, j = 0; i < n_pids; i++) {
382 _cleanup_free_ char *k = NULL;
383
384 r = cg_pid_get_path(controller, pids[i], &k);
385 if (r < 0)
386 return r;
387
388 if (path_startswith(k, path))
389 continue;
390
391 copy[j++] = pids[i];
392 }
393
394 show_pid_array(copy, j, prefix, n_columns, true, false, flags);
395
396 return 0;
397 }
398
show_cgroup_and_extra(const char * controller,const char * path,const char * prefix,size_t n_columns,const pid_t extra_pids[],unsigned n_extra_pids,OutputFlags flags)399 int show_cgroup_and_extra(
400 const char *controller,
401 const char *path,
402 const char *prefix,
403 size_t n_columns,
404 const pid_t extra_pids[],
405 unsigned n_extra_pids,
406 OutputFlags flags) {
407
408 int r;
409
410 assert(path);
411
412 r = show_cgroup(controller, path, prefix, n_columns, flags);
413 if (r < 0)
414 return r;
415
416 return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags);
417 }
418
show_cgroup_get_unit_path_and_warn(sd_bus * bus,const char * unit,char ** ret)419 int show_cgroup_get_unit_path_and_warn(
420 sd_bus *bus,
421 const char *unit,
422 char **ret) {
423
424 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
425 _cleanup_free_ char *path = NULL;
426 int r;
427
428 path = unit_dbus_path_from_name(unit);
429 if (!path)
430 return log_oom();
431
432 r = sd_bus_get_property_string(
433 bus,
434 "org.freedesktop.systemd1",
435 path,
436 unit_dbus_interface_from_name(unit),
437 "ControlGroup",
438 &error,
439 ret);
440 if (r < 0)
441 return log_error_errno(r, "Failed to query unit control group path: %s",
442 bus_error_message(&error, r));
443
444 return 0;
445 }
446
show_cgroup_get_path_and_warn(const char * machine,const char * prefix,char ** ret)447 int show_cgroup_get_path_and_warn(
448 const char *machine,
449 const char *prefix,
450 char **ret) {
451
452 _cleanup_free_ char *root = NULL;
453 int r;
454
455 if (machine) {
456 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
457 _cleanup_free_ char *unit = NULL;
458 const char *m;
459
460 if (!hostname_is_valid(machine, 0))
461 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine name is not valid: %s", machine);
462
463 m = strjoina("/run/systemd/machines/", machine);
464 r = parse_env_file(NULL, m, "SCOPE", &unit);
465 if (r < 0)
466 return log_error_errno(r, "Failed to load machine data: %m");
467
468 r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus);
469 if (r < 0)
470 return bus_log_connect_error(r, BUS_TRANSPORT_LOCAL);
471
472 r = show_cgroup_get_unit_path_and_warn(bus, unit, &root);
473 if (r < 0)
474 return r;
475 } else {
476 r = cg_get_root_path(&root);
477 if (r == -ENOMEDIUM)
478 return log_error_errno(r, "Failed to get root control group path.\n"
479 "No cgroup filesystem mounted on /sys/fs/cgroup");
480 if (r < 0)
481 return log_error_errno(r, "Failed to get root control group path: %m");
482 }
483
484 if (prefix) {
485 char *t;
486
487 t = path_join(root, prefix);
488 if (!t)
489 return log_oom();
490
491 *ret = t;
492 } else
493 *ret = TAKE_PTR(root);
494
495 return 0;
496 }
497