Files
apfstests/ltp/fsx.c
T
Felix Blyakher d0f0d2f428 xfstests: fix async io error handling in fsx
The result of async io returned in the event.res in addition
to the number of bytes read/written provides negated error
number. The broken libaio defines event.res as unsigned
while the same structure in the kernel defines it as signed.
The kernel indeed treats it as signed, and returns the
negated error number. Till libaio is fixed we provide
the signed long temp var.
Also set errno for each error condition in aio_rw, as the
caller is not aio aware and expects ret(-1)+errno by the
traditional libc convention.

Signed-off-by: Felix Blyakher <felixb@sgi.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
2009-04-13 16:46:00 -05:00

1311 lines
29 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.
*/
#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
#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
#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 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;
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_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, L_XTND);
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%qx bytes instead of 0x%qx\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%qx\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 (bcmp(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, L_XTND);
if (file_size != statbuf.st_size || file_size != size_by_seek) {
prt("Size error: expected 0x%qx stat 0x%qx seek 0x%qx\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
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);
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)
bzero(good_buf + file_size, 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)
bzero(good_buf + file_size, 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);
}
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)
bzero(good_buf + file_size, 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);
}
}
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%qx\n",
iret, (unsigned long long)file_size);
report_failure(172);
}
if (lite ? 0 : ftruncate(fd, file_size) == -1) {
prt("ftruncate2: %qx\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);
/* 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);
/*
* READ: op = 0
* WRITE: op = 1
* MAPREAD: op = 2
* TRUNCATE: op = 3
* MAPWRITE: op = 3 or 4
*/
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)
dotruncate(size);
else {
offset = random();
if (op == 1 || op == (lite ? 3 : 4)) {
offset %= maxfilelen;
if (offset + size > maxfilelen)
size = maxfilelen - offset;
if (op != 1)
domapwrite(offset, size);
else
dowrite(offset, size);
} else {
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 [-dnqxALOWZ] [-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\
-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=%d\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
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:LN: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 '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, L_XTND);
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);
bzero(good_buf, maxfilelen);
temp_buf = (char *) malloc(maxoplen + readbdy);
temp_buf = round_up(temp_buf, readbdy, 0);
bzero(temp_buf, 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();
while (numops == -1 || numops--)
test();
if (close(fd)) {
prterr("close");
report_failure(99);
}
prt("All operations completed A-OK!\n");
exit(0);
return 0;
}