[LTP] [PATCH] Add read_all01 test
Richard Palethorpe
rpalethorpe@suse.de
Wed Jan 10 14:05:50 CET 2018
Hello,
Cyril Hrubis writes:
> Hi!
>> # Was not sure why it should reside in runtest/crashme and won??t get tested ever
>> proc01 proc01 -m 128
>>
>> +read_all01_dev read_all01 -d /dev
>> +read_all01_proc read_all01 -d /proc
>> +read_all01_sys read_all01
>
> I find it a little bit confusing that we default to /sys, I wonder if
> printing error in case that user haven't supplied the path would be
> better.
OK.
>
> And mabe we can drop the 01 from the name, or do you plan to write
> more tests likes this one?
Maybe read_all02 could take a dictionary with callbacks for handling
particular files. I think that even if we just have one executable which
takes various arguments to perform different tests it might be good to
number the tests in the runtest file.
I am not against dropping the 01 from the .c file though.
>
>> diff --git a/testcases/kernel/fs/read_all/.gitignore b/testcases/kernel/fs/read_all/.gitignore
>> new file mode 100644
>> index 000000000..4b405760c
>> --- /dev/null
>> +++ b/testcases/kernel/fs/read_all/.gitignore
>> @@ -0,0 +1 @@
>> +read_all01
>> diff --git a/testcases/kernel/fs/read_all/Makefile b/testcases/kernel/fs/read_all/Makefile
>> new file mode 100644
>> index 000000000..f51fbe591
>> --- /dev/null
>> +++ b/testcases/kernel/fs/read_all/Makefile
>> @@ -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
>> +
>> +include $(top_srcdir)/include/mk/generic_leaf_target.mk
>> diff --git a/testcases/kernel/fs/read_all/read_all01.c b/testcases/kernel/fs/read_all/read_all01.c
>> new file mode 100644
>> index 000000000..f746db33b
>> --- /dev/null
>> +++ b/testcases/kernel/fs/read_all/read_all01.c
>> @@ -0,0 +1,288 @@
>> +/*
>> + * 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 through
>> + * a pipe. The test will use a maximum of 15 processes, depending on the
>> + * number of CPU cores available, under the assumption that while parallelism
>> + * is good we don't want to spend too much time creating processes,
>> + * distributing data and waiting for locks.
>
> I wonder if we could make this faster with threads and semaphore over
> circular string buffer. Design with processes and pipes resembless the
> shell version quite a lot...
I suppose so. I would like to keep processes as well as threads though,
but I could communicate using shared memory instead of pipes.
>
>> +#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 "tst_test.h"
>> +#include "tst_minmax.h"
>
> Isn't the tst_minmax.h included in tst_test.h?
>
> I think that it needs to be included separately only for oldlib tests.
>
>> +#define BUFFER_SIZE 1024
>> +#define MAX_PATH 4096
>> +
>> +struct worker {
>> + pid_t pid;
>> + int in;
>> + int out;
>> +};
>> +
>> +static char *verbose;
>> +static char *root_dir = "/sys";
>> +static char *exclude;
>> +static long worker_count;
>> +static struct worker *workers;
>> +
>> +static struct tst_option options[] = {
>> + {"v", &verbose,
>> + "-v Print information about successful reads"},
>> + {"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)"},
>> + {NULL, NULL, NULL}
>> +};
>> +
>> +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) {
>> + tst_res(TINFO | TERRNO, "open(%s)", path);
>> + return;
>> + }
>> +
>> + count = read(fd, buf, sizeof(buf) - 1);
>> + if (count > 0 && verbose) {
>> + if (count <= 20) {
>> + if (buf[count - 1] == '\n')
>> + buf[count - 1] = '\0';
>> + else
>> + buf[count] = '\0';
>> + } else
>> + strcpy(buf + 20, "...");
>> +
>> + tst_res(TINFO, "read(%s, buf) = %ld, buf = %s",
>> + path, count, buf);
>
> We should probably make sure here that the buffer contains printable
> characters (isprint() ?) otherwise we may print some terminal controll
> characters.
>
>> + } else if (!count && verbose)
>> + tst_res(TINFO, "read(%s) = EOF", path);
>> + else if (count < 0)
>> + tst_res(TINFO | TERRNO, "read(%s)", path);
>> +
>> + SAFE_CLOSE(fd);
>> +}
>> +
>> +static int worker_run(struct worker *self)
>> +{
>> + ssize_t i, j, ret, count;
>> + char buf[PIPE_BUF];
>> + struct sigaction term_sa = {
>> + .sa_handler = SIG_IGN,
>> + .sa_flags = 0,
>> + };
>> +
>> + sigaction(SIGTTIN, &term_sa, NULL);
>> +
>> + for (count = 0;;) {
>> + ret = read(self->in, buf + count, sizeof(buf) - 1 - count);
>> + if (ret < 0 && errno == EINTR)
>> + continue;
>> + else if (ret < 0)
>> + tst_res(TBROK | TERRNO,
>> + "Worker can not read from pipe");
>> + else if (ret == 0)
>> + break;
>> +
>> + count += ret;
>> +
>> + for (i = 0, j = 0; i < count; i++) {
>> + if (buf[i] == '\n') {
>> + buf[i] = '\0';
>> + read_test(buf + j);
>> + j = i + 1;
>> + }
>> + }
>> +
>> + count = i - j;
>> + memmove(buf, buf + j, count);
>> + if (sizeof(buf) - 1 - count < 1)
>> + tst_brk(TBROK,
>> + "Worker receive buffer is too small for path");
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static void spawn_workers(void)
>> +{
>> + int i, j;
>> + int pipe[2];
>> + struct worker *wa = workers;
>> +
>> + bzero(workers, worker_count * sizeof(*workers));
>> +
>> + for (i = 0; i < worker_count; i++) {
>> + SAFE_PIPE(pipe);
>> + wa[i].in = pipe[0];
>> + wa[i].out = pipe[1];
>> + wa[i].pid = SAFE_FORK();
>> + if (!wa[i].pid) {
>> + for (j = 0; j <= i; j++)
>> + SAFE_CLOSE(wa[j].out);
>> + exit(worker_run(wa + i));
>> + } else {
>> + SAFE_CLOSE(wa[i].in);
>> + }
>> + }
>> +}
>> +
>> +static void stop_workers(void)
>> +{
>> + int i;
>> +
>> + for (i = 0; workers && i < worker_count; i++) {
>> + if (workers[i].out > 0)
>> + SAFE_CLOSE(workers[i].out);
>> + }
>> +}
>> +
>> +static void sched_work(const char *path)
>> +{
>> + static long cur;
>> +
>> + SAFE_WRITE(1, workers[cur].out, path, strlen(path));
>> + cur++;
>> + if (cur >= worker_count)
>> + cur = 0;
>> +}
>> +
>> +static void setup(void)
>> +{
>> + worker_count = MIN(MAX(SAFE_SYSCONF(_SC_NPROCESSORS_ONLN) - 1, 1), 15);
>> + workers = (struct worker *)SAFE_MALLOC(worker_count * sizeof(*workers));
> ^
> Please no useless casts like this one.
>> +}
>> +
>> +static void cleanup(void)
>> +{
>> + stop_workers();
>> +
>> + if (workers)
>> + free(workers);
>
> Again, free(NULL) is no-op, no need for the if () here.
>
>> +}
>> +
>> +static void visit_dir(const char *path)
>> +{
>> + DIR *dir;
>> + struct dirent *dent;
>> + struct stat dent_st;
>> + char dent_path[MAX_PATH];
>> +
>> + dir = opendir(path);
>> + if (!dir) {
>> + tst_res(TINFO | TERRNO, "opendir(%s)", path);
>> + return;
>> + }
>> +
>> + for (dent = SAFE_READDIR(dir); dent; dent = SAFE_READDIR(dir)) {
>> + if (!strcmp(dent->d_name, ".") ||
>> + !strcmp(dent->d_name, ".."))
>> + continue;
>> +
>> + if (dent->d_type != DT_UNKNOWN) {
>> + switch (dent->d_type) {
>> + case DT_DIR:
>> + snprintf(dent_path, MAX_PATH,
>> + "%s/%s", path, dent->d_name);
>> + visit_dir(dent_path);
>> + break;
>> + case DT_LNK:
>> + break;
>> + default:
>> + snprintf(dent_path, MAX_PATH,
>> + "%s/%s\n", path, dent->d_name);
>> + sched_work(dent_path);
>> + }
>> + } else {
>> + SAFE_LSTAT(dent_path, &dent_st);
> ^
> Shouldn't we initialize the
> dent_path with snprintf() first?
doh
>
>> + switch (dent_st.st_mode & S_IFMT) {
>> + case S_IFDIR:
>> + snprintf(dent_path, MAX_PATH,
>> + "%s/%s", path, dent->d_name);
>> + visit_dir(dent_path);
>> + break;
>> + case S_IFLNK:
>> + break;
>> + default:
>> + snprintf(dent_path, MAX_PATH,
>> + "%s/%s\n", path, dent->d_name);
>> + sched_work(dent_path);
>> + }
>> + }
>> + }
>> +
>> + SAFE_CLOSEDIR(dir);
>> +}
>> +
>> +static void run(void)
>> +{
>> + spawn_workers();
>> +
>> + visit_dir(root_dir);
>> + tst_res(TPASS, "Finished scanning directory tree");
>
> I also wonder if it would be beneficial to run several visit_dir()
> threads here in order to try to trigger race conditions when the same
> file is opened several times.
>
> I would probable have created abstract rreader (recursive reader) API
> so that we can initialize/allocate rreader then request it to run on a
> directory with a given number of threads and then deinit/free it at the
> end. Then we can run several of these in parallel even for a given
> subdirectory.
>
That seems reasonable; the user could specify the number of directory
reads they want to do in parallel and then each directory reader could
queue up work for the file readers.
--
Thank you,
Richard.
More information about the ltp
mailing list