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