[LTP] [PATCH v2] Add read_all file systems test

Cyril Hrubis chrubis@suse.cz
Mon Jan 22 14:21:23 CET 2018


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.

> +	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?

> +	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.

-- 
Cyril Hrubis
chrubis@suse.cz


More information about the ltp mailing list