|  | // 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; | 
|  | } | 
|  |  |