xref: /linux/tools/bpf/bpftool/cgroup.c (revision 7d8d6ad659c02ed5d2387777194c22e8e81dbb2b)
1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
2 // Copyright (C) 2017 Facebook
3 // Author: Roman Gushchin <guro@fb.com>
4 
5 #undef GCC_VERSION
6 #ifndef _GNU_SOURCE
7 #define _GNU_SOURCE
8 #endif
9 #define _XOPEN_SOURCE 500
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <ftw.h>
13 #include <mntent.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19 #include <unistd.h>
20 
21 #include <bpf/bpf.h>
22 #include <bpf/btf.h>
23 
24 #include "main.h"
25 
26 static const int cgroup_attach_types[] = {
27 	BPF_CGROUP_INET_INGRESS,
28 	BPF_CGROUP_INET_EGRESS,
29 	BPF_CGROUP_INET_SOCK_CREATE,
30 	BPF_CGROUP_INET_SOCK_RELEASE,
31 	BPF_CGROUP_INET4_BIND,
32 	BPF_CGROUP_INET6_BIND,
33 	BPF_CGROUP_INET4_POST_BIND,
34 	BPF_CGROUP_INET6_POST_BIND,
35 	BPF_CGROUP_INET4_CONNECT,
36 	BPF_CGROUP_INET6_CONNECT,
37 	BPF_CGROUP_UNIX_CONNECT,
38 	BPF_CGROUP_INET4_GETPEERNAME,
39 	BPF_CGROUP_INET6_GETPEERNAME,
40 	BPF_CGROUP_UNIX_GETPEERNAME,
41 	BPF_CGROUP_INET4_GETSOCKNAME,
42 	BPF_CGROUP_INET6_GETSOCKNAME,
43 	BPF_CGROUP_UNIX_GETSOCKNAME,
44 	BPF_CGROUP_UDP4_SENDMSG,
45 	BPF_CGROUP_UDP6_SENDMSG,
46 	BPF_CGROUP_UNIX_SENDMSG,
47 	BPF_CGROUP_UDP4_RECVMSG,
48 	BPF_CGROUP_UDP6_RECVMSG,
49 	BPF_CGROUP_UNIX_RECVMSG,
50 	BPF_CGROUP_SOCK_OPS,
51 	BPF_CGROUP_DEVICE,
52 	BPF_CGROUP_SYSCTL,
53 	BPF_CGROUP_GETSOCKOPT,
54 	BPF_CGROUP_SETSOCKOPT,
55 	BPF_LSM_CGROUP
56 };
57 
58 #define HELP_SPEC_ATTACH_FLAGS						\
59 	"ATTACH_FLAGS := { multi | override }"
60 
61 #define HELP_SPEC_ATTACH_TYPES						\
62 	"       ATTACH_TYPE := { cgroup_inet_ingress | cgroup_inet_egress |\n" \
63 	"                        cgroup_inet_sock_create | cgroup_sock_ops |\n" \
64 	"                        cgroup_device | cgroup_inet4_bind |\n" \
65 	"                        cgroup_inet6_bind | cgroup_inet4_post_bind |\n" \
66 	"                        cgroup_inet6_post_bind | cgroup_inet4_connect |\n" \
67 	"                        cgroup_inet6_connect | cgroup_unix_connect |\n" \
68 	"                        cgroup_inet4_getpeername | cgroup_inet6_getpeername |\n" \
69 	"                        cgroup_unix_getpeername | cgroup_inet4_getsockname |\n" \
70 	"                        cgroup_inet6_getsockname | cgroup_unix_getsockname |\n" \
71 	"                        cgroup_udp4_sendmsg | cgroup_udp6_sendmsg |\n" \
72 	"                        cgroup_unix_sendmsg | cgroup_udp4_recvmsg |\n" \
73 	"                        cgroup_udp6_recvmsg | cgroup_unix_recvmsg |\n" \
74 	"                        cgroup_sysctl | cgroup_getsockopt |\n" \
75 	"                        cgroup_setsockopt | cgroup_inet_sock_release }"
76 
77 static unsigned int query_flags;
78 static struct btf *btf_vmlinux;
79 static __u32 btf_vmlinux_id;
80 
81 static void free_btf_vmlinux(void)
82 {
83 	btf__free(btf_vmlinux);
84 	btf_vmlinux = NULL;
85 	btf_vmlinux_id = 0;
86 }
87 
88 static enum bpf_attach_type parse_attach_type(const char *str)
89 {
90 	const char *attach_type_str;
91 	enum bpf_attach_type type;
92 
93 	for (type = 0; ; type++) {
94 		attach_type_str = libbpf_bpf_attach_type_str(type);
95 		if (!attach_type_str)
96 			break;
97 		if (!strcmp(str, attach_type_str))
98 			return type;
99 	}
100 
101 	/* Also check traditionally used attach type strings. For these we keep
102 	 * allowing prefixed usage.
103 	 */
104 	for (type = 0; ; type++) {
105 		attach_type_str = bpf_attach_type_input_str(type);
106 		if (!attach_type_str)
107 			break;
108 		if (is_prefix(str, attach_type_str))
109 			return type;
110 	}
111 
112 	return __MAX_BPF_ATTACH_TYPE;
113 }
114 
115 static void guess_vmlinux_btf_id(__u32 attach_btf_obj_id)
116 {
117 	struct bpf_btf_info btf_info = {};
118 	__u32 btf_len = sizeof(btf_info);
119 	char name[16] = {};
120 	int err;
121 	int fd;
122 
123 	btf_info.name = ptr_to_u64(name);
124 	btf_info.name_len = sizeof(name);
125 
126 	fd = bpf_btf_get_fd_by_id(attach_btf_obj_id);
127 	if (fd < 0)
128 		return;
129 
130 	err = bpf_btf_get_info_by_fd(fd, &btf_info, &btf_len);
131 	if (err)
132 		goto out;
133 
134 	if (btf_info.kernel_btf && strncmp(name, "vmlinux", sizeof(name)) == 0)
135 		btf_vmlinux_id = btf_info.id;
136 
137 out:
138 	close(fd);
139 }
140 
141 static int show_bpf_prog(int id, enum bpf_attach_type attach_type,
142 			 const char *attach_flags_str,
143 			 int level)
144 {
145 	char prog_name[MAX_PROG_FULL_NAME];
146 	const char *attach_btf_name = NULL;
147 	struct bpf_prog_info info = {};
148 	const char *attach_type_str;
149 	__u32 info_len = sizeof(info);
150 	int prog_fd;
151 
152 	prog_fd = bpf_prog_get_fd_by_id(id);
153 	if (prog_fd < 0)
154 		return -1;
155 
156 	if (bpf_prog_get_info_by_fd(prog_fd, &info, &info_len)) {
157 		close(prog_fd);
158 		return -1;
159 	}
160 
161 	attach_type_str = libbpf_bpf_attach_type_str(attach_type);
162 
163 	if (btf_vmlinux) {
164 		if (!btf_vmlinux_id)
165 			guess_vmlinux_btf_id(info.attach_btf_obj_id);
166 
167 		if (btf_vmlinux_id == info.attach_btf_obj_id &&
168 		    info.attach_btf_id < btf__type_cnt(btf_vmlinux)) {
169 			const struct btf_type *t =
170 				btf__type_by_id(btf_vmlinux, info.attach_btf_id);
171 			attach_btf_name =
172 				btf__name_by_offset(btf_vmlinux, t->name_off);
173 		}
174 	}
175 
176 	get_prog_full_name(&info, prog_fd, prog_name, sizeof(prog_name));
177 	if (json_output) {
178 		jsonw_start_object(json_wtr);
179 		jsonw_uint_field(json_wtr, "id", info.id);
180 		if (attach_type_str)
181 			jsonw_string_field(json_wtr, "attach_type", attach_type_str);
182 		else
183 			jsonw_uint_field(json_wtr, "attach_type", attach_type);
184 		if (!(query_flags & BPF_F_QUERY_EFFECTIVE))
185 			jsonw_string_field(json_wtr, "attach_flags", attach_flags_str);
186 		jsonw_string_field(json_wtr, "name", prog_name);
187 		if (attach_btf_name)
188 			jsonw_string_field(json_wtr, "attach_btf_name", attach_btf_name);
189 		jsonw_uint_field(json_wtr, "attach_btf_obj_id", info.attach_btf_obj_id);
190 		jsonw_uint_field(json_wtr, "attach_btf_id", info.attach_btf_id);
191 		jsonw_end_object(json_wtr);
192 	} else {
193 		printf("%s%-8u ", level ? "    " : "", info.id);
194 		if (attach_type_str)
195 			printf("%-15s", attach_type_str);
196 		else
197 			printf("type %-10u", attach_type);
198 		if (query_flags & BPF_F_QUERY_EFFECTIVE)
199 			printf(" %-15s", prog_name);
200 		else
201 			printf(" %-15s %-15s", attach_flags_str, prog_name);
202 		if (attach_btf_name)
203 			printf(" %-15s", attach_btf_name);
204 		else if (info.attach_btf_id)
205 			printf(" attach_btf_obj_id=%u attach_btf_id=%u",
206 			       info.attach_btf_obj_id, info.attach_btf_id);
207 		printf("\n");
208 	}
209 
210 	close(prog_fd);
211 	return 0;
212 }
213 
214 static int count_attached_bpf_progs(int cgroup_fd, enum bpf_attach_type type)
215 {
216 	__u32 prog_cnt = 0;
217 	int ret;
218 
219 	ret = bpf_prog_query(cgroup_fd, type, query_flags, NULL,
220 			     NULL, &prog_cnt);
221 	if (ret)
222 		return -1;
223 
224 	return prog_cnt;
225 }
226 
227 static int cgroup_has_attached_progs(int cgroup_fd)
228 {
229 	unsigned int i = 0;
230 	bool no_prog = true;
231 
232 	for (i = 0; i < ARRAY_SIZE(cgroup_attach_types); i++) {
233 		int count = count_attached_bpf_progs(cgroup_fd, cgroup_attach_types[i]);
234 
235 		if (count < 0 && errno != EINVAL)
236 			return -1;
237 
238 		if (count > 0) {
239 			no_prog = false;
240 			break;
241 		}
242 	}
243 
244 	return no_prog ? 0 : 1;
245 }
246 
247 static int show_effective_bpf_progs(int cgroup_fd, enum bpf_attach_type type,
248 				    int level)
249 {
250 	LIBBPF_OPTS(bpf_prog_query_opts, p);
251 	__u32 prog_ids[1024] = {0};
252 	__u32 iter;
253 	int ret;
254 
255 	p.query_flags = query_flags;
256 	p.prog_cnt = ARRAY_SIZE(prog_ids);
257 	p.prog_ids = prog_ids;
258 
259 	ret = bpf_prog_query_opts(cgroup_fd, type, &p);
260 	if (ret)
261 		return ret;
262 
263 	if (p.prog_cnt == 0)
264 		return 0;
265 
266 	for (iter = 0; iter < p.prog_cnt; iter++)
267 		show_bpf_prog(prog_ids[iter], type, NULL, level);
268 
269 	return 0;
270 }
271 
272 static int show_attached_bpf_progs(int cgroup_fd, enum bpf_attach_type type,
273 				   int level)
274 {
275 	LIBBPF_OPTS(bpf_prog_query_opts, p);
276 	__u32 prog_attach_flags[1024] = {0};
277 	const char *attach_flags_str;
278 	__u32 prog_ids[1024] = {0};
279 	char buf[32];
280 	__u32 iter;
281 	int ret;
282 
283 	p.query_flags = query_flags;
284 	p.prog_cnt = ARRAY_SIZE(prog_ids);
285 	p.prog_ids = prog_ids;
286 	p.prog_attach_flags = prog_attach_flags;
287 
288 	ret = bpf_prog_query_opts(cgroup_fd, type, &p);
289 	if (ret)
290 		return ret;
291 
292 	if (p.prog_cnt == 0)
293 		return 0;
294 
295 	for (iter = 0; iter < p.prog_cnt; iter++) {
296 		__u32 attach_flags;
297 
298 		attach_flags = prog_attach_flags[iter] ?: p.attach_flags;
299 
300 		switch (attach_flags) {
301 		case BPF_F_ALLOW_MULTI:
302 			attach_flags_str = "multi";
303 			break;
304 		case BPF_F_ALLOW_OVERRIDE:
305 			attach_flags_str = "override";
306 			break;
307 		case 0:
308 			attach_flags_str = "";
309 			break;
310 		default:
311 			snprintf(buf, sizeof(buf), "unknown(%x)", attach_flags);
312 			attach_flags_str = buf;
313 		}
314 
315 		show_bpf_prog(prog_ids[iter], type,
316 			      attach_flags_str, level);
317 	}
318 
319 	return 0;
320 }
321 
322 static int show_bpf_progs(int cgroup_fd, enum bpf_attach_type type,
323 			  int level)
324 {
325 	return query_flags & BPF_F_QUERY_EFFECTIVE ?
326 	       show_effective_bpf_progs(cgroup_fd, type, level) :
327 	       show_attached_bpf_progs(cgroup_fd, type, level);
328 }
329 
330 static int do_show(int argc, char **argv)
331 {
332 	int has_attached_progs;
333 	const char *path;
334 	int cgroup_fd;
335 	int ret = -1;
336 	unsigned int i;
337 
338 	query_flags = 0;
339 
340 	if (!REQ_ARGS(1))
341 		return -1;
342 	path = GET_ARG();
343 
344 	while (argc) {
345 		if (is_prefix(*argv, "effective")) {
346 			if (query_flags & BPF_F_QUERY_EFFECTIVE) {
347 				p_err("duplicated argument: %s", *argv);
348 				return -1;
349 			}
350 			query_flags |= BPF_F_QUERY_EFFECTIVE;
351 			NEXT_ARG();
352 		} else {
353 			p_err("expected no more arguments, 'effective', got: '%s'?",
354 			      *argv);
355 			return -1;
356 		}
357 	}
358 
359 	cgroup_fd = open(path, O_RDONLY);
360 	if (cgroup_fd < 0) {
361 		p_err("can't open cgroup %s", path);
362 		goto exit;
363 	}
364 
365 	has_attached_progs = cgroup_has_attached_progs(cgroup_fd);
366 	if (has_attached_progs < 0) {
367 		p_err("can't query bpf programs attached to %s: %s",
368 		      path, strerror(errno));
369 		goto exit_cgroup;
370 	} else if (!has_attached_progs) {
371 		ret = 0;
372 		goto exit_cgroup;
373 	}
374 
375 	if (json_output)
376 		jsonw_start_array(json_wtr);
377 	else if (query_flags & BPF_F_QUERY_EFFECTIVE)
378 		printf("%-8s %-15s %-15s\n", "ID", "AttachType", "Name");
379 	else
380 		printf("%-8s %-15s %-15s %-15s\n", "ID", "AttachType",
381 		       "AttachFlags", "Name");
382 
383 	btf_vmlinux = libbpf_find_kernel_btf();
384 	for (i = 0; i < ARRAY_SIZE(cgroup_attach_types); i++) {
385 		/*
386 		 * Not all attach types may be supported, so it's expected,
387 		 * that some requests will fail.
388 		 * If we were able to get the show for at least one
389 		 * attach type, let's return 0.
390 		 */
391 		if (show_bpf_progs(cgroup_fd, cgroup_attach_types[i], 0) == 0)
392 			ret = 0;
393 	}
394 
395 	if (json_output)
396 		jsonw_end_array(json_wtr);
397 
398 	free_btf_vmlinux();
399 
400 exit_cgroup:
401 	close(cgroup_fd);
402 exit:
403 	return ret;
404 }
405 
406 /*
407  * To distinguish nftw() errors and do_show_tree_fn() errors
408  * and avoid duplicating error messages, let's return -2
409  * from do_show_tree_fn() in case of error.
410  */
411 #define NFTW_ERR		-1
412 #define SHOW_TREE_FN_ERR	-2
413 static int do_show_tree_fn(const char *fpath, const struct stat *sb,
414 			   int typeflag, struct FTW *ftw)
415 {
416 	int has_attached_progs;
417 	int cgroup_fd;
418 	unsigned int i;
419 
420 	if (typeflag != FTW_D)
421 		return 0;
422 
423 	cgroup_fd = open(fpath, O_RDONLY);
424 	if (cgroup_fd < 0) {
425 		p_err("can't open cgroup %s: %s", fpath, strerror(errno));
426 		return SHOW_TREE_FN_ERR;
427 	}
428 
429 	has_attached_progs = cgroup_has_attached_progs(cgroup_fd);
430 	if (has_attached_progs < 0) {
431 		p_err("can't query bpf programs attached to %s: %s",
432 		      fpath, strerror(errno));
433 		close(cgroup_fd);
434 		return SHOW_TREE_FN_ERR;
435 	} else if (!has_attached_progs) {
436 		close(cgroup_fd);
437 		return 0;
438 	}
439 
440 	if (json_output) {
441 		jsonw_start_object(json_wtr);
442 		jsonw_string_field(json_wtr, "cgroup", fpath);
443 		jsonw_name(json_wtr, "programs");
444 		jsonw_start_array(json_wtr);
445 	} else {
446 		printf("%s\n", fpath);
447 	}
448 
449 	if (!btf_vmlinux)
450 		btf_vmlinux = libbpf_find_kernel_btf();
451 
452 	for (i = 0; i < ARRAY_SIZE(cgroup_attach_types); i++)
453 		show_bpf_progs(cgroup_fd, cgroup_attach_types[i], ftw->level);
454 
455 	if (errno == EINVAL)
456 		/* Last attach type does not support query.
457 		 * Do not report an error for this, especially because batch
458 		 * mode would stop processing commands.
459 		 */
460 		errno = 0;
461 
462 	if (json_output) {
463 		jsonw_end_array(json_wtr);
464 		jsonw_end_object(json_wtr);
465 	}
466 
467 	close(cgroup_fd);
468 
469 	return 0;
470 }
471 
472 static char *find_cgroup_root(void)
473 {
474 	struct mntent *mnt;
475 	FILE *f;
476 
477 	f = fopen("/proc/mounts", "r");
478 	if (f == NULL)
479 		return NULL;
480 
481 	while ((mnt = getmntent(f))) {
482 		if (strcmp(mnt->mnt_type, "cgroup2") == 0) {
483 			fclose(f);
484 			return strdup(mnt->mnt_dir);
485 		}
486 	}
487 
488 	fclose(f);
489 	return NULL;
490 }
491 
492 static int do_show_tree(int argc, char **argv)
493 {
494 	char *cgroup_root, *cgroup_alloced = NULL;
495 	int ret;
496 
497 	query_flags = 0;
498 
499 	if (!argc) {
500 		cgroup_alloced = find_cgroup_root();
501 		if (!cgroup_alloced) {
502 			p_err("cgroup v2 isn't mounted");
503 			return -1;
504 		}
505 		cgroup_root = cgroup_alloced;
506 	} else {
507 		cgroup_root = GET_ARG();
508 
509 		while (argc) {
510 			if (is_prefix(*argv, "effective")) {
511 				if (query_flags & BPF_F_QUERY_EFFECTIVE) {
512 					p_err("duplicated argument: %s", *argv);
513 					return -1;
514 				}
515 				query_flags |= BPF_F_QUERY_EFFECTIVE;
516 				NEXT_ARG();
517 			} else {
518 				p_err("expected no more arguments, 'effective', got: '%s'?",
519 				      *argv);
520 				return -1;
521 			}
522 		}
523 	}
524 
525 	if (json_output)
526 		jsonw_start_array(json_wtr);
527 	else if (query_flags & BPF_F_QUERY_EFFECTIVE)
528 		printf("%s\n"
529 		       "%-8s %-15s %-15s\n",
530 		       "CgroupPath",
531 		       "ID", "AttachType", "Name");
532 	else
533 		printf("%s\n"
534 		       "%-8s %-15s %-15s %-15s\n",
535 		       "CgroupPath",
536 		       "ID", "AttachType", "AttachFlags", "Name");
537 
538 	switch (nftw(cgroup_root, do_show_tree_fn, 1024, FTW_MOUNT)) {
539 	case NFTW_ERR:
540 		p_err("can't iterate over %s: %s", cgroup_root,
541 		      strerror(errno));
542 		ret = -1;
543 		break;
544 	case SHOW_TREE_FN_ERR:
545 		ret = -1;
546 		break;
547 	default:
548 		ret = 0;
549 	}
550 
551 	if (json_output)
552 		jsonw_end_array(json_wtr);
553 
554 	free_btf_vmlinux();
555 	free(cgroup_alloced);
556 
557 	return ret;
558 }
559 
560 static int do_attach(int argc, char **argv)
561 {
562 	enum bpf_attach_type attach_type;
563 	int cgroup_fd, prog_fd;
564 	int attach_flags = 0;
565 	int ret = -1;
566 	int i;
567 
568 	if (argc < 4) {
569 		p_err("too few parameters for cgroup attach");
570 		goto exit;
571 	}
572 
573 	cgroup_fd = open(argv[0], O_RDONLY);
574 	if (cgroup_fd < 0) {
575 		p_err("can't open cgroup %s", argv[0]);
576 		goto exit;
577 	}
578 
579 	attach_type = parse_attach_type(argv[1]);
580 	if (attach_type == __MAX_BPF_ATTACH_TYPE) {
581 		p_err("invalid attach type");
582 		goto exit_cgroup;
583 	}
584 
585 	argc -= 2;
586 	argv = &argv[2];
587 	prog_fd = prog_parse_fd(&argc, &argv);
588 	if (prog_fd < 0)
589 		goto exit_cgroup;
590 
591 	for (i = 0; i < argc; i++) {
592 		if (is_prefix(argv[i], "multi")) {
593 			attach_flags |= BPF_F_ALLOW_MULTI;
594 		} else if (is_prefix(argv[i], "override")) {
595 			attach_flags |= BPF_F_ALLOW_OVERRIDE;
596 		} else {
597 			p_err("unknown option: %s", argv[i]);
598 			goto exit_cgroup;
599 		}
600 	}
601 
602 	if (bpf_prog_attach(prog_fd, cgroup_fd, attach_type, attach_flags)) {
603 		p_err("failed to attach program");
604 		goto exit_prog;
605 	}
606 
607 	if (json_output)
608 		jsonw_null(json_wtr);
609 
610 	ret = 0;
611 
612 exit_prog:
613 	close(prog_fd);
614 exit_cgroup:
615 	close(cgroup_fd);
616 exit:
617 	return ret;
618 }
619 
620 static int do_detach(int argc, char **argv)
621 {
622 	enum bpf_attach_type attach_type;
623 	int prog_fd, cgroup_fd;
624 	int ret = -1;
625 
626 	if (argc < 4) {
627 		p_err("too few parameters for cgroup detach");
628 		goto exit;
629 	}
630 
631 	cgroup_fd = open(argv[0], O_RDONLY);
632 	if (cgroup_fd < 0) {
633 		p_err("can't open cgroup %s", argv[0]);
634 		goto exit;
635 	}
636 
637 	attach_type = parse_attach_type(argv[1]);
638 	if (attach_type == __MAX_BPF_ATTACH_TYPE) {
639 		p_err("invalid attach type");
640 		goto exit_cgroup;
641 	}
642 
643 	argc -= 2;
644 	argv = &argv[2];
645 	prog_fd = prog_parse_fd(&argc, &argv);
646 	if (prog_fd < 0)
647 		goto exit_cgroup;
648 
649 	if (bpf_prog_detach2(prog_fd, cgroup_fd, attach_type)) {
650 		p_err("failed to detach program");
651 		goto exit_prog;
652 	}
653 
654 	if (json_output)
655 		jsonw_null(json_wtr);
656 
657 	ret = 0;
658 
659 exit_prog:
660 	close(prog_fd);
661 exit_cgroup:
662 	close(cgroup_fd);
663 exit:
664 	return ret;
665 }
666 
667 static int do_help(int argc, char **argv)
668 {
669 	if (json_output) {
670 		jsonw_null(json_wtr);
671 		return 0;
672 	}
673 
674 	fprintf(stderr,
675 		"Usage: %1$s %2$s { show | list } CGROUP [**effective**]\n"
676 		"       %1$s %2$s tree [CGROUP_ROOT] [**effective**]\n"
677 		"       %1$s %2$s attach CGROUP ATTACH_TYPE PROG [ATTACH_FLAGS]\n"
678 		"       %1$s %2$s detach CGROUP ATTACH_TYPE PROG\n"
679 		"       %1$s %2$s help\n"
680 		"\n"
681 		HELP_SPEC_ATTACH_TYPES "\n"
682 		"       " HELP_SPEC_ATTACH_FLAGS "\n"
683 		"       " HELP_SPEC_PROGRAM "\n"
684 		"       " HELP_SPEC_OPTIONS " |\n"
685 		"                    {-f|--bpffs} }\n"
686 		"",
687 		bin_name, argv[-2]);
688 
689 	return 0;
690 }
691 
692 static const struct cmd cmds[] = {
693 	{ "show",	do_show },
694 	{ "list",	do_show },
695 	{ "tree",       do_show_tree },
696 	{ "attach",	do_attach },
697 	{ "detach",	do_detach },
698 	{ "help",	do_help },
699 	{ 0 }
700 };
701 
702 int do_cgroup(int argc, char **argv)
703 {
704 	return cmd_select(cmds, argc, argv, do_help);
705 }
706