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 <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);
+}
-- 
2.27.0

Reply via email to