1fe0dd9d4SHao Luo // SPDX-License-Identifier: GPL-2.0
2fe0dd9d4SHao Luo /* Copyright (c) 2022 Google */
3fe0dd9d4SHao Luo 
4fe0dd9d4SHao Luo #include <test_progs.h>
5fe0dd9d4SHao Luo #include <bpf/libbpf.h>
6fe0dd9d4SHao Luo #include <bpf/btf.h>
7fe0dd9d4SHao Luo #include "cgroup_iter.skel.h"
8fe0dd9d4SHao Luo #include "cgroup_helpers.h"
9fe0dd9d4SHao Luo 
10fe0dd9d4SHao Luo #define ROOT           0
11fe0dd9d4SHao Luo #define PARENT         1
12fe0dd9d4SHao Luo #define CHILD1         2
13fe0dd9d4SHao Luo #define CHILD2         3
14fe0dd9d4SHao Luo #define NUM_CGROUPS    4
15fe0dd9d4SHao Luo 
16fe0dd9d4SHao Luo #define PROLOGUE       "prologue\n"
17fe0dd9d4SHao Luo #define EPILOGUE       "epilogue\n"
18fe0dd9d4SHao Luo 
19fe0dd9d4SHao Luo static const char *cg_path[] = {
20fe0dd9d4SHao Luo 	"/", "/parent", "/parent/child1", "/parent/child2"
21fe0dd9d4SHao Luo };
22fe0dd9d4SHao Luo 
23fe0dd9d4SHao Luo static int cg_fd[] = {-1, -1, -1, -1};
24fe0dd9d4SHao Luo static unsigned long long cg_id[] = {0, 0, 0, 0};
25fe0dd9d4SHao Luo static char expected_output[64];
26fe0dd9d4SHao Luo 
setup_cgroups(void)27fe0dd9d4SHao Luo static int setup_cgroups(void)
28fe0dd9d4SHao Luo {
29fe0dd9d4SHao Luo 	int fd, i = 0;
30fe0dd9d4SHao Luo 
31fe0dd9d4SHao Luo 	for (i = 0; i < NUM_CGROUPS; i++) {
32fe0dd9d4SHao Luo 		fd = create_and_get_cgroup(cg_path[i]);
33fe0dd9d4SHao Luo 		if (fd < 0)
34fe0dd9d4SHao Luo 			return fd;
35fe0dd9d4SHao Luo 
36fe0dd9d4SHao Luo 		cg_fd[i] = fd;
37fe0dd9d4SHao Luo 		cg_id[i] = get_cgroup_id(cg_path[i]);
38fe0dd9d4SHao Luo 	}
39fe0dd9d4SHao Luo 	return 0;
40fe0dd9d4SHao Luo }
41fe0dd9d4SHao Luo 
cleanup_cgroups(void)42fe0dd9d4SHao Luo static void cleanup_cgroups(void)
43fe0dd9d4SHao Luo {
44fe0dd9d4SHao Luo 	int i;
45fe0dd9d4SHao Luo 
46fe0dd9d4SHao Luo 	for (i = 0; i < NUM_CGROUPS; i++)
47fe0dd9d4SHao Luo 		close(cg_fd[i]);
48fe0dd9d4SHao Luo }
49fe0dd9d4SHao Luo 
read_from_cgroup_iter(struct bpf_program * prog,int cgroup_fd,int order,const char * testname)50fe0dd9d4SHao Luo static void read_from_cgroup_iter(struct bpf_program *prog, int cgroup_fd,
51fe0dd9d4SHao Luo 				  int order, const char *testname)
52fe0dd9d4SHao Luo {
53fe0dd9d4SHao Luo 	DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
54fe0dd9d4SHao Luo 	union bpf_iter_link_info linfo;
55fe0dd9d4SHao Luo 	struct bpf_link *link;
56fe0dd9d4SHao Luo 	int len, iter_fd;
57fe0dd9d4SHao Luo 	static char buf[128];
58fe0dd9d4SHao Luo 	size_t left;
59fe0dd9d4SHao Luo 	char *p;
60fe0dd9d4SHao Luo 
61fe0dd9d4SHao Luo 	memset(&linfo, 0, sizeof(linfo));
62fe0dd9d4SHao Luo 	linfo.cgroup.cgroup_fd = cgroup_fd;
63fe0dd9d4SHao Luo 	linfo.cgroup.order = order;
64fe0dd9d4SHao Luo 	opts.link_info = &linfo;
65fe0dd9d4SHao Luo 	opts.link_info_len = sizeof(linfo);
66fe0dd9d4SHao Luo 
67fe0dd9d4SHao Luo 	link = bpf_program__attach_iter(prog, &opts);
68fe0dd9d4SHao Luo 	if (!ASSERT_OK_PTR(link, "attach_iter"))
69fe0dd9d4SHao Luo 		return;
70fe0dd9d4SHao Luo 
71fe0dd9d4SHao Luo 	iter_fd = bpf_iter_create(bpf_link__fd(link));
72fe0dd9d4SHao Luo 	if (iter_fd < 0)
73fe0dd9d4SHao Luo 		goto free_link;
74fe0dd9d4SHao Luo 
75fe0dd9d4SHao Luo 	memset(buf, 0, sizeof(buf));
76fe0dd9d4SHao Luo 	left = ARRAY_SIZE(buf);
77fe0dd9d4SHao Luo 	p = buf;
78fe0dd9d4SHao Luo 	while ((len = read(iter_fd, p, left)) > 0) {
79fe0dd9d4SHao Luo 		p += len;
80fe0dd9d4SHao Luo 		left -= len;
81fe0dd9d4SHao Luo 	}
82fe0dd9d4SHao Luo 
83fe0dd9d4SHao Luo 	ASSERT_STREQ(buf, expected_output, testname);
84fe0dd9d4SHao Luo 
85fe0dd9d4SHao Luo 	/* read() after iter finishes should be ok. */
86fe0dd9d4SHao Luo 	if (len == 0)
87fe0dd9d4SHao Luo 		ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read");
88fe0dd9d4SHao Luo 
89fe0dd9d4SHao Luo 	close(iter_fd);
90fe0dd9d4SHao Luo free_link:
91fe0dd9d4SHao Luo 	bpf_link__destroy(link);
92fe0dd9d4SHao Luo }
93fe0dd9d4SHao Luo 
94fe0dd9d4SHao Luo /* Invalid cgroup. */
test_invalid_cgroup(struct cgroup_iter * skel)95fe0dd9d4SHao Luo static void test_invalid_cgroup(struct cgroup_iter *skel)
96fe0dd9d4SHao Luo {
97fe0dd9d4SHao Luo 	DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
98fe0dd9d4SHao Luo 	union bpf_iter_link_info linfo;
99fe0dd9d4SHao Luo 	struct bpf_link *link;
100fe0dd9d4SHao Luo 
101fe0dd9d4SHao Luo 	memset(&linfo, 0, sizeof(linfo));
102fe0dd9d4SHao Luo 	linfo.cgroup.cgroup_fd = (__u32)-1;
103fe0dd9d4SHao Luo 	opts.link_info = &linfo;
104fe0dd9d4SHao Luo 	opts.link_info_len = sizeof(linfo);
105fe0dd9d4SHao Luo 
106fe0dd9d4SHao Luo 	link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
107fe0dd9d4SHao Luo 	ASSERT_ERR_PTR(link, "attach_iter");
108fe0dd9d4SHao Luo 	bpf_link__destroy(link);
109fe0dd9d4SHao Luo }
110fe0dd9d4SHao Luo 
111fe0dd9d4SHao Luo /* Specifying both cgroup_fd and cgroup_id is invalid. */
test_invalid_cgroup_spec(struct cgroup_iter * skel)112fe0dd9d4SHao Luo static void test_invalid_cgroup_spec(struct cgroup_iter *skel)
113fe0dd9d4SHao Luo {
114fe0dd9d4SHao Luo 	DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
115fe0dd9d4SHao Luo 	union bpf_iter_link_info linfo;
116fe0dd9d4SHao Luo 	struct bpf_link *link;
117fe0dd9d4SHao Luo 
118fe0dd9d4SHao Luo 	memset(&linfo, 0, sizeof(linfo));
119fe0dd9d4SHao Luo 	linfo.cgroup.cgroup_fd = (__u32)cg_fd[PARENT];
120fe0dd9d4SHao Luo 	linfo.cgroup.cgroup_id = (__u64)cg_id[PARENT];
121fe0dd9d4SHao Luo 	opts.link_info = &linfo;
122fe0dd9d4SHao Luo 	opts.link_info_len = sizeof(linfo);
123fe0dd9d4SHao Luo 
124fe0dd9d4SHao Luo 	link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
125fe0dd9d4SHao Luo 	ASSERT_ERR_PTR(link, "attach_iter");
126fe0dd9d4SHao Luo 	bpf_link__destroy(link);
127fe0dd9d4SHao Luo }
128fe0dd9d4SHao Luo 
129fe0dd9d4SHao Luo /* Preorder walk prints parent and child in order. */
test_walk_preorder(struct cgroup_iter * skel)130fe0dd9d4SHao Luo static void test_walk_preorder(struct cgroup_iter *skel)
131fe0dd9d4SHao Luo {
132fe0dd9d4SHao Luo 	snprintf(expected_output, sizeof(expected_output),
133fe0dd9d4SHao Luo 		 PROLOGUE "%8llu\n%8llu\n%8llu\n" EPILOGUE,
134fe0dd9d4SHao Luo 		 cg_id[PARENT], cg_id[CHILD1], cg_id[CHILD2]);
135fe0dd9d4SHao Luo 
136fe0dd9d4SHao Luo 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
137d4ffb6f3SHao Luo 			      BPF_CGROUP_ITER_DESCENDANTS_PRE, "preorder");
138fe0dd9d4SHao Luo }
139fe0dd9d4SHao Luo 
140fe0dd9d4SHao Luo /* Postorder walk prints child and parent in order. */
test_walk_postorder(struct cgroup_iter * skel)141fe0dd9d4SHao Luo static void test_walk_postorder(struct cgroup_iter *skel)
142fe0dd9d4SHao Luo {
143fe0dd9d4SHao Luo 	snprintf(expected_output, sizeof(expected_output),
144fe0dd9d4SHao Luo 		 PROLOGUE "%8llu\n%8llu\n%8llu\n" EPILOGUE,
145fe0dd9d4SHao Luo 		 cg_id[CHILD1], cg_id[CHILD2], cg_id[PARENT]);
146fe0dd9d4SHao Luo 
147fe0dd9d4SHao Luo 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
148d4ffb6f3SHao Luo 			      BPF_CGROUP_ITER_DESCENDANTS_POST, "postorder");
149fe0dd9d4SHao Luo }
150fe0dd9d4SHao Luo 
151fe0dd9d4SHao Luo /* Walking parents prints parent and then root. */
test_walk_ancestors_up(struct cgroup_iter * skel)152fe0dd9d4SHao Luo static void test_walk_ancestors_up(struct cgroup_iter *skel)
153fe0dd9d4SHao Luo {
154fe0dd9d4SHao Luo 	/* terminate the walk when ROOT is met. */
155fe0dd9d4SHao Luo 	skel->bss->terminal_cgroup = cg_id[ROOT];
156fe0dd9d4SHao Luo 
157fe0dd9d4SHao Luo 	snprintf(expected_output, sizeof(expected_output),
158fe0dd9d4SHao Luo 		 PROLOGUE "%8llu\n%8llu\n" EPILOGUE,
159fe0dd9d4SHao Luo 		 cg_id[PARENT], cg_id[ROOT]);
160fe0dd9d4SHao Luo 
161fe0dd9d4SHao Luo 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
162d4ffb6f3SHao Luo 			      BPF_CGROUP_ITER_ANCESTORS_UP, "ancestors_up");
163fe0dd9d4SHao Luo 
164fe0dd9d4SHao Luo 	skel->bss->terminal_cgroup = 0;
165fe0dd9d4SHao Luo }
166fe0dd9d4SHao Luo 
167fe0dd9d4SHao Luo /* Early termination prints parent only. */
test_early_termination(struct cgroup_iter * skel)168fe0dd9d4SHao Luo static void test_early_termination(struct cgroup_iter *skel)
169fe0dd9d4SHao Luo {
170fe0dd9d4SHao Luo 	/* terminate the walk after the first element is processed. */
171fe0dd9d4SHao Luo 	skel->bss->terminate_early = 1;
172fe0dd9d4SHao Luo 
173fe0dd9d4SHao Luo 	snprintf(expected_output, sizeof(expected_output),
174fe0dd9d4SHao Luo 		 PROLOGUE "%8llu\n" EPILOGUE, cg_id[PARENT]);
175fe0dd9d4SHao Luo 
176fe0dd9d4SHao Luo 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
177d4ffb6f3SHao Luo 			      BPF_CGROUP_ITER_DESCENDANTS_PRE, "early_termination");
178fe0dd9d4SHao Luo 
179fe0dd9d4SHao Luo 	skel->bss->terminate_early = 0;
180fe0dd9d4SHao Luo }
181fe0dd9d4SHao Luo 
182fe0dd9d4SHao Luo /* Waling self prints self only. */
test_walk_self_only(struct cgroup_iter * skel)183fe0dd9d4SHao Luo static void test_walk_self_only(struct cgroup_iter *skel)
184fe0dd9d4SHao Luo {
185fe0dd9d4SHao Luo 	snprintf(expected_output, sizeof(expected_output),
186fe0dd9d4SHao Luo 		 PROLOGUE "%8llu\n" EPILOGUE, cg_id[PARENT]);
187fe0dd9d4SHao Luo 
188fe0dd9d4SHao Luo 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
189d4ffb6f3SHao Luo 			      BPF_CGROUP_ITER_SELF_ONLY, "self_only");
190fe0dd9d4SHao Luo }
191fe0dd9d4SHao Luo 
test_walk_dead_self_only(struct cgroup_iter * skel)192*8589e926SHou Tao static void test_walk_dead_self_only(struct cgroup_iter *skel)
193*8589e926SHou Tao {
194*8589e926SHou Tao 	DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
195*8589e926SHou Tao 	char expected_output[128], buf[128];
196*8589e926SHou Tao 	const char *cgrp_name = "/dead";
197*8589e926SHou Tao 	union bpf_iter_link_info linfo;
198*8589e926SHou Tao 	int len, cgrp_fd, iter_fd;
199*8589e926SHou Tao 	struct bpf_link *link;
200*8589e926SHou Tao 	size_t left;
201*8589e926SHou Tao 	char *p;
202*8589e926SHou Tao 
203*8589e926SHou Tao 	cgrp_fd = create_and_get_cgroup(cgrp_name);
204*8589e926SHou Tao 	if (!ASSERT_GE(cgrp_fd, 0, "create cgrp"))
205*8589e926SHou Tao 		return;
206*8589e926SHou Tao 
207*8589e926SHou Tao 	/* The cgroup will be dead during read() iteration, so it only has
208*8589e926SHou Tao 	 * epilogue in the output
209*8589e926SHou Tao 	 */
210*8589e926SHou Tao 	snprintf(expected_output, sizeof(expected_output), EPILOGUE);
211*8589e926SHou Tao 
212*8589e926SHou Tao 	memset(&linfo, 0, sizeof(linfo));
213*8589e926SHou Tao 	linfo.cgroup.cgroup_fd = cgrp_fd;
214*8589e926SHou Tao 	linfo.cgroup.order = BPF_CGROUP_ITER_SELF_ONLY;
215*8589e926SHou Tao 	opts.link_info = &linfo;
216*8589e926SHou Tao 	opts.link_info_len = sizeof(linfo);
217*8589e926SHou Tao 
218*8589e926SHou Tao 	link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
219*8589e926SHou Tao 	if (!ASSERT_OK_PTR(link, "attach_iter"))
220*8589e926SHou Tao 		goto close_cgrp;
221*8589e926SHou Tao 
222*8589e926SHou Tao 	iter_fd = bpf_iter_create(bpf_link__fd(link));
223*8589e926SHou Tao 	if (!ASSERT_GE(iter_fd, 0, "iter_create"))
224*8589e926SHou Tao 		goto free_link;
225*8589e926SHou Tao 
226*8589e926SHou Tao 	/* Close link fd and cgroup fd */
227*8589e926SHou Tao 	bpf_link__destroy(link);
228*8589e926SHou Tao 	close(cgrp_fd);
229*8589e926SHou Tao 
230*8589e926SHou Tao 	/* Remove cgroup to mark it as dead */
231*8589e926SHou Tao 	remove_cgroup(cgrp_name);
232*8589e926SHou Tao 
233*8589e926SHou Tao 	/* Two kern_sync_rcu() and usleep() pairs are used to wait for the
234*8589e926SHou Tao 	 * releases of cgroup css, and the last kern_sync_rcu() and usleep()
235*8589e926SHou Tao 	 * pair is used to wait for the free of cgroup itself.
236*8589e926SHou Tao 	 */
237*8589e926SHou Tao 	kern_sync_rcu();
238*8589e926SHou Tao 	usleep(8000);
239*8589e926SHou Tao 	kern_sync_rcu();
240*8589e926SHou Tao 	usleep(8000);
241*8589e926SHou Tao 	kern_sync_rcu();
242*8589e926SHou Tao 	usleep(1000);
243*8589e926SHou Tao 
244*8589e926SHou Tao 	memset(buf, 0, sizeof(buf));
245*8589e926SHou Tao 	left = ARRAY_SIZE(buf);
246*8589e926SHou Tao 	p = buf;
247*8589e926SHou Tao 	while ((len = read(iter_fd, p, left)) > 0) {
248*8589e926SHou Tao 		p += len;
249*8589e926SHou Tao 		left -= len;
250*8589e926SHou Tao 	}
251*8589e926SHou Tao 
252*8589e926SHou Tao 	ASSERT_STREQ(buf, expected_output, "dead cgroup output");
253*8589e926SHou Tao 
254*8589e926SHou Tao 	/* read() after iter finishes should be ok. */
255*8589e926SHou Tao 	if (len == 0)
256*8589e926SHou Tao 		ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read");
257*8589e926SHou Tao 
258*8589e926SHou Tao 	close(iter_fd);
259*8589e926SHou Tao 	return;
260*8589e926SHou Tao free_link:
261*8589e926SHou Tao 	bpf_link__destroy(link);
262*8589e926SHou Tao close_cgrp:
263*8589e926SHou Tao 	close(cgrp_fd);
264*8589e926SHou Tao }
265*8589e926SHou Tao 
test_cgroup_iter(void)266fe0dd9d4SHao Luo void test_cgroup_iter(void)
267fe0dd9d4SHao Luo {
268fe0dd9d4SHao Luo 	struct cgroup_iter *skel = NULL;
269fe0dd9d4SHao Luo 
270fe0dd9d4SHao Luo 	if (setup_cgroup_environment())
271fe0dd9d4SHao Luo 		return;
272fe0dd9d4SHao Luo 
273fe0dd9d4SHao Luo 	if (setup_cgroups())
274fe0dd9d4SHao Luo 		goto out;
275fe0dd9d4SHao Luo 
276fe0dd9d4SHao Luo 	skel = cgroup_iter__open_and_load();
277fe0dd9d4SHao Luo 	if (!ASSERT_OK_PTR(skel, "cgroup_iter__open_and_load"))
278fe0dd9d4SHao Luo 		goto out;
279fe0dd9d4SHao Luo 
280fe0dd9d4SHao Luo 	if (test__start_subtest("cgroup_iter__invalid_cgroup"))
281fe0dd9d4SHao Luo 		test_invalid_cgroup(skel);
282fe0dd9d4SHao Luo 	if (test__start_subtest("cgroup_iter__invalid_cgroup_spec"))
283fe0dd9d4SHao Luo 		test_invalid_cgroup_spec(skel);
284fe0dd9d4SHao Luo 	if (test__start_subtest("cgroup_iter__preorder"))
285fe0dd9d4SHao Luo 		test_walk_preorder(skel);
286fe0dd9d4SHao Luo 	if (test__start_subtest("cgroup_iter__postorder"))
287fe0dd9d4SHao Luo 		test_walk_postorder(skel);
288fe0dd9d4SHao Luo 	if (test__start_subtest("cgroup_iter__ancestors_up_walk"))
289fe0dd9d4SHao Luo 		test_walk_ancestors_up(skel);
290fe0dd9d4SHao Luo 	if (test__start_subtest("cgroup_iter__early_termination"))
291fe0dd9d4SHao Luo 		test_early_termination(skel);
292fe0dd9d4SHao Luo 	if (test__start_subtest("cgroup_iter__self_only"))
293fe0dd9d4SHao Luo 		test_walk_self_only(skel);
294*8589e926SHou Tao 	if (test__start_subtest("cgroup_iter__dead_self_only"))
295*8589e926SHou Tao 		test_walk_dead_self_only(skel);
296fe0dd9d4SHao Luo out:
297fe0dd9d4SHao Luo 	cgroup_iter__destroy(skel);
298fe0dd9d4SHao Luo 	cleanup_cgroups();
299fe0dd9d4SHao Luo 	cleanup_cgroup_environment();
300fe0dd9d4SHao Luo }
301