xref: /linux/tools/testing/selftests/bpf/prog_tests/libarena.c (revision 42998f819256ef272b6a445310e2b64a3729a139)
1 // SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
2 /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
3 #include <test_progs.h>
4 #include <unistd.h>
5 
6 #include <libarena/common.h>
7 #include <libarena/asan.h>
8 #include <libarena/buddy.h>
9 #include <libarena/userspace.h>
10 
11 #include "libarena/libarena.skel.h"
12 
13 static void run_libarena_test(struct libarena *skel, struct bpf_program *prog,
14 		const char *name)
15 {
16 	int ret;
17 
18 	if (!strstr(name, "test_buddy")) {
19 		ret = libarena_run_prog(bpf_program__fd(skel->progs.arena_buddy_reset));
20 		if (!ASSERT_OK(ret, "arena_buddy_reset"))
21 			return;
22 	}
23 
24 	ret = libarena_run_prog(bpf_program__fd(prog));
25 
26 	ASSERT_OK(ret, name);
27 
28 }
29 
30 static void *run_libarena_parallel_prog(void *arg)
31 {
32 	struct bpf_program *prog = arg;
33 
34 	return (void *)(long)libarena_run_prog(bpf_program__fd(prog));
35 }
36 
37 /* Max suffix is ceil((lg 2^32) / (lg 10)) + sizeof("__") = 10 + 2 = 12. */
38 #define MAX_PARTEST_SUFFIX (12)
39 #define MAX_PARTEST_NAME (1024)
40 #define MAX_PARTEST_PREFIX (MAX_PARTEST_NAME - MAX_PARTEST_SUFFIX)
41 
42 static int run_libarena_parallel_fini(struct libarena *skel, const char *name,
43 				      size_t prefixlen)
44 {
45 	char tdname[MAX_PARTEST_NAME];
46 	struct bpf_program *fini_prog;
47 	int ret;
48 
49 	ret = snprintf(tdname, sizeof(tdname), "%.*s__fini", (int)prefixlen, name);
50 	if (!ASSERT_LT(ret, sizeof(tdname), "partest fini name"))
51 		return -ENAMETOOLONG;
52 
53 	fini_prog = bpf_object__find_program_by_name(skel->obj, tdname);
54 	if (!ASSERT_TRUE(fini_prog, "partest fini prog"))
55 		return -ENOENT;
56 
57 	ret = libarena_run_prog(bpf_program__fd(fini_prog));
58 	ASSERT_OK(ret, tdname);
59 
60 	return ret;
61 }
62 
63 static int run_libarena_parallel_test_workers(struct libarena *skel,
64 		const char *name, size_t prefixlen)
65 {
66 	pthread_t *threads = NULL, *tmp_threads;
67 	char tdname[MAX_PARTEST_NAME];
68 	struct bpf_program *tdprog;
69 	uint32_t nthreads;
70 	void *thread_ret;
71 	int ret, err = 0;
72 	int i;
73 
74 	for (nthreads = 0; nthreads < UINT_MAX; nthreads++) {
75 		ret = snprintf(tdname, sizeof(tdname), "%.*s__%u", (int)prefixlen,
76 			       name, nthreads);
77 		if (!ASSERT_LT(ret, sizeof(tdname), "test worker name")) {
78 			err = -ENAMETOOLONG;
79 			break;
80 		}
81 
82 		/*
83 		 * We enumerate the worker threads for a given test with __0, __1,
84 		 * and so on. The suffixes always start from 0 and are contiguous,
85 		 * so if we don't find a program with the requested name we have
86 		 * discovered all available worker programs.
87 		 */
88 		tdprog = bpf_object__find_program_by_name(skel->obj, tdname);
89 		if (!tdprog)
90 			break;
91 
92 		/* Bump the alloc array to accommodate the new thread. */
93 		tmp_threads = realloc(threads, (nthreads + 1) * sizeof(*threads));
94 		if (!ASSERT_TRUE(tmp_threads, "realloc")) {
95 			err = -ENOMEM;
96 			break;
97 		}
98 		threads = tmp_threads;
99 
100 		ret = pthread_create(&threads[nthreads], NULL,
101 				     run_libarena_parallel_prog,
102 				     tdprog);
103 		if (!ASSERT_OK(ret, "pthread_create")) {
104 			err = ret;
105 			break;
106 		}
107 	}
108 
109 
110 	for (i = 0; i < nthreads; i++) {
111 		ret = pthread_join(threads[i], &thread_ret);
112 		if (!ASSERT_OK(ret, "pthread_join")) {
113 			err = err ?: ret;
114 			continue;
115 		}
116 
117 		err = err ?: (long)thread_ret;
118 	}
119 
120 	free(threads);
121 
122 	return err;
123 }
124 
125 static bool libarena_parallel_test_enabled(struct libarena *skel,
126 					   const char *prefix,
127 					   size_t prefixlen)
128 {
129 	struct bpf_program *prog;
130 	char progname[MAX_PARTEST_NAME];
131 	int ret;
132 
133 	ret = snprintf(progname, sizeof(progname), "%.*s__enabled", (int)prefixlen,
134 		       prefix);
135 	if (!ASSERT_LT(ret, sizeof(progname), "partest enabled name"))
136 		return false;
137 
138 	prog = bpf_object__find_program_by_name(skel->obj, progname);
139 	if (!prog)
140 		return true;
141 
142 	ret = libarena_run_prog(bpf_program__fd(prog));
143 	if (ret == -EOPNOTSUPP)
144 		return false;
145 	if (!ASSERT_OK(ret, progname))
146 		return false;
147 	return true;
148 }
149 
150 static void run_libarena_parallel_test(struct libarena *skel, struct bpf_program *prog,
151 		const char *name)
152 {
153 	char testname[MAX_PARTEST_NAME];
154 	size_t prefixlen;
155 	const char *pos;
156 	int ret;
157 
158 	/*
159 	 * We annotate the initialization prog with __init. If the current prog does
160 	 * not match, it is one of the parallel threads instead and is ignored.
161 	 *
162 	 * We assume the test writer knows what they are doing and do not add __init
163 	 * randomly in the middle of a test name.
164 	 */
165 	pos = strstr(name, "__init");
166 	if (!pos)
167 		return;
168 
169 	prefixlen = pos - name;
170 	if (!ASSERT_LT(prefixlen, MAX_PARTEST_PREFIX, "partest prefix too long"))
171 		return;
172 
173 	/* The name of the test without the __init suffix. Looks nicer in the test log. */
174 	ret = snprintf(testname, sizeof(testname), "%.*s", (int)prefixlen, name);
175 	if (!ASSERT_LT(ret, sizeof(testname), "partest test name"))
176 		return;
177 
178 	if (!test__start_subtest(testname))
179 		return;
180 
181 	if (!libarena_parallel_test_enabled(skel, testname, prefixlen)) {
182 		test__skip();
183 		return;
184 	}
185 
186 	ret = libarena_run_prog(bpf_program__fd(skel->progs.arena_buddy_reset));
187 	if (!ASSERT_OK(ret, "arena_buddy_reset"))
188 		return;
189 
190 	ret = libarena_run_prog(bpf_program__fd(prog));
191 	if (!ASSERT_OK(ret, testname))
192 		return;
193 
194 	ret = run_libarena_parallel_test_workers(skel, name, prefixlen);
195 
196 	ASSERT_OK(ret, testname);
197 
198 	run_libarena_parallel_fini(skel, name, prefixlen);
199 }
200 
201 void test_libarena(void)
202 {
203 	struct arena_alloc_reserve_args args;
204 	struct libarena *skel;
205 	struct bpf_program *prog;
206 	int ret;
207 
208 	skel = libarena__open_and_load();
209 	if (!ASSERT_OK_PTR(skel, "open_and_load"))
210 		return;
211 
212 	ret = libarena__attach(skel);
213 	if (!ASSERT_OK(ret, "attach"))
214 		goto out;
215 
216 	args.nr_pages = ARENA_RESERVE_PAGES_DFL;
217 
218 	ret = libarena_run_prog_args(bpf_program__fd(skel->progs.arena_alloc_reserve),
219 			&args, sizeof(args));
220 	if (!ASSERT_OK(ret, "arena_alloc_reserve"))
221 		goto out;
222 
223 	bpf_object__for_each_program(prog, skel->obj) {
224 		const char *name = bpf_program__name(prog);
225 
226 		/*
227 		 * Handle parallel test progs separately. For those
228 		 * progs it's not a matter of test/skip, because each
229 		 * parallel test prog includes an initialization prog
230 		 * and a set of progs to be run in parallel. For the
231 		 * latter we do not record them as skipped or run,
232 		 * because we run them all at once when we come across
233 		 * the initialization prog. For more details on how we
234 		 * discover the progs see the comment on
235 		 * run_libarena_parallel_test.
236 		 */
237 		if (libarena_is_parallel_test_prog(name)) {
238 			run_libarena_parallel_test(skel, prog, name);
239 			continue;
240 		}
241 
242 		if (!libarena_is_test_prog(name))
243 			continue;
244 
245 		if (!test__start_subtest(name))
246 			continue;
247 
248 		run_libarena_test(skel, prog, name);
249 	}
250 
251 out:
252 	libarena__destroy(skel);
253 }
254