blob: 8b9280a4fb57222a5c420d8deb114387d587d723 [file] [log] [blame]
From 8928abbad99c7d3750695998e5fa7ba144da3300 Mon Sep 17 00:00:00 2001
From: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Date: Sun, 8 Mar 2020 09:54:45 +0100
Subject: [PATCH 3/4] selftests: add readfile(2) selftests
Test the functionality of readfile(2) in various ways.
Also provide a simple speed test program to benchmark using readfile()
instead of using open()/read()/close().
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
tools/testing/selftests/Makefile | 1
tools/testing/selftests/readfile/.gitignore | 3
tools/testing/selftests/readfile/Makefile | 7
tools/testing/selftests/readfile/readfile.c | 285 ++++++++++++++++++++++
tools/testing/selftests/readfile/readfile_speed.c | 221 +++++++++++++++++
5 files changed, 517 insertions(+)
create mode 100644 tools/testing/selftests/readfile/.gitignore
create mode 100644 tools/testing/selftests/readfile/Makefile
create mode 100644 tools/testing/selftests/readfile/readfile.c
create mode 100644 tools/testing/selftests/readfile/readfile_speed.c
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -46,6 +46,7 @@ TARGETS += ptrace
TARGETS += openat2
TARGETS += rseq
TARGETS += rtc
+TARGETS += readfile
TARGETS += seccomp
TARGETS += sigaltstack
TARGETS += size
--- /dev/null
+++ b/tools/testing/selftests/readfile/.gitignore
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+readfile
+readfile_speed
--- /dev/null
+++ b/tools/testing/selftests/readfile/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += -g -I../../../../usr/include/
+CFLAGS += -O2 -Wl,-no-as-needed -Wall
+
+TEST_GEN_PROGS := readfile readfile_speed
+
+include ../lib.mk
--- /dev/null
+++ b/tools/testing/selftests/readfile/readfile.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+ * Copyright (c) 2020 The Linux Foundation
+ *
+ * Test the readfile() syscall in various ways.
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <string.h>
+#include <syscall.h>
+
+#include "../kselftest.h"
+
+//#ifndef __NR_readfile
+//#define __NR_readfile -1
+//#endif
+
+#define __NR_readfile 440
+
+#define TEST_FILE1 "/sys/devices/system/cpu/vulnerabilities/meltdown"
+#define TEST_FILE2 "/sys/devices/system/cpu/vulnerabilities/spectre_v1"
+#define TEST_FILE4 "/sys/kernel/debug/usb/devices"
+
+static int sys_readfile(int fd, const char *filename, unsigned char *buffer,
+ size_t bufsize, int flags)
+{
+ return syscall(__NR_readfile, fd, filename, buffer, bufsize, flags);
+}
+
+/*
+ * Test that readfile() is even in the running kernel or not.
+ */
+static void test_readfile_supported(void)
+{
+ const char *proc_map = "/proc/self/maps";
+ unsigned char buffer[10];
+ int retval;
+
+ if (__NR_readfile < 0)
+ ksft_exit_skip("readfile() syscall is not defined for the kernel this test was built against\n");
+
+ /*
+ * Do a simple test to see if the syscall really is present in the
+ * running kernel
+ */
+ retval = sys_readfile(0, proc_map, &buffer[0], sizeof(buffer), 0);
+ if (retval == -1)
+ ksft_exit_skip("readfile() syscall not present on running kernel\n");
+
+ ksft_test_result_pass("readfile() syscall present\n");
+}
+
+/*
+ * Open all files in a specific sysfs directory and read from them
+ *
+ * This tests the "openat" type functionality of opening all files relative to a
+ * directory. We don't care at the moment about the contents.
+ */
+static void test_sysfs_files(void)
+{
+ static unsigned char buffer[8000];
+ const char *sysfs_dir = "/sys/devices/system/cpu/vulnerabilities/";
+ struct dirent *dirent;
+ DIR *vuln_sysfs_dir;
+ int sysfs_fd;
+ int retval;
+
+ sysfs_fd = open(sysfs_dir, O_PATH | O_DIRECTORY);
+ if (sysfs_fd == -1) {
+ ksft_test_result_skip("unable to open %s directory\n",
+ sysfs_dir);
+ return;
+ }
+
+ vuln_sysfs_dir = opendir(sysfs_dir);
+ if (!vuln_sysfs_dir) {
+ ksft_test_result_skip("%s unable to be opened, skipping test\n");
+ return;
+ }
+
+ ksft_print_msg("readfile: testing relative path functionality by reading files in %s\n",
+ sysfs_dir);
+ /* open all sysfs file in this directory and read the whole thing */
+ while ((dirent = readdir(vuln_sysfs_dir))) {
+ /* ignore . and .. */
+ if (strcmp(dirent->d_name, ".") == 0 ||
+ strcmp(dirent->d_name, "..") == 0)
+ continue;
+
+ retval = sys_readfile(sysfs_fd, dirent->d_name, &buffer[0],
+ sizeof(buffer), 0);
+
+ if (retval <= 0) {
+ ksft_test_result_fail("readfile(%s) failed with %d\n",
+ dirent->d_name, retval);
+ goto exit;
+ }
+
+ /* cut off trailing \n character */
+ buffer[retval - 1] = 0x00;
+ ksft_print_msg(" '%s' contains \"%s\"\n", dirent->d_name,
+ buffer);
+ }
+
+ ksft_test_result_pass("readfile() relative path functionality passed\n");
+
+exit:
+ closedir(vuln_sysfs_dir);
+ close(sysfs_fd);
+}
+
+/* Temporary directory variables */
+static int root_fd; /* test root directory file handle */
+static char tmpdir[PATH_MAX];
+
+static void setup_tmpdir(void)
+{
+ char *tmpdir_root;
+
+ tmpdir_root = getenv("TMPDIR");
+ if (!tmpdir_root)
+ tmpdir_root = "/tmp";
+
+ snprintf(tmpdir, PATH_MAX, "%s/readfile.XXXXXX", tmpdir_root);
+ if (!mkdtemp(tmpdir)) {
+ ksft_test_result_fail("mkdtemp(%s) failed\n", tmpdir);
+ ksft_exit_fail();
+ }
+
+ root_fd = open(tmpdir, O_PATH | O_DIRECTORY);
+ if (root_fd == -1) {
+ ksft_exit_fail_msg("%s unable to be opened, error = %d\n",
+ tmpdir, root_fd);
+ ksft_exit_fail();
+ }
+
+ ksft_print_msg("%s created to use for testing\n", tmpdir);
+}
+
+static void teardown_tmpdir(void)
+{
+ int retval;
+
+ close(root_fd);
+
+ retval = rmdir(tmpdir);
+ if (retval) {
+ ksft_exit_fail_msg("%s removed with return value %d\n",
+ tmpdir, retval);
+ ksft_exit_fail();
+ }
+ ksft_print_msg("%s cleaned up and removed\n", tmpdir);
+
+}
+
+static void test_filesize(size_t size)
+{
+ char filename[PATH_MAX];
+ unsigned char *write_data;
+ unsigned char *read_data;
+ int fd;
+ int retval;
+ size_t i;
+
+ snprintf(filename, PATH_MAX, "size-%ld", size);
+
+ read_data = malloc(size);
+ write_data = malloc(size);
+ if (!read_data || !write_data)
+ ksft_exit_fail_msg("Unable to allocate %ld bytes\n", size);
+
+ fd = openat(root_fd, filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
+ if (fd < 0)
+ ksft_exit_fail_msg("Unable to create file %s\n", filename);
+
+ ksft_print_msg("%s created\n", filename);
+
+ for (i = 0; i < size; ++i)
+ write_data[i] = (unsigned char)(0xff & i);
+
+ write(fd, write_data, size);
+ close(fd);
+
+ retval = sys_readfile(root_fd, filename, read_data, size, 0);
+
+ if (retval != size) {
+ ksft_test_result_fail("Read %d bytes but wanted to read %ld bytes.\n",
+ retval, size);
+ goto exit;
+ }
+
+ if (memcmp(read_data, write_data, size) != 0) {
+ ksft_test_result_fail("Read data of buffer size %d did not match written data\n",
+ size);
+ goto exit;
+ }
+
+ ksft_test_result_pass("readfile() of size %ld succeeded.\n", size);
+
+exit:
+ unlinkat(root_fd, filename, 0);
+ free(write_data);
+ free(read_data);
+}
+
+
+/*
+ * Create a bunch of differently sized files, and verify we read the correct
+ * amount of data from them.
+ */
+static void test_filesizes(void)
+{
+ setup_tmpdir();
+
+ test_filesize(0x10);
+ test_filesize(0x100);
+ test_filesize(0x1000);
+ test_filesize(0x10000);
+ test_filesize(0x100000);
+ test_filesize(0x1000000);
+
+ teardown_tmpdir();
+
+}
+
+static void readfile(const char *filename)
+{
+// int root_fd;
+ unsigned char buffer[16000];
+ int retval;
+
+ memset(buffer, 0x00, sizeof(buffer));
+
+// root_fd = open("/", O_DIRECTORY);
+// if (root_fd == -1)
+// ksft_exit_fail_msg("error with root_fd\n");
+
+ retval = sys_readfile(root_fd, filename, &buffer[0], sizeof(buffer), 0);
+
+// close(root_fd);
+
+ if (retval <= 0)
+ ksft_test_result_fail("readfile() test of filename=%s failed with retval %d\n",
+ filename, retval);
+ else
+ ksft_test_result_pass("readfile() test of filename=%s succeeded with retval=%d\n",
+ filename, retval);
+// buffer='%s'\n",
+// filename, retval, &buffer[0]);
+
+}
+
+
+int main(int argc, char *argv[])
+{
+ ksft_print_header();
+ ksft_set_plan(10);
+
+ test_readfile_supported(); // 1 test
+
+ test_sysfs_files(); // 1 test
+
+ test_filesizes(); // 6 tests
+
+ setup_tmpdir();
+
+ readfile(TEST_FILE1);
+ readfile(TEST_FILE2);
+// readfile(TEST_FILE4);
+
+ teardown_tmpdir();
+
+ if (ksft_get_fail_cnt())
+ return ksft_exit_fail();
+
+ return ksft_exit_pass();
+}
+
--- /dev/null
+++ b/tools/testing/selftests/readfile/readfile_speed.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+ * Copyright (c) 2020 The Linux Foundation
+ *
+ * Tiny test program to try to benchmark the speed of the readfile syscall vs.
+ * the open/read/close sequence it replaces.
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <string.h>
+#include <syscall.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+//#ifndef __NR_readfile
+//#define __NR_readfile -1
+//#endif
+
+#define __NR_readfile 440
+
+#define TEST_FILE "/sys/devices/system/cpu/vulnerabilities/meltdown"
+
+static int sys_readfile(int fd, const char *filename, unsigned char *buffer,
+ size_t bufsize, int flags)
+{
+ return syscall(__NR_readfile, fd, filename, buffer, bufsize, flags);
+}
+
+/* taken from all-io.h from util-linux repo */
+static inline ssize_t read_all(int fd, unsigned char *buf, size_t count)
+{
+ ssize_t ret;
+ ssize_t c = 0;
+ int tries = 0;
+
+ //memset(buf, 0, count);
+ while (count > 0) {
+ ret = read(fd, buf, count);
+ if (ret <= 0) {
+ if (ret < 0 && (errno == EAGAIN || errno == EINTR) &&
+ (tries++ < 5)) {
+ usleep(250000);
+ continue;
+ }
+ return c ? c : -1;
+ }
+ tries = 0;
+ count -= ret;
+ buf += ret;
+ c += ret;
+ }
+ return c;
+}
+
+static int openreadclose(const char *filename, unsigned char *buffer,
+ size_t bufsize)
+{
+ size_t count;
+ int fd;
+
+ fd = openat(0, filename, O_RDONLY);
+ if (fd < 0) {
+ printf("error opening %s\n", filename);
+ return fd;
+ }
+
+ count = read_all(fd, buffer, bufsize);
+ if (count < 0) {
+ printf("Error %ld reading from %s\n", count, filename);
+ }
+
+ close(fd);
+ return count;
+}
+
+enum test_type {
+ READFILE,
+ OPENREADCLOSE,
+};
+
+static int do_read_file_test(int loops, enum test_type test_type,
+ const char *filename,
+ unsigned char *buffer, size_t bufsize)
+{
+ char *test;
+ int retval;
+ int i;
+
+ switch (test_type) {
+ case READFILE:
+ test = "readfile";
+ break;
+
+ case OPENREADCLOSE:
+ test = "open/read/close";
+ break;
+ default:
+ fprintf(stderr, "wrong test type");
+ return -1;
+ }
+
+ fprintf(stdout,
+ "Running %s test on file %s for %d loops...\n",
+ test, filename, loops);
+
+ for (i = 0; i < loops; ++i) {
+ switch (test_type) {
+ case READFILE:
+ retval = sys_readfile(0, filename, buffer, bufsize, O_RDONLY);
+ break;
+
+ case OPENREADCLOSE:
+ retval = openreadclose(filename, buffer, bufsize);
+ break;
+ }
+ if (retval < 0) {
+ fprintf(stderr,
+ "test failed on loop %d with error %d\n",
+ i, retval);
+ return retval;
+ }
+ }
+ return 0;
+}
+
+static int check_file_present(const char *filename)
+{
+ struct stat sb;
+ int retval;
+
+ retval = stat(filename, &sb);
+ if (retval == -1) {
+ fprintf(stderr,
+ "filename %s is not present\n", filename);
+ return retval;
+ }
+
+ if ((sb.st_mode & S_IFMT) != S_IFREG) {
+ fprintf(stderr,
+ "filename %s must be a real file, not anything else.\n",
+ filename);
+ return -1;
+ }
+ return 0;
+}
+
+static void usage(char *progname)
+{
+ fprintf(stderr,
+ "usage: %s [options]\n"
+ " -l loops Number of loops to run the test for.\n"
+ " default is 10'000\n"
+ " -t testtype Test type to run\n"
+ " types are: readfile, openreadclose\n"
+ " default is readfile\n",
+ progname);
+}
+
+int main(int argc, char *argv[])
+{
+ int loops = 10000;
+ unsigned char buffer[10000];
+ char c;
+ char *testtype = "readfile";
+ char *progname;
+ char *filename;
+ enum test_type test_type;
+ int retval;
+
+ progname = strrchr(argv[0], '/');
+ progname = progname ? 1+progname : argv[0];
+
+ while (EOF != (c = getopt(argc, argv, "t:hl:"))) {
+ switch (c) {
+ case 'l':
+ loops = atoi(optarg);
+ break;
+
+ case 't':
+ testtype = optarg;
+ break;
+
+ case 'h':
+ usage(progname);
+ return 0;
+
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+
+ if (strcmp(testtype, "readfile") == 0)
+ test_type = READFILE;
+ else if (strcmp(testtype, "openreadclose") == 0)
+ test_type = OPENREADCLOSE;
+ else {
+ usage(progname);
+ return -1;
+ }
+
+ filename = TEST_FILE;
+
+ retval = check_file_present(filename);
+ if (retval)
+ return retval;
+
+ retval = do_read_file_test(loops, test_type, TEST_FILE,
+ &buffer[0], sizeof(buffer));
+
+ return retval;
+}