Sometimes, it's nice to see what a difference it makes in timing
based on whether Forced Unit Access or a full flush is used when
waiting for particular data to land in persistent storage. Add a
new filter that makes it trivial to switch between different FUA
policies (none: the client must use flush, emulate: the filter
uses flush even if the plugin supports efficient FUA, native:
the filter advertises native even if the plugin relies on flush,
force: the filter forces FUA even when the client didn't request
it). The log filter makes it easy to test the difference between
the various modes, and also demonstrates that the previous
patches that optimized various filters based on whether FUA is
emulated or native actually work.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
TODO | 11 +-
docs/nbdkit-filter.pod | 1 +
docs/nbdkit.pod | 1 +
filters/fua/nbdkit-fua-filter.pod | 119 ++++++++++++++++++
configure.ac | 1 +
filters/fua/fua.c | 251 ++++++++++++++++++++++++++++++++++++++
filters/Makefile.am | 1 +
filters/fua/Makefile.am | 62 ++++++++++
tests/Makefile.am | 4 +
tests/test-fua.sh | 153 +++++++++++++++++++++++
10 files changed, 594 insertions(+), 10 deletions(-)
create mode 100644 filters/fua/nbdkit-fua-filter.pod
create mode 100644 filters/fua/fua.c
create mode 100644 filters/fua/Makefile.am
create mode 100755 tests/test-fua.sh
diff --git a/TODO b/TODO
index e7a8405..b6fb0b1 100644
--- a/TODO
+++ b/TODO
@@ -75,16 +75,7 @@ Suggestions for filters
-----------------------
* injecting artificial errors or otherwise masking plugin features
- for testing clients (see 'nozero' filter for example)
-
-* fua filter: setting mode=none stops advertisement, mode=emulate uses
- flush emulation (or fails if !can_flush), mode=native passes on
- through (or fails if can_fua does not report native support). When
- breaking up a large request into smaller pieces, then native support
- requires passing fua through to all sub-requests; but emulation by
- flushing only needs one flush after the last sub-request, to avoid
- unneeded intermediate flushing; hence, where this filter is placed
- in the stack may have a performance impact.
+ for testing clients (see 'nozero' and 'fua' filters for example)
nbdkit-cache-filter needs considerable work:
diff --git a/docs/nbdkit-filter.pod b/docs/nbdkit-filter.pod
index d53daf4..851bc37 100644
--- a/docs/nbdkit-filter.pod
+++ b/docs/nbdkit-filter.pod
@@ -616,6 +616,7 @@ L<nbdkit-blocksize-filter(1)>,
L<nbdkit-cache-filter(1)>,
L<nbdkit-cow-filter(1)>,
L<nbdkit-delay-filter(1)>,
+L<nbdkit-fua-filter(1)>,
L<nbdkit-log-filter(1)>,
L<nbdkit-nozero-filter(1)>,
L<nbdkit-offset-filter(1)>,
diff --git a/docs/nbdkit.pod b/docs/nbdkit.pod
index 9247ff5..dacf11c 100644
--- a/docs/nbdkit.pod
+++ b/docs/nbdkit.pod
@@ -921,6 +921,7 @@ L<nbdkit-blocksize-filter(1)>,
L<nbdkit-cache-filter(1)>,
L<nbdkit-cow-filter(1)>,
L<nbdkit-delay-filter(1)>,
+L<nbdkit-fua-filter(1)>,
L<nbdkit-log-filter(1)>,
L<nbdkit-nozero-filter(1)>,
L<nbdkit-offset-filter(1)>,
diff --git a/filters/fua/nbdkit-fua-filter.pod b/filters/fua/nbdkit-fua-filter.pod
new file mode 100644
index 0000000..cec54d5
--- /dev/null
+++ b/filters/fua/nbdkit-fua-filter.pod
@@ -0,0 +1,119 @@
+=encoding utf8
+
+=head1 NAME
+
+nbdkit-fua-filter - nbdkit FUA filter
+
+=head1 SYNOPSIS
+
+ nbdkit --filter=fua plugin [fuamode=MODE] [plugin-args...]
+
+=head1 DESCRIPTION
+
+C<nbdkit-fua-filter> is a filter that intentionally modifies handling
+of the 'Forced Unit Access' (FUA) flag across the NBD protocol. It is
+mainly useful for testing client or server fallbacks, and for
+evaluating timing differences between proper use of FUA compared to a
+full flush.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<fuamode=MODE>
+
+Optional, controls which mode the filter will use. Mode B<none>
+(default) means that FUA support is not advertised to the client.
+Mode B<emulate> causes the filter to emulate FUA support using the
+plugin's C<flush> callback, regardless of whether the plugin itself
+supports more efficient FUA (or refuses to load if the plugin's
+C<can_flush> callback returns false). Mode B<native> causes the
+filter to advertise native FUA support to earlier filters, useful for
+comparing optimizations of FUA handling when splitting large requests
+into sub-requests (or refuses to load if the plugin's C<can_fua>
+callback returns C<NBDKIT_FUA_NONE>). Mode B<force> causes the filter
+to request FUA on all write transactions, even when the client did not
+request it, and in turn treats client flush requests as a no-op (or
+refuses to load if the plugin's C<can_fua> callback returns
+C<NBDKIT_FUA_NONE>).
+
+=back
+
+=head1 EXAMPLES
+
+Serve the file F<disk.img>, but force the client to submit explicit
+flush requests instead of using C<NBD_CMD_FLAG_FUA>:
+
+ nbdkit --filter=fua file file=disk.img
+
+Observe that the blocksize filter optimizes its handling of the FUA
+flag based on whether it knows nbdkit will be emulating FUA with a
+flush, by comparing the log filter output on top of different fua
+filter modes:
+
+ nbdkit --filter=blocksize --filter=log --filter=fua file file=disk.img \
+ maxlen=4k logfile=fua_emulated fuamode=emulate
+ nbdkit --filter=blocksize --filter=log --filter=fua file file=disk.img \
+ maxlen=4k logfile=fua_native fuamode=native
+
+Serve the file F<disk.img> in write-through mode, where all writes
+from the client are immediately flushed to disk as if the client had
+always requested FUA:
+
+ nbdkit --filter=fua file fuamode=force file=disk.img
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-file-plugin(1)>,
+L<nbdkit-filter(3)>,
+L<nbdkit-blocksize-filter(3)>,
+L<nbdkit-log-filter(3)>.
+
+=head1 AUTHORS
+
+Eric Blake
+
+=head1 COPYRIGHT
+
+Copyright (C) 2018 Red Hat Inc.
+
+=head1 LICENSE
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+=over 4
+
+=item *
+
+Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+=item *
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+=item *
+
+Neither the name of Red Hat nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+=back
+
+THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/configure.ac b/configure.ac
index 29ef109..c914e00 100644
--- a/configure.ac
+++ b/configure.ac
@@ -517,6 +517,7 @@ AC_CONFIG_FILES([Makefile
filters/cache/Makefile
filters/cow/Makefile
filters/delay/Makefile
+ filters/fua/Makefile
filters/log/Makefile
filters/nozero/Makefile
filters/offset/Makefile
diff --git a/filters/fua/fua.c b/filters/fua/fua.c
new file mode 100644
index 0000000..f3bcbc9
--- /dev/null
+++ b/filters/fua/fua.c
@@ -0,0 +1,251 @@
+/* nbdkit
+ * Copyright (C) 2018 Red Hat Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#include <nbdkit-filter.h>
+
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
+
+enum FuaMode {
+ NONE,
+ EMULATE,
+ NATIVE,
+ FORCE,
+} fuamode;
+
+static int
+fua_config (nbdkit_next_config *next, void *nxdata,
+ const char *key, const char *value)
+{
+ if (strcmp (key, "fuamode") == 0) {
+ if (strcmp (value, "none") == 0)
+ fuamode = NONE;
+ if (strcmp (value, "emulate") == 0)
+ fuamode = EMULATE;
+ else if (strcmp (value, "native") == 0)
+ fuamode = NATIVE;
+ else if (strcmp (value, "force") == 0)
+ fuamode = FORCE;
+ else {
+ nbdkit_error ("unknown fuamode '%s'", value);
+ return -1;
+ }
+ return 0;
+ }
+ return next (nxdata, key, value);
+}
+
+#define fua_config_help \
+ "fuamode=<MODE> One of 'none' (default), 'emulate',
'native', 'force'.\n" \
+
+/* Check that desired mode is supported by plugin. */
+static int
+fua_prepare (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+ int r;
+
+ switch (fuamode) {
+ case NONE:
+ break;
+ case EMULATE:
+ r = next_ops->can_flush (nxdata);
+ if (r == -1)
+ return -1;
+ if (r == 0) {
+ nbdkit_error ("fuamode 'emulate' requires plugin flush
support");
+ return -1;
+ }
+ break;
+ case NATIVE:
+ case FORCE:
+ r = next_ops->can_fua (nxdata);
+ if (r == -1)
+ return -1;
+ if (r == NBDKIT_FUA_NONE) {
+ nbdkit_error ("fuamode '%s' requires plugin fua support",
+ fuamode == EMULATE ? "emulate" : "force");
+ return -1;
+ }
+ break;
+ }
+ return 0;
+}
+
+/* Advertise proper flush support. */
+static int
+fua_can_flush (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+ if (fuamode == FORCE)
+ return 1; /* Advertise our no-op flush, even if plugin lacks it */
+ return next_ops->can_flush (nxdata);
+}
+
+/* Advertise desired fua mode. */
+static int
+fua_can_fua (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+ switch (fuamode) {
+ case NONE:
+ return NBDKIT_FUA_NONE;
+ case EMULATE:
+ return NBDKIT_FUA_EMULATE;
+ case NATIVE:
+ case FORCE:
+ return NBDKIT_FUA_NATIVE;
+ }
+ abort ();
+}
+
+static int
+fua_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, const void *buf, uint32_t count, uint64_t offs,
+ uint32_t flags, int *err)
+{
+ int r;
+ bool need_flush = false;
+
+ switch (fuamode) {
+ case NONE:
+ assert (!(flags & NBDKIT_FLAG_FUA));
+ break;
+ case EMULATE:
+ if (flags & NBDKIT_FLAG_FUA) {
+ need_flush = true;
+ flags &= ~NBDKIT_FLAG_FUA;
+ }
+ break;
+ case NATIVE:
+ break;
+ case FORCE:
+ flags |= NBDKIT_FLAG_FUA;
+ break;
+ }
+ r = next_ops->pwrite (nxdata, buf, count, offs, flags, err);
+ if (r != -1 && need_flush)
+ r = next_ops->flush (nxdata, 0, err);
+ return r;
+}
+
+static int
+fua_flush (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, uint32_t flags, int *err)
+{
+ if (fuamode == FORCE)
+ return 0; /* Nothing to flush, since all writes already used FUA */
+ return next_ops->flush (nxdata, flags, err);
+}
+
+static int
+fua_trim (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, uint32_t count, uint64_t offs, uint32_t flags,
+ int *err)
+{
+ int r;
+ bool need_flush = false;
+
+ switch (fuamode) {
+ case NONE:
+ assert (!(flags & NBDKIT_FLAG_FUA));
+ break;
+ case EMULATE:
+ if (flags & NBDKIT_FLAG_FUA) {
+ need_flush = true;
+ flags &= ~NBDKIT_FLAG_FUA;
+ }
+ break;
+ case NATIVE:
+ break;
+ case FORCE:
+ flags |= NBDKIT_FLAG_FUA;
+ break;
+ }
+ r = next_ops->trim (nxdata, count, offs, flags, err);
+ if (r != -1 && need_flush)
+ r = next_ops->flush (nxdata, 0, err);
+ return r;
+}
+
+static int
+fua_zero (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, uint32_t count, uint64_t offs, uint32_t flags,
+ int *err)
+{
+ int r;
+ bool need_flush = false;
+
+ switch (fuamode) {
+ case NONE:
+ assert (!(flags & NBDKIT_FLAG_FUA));
+ break;
+ case EMULATE:
+ if (flags & NBDKIT_FLAG_FUA) {
+ need_flush = true;
+ flags &= ~NBDKIT_FLAG_FUA;
+ }
+ break;
+ case NATIVE:
+ break;
+ case FORCE:
+ flags |= NBDKIT_FLAG_FUA;
+ break;
+ }
+ r = next_ops->zero (nxdata, count, offs, flags, err);
+ if (r != -1 && need_flush)
+ r = next_ops->flush (nxdata, 0, err);
+ return r;
+}
+
+static struct nbdkit_filter filter = {
+ .name = "fua",
+ .longname = "nbdkit fua filter",
+ .version = PACKAGE_VERSION,
+ .config = fua_config,
+ .config_help = fua_config_help,
+ .prepare = fua_prepare,
+ .can_flush = fua_can_flush,
+ .can_fua = fua_can_fua,
+ .pwrite = fua_pwrite,
+ .flush = fua_flush,
+ .trim = fua_trim,
+ .zero = fua_zero,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
diff --git a/filters/Makefile.am b/filters/Makefile.am
index 170ee09..fd1cf13 100644
--- a/filters/Makefile.am
+++ b/filters/Makefile.am
@@ -35,6 +35,7 @@ SUBDIRS = \
cache \
cow \
delay \
+ fua \
log \
nozero \
offset \
diff --git a/filters/fua/Makefile.am b/filters/fua/Makefile.am
new file mode 100644
index 0000000..03df0c3
--- /dev/null
+++ b/filters/fua/Makefile.am
@@ -0,0 +1,62 @@
+# nbdkit
+# Copyright (C) 2018 Red Hat Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+EXTRA_DIST = nbdkit-fua-filter.pod
+
+CLEANFILES = *~
+
+filterdir = $(libdir)/nbdkit/filters
+
+filter_LTLIBRARIES = nbdkit-fua-filter.la
+
+nbdkit_fua_filter_la_SOURCES = \
+ fua.c \
+ $(top_srcdir)/include/nbdkit-filter.h
+
+nbdkit_fua_filter_la_CPPFLAGS = \
+ -I$(top_srcdir)/include
+nbdkit_fua_filter_la_CFLAGS = \
+ $(WARNINGS_CFLAGS)
+nbdkit_fua_filter_la_LDFLAGS = \
+ -module -avoid-version -shared
+
+if HAVE_POD2MAN
+
+man_MANS = nbdkit-fua-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-fua-filter.1: nbdkit-fua-filter.pod
+ $(POD2MAN) $(POD2MAN_ARGS) --section=1 --name=`basename $@ .1` $< $@.t && \
+ if grep 'POD ERROR' $@.t; then rm $@.t; exit 1; fi && \
+ mv $@.t $@
+
+endif
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0013fda..cf61c40 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -49,6 +49,7 @@ EXTRA_DIST = \
test-dump-plugin.sh \
test-dump-plugin-example4.sh \
test-foreground.sh \
+ test-fua.sh \
test-help.sh \
test-help-plugin.sh \
test-ip.sh \
@@ -433,6 +434,9 @@ test_delay_SOURCES = test-delay.c test.h
test_delay_CFLAGS = $(WARNINGS_CFLAGS) $(LIBGUESTFS_CFLAGS)
test_delay_LDADD = libtest.la $(LIBGUESTFS_LIBS)
+# fua filter test.
+TESTS += test-fua.sh
+
# log filter test.
TESTS += test-log.sh
diff --git a/tests/test-fua.sh b/tests/test-fua.sh
new file mode 100755
index 0000000..c102f49
--- /dev/null
+++ b/tests/test-fua.sh
@@ -0,0 +1,153 @@
+#!/bin/bash -
+# nbdkit
+# Copyright (C) 2018 Red Hat Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+set -e
+
+files="fua.img
+ fua1.log fua1.sock fua1.pid
+ fua2.log fua2.sock fua2.pid
+ fua3.log fua3.sock fua3.pid
+ fua4.log fua4.sock fua4.pid"
+rm -f $files
+
+: ${QEMU_IO=qemu-io}
+
+# Prep images, and check that qemu-io understands the actions we plan on
+# doing. Too bad qemu-io 2.11 doesn't support trim with FUA.
+truncate --size 1M fua.img
+if ! $QEMU_IO -f raw -t none -c flush -c 'w -f -z 0 64k' fua.img; then
+ echo "$0: missing or broken qemu-io"
+ rm fua.img
+ exit 77
+fi
+
+pid1= pid2= pid3= pid4=
+
+# Kill any nbdkit processes on exit.
+cleanup ()
+{
+ status=$?
+
+ test "$pid1" && kill $pid1
+ test "$pid2" && kill $pid2
+ test "$pid3" && kill $pid3
+ test "$pid4" && kill $pid4
+ # For easier debugging, dump the final log files before removing them.
+ echo "Log 1 file contents:"
+ cat fua1.log || :
+ echo "Log 2 file contents:"
+ cat fua2.log || :
+ echo "Log 3 file contents:"
+ cat fua3.log || :
+ echo "Log 4 file contents:"
+ cat fua4.log || :
+ rm -f $files
+
+ exit $status
+}
+trap cleanup INT QUIT TERM EXIT ERR
+
+# Run four parallel nbdkit; to compare the logs and see what changes.
+# 1: fuamode=none (default): client should send flush instead
+# 2: fuamode=emulate: log shows that blocksize optimizes fua to flush
+# 3: fuamode=native: log shows that blocksize preserves fua
+# 4: fuamode=force: log shows that fua is always enabled
+nbdkit -P fua1.pid -U fua1.sock --filter=log --filter=fua \
+ file logfile=fua1.log file=fua.img
+nbdkit -P fua2.pid -U fua2.sock --filter=blocksize --filter=log --filter=fua \
+ file logfile=fua2.log file=fua.img fuamode=emulate maxdata=4k maxlen=4k
+nbdkit -P fua3.pid -U fua3.sock --filter=blocksize --filter=log --filter=fua \
+ file logfile=fua3.log file=fua.img fuamode=native maxdata=4k maxlen=4k
+nbdkit -P fua4.pid -U fua4.sock --filter=fua --filter=log \
+ file logfile=fua4.log file=fua.img fuamode=force
+
+# We may have to wait a short time for the pid files to appear.
+for i in `seq 1 10`; do
+ if test -f fua1.pid && test -f fua2.pid && test -f fua3.pid
&&
+ test -f fua4.pid; then
+ break
+ fi
+ sleep 1
+done
+
+pid1="$(cat fua1.pid)" || :
+pid2="$(cat fua2.pid)" || :
+pid3="$(cat fua3.pid)" || :
+pid4="$(cat fua4.pid)" || :
+
+if ! test -f fua1.pid || ! test -f fua2.pid || ! test -f fua3.pid ||
+ ! test -f fua4.pid; then
+ echo "$0: PID files were not created"
+ exit 1
+fi
+
+# Perform a flush, write, and zero, with and without FUA
+for f in '' -f; do
+ for i in {1..4}; do
+ $QEMU_IO -f raw -t none -c flush -c "w $f 0 64k" -c "w -z $f 64k
64k" \
+ "nbd+unix://?socket=fua$i.sock"
+ done
+done
+
+# Test 1: no fua sent over wire, qemu-io sent more flushes in place of fua
+if grep 'fua=1' fua1.log; then
+ echo "filter should have prevented fua"
+ exit 1
+fi
+test $(grep -c 'connection=1 Flush' fua1.log) -lt \
+ $(grep -c 'connection=2 Flush' fua1.log)
+
+# Test 2: either last part of split has fua, or a flush is added, but
+# all earlier parts of the transaction do not have fua
+flush1=$(grep -c 'connection=1 Flush' fua2.log || :)
+flush2=$(grep -c 'connection=2 Flush' fua2.log || :)
+fua=$(grep -c 'fua=1' fua2.log || :)
+test $(( $flush2 - $flush1 + $fua )) = 2
+
+# Test 3: every part of split has fua, and no flushes are added
+flush1=$(grep -c 'connection=1 Flush' fua3.log || :)
+flush2=$(grep -c 'connection=2 Flush' fua3.log || :)
+test $flush1 = $flush2
+test $(grep -c 'fua=1' fua3.log) = 32
+
+# Test 4: flush is no-op, and every transaction has fua
+if grep 'fua=0' fua4.log; then
+ echo "filter should have forced fua"
+ exit 1
+fi
+if grep 'Flush' fua4.log; then
+ echo "filter should have elided flush"
+ exit 1
+fi
+
+# The cleanup() function is called implicitly on exit.
--
2.14.3