[3/3] selftests: add readfile(2) selftests
diff mbox series

Message ID 20200704140250.423345-4-gregkh@linuxfoundation.org
State New
Headers show
Series
  • readfile(2): a new syscall to make open/read/close faster
Related show

Commit Message

Greg KH July 4, 2020, 2:02 p.m. UTC
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

Comments

Geert Uytterhoeven July 4, 2020, 6:38 p.m. UTC | #1
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
Heinrich Schuchardt July 5, 2020, 1:41 a.m. UTC | #2
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);
> +}
>
Greg KH July 5, 2020, 6:55 a.m. UTC | #3
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
Greg KH July 5, 2020, 7:34 a.m. UTC | #4
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
Heinrich Schuchardt July 5, 2020, 9:46 a.m. UTC | #5
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
Geert Uytterhoeven July 5, 2020, 11:24 a.m. UTC | #6
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
Greg KH July 5, 2020, 11:36 a.m. UTC | #7
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

Patch
diff mbox series

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