424 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * User Events ABI Test Program4 *5 * Copyright (c) 2022 Beau Belgrave <beaub@linux.microsoft.com>6 */7 8#define _GNU_SOURCE9#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 26const char *data_file = "/sys/kernel/tracing/user_events_data";27const char *enable_file = "/sys/kernel/tracing/events/user_events/__abi_event/enable";28const char *multi_dir_glob = "/sys/kernel/tracing/events/user_events_multi/__abi_event.*";29 30static 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 45static 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 }79out:80 globfree(&buf);81 82 return ret;83}84 85static 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 97static 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 else108 ret = write(fd, "0", 1);109 110 close(fd);111 112 if (ret == 1)113 ret = 0;114 else115 ret = -1;116 117 return ret;118}119 120static 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 135static 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(full_args, sizeof(full_args), "__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, ®);162out:163 close(fd);164 165 return ret;166}167 168static 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 191static int reg_enable(void *enable, int size, int bit)192{193 return reg_enable_flags(enable, size, bit, 0);194}195 196static 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 216FIXTURE(user) {217 int check;218 long check_long;219 bool umount;220};221 222FIXTURE_SETUP(user) {223 USER_EVENT_FIXTURE_SETUP(return, self->umount);224 225 change_event(false);226 self->check = 0;227 self->check_long = 0;228}229 230FIXTURE_TEARDOWN(user) {231 USER_EVENT_FIXTURE_TEARDOWN(self->umount);232}233 234TEST_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 255TEST_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 275TEST_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 == 8284 /* 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#endif289 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 301TEST_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 344TEST_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(&self->check, 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 */385static 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 399TEST_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 420int main(int argc, char **argv)421{422 return test_harness_run(argc, argv);423}424