blob: 648cabf7c4694f01661b9f94752b55ae0d875d70 [file] [log] [blame]
// 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;
}