Message ID | 20200704140250.423345-4-gregkh@linuxfoundation.org (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | readfile(2): a new syscall to make open/read/close faster | expand |
Hi Greg, On Sat, Jul 4, 2020 at 4:05 PM Greg Kroah-Hartman <gregkh@linuxfoundation.org> wrote: > 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> Any benchmark results to share? > --- /dev/null > +++ b/tools/testing/selftests/readfile/readfile.c > +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(); > +} Gr{oetje,eeting}s, Geert
On 7/4/20 4:02 PM, Greg Kroah-Hartman wrote: > Test the functionality of readfile(2) in various ways. Hello Greg, I expect readfile() to generate fanotify events FAN_OPEN_PERM, FAN_OPEN, FAN_ACCESS_PERM, FAN_ACCESS, FAN_CLOSE_NOWRITE in this sequence. Looking at patch 1/3 you took care of notifications. Would this deserve testing here? Best regards Heinrich > > 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 +++++++++++++++++ > .../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@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(); > +} > + > 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@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 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); > +} >
On Sat, Jul 04, 2020 at 08:38:26PM +0200, Geert Uytterhoeven wrote: > Hi Greg, > > On Sat, Jul 4, 2020 at 4:05 PM Greg Kroah-Hartman > <gregkh@linuxfoundation.org> wrote: > > 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> > > Any benchmark results to share? Yes, the readfile_speed.c file will show you that on your machine, I'll post the results of that for two of my machines when I send the next version of this patch series. > > > --- /dev/null > > +++ b/tools/testing/selftests/readfile/readfile.c > > > +static void readfile(const char *filename) > > +{ > > +// int root_fd; > > ??? Ugh, sorry about that, I obviously didn't clean up my last tests from this file, thanks for catching that. I should add more tests to validate the flag handling as well, will do all of that for the next version, thanks for noticing this. greg k-h
On Sun, Jul 05, 2020 at 03:41:48AM +0200, Heinrich Schuchardt wrote: > On 7/4/20 4:02 PM, Greg Kroah-Hartman wrote: > > Test the functionality of readfile(2) in various ways. > > Hello Greg, > > I expect readfile() to generate fanotify events FAN_OPEN_PERM, FAN_OPEN, > FAN_ACCESS_PERM, FAN_ACCESS, FAN_CLOSE_NOWRITE in this sequence. Yes, it should, I don't think I do anything unique here when it comes to vfs accesses that would go around those events. > Looking at patch 1/3 you took care of notifications. Would this deserve > testing here? Possibly, do we have other in-tree tests of syscalls that validate those events properly being created? thanks, greg k-h
On 7/5/20 9:34 AM, Greg Kroah-Hartman wrote: > On Sun, Jul 05, 2020 at 03:41:48AM +0200, Heinrich Schuchardt wrote: >> On 7/4/20 4:02 PM, Greg Kroah-Hartman wrote: >>> Test the functionality of readfile(2) in various ways. >> >> Hello Greg, >> >> I expect readfile() to generate fanotify events FAN_OPEN_PERM, FAN_OPEN, >> FAN_ACCESS_PERM, FAN_ACCESS, FAN_CLOSE_NOWRITE in this sequence. > > Yes, it should, I don't think I do anything unique here when it comes to > vfs accesses that would go around those events. > >> Looking at patch 1/3 you took care of notifications. Would this deserve >> testing here? > > Possibly, do we have other in-tree tests of syscalls that validate those > events properly being created? There is an inotify test in tools/testing/selftests/cgroup/test_freezer.c There is no fanotify test in tree test. An fanotify test will require running with CAP_SYS_ADMIN. The kselftest documentation does not describe that tests should be run as root. So it may be preferable to test that the inotify events IN_OPEN, IN_ACCESS, IN_CLOSE_NOWRITE are created for readfile(). Example coding is included in the inotify.7 and fanotify.7 manpages. Best regards Heinrich
Hi Greg, On Sun, Jul 5, 2020 at 8:55 AM Greg Kroah-Hartman <gregkh@linuxfoundation.org> wrote: > On Sat, Jul 04, 2020 at 08:38:26PM +0200, Geert Uytterhoeven wrote: > > On Sat, Jul 4, 2020 at 4:05 PM Greg Kroah-Hartman > > <gregkh@linuxfoundation.org> wrote: > > > 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> > > > --- /dev/null > > > +++ b/tools/testing/selftests/readfile/readfile.c > > > > > +static void readfile(const char *filename) > > > +{ > > > +// int root_fd; > > > > ??? > > Ugh, sorry about that, I obviously didn't clean up my last tests from > this file, thanks for catching that. Reading about seq_file behavior, did the commented-out test for "/sys/kernel/debug/usb/devices" work? Gr{oetje,eeting}s, Geert
On Sun, Jul 05, 2020 at 01:24:07PM +0200, Geert Uytterhoeven wrote: > Hi Greg, > > On Sun, Jul 5, 2020 at 8:55 AM Greg Kroah-Hartman > <gregkh@linuxfoundation.org> wrote: > > On Sat, Jul 04, 2020 at 08:38:26PM +0200, Geert Uytterhoeven wrote: > > > On Sat, Jul 4, 2020 at 4:05 PM Greg Kroah-Hartman > > > <gregkh@linuxfoundation.org> wrote: > > > > 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> > > > > > --- /dev/null > > > > +++ b/tools/testing/selftests/readfile/readfile.c > > > > > > > +static void readfile(const char *filename) > > > > +{ > > > > +// int root_fd; > > > > > > ??? > > > > Ugh, sorry about that, I obviously didn't clean up my last tests from > > this file, thanks for catching that. > > Reading about seq_file behavior, did the commented-out test for > "/sys/kernel/debug/usb/devices" work? Yes it did, which means I need to go dig to try to find a "problem" seq_file procfs file to try to debug that behavior... thanks, greg k-h
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@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(); +} + 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@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 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); +}
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 +++++++++++++++++ .../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