1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <errno.h>
4 #include <getopt.h>
5 #include <stdio.h>
6 #include <unistd.h>
7 
8 #include "sd-bus.h"
9 
10 #include "alloc-util.h"
11 #include "bus-util.h"
12 #include "cgroup-show.h"
13 #include "cgroup-util.h"
14 #include "fileio.h"
15 #include "log.h"
16 #include "main-func.h"
17 #include "output-mode.h"
18 #include "pager.h"
19 #include "parse-util.h"
20 #include "path-util.h"
21 #include "pretty-print.h"
22 #include "strv.h"
23 #include "unit-name.h"
24 #include "util.h"
25 
26 static PagerFlags arg_pager_flags = 0;
27 static OutputFlags arg_output_flags = OUTPUT_CGROUP_XATTRS | OUTPUT_CGROUP_ID;
28 
29 static enum {
30         SHOW_UNIT_NONE,
31         SHOW_UNIT_SYSTEM,
32         SHOW_UNIT_USER,
33 } arg_show_unit = SHOW_UNIT_NONE;
34 static char **arg_names = NULL;
35 
36 static int arg_full = -1;
37 static const char* arg_machine = NULL;
38 
39 STATIC_DESTRUCTOR_REGISTER(arg_names, freep); /* don't free the strings */
40 
help(void)41 static int help(void) {
42         _cleanup_free_ char *link = NULL;
43         int r;
44 
45         r = terminal_urlify_man("systemd-cgls", "1", &link);
46         if (r < 0)
47                 return log_oom();
48 
49         printf("%s [OPTIONS...] [CGROUP...]\n\n"
50                "Recursively show control group contents.\n\n"
51                "  -h --help           Show this help\n"
52                "     --version        Show package version\n"
53                "     --no-pager       Do not pipe output into a pager\n"
54                "  -a --all            Show all groups, including empty\n"
55                "  -u --unit           Show the subtrees of specified system units\n"
56                "     --user-unit      Show the subtrees of specified user units\n"
57                "     --xattr=BOOL     Show cgroup extended attributes\n"
58                "     --cgroup-id=BOOL Show cgroup ID\n"
59                "  -l --full           Do not ellipsize output\n"
60                "  -k                  Include kernel threads in output\n"
61                "  -M --machine=       Show container\n"
62                "\nSee the %s for details.\n",
63                program_invocation_short_name,
64                link);
65 
66         return 0;
67 }
68 
parse_argv(int argc,char * argv[])69 static int parse_argv(int argc, char *argv[]) {
70 
71         enum {
72                 ARG_NO_PAGER = 0x100,
73                 ARG_VERSION,
74                 ARG_USER_UNIT,
75                 ARG_XATTR,
76                 ARG_CGROUP_ID,
77         };
78 
79         static const struct option options[] = {
80                 { "help",      no_argument,       NULL, 'h'           },
81                 { "version",   no_argument,       NULL, ARG_VERSION   },
82                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
83                 { "all",       no_argument,       NULL, 'a'           },
84                 { "full",      no_argument,       NULL, 'l'           },
85                 { "machine",   required_argument, NULL, 'M'           },
86                 { "unit",      optional_argument, NULL, 'u'           },
87                 { "user-unit", optional_argument, NULL, ARG_USER_UNIT },
88                 { "xattr",     required_argument, NULL, ARG_XATTR     },
89                 { "cgroup-id", required_argument, NULL, ARG_CGROUP_ID },
90                 {}
91         };
92 
93         int c, r;
94 
95         assert(argc >= 1);
96         assert(argv);
97 
98         while ((c = getopt_long(argc, argv, "-hkalM:u::", options, NULL)) >= 0)
99 
100                 switch (c) {
101 
102                 case 'h':
103                         return help();
104 
105                 case ARG_VERSION:
106                         return version();
107 
108                 case ARG_NO_PAGER:
109                         arg_pager_flags |= PAGER_DISABLE;
110                         break;
111 
112                 case 'a':
113                         arg_output_flags |= OUTPUT_SHOW_ALL;
114                         break;
115 
116                 case 'u':
117                         arg_show_unit = SHOW_UNIT_SYSTEM;
118                         if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */
119                                 return log_oom();
120                         break;
121 
122                 case ARG_USER_UNIT:
123                         arg_show_unit = SHOW_UNIT_USER;
124                         if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */
125                                 return log_oom();
126                         break;
127 
128                 case 1:
129                         /* positional argument */
130                         if (strv_push(&arg_names, optarg) < 0)
131                                 return log_oom();
132                         break;
133 
134                 case 'l':
135                         arg_full = true;
136                         break;
137 
138                 case 'k':
139                         arg_output_flags |= OUTPUT_KERNEL_THREADS;
140                         break;
141 
142                 case 'M':
143                         arg_machine = optarg;
144                         break;
145 
146                 case ARG_XATTR:
147                         r = parse_boolean(optarg);
148                         if (r < 0)
149                                 return log_error_errno(r, "Failed to parse --xattr= value: %s", optarg);
150 
151                         SET_FLAG(arg_output_flags, OUTPUT_CGROUP_XATTRS, r);
152                         break;
153 
154                 case ARG_CGROUP_ID:
155                         r = parse_boolean(optarg);
156                         if (r < 0)
157                                 return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", optarg);
158 
159                         SET_FLAG(arg_output_flags, OUTPUT_CGROUP_ID, r);
160                         break;
161 
162                 case '?':
163                         return -EINVAL;
164 
165                 default:
166                         assert_not_reached();
167                 }
168 
169         if (arg_machine && arg_show_unit != SHOW_UNIT_NONE)
170                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
171                                        "Cannot combine --unit or --user-unit with --machine=.");
172 
173         return 1;
174 }
175 
show_cg_info(const char * controller,const char * path)176 static void show_cg_info(const char *controller, const char *path) {
177 
178         if (cg_all_unified() == 0 && controller && !streq(controller, SYSTEMD_CGROUP_CONTROLLER))
179                 printf("Controller %s; ", controller);
180 
181         printf("Control group %s:\n", empty_to_root(path));
182         fflush(stdout);
183 }
184 
run(int argc,char * argv[])185 static int run(int argc, char *argv[]) {
186         int r;
187 
188         log_setup();
189 
190         r = parse_argv(argc, argv);
191         if (r <= 0)
192                 return r;
193 
194         pager_open(arg_pager_flags);
195         if (arg_full < 0 && pager_have())
196                 arg_full = true;
197 
198         if (arg_full > 0)
199                 arg_output_flags |= OUTPUT_FULL_WIDTH;
200 
201         if (arg_names) {
202                 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
203                 _cleanup_free_ char *root = NULL;
204 
205                 STRV_FOREACH(name, arg_names) {
206                         int q;
207 
208                         if (arg_show_unit != SHOW_UNIT_NONE) {
209                                 /* Command line arguments are unit names */
210                                 _cleanup_free_ char *cgroup = NULL, *unit_name = NULL;
211 
212                                 r = unit_name_mangle(*name, UNIT_NAME_MANGLE_WARN, &unit_name);
213                                 if (r < 0)
214                                         return log_error_errno(r, "Failed to mangle unit name: %m");
215 
216                                 if (!bus) {
217                                         /* Connect to the bus only if necessary */
218                                         r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL,
219                                                                           arg_show_unit == SHOW_UNIT_USER,
220                                                                           &bus);
221                                         if (r < 0)
222                                                 return bus_log_connect_error(r, BUS_TRANSPORT_LOCAL);
223                                 }
224 
225                                 q = show_cgroup_get_unit_path_and_warn(bus, unit_name, &cgroup);
226                                 if (q < 0)
227                                         goto failed;
228 
229                                 if (isempty(cgroup)) {
230                                         q = log_warning_errno(SYNTHETIC_ERRNO(ENOENT), "Unit %s not found.", unit_name);
231                                         goto failed;
232                                 }
233 
234                                 printf("Unit %s (%s):\n", unit_name, cgroup);
235                                 fflush(stdout);
236 
237                                 q = show_cgroup_by_path(cgroup, NULL, 0, arg_output_flags);
238 
239                         } else if (path_startswith(*name, "/sys/fs/cgroup")) {
240 
241                                 printf("Directory %s:\n", *name);
242                                 fflush(stdout);
243 
244                                 q = show_cgroup_by_path(*name, NULL, 0, arg_output_flags);
245                         } else {
246                                 _cleanup_free_ char *c = NULL, *p = NULL, *j = NULL;
247                                 const char *controller, *path;
248 
249                                 if (!root) {
250                                         /* Query root only if needed, treat error as fatal */
251                                         r = show_cgroup_get_path_and_warn(arg_machine, NULL, &root);
252                                         if (r < 0)
253                                                 return log_error_errno(r, "Failed to list cgroup tree: %m");
254                                 }
255 
256                                 q = cg_split_spec(*name, &c, &p);
257                                 if (q < 0) {
258                                         log_error_errno(q, "Failed to split argument %s: %m", *name);
259                                         goto failed;
260                                 }
261 
262                                 controller = c ?: SYSTEMD_CGROUP_CONTROLLER;
263                                 if (p) {
264                                         j = path_join(root, p);
265                                         if (!j)
266                                                 return log_oom();
267 
268                                         path_simplify(j);
269                                         path = j;
270                                 } else
271                                         path = root;
272 
273                                 show_cg_info(controller, path);
274 
275                                 q = show_cgroup(controller, path, NULL, 0, arg_output_flags);
276                         }
277 
278                 failed:
279                         if (q < 0 && r >= 0)
280                                 r = q;
281                 }
282 
283         } else {
284                 bool done = false;
285 
286                 if (!arg_machine)  {
287                         _cleanup_free_ char *cwd = NULL;
288 
289                         r = safe_getcwd(&cwd);
290                         if (r < 0)
291                                 return log_error_errno(r, "Cannot determine current working directory: %m");
292 
293                         if (path_startswith(cwd, "/sys/fs/cgroup")) {
294                                 printf("Working directory %s:\n", cwd);
295                                 fflush(stdout);
296 
297                                 r = show_cgroup_by_path(cwd, NULL, 0, arg_output_flags);
298                                 done = true;
299                         }
300                 }
301 
302                 if (!done) {
303                         _cleanup_free_ char *root = NULL;
304 
305                         r = show_cgroup_get_path_and_warn(arg_machine, NULL, &root);
306                         if (r < 0)
307                                 return log_error_errno(r, "Failed to list cgroup tree: %m");
308 
309                         show_cg_info(SYSTEMD_CGROUP_CONTROLLER, root);
310 
311                         printf("-.slice\n");
312                         r = show_cgroup(SYSTEMD_CGROUP_CONTROLLER, root, NULL, 0, arg_output_flags);
313                 }
314         }
315         if (r < 0)
316                 return log_error_errno(r, "Failed to list cgroup tree: %m");
317 
318         return 0;
319 }
320 
321 DEFINE_MAIN_FUNCTION(run);
322