| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2000-2003, 2010 SGI |
| * All Rights Reserved. |
| */ |
| |
| #include <inttypes.h> |
| |
| #include "global.h" |
| |
| #define power_of_2(x) ((x) && !((x) & ((x) - 1))) |
| #define DEFAULT_FILESIZE ((uint64_t) (256 * 1024 * 1024)) |
| #define DEFAULT_BLOCKSIZE 512 |
| |
| #define SETBIT(ARRAY, N) ((ARRAY)[(N)/8] |= (1 << ((N)%8))) |
| #define BITVAL(ARRAY, N) ((ARRAY)[(N)/8] & (1 << ((N)%8))) |
| |
| /* Bit-vector array showing which blocks have been written */ |
| static unsigned char *valid; |
| |
| static uint64_t filesize; |
| static uint64_t fileoffset; |
| |
| static unsigned int blocksize; |
| static int count; |
| static int verbose; |
| static int wsync; |
| static int direct; |
| static int alloconly; |
| static int rt; |
| static int extsize; /* used only for real-time */ |
| static int preserve; |
| static int test; |
| |
| #define READ_XFER 256 /* blocks to read at a time when checking */ |
| |
| /* |
| * Define xfscntl() to mask the difference between the Linux |
| * and the Irix fcntl() interfaces to XFS for user space. The |
| * "cmd" argument is just the last part of the command, e.g. |
| * pass FSGETXATTR in place of either XFS_IOC_FSGETXATTR (Linux) |
| * F_FSGETXATTR (Irix). |
| * |
| */ |
| # define xfscntl(filename, fd, cmd, arg) \ |
| xfsctl((filename), (fd), XFS_IOC_ ## cmd, (arg)) |
| |
| static void |
| usage(char *progname) |
| { |
| fprintf(stderr, |
| "usage: %s [-l filesize] [-b blocksize] [-c count]\n" |
| "\t\t[-o write_offset] [-s seed] [-r [-x extentsize]]\n" |
| "\t\t[-w] [-v] [-d] [-a] [-p] [-t] filename\n\n", |
| progname); |
| fprintf(stderr, "\tdefault filesize is %" PRIu64 " bytes\n", |
| DEFAULT_FILESIZE); |
| fprintf(stderr, "\tdefault blocksize is %u bytes\n", |
| DEFAULT_BLOCKSIZE); |
| fprintf(stderr, "\tdefault count is %d block-sized writes\n", |
| (int) (DEFAULT_FILESIZE / DEFAULT_BLOCKSIZE)); |
| fprintf(stderr, "\tdefault write_offset is %" PRIu64 " bytes\n", |
| (uint64_t) 0); |
| exit(1); |
| } |
| |
| /* Returns filename if successful or a null pointer if an error occurs */ |
| static char * |
| parseargs(int argc, char *argv[]) |
| { |
| int seed; |
| int ch; |
| |
| filesize = DEFAULT_FILESIZE; |
| blocksize = DEFAULT_BLOCKSIZE; |
| count = (int) filesize / blocksize; |
| verbose = 0; |
| wsync = 0; |
| seed = time(NULL); |
| test = 0; |
| while ((ch = getopt(argc, argv, "b:l:s:c:o:x:vwdrapt")) != EOF) { |
| switch(ch) { |
| case 'b': blocksize = atoi(optarg); break; |
| case 'l': filesize = strtoull(optarg, NULL, 16); break; |
| case 's': seed = atoi(optarg); break; |
| case 'c': count = atoi(optarg); break; |
| case 'o': fileoffset = strtoull(optarg, NULL, 16); break; |
| case 'x': extsize = atoi(optarg); break; |
| case 'v': verbose++; break; |
| case 'w': wsync++; break; |
| case 'd': direct++; break; |
| case 'r': rt++; direct++; break; |
| case 'a': alloconly++; break; |
| case 'p': preserve++; break; |
| case 't': test++; preserve++; break; |
| default: usage(argv[0]); break; |
| } |
| } |
| if (optind != argc - 1) |
| usage(argv[0]); |
| |
| if ((filesize % blocksize) != 0) { |
| filesize -= filesize % blocksize; |
| printf("filesize not a multiple of blocksize, " |
| "reducing filesize to %llu\n", |
| (unsigned long long) filesize); |
| } |
| if ((fileoffset % blocksize) != 0) { |
| fileoffset -= fileoffset % blocksize; |
| printf("fileoffset not a multiple of blocksize, " |
| "reducing fileoffset to %llu\n", |
| (unsigned long long) fileoffset); |
| } |
| if (count > (filesize/blocksize)) { |
| count = (filesize/blocksize); |
| printf("count of blocks written is too large, " |
| "setting to %d\n", count); |
| } else if (count < 1) { |
| count = 1; |
| printf("count of blocks written is too small, " |
| "setting to %d\n", count); |
| } |
| printf("randholes: Seed = %d (use \"-s %d\" " |
| "to re-execute this test)\n", seed, seed); |
| srandom(seed); |
| |
| printf("randholes: blocksize=%d, filesize=%llu, seed=%d\n" |
| "randholes: count=%d, offset=%llu, extsize=%d\n", |
| blocksize, (unsigned long long)filesize, seed, |
| count, (unsigned long long)fileoffset, extsize); |
| printf("randholes: verbose=%d, wsync=%d, direct=%d, " |
| "rt=%d, alloconly=%d, preserve=%d, test=%d\n", |
| verbose, wsync, direct ? 1 : 0, rt, alloconly, preserve, test); |
| |
| /* Last argument is the file name. Return it. */ |
| |
| return argv[optind]; /* Success */ |
| } |
| |
| /* |
| * Determine the next random block number to which to write. |
| * If an already-written block is selected, choose the next |
| * unused higher-numbered block. Returns the block number, |
| * or -1 if we exhaust available blocks looking for an unused |
| * one. |
| */ |
| static int |
| findblock(void) |
| { |
| int block, numblocks; |
| |
| numblocks = filesize / blocksize; |
| block = random() % numblocks; |
| |
| while (BITVAL(valid, block)) { |
| if (++block == numblocks) { |
| printf("returning block -1\n"); |
| return -1; |
| } |
| } |
| return block; |
| } |
| |
| static void |
| dumpblock(int *buffer, uint64_t offset, int blocksize) |
| { |
| int i; |
| |
| for (i = 0; i < (blocksize / 16); i++) { |
| printf("%llx: 0x%08x 0x%08x 0x%08x 0x%08x\n", |
| (unsigned long long) offset, *buffer, *(buffer + 1), |
| *(buffer + 2), *(buffer + 3)); |
| offset += 16; |
| buffer += 4; |
| } |
| } |
| |
| static void |
| writeblks(char *fname, int fd, size_t alignment) |
| { |
| uint64_t offset; |
| char *buffer = NULL; |
| int block; |
| int ret; |
| struct flock64 fl; |
| |
| if (!test) { |
| ret = posix_memalign((void **) &buffer, alignment, blocksize); |
| if (ret) { |
| fprintf(stderr, "posix_memalign: %s\n", strerror(ret)); |
| exit(1); |
| } |
| memset(buffer, 0, blocksize); |
| } |
| |
| /* |
| * Avoid allocation patterns being perturbed by different speculative |
| * preallocation beyond EOF configurations by first truncating the file |
| * to the expected maximum file size. |
| */ |
| if (ftruncate(fd, filesize) < 0) { |
| perror("ftruncate"); |
| exit(EXIT_FAILURE); |
| } |
| |
| do { |
| if (verbose && ((count % 100) == 0)) { |
| printf("."); |
| fflush(stdout); |
| } |
| block = findblock(); |
| if (block < 0) { |
| perror("findblock"); |
| exit(1); |
| } |
| |
| offset = (uint64_t) block * blocksize; |
| if (alloconly) { |
| if (test) continue; |
| |
| fl.l_start = fileoffset + offset; |
| fl.l_len = blocksize; |
| fl.l_whence = 0; |
| |
| if (xfscntl(fname, fd, RESVSP64, &fl) < 0) { |
| perror("xfsnctl(RESVSP64)"); |
| exit(1); |
| } |
| continue; |
| } |
| SETBIT(valid, block); |
| if (!test) { |
| if (lseek64(fd, fileoffset + offset, SEEK_SET) < 0) { |
| perror("lseek"); |
| exit(1); |
| } |
| /* |
| * Before writing, record offset at the base |
| * of the buffer and at offset 256 bytes |
| * into it. We'll verify this when we read |
| * it back in again. |
| */ |
| *(uint64_t *) buffer = fileoffset + offset; |
| *(uint64_t *) (buffer + 256) = fileoffset + offset; |
| |
| if (write(fd, buffer, blocksize) < blocksize) { |
| perror("write"); |
| exit(1); |
| } |
| } |
| if (verbose > 1) { |
| printf("%swriting data at offset=%llx\n", |
| test ? "NOT " : "", |
| (unsigned long long) (fileoffset + offset)); |
| } |
| } while (--count); |
| |
| free(buffer); |
| } |
| |
| static int |
| readblks(int fd, size_t alignment) |
| { |
| uint64_t offset; |
| char *buffer, *tmp; |
| unsigned int xfer, block, i; |
| int err=0; |
| |
| if (alloconly) |
| return 0; |
| xfer = READ_XFER*blocksize; |
| err = posix_memalign((void **) &buffer, alignment, xfer); |
| if (err) { |
| fprintf(stderr, "posix_memalign: %s\n", strerror(err)); |
| exit(1); |
| } |
| memset(buffer, 0, xfer); |
| if (verbose) |
| printf("\n"); |
| |
| if (lseek64(fd, fileoffset, SEEK_SET) < 0) { |
| perror("lseek"); |
| exit(1); |
| } |
| block = 0; |
| offset = 0; |
| while (offset < filesize) { |
| if ((i = read(fd, buffer, xfer) < xfer)) { |
| if (i < 2) |
| break; |
| perror("read"); |
| exit(1); |
| } |
| tmp = buffer; |
| for (i = 0; i < READ_XFER; i++) { |
| uint64_t want; |
| uint64_t first; |
| uint64_t second; |
| |
| if (verbose && ((block % 100) == 0)) { |
| printf("+"); |
| fflush(stdout); |
| } |
| |
| want = BITVAL(valid, block) ? offset : 0; |
| first = *(uint64_t *) tmp; |
| second = *(uint64_t *) (tmp + 256); |
| if (first != want || second != want) { |
| printf("mismatched data at offset=0x%" PRIx64 |
| ", expected 0x%" PRIx64 |
| ", got 0x%" PRIx64 |
| " and 0x%" PRIx64 "\n", |
| fileoffset + offset, want, |
| first, second); |
| err++; |
| } |
| if (verbose > 2) { |
| printf("block %d blocksize %d\n", block, |
| blocksize); |
| dumpblock((int *)tmp, fileoffset + offset, |
| blocksize); |
| } |
| |
| block++; |
| offset += blocksize; |
| tmp += blocksize; |
| } |
| } |
| if (verbose) |
| printf("\n"); |
| |
| free(buffer); |
| return err; |
| } |
| |
| /* |
| * Determine the memory alignment required for I/O buffers. For |
| * direct I/O we request the needed information from the file |
| * system; otherwise pointer alignment is fine. Returns the |
| * alignment multiple, or 0 if an error occurs. |
| */ |
| static size_t |
| get_alignment(char *filename, int fd) |
| { |
| struct dioattr dioattr; |
| |
| if (! direct) |
| return sizeof (void *); |
| |
| memset(&dioattr, 0, sizeof dioattr); |
| if (xfscntl(filename, fd, DIOINFO, &dioattr) < 0) { |
| perror("xfscntl(FIOINFO)"); |
| return 0; |
| } |
| |
| /* Make sure the alignment meets the needs of posix_memalign() */ |
| |
| if (dioattr.d_mem % sizeof (void *) || ! power_of_2(dioattr.d_mem)) { |
| perror("d_mem bad"); |
| return 0; |
| } |
| |
| /* |
| * Also make sure user doesn't specify a block size that's |
| * incompatible with the underlying file system. |
| */ |
| if (! dioattr.d_miniosz) { |
| perror("miniosz == 0!"); |
| return 0; |
| } |
| if (blocksize % dioattr.d_miniosz) { |
| fprintf(stderr, "blocksize %d must be a multiple of " |
| "%d for direct I/O\n", blocksize, dioattr.d_miniosz); |
| return 0; |
| } |
| |
| return (size_t) dioattr.d_mem; |
| } |
| |
| static int |
| realtime_setup(char *filename, int fd) |
| { |
| struct fsxattr rtattr; |
| |
| (void) memset(&rtattr, 0, sizeof rtattr); |
| if (xfscntl(filename, fd, FSGETXATTR, &rtattr) < 0) { |
| perror("FSGETXATTR)"); |
| return 1; |
| } |
| if ((rtattr.fsx_xflags & XFS_XFLAG_REALTIME) == 0 || |
| (extsize && rtattr.fsx_extsize != extsize * blocksize)) { |
| rtattr.fsx_xflags |= XFS_XFLAG_REALTIME; |
| if (extsize) |
| rtattr.fsx_extsize = extsize * blocksize; |
| if (xfscntl(filename, fd, FSSETXATTR, &rtattr) < 0) { |
| perror("FSSETXATTR)"); |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| char *filename; |
| size_t size; |
| int oflags; |
| int fd; |
| size_t alignment; |
| int errors; |
| |
| filename = parseargs(argc, argv); |
| if (! filename) |
| return 1; |
| |
| /* |
| * Allocate a bitmap big enough to track the range of |
| * blocks we'll be dealing with. |
| */ |
| size = (filesize / blocksize) / 8 + 1; |
| valid = malloc(size); |
| if ((valid = malloc(size)) == NULL) { |
| perror("malloc"); |
| return 1; |
| } |
| memset(valid, 0, size); |
| |
| /* Lots of arguments affect how we open the file */ |
| oflags = test ? O_RDONLY : O_RDWR|O_CREAT; |
| oflags |= preserve ? 0 : O_TRUNC; |
| oflags |= wsync ? O_SYNC : 0; |
| oflags |= direct ? O_DIRECT : 0; |
| |
| /* |
| * Open the file, write rand block in random places, read them all |
| * back to check for correctness, then close the file. |
| */ |
| if ((fd = open(filename, oflags, 0666)) < 0) { |
| perror("open"); |
| return 1; |
| } |
| if (rt && realtime_setup(filename, fd)) |
| return 1; |
| alignment = get_alignment(filename, fd); |
| if (! alignment) |
| return 1; |
| |
| printf("write%s\n", test ? " (skipped)" : ""); |
| writeblks(filename, fd, alignment); |
| |
| printf("readback\n"); |
| errors = readblks(fd, alignment); |
| |
| if (close(fd) < 0) { |
| perror("close"); |
| return 1; |
| } |
| free(valid); |
| |
| if (errors) { |
| printf("randholes: %d errors found during readback\n", errors); |
| return 2; |
| } |
| |
| printf("randholes: ok\n"); |
| |
| return 0; |
| } |
| |