mirror of
https://github.com/linux-apfs/apfstests.git
synced 2026-05-01 15:01:44 -07:00
d0f0d2f428
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>
1311 lines
29 KiB
C
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;
|
|
}
|