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