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 | 61 ++++
filters/truncate/nbdkit-truncate-filter.pod | 88 +++++
filters/truncate/truncate.c | 301 ++++++++++++++++++
7 files changed, 459 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 774f290..d68fdb7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -579,6 +579,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..cab2ca9
--- /dev/null
+++ b/filters/truncate/Makefile.am
@@ -0,0 +1,61 @@
+# 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 \
+ -I$(top_srcdir)/common/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..5b145a4
--- /dev/null
+++ b/filters/truncate/nbdkit-truncate-filter.pod
@@ -0,0 +1,88 @@
+=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 is larger than the underlying plugin, reading the extra
+space returns zeroes. Writes are also permitted to the extra space,
+but you must only write zeroes (any attempts to write non-zero bytes
+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..5d146c4
--- /dev/null
+++ b/filters/truncate/truncate.c
@@ -0,0 +1,301 @@
+/* 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 <limits.h>
+#include <errno.h>
+
+#include <pthread.h>
+
+#include <nbdkit-filter.h>
+
+#include "ispowerof2.h"
+#include "iszero.h"
+
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
+
+/* These are the parameters. */
+static int64_t truncate = -1;
+static unsigned round_up = 0, round_down = 0;
+
+/* The real size of the underlying plugin. */
+static uint64_t real_size;
+
+/* The calculated size after applying the parameters. */
+static uint64_t size;
+
+/* This lock protects the real_size and size fields. */
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+static int
+parse_round_param (const char *key, const char *value, unsigned *ret)
+{
+ int64_t r;
+ unsigned u;
+
+ /* Parse it as a "size" quantity so we allow round-up=1M and similar. */
+ r = nbdkit_parse_size (value);
+ if (r == -1)
+ return -1;
+
+ /* Must not be zero or larger than an unsigned int. */
+ if (r == 0) {
+ nbdkit_error ("if set, the %s parameter must be > 0", key);
+ return -1;
+ }
+ if (r > UINT_MAX) {
+ nbdkit_error ("the %s parameter is too large", key);
+ return -1;
+ }
+ u = r;
+
+ /* Must be a power of 2. We could relax this in future. */
+ if (!is_power_of_2 (u)) {
+ nbdkit_error ("the %s parameter must be a power of 2", key);
+ return -1;
+ }
+
+ *ret = u;
+ return 0;
+}
+
+/* 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) {
+ return parse_round_param (key, value, &round_up);
+ }
+ else if (strcmp (key, "round-down") == 0) {
+ return parse_round_param (key, value, &round_down);
+ }
+ 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)
+{
+ int64_t r, ret;
+
+ r = next_ops->get_size (nxdata);
+ if (r == -1)
+ return -1;
+
+ pthread_mutex_lock (&lock);
+
+ real_size = size = r;
+
+ /* 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);
+ ret = size;
+
+ pthread_mutex_unlock (&lock);
+
+ return ret;
+}
+
+/* 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;
+ uint64_t real_size_copy;
+
+ pthread_mutex_lock (&lock);
+ real_size_copy = real_size;
+ pthread_mutex_unlock (&lock);
+
+ if (offset < real_size_copy) {
+ if (offset + count <= real_size_copy)
+ n = count;
+ else
+ n = real_size_copy - 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;
+}
+
+/* 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;
+ uint64_t real_size_copy;
+
+ pthread_mutex_lock (&lock);
+ real_size_copy = real_size;
+ pthread_mutex_unlock (&lock);
+
+ if (offset < real_size_copy) {
+ if (offset + count <= real_size_copy)
+ n = count;
+ else
+ n = real_size_copy - 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;
+ uint64_t real_size_copy;
+
+ pthread_mutex_lock (&lock);
+ real_size_copy = real_size;
+ pthread_mutex_unlock (&lock);
+
+ if (offset < real_size_copy) {
+ if (offset + count <= real_size_copy)
+ n = count;
+ else
+ n = real_size_copy - 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;
+ uint64_t real_size_copy;
+
+ pthread_mutex_lock (&lock);
+ real_size_copy = real_size;
+ pthread_mutex_unlock (&lock);
+
+ if (offset < real_size_copy) {
+ if (offset + count <= real_size_copy)
+ n = count;
+ else
+ n = real_size_copy - 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