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