| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * User Events ABI Test Program |
| * |
| * Copyright (c) 2022 Beau Belgrave <beaub@linux.microsoft.com> |
| */ |
| |
| #define _GNU_SOURCE |
| #include <sched.h> |
| |
| #include <errno.h> |
| #include <linux/user_events.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <asm/unistd.h> |
| |
| #include "../kselftest_harness.h" |
| #include "user_events_selftests.h" |
| |
| const char *data_file = "/sys/kernel/tracing/user_events_data"; |
| const char *enable_file = "/sys/kernel/tracing/events/user_events/__abi_event/enable"; |
| |
| static bool event_exists(void) |
| { |
| int fd = open(enable_file, O_RDWR); |
| |
| if (fd < 0) |
| return false; |
| |
| close(fd); |
| |
| return true; |
| } |
| |
| static int change_event(bool enable) |
| { |
| int fd = open(enable_file, O_RDWR); |
| int ret; |
| |
| if (fd < 0) |
| return -1; |
| |
| if (enable) |
| ret = write(fd, "1", 1); |
| else |
| ret = write(fd, "0", 1); |
| |
| close(fd); |
| |
| if (ret == 1) |
| ret = 0; |
| else |
| ret = -1; |
| |
| return ret; |
| } |
| |
| static int event_delete(void) |
| { |
| int fd = open(data_file, O_RDWR); |
| int ret; |
| |
| if (fd < 0) |
| return -1; |
| |
| ret = ioctl(fd, DIAG_IOCSDEL, "__abi_event"); |
| |
| close(fd); |
| |
| return ret; |
| } |
| |
| static int reg_enable_flags(void *enable, int size, int bit, int flags) |
| { |
| struct user_reg reg = {0}; |
| int fd = open(data_file, O_RDWR); |
| int ret; |
| |
| if (fd < 0) |
| return -1; |
| |
| reg.size = sizeof(reg); |
| reg.name_args = (__u64)"__abi_event"; |
| reg.flags = flags; |
| reg.enable_bit = bit; |
| reg.enable_addr = (__u64)enable; |
| reg.enable_size = size; |
| |
| ret = ioctl(fd, DIAG_IOCSREG, ®); |
| |
| close(fd); |
| |
| return ret; |
| } |
| |
| static int reg_enable(void *enable, int size, int bit) |
| { |
| return reg_enable_flags(enable, size, bit, 0); |
| } |
| |
| static int reg_disable(void *enable, int bit) |
| { |
| struct user_unreg reg = {0}; |
| int fd = open(data_file, O_RDWR); |
| int ret; |
| |
| if (fd < 0) |
| return -1; |
| |
| reg.size = sizeof(reg); |
| reg.disable_bit = bit; |
| reg.disable_addr = (__u64)enable; |
| |
| ret = ioctl(fd, DIAG_IOCSUNREG, ®); |
| |
| close(fd); |
| |
| return ret; |
| } |
| |
| FIXTURE(user) { |
| int check; |
| long check_long; |
| bool umount; |
| }; |
| |
| FIXTURE_SETUP(user) { |
| USER_EVENT_FIXTURE_SETUP(return, self->umount); |
| |
| change_event(false); |
| self->check = 0; |
| self->check_long = 0; |
| } |
| |
| FIXTURE_TEARDOWN(user) { |
| USER_EVENT_FIXTURE_TEARDOWN(self->umount); |
| } |
| |
| TEST_F(user, enablement) { |
| /* Changes should be reflected immediately */ |
| ASSERT_EQ(0, self->check); |
| ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); |
| ASSERT_EQ(0, change_event(true)); |
| ASSERT_EQ(1, self->check); |
| ASSERT_EQ(0, change_event(false)); |
| ASSERT_EQ(0, self->check); |
| |
| /* Ensure kernel clears bit after disable */ |
| ASSERT_EQ(0, change_event(true)); |
| ASSERT_EQ(1, self->check); |
| ASSERT_EQ(0, reg_disable(&self->check, 0)); |
| ASSERT_EQ(0, self->check); |
| |
| /* Ensure doesn't change after unreg */ |
| ASSERT_EQ(0, change_event(true)); |
| ASSERT_EQ(0, self->check); |
| ASSERT_EQ(0, change_event(false)); |
| } |
| |
| TEST_F(user, flags) { |
| /* USER_EVENT_REG_PERSIST is allowed */ |
| ASSERT_EQ(0, reg_enable_flags(&self->check, sizeof(int), 0, |
| USER_EVENT_REG_PERSIST)); |
| ASSERT_EQ(0, reg_disable(&self->check, 0)); |
| |
| /* Ensure it exists after close and disable */ |
| ASSERT_TRUE(event_exists()); |
| |
| /* Ensure we can delete it */ |
| ASSERT_EQ(0, event_delete()); |
| |
| /* USER_EVENT_REG_MAX or above is not allowed */ |
| ASSERT_EQ(-1, reg_enable_flags(&self->check, sizeof(int), 0, |
| USER_EVENT_REG_MAX)); |
| |
| /* Ensure it does not exist after invalid flags */ |
| ASSERT_FALSE(event_exists()); |
| } |
| |
| TEST_F(user, bit_sizes) { |
| /* Allow 0-31 bits for 32-bit */ |
| ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); |
| ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 31)); |
| ASSERT_NE(0, reg_enable(&self->check, sizeof(int), 32)); |
| ASSERT_EQ(0, reg_disable(&self->check, 0)); |
| ASSERT_EQ(0, reg_disable(&self->check, 31)); |
| |
| #if BITS_PER_LONG == 8 |
| /* Allow 0-64 bits for 64-bit */ |
| ASSERT_EQ(0, reg_enable(&self->check_long, sizeof(long), 63)); |
| ASSERT_NE(0, reg_enable(&self->check_long, sizeof(long), 64)); |
| ASSERT_EQ(0, reg_disable(&self->check_long, 63)); |
| #endif |
| |
| /* Disallowed sizes (everything beside 4 and 8) */ |
| ASSERT_NE(0, reg_enable(&self->check, 1, 0)); |
| ASSERT_NE(0, reg_enable(&self->check, 2, 0)); |
| ASSERT_NE(0, reg_enable(&self->check, 3, 0)); |
| ASSERT_NE(0, reg_enable(&self->check, 5, 0)); |
| ASSERT_NE(0, reg_enable(&self->check, 6, 0)); |
| ASSERT_NE(0, reg_enable(&self->check, 7, 0)); |
| ASSERT_NE(0, reg_enable(&self->check, 9, 0)); |
| ASSERT_NE(0, reg_enable(&self->check, 128, 0)); |
| } |
| |
| TEST_F(user, forks) { |
| int i; |
| |
| /* Ensure COW pages get updated after fork */ |
| ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); |
| ASSERT_EQ(0, self->check); |
| |
| if (fork() == 0) { |
| /* Force COW */ |
| self->check = 0; |
| |
| /* Up to 1 sec for enablement */ |
| for (i = 0; i < 10; ++i) { |
| usleep(100000); |
| |
| if (self->check) |
| exit(0); |
| } |
| |
| exit(1); |
| } |
| |
| /* Allow generous time for COW, then enable */ |
| usleep(100000); |
| ASSERT_EQ(0, change_event(true)); |
| |
| ASSERT_NE(-1, wait(&i)); |
| ASSERT_EQ(0, WEXITSTATUS(i)); |
| |
| /* Ensure child doesn't disable parent */ |
| if (fork() == 0) |
| exit(reg_disable(&self->check, 0)); |
| |
| ASSERT_NE(-1, wait(&i)); |
| ASSERT_EQ(0, WEXITSTATUS(i)); |
| ASSERT_EQ(1, self->check); |
| ASSERT_EQ(0, change_event(false)); |
| ASSERT_EQ(0, self->check); |
| } |
| |
| /* Waits up to 1 sec for enablement */ |
| static int clone_check(void *check) |
| { |
| int i; |
| |
| for (i = 0; i < 10; ++i) { |
| usleep(100000); |
| |
| if (*(int *)check) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| TEST_F(user, clones) { |
| int i, stack_size = 4096; |
| void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, |
| -1, 0); |
| |
| ASSERT_NE(MAP_FAILED, stack); |
| ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); |
| ASSERT_EQ(0, self->check); |
| |
| /* Shared VM should see enablements */ |
| ASSERT_NE(-1, clone(&clone_check, stack + stack_size, |
| CLONE_VM | SIGCHLD, &self->check)); |
| |
| ASSERT_EQ(0, change_event(true)); |
| ASSERT_NE(-1, wait(&i)); |
| ASSERT_EQ(0, WEXITSTATUS(i)); |
| munmap(stack, stack_size); |
| ASSERT_EQ(0, change_event(false)); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| return test_harness_run(argc, argv); |
| } |