+/*
+ * Copyright (c) 2017 Red Hat Inc. All Rights Reserved.
+ *
+ * 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.
+ *
+ * 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, write the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * 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, %lu)\n", buf_wr, blksz, 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, %lu)\n", buf_rd, blksz, 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);
+}