Files
apfstests/ltp/fsx.c
T
Dave Chinner c18bf42de9 xfstests: fix fsx fpunch test to actually test for fpunch
The operation flags parameter to fallocate is the second parameter,
not the last. Hence the fpunch test is actually testing for falloc
support, not fpunch. Somebody needs a brown paper bag.

Also, add a ftruncate call whenthe fpunch succeeds just in case the
file was not already zero sized. Failing to ensure we start with a
zero length file can cause read ops to fail size checks if they
occur before the file is written to be the main test loop.

While there, observe the quiet flag the same as the falloc test
does and have them both emit the warning at the same error level.

Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Eric Sandeen <sandeen@redhat.com>
Reviewed-by: Alex Elder <aelder@sgi.com>
2011-07-14 14:07:58 +10:00

1554 lines
35 KiB
C

/*
* Copyright (C) 1991, NeXT Computer, Inc. All Rights Reserverd.
*
* File: fsx.c
* Author: Avadis Tevanian, Jr.
*
* File system exerciser.
*
* Rewritten 8/98 by Conrad Minshall.
*
* Small changes to work under Linux -- davej.
*
* Checks for mmap last-page zero fill.
*/
#include "global.h"
#include <limits.h>
#include <time.h>
#include <strings.h>
#include <sys/file.h>
#include <sys/mman.h>
#ifdef HAVE_ERR_H
#include <err.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#ifdef AIO
#include <libaio.h>
#endif
#ifdef FALLOCATE
#include <linux/falloc.h>
#endif
#ifndef MAP_FILE
# define MAP_FILE 0
#endif
#define NUMPRINTCOLUMNS 32 /* # columns of data to print on each line */
/*
* A log entry is an operation and a bunch of arguments.
*/
struct log_entry {
int operation;
int args[3];
};
#define LOGSIZE 1000
struct log_entry oplog[LOGSIZE]; /* the log */
int logptr = 0; /* current position in log */
int logcount = 0; /* total ops */
/*
* Define operations
*/
#define OP_READ 1
#define OP_WRITE 2
#define OP_TRUNCATE 3
#define OP_CLOSEOPEN 4
#define OP_MAPREAD 5
#define OP_MAPWRITE 6
#define OP_SKIPPED 7
#define OP_FALLOCATE 8
#define OP_PUNCH_HOLE 9
#undef PAGE_SIZE
#define PAGE_SIZE getpagesize()
#undef PAGE_MASK
#define PAGE_MASK (PAGE_SIZE - 1)
char *original_buf; /* a pointer to the original data */
char *good_buf; /* a pointer to the correct data */
char *temp_buf; /* a pointer to the current data */
char *fname; /* name of our test file */
int fd; /* fd for our test file */
off_t file_size = 0;
off_t biggest = 0;
char state[256];
unsigned long testcalls = 0; /* calls to function "test" */
unsigned long simulatedopcount = 0; /* -b flag */
int closeprob = 0; /* -c flag */
int debug = 0; /* -d flag */
unsigned long debugstart = 0; /* -D flag */
int flush = 0; /* -f flag */
int do_fsync = 0; /* -y flag */
unsigned long maxfilelen = 256 * 1024; /* -l flag */
int sizechecks = 1; /* -n flag disables them */
int maxoplen = 64 * 1024; /* -o flag */
int quiet = 0; /* -q flag */
unsigned long progressinterval = 0; /* -p flag */
int readbdy = 1; /* -r flag */
int style = 0; /* -s flag */
int prealloc = 0; /* -x flag */
int truncbdy = 1; /* -t flag */
int writebdy = 1; /* -w flag */
long monitorstart = -1; /* -m flag */
long monitorend = -1; /* -m flag */
int lite = 0; /* -L flag */
long numops = -1; /* -N flag */
int randomoplen = 1; /* -O flag disables it */
int seed = 1; /* -S flag */
int mapped_writes = 1; /* -W flag disables */
int fallocate_calls = 1; /* -F flag disables */
int punch_hole_calls = 1; /* -H flag disables */
int mapped_reads = 1; /* -R flag disables it */
int fsxgoodfd = 0;
int o_direct; /* -Z */
int aio = 0;
int page_size;
int page_mask;
int mmap_mask;
#ifdef AIO
int aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset);
#define READ 0
#define WRITE 1
#define fsxread(a,b,c,d) aio_rw(READ, a,b,c,d)
#define fsxwrite(a,b,c,d) aio_rw(WRITE, a,b,c,d)
#else
#define fsxread(a,b,c,d) read(a,b,c)
#define fsxwrite(a,b,c,d) write(a,b,c)
#endif
FILE * fsxlogf = NULL;
int badoff = -1;
int closeopen = 0;
static void *round_up(void *ptr, unsigned long align, unsigned long offset)
{
unsigned long ret = (unsigned long)ptr;
ret = ((ret + align - 1) & ~(align - 1));
ret += offset;
return (void *)ret;
}
void
vwarnc(int code, const char *fmt, va_list ap) {
fprintf(stderr, "fsx: ");
if (fmt != NULL) {
vfprintf(stderr, fmt, ap);
fprintf(stderr, ": ");
}
fprintf(stderr, "%s\n", strerror(code));
}
void
warn(const char * fmt, ...) {
va_list ap;
va_start(ap, fmt);
vwarnc(errno, fmt, ap);
va_end(ap);
}
#define BUF_SIZE 1024
void
prt(char *fmt, ...)
{
va_list args;
char buffer[BUF_SIZE];
va_start(args, fmt);
vsnprintf(buffer, BUF_SIZE, fmt, args);
va_end(args);
fprintf(stdout, buffer);
if (fsxlogf)
fprintf(fsxlogf, buffer);
}
void
prterr(char *prefix)
{
prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(errno));
}
void
log4(int operation, int arg0, int arg1, int arg2)
{
struct log_entry *le;
le = &oplog[logptr];
le->operation = operation;
if (closeopen)
le->operation = ~ le->operation;
le->args[0] = arg0;
le->args[1] = arg1;
le->args[2] = arg2;
logptr++;
logcount++;
if (logptr >= LOGSIZE)
logptr = 0;
}
void
logdump(void)
{
int i, count, down;
struct log_entry *lp;
char *falloc_type[3] = {"PAST_EOF", "EXTENDING", "INTERIOR"};
prt("LOG DUMP (%d total operations):\n", logcount);
if (logcount < LOGSIZE) {
i = 0;
count = logcount;
} else {
i = logptr;
count = LOGSIZE;
}
for ( ; count > 0; count--) {
int opnum;
opnum = i+1 + (logcount/LOGSIZE)*LOGSIZE;
prt("%d(%d mod 256): ", opnum, opnum%256);
lp = &oplog[i];
if ((closeopen = lp->operation < 0))
lp->operation = ~ lp->operation;
switch (lp->operation) {
case OP_MAPREAD:
prt("MAPREAD\t0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]);
if (badoff >= lp->args[0] && badoff <
lp->args[0] + lp->args[1])
prt("\t***RRRR***");
break;
case OP_MAPWRITE:
prt("MAPWRITE 0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]);
if (badoff >= lp->args[0] && badoff <
lp->args[0] + lp->args[1])
prt("\t******WWWW");
break;
case OP_READ:
prt("READ\t0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]);
if (badoff >= lp->args[0] &&
badoff < lp->args[0] + lp->args[1])
prt("\t***RRRR***");
break;
case OP_WRITE:
prt("WRITE\t0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]);
if (lp->args[0] > lp->args[2])
prt(" HOLE");
else if (lp->args[0] + lp->args[1] > lp->args[2])
prt(" EXTEND");
if ((badoff >= lp->args[0] || badoff >=lp->args[2]) &&
badoff < lp->args[0] + lp->args[1])
prt("\t***WWWW");
break;
case OP_TRUNCATE:
down = lp->args[0] < lp->args[1];
prt("TRUNCATE %s\tfrom 0x%x to 0x%x",
down ? "DOWN" : "UP", lp->args[1], lp->args[0]);
if (badoff >= lp->args[!down] &&
badoff < lp->args[!!down])
prt("\t******WWWW");
break;
case OP_FALLOCATE:
/* 0: offset 1: length 2: where alloced */
prt("FALLOCATE %s\tfrom 0x%x to 0x%x",
falloc_type[lp->args[2]], lp->args[0], lp->args[0] + lp->args[1]);
if (badoff >= lp->args[0] &&
badoff < lp->args[0] + lp->args[1])
prt("\t******FFFF");
break;
case OP_PUNCH_HOLE:
prt("PUNCH HOLE\t0x%x thru 0x%x\t(0x%x bytes)",
lp->args[0], lp->args[0] + lp->args[1] - 1,
lp->args[1]);
if (badoff >= lp->args[0] && badoff <
lp->args[0] + lp->args[1])
prt("\t******PPPP");
break;
case OP_SKIPPED:
prt("SKIPPED (no operation)");
break;
default:
prt("BOGUS LOG ENTRY (operation code = %d)!",
lp->operation);
}
if (closeopen)
prt("\n\t\tCLOSE/OPEN");
prt("\n");
i++;
if (i == LOGSIZE)
i = 0;
}
}
void
save_buffer(char *buffer, off_t bufferlength, int fd)
{
off_t ret;
ssize_t byteswritten;
if (fd <= 0 || bufferlength == 0)
return;
if (bufferlength > SSIZE_MAX) {
prt("fsx flaw: overflow in save_buffer\n");
exit(67);
}
if (lite) {
off_t size_by_seek = lseek(fd, (off_t)0, SEEK_END);
if (size_by_seek == (off_t)-1)
prterr("save_buffer: lseek eof");
else if (bufferlength > size_by_seek) {
warn("save_buffer: .fsxgood file too short... will save 0x%llx bytes instead of 0x%llx\n", (unsigned long long)size_by_seek,
(unsigned long long)bufferlength);
bufferlength = size_by_seek;
}
}
ret = lseek(fd, (off_t)0, SEEK_SET);
if (ret == (off_t)-1)
prterr("save_buffer: lseek 0");
byteswritten = write(fd, buffer, (size_t)bufferlength);
if (byteswritten != bufferlength) {
if (byteswritten == -1)
prterr("save_buffer write");
else
warn("save_buffer: short write, 0x%x bytes instead of 0x%llx\n",
(unsigned)byteswritten,
(unsigned long long)bufferlength);
}
}
void
report_failure(int status)
{
logdump();
if (fsxgoodfd) {
if (good_buf) {
save_buffer(good_buf, file_size, fsxgoodfd);
prt("Correct content saved for comparison\n");
prt("(maybe hexdump \"%s\" vs \"%s.fsxgood\")\n",
fname, fname);
}
close(fsxgoodfd);
}
exit(status);
}
#define short_at(cp) ((unsigned short)((*((unsigned char *)(cp)) << 8) | \
*(((unsigned char *)(cp)) + 1)))
void
check_buffers(unsigned offset, unsigned size)
{
unsigned char c, t;
unsigned i = 0;
unsigned n = 0;
unsigned op = 0;
unsigned bad = 0;
if (memcmp(good_buf + offset, temp_buf, size) != 0) {
prt("READ BAD DATA: offset = 0x%x, size = 0x%x, fname = %s\n",
offset, size, fname);
prt("OFFSET\tGOOD\tBAD\tRANGE\n");
while (size > 0) {
c = good_buf[offset];
t = temp_buf[i];
if (c != t) {
if (n < 16) {
bad = short_at(&temp_buf[i]);
prt("0x%5x\t0x%04x\t0x%04x", offset,
short_at(&good_buf[offset]), bad);
op = temp_buf[offset & 1 ? i+1 : i];
prt("\t0x%5x\n", n);
if (op)
prt("operation# (mod 256) for "
"the bad data may be %u\n",
((unsigned)op & 0xff));
else
prt("operation# (mod 256) for "
"the bad data unknown, check"
" HOLE and EXTEND ops\n");
}
n++;
badoff = offset;
}
offset++;
i++;
size--;
}
report_failure(110);
}
}
void
check_size(void)
{
struct stat statbuf;
off_t size_by_seek;
if (fstat(fd, &statbuf)) {
prterr("check_size: fstat");
statbuf.st_size = -1;
}
size_by_seek = lseek(fd, (off_t)0, SEEK_END);
if (file_size != statbuf.st_size || file_size != size_by_seek) {
prt("Size error: expected 0x%llx stat 0x%llx seek 0x%llx\n",
(unsigned long long)file_size,
(unsigned long long)statbuf.st_size,
(unsigned long long)size_by_seek);
report_failure(120);
}
}
void
check_trunc_hack(void)
{
struct stat statbuf;
ftruncate(fd, (off_t)0);
ftruncate(fd, (off_t)100000);
fstat(fd, &statbuf);
if (statbuf.st_size != (off_t)100000) {
prt("no extend on truncate! not posix!\n");
exit(130);
}
ftruncate(fd, 0);
}
void
doflush(unsigned offset, unsigned size)
{
unsigned pg_offset;
unsigned map_size;
char *p;
if (o_direct == O_DIRECT)
return;
pg_offset = offset & mmap_mask;
map_size = pg_offset + size;
if ((p = (char *)mmap(0, map_size, PROT_READ | PROT_WRITE,
MAP_FILE | MAP_SHARED, fd,
(off_t)(offset - pg_offset))) == (char *)-1) {
prterr("doflush: mmap");
report_failure(202);
}
if (msync(p, map_size, MS_INVALIDATE) != 0) {
prterr("doflush: msync");
report_failure(203);
}
if (munmap(p, map_size) != 0) {
prterr("doflush: munmap");
report_failure(204);
}
}
void
doread(unsigned offset, unsigned size)
{
off_t ret;
unsigned iret;
offset -= offset % readbdy;
if (o_direct)
size -= size % readbdy;
if (size == 0) {
if (!quiet && testcalls > simulatedopcount && !o_direct)
prt("skipping zero size read\n");
log4(OP_SKIPPED, OP_READ, offset, size);
return;
}
if (size + offset > file_size) {
if (!quiet && testcalls > simulatedopcount)
prt("skipping seek/read past end of file\n");
log4(OP_SKIPPED, OP_READ, offset, size);
return;
}
log4(OP_READ, offset, size, 0);
if (testcalls <= simulatedopcount)
return;
if (!quiet &&
((progressinterval && testcalls % progressinterval == 0) ||
(debug &&
(monitorstart == -1 ||
(offset + size > monitorstart &&
(monitorend == -1 || offset <= monitorend))))))
prt("%lu read\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
offset, offset + size - 1, size);
ret = lseek(fd, (off_t)offset, SEEK_SET);
if (ret == (off_t)-1) {
prterr("doread: lseek");
report_failure(140);
}
iret = fsxread(fd, temp_buf, size, offset);
if (iret != size) {
if (iret == -1)
prterr("doread: read");
else
prt("short read: 0x%x bytes instead of 0x%x\n",
iret, size);
report_failure(141);
}
check_buffers(offset, size);
}
void
check_eofpage(char *s, unsigned offset, char *p, int size)
{
unsigned long last_page, should_be_zero;
if (offset + size <= (file_size & ~page_mask))
return;
/*
* we landed in the last page of the file
* test to make sure the VM system provided 0's
* beyond the true end of the file mapping
* (as required by mmap def in 1996 posix 1003.1)
*/
last_page = ((unsigned long)p + (offset & page_mask) + size) & ~page_mask;
for (should_be_zero = last_page + (file_size & page_mask);
should_be_zero < last_page + page_size;
should_be_zero++)
if (*(char *)should_be_zero) {
prt("Mapped %s: non-zero data past EOF (0x%llx) page offset 0x%x is 0x%04x\n",
s, file_size - 1, should_be_zero & page_mask,
short_at(should_be_zero));
report_failure(205);
}
}
void
domapread(unsigned offset, unsigned size)
{
unsigned pg_offset;
unsigned map_size;
char *p;
offset -= offset % readbdy;
if (size == 0) {
if (!quiet && testcalls > simulatedopcount)
prt("skipping zero size read\n");
log4(OP_SKIPPED, OP_MAPREAD, offset, size);
return;
}
if (size + offset > file_size) {
if (!quiet && testcalls > simulatedopcount)
prt("skipping seek/read past end of file\n");
log4(OP_SKIPPED, OP_MAPREAD, offset, size);
return;
}
log4(OP_MAPREAD, offset, size, 0);
if (testcalls <= simulatedopcount)
return;
if (!quiet &&
((progressinterval && testcalls % progressinterval == 0) ||
(debug &&
(monitorstart == -1 ||
(offset + size > monitorstart &&
(monitorend == -1 || offset <= monitorend))))))
prt("%lu mapread\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
offset, offset + size - 1, size);
pg_offset = offset & PAGE_MASK;
map_size = pg_offset + size;
if ((p = (char *)mmap(0, map_size, PROT_READ, MAP_SHARED, fd,
(off_t)(offset - pg_offset))) == (char *)-1) {
prterr("domapread: mmap");
report_failure(190);
}
memcpy(temp_buf, p + pg_offset, size);
check_eofpage("Read", offset, p, size);
if (munmap(p, map_size) != 0) {
prterr("domapread: munmap");
report_failure(191);
}
check_buffers(offset, size);
}
void
gendata(char *original_buf, char *good_buf, unsigned offset, unsigned size)
{
while (size--) {
good_buf[offset] = testcalls % 256;
if (offset % 2)
good_buf[offset] += original_buf[offset];
offset++;
}
}
void
dowrite(unsigned offset, unsigned size)
{
off_t ret;
unsigned iret;
offset -= offset % writebdy;
if (o_direct)
size -= size % writebdy;
if (size == 0) {
if (!quiet && testcalls > simulatedopcount && !o_direct)
prt("skipping zero size write\n");
log4(OP_SKIPPED, OP_WRITE, offset, size);
return;
}
log4(OP_WRITE, offset, size, file_size);
gendata(original_buf, good_buf, offset, size);
if (file_size < offset + size) {
if (file_size < offset)
memset(good_buf + file_size, '\0', offset - file_size);
file_size = offset + size;
if (lite) {
warn("Lite file size bug in fsx!");
report_failure(149);
}
}
if (testcalls <= simulatedopcount)
return;
if (!quiet &&
((progressinterval && testcalls % progressinterval == 0) ||
(debug &&
(monitorstart == -1 ||
(offset + size > monitorstart &&
(monitorend == -1 || offset <= monitorend))))))
prt("%lu write\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
offset, offset + size - 1, size);
ret = lseek(fd, (off_t)offset, SEEK_SET);
if (ret == (off_t)-1) {
prterr("dowrite: lseek");
report_failure(150);
}
iret = fsxwrite(fd, good_buf + offset, size, offset);
if (iret != size) {
if (iret == -1)
prterr("dowrite: write");
else
prt("short write: 0x%x bytes instead of 0x%x\n",
iret, size);
report_failure(151);
}
if (do_fsync) {
if (fsync(fd)) {
prt("fsync() failed: %s\n", strerror(errno));
report_failure(152);
}
}
if (flush) {
doflush(offset, size);
}
}
void
domapwrite(unsigned offset, unsigned size)
{
unsigned pg_offset;
unsigned map_size;
off_t cur_filesize;
char *p;
offset -= offset % writebdy;
if (size == 0) {
if (!quiet && testcalls > simulatedopcount)
prt("skipping zero size write\n");
log4(OP_SKIPPED, OP_MAPWRITE, offset, size);
return;
}
cur_filesize = file_size;
log4(OP_MAPWRITE, offset, size, 0);
gendata(original_buf, good_buf, offset, size);
if (file_size < offset + size) {
if (file_size < offset)
memset(good_buf + file_size, '\0', offset - file_size);
file_size = offset + size;
if (lite) {
warn("Lite file size bug in fsx!");
report_failure(200);
}
}
if (testcalls <= simulatedopcount)
return;
if (!quiet &&
((progressinterval && testcalls % progressinterval == 0) ||
(debug &&
(monitorstart == -1 ||
(offset + size > monitorstart &&
(monitorend == -1 || offset <= monitorend))))))
prt("%lu mapwrite\t0x%x thru\t0x%x\t(0x%x bytes)\n", testcalls,
offset, offset + size - 1, size);
if (file_size > cur_filesize) {
if (ftruncate(fd, file_size) == -1) {
prterr("domapwrite: ftruncate");
exit(201);
}
}
pg_offset = offset & PAGE_MASK;
map_size = pg_offset + size;
if ((p = (char *)mmap(0, map_size, PROT_READ | PROT_WRITE,
MAP_FILE | MAP_SHARED, fd,
(off_t)(offset - pg_offset))) == (char *)-1) {
prterr("domapwrite: mmap");
report_failure(202);
}
memcpy(p + pg_offset, good_buf + offset, size);
if (msync(p, map_size, 0) != 0) {
prterr("domapwrite: msync");
report_failure(203);
}
check_eofpage("Write", offset, p, size);
if (munmap(p, map_size) != 0) {
prterr("domapwrite: munmap");
report_failure(204);
}
}
void
dotruncate(unsigned size)
{
int oldsize = file_size;
size -= size % truncbdy;
if (size > biggest) {
biggest = size;
if (!quiet && testcalls > simulatedopcount)
prt("truncating to largest ever: 0x%x\n", size);
}
log4(OP_TRUNCATE, size, (unsigned)file_size, 0);
if (size > file_size)
memset(good_buf + file_size, '\0', size - file_size);
file_size = size;
if (testcalls <= simulatedopcount)
return;
if ((progressinterval && testcalls % progressinterval == 0) ||
(debug && (monitorstart == -1 || monitorend == -1 ||
size <= monitorend)))
prt("%lu trunc\tfrom 0x%x to 0x%x\n", testcalls, oldsize, size);
if (ftruncate(fd, (off_t)size) == -1) {
prt("ftruncate1: %x\n", size);
prterr("dotruncate: ftruncate");
report_failure(160);
}
}
#ifdef FALLOC_FL_PUNCH_HOLE
void
do_punch_hole(unsigned offset, unsigned length)
{
unsigned end_offset;
int max_offset = 0;
int max_len = 0;
int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
if (length == 0) {
if (!quiet && testcalls > simulatedopcount)
prt("skipping zero length punch hole\n");
log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, length);
return;
}
if (file_size <= (loff_t)offset) {
if (!quiet && testcalls > simulatedopcount)
prt("skipping hole punch off the end of the file\n");
log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, length);
return;
}
end_offset = offset + length;
log4(OP_PUNCH_HOLE, offset, length, 0);
if (testcalls <= simulatedopcount)
return;
if ((progressinterval && testcalls % progressinterval == 0) ||
(debug && (monitorstart == -1 || monitorend == -1 ||
end_offset <= monitorend))) {
prt("%lu punch\tfrom 0x%x to 0x%x, (0x%x bytes)\n", testcalls,
offset, offset+length, length);
}
if (fallocate(fd, mode, (loff_t)offset, (loff_t)length) == -1) {
prt("%punch hole: %x to %x\n", offset, length);
prterr("do_punch_hole: fallocate");
report_failure(161);
}
max_offset = offset < file_size ? offset : file_size;
max_len = max_offset + length <= file_size ? length :
file_size - max_offset;
memset(good_buf + max_offset, '\0', max_len);
}
#else
void
do_punch_hole(unsigned offset, unsigned length)
{
return;
}
#endif
#ifdef FALLOCATE
/* fallocate is basically a no-op unless extending, then a lot like a truncate */
void
do_preallocate(unsigned offset, unsigned length)
{
unsigned end_offset;
int keep_size;
if (length == 0) {
if (!quiet && testcalls > simulatedopcount)
prt("skipping zero length fallocate\n");
log4(OP_SKIPPED, OP_FALLOCATE, offset, length);
return;
}
keep_size = random() % 2;
end_offset = keep_size ? 0 : offset + length;
if (end_offset > biggest) {
biggest = end_offset;
if (!quiet && testcalls > simulatedopcount)
prt("fallocating to largest ever: 0x%x\n", end_offset);
}
/*
* last arg:
* 1: allocate past EOF
* 2: extending prealloc
* 3: interior prealloc
*/
log4(OP_FALLOCATE, offset, length, (end_offset > file_size) ? (keep_size ? 1 : 2) : 3);
if (end_offset > file_size) {
memset(good_buf + file_size, '\0', end_offset - file_size);
file_size = end_offset;
}
if (testcalls <= simulatedopcount)
return;
if ((progressinterval && testcalls % progressinterval == 0) ||
(debug && (monitorstart == -1 || monitorend == -1 ||
end_offset <= monitorend)))
prt("%lu falloc\tfrom 0x%x to 0x%x\n", testcalls, offset, length);
if (fallocate(fd, keep_size ? FALLOC_FL_KEEP_SIZE : 0, (loff_t)offset, (loff_t)length) == -1) {
prt("fallocate: %x to %x\n", offset, length);
prterr("do_preallocate: fallocate");
report_failure(161);
}
}
#else
void
do_preallocate(unsigned offset, unsigned length)
{
return;
}
#endif
void
writefileimage()
{
ssize_t iret;
if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
prterr("writefileimage: lseek");
report_failure(171);
}
iret = write(fd, good_buf, file_size);
if ((off_t)iret != file_size) {
if (iret == -1)
prterr("writefileimage: write");
else
prt("short write: 0x%x bytes instead of 0x%llx\n",
iret, (unsigned long long)file_size);
report_failure(172);
}
if (lite ? 0 : ftruncate(fd, file_size) == -1) {
prt("ftruncate2: %llx\n", (unsigned long long)file_size);
prterr("writefileimage: ftruncate");
report_failure(173);
}
}
void
docloseopen(void)
{
if (testcalls <= simulatedopcount)
return;
if (debug)
prt("%lu close/open\n", testcalls);
if (close(fd)) {
prterr("docloseopen: close");
report_failure(180);
}
fd = open(fname, O_RDWR|o_direct, 0);
if (fd < 0) {
prterr("docloseopen: open");
report_failure(181);
}
}
void
test(void)
{
unsigned long offset;
unsigned long size = maxoplen;
unsigned long rv = random();
unsigned long op = rv % (3 + !lite + mapped_writes + fallocate_calls + punch_hole_calls);
/* turn off the map read if necessary */
if (op == 2 && !mapped_reads)
op = 0;
if (simulatedopcount > 0 && testcalls == simulatedopcount)
writefileimage();
testcalls++;
if (closeprob)
closeopen = (rv >> 3) < (1 << 28) / closeprob;
if (debugstart > 0 && testcalls >= debugstart)
debug = 1;
if (!quiet && testcalls < simulatedopcount && testcalls % 100000 == 0)
prt("%lu...\n", testcalls);
/*
* lite !lite
* READ: op = 0 0
* WRITE: op = 1 1
* MAPREAD: op = 2 2
* TRUNCATE: op = - 3
* MAPWRITE: op = 3 4
* FALLOCATE: op = - 5
* PUNCH HOLE: op = - 6
*/
if (lite ? 0 : op == 3 && (style & 1) == 0) /* vanilla truncate? */
dotruncate(random() % maxfilelen);
else {
if (randomoplen)
size = random() % (maxoplen+1);
if (lite ? 0 : op == 3) {
/* truncate */
dotruncate(size);
} else {
offset = random();
if (op == 5) {
/* fallocate */
offset %= maxfilelen;
if (offset + size > maxfilelen)
size = maxfilelen - offset;
do_preallocate(offset, size);
} else if (op == 6) {
offset %= maxfilelen;
if (offset + size > maxfilelen)
size = maxfilelen - offset;
do_punch_hole(offset, size);
} else if (op == 1 || op == (lite ? 3 : 4)) {
/* write / mapwrite */
offset %= maxfilelen;
if (offset + size > maxfilelen)
size = maxfilelen - offset;
if (op != 1)
domapwrite(offset, size);
else
dowrite(offset, size);
} else {
/* read / mapread */
if (file_size)
offset %= file_size;
else
offset = 0;
if (offset + size > file_size)
size = file_size - offset;
if (op != 0)
domapread(offset, size);
else
doread(offset, size);
}
}
}
if (sizechecks && testcalls > simulatedopcount)
check_size();
if (closeopen)
docloseopen();
}
void
cleanup(sig)
int sig;
{
if (sig)
prt("signal %d\n", sig);
prt("testcalls = %lu\n", testcalls);
exit(sig);
}
void
usage(void)
{
fprintf(stdout, "usage: %s",
"fsx [-dnqxAFLOWZ] [-b opnum] [-c Prob] [-l flen] [-m start:end] [-o oplen] [-p progressinterval] [-r readbdy] [-s style] [-t truncbdy] [-w writebdy] [-D startingop] [-N numops] [-P dirpath] [-S seed] fname\n\
-b opnum: beginning operation number (default 1)\n\
-c P: 1 in P chance of file close+open at each op (default infinity)\n\
-d: debug output for all operations\n\
-f flush and invalidate cache after I/O\n\
-l flen: the upper bound on file size (default 262144)\n\
-m startop:endop: monitor (print debug output) specified byte range (default 0:infinity)\n\
-n: no verifications of file size\n\
-o oplen: the upper bound on operation size (default 65536)\n\
-p progressinterval: debug output at specified operation interval\n\
-q: quieter operation\n\
-r readbdy: 4096 would make reads page aligned (default 1)\n\
-s style: 1 gives smaller truncates (default 0)\n\
-t truncbdy: 4096 would make truncates page aligned (default 1)\n\
-w writebdy: 4096 would make writes page aligned (default 1)\n\
-x: preallocate file space before starting, XFS only (default 0)\n\
-y synchronize changes to a file\n"
#ifdef AIO
" -A: Use the AIO system calls\n"
#endif
" -D startingop: debug output starting at specified operation\n"
#ifdef FALLOCATE
" -F: Do not use fallocate (preallocation) calls\n"
#endif
#ifdef FALLOC_FL_PUNCH_HOLE
" -H: Do not use punch hole calls\n"
#endif
" -L: fsxLite - no file creations & no file size changes\n\
-N numops: total # operations to do (default infinity)\n\
-O: use oplen (see -o flag) for every op (default random)\n\
-P: save .fsxlog and .fsxgood files in dirpath (default ./)\n\
-S seed: for random # generator (default 1) 0 gets timestamp\n\
-W: mapped write operations DISabled\n\
-R: read() system calls only (mapped reads disabled)\n\
-Z: O_DIRECT (use -R, -W, -r and -w too)\n\
fname: this filename is REQUIRED (no default)\n");
exit(90);
}
int
getnum(char *s, char **e)
{
int ret;
*e = (char *) 0;
ret = strtol(s, e, 0);
if (*e)
switch (**e) {
case 'b':
case 'B':
ret *= 512;
*e = *e + 1;
break;
case 'k':
case 'K':
ret *= 1024;
*e = *e + 1;
break;
case 'm':
case 'M':
ret *= 1024*1024;
*e = *e + 1;
break;
case 'w':
case 'W':
ret *= 4;
*e = *e + 1;
break;
}
return (ret);
}
#ifdef AIO
#define QSZ 1024
io_context_t io_ctx;
struct iocb iocb;
int aio_setup()
{
int ret;
ret = io_queue_init(QSZ, &io_ctx);
if (ret != 0) {
fprintf(stderr, "aio_setup: io_queue_init failed: %s\n",
strerror(ret));
return(-1);
}
return(0);
}
int
__aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset)
{
struct io_event event;
static struct timespec ts;
struct iocb *iocbs[] = { &iocb };
int ret;
long res;
if (rw == READ) {
io_prep_pread(&iocb, fd, buf, len, offset);
} else {
io_prep_pwrite(&iocb, fd, buf, len, offset);
}
ts.tv_sec = 30;
ts.tv_nsec = 0;
ret = io_submit(io_ctx, 1, iocbs);
if (ret != 1) {
fprintf(stderr, "errcode=%d\n", ret);
fprintf(stderr, "aio_rw: io_submit failed: %s\n",
strerror(ret));
goto out_error;
}
ret = io_getevents(io_ctx, 1, 1, &event, &ts);
if (ret != 1) {
if (ret == 0)
fprintf(stderr, "aio_rw: no events available\n");
else {
fprintf(stderr, "errcode=%d\n", -ret);
fprintf(stderr, "aio_rw: io_getevents failed: %s\n",
strerror(-ret));
}
goto out_error;
}
if (len != event.res) {
/*
* The b0rked libaio defines event.res as unsigned.
* However the kernel strucuture has it signed,
* and it's used to pass negated error value.
* Till the library is fixed use the temp var.
*/
res = (long)event.res;
if (res >= 0)
fprintf(stderr, "bad io length: %lu instead of %u\n",
res, len);
else {
fprintf(stderr, "errcode=%ld\n", -res);
fprintf(stderr, "aio_rw: async io failed: %s\n",
strerror(-res));
ret = res;
goto out_error;
}
}
return event.res;
out_error:
/*
* The caller expects error return in traditional libc
* convention, i.e. -1 and the errno set to error.
*/
errno = -ret;
return -1;
}
int aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset)
{
int ret;
if (aio) {
ret = __aio_rw(rw, fd, buf, len, offset);
} else {
if (rw == READ)
ret = read(fd, buf, len);
else
ret = write(fd, buf, len);
}
return ret;
}
#endif
void
test_fallocate()
{
#ifdef FALLOCATE
if (!lite && fallocate_calls) {
if (fallocate(fd, 0, 0, 1) && errno == EOPNOTSUPP) {
if(!quiet)
warn("main: filesystem does not support fallocate, disabling\n");
fallocate_calls = 0;
} else {
ftruncate(fd, 0);
}
}
#else /* ! FALLOCATE */
fallocate_calls = 0;
#endif
}
void
test_punch_hole()
{
#ifdef FALLOC_FL_PUNCH_HOLE
if (!lite && punch_hole_calls) {
if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
0, 1) && errno == EOPNOTSUPP) {
if(!quiet)
warn("main: filesystem does not support fallocate punch hole, disabling");
punch_hole_calls = 0;
} else
ftruncate(fd, 0);
}
#else /* ! PUNCH HOLE */
punch_hole_calls = 0;
#endif
}
int
main(int argc, char **argv)
{
int i, style, ch;
char *endp;
char goodfile[1024];
char logfile[1024];
goodfile[0] = 0;
logfile[0] = 0;
page_size = getpagesize();
page_mask = page_size - 1;
mmap_mask = page_mask;
setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
while ((ch = getopt(argc, argv, "b:c:dfl:m:no:p:qr:s:t:w:xyAD:FHLN:OP:RS:WZ"))
!= EOF)
switch (ch) {
case 'b':
simulatedopcount = getnum(optarg, &endp);
if (!quiet)
fprintf(stdout, "Will begin at operation %ld\n",
simulatedopcount);
if (simulatedopcount == 0)
usage();
simulatedopcount -= 1;
break;
case 'c':
closeprob = getnum(optarg, &endp);
if (!quiet)
fprintf(stdout,
"Chance of close/open is 1 in %d\n",
closeprob);
if (closeprob <= 0)
usage();
break;
case 'd':
debug = 1;
break;
case 'f':
flush = 1;
break;
case 'l':
maxfilelen = getnum(optarg, &endp);
if (maxfilelen <= 0)
usage();
break;
case 'm':
monitorstart = getnum(optarg, &endp);
if (monitorstart < 0)
usage();
if (!endp || *endp++ != ':')
usage();
monitorend = getnum(endp, &endp);
if (monitorend < 0)
usage();
if (monitorend == 0)
monitorend = -1; /* aka infinity */
debug = 1;
case 'n':
sizechecks = 0;
break;
case 'o':
maxoplen = getnum(optarg, &endp);
if (maxoplen <= 0)
usage();
break;
case 'p':
progressinterval = getnum(optarg, &endp);
if (progressinterval == 0)
usage();
break;
case 'q':
quiet = 1;
break;
case 'r':
readbdy = getnum(optarg, &endp);
if (readbdy <= 0)
usage();
break;
case 's':
style = getnum(optarg, &endp);
if (style < 0 || style > 1)
usage();
break;
case 't':
truncbdy = getnum(optarg, &endp);
if (truncbdy <= 0)
usage();
break;
case 'w':
writebdy = getnum(optarg, &endp);
if (writebdy <= 0)
usage();
break;
case 'x':
prealloc = 1;
break;
case 'y':
do_fsync = 1;
break;
case 'A':
aio = 1;
break;
case 'D':
debugstart = getnum(optarg, &endp);
if (debugstart < 1)
usage();
break;
case 'F':
fallocate_calls = 0;
break;
case 'H':
punch_hole_calls = 0;
break;
case 'L':
lite = 1;
break;
case 'N':
numops = getnum(optarg, &endp);
if (numops < 0)
usage();
break;
case 'O':
randomoplen = 0;
break;
case 'P':
strncpy(goodfile, optarg, sizeof(goodfile));
strcat(goodfile, "/");
strncpy(logfile, optarg, sizeof(logfile));
strcat(logfile, "/");
break;
case 'R':
mapped_reads = 0;
break;
case 'S':
seed = getnum(optarg, &endp);
if (seed == 0)
seed = time(0) % 10000;
if (!quiet)
fprintf(stdout, "Seed set to %d\n", seed);
if (seed < 0)
usage();
break;
case 'W':
mapped_writes = 0;
if (!quiet)
fprintf(stdout, "mapped writes DISABLED\n");
break;
case 'Z':
o_direct = O_DIRECT;
break;
default:
usage();
/* NOTREACHED */
}
argc -= optind;
argv += optind;
if (argc != 1)
usage();
fname = argv[0];
signal(SIGHUP, cleanup);
signal(SIGINT, cleanup);
signal(SIGPIPE, cleanup);
signal(SIGALRM, cleanup);
signal(SIGTERM, cleanup);
signal(SIGXCPU, cleanup);
signal(SIGXFSZ, cleanup);
signal(SIGVTALRM, cleanup);
signal(SIGUSR1, cleanup);
signal(SIGUSR2, cleanup);
initstate(seed, state, 256);
setstate(state);
fd = open(fname,
O_RDWR|(lite ? 0 : O_CREAT|O_TRUNC)|o_direct, 0666);
if (fd < 0) {
prterr(fname);
exit(91);
}
#ifdef XFS
if (prealloc) {
xfs_flock64_t resv = { 0 };
#ifdef HAVE_XFS_PLATFORM_DEFS_H
if (!platform_test_xfs_fd(fd)) {
prterr(fname);
fprintf(stderr, "main: cannot prealloc, non XFS\n");
exit(96);
}
#endif
resv.l_len = maxfilelen;
if ((xfsctl(fname, fd, XFS_IOC_RESVSP, &resv)) < 0) {
prterr(fname);
exit(97);
}
}
#endif
strncat(goodfile, fname, 256);
strcat (goodfile, ".fsxgood");
fsxgoodfd = open(goodfile, O_RDWR|O_CREAT|O_TRUNC, 0666);
if (fsxgoodfd < 0) {
prterr(goodfile);
exit(92);
}
strncat(logfile, fname, 256);
strcat (logfile, ".fsxlog");
fsxlogf = fopen(logfile, "w");
if (fsxlogf == NULL) {
prterr(logfile);
exit(93);
}
#ifdef AIO
if (aio)
aio_setup();
#endif
if (lite) {
off_t ret;
file_size = maxfilelen = lseek(fd, (off_t)0, SEEK_END);
if (file_size == (off_t)-1) {
prterr(fname);
warn("main: lseek eof");
exit(94);
}
ret = lseek(fd, (off_t)0, SEEK_SET);
if (ret == (off_t)-1) {
prterr(fname);
warn("main: lseek 0");
exit(95);
}
}
original_buf = (char *) malloc(maxfilelen);
for (i = 0; i < maxfilelen; i++)
original_buf[i] = random() % 256;
good_buf = (char *) malloc(maxfilelen + writebdy);
good_buf = round_up(good_buf, writebdy, 0);
memset(good_buf, '\0', maxfilelen);
temp_buf = (char *) malloc(maxoplen + readbdy);
temp_buf = round_up(temp_buf, readbdy, 0);
memset(temp_buf, '\0', maxoplen);
if (lite) { /* zero entire existing file */
ssize_t written;
written = write(fd, good_buf, (size_t)maxfilelen);
if (written != maxfilelen) {
if (written == -1) {
prterr(fname);
warn("main: error on write");
} else
warn("main: short write, 0x%x bytes instead "
"of 0x%lx\n",
(unsigned)written,
maxfilelen);
exit(98);
}
} else
check_trunc_hack();
test_fallocate();
test_punch_hole();
while (numops == -1 || numops--)
test();
if (close(fd)) {
prterr("close");
report_failure(99);
}
prt("All operations completed A-OK!\n");
exit(0);
return 0;
}