| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * Copyright (c) 2017 Red Hat Inc. All Rights Reserved. | 
 |  */ | 
 |  | 
 | /* | 
 |  * Fork N children, each child writes to and reads from its own region of the | 
 |  * same test file, and check if what it reads is what it writes. The test | 
 |  * region is determined by N * blksz. Write and read operation can be either | 
 |  * direct or buffered. | 
 |  */ | 
 |  | 
 | #ifndef _GNU_SOURCE | 
 | #define _GNU_SOURCE | 
 | #endif | 
 | #include <sys/file.h> | 
 | #include <sys/types.h> | 
 | #include <sys/wait.h> | 
 | #include <errno.h> | 
 | #include <fcntl.h> | 
 | #include <signal.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <unistd.h> | 
 |  | 
 | #define DEF_BLKSZ 4096 | 
 |  | 
 | int verbose = 0; | 
 |  | 
 | static void usage(const char *prog) | 
 | { | 
 | 	fprintf(stderr, "Usage: %s [-Fhptrwv] [-b blksz] [-n nr_child] [-i iterations] [-o offset] <-f filename>\n", prog); | 
 | 	fprintf(stderr, "\t-F\tPreallocate all blocks by writing them before test\n"); | 
 | 	fprintf(stderr, "\t-p\tPreallocate all blocks using fallocate(2) before test\n"); | 
 | 	fprintf(stderr, "\t-t\tTruncate test file to largest size before test\n"); | 
 | 	fprintf(stderr, "\t-r\tDo direct read\n"); | 
 | 	fprintf(stderr, "\t-w\tDo direct write\n"); | 
 | 	fprintf(stderr, "\t-v\tBe verbose\n"); | 
 | 	fprintf(stderr, "\t-h\tshow this help message\n"); | 
 | 	exit(EXIT_FAILURE); | 
 | } | 
 |  | 
 | static int cmpbuf(char *b1, char *b2, int bsize) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < bsize; i++) { | 
 | 		if (b1[i] != b2[i]) { | 
 | 			fprintf(stderr, "cmpbuf: offset %d: Expected: 0x%x," | 
 | 				" got 0x%x\n", i, b1[i], b2[i]); | 
 | 			return 1; | 
 | 		} | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void kill_children(pid_t *pids, int nr_child) | 
 | { | 
 | 	int i; | 
 | 	pid_t pid; | 
 |  | 
 | 	for (i = 0; i < nr_child; i++) { | 
 | 		pid = pids[i]; | 
 | 		if (pid == 0) | 
 | 			continue; | 
 | 		kill(pid, SIGTERM); | 
 | 	} | 
 | } | 
 |  | 
 | static int wait_children(pid_t *pids, int nr_child) | 
 | { | 
 | 	int i, status, ret = 0; | 
 | 	pid_t pid; | 
 |  | 
 | 	for (i = 0; i < nr_child; i++) { | 
 | 		pid = pids[i]; | 
 | 		if (pid == 0) | 
 | 			continue; | 
 | 		waitpid(pid, &status, 0); | 
 | 		ret += WEXITSTATUS(status); | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void dumpbuf(char *buf, int size, int blksz) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	printf("dumping buffer content\n"); | 
 | 	for (i = 0; i < size; i++) { | 
 | 		if (((i % blksz) == 0) || ((i % 64) == 0)) | 
 | 			putchar('\n'); | 
 | 		printf("%x", buf[i]); | 
 | 	} | 
 | 	putchar('\n'); | 
 | } | 
 |  | 
 | static int run_test(const char *filename, int n_child, int blksz, off_t offset, | 
 | 		    int nr_iter, int flag_rd, int flag_wr) | 
 | { | 
 | 	char *buf_rd; | 
 | 	char *buf_wr; | 
 | 	off_t seekoff; | 
 | 	int fd_rd, fd_wr; | 
 | 	int i, ret; | 
 | 	long page_size; | 
 |  | 
 | 	seekoff = offset + blksz * n_child; | 
 |  | 
 | 	page_size = sysconf(_SC_PAGESIZE); | 
 | 	ret = posix_memalign((void **)&buf_rd, (size_t)page_size, | 
 | 		blksz > page_size ? blksz : (size_t)page_size); | 
 | 	if (ret) { | 
 | 		fprintf(stderr, "posix_memalign(buf_rd, %d, %d) failed: %d\n", | 
 | 			blksz, blksz, ret); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 | 	memset(buf_rd, 0, blksz); | 
 | 	ret = posix_memalign((void **)&buf_wr, (size_t)page_size, | 
 | 		blksz > page_size ? blksz : (size_t)page_size); | 
 | 	if (ret) { | 
 | 		fprintf(stderr, "posix_memalign(buf_wr, %d, %d) failed: %d\n", | 
 | 			blksz, blksz, ret); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 | 	memset(buf_wr, 0, blksz); | 
 |  | 
 | 	fd_rd = open(filename, flag_rd); | 
 | 	if (fd_rd < 0) { | 
 | 		perror("open readonly for read"); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 |  | 
 | 	fd_wr = open(filename, flag_wr); | 
 | 	if (fd_wr < 0) { | 
 | 		perror("open writeonly for direct write"); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 |  | 
 | #define log(format, ...) 			\ | 
 | 	if (verbose) {				\ | 
 | 		printf("[%d:%d] ", n_child, i);	\ | 
 | 		printf(format, __VA_ARGS__);	\ | 
 | 	} | 
 |  | 
 |  | 
 | 	/* seek, write, read and verify */ | 
 | 	for (i = 0; i < nr_iter; i++) { | 
 | 		memset(buf_wr, i + 1, blksz); | 
 | 		log("pwrite(fd_wr, %p, %d, %lld)\n", buf_wr, blksz, | 
 | 		    (long long) seekoff); | 
 | 		if (pwrite(fd_wr, buf_wr, blksz, seekoff) != blksz) { | 
 | 			perror("direct write"); | 
 | 			exit(EXIT_FAILURE); | 
 | 		} | 
 |  | 
 | 		/* make sure buffer write hits disk before direct read */ | 
 | 		if (!(flag_wr & O_DIRECT)) { | 
 | 			if (fsync(fd_wr) < 0) { | 
 | 				perror("fsync(fd_wr)"); | 
 | 				exit(EXIT_FAILURE); | 
 | 			} | 
 | 		} | 
 |  | 
 | 		log("pread(fd_rd, %p, %d, %lld)\n", buf_rd, blksz, | 
 | 		    (long long) seekoff); | 
 | 		if (pread(fd_rd, buf_rd, blksz, seekoff) != blksz) { | 
 | 			perror("buffer read"); | 
 | 			exit(EXIT_FAILURE); | 
 | 		} | 
 | 		if (cmpbuf(buf_wr, buf_rd, blksz) != 0) { | 
 | 			fprintf(stderr, "[%d:%d] FAIL - comparison failed, " | 
 | 				"offset %d\n", n_child, i, (int)seekoff); | 
 | 			if (verbose) | 
 | 				dumpbuf(buf_rd, blksz, blksz); | 
 | 			exit(EXIT_FAILURE); | 
 | 		} | 
 | 	} | 
 | 	exit(EXIT_SUCCESS); | 
 | } | 
 |  | 
 | int main(int argc, char *argv[]) | 
 | { | 
 | 	int nr_iter = 1; | 
 | 	int nr_child = 1; | 
 | 	int blksz = DEF_BLKSZ; | 
 | 	int fd, i, ret = 0; | 
 | 	int flag_rd = O_RDONLY; | 
 | 	int flag_wr = O_WRONLY; | 
 | 	int do_trunc = 0; | 
 | 	int pre_fill = 0; | 
 | 	int pre_alloc = 0; | 
 | 	pid_t pid; | 
 | 	pid_t *pids; | 
 | 	off_t offset = 0; | 
 | 	char *filename = NULL; | 
 |  | 
 | 	while ((i = getopt(argc, argv, "b:i:n:f:Fpo:tvrw")) != -1) { | 
 | 		switch (i) { | 
 | 		case 'b': | 
 | 			if ((blksz = atoi(optarg)) <= 0) { | 
 | 				fprintf(stderr, "blksz must be > 0\n"); | 
 | 				exit(EXIT_FAILURE); | 
 | 			} | 
 | 			if (blksz % 512 != 0) { | 
 | 				fprintf(stderr, "blksz must be multiple of 512\n"); | 
 | 				exit(EXIT_FAILURE); | 
 | 			} | 
 | 			break; | 
 | 		case 'i': | 
 | 			if ((nr_iter = atoi(optarg)) <= 0) { | 
 | 				fprintf(stderr, "iterations must be > 0\n"); | 
 | 				exit(EXIT_FAILURE); | 
 | 			} | 
 | 			break; | 
 | 		case 'n': | 
 | 			if ((nr_child = atoi(optarg)) <= 0) { | 
 | 				fprintf(stderr, "no of children must be > 0\n"); | 
 | 				exit(EXIT_FAILURE); | 
 | 			} | 
 | 			break; | 
 | 		case 'f': | 
 | 			filename = optarg; | 
 | 			break; | 
 | 		case 'F': | 
 | 			pre_fill = 1; | 
 | 			break; | 
 | 		case 'p': | 
 | 			pre_alloc = 1; | 
 | 			break; | 
 | 		case 'r': | 
 | 			flag_rd |= O_DIRECT; | 
 | 			break; | 
 | 		case 'w': | 
 | 			flag_wr |= O_DIRECT; | 
 | 			break; | 
 | 		case 't': | 
 | 			do_trunc = 1; | 
 | 			break; | 
 | 		case 'o': | 
 | 			if ((offset = atol(optarg)) < 0) { | 
 | 				fprintf(stderr, "offset must be >= 0\n"); | 
 | 				exit(EXIT_FAILURE); | 
 | 			} | 
 | 			break; | 
 | 		case 'v': | 
 | 			verbose = 1; | 
 | 			break; | 
 | 		case 'h':	/* fall through */ | 
 | 		default: | 
 | 			usage(argv[0]); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (filename == NULL) | 
 | 		usage(argv[0]); | 
 | 	if (pre_fill && pre_alloc) { | 
 | 		fprintf(stderr, "Error: -F and -p are both specified\n"); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 |  | 
 | 	pids = malloc(nr_child * sizeof(pid_t)); | 
 | 	if (!pids) { | 
 | 		fprintf(stderr, "failed to malloc memory for pids\n"); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 | 	memset(pids, 0, nr_child * sizeof(pid_t)); | 
 |  | 
 | 	/* create & truncate testfile first */ | 
 | 	fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0600); | 
 | 	if (fd < 0) { | 
 | 		perror("create & truncate testfile"); | 
 | 		free(pids); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 | 	if (do_trunc && (ftruncate(fd, blksz * nr_child) < 0)) { | 
 | 		perror("ftruncate failed"); | 
 | 		free(pids); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 | 	if (pre_fill) { | 
 | 		char *buf; | 
 | 		buf = malloc(blksz * nr_child); | 
 | 		memset(buf, 's', blksz * nr_child); | 
 | 		write(fd, buf, blksz * nr_child); | 
 | 		free(buf); | 
 | 	} | 
 | 	if (pre_alloc) { | 
 | 		fallocate(fd, 0, 0, blksz * nr_child); | 
 | 	} | 
 | 	fsync(fd); | 
 | 	close(fd); | 
 |  | 
 | 	/* fork workers */ | 
 | 	for (i = 0; i < nr_child; i++) { | 
 | 		pid = fork(); | 
 | 		if (pid < 0) { | 
 | 			perror("fork"); | 
 | 			kill_children(pids, nr_child); | 
 | 			free(pids); | 
 | 			exit(EXIT_FAILURE); | 
 | 		} else if (pid == 0) { | 
 | 			/* never returns */ | 
 | 			run_test(filename, i, blksz, offset, nr_iter, | 
 | 				 flag_rd, flag_wr); | 
 | 		} else { | 
 | 			pids[i] = pid; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	ret = wait_children(pids, nr_child); | 
 | 	free(pids); | 
 | 	exit(ret); | 
 | } |