// 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 #include #include #include #include #include #include #include #include #include #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); }