@@ -69,6 +69,10 @@ fs_di fs_di -d $TMPDIR
# Was not sure why it should reside in runtest/crashme and won´t get tested ever
proc01 proc01 -m 128
+read_all_dev read_all -d /dev -q -r 10
+read_all_proc read_all -d /proc -q -r 10
+read_all_sys read_all -d /sys -q -r 10
+
#Run the File System Race Condition Check tests as well
fs_racer fs_racer.sh -t 5
new file mode 100644
@@ -0,0 +1 @@
+read_all
new file mode 100644
@@ -0,0 +1,22 @@
+# Copyright (c) 2017 Linux Test Project
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it would be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+top_srcdir ?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+
+CFLAGS += -D_GNU_SOURCE -lpthread
+
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
new file mode 100644
@@ -0,0 +1,434 @@
+/*
+ * Copyright (c) 2017 Richard Palethorpe <rpalethorpe@suse.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Perform a small read on every file in a directory tree.
+ *
+ * Useful for testing file systems like proc, sysfs and debugfs or anything
+ * which exposes a file like API so long as it respects O_NONBLOCK. This test
+ * is not concerned if a particular file in one of these file systems conforms
+ * exactly to its specific documented behavior. Just whether reading from that
+ * file causes a serious error such as a NULL pointer dereference.
+ *
+ * It is not required to run this as root, but test coverage will be much
+ * higher with full privileges.
+ *
+ * The reads are preformed by worker processes which are given file paths by a
+ * single parent process. The parent process recursively scans a given
+ * directory and passes the file paths it finds to the child processes using a
+ * queue structure stored in shared memory.
+ *
+ * This allows the file system and individual files to be accessed in
+ * parallel. Passing the 'reads' parameter (-r) will encourage this. The
+ * number of worker processes is based on the number of available
+ * processors. However this is limited by default to 15 to avoid this becoming
+ * an IPC stress test on systems with large numbers of weak cores. This can be
+ * overridden with the 'w' parameters.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <fnmatch.h>
+#include <semaphore.h>
+#include <ctype.h>
+
+#include "tst_test.h"
+
+#define QUEUE_SIZE 16384
+#define BUFFER_SIZE 1024
+#define MAX_PATH 4096
+#define MAX_DISPLAY 40
+
+struct queue {
+ sem_t sem;
+ int front;
+ int back;
+ char data[QUEUE_SIZE];
+};
+
+struct worker {
+ pid_t pid;
+ struct queue *q;
+};
+
+enum dent_action {
+ DA_UNKNOWN,
+ DA_IGNORE,
+ DA_READ,
+ DA_VISIT,
+};
+
+static char *verbose;
+static char *quiet;
+static char *root_dir;
+static char *exclude;
+static char *str_reads;
+static int reads = 1;
+static char *str_worker_count;
+static long worker_count;
+static char *str_max_workers;
+static long max_workers = 15;
+static struct worker *workers;
+
+static struct tst_option options[] = {
+ {"v", &verbose,
+ "-v Print information about successful reads."},
+ {"q", &quiet,
+ "-q Don't print file read or open errors."},
+ {"d:", &root_dir,
+ "-d path Path to the directory to read from, defaults to /sys."},
+ {"e:", &exclude,
+ "-e pattern Ignore files which match an 'extended' pattern, see fnmatch(3)."},
+ {"r:", &str_reads,
+ "-r count The number of times to schedule a file for reading."},
+ {"w:", &str_max_workers,
+ "-w count Set the worker count limit, the default is 15."},
+ {"W:", &str_worker_count,
+ "-W count Override the worker count. Ignores (-w) and the processor count."},
+ {NULL, NULL, NULL}
+};
+
+static int queue_pop(struct queue *q, char *buf)
+{
+ int i = q->front, j = 0;
+
+ sem_wait(&q->sem);
+
+ if (!q->data[i])
+ return 0;
+
+ while (q->data[i]) {
+ buf[j] = q->data[i];
+
+ if (++j >= BUFFER_SIZE - 1)
+ tst_brk(TBROK, "Buffer is too small for path");
+ if (++i >= QUEUE_SIZE)
+ i = 0;
+ }
+
+ buf[j] = '\0';
+ tst_atomic_store(i + 1, &q->front);
+
+ return 1;
+}
+
+static int queue_push(struct queue *q, const char *buf)
+{
+ int i = q->back, j = 0;
+ int front = tst_atomic_load(&q->front);
+
+ do {
+ q->data[i] = buf[j];
+
+ if (++i >= QUEUE_SIZE)
+ i = 0;
+ if (i == front)
+ return 0;
+
+ } while (buf[j++]);
+
+ q->back = i;
+ sem_post(&q->sem);
+
+ return 1;
+}
+
+static struct queue *queue_init(void)
+{
+ struct queue *q = SAFE_MMAP(NULL, sizeof(*q),
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS,
+ 0, 0);
+
+ sem_init(&q->sem, 1, 0);
+ q->front = 0;
+ q->back = 0;
+
+ return q;
+}
+
+static void queue_destroy(struct queue *q, int is_worker)
+{
+ if (is_worker)
+ sem_destroy(&q->sem);
+
+ SAFE_MUNMAP(q, sizeof(*q));
+}
+
+static void sanitize_str(char *buf, ssize_t count)
+{
+ int i;
+
+ for (i = 0; i < MIN(count, MAX_DISPLAY); i++)
+ if (!isprint(buf[i]))
+ buf[i] = ' ';
+
+ if (count <= MAX_DISPLAY)
+ buf[count] = '\0';
+ else
+ strcpy(buf + MAX_DISPLAY, "...");
+}
+
+static void read_test(const char *path)
+{
+ char buf[BUFFER_SIZE];
+ int fd;
+ ssize_t count;
+
+ if (exclude && !fnmatch(exclude, path, FNM_EXTMATCH)) {
+ if (verbose)
+ tst_res(TINFO, "Ignoring %s", path);
+ return;
+ }
+
+ if (verbose)
+ tst_res(TINFO, "%s(%s)", __func__, path);
+
+ fd = open(path, O_RDONLY | O_NONBLOCK);
+ if (fd < 0) {
+ if (!quiet)
+ tst_res(TINFO | TERRNO, "open(%s)", path);
+ return;
+ }
+
+ count = read(fd, buf, sizeof(buf) - 1);
+ if (count > 0 && verbose) {
+ sanitize_str(buf, count);
+ tst_res(TINFO, "read(%s, buf) = %ld, buf = %s",
+ path, count, buf);
+ } else if (!count && verbose) {
+ tst_res(TINFO, "read(%s) = EOF", path);
+ } else if (count < 0 && !quiet) {
+ tst_res(TINFO | TERRNO, "read(%s)", path);
+ }
+
+ SAFE_CLOSE(fd);
+}
+
+static int worker_run(struct worker *self)
+{
+ char buf[BUFFER_SIZE];
+ struct sigaction term_sa = {
+ .sa_handler = SIG_IGN,
+ .sa_flags = 0,
+ };
+ struct queue *q = self->q;
+
+ sigaction(SIGTTIN, &term_sa, NULL);
+
+ while (1) {
+ if (!queue_pop(q, buf))
+ break;
+
+ read_test(buf);
+ }
+
+ queue_destroy(q, 1);
+ fflush(stderr);
+ return 0;
+}
+
+static void spawn_workers(void)
+{
+ int i;
+ struct worker *wa = workers;
+
+ bzero(workers, worker_count * sizeof(*workers));
+
+ for (i = 0; i < worker_count; i++) {
+ wa[i].q = queue_init();
+ wa[i].pid = SAFE_FORK();
+ if (!wa[i].pid)
+ exit(worker_run(wa + i));
+ }
+}
+
+static void stop_workers(void)
+{
+ const char stop_code[1] = { '\0' };
+ int i, stop_attempts;
+
+ if (!workers)
+ return;
+
+ for (i = 0; i < worker_count; i++) {
+ stop_attempts = 0xffff;
+ if (workers[i].q) {
+ while (!queue_push(workers[i].q, stop_code)) {
+ if (--stop_attempts < 0) {
+ tst_brk(TBROK,
+ "Worker %d is stalled",
+ workers[i].pid);
+ break;
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < worker_count; i++) {
+ if (workers[i].q) {
+ queue_destroy(workers[i].q, 0);
+ workers[i].q = 0;
+ }
+ }
+}
+
+static void sched_work(const char *path)
+{
+ static int cur;
+ int push_attempts = 0, pushed;
+
+ while (1) {
+ pushed = queue_push(workers[cur].q, path);
+
+ if (++cur >= worker_count)
+ cur = 0;
+
+ if (pushed)
+ break;
+
+ if (++push_attempts > worker_count) {
+ usleep(100);
+ push_attempts = 0;
+ }
+ }
+}
+
+static void rep_sched_work(const char *path, int rep)
+{
+ int i;
+
+ for (i = 0; i < rep; i++)
+ sched_work(path);
+}
+
+static void setup(void)
+{
+ if (tst_parse_int(str_reads, &reads, 1, INT_MAX))
+ tst_brk(TBROK,
+ "Invalid reads (-r) argument: '%s'", str_reads);
+
+ if (tst_parse_long(str_max_workers, &max_workers, 1, LONG_MAX)) {
+ tst_brk(TBROK,
+ "Invalid max workers (-w) argument: '%s'",
+ str_max_workers);
+ }
+
+ if (tst_parse_long(str_worker_count, &worker_count, 1, LONG_MAX)) {
+ tst_brk(TBROK,
+ "Invalid worker count (-W) argument: '%s'",
+ str_worker_count);
+ }
+
+ if (!root_dir)
+ tst_brk(TBROK, "The directory argument (-d) is required");
+
+ if (!worker_count)
+ worker_count = MIN(MAX(tst_ncpus() - 1, 1), max_workers);
+ workers = SAFE_MALLOC(worker_count * sizeof(*workers));
+}
+
+static void cleanup(void)
+{
+ stop_workers();
+ free(workers);
+}
+
+static void visit_dir(const char *path)
+{
+ DIR *dir;
+ struct dirent *dent;
+ struct stat dent_st;
+ char dent_path[MAX_PATH];
+ enum dent_action act;
+
+ dir = opendir(path);
+ if (!dir) {
+ tst_res(TINFO | TERRNO, "opendir(%s)", path);
+ return;
+ }
+
+ while (1) {
+ errno = 0;
+ dent = readdir(dir);
+ if (!dent && errno) {
+ tst_res(TINFO | TERRNO, "readdir(%s)", path);
+ break;
+ } else if (!dent) {
+ break;
+ }
+
+ if (!strcmp(dent->d_name, ".") ||
+ !strcmp(dent->d_name, ".."))
+ continue;
+
+ if (dent->d_type == DT_DIR)
+ act = DA_VISIT;
+ else if (dent->d_type == DT_LNK)
+ act = DA_IGNORE;
+ else if (dent->d_type == DT_UNKNOWN)
+ act = DA_UNKNOWN;
+ else
+ act = DA_READ;
+
+ snprintf(dent_path, MAX_PATH,
+ "%s/%s", path, dent->d_name);
+
+ if (act == DA_UNKNOWN) {
+ if (lstat(dent_path, &dent_st))
+ tst_res(TINFO | TERRNO, "lstat(%s)", path);
+ else if ((dent_st.st_mode & S_IFMT) == S_IFDIR)
+ act = DA_VISIT;
+ else if ((dent_st.st_mode & S_IFMT) == S_IFLNK)
+ act = DA_IGNORE;
+ else
+ act = DA_READ;
+ }
+
+ if (act == DA_VISIT)
+ visit_dir(dent_path);
+ else if (act == DA_READ)
+ rep_sched_work(dent_path, reads);
+ }
+
+ if (closedir(dir))
+ tst_res(TINFO | TERRNO, "closedir(%s)", path);
+}
+
+static void run(void)
+{
+ spawn_workers();
+ visit_dir(root_dir);
+ stop_workers();
+
+ tst_reap_children();
+ tst_res(TPASS, "Finished reading files");
+}
+
+static struct tst_test test = {
+ .options = options,
+ .setup = setup,
+ .cleanup = cleanup,
+ .test_all = run,
+ .forks_child = 1,
+};
+
While using a shell script I wrote, which dumps the contents of /sys and /proc (submitted in a separate patch), I found some minor kernel bugs. There is already a test specifically for /proc however it is attempting to verify the behavior of particular files. This test is more general and can be applied to any file system. Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com> --- V4: * Move lpthread to CFLAGS. * Handle edge case where it may not be possible to add the stop code to a queue. runtest/fs | 4 + testcases/kernel/fs/read_all/.gitignore | 1 + testcases/kernel/fs/read_all/Makefile | 22 ++ testcases/kernel/fs/read_all/read_all.c | 434 ++++++++++++++++++++++++++++++++ 4 files changed, 461 insertions(+) create mode 100644 testcases/kernel/fs/read_all/.gitignore create mode 100644 testcases/kernel/fs/read_all/Makefile create mode 100644 testcases/kernel/fs/read_all/read_all.c