Whamcloud - gitweb
b=18404
authorgrev <grev>
Fri, 3 Apr 2009 17:28:16 +0000 (17:28 +0000)
committergrev <grev>
Fri, 3 Apr 2009 17:28:16 +0000 (17:28 +0000)
o=Adilger
i=grev
i=Johann
write-append-truncate cleanup

lustre/tests/write_append_truncate.1 [new file with mode: 0644]
lustre/tests/write_append_truncate.c

diff --git a/lustre/tests/write_append_truncate.1 b/lustre/tests/write_append_truncate.1
new file mode 100644 (file)
index 0000000..4006f98
--- /dev/null
@@ -0,0 +1,109 @@
+.\" Copyright  2008 Sun Microsystems, Inc. All rights reserved
+.\" Use is subject to license terms.
+.\"
+.\" Author: Andreas Dilger <adilger@sun.com>
+.TH write_append_truncate 1 "Oct 29, 2008" Lustre "utilities"
+.SH NAME
+write_append_truncate \- MPI test of concurrent file operation coherency
+.SH SYNOPSIS
+.B write_append_truncate
+.RB [ -a
+.IR append_max ]
+.RB [ -C ]
+.RB [ -n
+.IR nloops ]
+.RB [ -s
+.IR seed ]
+.RB [ -t
+.IR trunc_max ]
+.RB [ -T ]
+.RB [ -w
+.IR write_max ]
+.RB [ -W ]
+.I filename
+.I ...
+.SH DESCRIPTION
+The
+.B write_append_truncate
+program does data coherency testing for parallel filesystem using
+.BR mpi (7)
+to launch and co-ordinate processes.  It runs multiple parallel
+processes on one or more nodes, as specified to
+.BR mpirun (1),
+.BR pdsh (1),
+or other MPI job launcher, each operating on the same shared file.
+.sp
+The master process creates
+.I filename
+if it does not exist
+.I
+For each of the
+.I num_loops
+test iterations one process truncates the file to zero size and writes
+a random number of check bytes,
+.BR W
+between 1 and
+.I write_max
+to
+.IR filename .
+.sp
+Other processes wait on an MPI barrier until
+this is complete.  One of the processes is chosen, to do an append
+.RB ( write (2)
+with
+.BR O_APPEND ) of
+a random size,
+.BR A ,
+between 1 and 
+.I append_max
+bytes to the end of the file.  A second process does a concurrent
+.BR truncate (2)
+operation on the file to a random size,
+.BR W + T ,
+between 1 and
+.I trunc_max
+bytes beyond the end of the initial
+.B W
+bytes data.  This truncate will either extend the file size if it is
+done before the append, or it will truncate the file if it is done
+after the append.
+.sp
+A second MPI barrier ensures both of the processes have completed
+their respective operations, and then the master process reads the entire
+file and verifies the contents.
+.sp
+The beginning of the file is verified to contain the
+.B W
+check bytes up to the offset it was initially filled to.
+.br
+If the file size is equal to the truncated size,
+.BR W + T ,
+then the truncate operation was done
+.I after
+the append and the data beyond the initial write offset
+.BR W
+to the end of file
+.BR W + T
+should contain the
+.B A
+bytes of append check data, and possibly a zero-filled hole at the
+end of the file if
+.B T
+is larger than
+.BR A .
+.sp
+Otherwise, the truncate operation was done
+.I before
+the append and the data beyond the initial write offset
+.B W
+to the truncate offset
+.BR W + T
+should be a zero-filled hole.  Beyond the hole to the end of file
+there should be
+.B A
+bytes of append check data.  The total file size should be
+.BR W " + " T " + " A .
+.LP
+.SH SEE ALSO
+.BR mpirun (1)
+.BR pdsh (1)
index 24f9043..bae9ba4 100644 (file)
  *
  * Each loop does 3 things:
  *   - truncate file to zero (not via ftruncate though, to test O_APPEND)
- *   - append a "chunk" of data (should be at file offset 0 after truncate)
+ *   - write a "chunk" of data (should be at file offset 0 after truncate)
  *   - on each of two threads either append or truncate-up the file
  *
  * If the truncate happened first, we should have a hole in the file.
  * If the append happened first, we should have truncated the file down.
  *
- * We pick the CHUNK_SIZE_MAX and APPEND_SIZE_MAX so that we cross a stripe.
+ * WRITE_SIZE_MAX and APPEND_SIZE_MAX are large enough to cross a stripe.
  *
  * compile: mpicc -g -Wall -o write_append_truncate write_append_truncate.c
  * run:     mpirun -np 2 -machlist <hostlist file> write_append_truncate <file>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <time.h>
 #include <string.h>
 #include <unistd.h>
+#include <getopt.h>
 #include "mpi.h"
 
-#define DEFAULT_ITER     50000
+#define DEFAULT_ITER    10000
 
-#define CHUNK_SIZE_MAX   123456
-#define CHUNK_CHAR   'C'
+#define WRITE_SIZE_MAX  1234567
+#define APPEND_SIZE_MAX 1234567
+#define TRUNC_SIZE_MAX  1234567
 
-#define APPEND_SIZE_MAX  123456
-#define APPEND_CHAR  'A'
-
-#define TRUNC_SIZE_MAX   (CHUNK_SIZE_MAX+APPEND_SIZE_MAX)
+#define STATUS_FMT "WR %c %7d/%#08x, AP %c %7d/%#08x, TR@ %7d/%#08x"
 
 #define HOSTNAME_SIZE 50
+char hostname[HOSTNAME_SIZE];
+#define FNAMES_MAX 256
 
 void usage(char *prog)
 {
-        printf("usage: %s <filename> [nloops]\n", prog);
-        printf("%s must be run with at least 2 processes\n", prog);
+        printf("usage: %s [-a append_max] [-C] [-n nloops] [-s seed]\n"
+               "\t\t[-t trunc_max] [-T] [-v] [-w write_max] <filename> ...\n", prog);
+        printf("\t-a append_max: maximum size of append, default %u bytes\n",
+               APPEND_SIZE_MAX);
+        printf("\t-C: 'classic' checks (on file 0)\n");
+        printf("\t-n nloops: count of loops to run, default %u\n",DEFAULT_ITER);
+        printf("\t-s seed: random seed to use, default {current time}\n");
+        printf("\t-t trunc_max: maximum size of truncate, default %u bytes\n",
+               TRUNC_SIZE_MAX);
+        printf("\t-T: 'classic' truncates (on file 0)\n");
+        printf("\t-w write_max: maximum size of write, default %u bytes\n",
+               WRITE_SIZE_MAX);
+        printf("\t-W: 'classic' writes (on rank 0, file 0)\n");
+        printf("\t-v: run in verbose mode (repeat for more verbosity)\n");
+        printf("\tfilename for each mountpoint of same filesystem on a node\n");
+        printf("\b%s must be run with at least 2 processes\n", prog);
 
         MPI_Finalize();
         exit(1);
 }
 
 /* Print process rank, loop count, message, and exit (i.e. a fatal error) */
-void rprintf(int rank, int loop, const char *fmt, ...)
+void rprintf(int rank, int loop, int error, const char *fmt, ...)
+__attribute__ ((format (printf, 4, 5)));
+
+void rprintf(int rank, int loop, int error, const char *fmt, ...)
 {
         va_list       ap;
 
-        printf("rank %d, loop %d: ", rank, loop);
+        printf("r=%2u", rank);
+        if (loop >= 0)
+                printf(" l=%04u", loop);
+        if (error != 0)
+                printf(" %s", hostname);
+        printf(": ");
 
         va_start(ap, fmt);
 
         vprintf(fmt, ap);
 
-        MPI_Abort(MPI_COMM_WORLD, -1);
+        if (error != 0)
+                MPI_Abort(MPI_COMM_WORLD, error);
 }
 
 int main(int argc, char *argv[])
 {
-        int n, nloops = 0, fd;
-        int rank, size, ret;
-        int chunk_size, append_size, trunc_offset;
-        char append_buf[APPEND_SIZE_MAX];
-        char chunk_buf[CHUNK_SIZE_MAX];
-        char read_buf[TRUNC_SIZE_MAX+APPEND_SIZE_MAX];
-        char trunc_buf[TRUNC_SIZE_MAX];
+        int n, nloops = DEFAULT_ITER;
+        int nfnames = 0, ifnames, fd;
+        int rank = -1, nproc, ret;
+        unsigned write_max = WRITE_SIZE_MAX;
+        unsigned append_max = APPEND_SIZE_MAX;
+        unsigned write_size = 0, append_size = 0, trunc_size = 0;
+        unsigned trunc_max = 0, trunc_offset = 0;
+        char *append_buf;
+        char *write_buf;
+        char *read_buf = NULL;
+        char *trunc_buf = NULL;
+        int seed = time(0);
         int done;
         int error;
-        char hostname[HOSTNAME_SIZE];
-        char *fname, *prog;
+        int verbose = 0;
+        int classic_check = 0, classic_trunc = 0, classic_write = 0;
+        char write_char = 'A', append_char = 'a';
+        char *fnames[FNAMES_MAX], *end;
+        char *prog = "write_append_truncate";
+        int c;
 
         error = MPI_Init(&argc, &argv);
         if (error != MPI_SUCCESS)
-                rprintf(-1, -1, "MPI_Init failed: %d\n", error);
+                printf("%s: MPI_Init failed: %d\n", prog, error);
+        else if (verbose > 2)
+                printf("%s: MPI_Init succeeded\n", prog);
 
         prog = strrchr(argv[0], '/');
         if (prog == NULL)
@@ -121,199 +157,399 @@ int main(int argc, char *argv[])
         else
                 prog++;
 
-        if (argc < 2 || argc > 3)
+        while ((c = getopt(argc, argv, "a:cCn:s:t:Tvw:W")) != -1) {
+                switch(c) {
+                case 'a':
+                        append_max = strtoul(optarg, &end, 0);
+                        if (append_max == 0 || *end) {
+                                fprintf(stderr, "%s: bad append option '%s'\n",
+                                        prog, optarg);
+                                usage(prog);
+                        }
+                        break;
+                case 'C':
+                        classic_check++;
+                        break;
+                case 'n':
+                        nloops = strtoul(optarg, &end, 0);
+                        if (nloops == 0 || *end) {
+                                fprintf(stderr, "%s: bad nloops option '%s'\n",
+                                        prog, optarg);
+                                usage(prog);
+                        }
+                        break;
+                case 's':
+                        seed = strtoul(optarg, &end, 0);
+                        if (*end) {
+                                fprintf(stderr, "%s: bad seed option '%s'\n",
+                                        prog, optarg);
+                                usage(prog);
+                        }
+                        break;
+                case 't':
+                        trunc_max = strtoul(optarg, &end, 0);
+                        if (*end) {
+                                fprintf(stderr,"%s: bad truncate option '%s'\n",
+                                        prog, optarg);
+                                usage(prog);
+                        }
+                        break;
+                case 'T':
+                        classic_trunc++;
+                        break;
+                case 'v':
+                        verbose++;
+                        break;
+                case 'w':
+                        write_max = strtoul(optarg, &end, 0);
+                        if (write_max == 0 || *end) {
+                                fprintf(stderr, "%s: bad write option '%s'\n",
+                                        prog, optarg);
+                                usage(prog);
+                        }
+                        break;
+                case 'W':
+                        classic_write++;
+                        break;
+                default:
+                        fprintf(stderr, "%s: unknown option '%c'\n", prog, c);
+                        usage(prog);
+                }
+        }
+
+        srand(seed);
+
+        if (argc == optind) {
+                fprintf(stderr, "%s: missing filename argument\n", prog);
+                usage(prog);
+        }
+
+        if (argc > optind + FNAMES_MAX) {
+                fprintf(stderr, "%s: too many extra options\n", prog);
                 usage(prog);
+        }
+
+        while (optind < argc)
+                fnames[nfnames++] = argv[optind++];
 
         error = MPI_Comm_rank(MPI_COMM_WORLD, &rank);
-        if (error != MPI_SUCCESS)
-                rprintf(-1, -1, "MPI_Comm_rank failed: %d\n", error);
+        if (verbose > 2 || error != MPI_SUCCESS)
+                rprintf(rank, -1, error != MPI_SUCCESS, "MPI_Comm_rank: %d\n",
+                        error);
 
-        error = MPI_Comm_size(MPI_COMM_WORLD, &size);
-        if (error != MPI_SUCCESS)
-                rprintf(rank, -1, "MPI_Comm_size failed: %d\n", error);
+        error = MPI_Comm_size(MPI_COMM_WORLD, &nproc);
+        if (verbose > 2 || error != MPI_SUCCESS)
+                rprintf(rank, -1, error != MPI_SUCCESS, "MPI_Comm_size: %d\n",
+                        error);
 
-        if (size < 2)
-                rprintf(rank, -1, "%s: must run with at least 2 processes\n",
+        if (nproc < 2)
+                rprintf(rank, -1, 1, "%s: must run with at least 2 processes\n",
                         prog);
 
-        memset(append_buf, APPEND_CHAR, APPEND_SIZE_MAX);
-        memset(chunk_buf, CHUNK_CHAR, CHUNK_SIZE_MAX);
-        memset(trunc_buf, 0, TRUNC_SIZE_MAX);
+        append_buf = malloc(append_max);
+        if (append_buf == NULL)
+                rprintf(rank, -1, 1,"%s: error allocating append_buf %u\n",
+                        prog, append_max);
 
-        if (gethostname(hostname, HOSTNAME_SIZE) < 0)
-                rprintf(rank, -1, "gethostname failed: %s\n", strerror(errno));
-
-        fname = argv[1];
+        write_buf = malloc(write_max);
+        if (write_buf == NULL)
+                rprintf(rank, -1, 1, "%s: error allocating write_buf %u\n",
+                        prog, write_max);
 
-        if (argc == 3)
-                nloops = strtoul(argv[2], NULL, 0);
-        if (nloops == 0)
-                nloops = DEFAULT_ITER;
+        if (gethostname(hostname, HOSTNAME_SIZE) < 0)
+                rprintf(rank, -1, 1, "%s: gethostname failed: %s\n",
+                        prog, strerror(errno));
 
         if (rank == 0) {
-                fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
-                if (fd < 0)
-                        rprintf(0, -1, "create %s failed: %s\n", fname,
-                                strerror(errno));
-                printf("using %s\n", fname);
+                int max_size = write_max + (trunc_max ?: append_max)+append_max;
+
+                fd = open(fnames[0], O_WRONLY|O_CREAT|O_TRUNC, 0666);
+                rprintf(rank,-1, fd<0, "create %s, max size: %u, seed %u: %s\n",
+                        fnames[0], max_size, seed, strerror(errno));
+                close(fd);
+
+                trunc_buf = calloc(1, trunc_max ?: append_max);
+                if (trunc_buf == NULL)
+                        rprintf(rank,-1,1,"%s: error allocating trunc_buf %u\n",
+                                prog, trunc_max ?: append_max);
+
+                /* initial write + truncate up + append */
+                read_buf = malloc(max_size);
+                if (read_buf == NULL)
+                        rprintf(rank,-1,1,"%s: error allocating read_buf %u\n",
+                                prog, max_size);
         }
+
         error = MPI_Barrier(MPI_COMM_WORLD);
-        if (error != MPI_SUCCESS)
-                rprintf(rank, -1, "prep MPI_Barrier failed: %d\n", error);
+        if (verbose > 2 || error != MPI_SUCCESS)
+                rprintf(rank, -1, error != MPI_SUCCESS,
+                        "prep MPI_Barrier: %d\n", error);
 
-        fd = open(fname, O_RDWR | O_APPEND);
-        if (fd < 0)
-                rprintf(rank, -1, "open %s failed: %s\n",fname,strerror(errno));
+        ifnames = rank % nfnames;
+        fd = open(fnames[ifnames], O_RDWR | O_APPEND);
+        if (verbose || fd < 0)
+                rprintf(rank, -1, errno, "open '%s' (%u): %s\n",
+                        fnames[ifnames], ifnames, strerror(errno));
 
         for (n = 0; n < nloops; n++) {
+                /* Initialized only to quiet stupid GCC warnings */
+                unsigned write_rank = 0, append_rank = n, trunc_rank = n + 1;
+                unsigned mpi_shared_vars[6];
+
                 /* reset the environment */
-                chunk_size = (rand()%(CHUNK_SIZE_MAX-1))+1;
-                append_size = (rand()%(APPEND_SIZE_MAX-1))+1;
-                trunc_offset = chunk_size + rand()%append_size;
+                write_char = 'A' + (n % 26);
+                append_char = 'a' + (n % 26);
+
                 if (rank == 0) {
-                        if (n % 1000 == 0)
-                                printf("loop %5d: chunk %6d/%#07x, "
-                                       "append %6d/%#07x, trunc @ %6d/%#07x\n",
-                                       n, chunk_size, chunk_size, append_size,
-                                       append_size, trunc_offset, trunc_offset);
-
-                        ret = truncate(fname, (off_t)0);
-                        if (ret < 0)
-                                rprintf(0, n, "truncate @ 0: %s\n",
+                        write_size = (rand() % (write_max - 1)) + 1;
+                        append_size = (rand() % (append_max - 1)) + 1;
+                        trunc_size = (rand() % ((trunc_max?: append_size)-1))+1;
+                        trunc_offset = write_size + trunc_size;
+
+                        if (verbose || n % 1000 == 0)
+                                rprintf(rank, n, 0, STATUS_FMT"\n",
+                                        write_char, write_size, write_size,
+                                        append_char, append_size, append_size,
+                                        trunc_offset, trunc_offset);
+
+                        write_rank = (classic_write ? 0 : rand()) % nproc;
+                        do {
+                                append_rank = (classic_write ? n : rand()) %
+                                                                nproc;
+                                /* We can't allow the append rank be the same
+                                 * as the classic_trunc trunc_rank, or we will
+                                 * spin here forever. */
+                        } while (append_rank == (n + 1) % nproc);
+                        do {
+                                trunc_rank = (classic_trunc? (n + 1) : rand()) %
+                                                                nproc;
+                        } while (trunc_rank == append_rank);
+
+                        mpi_shared_vars[0] = write_size;
+                        mpi_shared_vars[1] = append_size;
+                        mpi_shared_vars[2] = trunc_size;
+                        mpi_shared_vars[3] = write_rank;
+                        mpi_shared_vars[4] = append_rank;
+                        mpi_shared_vars[5] = trunc_rank;
+                }
+
+                error = MPI_Bcast(&mpi_shared_vars, 6,
+                                  MPI_INT, 0, MPI_COMM_WORLD);
+                if (verbose > 2 || error != MPI_SUCCESS)
+                        rprintf(rank, n, error != MPI_SUCCESS,
+                                "MPI_Bcast mpi_shared_vars"
+                                "[%u, %u, %u, %u, %u, %u]: %d\n",
+                                mpi_shared_vars[0], mpi_shared_vars[1],
+                                mpi_shared_vars[2], mpi_shared_vars[3],
+                                mpi_shared_vars[4], mpi_shared_vars[5], error);
+
+                if (rank != 0) {
+                        write_size  = mpi_shared_vars[0];
+                        append_size = mpi_shared_vars[1];
+                        trunc_size  = mpi_shared_vars[2];
+                        write_rank  = mpi_shared_vars[3];
+                        append_rank = mpi_shared_vars[4];
+                        trunc_rank  = mpi_shared_vars[5];
+
+                        trunc_offset = write_size + trunc_size;
+                }
+
+                if (rank == write_rank || rank == 0)
+                        memset(write_buf, write_char, write_max);
+
+                if (rank == write_rank) {
+                        ifnames = (classic_write ? 0 : rand()) % nfnames;
+                        ret = truncate(fnames[ifnames], 0);
+                        if (verbose > 1 || ret != 0)
+                                rprintf(rank, n, ret,
+                                        "initial truncate %s (%u) @ 0: %s\n",
+                                        fnames[ifnames], ifnames,
                                         strerror(errno));
+
                         done = 0;
                         do {
-                                ret = write(fd, chunk_buf+done,chunk_size-done);
-                                if (ret <= 0) {
-                                        rprintf(0, n, "chunk @ %d: %s\n",
-                                                done, strerror(errno));
-                                        break;
+                                ret = write(fd, write_buf+done,write_size-done);
+                                if (verbose > 1 || ret <= 0) {
+                                        rprintf(rank, n, ret <= 0,
+                                                "write %d/%d @ %d: %s\n",
+                                                ret + done, write_size, done,
+                                                strerror(errno));
+                                        if (ret <= 0)
+                                                break;
                                 }
                                 done += ret;
-                        } while (done != chunk_size);
+                        } while (done != write_size);
                 }
 
+                if (rank == append_rank || rank == 0)
+                        memset(append_buf, append_char, append_size);
+
                 error = MPI_Barrier(MPI_COMM_WORLD);
-                if (error != MPI_SUCCESS)
-                        rprintf(rank, n, "start MPI_Barrier: %d\n",error);
+                if (verbose > 2 || error != MPI_SUCCESS)
+                        rprintf(rank, n, error != MPI_SUCCESS,
+                                "start MPI_Barrier: %d\n", error);
 
                 /* Do the race */
-                if (rank == n % size) {
-                        //
+                if (rank == append_rank) {
                         done = 0;
                         do {
                                 ret = write(fd, append_buf + done,
                                             append_size - done);
                                 if (ret < 0) {
-                                        rprintf(rank, n,
-                                                "loop %d: append @ %u: %s\n",
-                                                done, strerror(errno));
+                                        rprintf(rank, n, ret < 0,
+                                                "append %u/%u: %s\n",
+                                                ret + done, append_size,
+                                                strerror(errno));
                                         break;
+                                } else if (verbose > 1 || ret != append_size) {
+                                        rprintf(rank, n, ret != append_size,
+                                                "append %u/%u\n",
+                                                ret + done, append_size);
                                 }
                                 done += ret;
                         } while (done != append_size);
-                } else if (rank == (n + 1) % size) {
-                        ret = truncate(fname, (off_t)trunc_offset);
-                        if (ret != 0)
-                                rprintf(rank, n, "truncate @ %u: %s\n",
-                                        trunc_offset, strerror(errno) );
+                } else if (rank == trunc_rank) {
+                        /* XXX: truncating the same file descriptor as the
+                         *      append on a single node causes this test
+                         *      to fail currently (2009-02-01). */
+                        ifnames = (classic_trunc ? rank : rand()) % nfnames;
+                        ret = truncate(fnames[ifnames], trunc_offset);
+                        if (verbose > 1 || ret != 0)
+                                rprintf(rank, n, ret,
+                                        "truncate %s (%u) @ %u: %s\n",
+                                        fnames[ifnames], ifnames,
+                                        trunc_offset, strerror(errno));
                 }
 
                 error = MPI_Barrier(MPI_COMM_WORLD);
-                if (error != MPI_SUCCESS)
-                        rprintf(rank, n, "end MPI_Barrier: %d\n", error);
+                if (verbose > 2 || error != MPI_SUCCESS)
+                        rprintf(rank, n, error != MPI_SUCCESS,
+                                "end MPI_Barrier: %d\n", error);
 
                 error = 0;
 
                 /* Check the result */
                 if (rank == 0) {
-                        struct stat st;
-                        if (stat(fname, &st) < 0)
-                                rprintf(0, n, "loop %d: stat %s: %s\n",
-                                        fname, strerror(errno));
-
-                        if (lseek(fd, (off_t)0, SEEK_SET) != 0)
-                                rprintf(0, n, "lseek fname 0: %s\n", fname,
+                        char *tmp_buf;
+                        struct stat st = { 0 };
+
+                        ifnames = classic_check ? 0 : (rand() % nfnames);
+                        ret = stat(fnames[ifnames], &st);
+                        if (verbose > 1 || ret != 0)
+                                rprintf(rank, n, ret,
+                                        "stat %s (%u) size %llu: %s\n",
+                                        fnames[ifnames], ifnames,
+                                        (long long)st.st_size, strerror(errno));
+
+                        ret = lseek(fd, 0, SEEK_SET);
+                        if (ret != 0)
+                                rprintf(rank, n, ret, "lseek 0: %s\n",
                                         strerror(errno));
 
                         done = 0;
                         do {
                                 ret = read(fd, read_buf+done, st.st_size-done);
-                                if (ret < 0) {
-                                        rprintf(0, n, "read @ %u: %s\n",
-                                               done, strerror(errno));
+                                if (verbose > 1 || ret <= 0) {
+                                        rprintf(rank, n, ret <= 0,
+                                                "read %d/%llu @ %u: %s\n",
+                                                ret, (long long)st.st_size-done,
+                                                done, ret != 0 ?
+                                                strerror(errno) : "short read");
                                 }
                                 done += ret;
                         } while (done != st.st_size);
 
-                        if (memcmp(read_buf, chunk_buf, chunk_size)) {
-                                printf("loop %d: base chunk bad"
-                                       " [0-%d]/[0-%#x] != %c\n", n,
-                                       chunk_size - 1, chunk_size - 1,
-                                       CHUNK_CHAR);
+                        if (memcmp(read_buf, write_buf, write_size)) {
+                                rprintf(rank, n, 0, "WRITE bad "
+                                        "[0-%d]/[0-%#x] != %c\n",
+                                        write_size - 1, write_size - 1,
+                                        write_char);
                                 error = 1;
                         }
 
+                        tmp_buf = read_buf + write_size;
+
                         if (st.st_size == trunc_offset) {
                                 /* Check case 1: first append then truncate */
-                                error = memcmp(read_buf+chunk_size, append_buf,
-                                               trunc_offset - chunk_size);
-                                if (error) {
-                                        printf("loop %d: trunc-after-append bad"
-                                               " [%d-%d]/[%#x-%#x] != %c\n",
-                                               n, chunk_size, trunc_offset - 1,
-                                               chunk_size, trunc_offset - 1,
-                                               APPEND_CHAR);
+                                int tmp_size, tmp_offset;
+
+                                tmp_size = trunc_size < append_size ?
+                                                trunc_size : append_size;
+                                tmp_offset = write_size + tmp_size;
+
+                                if (memcmp(tmp_buf, append_buf, tmp_size)) {
+                                        rprintf(rank, n, 0,"trunc-after-APPEND "
+                                                "bad [%d-%d]/[%#x-%#x] != %c\n",
+                                                write_size, tmp_offset - 1,
+                                                write_size, tmp_offset - 1,
+                                                append_char);
+                                        error = 1;
+                                } else if (trunc_size > append_size &&
+                                           memcmp(tmp_buf+append_size,trunc_buf,
+                                                  trunc_size - append_size)) {
+                                        rprintf(rank, n, 0,"TRUNC-after-append "
+                                                "bad [%d-%d]/[%#x-%#x] != 0\n",
+                                                tmp_offset, trunc_offset - 1,
+                                                tmp_offset, trunc_offset - 1);
+                                        error = 1;
                                 }
                         } else {
+                                int expected_size = trunc_offset + append_size;
                                 /* Check case 2: first truncate then append */
-                                if (memcmp(read_buf+chunk_size, trunc_buf,
-                                           trunc_offset-chunk_size)) {
-                                        printf("loop %d: append-after-TRUNC bad"
-                                               " [%d-%d]/[%#x-%#x] != 0\n",
-                                               n, chunk_size, trunc_offset - 1,
-                                               chunk_size, trunc_offset - 1);
+                                if (st.st_size != expected_size) {
+                                        rprintf(rank, n, 0,"APPEND-after-trunc "
+                                                "bad file size %llu != %u\n",
+                                                (long long)st.st_size,
+                                                expected_size);
                                         error = 1;
-                                } else if (memcmp(read_buf+trunc_offset,
+                                }
+
+                                if (memcmp(tmp_buf, trunc_buf, trunc_size)) {
+                                        rprintf(rank, n, 0,"append-after-TRUNC "
+                                                "bad [%d-%d]/[%#x-%#x] != 0\n",
+                                                write_size, trunc_offset - 1,
+                                                write_size, trunc_offset - 1);
+                                        error = 1;
+                                } else if (memcmp(read_buf + trunc_offset,
                                                   append_buf, append_size)) {
-                                        printf("loop %d: APPEND-after-trunc bad"
-                                               " [%d-%d]/[%#x-%#x] != %c\n",
-                                               n, trunc_offset, append_size - 1,
-                                               trunc_offset, append_size - 1,
-                                               APPEND_CHAR);
+                                        rprintf(rank, n, 0,"APPEND-after-trunc "
+                                                "bad [%d-%d]/[%#x-%#x] != %c\n",
+                                                trunc_offset, expected_size - 1,
+                                                trunc_offset, expected_size - 1,
+                                                append_char);
                                         error = 1;
                                 }
                         }
-                }
-                ret = MPI_Bcast(&error, 1, MPI_INT, 0, MPI_COMM_WORLD);
-                if (ret != MPI_SUCCESS)
-                        rprintf(rank, n, "MPI_Bcast: %d\n");
 
-                if (error == 1) {
-                        if (rank == 0) {
+                        if (error == 1) {
                                 char command[4096];
 
-                                printf("loop %5d: chunk %6d/%#07x, "
-                                       "append %6d/%#07x, trunc @ %6d/%#07x\n",
-                                       n, chunk_size, chunk_size, append_size,
-                                       append_size, trunc_offset, trunc_offset);
+                                rprintf(rank, n, 0, STATUS_FMT"\n",
+                                        write_char, write_size, write_size,
+                                        append_char, append_size, append_size,
+                                        trunc_offset, trunc_offset);
 
-                                sprintf(command, "od -Ax -a %s", fname);
+                                sprintf(command, "od -Ax -a %s", fnames[0]);
                                 system(command);
+                                MPI_Abort(MPI_COMM_WORLD, 1);
                         }
-                        rprintf(rank, n, "on machine %s with pid %d\n",
-                                hostname, (int)getpid());
                 }
         }
 
-        printf("rank %d, loop %d: finished\n", rank, n);
+        if (rank == 0 || verbose)
+                printf("r=%2u n=%4u: "STATUS_FMT"\nPASS\n", rank, n - 1,
+                       write_char, write_size, write_size,
+                       append_char, append_size, append_size,
+                       trunc_offset, trunc_offset);
+
         close(fd);
 
         if (rank == 0) {
-                error = unlink(fname);
-                if (error < 0)
-                        rprintf(0, n, "unlink %s failed: %s\n",
-                                fname, strerror(errno));
+                ifnames = rand() % nfnames;
+                ret = unlink(fnames[ifnames]);
+                if (ret != 0)
+                        printf("%s: unlink %s failed: %s\n",
+                               prog, fnames[ifnames], strerror(errno));
         }
 
         MPI_Finalize();