#include <libcfs/util/string.h>
#include <setjmp.h>
+#include <linux/lustre/lustre_idl.h>
+#include <lustre/lustreapi.h>
+
/*
* Each test run will work with one or more separate file descriptors for the
* same file. This allows testing cache coherency across multiple mountpoints
int jmpbuf_good;
jmp_buf jmpbuf;
+unsigned int mirror_ids[LUSTRE_MIRROR_COUNT_MAX];
/*
* Define operations
*/
#define OP_PUNCH_HOLE 6
#define OP_ZERO_RANGE 7
#define OP_CLOSEOPEN 8
-#define OP_MAX_FULL 9
+#define OP_MIRROR_OPS 9
+#define OP_MAX_FULL 10
+
+#define MIRROR_EXTEND 0
+#define MIRROR_SPLIT 1
+#define MIRROR_RESYNC 2
+#define MIRROR_OPS 3
+
+char *mirror_op_str[] = {
+ [MIRROR_EXTEND] = "MIRROR_EXTEND",
+ [MIRROR_SPLIT] = "MIRROR_SPLIT",
+ [MIRROR_RESYNC] = "MIRROR_RESYNC",
+};
#define OP_SKIPPED 101
-/* _GNU_SOURCE defines O_DIRECT as 14th bit which is 0x4000(16384) */
-#define OP_DIRECT 16384
+#define OP_DIRECT O_DIRECT
#ifndef FALLOC_FL_PUNCH_HOLE
#define FALLOC_FL_PUNCH_HOLE 0x02 /* de-allocates range */
int writebdy = 1; /* -w flag */
long monitorstart = -1; /* -m flag */
long monitorend = -1; /* -m flag */
+long flrmode; /* -M flag */
int lite; /* -L flag */
long numops = -1; /* -N flag */
int randomoplen = 1; /* -O flag disables it */
FILE *fsxlogf;
int badoff = -1;
-void
+static void
vwarnc(code, fmt, ap)
int code;
const char *fmt;
fprintf(stderr, "%s\n", strerror(code));
}
-void
+static void
__attribute__((format(__printf__, 1, 2)))
warn(const char *fmt, ...)
{
va_end(ap);
}
-void
+static void
__attribute__((format(__printf__, 1, 2)))
prt(char *fmt, ...)
{
* which transparently handles passing of function name.
* This version also keeps checkpatch happy.
*/
-void
+static void
ptrerr_func(const char *func, const char *prefix)
{
prt("%s: %s%s%s\n", func, prefix, prefix ? ": " : "", strerror(errno));
}
#define prterr(prefix) ptrerr_func(__func__, prefix)
-void
+static void
log4(int operation, int arg0, int arg1, int arg2)
{
struct log_entry *le;
logptr = 0;
}
-const char *
+static const char *
fill_tf_buf(const struct test_file *tf)
{
static int max_tf_len;
return tf_buf;
}
-void
+static void
logdump(void)
{
int i, count, down;
break;
case OP_TRUNCATE:
down = lp->args[0] < lp->args[1];
- prt("TRUNCATE %s\tfrom 0x%05x to 0x%05x",
- down ? "DOWN" : "UP", lp->args[1], lp->args[0]);
+ prt("TRUNC%s 0x%05x to 0x%05x",
+ down ? "_DN" : "_UP", lp->args[1], lp->args[0]);
if (badoff >= lp->args[!down] &&
badoff < lp->args[!!down])
- prt("\t******WWWW");
+ prt("\t******TTTT");
break;
case OP_FALLOCATE:
/* 0: offset 1: length 2: where alloced */
- prt("FALLOC \tfrom 0x%05x to 0x%05x\t(0x%05x bytes)%s",
+ prt("FALLOC 0x%05x thru 0x%05x\t(0x%05x bytes)%s",
lp->args[0], lp->args[0] + lp->args[1],
lp->args[1], falloc_type[lp->args[2]]);
if (badoff >= lp->args[0] &&
prt("CLOSE/OPEN%s",
lp->operation & OP_DIRECT ? "_OD" : " ");
break;
+ case OP_MIRROR_OPS: {
+ prt("%s ", mirror_op_str[lp->args[0]]);
+ if (lp->args[0] == MIRROR_EXTEND)
+ prt("to %d mirrors", lp->args[1] + 1);
+ else if (lp->args[0] == MIRROR_SPLIT)
+ prt("mirror %d to %d mirrors", lp->args[2],
+ lp->args[1] - 1);
+ else if (lp->args[0] == MIRROR_RESYNC)
+ prt("%d mirrors", lp->args[1]);
+ break;
+ }
case OP_SKIPPED:
prt("SKIPPED (no operation)");
break;
}
}
-void
+static void
save_buffer(char *buffer, off_t bufferlength, int fd)
{
off_t ret;
}
}
-void
+static void
report_failure(int status)
{
logdump();
+ prt("Using seed %d\n", seed);
if (fsxgoodfd) {
if (good_buf) {
#define short_at(cp) ((unsigned short)((*((unsigned char *)(cp)) << 8) | \
*(((unsigned char *)(cp)) + 1)))
-void
+static void
check_buffers(unsigned int offset, unsigned int size)
{
unsigned char c, t;
}
}
-struct test_file *
+static struct test_file *
get_tf(void)
{
unsigned int index = 0;
return &test_files[index % num_test_files];
}
-void
+static void
assign_fd_policy(char *policy)
{
if (!strcmp(policy, "random")) {
}
}
-int
+static int
get_fd(void)
{
struct test_file *tf = get_tf();
return c ? c++ : path;
}
-void
+static void
open_test_files(char **argv, int argc)
{
struct test_file *tf;
for (i = 0, tf = test_files; i < num_test_files; i++, tf++) {
tf->path = argv[i];
-#ifdef O_DIRECT
tf->o_direct = (random() % (o_direct + 1)) ? OP_DIRECT : 0;
-#endif
tf->fd = open(tf->path,
O_RDWR | (lite ? 0 : O_CREAT | O_TRUNC) |
tf->o_direct, 0666);
prt("fd %d: %s\n", i, tf->path);
}
-void
+static void
close_test_files(void)
{
int i;
}
}
-void
+static void
check_size(void)
{
struct stat statbuf;
}
}
-void
+static void
check_trunc_hack(void)
{
struct stat statbuf;
}
}
-void
+static void
output_line(struct test_file *tf, int op, unsigned int offset,
unsigned int size)
{
[OP_READ + OP_DIRECT] = "read_OD",
[OP_WRITE + OP_DIRECT] = "write_OD",
[OP_FALLOCATE] = "fallocate",
+ [OP_PUNCH_HOLE] = "punch from",
};
/* W. */
(monitorend == -1 || offset <= monitorend)))))))
return;
- prt("%06lu%s %lu.%06u %-10s %#08x %s %#08x\t(0x05%x bytes)\n",
+ prt("%06lu%s %lu.%06u %-10s %#08x %s %#08x\t(0x%x bytes)\n",
testcalls, fill_tf_buf(tf), tv.tv_sec, (int)tv.tv_usec,
- ops[op], offset, op == OP_TRUNCATE ? " to " : "thru",
- offset + size - 1, (int)size < 0 ? -(int)size : size);
+ ops[op], offset, op == OP_TRUNCATE || op == OP_PUNCH_HOLE ?
+ " to " : "thru", offset + size - 1,
+ (int)size < 0 ? -(int)size : size);
+}
+
+static void
+mirror_output_line(struct test_file *tf, int op, int mirrors, int id)
+{
+ if (!(!quiet &&
+ ((progressinterval && testcalls % progressinterval == 0) ||
+ (debug && (monitorstart == -1)))))
+ return;
+
+ prt("%06lu %lu.%06u %-10s ",
+ testcalls, tv.tv_sec, (int)tv.tv_usec, mirror_op_str[op]);
+
+ switch (op) {
+ case MIRROR_EXTEND:
+ prt("to %d mirrors\n", mirrors + 1);
+ break;
+ case MIRROR_SPLIT:
+ prt("mirror %d to %d mirrors\n", id, mirrors - 1);
+ break;
+ case MIRROR_RESYNC:
+ prt("%d mirrors\n", mirrors);
+ break;
+ }
}
-void output_debug(unsigned int offset, unsigned int size, const char *what)
+static void output_debug(unsigned int offset, unsigned int size,
+ const char *what)
{
struct timeval t;
}
}
-void
+static void
doflush(unsigned int offset, unsigned int size)
{
unsigned int pg_offset;
output_debug(offset, size, "flush done");
}
-void
+static void
doread(unsigned int offset, unsigned int size)
{
off_t ret;
check_buffers(offset, size);
}
-void
+static void
check_eofpage(char *s, unsigned int offset, char *p, int size)
{
long last_page, should_be_zero;
}
}
-void
+static void
domapread(unsigned int offset, unsigned int size)
{
unsigned int pg_offset;
check_buffers(offset, size);
}
-void
+static void
gendata(char *original_buf, char *good_buf, unsigned int offset,
unsigned int size)
{
}
}
-void
+static void
dowrite(unsigned int offset, unsigned int size)
{
off_t ret;
}
}
-void
+static void
domapwrite(unsigned int offset, unsigned int size)
{
unsigned int pg_offset;
output_debug(offset, map_size, "munmap done");
}
-void
+static void
dotruncate(unsigned int size)
{
int oldsize = file_size;
output_debug(size, 0, "truncate done");
}
-void
+static void
do_punch_hole(unsigned int offset, unsigned int length)
{
int max_offset = 0;
memset(good_buf + max_offset, '\0', max_len);
}
-void
+static void
do_zero_range(unsigned int offset, unsigned int length)
{
unsigned int end_offset;
* fallocate is basically a no-op unless extending,
* then a lot like a truncate
*/
-void
+static void
do_preallocate(unsigned int offset, unsigned int length)
{
off_t end_offset;
- off_t new_offset;
int keep_size;
int fd;
struct stat statbufs;
keep_size = fl_keep_size && (random() % 2);
- end_offset = keep_size ? 0 : offset + length;
-
+ end_offset = offset + length;
if (end_offset > biggest) {
biggest = end_offset;
if (!quiet && testcalls > simulatedopcount)
log4(OP_FALLOCATE, offset, length, (end_offset > file_size) ?
(keep_size ? 0 : 1) : 2);
- if (end_offset > file_size) {
+ if (end_offset > file_size && !keep_size) {
memset(good_buf + file_size, '\0', end_offset - file_size);
file_size = end_offset;
- } else {
- new_offset = file_size - (offset + length);
- length = length + new_offset;
}
if (testcalls <= simulatedopcount)
output_debug(offset, length, "fallocate done");
}
-void
+static void
writefileimage()
{
ssize_t iret;
}
}
-void
+static void
docloseopen(void)
{
int direct = 0;
return;
tf = get_tf();
-#ifdef O_DIRECT
direct = (random() % (o_direct + 1)) ? OP_DIRECT : 0;
-#endif
log4(OP_CLOSEOPEN + direct, file_size, (unsigned int)file_size, 0);
if (fd_policy != FD_SINGLE)
tf->o_direct ? "open(O_DIRECT) done" : "open done");
}
+static int
+get_mirror_ids(int fd, unsigned int *ids)
+{
+ struct llapi_layout *layout;
+ uint16_t count;
+ int rc;
+
+ layout = llapi_layout_get_by_fd(fd, 0);
+ if (layout == NULL)
+ return 0;
+
+ /* only get mirror count */
+ rc = llapi_layout_mirror_count_get(layout, &count);
+ if (rc < 0)
+ prt("llapi_layout_mirror_count_get: %d\n", rc);
+ if (count == 0)
+ return 0;
+
+ count = 0;
+
+ rc = llapi_layout_comp_use(layout, LLAPI_LAYOUT_COMP_USE_FIRST);
+ if (rc < 0) {
+ prt("llapi_layout_comp_use(USE_FIRST): %d\n", rc);
+ goto free;
+ }
+
+ do {
+ unsigned int id;
+
+ rc = llapi_layout_mirror_id_get(layout, &id);
+ if (rc < 0) {
+ prt("llapi_layout_mirror_id_get: %d\n", rc);
+ goto free;
+ }
+
+ if (!count || ids[count - 1] != id)
+ ids[count++] = id;
+
+ rc = llapi_layout_comp_use(layout, LLAPI_LAYOUT_COMP_USE_NEXT);
+ if (rc < 0) {
+ prt("llapi_layout_comp_use(USE_NEXT): %d\n", rc);
+ goto free;
+ }
+ } while (rc == 0);
+
+free:
+ llapi_layout_free(layout);
+
+ return rc < 0 ? rc : count;
+}
+
+static void
+do_mirror_ops(int op)
+{
+ int mirror_count;
+ char cmd[PATH_MAX * 2];
+ int i = 0;
+ int rc;
+
+ if (testcalls <= simulatedopcount)
+ return;
+
+ tf = get_tf();
+
+ mirror_count = get_mirror_ids(tf->fd, mirror_ids);
+ if (mirror_count < 0) {
+ prterr("get_mirror_ids");
+ report_failure(182);
+ }
+
+ switch (op) {
+ case MIRROR_EXTEND:
+ if (mirror_count == LUSTRE_MIRROR_COUNT_MAX)
+ return;
+ snprintf(cmd, sizeof(cmd), "lfs mirror extend -N -c-1 %s",
+ tf->path);
+ break;
+ case MIRROR_SPLIT:
+ if (mirror_count == 0 || mirror_count == 1)
+ return;
+
+ i = random() % mirror_count;
+ if (i == 0)
+ i++;
+
+ snprintf(cmd, sizeof(cmd),
+ "lfs mirror split -d --mirror-id=%d %s",
+ mirror_ids[i], tf->path);
+ break;
+ case MIRROR_RESYNC:
+ if (mirror_count < 2)
+ return;
+
+ snprintf(cmd, sizeof(cmd),
+ "lfs mirror resync %s", tf->path);
+ break;
+ }
+
+ if (close(tf->fd))
+ report_failure(183);
+ output_debug(monitorstart, 0, "close done");
+
+ log4(OP_MIRROR_OPS, op, mirror_count, i);
+
+ mirror_output_line(tf, op, mirror_count, i);
+
+ rc = system(cmd);
+ if (rc < 0) {
+ prt("%s: %d\n", cmd, errno);
+ report_failure(184);
+ } else if (WIFEXITED(rc)) {
+ rc = WEXITSTATUS(rc);
+ if (rc > 0) {
+ prt("%s: %d\n", cmd, rc);
+ /* mirror split won't delete the last non-stale mirror,
+ * and returns EUCLEAN
+ */
+ if (rc != EUCLEAN)
+ report_failure(184);
+ }
+ }
+ output_debug(monitorstart, 0, cmd);
+
+ switch (op) {
+ case MIRROR_SPLIT:
+ if (mirror_count == 2)
+ break;
+ case MIRROR_EXTEND:
+ case MIRROR_RESYNC:
+ /* verify mirror */
+ snprintf(cmd, sizeof(cmd),
+ "lfs mirror verify %s", tf->path);
+
+ rc = system(cmd);
+ if (rc < 0) {
+ prt("mirror op %d: %s: %d\n", op, cmd, errno);
+ report_failure(184);
+ } else if (WIFEXITED(rc)) {
+ rc = WEXITSTATUS(rc);
+ if (rc > 0) {
+ prt("mirror op %d: %s: %d\n", op, cmd, rc);
+ snprintf(cmd, sizeof(cmd),
+ "lfs mirror verify -v %s", tf->path);
+ rc = system(cmd);
+ report_failure(184);
+ }
+ }
+ }
+
+ output_debug(monitorstart, 0, cmd);
+
+ tf->fd = open(tf->path, O_RDWR | tf->o_direct, 0);
+ if (tf->fd < 0) {
+ prterr(tf->o_direct ? "open(O_DIRECT)" : "open");
+ report_failure(185);
+ }
+ output_debug(monitorstart, 0,
+ tf->o_direct ? "open(O_DIRECT) done" : "open done");
+}
+
#define TRIM_OFF_LEN(off, len, size) \
do { \
if (size) \
(len) = (size) - (off); \
} while (0)
-void
+static void
test(void)
{
unsigned long offset;
if (closeopen)
docloseopen();
break;
+ case OP_MIRROR_OPS:
+ if (flrmode)
+ do_mirror_ops(random() % MIRROR_OPS);
+ break;
default:
prterr("unknown operation %d: Operation not supported");
report_failure(42);
check_size();
}
-void
+static void
segv(int sig)
{
if (jmpbuf_good) {
report_failure(9999);
}
-void
+static void
cleanup(sig)
int sig;
{
exit(sig);
}
-void
+static void
usage(void)
{
fprintf(stdout,
" order with 'rotate' or chose them at 'random'. (default random)\n"
" -L: fsxLite - no file creations & no file size changes\n"
/* OSX: -I: start interactive mode since operation opnum\n\ */
-/* OSX: -M: slow motion mode, wait 1 second before each op\n\ */
+" -M: mirror file test mode\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"
/* OSX: -T datasize: atomic data element write size [1,2,4] (default 4)\n\ */
" -W: mapped write operations DISabled\n"
-#ifdef O_DIRECT
" -Z[P]: O_DIRECT file IO [1 in P chance for each open] (default off)\n"
-#endif
" fname: this filename is REQUIRED (no default)\n",
page_size);
exit(90);
}
-int
+static int
getnum(char *s, char **e)
{
int ret = -1;
return (ret);
}
-int
+static int
test_fallocate(int mode)
{
int ret = 0;
int fd = get_fd();
if (!lite) {
- if (fallocate(fd, mode, 0, 1) && errno == EOPNOTSUPP) {
+ /* Must go more than a page away so let's go 4M to be sure */
+ if (fallocate(fd, mode, 0, 4096*1024) && errno == EOPNOTSUPP) {
if (!quiet)
warn("%s: filesystem does not support fallocate mode 0x%x, disabling!",
__func__, mode);
ret = 1;
}
- /* Call truncate only when fallocate succeeds */
- if (ret == 1 && ftruncate(fd, 0) == -1)
+ /* Always call ftruncate since file size might be adjusted
+ * by fallocate even on error
+ */
+ if (ftruncate(fd, 0) == -1)
warn("ftruncate to 0 size failed");
}
return ret;
setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
while ((ch = getopt(argc, argv,
- "b:c:dfl:m:no:p:qr:s:t:w:xyzD:FHI:LN:OP:RS:WZ::"))
+ "b:c:dfl:m:no:p:qr:s:t:w:xyzD:FHI:LMN:OP:RS:WZ::"))
!= EOF)
switch (ch) {
case 'b':
case 'L':
lite = 1;
break;
+ case 'M':
+ flrmode = 1;
+ break;
case 'N':
numops = getnum(optarg, &endp);
if (numops < 0)
fprintf(stdout, "mapped writes DISABLED\n");
break;
case 'Z':
-#ifdef O_DIRECT
if (optarg)
o_direct = getnum(optarg, &endp);
if (!optarg || o_direct == 0)
o_direct = 1;
-#endif
break;
default:
usage();