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