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