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