Sometimes, it's nice to see what a difference it makes in timing
or in destination file size when sparse handling is enabled or
disabled. Add a new filter that makes it trivial to disable
write zero support, both at the client side (the feature is not
advertised, so the client must do fallbacks) and at the server
side (the feature is always emulated, rather than using any
fast paths built into the plugin).
The log filter makes it easy to test the difference between the
two modes.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
v3: rebase to improved API, get unit test working
---
TODO | 2 +-
docs/nbdkit-filter.pod | 1 +
docs/nbdkit.pod | 1 +
filters/nozero/nbdkit-nozero-filter.pod | 99 ++++++++++++++++++++++
configure.ac | 1 +
filters/nozero/nozero.c | 106 +++++++++++++++++++++++
filters/Makefile.am | 1 +
filters/nozero/Makefile.am | 62 ++++++++++++++
tests/Makefile.am | 4 +
tests/test-nozero.sh | 145 ++++++++++++++++++++++++++++++++
10 files changed, 421 insertions(+), 1 deletion(-)
create mode 100644 filters/nozero/nbdkit-nozero-filter.pod
create mode 100644 filters/nozero/nozero.c
create mode 100644 filters/nozero/Makefile.am
create mode 100755 tests/test-nozero.sh
diff --git a/TODO b/TODO
index d02671d..e7a8405 100644
--- a/TODO
+++ b/TODO
@@ -75,7 +75,7 @@ Suggestions for filters
-----------------------
* injecting artificial errors or otherwise masking plugin features
- (such as hiding zero support) for testing clients
+ 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
diff --git a/docs/nbdkit-filter.pod b/docs/nbdkit-filter.pod
index 3af97b0..6766216 100644
--- a/docs/nbdkit-filter.pod
+++ b/docs/nbdkit-filter.pod
@@ -583,6 +583,7 @@ L<nbdkit-cache-filter(1)>,
L<nbdkit-cow-filter(1)>,
L<nbdkit-delay-filter(1)>,
L<nbdkit-log-filter(1)>,
+L<nbdkit-nozero-filter(1)>,
L<nbdkit-offset-filter(1)>,
L<nbdkit-partition-filter(1)>.
diff --git a/docs/nbdkit.pod b/docs/nbdkit.pod
index 94ddb62..9247ff5 100644
--- a/docs/nbdkit.pod
+++ b/docs/nbdkit.pod
@@ -922,6 +922,7 @@ L<nbdkit-cache-filter(1)>,
L<nbdkit-cow-filter(1)>,
L<nbdkit-delay-filter(1)>,
L<nbdkit-log-filter(1)>,
+L<nbdkit-nozero-filter(1)>,
L<nbdkit-offset-filter(1)>,
L<nbdkit-partition-filter(1)>.
diff --git a/filters/nozero/nbdkit-nozero-filter.pod
b/filters/nozero/nbdkit-nozero-filter.pod
new file mode 100644
index 0000000..4065302
--- /dev/null
+++ b/filters/nozero/nbdkit-nozero-filter.pod
@@ -0,0 +1,99 @@
+=encoding utf8
+
+=head1 NAME
+
+nbdkit-nozero-filter - nbdkit nozero filter
+
+=head1 SYNOPSIS
+
+ nbdkit --filter=nozero plugin [zeromode=MODE] [plugin-args...]
+
+=head1 DESCRIPTION
+
+C<nbdkit-nozero-filter> is a filter that intentionally disables
+efficient handling of sparse file holes (ranges of all-zero bytes)
+across the NBD protocol. It is mainly useful for evaluating timing
+differences between naive vs. sparse-aware connections, and for
+testing client or server fallbacks.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<zeromode=MODE>
+
+Optional, controls which mode the filter will use. Mode B<none>
+(default) means that zero support is not advertised to the client;
+mode B<emulate> means that zero support is emulated by the filter
+using the plugin's C<pwrite> callback, regardless of whether the
+plugin itself implemented the C<zero> callback with a more efficient
+way to write zeros.
+
+=back
+
+=head1 EXAMPLES
+
+Serve the file F<disk.img>, but force the client to write zeroes
+explicitly rather than with C<NBD_CMD_WRITE_ZEROES>:
+
+ nbdkit --filter=nozero file file=disk.img
+
+Serve the file F<disk.img>, allowing the client to take advantage of
+less network traffic via C<NBD_CMD_WRITE_ZEROES>, but still forcing
+the data to be written explicitly rather than punching any holes:
+
+ nbdkit --filter=nozero file zeromode=emulate file=disk.img
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-file-plugin(1)>,
+L<nbdkit-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 c3a121d..29ef109 100644
--- a/configure.ac
+++ b/configure.ac
@@ -518,6 +518,7 @@ AC_CONFIG_FILES([Makefile
filters/cow/Makefile
filters/delay/Makefile
filters/log/Makefile
+ filters/nozero/Makefile
filters/offset/Makefile
filters/partition/Makefile
src/Makefile
diff --git a/filters/nozero/nozero.c b/filters/nozero/nozero.c
new file mode 100644
index 0000000..dac47ad
--- /dev/null
+++ b/filters/nozero/nozero.c
@@ -0,0 +1,106 @@
+/* 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
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX_WRITE (64 * 1024 * 1024)
+
+static char buffer[MAX_WRITE];
+static bool emulate;
+
+static int
+nozero_config (nbdkit_next_config *next, void *nxdata,
+ const char *key, const char *value)
+{
+ if (strcmp (key, "zeromode") == 0) {
+ if (strcmp (value, "emulate") == 0)
+ emulate = true;
+ else if (strcmp (value, "none") != 0) {
+ nbdkit_error ("unknown zeromode '%s'", value);
+ return -1;
+ }
+ return 0;
+ }
+ return next (nxdata, key, value);
+}
+
+#define nozero_config_help \
+ "zeromode=<MODE> Either 'none' (default) or
'emulate'.\n" \
+
+/* Advertise desired WRITE_ZEROES mode. */
+static int
+nozero_can_zero (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+ return emulate;
+}
+
+static int
+nozero_zero (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, uint32_t count, uint64_t offs, uint32_t flags,
+ int *err)
+{
+ assert (emulate);
+ while (count) {
+ uint32_t size = MIN (count, MAX_WRITE);
+ if (next_ops->pwrite (nxdata, buffer, size, offs,
+ flags & ~NBDKIT_FLAG_MAY_TRIM, err) == -1)
+ return -1;
+ offs += size;
+ count -= size;
+ }
+ return 0;
+}
+
+static struct nbdkit_filter filter = {
+ .name = "nozero",
+ .longname = "nbdkit nozero filter",
+ .version = PACKAGE_VERSION,
+ .config = nozero_config,
+ .config_help = nozero_config_help,
+ .can_zero = nozero_can_zero,
+ .zero = nozero_zero,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
diff --git a/filters/Makefile.am b/filters/Makefile.am
index de98f43..170ee09 100644
--- a/filters/Makefile.am
+++ b/filters/Makefile.am
@@ -36,5 +36,6 @@ SUBDIRS = \
cow \
delay \
log \
+ nozero \
offset \
partition
diff --git a/filters/nozero/Makefile.am b/filters/nozero/Makefile.am
new file mode 100644
index 0000000..1c66ed8
--- /dev/null
+++ b/filters/nozero/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-nozero-filter.pod
+
+CLEANFILES = *~
+
+filterdir = $(libdir)/nbdkit/filters
+
+filter_LTLIBRARIES = nbdkit-nozero-filter.la
+
+nbdkit_nozero_filter_la_SOURCES = \
+ nozero.c \
+ $(top_srcdir)/include/nbdkit-filter.h
+
+nbdkit_nozero_filter_la_CPPFLAGS = \
+ -I$(top_srcdir)/include
+nbdkit_nozero_filter_la_CFLAGS = \
+ $(WARNINGS_CFLAGS)
+nbdkit_nozero_filter_la_LDFLAGS = \
+ -module -avoid-version -shared
+
+if HAVE_POD2MAN
+
+man_MANS = nbdkit-nozero-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-nozero-filter.1: nbdkit-nozero-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 2b3082e..0013fda 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -53,6 +53,7 @@ EXTRA_DIST = \
test-help-plugin.sh \
test-ip.sh \
test-log.sh \
+ test-nozero.sh \
test_ocaml_plugin.ml \
test-ocaml.c \
test-parallel-file.sh \
@@ -435,6 +436,9 @@ test_delay_LDADD = libtest.la $(LIBGUESTFS_LIBS)
# log filter test.
TESTS += test-log.sh
+# nozero filter test.
+TESTS += test-nozero.sh
+
# offset filter test.
check_DATA += offset-data
MAINTAINERCLEANFILES += offset-data
diff --git a/tests/test-nozero.sh b/tests/test-nozero.sh
new file mode 100755
index 0000000..85caa61
--- /dev/null
+++ b/tests/test-nozero.sh
@@ -0,0 +1,145 @@
+#!/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="nozero1.img nozero1.log nozero1.sock nozero1.pid
+ nozero2.img nozero2.log nozero2.sock nozero2.pid
+ nozero3.img nozero3.log nozero3.sock nozero3.pid
+ nozero4.img nozero4.log nozero4.sock nozero4.pid"
+rm -f $files
+
+: ${QEMU_IO=qemu-io}
+
+# Prep images, and check that qemu-io understands the actions we plan on
+# doing, and that zero with trim results in a sparse image.
+for f in {0..1023}; do printf '%1024s' . >> nozero1.img; done
+cp nozero1.img nozero2.img
+cp nozero1.img nozero3.img
+cp nozero1.img nozero4.img
+if ! $QEMU_IO -f raw -d unmap -c 'w -z -u 0 1M' nozero1.img; then
+ echo "$0: missing or broken qemu-io"
+ rm nozero?.img
+ exit 77
+fi
+if test "$(stat -c %b nozero1.img)" = "$(stat -c %b nozero2.img)";
then
+ echo "$0: can't trim file by writing zeros"
+ rm nozero?.img
+ exit 77
+fi
+cp nozero2.img nozero1.img
+
+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 nozero1.log || :
+ echo "Log 2 file contents:"
+ cat nozero2.log || :
+ echo "Log 3 file contents:"
+ cat nozero3.log || :
+ echo "Log 4 file contents:"
+ cat nozero4.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: unfiltered, to check that qemu-io sends ZERO request and plugin trims
+# 2: log before filter with zeromode=none (default), to ensure no ZERO request
+# 3: log before filter with zeromode=emulate, to ensure ZERO from client
+# 4: log after filter with zeromode=emulate, to ensure no ZERO to plugin
+nbdkit -P nozero1.pid -U nozero1.sock --filter=log \
+ file logfile=nozero1.log file=nozero1.img
+nbdkit -P nozero2.pid -U nozero2.sock --filter=log --filter=nozero \
+ file logfile=nozero2.log file=nozero2.img
+nbdkit -P nozero3.pid -U nozero3.sock --filter=log --filter=nozero \
+ file logfile=nozero3.log file=nozero3.img zeromode=emulate
+nbdkit -P nozero4.pid -U nozero4.sock --filter=nozero --filter=log \
+ file logfile=nozero4.log file=nozero4.img zeromode=emulate
+
+# We may have to wait a short time for the pid files to appear.
+for i in `seq 1 10`; do
+ if test -f nozero1.pid && test -f nozero2.pid && test -f nozero3.pid
&&
+ test -f nozero4.pid; then
+ break
+ fi
+ sleep 1
+done
+
+pid1="$(cat nozero1.pid)" || :
+pid2="$(cat nozero2.pid)" || :
+pid3="$(cat nozero3.pid)" || :
+pid4="$(cat nozero4.pid)" || :
+
+if ! test -f nozero1.pid || ! test -f nozero2.pid || ! test -f nozero3.pid ||
+ ! test -f nozero4.pid; then
+ echo "$0: PID files were not created"
+ exit 1
+fi
+
+# Perform the zero write.
+$QEMU_IO -f raw -c 'w -z -u 0 1M' 'nbd+unix://?socket=nozero1.sock'
+$QEMU_IO -f raw -c 'w -z -u 0 1M' 'nbd+unix://?socket=nozero2.sock'
+$QEMU_IO -f raw -c 'w -z -u 0 1M' 'nbd+unix://?socket=nozero3.sock'
+$QEMU_IO -f raw -c 'w -z -u 0 1M' 'nbd+unix://?socket=nozero4.sock'
+
+# Check for expected ZERO vs. WRITE results
+grep 'connection=1 Zero' nozero1.log
+if grep 'connection=1 Zero' nozero2.log; then
+ echo "filter should have prevented zero"
+ exit 1
+fi
+grep 'connection=1 Zero' nozero3.log
+if grep 'connection=1 Zero' nozero4.log; then
+ echo "filter should have converted zero into write"
+ exit 1
+fi
+
+# Sanity check on contents - all 4 files should read identically
+cmp nozero1.img nozero2.img
+cmp nozero2.img nozero3.img
+cmp nozero3.img nozero4.img
+
+# The cleanup() function is called implicitly on exit.
--
2.14.3