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 <gre...@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 <gre...@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 <gre...@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);
> +}
>

Reply via email to