| From 8928abbad99c7d3750695998e5fa7ba144da3300 Mon Sep 17 00:00:00 2001 |
| From: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Date: Sun, 8 Mar 2020 09:54:45 +0100 |
| Subject: [PATCH 3/4] selftests: add readfile(2) selftests |
| |
| 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@linuxfoundation.org> |
| --- |
| tools/testing/selftests/Makefile | 1 |
| tools/testing/selftests/readfile/.gitignore | 3 |
| tools/testing/selftests/readfile/Makefile | 7 |
| tools/testing/selftests/readfile/readfile.c | 285 ++++++++++++++++++++++ |
| tools/testing/selftests/readfile/readfile_speed.c | 221 +++++++++++++++++ |
| 5 files changed, 517 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 |
| |
| --- 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 |
| --- /dev/null |
| +++ b/tools/testing/selftests/readfile/.gitignore |
| @@ -0,0 +1,3 @@ |
| +# SPDX-License-Identifier: GPL-2.0 |
| +readfile |
| +readfile_speed |
| --- /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 |
| --- /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@linuxfoundation.org> |
| + * 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(); |
| +} |
| + |
| --- /dev/null |
| +++ b/tools/testing/selftests/readfile/readfile_speed.c |
| @@ -0,0 +1,221 @@ |
| +// SPDX-License-Identifier: GPL-2.0 |
| +/* |
| + * Copyright (c) 2020 Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| + * 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 replaces. |
| + */ |
| +#define _GNU_SOURCE |
| +#include <stdio.h> |
| +#include <stdlib.h> |
| +#include <sys/syscall.h> |
| +#include <sys/types.h> |
| +#include <sys/stat.h> |
| +#include <dirent.h> |
| +#include <fcntl.h> |
| +#include <limits.h> |
| +#include <string.h> |
| +#include <syscall.h> |
| +#include <errno.h> |
| +#include <unistd.h> |
| +#include <stdarg.h> |
| + |
| +//#ifndef __NR_readfile |
| +//#define __NR_readfile -1 |
| +//#endif |
| + |
| +#define __NR_readfile 440 |
| + |
| +#define TEST_FILE "/sys/devices/system/cpu/vulnerabilities/meltdown" |
| + |
| +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); |
| +} |
| + |
| +/* 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; |
| + |
| + //memset(buf, 0, count); |
| + 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; |
| +} |
| + |
| +enum test_type { |
| + READFILE, |
| + OPENREADCLOSE, |
| +}; |
| + |
| +static int do_read_file_test(int loops, enum test_type test_type, |
| + const char *filename, |
| + unsigned char *buffer, size_t bufsize) |
| +{ |
| + char *test; |
| + int retval; |
| + int i; |
| + |
| + switch (test_type) { |
| + case READFILE: |
| + test = "readfile"; |
| + break; |
| + |
| + case OPENREADCLOSE: |
| + test = "open/read/close"; |
| + break; |
| + default: |
| + fprintf(stderr, "wrong test type"); |
| + return -1; |
| + } |
| + |
| + fprintf(stdout, |
| + "Running %s test on file %s for %d loops...\n", |
| + test, filename, loops); |
| + |
| + for (i = 0; i < loops; ++i) { |
| + switch (test_type) { |
| + case READFILE: |
| + retval = sys_readfile(0, filename, buffer, bufsize, O_RDONLY); |
| + break; |
| + |
| + case OPENREADCLOSE: |
| + retval = openreadclose(filename, buffer, bufsize); |
| + break; |
| + } |
| + if (retval < 0) { |
| + fprintf(stderr, |
| + "test failed on loop %d with error %d\n", |
| + i, retval); |
| + return retval; |
| + } |
| + } |
| + return 0; |
| +} |
| + |
| +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 10'000\n" |
| + " -t testtype Test type to run\n" |
| + " types are: readfile, openreadclose\n" |
| + " default is readfile\n", |
| + progname); |
| +} |
| + |
| +int main(int argc, char *argv[]) |
| +{ |
| + int loops = 10000; |
| + unsigned char buffer[10000]; |
| + char c; |
| + char *testtype = "readfile"; |
| + char *progname; |
| + char *filename; |
| + enum test_type test_type; |
| + int retval; |
| + |
| + progname = strrchr(argv[0], '/'); |
| + progname = progname ? 1+progname : argv[0]; |
| + |
| + while (EOF != (c = getopt(argc, argv, "t:hl:"))) { |
| + switch (c) { |
| + case 'l': |
| + loops = atoi(optarg); |
| + break; |
| + |
| + case 't': |
| + testtype = optarg; |
| + break; |
| + |
| + case 'h': |
| + usage(progname); |
| + return 0; |
| + |
| + default: |
| + usage(progname); |
| + return -1; |
| + } |
| + } |
| + |
| + if (strcmp(testtype, "readfile") == 0) |
| + test_type = READFILE; |
| + else if (strcmp(testtype, "openreadclose") == 0) |
| + test_type = OPENREADCLOSE; |
| + else { |
| + usage(progname); |
| + return -1; |
| + } |
| + |
| + filename = TEST_FILE; |
| + |
| + retval = check_file_present(filename); |
| + if (retval) |
| + return retval; |
| + |
| + retval = do_read_file_test(loops, test_type, TEST_FILE, |
| + &buffer[0], sizeof(buffer)); |
| + |
| + return retval; |
| +} |