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