1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * User Events ABI Test Program |
4 | * |
5 | * Copyright (c) 2022 Beau Belgrave <beaub@linux.microsoft.com> |
6 | */ |
7 | |
8 | #define _GNU_SOURCE |
9 | #include <sched.h> |
10 | |
11 | #include <errno.h> |
12 | #include <linux/user_events.h> |
13 | #include <stdio.h> |
14 | #include <stdlib.h> |
15 | #include <fcntl.h> |
16 | #include <sys/ioctl.h> |
17 | #include <sys/stat.h> |
18 | #include <unistd.h> |
19 | #include <glob.h> |
20 | #include <string.h> |
21 | #include <asm/unistd.h> |
22 | |
23 | #include "../kselftest_harness.h" |
24 | #include "user_events_selftests.h" |
25 | |
26 | const char *data_file = "/sys/kernel/tracing/user_events_data" ; |
27 | const char *enable_file = "/sys/kernel/tracing/events/user_events/__abi_event/enable" ; |
28 | const char *multi_dir_glob = "/sys/kernel/tracing/events/user_events_multi/__abi_event.*" ; |
29 | |
30 | static int wait_for_delete(char *dir) |
31 | { |
32 | struct stat buf; |
33 | int i; |
34 | |
35 | for (i = 0; i < 10000; ++i) { |
36 | if (stat(dir, &buf) == -1 && errno == ENOENT) |
37 | return 0; |
38 | |
39 | usleep(1000); |
40 | } |
41 | |
42 | return -1; |
43 | } |
44 | |
45 | static int find_multi_event_dir(char *unique_field, char *out_dir, int dir_len) |
46 | { |
47 | char path[256]; |
48 | glob_t buf; |
49 | int i, ret; |
50 | |
51 | ret = glob(multi_dir_glob, GLOB_ONLYDIR, NULL, &buf); |
52 | |
53 | if (ret) |
54 | return -1; |
55 | |
56 | ret = -1; |
57 | |
58 | for (i = 0; i < buf.gl_pathc; ++i) { |
59 | FILE *fp; |
60 | |
61 | snprintf(path, sizeof(path), "%s/format" , buf.gl_pathv[i]); |
62 | fp = fopen(path, "r" ); |
63 | |
64 | if (!fp) |
65 | continue; |
66 | |
67 | while (fgets(path, sizeof(path), fp) != NULL) { |
68 | if (strstr(path, unique_field)) { |
69 | fclose(fp); |
70 | /* strscpy is not available, use snprintf */ |
71 | snprintf(out_dir, dir_len, "%s" , buf.gl_pathv[i]); |
72 | ret = 0; |
73 | goto out; |
74 | } |
75 | } |
76 | |
77 | fclose(fp); |
78 | } |
79 | out: |
80 | globfree(&buf); |
81 | |
82 | return ret; |
83 | } |
84 | |
85 | static bool event_exists(void) |
86 | { |
87 | int fd = open(enable_file, O_RDWR); |
88 | |
89 | if (fd < 0) |
90 | return false; |
91 | |
92 | close(fd); |
93 | |
94 | return true; |
95 | } |
96 | |
97 | static int change_event(bool enable) |
98 | { |
99 | int fd = open(enable_file, O_RDWR); |
100 | int ret; |
101 | |
102 | if (fd < 0) |
103 | return -1; |
104 | |
105 | if (enable) |
106 | ret = write(fd, "1" , 1); |
107 | else |
108 | ret = write(fd, "0" , 1); |
109 | |
110 | close(fd); |
111 | |
112 | if (ret == 1) |
113 | ret = 0; |
114 | else |
115 | ret = -1; |
116 | |
117 | return ret; |
118 | } |
119 | |
120 | static int event_delete(void) |
121 | { |
122 | int fd = open(data_file, O_RDWR); |
123 | int ret; |
124 | |
125 | if (fd < 0) |
126 | return -1; |
127 | |
128 | ret = ioctl(fd, DIAG_IOCSDEL, "__abi_event" ); |
129 | |
130 | close(fd); |
131 | |
132 | return ret; |
133 | } |
134 | |
135 | static int reg_enable_multi(void *enable, int size, int bit, int flags, |
136 | char *args) |
137 | { |
138 | struct user_reg reg = {0}; |
139 | char full_args[512] = {0}; |
140 | int fd = open(data_file, O_RDWR); |
141 | int len; |
142 | int ret; |
143 | |
144 | if (fd < 0) |
145 | return -1; |
146 | |
147 | len = snprintf(buf: full_args, size: sizeof(full_args), fmt: "__abi_event %s" , args); |
148 | |
149 | if (len > sizeof(full_args)) { |
150 | ret = -E2BIG; |
151 | goto out; |
152 | } |
153 | |
154 | reg.size = sizeof(reg); |
155 | reg.name_args = (__u64)full_args; |
156 | reg.flags = USER_EVENT_REG_MULTI_FORMAT | flags; |
157 | reg.enable_bit = bit; |
158 | reg.enable_addr = (__u64)enable; |
159 | reg.enable_size = size; |
160 | |
161 | ret = ioctl(fd, DIAG_IOCSREG, ®); |
162 | out: |
163 | close(fd); |
164 | |
165 | return ret; |
166 | } |
167 | |
168 | static int reg_enable_flags(void *enable, int size, int bit, int flags) |
169 | { |
170 | struct user_reg reg = {0}; |
171 | int fd = open(data_file, O_RDWR); |
172 | int ret; |
173 | |
174 | if (fd < 0) |
175 | return -1; |
176 | |
177 | reg.size = sizeof(reg); |
178 | reg.name_args = (__u64)"__abi_event" ; |
179 | reg.flags = flags; |
180 | reg.enable_bit = bit; |
181 | reg.enable_addr = (__u64)enable; |
182 | reg.enable_size = size; |
183 | |
184 | ret = ioctl(fd, DIAG_IOCSREG, ®); |
185 | |
186 | close(fd); |
187 | |
188 | return ret; |
189 | } |
190 | |
191 | static int reg_enable(void *enable, int size, int bit) |
192 | { |
193 | return reg_enable_flags(enable, size, bit, flags: 0); |
194 | } |
195 | |
196 | static int reg_disable(void *enable, int bit) |
197 | { |
198 | struct user_unreg reg = {0}; |
199 | int fd = open(data_file, O_RDWR); |
200 | int ret; |
201 | |
202 | if (fd < 0) |
203 | return -1; |
204 | |
205 | reg.size = sizeof(reg); |
206 | reg.disable_bit = bit; |
207 | reg.disable_addr = (__u64)enable; |
208 | |
209 | ret = ioctl(fd, DIAG_IOCSUNREG, ®); |
210 | |
211 | close(fd); |
212 | |
213 | return ret; |
214 | } |
215 | |
216 | FIXTURE(user) { |
217 | int check; |
218 | long check_long; |
219 | bool umount; |
220 | }; |
221 | |
222 | FIXTURE_SETUP(user) { |
223 | USER_EVENT_FIXTURE_SETUP(return, self->umount); |
224 | |
225 | change_event(enable: false); |
226 | self->check = 0; |
227 | self->check_long = 0; |
228 | } |
229 | |
230 | FIXTURE_TEARDOWN(user) { |
231 | USER_EVENT_FIXTURE_TEARDOWN(self->umount); |
232 | } |
233 | |
234 | TEST_F(user, enablement) { |
235 | /* Changes should be reflected immediately */ |
236 | ASSERT_EQ(0, self->check); |
237 | ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); |
238 | ASSERT_EQ(0, change_event(true)); |
239 | ASSERT_EQ(1, self->check); |
240 | ASSERT_EQ(0, change_event(false)); |
241 | ASSERT_EQ(0, self->check); |
242 | |
243 | /* Ensure kernel clears bit after disable */ |
244 | ASSERT_EQ(0, change_event(true)); |
245 | ASSERT_EQ(1, self->check); |
246 | ASSERT_EQ(0, reg_disable(&self->check, 0)); |
247 | ASSERT_EQ(0, self->check); |
248 | |
249 | /* Ensure doesn't change after unreg */ |
250 | ASSERT_EQ(0, change_event(true)); |
251 | ASSERT_EQ(0, self->check); |
252 | ASSERT_EQ(0, change_event(false)); |
253 | } |
254 | |
255 | TEST_F(user, flags) { |
256 | /* USER_EVENT_REG_PERSIST is allowed */ |
257 | ASSERT_EQ(0, reg_enable_flags(&self->check, sizeof(int), 0, |
258 | USER_EVENT_REG_PERSIST)); |
259 | ASSERT_EQ(0, reg_disable(&self->check, 0)); |
260 | |
261 | /* Ensure it exists after close and disable */ |
262 | ASSERT_TRUE(event_exists()); |
263 | |
264 | /* Ensure we can delete it */ |
265 | ASSERT_EQ(0, event_delete()); |
266 | |
267 | /* USER_EVENT_REG_MAX or above is not allowed */ |
268 | ASSERT_EQ(-1, reg_enable_flags(&self->check, sizeof(int), 0, |
269 | USER_EVENT_REG_MAX)); |
270 | |
271 | /* Ensure it does not exist after invalid flags */ |
272 | ASSERT_FALSE(event_exists()); |
273 | } |
274 | |
275 | TEST_F(user, bit_sizes) { |
276 | /* Allow 0-31 bits for 32-bit */ |
277 | ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); |
278 | ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 31)); |
279 | ASSERT_NE(0, reg_enable(&self->check, sizeof(int), 32)); |
280 | ASSERT_EQ(0, reg_disable(&self->check, 0)); |
281 | ASSERT_EQ(0, reg_disable(&self->check, 31)); |
282 | |
283 | #if BITS_PER_LONG == 8 |
284 | /* Allow 0-64 bits for 64-bit */ |
285 | ASSERT_EQ(0, reg_enable(&self->check_long, sizeof(long), 63)); |
286 | ASSERT_NE(0, reg_enable(&self->check_long, sizeof(long), 64)); |
287 | ASSERT_EQ(0, reg_disable(&self->check_long, 63)); |
288 | #endif |
289 | |
290 | /* Disallowed sizes (everything beside 4 and 8) */ |
291 | ASSERT_NE(0, reg_enable(&self->check, 1, 0)); |
292 | ASSERT_NE(0, reg_enable(&self->check, 2, 0)); |
293 | ASSERT_NE(0, reg_enable(&self->check, 3, 0)); |
294 | ASSERT_NE(0, reg_enable(&self->check, 5, 0)); |
295 | ASSERT_NE(0, reg_enable(&self->check, 6, 0)); |
296 | ASSERT_NE(0, reg_enable(&self->check, 7, 0)); |
297 | ASSERT_NE(0, reg_enable(&self->check, 9, 0)); |
298 | ASSERT_NE(0, reg_enable(&self->check, 128, 0)); |
299 | } |
300 | |
301 | TEST_F(user, multi_format) { |
302 | char first_dir[256]; |
303 | char second_dir[256]; |
304 | struct stat buf; |
305 | |
306 | /* Multiple formats for the same name should work */ |
307 | ASSERT_EQ(0, reg_enable_multi(&self->check, sizeof(int), 0, |
308 | 0, "u32 multi_first" )); |
309 | |
310 | ASSERT_EQ(0, reg_enable_multi(&self->check, sizeof(int), 1, |
311 | 0, "u64 multi_second" )); |
312 | |
313 | /* Same name with same format should also work */ |
314 | ASSERT_EQ(0, reg_enable_multi(&self->check, sizeof(int), 2, |
315 | 0, "u64 multi_second" )); |
316 | |
317 | ASSERT_EQ(0, find_multi_event_dir("multi_first" , |
318 | first_dir, sizeof(first_dir))); |
319 | |
320 | ASSERT_EQ(0, find_multi_event_dir("multi_second" , |
321 | second_dir, sizeof(second_dir))); |
322 | |
323 | /* Should not be found in the same dir */ |
324 | ASSERT_NE(0, strcmp(first_dir, second_dir)); |
325 | |
326 | /* First dir should still exist */ |
327 | ASSERT_EQ(0, stat(first_dir, &buf)); |
328 | |
329 | /* Disabling first register should remove first dir */ |
330 | ASSERT_EQ(0, reg_disable(&self->check, 0)); |
331 | ASSERT_EQ(0, wait_for_delete(first_dir)); |
332 | |
333 | /* Second dir should still exist */ |
334 | ASSERT_EQ(0, stat(second_dir, &buf)); |
335 | |
336 | /* Disabling second register should remove second dir */ |
337 | ASSERT_EQ(0, reg_disable(&self->check, 1)); |
338 | /* Ensure bit 1 and 2 are tied together, should not delete yet */ |
339 | ASSERT_EQ(0, stat(second_dir, &buf)); |
340 | ASSERT_EQ(0, reg_disable(&self->check, 2)); |
341 | ASSERT_EQ(0, wait_for_delete(second_dir)); |
342 | } |
343 | |
344 | TEST_F(user, forks) { |
345 | int i; |
346 | |
347 | /* Ensure COW pages get updated after fork */ |
348 | ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); |
349 | ASSERT_EQ(0, self->check); |
350 | |
351 | if (fork() == 0) { |
352 | /* Force COW */ |
353 | self->check = 0; |
354 | |
355 | /* Up to 1 sec for enablement */ |
356 | for (i = 0; i < 10; ++i) { |
357 | usleep(100000); |
358 | |
359 | if (self->check) |
360 | exit(0); |
361 | } |
362 | |
363 | exit(1); |
364 | } |
365 | |
366 | /* Allow generous time for COW, then enable */ |
367 | usleep(100000); |
368 | ASSERT_EQ(0, change_event(true)); |
369 | |
370 | ASSERT_NE(-1, wait(&i)); |
371 | ASSERT_EQ(0, WEXITSTATUS(i)); |
372 | |
373 | /* Ensure child doesn't disable parent */ |
374 | if (fork() == 0) |
375 | exit(reg_disable(enable: &self->check, bit: 0)); |
376 | |
377 | ASSERT_NE(-1, wait(&i)); |
378 | ASSERT_EQ(0, WEXITSTATUS(i)); |
379 | ASSERT_EQ(1, self->check); |
380 | ASSERT_EQ(0, change_event(false)); |
381 | ASSERT_EQ(0, self->check); |
382 | } |
383 | |
384 | /* Waits up to 1 sec for enablement */ |
385 | static int clone_check(void *check) |
386 | { |
387 | int i; |
388 | |
389 | for (i = 0; i < 10; ++i) { |
390 | usleep(100000); |
391 | |
392 | if (*(int *)check) |
393 | return 0; |
394 | } |
395 | |
396 | return 1; |
397 | } |
398 | |
399 | TEST_F(user, clones) { |
400 | int i, stack_size = 4096; |
401 | void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, |
402 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, |
403 | -1, 0); |
404 | |
405 | ASSERT_NE(MAP_FAILED, stack); |
406 | ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); |
407 | ASSERT_EQ(0, self->check); |
408 | |
409 | /* Shared VM should see enablements */ |
410 | ASSERT_NE(-1, clone(&clone_check, stack + stack_size, |
411 | CLONE_VM | SIGCHLD, &self->check)); |
412 | |
413 | ASSERT_EQ(0, change_event(true)); |
414 | ASSERT_NE(-1, wait(&i)); |
415 | ASSERT_EQ(0, WEXITSTATUS(i)); |
416 | munmap(stack, stack_size); |
417 | ASSERT_EQ(0, change_event(false)); |
418 | } |
419 | |
420 | int main(int argc, char **argv) |
421 | { |
422 | return test_harness_run(argc, argv); |
423 | } |
424 | |