fsx: Add mechanism to replay failed operations
[xfstests-dev.git] / ltp / fsx.c
index 47d3ee8d45556f8d3c11d64932a3da0994deac1b..28f109cd14b467e92a2e359efe0d154e8cfb3df7 100644 (file)
--- a/ltp/fsx.c
+++ b/ltp/fsx.c
 
 #define NUMPRINTCOLUMNS 32     /* # columns of data to print on each line */
 
+/* Operation flags */
+
+enum opflags { FL_NONE = 0, FL_SKIPPED = 1, FL_CLOSE_OPEN = 2, FL_KEEP_SIZE = 4 };
+
 /*
  *     A log entry is an operation and a bunch of arguments.
  */
 struct log_entry {
        int     operation;
        int     args[3];
+       enum opflags flags;
 };
 
-#define        LOGSIZE 1000
+#define        LOGSIZE 10000
 
 struct log_entry       oplog[LOGSIZE]; /* the log */
 int                    logptr = 0;     /* current position in log */
@@ -73,9 +78,7 @@ int                   logcount = 0;   /* total ops */
  *
  * When mapped read/writes are disabled, they are simply converted to normal
  * reads and writes. When fallocate/fpunch calls are disabled, they are
- * converted to OP_SKIPPED. Hence OP_SKIPPED needs to have a number higher than
- * the operation selction matrix, as does the OP_CLOSEOPEN which is an
- * operation modifier rather than an operation in itself.
+ * skipped.
  *
  * Because of the "lite" version, we also need to have different "maximum
  * operation" defines to allow the ops to be selected correctly based on the
@@ -95,11 +98,8 @@ int                  logcount = 0;   /* total ops */
 #define OP_PUNCH_HOLE          6
 #define OP_ZERO_RANGE          7
 #define OP_COLLAPSE_RANGE      8
-#define OP_MAX_FULL            9
-
-/* operation modifiers */
-#define OP_CLOSEOPEN   100
-#define OP_SKIPPED     101
+#define OP_INSERT_RANGE        9
+#define OP_MAX_FULL            10
 
 #undef PAGE_SIZE
 #define PAGE_SIZE       getpagesize()
@@ -142,9 +142,11 @@ 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     keep_size_calls = 1;            /* -K flag disables */
 int     punch_hole_calls = 1;           /* -H flag disables */
 int     zero_range_calls = 1;           /* -z flag disables */
 int    collapse_range_calls = 1;       /* -C flag disables */
+int    insert_range_calls = 1;         /* -I flag disables */
 int    mapped_reads = 1;               /* -R flag disables it */
 int    fsxgoodfd = 0;
 int    o_direct;                       /* -Z */
@@ -164,7 +166,10 @@ int aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset);
 #define fsxwrite(a,b,c,d)      write(a,b,c)
 #endif
 
+const char *replayops = NULL;
 FILE * fsxlogf = NULL;
+FILE * replayopsf = NULL;
+char opsfile[1024];
 int badoff = -1;
 int closeopen = 0;
 
@@ -198,7 +203,7 @@ warn(const char * fmt, ...)  {
 #define BUF_SIZE 1024
 
 void
-prt(char *fmt, ...)
+prt(const char *fmt, ...)
 {
        va_list args;
        char buffer[BUF_SIZE];
@@ -212,24 +217,56 @@ prt(char *fmt, ...)
 }
 
 void
-prterr(char *prefix)
+prterr(const char *prefix)
 {
        prt("%s%s%s\n", prefix, prefix ? ": " : "", strerror(errno));
 }
 
 
+static const char *op_names[] = {
+       [OP_READ] = "read",
+       [OP_WRITE] = "write",
+       [OP_MAPREAD] = "mapread",
+       [OP_MAPWRITE] = "mapwrite",
+       [OP_TRUNCATE] = "truncate",
+       [OP_FALLOCATE] = "fallocate",
+       [OP_PUNCH_HOLE] = "punch_hole",
+       [OP_ZERO_RANGE] = "zero_range",
+       [OP_COLLAPSE_RANGE] = "collapse_range",
+       [OP_INSERT_RANGE] = "insert_range",
+};
+
+static const char *op_name(int operation)
+{
+       if (operation >= 0 &&
+           operation < sizeof(op_names) / sizeof(op_names[0]))
+               return op_names[operation];
+       return NULL;
+}
+
+static int op_code(const char *name)
+{
+       int i;
+
+       for (i = 0; i < sizeof(op_names) / sizeof(op_names[0]); i++)
+               if (op_names[i] && strcmp(name, op_names[i]) == 0)
+                       return i;
+       return -1;
+}
+
 void
-log4(int operation, int arg0, int arg1, int arg2)
+log4(int operation, int arg0, int arg1, enum opflags flags)
 {
        struct log_entry *le;
 
        le = &oplog[logptr];
        le->operation = operation;
        if (closeopen)
-               le->operation = ~ le->operation;
+               flags |= FL_CLOSE_OPEN;
        le->args[0] = arg0;
        le->args[1] = arg1;
-       le->args[2] = arg2;
+       le->args[2] = file_size;
+       le->flags = flags;
        logptr++;
        logcount++;
        if (logptr >= LOGSIZE)
@@ -240,11 +277,16 @@ log4(int operation, int arg0, int arg1, int arg2)
 void
 logdump(void)
 {
+       FILE    *logopsf;
        int     i, count, down;
        struct log_entry        *lp;
-       char *falloc_type[3] = {"PAST_EOF", "EXTENDING", "INTERIOR"};
 
        prt("LOG DUMP (%d total operations):\n", logcount);
+
+       logopsf = fopen(opsfile, "w");
+       if (!logopsf)
+               prterr(opsfile);
+
        if (logcount < LOGSIZE) {
                i = 0;
                count = logcount;
@@ -253,37 +295,41 @@ logdump(void)
                count = LOGSIZE;
        }
        for ( ; count > 0; count--) {
+               bool overlap;
                int opnum;
 
                opnum = i+1 + (logcount/LOGSIZE)*LOGSIZE;
                prt("%d(%3d mod 256): ", opnum, opnum%256);
                lp = &oplog[i];
-               if ((closeopen = lp->operation < 0))
-                       lp->operation = ~ lp->operation;
-                       
+
+               overlap = badoff >= lp->args[0] &&
+                         badoff < lp->args[0] + lp->args[1];
+
+               if (lp->flags & FL_SKIPPED) {
+                       prt("SKIPPED (no operation)");
+                       goto skipped;
+               }
+
                switch (lp->operation) {
                case OP_MAPREAD:
                        prt("MAPREAD  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])
+                       if (overlap)
                                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])
+                       if (overlap)
                                prt("\t******WWWW");
                        break;
                case OP_READ:
                        prt("READ     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])
+                       if (overlap)
                                prt("\t***RRRR***");
                        break;
                case OP_WRITE:
@@ -294,64 +340,100 @@ logdump(void)
                                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])
+                       overlap = (badoff >= lp->args[0] ||
+                                  badoff >=lp->args[2]) &&
+                                 badoff < lp->args[0] + lp->args[1];
+                       if (overlap)
                                prt("\t***WWWW");
                        break;
                case OP_TRUNCATE:
-                       down = lp->args[0] < lp->args[1];
+                       down = lp->args[1] < lp->args[2];
                        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])
+                           down ? "DOWN" : "UP", lp->args[2], lp->args[1]);
+                       overlap = badoff >= lp->args[1 + !down] &&
+                                 badoff < lp->args[1 + !!down];
+                       if (overlap)
                                prt("\t******WWWW");
                        break;
                case OP_FALLOCATE:
                        /* 0: offset 1: length 2: where alloced */
-                       prt("FALLOC   0x%x thru 0x%x\t(0x%x bytes) %s",
+                       prt("FALLOC   0x%x thru 0x%x\t(0x%x bytes) ",
                                lp->args[0], lp->args[0] + lp->args[1],
-                               lp->args[1], falloc_type[lp->args[2]]);
-                       if (badoff >= lp->args[0] &&
-                           badoff < lp->args[0] + lp->args[1])
+                               lp->args[1]);
+                       if (lp->args[0] + lp->args[1] <= lp->args[2])
+                               prt("INTERIOR");
+                       else if (lp->flags & FL_KEEP_SIZE)
+                               prt("PAST_EOF");
+                       else
+                               prt("EXTENDING");
+                       if (overlap)
                                prt("\t******FFFF");
                        break;
                case OP_PUNCH_HOLE:
                        prt("PUNCH    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])
+                       if (overlap)
                                prt("\t******PPPP");
                        break;
                case OP_ZERO_RANGE:
                        prt("ZERO     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])
+                       if (overlap)
                                prt("\t******ZZZZ");
                        break;
                case OP_COLLAPSE_RANGE:
                        prt("COLLAPSE 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])
+                       if (overlap)
                                prt("\t******CCCC");
                        break;
-               case OP_SKIPPED:
-                       prt("SKIPPED (no operation)");
+               case OP_INSERT_RANGE:
+                       prt("INSERT 0x%x thru 0x%x\t(0x%x bytes)",
+                           lp->args[0], lp->args[0] + lp->args[1] - 1,
+                           lp->args[1]);
+                       if (overlap)
+                               prt("\t******IIII");
                        break;
                default:
                        prt("BOGUS LOG ENTRY (operation code = %d)!",
                            lp->operation);
+                       continue;
                }
-               if (closeopen)
+
+           skipped:
+               if (lp->flags & FL_CLOSE_OPEN)
                        prt("\n\t\tCLOSE/OPEN");
                prt("\n");
                i++;
                if (i == LOGSIZE)
                        i = 0;
+
+               if (logopsf) {
+                       if (lp->flags & FL_SKIPPED)
+                               fprintf(logopsf, "skip ");
+                       fprintf(logopsf, "%s 0x%x 0x%x 0x%x",
+                               op_name(lp->operation),
+                               lp->args[0], lp->args[1], lp->args[2]);
+                       if (lp->flags & FL_KEEP_SIZE)
+                               fprintf(logopsf, " keep_size");
+                       if (lp->flags & FL_CLOSE_OPEN)
+                               fprintf(logopsf, " close_open");
+                       if (overlap)
+                               fprintf(logopsf, " *");
+                       fprintf(logopsf, "\n");
+               }
+       }
+
+       if (logopsf) {
+               if (fclose(logopsf) != 0)
+                       prterr(opsfile);
+               else
+                       prt("Log of operations saved to \"%s\"; "
+                           "replay with --replay-ops\n",
+                           opsfile);
        }
 }
 
@@ -436,10 +518,10 @@ check_buffers(unsigned offset, unsigned size)
                        if (c != t) {
                                if (n < 16) {
                                        bad = short_at(&temp_buf[i]);
-                                       prt("0x%5x\t0x%04x\t0x%04x", offset,
+                                       prt("0x%05x\t0x%04x\t0x%04x", offset,
                                            short_at(&good_buf[offset]), bad);
                                        op = temp_buf[offset & 1 ? i+1 : i];
-                                       prt("\t0x%5x\n", n);
+                                       prt("\t0x%05x\n", n);
                                        if (op)
                                                prt("operation# (mod 256) for "
                                                  "the bad data may be %u\n",
@@ -538,17 +620,17 @@ doread(unsigned offset, unsigned size)
        if (size == 0) {
                if (!quiet && testcalls > simulatedopcount && !o_direct)
                        prt("skipping zero size read\n");
-               log4(OP_SKIPPED, OP_READ, offset, size);
+               log4(OP_READ, offset, size, FL_SKIPPED);
                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);
+               log4(OP_READ, offset, size, FL_SKIPPED);
                return;
        }
 
-       log4(OP_READ, offset, size, 0);
+       log4(OP_READ, offset, size, FL_NONE);
 
        if (testcalls <= simulatedopcount)
                return;
@@ -617,17 +699,17 @@ domapread(unsigned offset, unsigned size)
        if (size == 0) {
                if (!quiet && testcalls > simulatedopcount)
                        prt("skipping zero size read\n");
-               log4(OP_SKIPPED, OP_MAPREAD, offset, size);
+               log4(OP_MAPREAD, offset, size, FL_SKIPPED);
                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);
+               log4(OP_MAPREAD, offset, size, FL_SKIPPED);
                return;
        }
 
-       log4(OP_MAPREAD, offset, size, 0);
+       log4(OP_MAPREAD, offset, size, FL_NONE);
 
        if (testcalls <= simulatedopcount)
                return;
@@ -686,11 +768,11 @@ dowrite(unsigned offset, unsigned size)
        if (size == 0) {
                if (!quiet && testcalls > simulatedopcount && !o_direct)
                        prt("skipping zero size write\n");
-               log4(OP_SKIPPED, OP_WRITE, offset, size);
+               log4(OP_WRITE, offset, size, FL_SKIPPED);
                return;
        }
 
-       log4(OP_WRITE, offset, size, file_size);
+       log4(OP_WRITE, offset, size, FL_NONE);
 
        gendata(original_buf, good_buf, offset, size);
        if (file_size < offset + size) {
@@ -752,12 +834,12 @@ domapwrite(unsigned offset, unsigned size)
        if (size == 0) {
                if (!quiet && testcalls > simulatedopcount)
                        prt("skipping zero size write\n");
-               log4(OP_SKIPPED, OP_MAPWRITE, offset, size);
+               log4(OP_MAPWRITE, offset, size, FL_SKIPPED);
                return;
        }
        cur_filesize = file_size;
 
-       log4(OP_MAPWRITE, offset, size, 0);
+       log4(OP_MAPWRITE, offset, size, FL_NONE);
 
        gendata(original_buf, good_buf, offset, size);
        if (file_size < offset + size) {
@@ -824,7 +906,7 @@ dotruncate(unsigned size)
                        prt("truncating to largest ever: 0x%x\n", size);
        }
 
-       log4(OP_TRUNCATE, size, (unsigned)file_size, 0);
+       log4(OP_TRUNCATE, 0, size, FL_NONE);
 
        if (size > file_size)
                memset(good_buf + file_size, '\0', size - file_size);
@@ -856,20 +938,20 @@ do_punch_hole(unsigned offset, unsigned length)
        if (length == 0) {
                if (!quiet && testcalls > simulatedopcount)
                        prt("skipping zero length punch hole\n");
-                       log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, length);
+                       log4(OP_PUNCH_HOLE, offset, length, FL_SKIPPED);
                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);
+                       log4(OP_PUNCH_HOLE, offset, length, FL_SKIPPED);
                return;
        }
 
        end_offset = offset + length;
 
-       log4(OP_PUNCH_HOLE, offset, length, 0);
+       log4(OP_PUNCH_HOLE, offset, length, FL_NONE);
 
        if (testcalls <= simulatedopcount)
                return;
@@ -903,21 +985,19 @@ do_punch_hole(unsigned offset, unsigned length)
 
 #ifdef FALLOC_FL_ZERO_RANGE
 void
-do_zero_range(unsigned offset, unsigned length)
+do_zero_range(unsigned offset, unsigned length, int keep_size)
 {
        unsigned end_offset;
        int mode = FALLOC_FL_ZERO_RANGE;
-       int keep_size;
 
        if (length == 0) {
                if (!quiet && testcalls > simulatedopcount)
                        prt("skipping zero length zero range\n");
-                       log4(OP_SKIPPED, OP_ZERO_RANGE, offset, length);
+                       log4(OP_ZERO_RANGE, offset, length, FL_SKIPPED |
+                            (keep_size ? FL_KEEP_SIZE : FL_NONE));
                return;
        }
 
-       keep_size = random() % 2;
-
        end_offset = keep_size ? 0 : offset + length;
 
        if (end_offset > biggest) {
@@ -932,7 +1012,8 @@ do_zero_range(unsigned offset, unsigned length)
         *      1: extending prealloc
         *      2: interior prealloc
         */
-       log4(OP_ZERO_RANGE, offset, length, (end_offset > file_size) ? (keep_size ? 0 : 1) : 2);
+       log4(OP_ZERO_RANGE, offset, length,
+            keep_size ? FL_KEEP_SIZE : FL_NONE);
 
        if (testcalls <= simulatedopcount)
                return;
@@ -954,7 +1035,7 @@ do_zero_range(unsigned offset, unsigned length)
 
 #else
 void
-do_zero_range(unsigned offset, unsigned length)
+do_zero_range(unsigned offset, unsigned length, int keep_size)
 {
        return;
 }
@@ -970,7 +1051,7 @@ do_collapse_range(unsigned offset, unsigned length)
        if (length == 0) {
                if (!quiet && testcalls > simulatedopcount)
                        prt("skipping zero length collapse range\n");
-               log4(OP_SKIPPED, OP_COLLAPSE_RANGE, offset, length);
+               log4(OP_COLLAPSE_RANGE, offset, length, FL_SKIPPED);
                return;
        }
 
@@ -978,11 +1059,11 @@ do_collapse_range(unsigned offset, unsigned length)
        if ((loff_t)end_offset >= file_size) {
                if (!quiet && testcalls > simulatedopcount)
                        prt("skipping collapse range behind EOF\n");
-               log4(OP_SKIPPED, OP_COLLAPSE_RANGE, offset, length);
+               log4(OP_COLLAPSE_RANGE, offset, length, FL_SKIPPED);
                return;
        }
 
-       log4(OP_COLLAPSE_RANGE, offset, length, 0);
+       log4(OP_COLLAPSE_RANGE, offset, length, FL_NONE);
 
        if (testcalls <= simulatedopcount)
                return;
@@ -1012,23 +1093,74 @@ do_collapse_range(unsigned offset, unsigned length)
 }
 #endif
 
+#ifdef FALLOC_FL_INSERT_RANGE
+void
+do_insert_range(unsigned offset, unsigned length)
+{
+       unsigned end_offset;
+       int mode = FALLOC_FL_INSERT_RANGE;
+
+       if (length == 0) {
+               if (!quiet && testcalls > simulatedopcount)
+                       prt("skipping zero length insert range\n");
+               log4(OP_INSERT_RANGE, offset, length, FL_SKIPPED);
+               return;
+       }
+
+       if ((loff_t)offset >= file_size) {
+               if (!quiet && testcalls > simulatedopcount)
+                       prt("skipping insert range behind EOF\n");
+               log4(OP_INSERT_RANGE, offset, length, FL_SKIPPED);
+               return;
+       }
+
+       log4(OP_INSERT_RANGE, offset, length, FL_NONE);
+
+       if (testcalls <= simulatedopcount)
+               return;
+
+       end_offset = offset + length;
+       if ((progressinterval && testcalls % progressinterval == 0) ||
+           (debug && (monitorstart == -1 || monitorend == -1 ||
+                     end_offset <= monitorend))) {
+               prt("%lu insert\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("insert range: %x to %x\n", offset, length);
+               prterr("do_insert_range: fallocate");
+               report_failure(161);
+       }
+
+       memmove(good_buf + end_offset, good_buf + offset,
+               file_size - offset);
+       memset(good_buf + offset, '\0', length);
+       file_size += length;
+}
+
+#else
+void
+do_insert_range(unsigned offset, unsigned length)
+{
+       return;
+}
+#endif
+
 #ifdef HAVE_LINUX_FALLOC_H
 /* fallocate is basically a no-op unless extending, then a lot like a truncate */
 void
-do_preallocate(unsigned offset, unsigned length)
+do_preallocate(unsigned offset, unsigned length, int keep_size)
 {
        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);
+                log4(OP_FALLOCATE, offset, length, FL_SKIPPED |
+                    (keep_size ? FL_KEEP_SIZE : FL_NONE));
                 return;
         }
 
-       keep_size = random() % 2;
-
        end_offset = keep_size ? 0 : offset + length;
 
        if (end_offset > biggest) {
@@ -1043,7 +1175,8 @@ do_preallocate(unsigned offset, unsigned length)
         *      1: extending prealloc
         *      2: interior prealloc
         */
-       log4(OP_FALLOCATE, offset, length, (end_offset > file_size) ? (keep_size ? 0 : 1) : 2);
+       log4(OP_FALLOCATE, offset, length,
+            keep_size ? FL_KEEP_SIZE : FL_NONE);
 
        if (end_offset > file_size) {
                memset(good_buf + file_size, '\0', end_offset - file_size);
@@ -1066,7 +1199,7 @@ do_preallocate(unsigned offset, unsigned length)
 }
 #else
 void
-do_preallocate(unsigned offset, unsigned length)
+do_preallocate(unsigned offset, unsigned length, int keep_size)
 {
        return;
 }
@@ -1117,39 +1250,140 @@ docloseopen(void)
        }
 }
 
-#define TRIM_OFF_LEN(off, len, size)   \
-do {                                   \
-       if (size)                       \
-               (off) %= (size);        \
-       else                            \
-               (off) = 0;              \
-       if ((off) + (len) > (size))     \
-               (len) = (size) - (off); \
+
+#define TRIM_OFF(off, size)                    \
+do {                                           \
+       if (size)                               \
+               (off) %= (size);                \
+       else                                    \
+               (off) = 0;                      \
 } while (0)
 
-void
+#define TRIM_LEN(off, len, size)               \
+do {                                           \
+       if ((off) + (len) > (size))             \
+               (len) = (size) - (off);         \
+} while (0)
+
+#define TRIM_OFF_LEN(off, len, size)           \
+do {                                           \
+       TRIM_OFF(off, size);                    \
+       TRIM_LEN(off, len, size);               \
+} while (0)
+
+void cleanup();
+
+static int
+read_op(struct log_entry *log_entry)
+{
+       char line[256];
+
+       memset(log_entry, 0, sizeof(*log_entry));
+       log_entry->operation = -1;
+
+       while (log_entry->operation == -1) {
+               char *str;
+               int i;
+
+               do {
+                       if (!fgets(line, sizeof(line), replayopsf)) {
+                               if (feof(replayopsf)) {
+                                       replayopsf = NULL;
+                                       return 0;
+                               }
+                               goto fail;
+                       }
+                       str = strtok(line, " \t\n");
+               } while (!str);
+
+               if (strcmp(str, "skip") == 0) {
+                       log_entry->flags |= FL_SKIPPED;
+                       str = strtok(NULL, " \t\n");
+                       if (!str)
+                               goto fail;
+               }
+               log_entry->operation = op_code(str);
+               if (log_entry->operation == -1)
+                       goto fail;
+               for (i = 0; i < 3; i++) {
+                       char *end;
+
+                       str = strtok(NULL, " \t\n");
+                       if (!str)
+                               goto fail;
+                       log_entry->args[i] = strtoul(str, &end, 0);
+                       if (*end)
+                               goto fail;
+               }
+               while ((str = strtok(NULL, " \t\n"))) {
+                       if (strcmp(str, "keep_size") == 0)
+                               log_entry->flags |= FL_KEEP_SIZE;
+                       else if (strcmp(str, "close_open") == 0)
+                               log_entry->flags |= FL_CLOSE_OPEN;
+                       else if (strcmp(str, "*") == 0)
+                               ;  /* overlap marker; ignore */
+                       else
+                               goto fail;
+               }
+       }
+       return 1;
+
+fail:
+       fprintf(stderr, "%s: parse error\n", replayops);
+       fclose(replayopsf);
+       replayopsf = NULL;
+       cleanup(100);  /* doesn't return */
+       return 0;
+}
+
+
+int
 test(void)
 {
        unsigned long   offset;
-       unsigned long   size = maxoplen;
-       unsigned long   rv = random();
+       unsigned long   size;
+       unsigned long   rv;
        unsigned long   op;
+       int             keep_size = 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);
 
+       if (replayopsf) {
+               struct log_entry log_entry;
+
+               while (read_op(&log_entry)) {
+                       if (log_entry.flags & FL_SKIPPED) {
+                               log4(log_entry.operation,
+                                    log_entry.args[0], log_entry.args[1],
+                                    log_entry.flags);
+                               continue;
+                       }
+
+                       op = log_entry.operation;
+                       offset = log_entry.args[0];
+                       size = log_entry.args[1];
+                       closeopen = !!(log_entry.flags & FL_CLOSE_OPEN);
+                       keep_size = !!(log_entry.flags & FL_KEEP_SIZE);
+                       goto have_op;
+               }
+               return 0;
+       }
+
+       rv = random();
+       if (closeprob)
+               closeopen = (rv >> 3) < (1 << 28) / closeprob;
+
        offset = random();
+       size = maxoplen;
        if (randomoplen)
                size = random() % (maxoplen + 1);
 
@@ -1159,6 +1393,23 @@ test(void)
        else
                op = rv % OP_MAX_FULL;
 
+       switch(op) {
+       case OP_TRUNCATE:
+               if (!style)
+                       size = random() % maxfilelen;
+               break;
+       case OP_FALLOCATE:
+               if (fallocate_calls && size && keep_size_calls)
+                       keep_size = random() % 2;
+               break;
+       case OP_ZERO_RANGE:
+               if (zero_range_calls && size && keep_size_calls)
+                       keep_size = random() % 2;
+               break;
+       }
+
+have_op:
+
        switch (op) {
        case OP_MAPREAD:
                if (!mapped_reads)
@@ -1170,25 +1421,31 @@ test(void)
                break;
        case OP_FALLOCATE:
                if (!fallocate_calls) {
-                       log4(OP_SKIPPED, OP_FALLOCATE, offset, size);
+                       log4(OP_FALLOCATE, offset, size, FL_SKIPPED);
                        goto out;
                }
                break;
        case OP_PUNCH_HOLE:
                if (!punch_hole_calls) {
-                       log4(OP_SKIPPED, OP_PUNCH_HOLE, offset, size);
+                       log4(OP_PUNCH_HOLE, offset, size, FL_SKIPPED);
                        goto out;
                }
                break;
        case OP_ZERO_RANGE:
                if (!zero_range_calls) {
-                       log4(OP_SKIPPED, OP_ZERO_RANGE, offset, size);
+                       log4(OP_ZERO_RANGE, offset, size, FL_SKIPPED);
                        goto out;
                }
                break;
        case OP_COLLAPSE_RANGE:
                if (!collapse_range_calls) {
-                       log4(OP_SKIPPED, OP_COLLAPSE_RANGE, offset, size);
+                       log4(OP_COLLAPSE_RANGE, offset, size, FL_SKIPPED);
+                       goto out;
+               }
+               break;
+       case OP_INSERT_RANGE:
+               if (!insert_range_calls) {
+                       log4(OP_INSERT_RANGE, offset, size, FL_SKIPPED);
                        goto out;
                }
                break;
@@ -1216,14 +1473,12 @@ test(void)
                break;
 
        case OP_TRUNCATE:
-               if (!style)
-                       size = random() % maxfilelen;
                dotruncate(size);
                break;
 
        case OP_FALLOCATE:
                TRIM_OFF_LEN(offset, size, maxfilelen);
-               do_preallocate(offset, size);
+               do_preallocate(offset, size, keep_size);
                break;
 
        case OP_PUNCH_HOLE:
@@ -1232,18 +1487,34 @@ test(void)
                break;
        case OP_ZERO_RANGE:
                TRIM_OFF_LEN(offset, size, file_size);
-               do_zero_range(offset, size);
+               do_zero_range(offset, size, keep_size);
                break;
        case OP_COLLAPSE_RANGE:
                TRIM_OFF_LEN(offset, size, file_size - 1);
                offset = offset & ~(block_size - 1);
                size = size & ~(block_size - 1);
                if (size == 0) {
-                       log4(OP_SKIPPED, OP_COLLAPSE_RANGE, offset, size);
+                       log4(OP_COLLAPSE_RANGE, offset, size, FL_SKIPPED);
                        goto out;
                }
                do_collapse_range(offset, size);
                break;
+       case OP_INSERT_RANGE:
+               TRIM_OFF(offset, file_size);
+               TRIM_LEN(file_size, size, maxfilelen);
+               offset = offset & ~(block_size - 1);
+               size = size & ~(block_size - 1);
+               if (size == 0) {
+                       log4(OP_INSERT_RANGE, offset, size, FL_SKIPPED);
+                       goto out;
+               }
+               if (file_size + size > maxfilelen) {
+                       log4(OP_INSERT_RANGE, offset, size, FL_SKIPPED);
+                       goto out;
+               }
+
+               do_insert_range(offset, size);
+               break;
        default:
                prterr("test: unknown operation");
                report_failure(42);
@@ -1255,6 +1526,7 @@ out:
                check_size();
        if (closeopen)
                docloseopen();
+       return 1;
 }
 
 
@@ -1307,6 +1579,9 @@ usage(void)
 #ifdef FALLOC_FL_COLLAPSE_RANGE
 "      -C: Do not use collapse range calls\n"
 #endif
+#ifdef FALLOC_FL_INSERT_RANGE
+"      -I: Do not use insert range 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\
@@ -1455,16 +1730,20 @@ int aio_rw(int rw, int fd, char *buf, unsigned len, unsigned offset)
 
 #endif
 
+#define test_fallocate(mode) __test_fallocate(mode, #mode)
+
 int
-test_fallocate(int mode)
+__test_fallocate(int mode, const char *mode_str)
 {
 #ifdef HAVE_LINUX_FALLOC_H
        int ret = 0;
        if (!lite) {
                if (fallocate(fd, mode, 0, 1) && errno == EOPNOTSUPP) {
                        if(!quiet)
-                               warn("main: filesystem does not support "
-                                    "fallocate mode 0x%x, disabling!\n", mode);
+                               fprintf(stderr,
+                                       "main: filesystem does not support "
+                                       "fallocate mode %s, disabling!\n",
+                                       mode_str);
                } else {
                        ret = 1;
                        ftruncate(fd, 0);
@@ -1474,6 +1753,11 @@ test_fallocate(int mode)
 #endif
 }
 
+static struct option longopts[] = {
+       {"replay-ops", required_argument, 0, 256},
+       { }
+};
+
 int
 main(int argc, char **argv)
 {
@@ -1493,8 +1777,9 @@ main(int argc, char **argv)
 
        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:FHzCLN:OP:RS:WZ"))
-              != EOF)
+       while ((ch = getopt_long(argc, argv,
+                                "b:c:dfl:m:no:p:qr:s:t:w:xyAD:FKHzCILN:OP:RS:WZ",
+                                longopts, NULL)) != EOF)
                switch (ch) {
                case 'b':
                        simulatedopcount = getnum(optarg, &endp);
@@ -1590,6 +1875,9 @@ main(int argc, char **argv)
                case 'F':
                        fallocate_calls = 0;
                        break;
+               case 'K':
+                       keep_size_calls = 0;
+                       break;
                case 'H':
                        punch_hole_calls = 0;
                        break;
@@ -1599,6 +1887,9 @@ main(int argc, char **argv)
                case 'C':
                        collapse_range_calls = 0;
                        break;
+               case 'I':
+                       insert_range_calls = 0;
+                       break;
                case 'L':
                        lite = 1;
                        break;
@@ -1636,6 +1927,9 @@ main(int argc, char **argv)
                case 'Z':
                        o_direct = O_DIRECT;
                        break;
+               case 256:  /* --replay-ops */
+                       replayops = optarg;
+                       break;
                default:
                        usage();
                        /* NOTREACHED */
@@ -1701,6 +1995,17 @@ main(int argc, char **argv)
                prterr(logfile);
                exit(93);
        }
+       strncat(opsfile, fname, 256);
+       strcat(opsfile, ".fsxops");
+       unlink(opsfile);
+
+       if (replayops) {
+               replayopsf = fopen(replayops, "r");
+               if (!replayopsf) {
+                       prterr(replayops);
+                       exit(93);
+               }
+       }
 
 #ifdef AIO
        if (aio) 
@@ -1751,22 +2056,26 @@ main(int argc, char **argv)
 
        if (fallocate_calls)
                fallocate_calls = test_fallocate(0);
+       if (keep_size_calls)
+               keep_size_calls = test_fallocate(FALLOC_FL_KEEP_SIZE);
        if (punch_hole_calls)
-               punch_hole_calls = test_fallocate(FALLOC_FL_PUNCH_HOLE |
-                                                 FALLOC_FL_KEEP_SIZE);
+               punch_hole_calls = test_fallocate(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE);
        if (zero_range_calls)
                zero_range_calls = test_fallocate(FALLOC_FL_ZERO_RANGE);
        if (collapse_range_calls)
                collapse_range_calls = test_fallocate(FALLOC_FL_COLLAPSE_RANGE);
+       if (insert_range_calls)
+               insert_range_calls = test_fallocate(FALLOC_FL_INSERT_RANGE);
 
        while (numops == -1 || numops--)
-               test();
+               if (!test())
+                       break;
 
        if (close(fd)) {
                prterr("close");
                report_failure(99);
        }
-       prt("All operations completed A-OK!\n");
+       prt("All %lu operations completed A-OK!\n", testcalls);
 
        exit(0);
        return 0;