Coverage Report

Created: 2025-11-16 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/shared/cgroup-show.c
Line
Count
Source
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include <dirent.h>
4
#include <stdio.h>
5
6
#include "sd-bus.h"
7
8
#include "alloc-util.h"
9
#include "ansi-color.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 "glyph-util.h"
19
#include "hostname-util.h"
20
#include "log.h"
21
#include "nulstr-util.h"
22
#include "output-mode.h"
23
#include "path-util.h"
24
#include "process-util.h"
25
#include "runtime-scope.h"
26
#include "sort-util.h"
27
#include "string-util.h"
28
#include "terminal-util.h"
29
#include "unit-def.h"
30
#include "xattr-util.h"
31
32
static void show_pid_array(
33
                pid_t pids[],
34
                size_t n_pids,
35
                const char *prefix,
36
                size_t n_columns,
37
                bool extra,
38
                bool more,
39
0
                OutputFlags flags) {
40
41
0
        size_t i, j, pid_width;
42
43
0
        if (n_pids == 0)
44
0
                return;
45
46
0
        typesafe_qsort(pids, n_pids, pid_compare_func);
47
48
        /* Filter duplicates */
49
0
        for (j = 0, i = 1; i < n_pids; i++) {
50
0
                if (pids[i] == pids[j])
51
0
                        continue;
52
0
                pids[++j] = pids[i];
53
0
        }
54
0
        n_pids = j + 1;
55
0
        pid_width = DECIMAL_STR_WIDTH(pids[j]);
56
57
0
        if (flags & OUTPUT_FULL_WIDTH)
58
0
                n_columns = SIZE_MAX;
59
0
        else {
60
0
                if (n_columns > pid_width + 3) /* something like "├─1114784 " */
61
0
                        n_columns -= pid_width + 3;
62
0
                else
63
0
                        n_columns = 20;
64
0
        }
65
0
        for (i = 0; i < n_pids; i++) {
66
0
                _cleanup_free_ char *t = NULL;
67
68
0
                (void) pid_get_cmdline(pids[i], n_columns,
69
0
                                       PROCESS_CMDLINE_COMM_FALLBACK | PROCESS_CMDLINE_USE_LOCALE,
70
0
                                       &t);
71
72
0
                if (extra)
73
0
                        printf("%s%s ", prefix, glyph(GLYPH_TRIANGULAR_BULLET));
74
0
                else
75
0
                        printf("%s%s", prefix, glyph(((more || i < n_pids-1) ? GLYPH_TREE_BRANCH : GLYPH_TREE_RIGHT)));
76
77
0
                printf("%s%*"PID_PRI" %s%s\n", ansi_grey(), (int) pid_width, pids[i], strna(t), ansi_normal());
78
0
        }
79
0
}
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
0
                OutputFlags flags) {
87
88
0
        _cleanup_free_ pid_t *pids = NULL;
89
0
        _cleanup_fclose_ FILE *f = NULL;
90
0
        _cleanup_free_ char *p = NULL;
91
0
        size_t n = 0;
92
0
        char *fn;
93
0
        int r;
94
95
0
        r = cg_mangle_path(path, &p);
96
0
        if (r < 0)
97
0
                return r;
98
99
0
        fn = strjoina(p, "/cgroup.procs");
100
0
        f = fopen(fn, "re");
101
0
        if (!f)
102
0
                return -errno;
103
104
0
        for (;;) {
105
0
                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
                 *
112
                 * ENODEV is generated when we enumerate processes from a cgroup and the cgroup is removed
113
                 * concurrently. */
114
0
                r = cg_read_pid(f, &pid, /* flags = */ 0);
115
0
                if (IN_SET(r, 0, -EOPNOTSUPP, -ENODEV))
116
0
                        break;
117
0
                if (r < 0)
118
0
                        return r;
119
120
0
                if (!(flags & OUTPUT_KERNEL_THREADS) && pid_is_kernel_thread(pid) > 0)
121
0
                        continue;
122
123
0
                if (!GREEDY_REALLOC(pids, n + 1))
124
0
                        return -ENOMEM;
125
126
0
                pids[n++] = pid;
127
0
        }
128
129
0
        show_pid_array(pids, n, prefix, n_columns, false, more, flags);
130
131
0
        return 0;
132
0
}
133
134
static int show_cgroup_name(
135
                const char *path,
136
                const char *prefix,
137
                Glyph tree,
138
0
                OutputFlags flags) {
139
140
0
        uint64_t cgroupid = UINT64_MAX;
141
0
        _cleanup_free_ char *b = NULL;
142
0
        _cleanup_close_ int fd = -EBADF;
143
0
        bool delegate;
144
0
        int r;
145
146
0
        fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW|O_DIRECTORY, 0);
147
0
        if (fd < 0)
148
0
                return log_debug_errno(errno, "Failed to open cgroup '%s', ignoring: %m", path);
149
150
0
        r = cg_is_delegated_fd(fd);
151
0
        if (r < 0)
152
0
                log_debug_errno(r, "Failed to check if cgroup is delegated, ignoring: %m");
153
0
        delegate = r > 0;
154
155
0
        if (FLAGS_SET(flags, OUTPUT_CGROUP_ID)) {
156
0
                r = cg_fd_get_cgroupid(fd, &cgroupid);
157
0
                if (r < 0)
158
0
                        log_debug_errno(r, "Failed to determine cgroup ID of %s, ignoring: %m", path);
159
0
        }
160
161
0
        r = path_extract_filename(path, &b);
162
0
        if (r < 0)
163
0
                return log_error_errno(r, "Failed to extract filename from cgroup path: %m");
164
165
0
        printf("%s%s%s%s%s",
166
0
               prefix, glyph(tree),
167
0
               delegate ? ansi_underline() : "",
168
0
               cg_unescape(b),
169
0
               delegate ? ansi_normal() : "");
170
171
0
        if (delegate)
172
0
                printf(" %s%s%s",
173
0
                       ansi_highlight(),
174
0
                       glyph(GLYPH_ELLIPSIS),
175
0
                       ansi_normal());
176
177
0
        if (cgroupid != UINT64_MAX)
178
0
                printf(" %s(#%" PRIu64 ")%s", ansi_grey(), cgroupid, ansi_normal());
179
180
0
        printf("\n");
181
182
0
        if (FLAGS_SET(flags, OUTPUT_CGROUP_XATTRS)) {
183
0
                _cleanup_free_ char *nl = NULL;
184
185
0
                r = flistxattr_malloc(fd, &nl);
186
0
                if (r < 0)
187
0
                        log_debug_errno(r, "Failed to enumerate xattrs on '%s', ignoring: %m", path);
188
189
0
                NULSTR_FOREACH(xa, nl) {
190
0
                        _cleanup_free_ char *x = NULL, *y = NULL, *buf = NULL;
191
192
0
                        if (!STARTSWITH_SET(xa, "user.", "trusted."))
193
0
                                continue;
194
195
0
                        size_t buf_size;
196
0
                        r = fgetxattr_malloc(fd, xa, &buf, &buf_size);
197
0
                        if (r < 0) {
198
0
                                log_debug_errno(r, "Failed to read xattr '%s' off '%s', ignoring: %m", xa, path);
199
0
                                continue;
200
0
                        }
201
202
0
                        x = cescape(xa);
203
0
                        if (!x)
204
0
                                return -ENOMEM;
205
206
0
                        y = cescape_length(buf, buf_size);
207
0
                        if (!y)
208
0
                                return -ENOMEM;
209
210
0
                        printf("%s%s%s %s%s%s: %s\n",
211
0
                               prefix,
212
0
                               tree == GLYPH_TREE_BRANCH ? glyph(GLYPH_TREE_VERTICAL) : "  ",
213
0
                               glyph(GLYPH_ARROW_RIGHT),
214
0
                               ansi_blue(), x, ansi_normal(),
215
0
                               y);
216
0
                }
217
0
        }
218
219
0
        return 0;
220
0
}
221
222
int show_cgroup_by_path(
223
                const char *path,
224
                const char *prefix,
225
                size_t n_columns,
226
0
                OutputFlags flags) {
227
228
0
        _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL;
229
0
        _cleanup_closedir_ DIR *d = NULL;
230
0
        bool shown_pids = false;
231
0
        char *gn = NULL;
232
0
        int r;
233
234
0
        assert(path);
235
236
0
        if (n_columns <= 0)
237
0
                n_columns = columns();
238
239
0
        prefix = strempty(prefix);
240
241
0
        r = cg_mangle_path(path, &fn);
242
0
        if (r < 0)
243
0
                return r;
244
245
0
        d = opendir(fn);
246
0
        if (!d)
247
0
                return -errno;
248
249
0
        while ((r = cg_read_subgroup(d, &gn)) > 0) {
250
0
                _cleanup_free_ char *k = NULL;
251
252
0
                k = path_join(fn, gn);
253
0
                free(gn);
254
0
                if (!k)
255
0
                        return -ENOMEM;
256
257
0
                if (!(flags & OUTPUT_SHOW_ALL) && cg_is_empty(NULL, k) > 0)
258
0
                        continue;
259
260
0
                if (!shown_pids) {
261
0
                        (void) show_cgroup_one_by_path(path, prefix, n_columns, true, flags);
262
0
                        shown_pids = true;
263
0
                }
264
265
0
                if (last) {
266
0
                        r = show_cgroup_name(last, prefix, GLYPH_TREE_BRANCH, flags);
267
0
                        if (r < 0)
268
0
                                return r;
269
270
0
                        if (!p1) {
271
0
                                p1 = strjoin(prefix, glyph(GLYPH_TREE_VERTICAL));
272
0
                                if (!p1)
273
0
                                        return -ENOMEM;
274
0
                        }
275
276
0
                        show_cgroup_by_path(last, p1, n_columns-2, flags);
277
0
                        free(last);
278
0
                }
279
280
0
                last = TAKE_PTR(k);
281
0
        }
282
283
0
        if (r < 0)
284
0
                return r;
285
286
0
        if (!shown_pids)
287
0
                (void) show_cgroup_one_by_path(path, prefix, n_columns, !!last, flags);
288
289
0
        if (last) {
290
0
                r = show_cgroup_name(last, prefix, GLYPH_TREE_RIGHT, flags);
291
0
                if (r < 0)
292
0
                        return r;
293
294
0
                if (!p2) {
295
0
                        p2 = strjoin(prefix, "  ");
296
0
                        if (!p2)
297
0
                                return -ENOMEM;
298
0
                }
299
300
0
                show_cgroup_by_path(last, p2, n_columns-2, flags);
301
0
        }
302
303
0
        return 0;
304
0
}
305
306
int show_cgroup(const char *controller,
307
                const char *path,
308
                const char *prefix,
309
                size_t n_columns,
310
0
                OutputFlags flags) {
311
0
        _cleanup_free_ char *p = NULL;
312
0
        int r;
313
314
0
        assert(path);
315
316
0
        r = cg_get_path(controller, path, NULL, &p);
317
0
        if (r < 0)
318
0
                return r;
319
320
0
        return show_cgroup_by_path(p, prefix, n_columns, flags);
321
0
}
322
323
static int show_extra_pids(
324
                const char *controller,
325
                const char *path,
326
                const char *prefix,
327
                size_t n_columns,
328
                const pid_t pids[],
329
                size_t n_pids,
330
0
                OutputFlags flags) {
331
332
0
        _cleanup_free_ pid_t *copy = NULL;
333
0
        size_t i, j;
334
0
        int r;
335
336
0
        assert(path);
337
338
0
        if (n_pids <= 0)
339
0
                return 0;
340
341
0
        if (n_columns <= 0)
342
0
                n_columns = columns();
343
344
0
        prefix = strempty(prefix);
345
346
0
        copy = new(pid_t, n_pids);
347
0
        if (!copy)
348
0
                return -ENOMEM;
349
350
0
        for (i = 0, j = 0; i < n_pids; i++) {
351
0
                _cleanup_free_ char *k = NULL;
352
353
0
                r = cg_pid_get_path(controller, pids[i], &k);
354
0
                if (r < 0)
355
0
                        return r;
356
357
0
                if (path_startswith(k, path))
358
0
                        continue;
359
360
0
                copy[j++] = pids[i];
361
0
        }
362
363
0
        show_pid_array(copy, j, prefix, n_columns, true, false, flags);
364
365
0
        return 0;
366
0
}
367
368
int show_cgroup_and_extra(
369
                const char *controller,
370
                const char *path,
371
                const char *prefix,
372
                size_t n_columns,
373
                const pid_t extra_pids[],
374
                size_t n_extra_pids,
375
0
                OutputFlags flags) {
376
377
0
        int r;
378
379
0
        assert(path);
380
381
0
        r = show_cgroup(controller, path, prefix, n_columns, flags);
382
0
        if (r < 0)
383
0
                return r;
384
385
0
        return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags);
386
0
}
387
388
int show_cgroup_get_unit_path_and_warn(
389
                sd_bus *bus,
390
                const char *unit,
391
0
                char **ret) {
392
393
0
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
394
0
        _cleanup_free_ char *path = NULL;
395
0
        int r;
396
397
0
        path = unit_dbus_path_from_name(unit);
398
0
        if (!path)
399
0
                return log_oom();
400
401
0
        r = sd_bus_get_property_string(
402
0
                        bus,
403
0
                        "org.freedesktop.systemd1",
404
0
                        path,
405
0
                        unit_dbus_interface_from_name(unit),
406
0
                        "ControlGroup",
407
0
                        &error,
408
0
                        ret);
409
0
        if (r < 0)
410
0
                return log_error_errno(r, "Failed to query unit control group path: %s",
411
0
                                       bus_error_message(&error, r));
412
413
0
        return 0;
414
0
}
415
416
int show_cgroup_get_path_and_warn(
417
                const char *machine,
418
                const char *prefix,
419
0
                char **ret) {
420
421
0
        _cleanup_free_ char *root = NULL;
422
0
        int r;
423
424
0
        if (machine) {
425
0
                _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
426
0
                _cleanup_free_ char *unit = NULL;
427
0
                const char *m;
428
429
0
                if (!hostname_is_valid(machine, 0))
430
0
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine name is not valid: %s", machine);
431
432
0
                m = strjoina("/run/systemd/machines/", machine);
433
0
                r = parse_env_file(NULL, m, "SCOPE", &unit);
434
0
                if (r < 0)
435
0
                        return log_error_errno(r, "Failed to load machine data: %m");
436
437
0
                r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, RUNTIME_SCOPE_SYSTEM, &bus);
438
0
                if (r < 0)
439
0
                        return bus_log_connect_error(r, BUS_TRANSPORT_LOCAL, RUNTIME_SCOPE_SYSTEM);
440
441
0
                r = show_cgroup_get_unit_path_and_warn(bus, unit, &root);
442
0
                if (r < 0)
443
0
                        return r;
444
0
        } else {
445
0
                r = cg_get_root_path(&root);
446
0
                if (r == -ENOMEDIUM)
447
0
                        return log_error_errno(r, "Failed to get root control group path.\n"
448
0
                                                  "No cgroup filesystem mounted on /sys/fs/cgroup");
449
0
                if (r < 0)
450
0
                        return log_error_errno(r, "Failed to get root control group path: %m");
451
0
        }
452
453
0
        if (prefix) {
454
0
                char *t;
455
456
0
                t = path_join(root, prefix);
457
0
                if (!t)
458
0
                        return log_oom();
459
460
0
                *ret = t;
461
0
        } else
462
0
                *ret = TAKE_PTR(root);
463
464
0
        return 0;
465
0
}