This can truncate, extend, or round up/down to a multiple.
---
common-rules.mk | 3 +-
configure.ac | 1 +
filters/offset/nbdkit-offset-filter.pod | 7 +-
filters/partition/nbdkit-partition-filter.pod | 1 +
filters/truncate/Makefile.am | 60 ++++
filters/truncate/nbdkit-truncate-filter.pod | 87 ++++++
filters/truncate/truncate.c | 261 ++++++++++++++++++
7 files changed, 417 insertions(+), 3 deletions(-)
diff --git a/common-rules.mk b/common-rules.mk
index 01beff5..f600293 100644
--- a/common-rules.mk
+++ b/common-rules.mk
@@ -68,7 +68,8 @@ filters = \
log \
nozero \
offset \
- partition
+ partition \
+ truncate
plugindir = $(libdir)/nbdkit/plugins
filterdir = $(libdir)/nbdkit/filters
diff --git a/configure.ac b/configure.ac
index b87efb9..e8d0a38 100644
--- a/configure.ac
+++ b/configure.ac
@@ -578,6 +578,7 @@ AC_CONFIG_FILES([Makefile
filters/nozero/Makefile
filters/offset/Makefile
filters/partition/Makefile
+ filters/truncate/Makefile
src/Makefile
src/nbdkit.pc
tests/Makefile])
diff --git a/filters/offset/nbdkit-offset-filter.pod
b/filters/offset/nbdkit-offset-filter.pod
index ee8061b..6d8f9be 100644
--- a/filters/offset/nbdkit-offset-filter.pod
+++ b/filters/offset/nbdkit-offset-filter.pod
@@ -32,7 +32,9 @@ file/device.
=back
Note it is an error if the offset and/or range specify data which lies
-beyond the end of the underlying device.
+beyond the end of the underlying device. Use
+L<nbdkit-truncate-filter(1)> to truncate or extend the size of
+plugins.
=head1 EXAMPLES
@@ -65,7 +67,8 @@ You can then serve the partition only using:
L<nbdkit(1)>,
L<nbdkit-file-plugin(1)>,
L<nbdkit-filter(3)>,
-L<nbdkit-partition-filter(1)>.
+L<nbdkit-partition-filter(1)>,
+L<nbdkit-truncate-filter(1)>.
=head1 AUTHORS
diff --git a/filters/partition/nbdkit-partition-filter.pod
b/filters/partition/nbdkit-partition-filter.pod
index bc5f346..71a7a3a 100644
--- a/filters/partition/nbdkit-partition-filter.pod
+++ b/filters/partition/nbdkit-partition-filter.pod
@@ -46,6 +46,7 @@ L<nbdkit(1)>,
L<nbdkit-file-plugin(1)>,
L<nbdkit-filter(3)>,
L<nbdkit-offset-filter(1)>,
+L<nbdkit-truncate-filter(1)>,
L<parted(8)>.
=head1 AUTHORS
diff --git a/filters/truncate/Makefile.am b/filters/truncate/Makefile.am
new file mode 100644
index 0000000..04ed4de
--- /dev/null
+++ b/filters/truncate/Makefile.am
@@ -0,0 +1,60 @@
+# 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 $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = nbdkit-truncate-filter.pod
+
+filter_LTLIBRARIES = nbdkit-truncate-filter.la
+
+nbdkit_truncate_filter_la_SOURCES = \
+ truncate.c \
+ $(top_srcdir)/include/nbdkit-filter.h
+
+nbdkit_truncate_filter_la_CPPFLAGS = \
+ -I$(top_srcdir)/include
+nbdkit_truncate_filter_la_CFLAGS = \
+ $(WARNINGS_CFLAGS)
+nbdkit_truncate_filter_la_LDFLAGS = \
+ -module -avoid-version -shared
+
+if HAVE_POD
+
+man_MANS = nbdkit-truncate-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-truncate-filter.1: nbdkit-truncate-filter.pod
+ $(PODWRAPPER) --section=1 --man $@ \
+ --html $(top_builddir)/html/$@.html \
+ $<
+
+endif HAVE_POD
diff --git a/filters/truncate/nbdkit-truncate-filter.pod
b/filters/truncate/nbdkit-truncate-filter.pod
new file mode 100644
index 0000000..d52a440
--- /dev/null
+++ b/filters/truncate/nbdkit-truncate-filter.pod
@@ -0,0 +1,87 @@
+=head1 NAME
+
+nbdkit-truncate-filter - change the size of plugins
+
+=head1 SYNOPSIS
+
+ nbdkit --filter=truncate plugin [truncate=SIZE]
+ [round-up=N] [round-down=N]
+
+=head1 DESCRIPTION
+
+C<nbdkit-truncate-filter> is a filter that changes the size of
+the underlying plugin. It can:
+
+=over 4
+
+=item *
+
+Make the plugin smaller (truncate it). Use the C<truncate=SIZE>
+parameter to set the smaller size.
+
+=item *
+
+Make the plugin larger (the additional bytes read back as zeroes).
+Use C<truncate=SIZE> to set the larger size.
+
+=item *
+
+Round the size of the plugin up or down to the next multiple of C<N>.
+Use either C<round-up=N> or C<round-down=N>.
+
+=back
+
+A common use for this filter is to handle NBD clients which have a
+problem dealing with device sizes which are not a multiple of 512
+bytes. Use C<round-up=512> to round the size up to the next multiple
+of 512 bytes. If the size is already a multiple of 512 bytes then
+this has no effect.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<truncate=SIZE>
+
+Set the absolute size in bytes of the apparent device. This may be
+smaller or larger or the same as the underlying plugin.
+
+If the size of larger than the underlying plugin, reading the extra
+space returns zeroes, and any non-zero writes will return an error
+back to the client.
+
+This parameter is optional.
+
+=item B<round-up=N>
+
+Round the size up to the next multiple of C<N> bytes. If the size of
+the underlying plugin is already a multiple of C<N> bytes, this has no
+effect.
+
+This parameter is optional.
+
+=item B<round-down=N>
+
+Round the size down to a multiple of C<N> bytes. If the size of the
+underlying plugin is already a multiple of C<N> bytes, this has no
+effect.
+
+This parameter is optional.
+
+=back
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-file-plugin(1)>,
+L<nbdkit-filter(3)>,
+L<nbdkit-offset-filter(1)>,
+L<nbdkit-partition-filter(1)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2018 Red Hat Inc.
diff --git a/filters/truncate/truncate.c b/filters/truncate/truncate.c
new file mode 100644
index 0000000..eccf8cd
--- /dev/null
+++ b/filters/truncate/truncate.c
@@ -0,0 +1,261 @@
+/* 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 <errno.h>
+
+#include <nbdkit-filter.h>
+
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
+
+/* These are the parameters. */
+static int64_t truncate = -1, round_up = -1, round_down = -1;
+
+/* The real size of the underlying plugin. */
+static int64_t real_size;
+
+/* The calculated size after applying the parameters. */
+static int64_t size;
+
+/* Called for each key=value passed on the command line. */
+static int
+truncate_config (nbdkit_next_config *next, void *nxdata,
+ const char *key, const char *value)
+{
+ if (strcmp (key, "truncate") == 0) {
+ truncate = nbdkit_parse_size (value);
+ if (truncate == -1)
+ return -1;
+ return 0;
+ }
+ else if (strcmp (key, "round-up") == 0) {
+ round_up = nbdkit_parse_size (value);
+ if (round_up == -1)
+ return -1;
+ if (round_up == 0) {
+ nbdkit_error ("if set, the round-up parameter must be > 0");
+ return -1;
+ }
+ return 0;
+ }
+ else if (strcmp (key, "round-down") == 0) {
+ round_down = nbdkit_parse_size (value);
+ if (round_down == -1)
+ return -1;
+ if (round_down == 0) {
+ nbdkit_error ("if set, the round-down parameter must be > 0");
+ return -1;
+ }
+ return 0;
+ }
+ else
+ return next (nxdata, key, value);
+}
+
+#define truncate_config_help \
+ "truncate=<SIZE> The new size.\n" \
+ "round-up=<N> Round up to next multiple of N.\n" \
+ "round-down=<N> Round down to multiple of N."
+
+static int64_t truncate_get_size (struct nbdkit_next_ops *next_ops, void *nxdata, void
*handle);
+
+/* In prepare, force a call to get_size which sets the real_size & size
+ * globals.
+ */
+static int
+truncate_prepare (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ int64_t r;
+
+ r = truncate_get_size (next_ops, nxdata, handle);
+ return r >= 0 ? 0 : -1;
+}
+
+/* Get the size. As a side effect, calculate the size to serve. */
+static int64_t
+truncate_get_size (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ real_size = size = next_ops->get_size (nxdata);
+
+ /* The truncate, round-up and round-down parameters are treated as
+ * separate operations. It's possible to specify more than one,
+ * although perhaps not very useful.
+ */
+ if (truncate >= 0)
+ size = truncate;
+ if (round_up > 0)
+ size = (size + round_up - 1) & ~(round_up-1);
+ if (round_down > 0)
+ size &= ~(round_down-1);
+ return size;
+}
+
+/* Read data. */
+static int
+truncate_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, void *buf, uint32_t count, uint64_t offset,
+ uint32_t flags, int *err)
+{
+ int r;
+ uint32_t n;
+
+ if (offset < real_size) {
+ if (offset + count <= real_size)
+ n = count;
+ else
+ n = real_size - offset;
+ r = next_ops->pread (nxdata, buf, n, offset, flags, err);
+ if (r == -1)
+ return -1;
+ count -= n;
+ buf += n;
+ }
+
+ if (count > 0)
+ memset (buf, 0, count);
+
+ return 0;
+}
+
+/* Return true iff the buffer is all zero bytes.
+ *
+ * The clever approach here was suggested by Eric Blake. See:
+ *
https://www.redhat.com/archives/libguestfs/2017-April/msg00171.html
+ */
+static inline int
+is_zero (const char *buffer, size_t size)
+{
+ size_t i;
+ const size_t limit = size < 16 ? size : 16;
+
+ for (i = 0; i < limit; ++i)
+ if (buffer[i])
+ return 0;
+ if (size != limit)
+ return !memcmp (buffer, buffer + 16, size - 16);
+
+ return 1;
+}
+
+/* Write data. */
+static int
+truncate_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle,
+ const void *buf, uint32_t count, uint64_t offset,
+ uint32_t flags, int *err)
+{
+ int r;
+ uint32_t n;
+
+ if (offset < real_size) {
+ if (offset + count <= real_size)
+ n = count;
+ else
+ n = real_size - offset;
+ r = next_ops->pwrite (nxdata, buf, n, offset, flags, err);
+ if (r == -1)
+ return -1;
+ count -= n;
+ buf += n;
+ }
+
+ if (count > 0) {
+ /* The caller must be writing zeroes, else it's an error. */
+ if (!is_zero (buf, count)) {
+ nbdkit_error ("truncate: write beyond end of underlying device");
+ *err = EIO;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Trim data. */
+static int
+truncate_trim (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, uint32_t count, uint64_t offset,
+ uint32_t flags, int *err)
+{
+ uint32_t n;
+
+ if (offset < real_size) {
+ if (offset + count <= real_size)
+ n = count;
+ else
+ n = real_size - offset;
+ return next_ops->trim (nxdata, n, offset, flags, err);
+ }
+ return 0;
+}
+
+/* Zero data. */
+static int
+truncate_zero (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, uint32_t count, uint64_t offset,
+ uint32_t flags, int *err)
+{
+ uint32_t n;
+
+ if (offset < real_size) {
+ if (offset + count <= real_size)
+ n = count;
+ else
+ n = real_size - offset;
+ return next_ops->zero (nxdata, n, offset, flags, err);
+ }
+ return 0;
+}
+
+static struct nbdkit_filter filter = {
+ .name = "truncate",
+ .longname = "nbdkit truncate filter",
+ .version = PACKAGE_VERSION,
+ .config = truncate_config,
+ .config_help = truncate_config_help,
+ .prepare = truncate_prepare,
+ .get_size = truncate_get_size,
+ .pread = truncate_pread,
+ .pwrite = truncate_pwrite,
+ .trim = truncate_trim,
+ .zero = truncate_zero,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
--
2.18.0