Now that we have .block_size, if the plugin reports specific block
constraints, honor those by default rather than requiring the user to
duplicate work by passing in extra filter parameters on the command
line.
The TODO in test-blocksize.sh is no longer relevant (we now advertise
block size, so modern qemu no longer does read-modify-write), but that
test is still valuable as written, so the real coverage of the new
default behavior is in a new test.
---
filters/blocksize/nbdkit-blocksize-filter.pod | 14 ++-
tests/Makefile.am | 14 ++-
filters/blocksize/blocksize.c | 19 +++-
tests/test-blocksize-default.sh | 88 +++++++++++++++++++
tests/test-blocksize.sh | 3 -
5 files changed, 124 insertions(+), 14 deletions(-)
create mode 100755 tests/test-blocksize-default.sh
diff --git a/filters/blocksize/nbdkit-blocksize-filter.pod
b/filters/blocksize/nbdkit-blocksize-filter.pod
index 5df9e719..4a1ebded 100644
--- a/filters/blocksize/nbdkit-blocksize-filter.pod
+++ b/filters/blocksize/nbdkit-blocksize-filter.pod
@@ -23,6 +23,9 @@ refuses a read or write larger than 64 megabytes, while many other NBD
servers limit things to 32 megabytes). The blocksize filter can be
used to modify the client requests to meet the plugin restrictions.
+This filter can be combined with L<nbdkit-blocksize-policy-filter(1)>
+to advertise different block sizes to the client.
+
=head1 PARAMETERS
The nbdkit-blocksize-filter accepts the following parameters.
@@ -33,7 +36,8 @@ The nbdkit-blocksize-filter accepts the following parameters.
The minimum block size and alignment to pass to the plugin. This must
be a power of two, and no larger than 64k. If omitted, this defaults
-to 1 (that is, no minimum size restrictions). The filter rounds up
+to the minimum block size of the underlying plugin, or 1 if the plugin
+did not report a minimum block size. The filter rounds up
read requests to alignment boundaries, performs read-modify-write
cycles for any unaligned head or tail of a write or zero request, and
silently ignores any unaligned head or tail of a trim request. The
@@ -47,8 +51,9 @@ This parameter understands the suffix 'k' for 1024.
=item B<maxdata=>SIZE
The maximum block size for any single transaction with data (read and
-write). If omitted, this defaults to 64 megabytes (that is, the
-nbdkit maximum). This need not be a power of two, but must be an
+write). If omitted, this defaults to the minimum of 64 megabytes (that
+is, the nbdkit maximum) or any maximum reported by the underlying plugin.
+This need not be a power of two, but must be an
integer multiple of C<minblock>. The filter fragments any larger
client request into multiple plugin requests.
@@ -113,6 +118,7 @@ L<nbdkit(1)>,
L<nbdkit-nbd-plugin(1)>,
L<nbdkit-vddk-plugin(1)>,
L<nbdkit-filter(3)>,
+L<nbdkit-blocksize-policy-filter(1)>,
L<nbdkit-truncate-filter(1)>.
=head1 AUTHORS
@@ -121,4 +127,4 @@ Eric Blake
=head1 COPYRIGHT
-Copyright (C) 2018 Red Hat Inc.
+Copyright (C) 2018-2022 Red Hat Inc.
diff --git a/tests/Makefile.am b/tests/Makefile.am
index dd3b4ded..77d13dc9 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,5 +1,5 @@
# nbdkit
-# Copyright (C) 2013-2021 Red Hat Inc.
+# Copyright (C) 2013-2022 Red Hat Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
@@ -1408,8 +1408,16 @@ test_layers_filter3_la_LDFLAGS = \
test_layers_filter3_la_LIBADD = $(IMPORT_LIBRARY_ON_WINDOWS)
# blocksize filter test.
-TESTS += test-blocksize.sh test-blocksize-extents.sh
-EXTRA_DIST += test-blocksize.sh test-blocksize-extents.sh
+TESTS += \
+ test-blocksize.sh \
+ test-blocksize-extents.sh \
+ test-blocksize-default.sh \
+ $(NULL)
+EXTRA_DIST += \
+ test-blocksize.sh \
+ test-blocksize-extents.sh \
+ test-blocksize-default.sh \
+ $(NULL)
# blocksize-policy filter test.
TESTS += test-blocksize-policy.sh test-blocksize-error-policy.sh
diff --git a/filters/blocksize/blocksize.c b/filters/blocksize/blocksize.c
index bca9fb73..03da4971 100644
--- a/filters/blocksize/blocksize.c
+++ b/filters/blocksize/blocksize.c
@@ -173,9 +173,15 @@ blocksize_prepare (nbdkit_next *next, void *handle,
int readonly)
{
struct blocksize_handle *h = handle;
+ uint32_t minimum, preferred, maximum;
- if (h->minblock == 0)
- h->minblock = 1;
+ /* Here, minimum and maximum will clamp per-handle defaults not set
+ * by globals in .config; preferred has no impact until .block_size.
+ */
+ if (next->block_size (next, &minimum, &preferred, &maximum) == -1)
+ return -1;
+
+ h->minblock = MAX (MAX (h->minblock, 1), minimum);
if (h->maxdata == 0) {
if (h->maxlen)
@@ -183,9 +189,14 @@ blocksize_prepare (nbdkit_next *next, void *handle,
else
h->maxdata = 64 * 1024 * 1024;
}
+ if (maximum)
+ h->maxdata = MIN (h->maxdata, maximum);
+ h->maxdata = ROUND_DOWN (h->maxdata, h->minblock);
if (h->maxlen == 0)
h->maxlen = -h->minblock;
+ else
+ h->maxlen = ROUND_DOWN (h->maxlen, h->minblock);
nbdkit_debug ("handle values minblock=%u maxdata=%u maxlen=%u",
h->minblock, h->maxdata, h->maxlen);
@@ -218,11 +229,11 @@ blocksize_block_size (nbdkit_next *next, void *handle,
{
struct blocksize_handle *h = handle;
+ /* Here we only need preferred; see also blocksize_prepare. */
if (next->block_size (next, minimum, preferred, maximum) == -1)
return -1;
- if (*preferred == 0)
- *preferred = MAX (4096, h->minblock);
+ *preferred = MAX (MAX (*preferred, 4096), h->minblock);
*minimum = 1;
*maximum = 0xffffffff;
diff --git a/tests/test-blocksize-default.sh b/tests/test-blocksize-default.sh
new file mode 100755
index 00000000..738b9c00
--- /dev/null
+++ b/tests/test-blocksize-default.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2019-2022 Red Hat Inc.
+#
+# 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.
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin eval
+requires nbdsh -c 'print(h.get_block_size)'
+
+# Create an nbdkit eval plugin which presents per-export block size
+# constraints based on the export name.
+# Check that the blocksize filter advertises full access, that size is
+# truncated to minblock constraints, and underlying plugin access uses
+# read-modify-write at appropriate boundaries.
+export script='
+import os
+
+sock = os.environ["unixsocket"]
+
+ha = h;
+ha.set_export_name("a")
+ha.connect_unix(sock)
+hb = nbd.NBD()
+hb.set_export_name("b")
+hb.connect_unix(sock)
+
+assert ha.get_size() == 2 * 1024 * 1024 - 1 * 1024
+assert ha.get_block_size(nbd.SIZE_MINIMUM) == 1
+assert ha.get_block_size(nbd.SIZE_PREFERRED) == 128 * 1024
+assert ha.get_block_size(nbd.SIZE_MAXIMUM) == 4 * 1024 * 1024 * 1024 - 1
+assert ha.pread(1024 * 1024 + 1, 1) == b"\0" * (1024 * 1024 + 1)
+
+assert hb.get_size() == 2 * 1024 * 1024 - 2 * 1024
+assert hb.get_block_size(nbd.SIZE_MINIMUM) == 1
+assert hb.get_block_size(nbd.SIZE_PREFERRED) == 64 * 1024
+assert hb.get_block_size(nbd.SIZE_MAXIMUM) == 4 * 1024 * 1024 * 1024 - 1
+assert hb.pread(1024 * 1024 + 1, 1) == b"\0" * (1024 * 1024 + 1)
+
+ha.shutdown()
+hb.shutdown()
+'
+nbdkit -v -U - --filter=blocksize eval \
+ open='echo $3' \
+ get_size="echo $((2 * 1024 * 1024 - 1))" \
+ block_size='case $2 in
+ a) echo 1K 128K 512K ;;
+ b) echo 2K 64K 1M ;;
+ *) echo unknown export name >&2; exit 1 ;;
+ esac' \
+ pread='case $2.$3.$4 in
+ a.$((1*1024)).0 | a.$((512*1024)).$((1*1024)) | \
+ a.$((511*1024)).$((513*1024)) | a.$((1*1024)).$((1024*1024)) | \
+ b.$((2*1024)).0 | b.$((1022*1024)).$((2*1024)) | \
+ b.$((2*1024)).$((1024*1024)) )
+ dd if=/dev/zero count=$3 iflag=count_bytes ;;
+ *) echo EIO >&2; exit 1 ;;
+ esac' \
+ --run 'export unixsocket; nbdsh -c "$script"'
diff --git a/tests/test-blocksize.sh b/tests/test-blocksize.sh
index 64196bb8..ee325eff 100755
--- a/tests/test-blocksize.sh
+++ b/tests/test-blocksize.sh
@@ -42,9 +42,6 @@ files="blocksize1.img blocksize1.log $sock1 blocksize1.pid
rm -f $files
# Prep images, and check that qemu-io understands the actions we plan on doing.
-# TODO: Until we implement NBD_INFO_BLOCK_SIZE, qemu-io does its own
-# read-modify-write at 512-byte alignment, while we'd like to ultimately test
-# 1-byte accesses
truncate -s 10M blocksize1.img
if ! qemu-io -f raw -c 'r 0 1' -c 'w -z 1000 2000' \
-c 'w -P 0 1M 2M' -c 'discard 3M 4M' blocksize1.img; then
--
2.35.1