Test the functionality of readfile(2) in various ways. Also provide a simple speed test program to benchmark using readfile() instead of using open()/read()/close(). Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx> --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/readfile/.gitignore | 3 + tools/testing/selftests/readfile/Makefile | 7 + tools/testing/selftests/readfile/readfile.c | 285 +++++++++++++++++ .../selftests/readfile/readfile_speed.c | 301 ++++++++++++++++++ 5 files changed, 597 insertions(+) create mode 100644 tools/testing/selftests/readfile/.gitignore create mode 100644 tools/testing/selftests/readfile/Makefile create mode 100644 tools/testing/selftests/readfile/readfile.c create mode 100644 tools/testing/selftests/readfile/readfile_speed.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 1195bd85af38..82359233b945 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -46,6 +46,7 @@ TARGETS += ptrace TARGETS += openat2 TARGETS += rseq TARGETS += rtc +TARGETS += readfile TARGETS += seccomp TARGETS += sigaltstack TARGETS += size diff --git a/tools/testing/selftests/readfile/.gitignore b/tools/testing/selftests/readfile/.gitignore new file mode 100644 index 000000000000..f0e758d437e4 --- /dev/null +++ b/tools/testing/selftests/readfile/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +readfile +readfile_speed diff --git a/tools/testing/selftests/readfile/Makefile b/tools/testing/selftests/readfile/Makefile new file mode 100644 index 000000000000..1bf1bdec40f8 --- /dev/null +++ b/tools/testing/selftests/readfile/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS += -g -I../../../../usr/include/ +CFLAGS += -O2 -Wl,-no-as-needed -Wall + +TEST_GEN_PROGS := readfile readfile_speed + +include ../lib.mk diff --git a/tools/testing/selftests/readfile/readfile.c b/tools/testing/selftests/readfile/readfile.c new file mode 100644 index 000000000000..f0736c6dfa69 --- /dev/null +++ b/tools/testing/selftests/readfile/readfile.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx> + * Copyright (c) 2020 The Linux Foundation + * + * Test the readfile() syscall in various ways. + */ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <dirent.h> +#include <fcntl.h> +#include <limits.h> +#include <string.h> +#include <syscall.h> + +#include "../kselftest.h" + +//#ifndef __NR_readfile +//#define __NR_readfile -1 +//#endif + +#define __NR_readfile 440 + +#define TEST_FILE1 "/sys/devices/system/cpu/vulnerabilities/meltdown" +#define TEST_FILE2 "/sys/devices/system/cpu/vulnerabilities/spectre_v1" +#define TEST_FILE4 "/sys/kernel/debug/usb/devices" + +static int sys_readfile(int fd, const char *filename, unsigned char *buffer, + size_t bufsize, int flags) +{ + return syscall(__NR_readfile, fd, filename, buffer, bufsize, flags); +} + +/* + * Test that readfile() is even in the running kernel or not. + */ +static void test_readfile_supported(void) +{ + const char *proc_map = "/proc/self/maps"; + unsigned char buffer[10]; + int retval; + + if (__NR_readfile < 0) + ksft_exit_skip("readfile() syscall is not defined for the kernel this test was built against\n"); + + /* + * Do a simple test to see if the syscall really is present in the + * running kernel + */ + retval = sys_readfile(0, proc_map, &buffer[0], sizeof(buffer), 0); + if (retval == -1) + ksft_exit_skip("readfile() syscall not present on running kernel\n"); + + ksft_test_result_pass("readfile() syscall present\n"); +} + +/* + * Open all files in a specific sysfs directory and read from them + * + * This tests the "openat" type functionality of opening all files relative to a + * directory. We don't care at the moment about the contents. + */ +static void test_sysfs_files(void) +{ + static unsigned char buffer[8000]; + const char *sysfs_dir = "/sys/devices/system/cpu/vulnerabilities/"; + struct dirent *dirent; + DIR *vuln_sysfs_dir; + int sysfs_fd; + int retval; + + sysfs_fd = open(sysfs_dir, O_PATH | O_DIRECTORY); + if (sysfs_fd == -1) { + ksft_test_result_skip("unable to open %s directory\n", + sysfs_dir); + return; + } + + vuln_sysfs_dir = opendir(sysfs_dir); + if (!vuln_sysfs_dir) { + ksft_test_result_skip("%s unable to be opened, skipping test\n"); + return; + } + + ksft_print_msg("readfile: testing relative path functionality by reading files in %s\n", + sysfs_dir); + /* open all sysfs file in this directory and read the whole thing */ + while ((dirent = readdir(vuln_sysfs_dir))) { + /* ignore . and .. */ + if (strcmp(dirent->d_name, ".") == 0 || + strcmp(dirent->d_name, "..") == 0) + continue; + + retval = sys_readfile(sysfs_fd, dirent->d_name, &buffer[0], + sizeof(buffer), 0); + + if (retval <= 0) { + ksft_test_result_fail("readfile(%s) failed with %d\n", + dirent->d_name, retval); + goto exit; + } + + /* cut off trailing \n character */ + buffer[retval - 1] = 0x00; + ksft_print_msg(" '%s' contains \"%s\"\n", dirent->d_name, + buffer); + } + + ksft_test_result_pass("readfile() relative path functionality passed\n"); + +exit: + closedir(vuln_sysfs_dir); + close(sysfs_fd); +} + +/* Temporary directory variables */ +static int root_fd; /* test root directory file handle */ +static char tmpdir[PATH_MAX]; + +static void setup_tmpdir(void) +{ + char *tmpdir_root; + + tmpdir_root = getenv("TMPDIR"); + if (!tmpdir_root) + tmpdir_root = "/tmp"; + + snprintf(tmpdir, PATH_MAX, "%s/readfile.XXXXXX", tmpdir_root); + if (!mkdtemp(tmpdir)) { + ksft_test_result_fail("mkdtemp(%s) failed\n", tmpdir); + ksft_exit_fail(); + } + + root_fd = open(tmpdir, O_PATH | O_DIRECTORY); + if (root_fd == -1) { + ksft_exit_fail_msg("%s unable to be opened, error = %d\n", + tmpdir, root_fd); + ksft_exit_fail(); + } + + ksft_print_msg("%s created to use for testing\n", tmpdir); +} + +static void teardown_tmpdir(void) +{ + int retval; + + close(root_fd); + + retval = rmdir(tmpdir); + if (retval) { + ksft_exit_fail_msg("%s removed with return value %d\n", + tmpdir, retval); + ksft_exit_fail(); + } + ksft_print_msg("%s cleaned up and removed\n", tmpdir); + +} + +static void test_filesize(size_t size) +{ + char filename[PATH_MAX]; + unsigned char *write_data; + unsigned char *read_data; + int fd; + int retval; + size_t i; + + snprintf(filename, PATH_MAX, "size-%ld", size); + + read_data = malloc(size); + write_data = malloc(size); + if (!read_data || !write_data) + ksft_exit_fail_msg("Unable to allocate %ld bytes\n", size); + + fd = openat(root_fd, filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + if (fd < 0) + ksft_exit_fail_msg("Unable to create file %s\n", filename); + + ksft_print_msg("%s created\n", filename); + + for (i = 0; i < size; ++i) + write_data[i] = (unsigned char)(0xff & i); + + write(fd, write_data, size); + close(fd); + + retval = sys_readfile(root_fd, filename, read_data, size, 0); + + if (retval != size) { + ksft_test_result_fail("Read %d bytes but wanted to read %ld bytes.\n", + retval, size); + goto exit; + } + + if (memcmp(read_data, write_data, size) != 0) { + ksft_test_result_fail("Read data of buffer size %d did not match written data\n", + size); + goto exit; + } + + ksft_test_result_pass("readfile() of size %ld succeeded.\n", size); + +exit: + unlinkat(root_fd, filename, 0); + free(write_data); + free(read_data); +} + + +/* + * Create a bunch of differently sized files, and verify we read the correct + * amount of data from them. + */ +static void test_filesizes(void) +{ + setup_tmpdir(); + + test_filesize(0x10); + test_filesize(0x100); + test_filesize(0x1000); + test_filesize(0x10000); + test_filesize(0x100000); + test_filesize(0x1000000); + + teardown_tmpdir(); + +} + +static void readfile(const char *filename) +{ +// int root_fd; + unsigned char buffer[16000]; + int retval; + + memset(buffer, 0x00, sizeof(buffer)); + +// root_fd = open("/", O_DIRECTORY); +// if (root_fd == -1) +// ksft_exit_fail_msg("error with root_fd\n"); + + retval = sys_readfile(root_fd, filename, &buffer[0], sizeof(buffer), 0); + +// close(root_fd); + + if (retval <= 0) + ksft_test_result_fail("readfile() test of filename=%s failed with retval %d\n", + filename, retval); + else + ksft_test_result_pass("readfile() test of filename=%s succeeded with retval=%d\n", + filename, retval); +// buffer='%s'\n", +// filename, retval, &buffer[0]); + +} + + +int main(int argc, char *argv[]) +{ + ksft_print_header(); + ksft_set_plan(10); + + test_readfile_supported(); // 1 test + + test_sysfs_files(); // 1 test + + test_filesizes(); // 6 tests + + setup_tmpdir(); + + readfile(TEST_FILE1); + readfile(TEST_FILE2); +// readfile(TEST_FILE4); + + teardown_tmpdir(); + + if (ksft_get_fail_cnt()) + return ksft_exit_fail(); + + return ksft_exit_pass(); +} + diff --git a/tools/testing/selftests/readfile/readfile_speed.c b/tools/testing/selftests/readfile/readfile_speed.c new file mode 100644 index 000000000000..11ca79163131 --- /dev/null +++ b/tools/testing/selftests/readfile/readfile_speed.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx> + * Copyright (c) 2020 The Linux Foundation + * + * Tiny test program to try to benchmark the speed of the readfile syscall vs. + * the open/read/close sequence it can replace. + */ +#define _GNU_SOURCE +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <syscall.h> +#include <time.h> +#include <unistd.h> + +/* Default test file if no one wants to pick something else */ +#define DEFAULT_TEST_FILE "/sys/devices/system/cpu/vulnerabilities/meltdown" + +#define DEFAULT_TEST_LOOPS 1000 + +#define DEFAULT_TEST_TYPE "both" + +/* Max number of bytes that will be read from the file */ +#define TEST_BUFFER_SIZE 10000 +static unsigned char test_buffer[TEST_BUFFER_SIZE]; + +enum test_type { + TEST_READFILE, + TEST_OPENREADCLOSE, + TEST_BOTH, +}; + +/* Find the readfile syscall number */ +//#ifndef __NR_readfile +//#define __NR_readfile -1 +//#endif +#define __NR_readfile 440 + +static int sys_readfile(int fd, const char *filename, unsigned char *buffer, + size_t bufsize, int flags) +{ + return syscall(__NR_readfile, fd, filename, buffer, bufsize, flags); +} + +/* Test that readfile() is even in the running kernel or not. */ +static void test_readfile_supported(void) +{ + const char *proc_map = "/proc/self/maps"; + unsigned char buffer[10]; + int retval; + + if (__NR_readfile < 0) { + fprintf(stderr, + "readfile() syscall is not defined for the kernel this test was built against.\n"); + exit(1); + } + + /* + * Do a simple test to see if the syscall really is present in the + * running kernel + */ + retval = sys_readfile(0, proc_map, &buffer[0], sizeof(buffer), 0); + if (retval == -1) { + fprintf(stderr, + "readfile() syscall not present on running kernel.\n"); + exit(1); + } +} + +static inline long long get_time_ns(void) +{ + struct timespec t; + + clock_gettime(CLOCK_MONOTONIC, &t); + + return (long long)t.tv_sec * 1000000000 + t.tv_nsec; +} + +/* taken from all-io.h from util-linux repo */ +static inline ssize_t read_all(int fd, unsigned char *buf, size_t count) +{ + ssize_t ret; + ssize_t c = 0; + int tries = 0; + + while (count > 0) { + ret = read(fd, buf, count); + if (ret <= 0) { + if (ret < 0 && (errno == EAGAIN || errno == EINTR) && + (tries++ < 5)) { + usleep(250000); + continue; + } + return c ? c : -1; + } + tries = 0; + count -= ret; + buf += ret; + c += ret; + } + return c; +} + +static int openreadclose(const char *filename, unsigned char *buffer, + size_t bufsize) +{ + size_t count; + int fd; + + fd = openat(0, filename, O_RDONLY); + if (fd < 0) { + printf("error opening %s\n", filename); + return fd; + } + + count = read_all(fd, buffer, bufsize); + if (count < 0) { + printf("Error %ld reading from %s\n", count, filename); + } + + close(fd); + return count; +} + +static int run_test(enum test_type test_type, const char *filename) +{ + switch (test_type) { + case TEST_READFILE: + return sys_readfile(0, filename, &test_buffer[0], + TEST_BUFFER_SIZE, O_RDONLY); + + case TEST_OPENREADCLOSE: + return openreadclose(filename, &test_buffer[0], + TEST_BUFFER_SIZE); + default: + return -EINVAL; + } +} + +static const char * const test_names[] = { + [TEST_READFILE] = "readfile", + [TEST_OPENREADCLOSE] = "open/read/close", +}; + +static int run_test_loop(int loops, enum test_type test_type, + const char *filename) +{ + long long time_start; + long long time_end; + long long time_elapsed; + int retval = 0; + int i; + + fprintf(stdout, + "Running %s test on file %s for %d loops...\n", + test_names[test_type], filename, loops); + + /* Fill the cache with one run of the read first */ + retval = run_test(test_type, filename); + if (retval < 0) { + fprintf(stderr, + "test %s was unable to run with error %d\n", + test_names[test_type], retval); + return retval; + } + + time_start = get_time_ns(); + + for (i = 0; i < loops; ++i) { + retval = run_test(test_type, filename); + + if (retval < 0) { + fprintf(stderr, + "test failed on loop %d with error %d\n", + i, retval); + break; + } + } + time_end = get_time_ns(); + + time_elapsed = time_end - time_start; + + fprintf(stdout, "Took %lld ns\n", time_elapsed); + + return retval; +} + +static int do_read_file_test(int loops, enum test_type test_type, + const char *filename) +{ + int retval; + + if (test_type == TEST_BOTH) { + retval = do_read_file_test(loops, TEST_READFILE, filename); + retval = do_read_file_test(loops, TEST_OPENREADCLOSE, filename); + return retval; + } + return run_test_loop(loops, test_type, filename); +} + +static int check_file_present(const char *filename) +{ + struct stat sb; + int retval; + + retval = stat(filename, &sb); + if (retval == -1) { + fprintf(stderr, + "filename %s is not present\n", filename); + return retval; + } + + if ((sb.st_mode & S_IFMT) != S_IFREG) { + fprintf(stderr, + "filename %s must be a real file, not anything else.\n", + filename); + return -1; + } + return 0; +} + +static void usage(char *progname) +{ + fprintf(stderr, + "usage: %s [options]\n" + " -l loops Number of loops to run the test for.\n" + " default is %d\n" + " -t testtype Test type to run.\n" + " types are: readfile, openreadclose, both\n" + " default is %s\n" + " -f filename Filename to read from, full path, not relative.\n" + " default is %s\n", + progname, + DEFAULT_TEST_LOOPS, DEFAULT_TEST_TYPE, DEFAULT_TEST_FILE); +} + +int main(int argc, char *argv[]) +{ + char *progname; + char *testtype = DEFAULT_TEST_TYPE; + char *filename = DEFAULT_TEST_FILE; + int loops = DEFAULT_TEST_LOOPS; + enum test_type test_type; + int retval; + char c; + + progname = strrchr(argv[0], '/'); + progname = progname ? 1+progname : argv[0]; + + while (EOF != (c = getopt(argc, argv, "t:l:f:h"))) { + switch (c) { + case 'l': + loops = atoi(optarg); + break; + + case 't': + testtype = optarg; + break; + + case 'f': + filename = optarg; + break; + + case 'h': + usage(progname); + return 0; + + default: + usage(progname); + return -1; + } + } + + if (strcmp(testtype, "readfile") == 0) + test_type = TEST_READFILE; + else if (strcmp(testtype, "openreadclose") == 0) + test_type = TEST_OPENREADCLOSE; + else if (strcmp(testtype, "both") == 0) + test_type = TEST_BOTH; + else { + usage(progname); + return -1; + } + + test_readfile_supported(); + + retval = check_file_present(filename); + if (retval) + return retval; + + return do_read_file_test(loops, test_type, filename); +} -- 2.27.0