[LTP] [PATCH v2] Add read_all file systems test
Richard Palethorpe
rpalethorpe@suse.de
Tue Feb 13 14:12:36 CET 2018
Hello,
Cyril Hrubis writes:
> Hi!
>> +#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 *quite;
> ^
> quiet?
>
>> +static char *root_dir;
>> +static char *exclude;
>> +static char *str_repeat;
>> +static int repeat = 1;
>> +static long worker_count;
>> +static struct worker *workers;
>> +
>> +static struct tst_option options[] = {
>> + {"v", &verbose,
>> + "-v Print information about successful reads"},
>> + {"q", &quite,
>> + "-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_repeat,
>> + "-r count The number of times to schedule a file for reading"},
>
> I'm not sure repeat is the right name for parameter, I do not have much
> better idea though, maybe iteration.
>
>> + {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);
>> +
>> + while (buf[j]) {
>> + q->data[i] = buf[j];
>> +
>> + ++j;
>> + if (++i >= QUEUE_SIZE)
>> + i = 0;
>> + if (i == front)
>> + return 0;
>> + }
>> + q->data[i] = '\0';
>> +
>> + q->back = i + 1;
>> + 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 && !quite) {
>> + tst_res(TINFO | TERRNO, "open(%s)", path);
>> + return;
>> + } else if (fd < 0)
>> + return;
>
> I guess that this would be a bit more readable:
>
>
> if (fd < 0) {
> if (!quiet)
> tst_res(...)
> 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 && !quite)
>> + tst_res(TINFO | TERRNO, "read(%s)", path);
>> +
>> + SAFE_CLOSE(fd);
>> +}
>> +
>> +static int worker_run(struct worker *self)
>> +{
>> + int ret;
>> + 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);
>> +
>> + for (ret = queue_pop(q, buf); ret; ret = queue_pop(q, buf))
>> + read_test(buf);
>
> As much as I do like to strech for loop for everything this would be
> probably more elegant as an infinite loop with a break.
>
> for (;;) {
> if (!queue_pop(q, buf))
> break;
>
> read_test(buf);
> }
>
>> + queue_destroy(q, 1);
>> + fflush(stdout);
>
> What are trying to flush here? The tst_res() messages are printed
> into the stderr btw.
I found that the pass message was being written after some of the
childrens' information messages. Calling fflush on stdout prevents that
from happening even though it is the wrong fd...
I have changed it to stderr, which seems more correct.
>
>> + 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;
>> +
>> + if (!workers)
>> + return;
>> +
>> + for (i = 0; i < worker_count; i++)
>> + if (workers[i].q)
>> + queue_push(workers[i].q, stop_code);
>> +
>> + for (i = 0; i < worker_count; i++)
>> + if (workers[i].q) {
>> + queue_destroy(workers[i].q, 0);
>> + workers[i].q = 0;
>> + }
>
> FYI LKML coding style preferes curly braces around the block in a case
> that it spans over multiple lines.
>
>> +}
>> +
>> +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_repeat, &repeat, 1, INT_MAX))
>> + tst_brk(TBROK,
>> + "Invalid repeat (-r) argument: '%s'", str_repeat);
>
>> + if (!root_dir)
>> + tst_brk(TBROK, "The directory argument (-d) is required");
>> +
>> + worker_count = MIN(MAX(SAFE_SYSCONF(_SC_NPROCESSORS_ONLN) - 1, 1), 15);
>
> We do have tst_ncpus() because there are older distros that do not
> define the _SC_* macros, please use that one instead here.
>
> Also we cap the number of workers on 15 here, shouldn't that be at least
> macro constant? I also wonder if we should supply command line option to
> override the number of workers.
>
>> + workers = SAFE_MALLOC(worker_count * sizeof(*workers));
>> +}
>> +
>> +static void cleanup(void)
>> +{
>> + stop_workers();
>
> We are stopping the workers at the end of the run(), do we really need
> to stop them here as well?
spawn_workers calls SAFE_FORK which could fail after a few workers have
already been spawned.
>
>> + 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, repeat);
>> + }
>> +
>> + 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,
>> +};
>
> The rest looks good to me.
--
Thank you,
Richard.
More information about the ltp
mailing list