Copies somewhat from the file plugin, with the difference that we always provide an .extents even if one or more of the split files does not support SEEK_HOLE.
Testing is possible on a file system that supports sparse files when using nbdsh. Signed-off-by: Eric Blake <[email protected]> --- I'm going ahead and committing this, but am also posting this for review in case we later find any problems with what I just pushed. plugins/split/Makefile.am | 6 +- plugins/split/split.c | 125 +++++++++++++++++++++++++++++++++++- tests/Makefile.am | 3 + tests/test-split-extents.sh | 74 +++++++++++++++++++++ 4 files changed, 206 insertions(+), 2 deletions(-) create mode 100755 tests/test-split-extents.sh diff --git a/plugins/split/Makefile.am b/plugins/split/Makefile.am index 003f6d9..ceff349 100644 --- a/plugins/split/Makefile.am +++ b/plugins/split/Makefile.am @@ -1,5 +1,5 @@ # nbdkit -# Copyright (C) 2017 Red Hat Inc. +# Copyright (C) 2017-2020 Red Hat Inc. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are @@ -42,12 +42,16 @@ nbdkit_split_plugin_la_SOURCES = \ nbdkit_split_plugin_la_CPPFLAGS = \ -I$(top_srcdir)/include \ + -I$(top_srcdir)/common/utils \ $(NULL) nbdkit_split_plugin_la_CFLAGS = $(WARNINGS_CFLAGS) nbdkit_split_plugin_la_LDFLAGS = \ -module -avoid-version -shared \ -Wl,--version-script=$(top_srcdir)/plugins/plugins.syms \ $(NULL) +nbdkit_split_plugin_la_LIBADD = \ + $(top_builddir)/common/utils/libutils.la \ + $(NULL) if HAVE_POD diff --git a/plugins/split/split.c b/plugins/split/split.c index 68682b2..70fd4d4 100644 --- a/plugins/split/split.c +++ b/plugins/split/split.c @@ -1,5 +1,5 @@ /* nbdkit - * Copyright (C) 2017-2019 Red Hat Inc. + * Copyright (C) 2017-2020 Red Hat Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -42,13 +42,19 @@ #include <errno.h> #include <sys/types.h> #include <sys/stat.h> +#include <stdbool.h> #include <nbdkit-plugin.h> +#include "cleanup.h" + /* The files. */ static char **filenames = NULL; static size_t nr_files = 0; +/* Any callbacks using lseek must be protected by this lock. */ +static pthread_mutex_t lseek_lock = PTHREAD_MUTEX_INITIALIZER; + static void split_unload (void) { @@ -96,6 +102,7 @@ struct handle { struct file { uint64_t offset, size; int fd; + bool can_extents; }; /* Create the per-connection handle. */ @@ -107,6 +114,7 @@ split_open (int readonly) size_t i; uint64_t offset; struct stat statbuf; + off_t r; h = malloc (sizeof *h); if (h == NULL) { @@ -151,6 +159,20 @@ split_open (int readonly) nbdkit_debug ("file[%zu]=%s: offset=%" PRIu64 ", size=%" PRIu64, i, filenames[i], h->files[i].offset, h->files[i].size); + +#ifdef SEEK_HOLE + /* Test if this file supports extents. */ + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lseek_lock); + r = lseek (h->files[i].fd, 0, SEEK_DATA); + if (r == -1 && errno != ENXIO) { + nbdkit_debug ("disabling extents: lseek on %s: %m", filenames[i]); + h->files[i].can_extents = false; + } + else + h->files[i].can_extents = true; +#else + h->files[i].can_extents = false; +#endif } h->size = offset; nbdkit_debug ("total size=%" PRIu64, h->size); @@ -319,6 +341,104 @@ split_cache (void *handle, uint32_t count, uint64_t offset, uint32_t flags) } #endif /* HAVE_POSIX_FADVISE */ +#ifdef SEEK_HOLE +static int64_t +do_extents (struct file *file, uint32_t count, uint64_t offset, + bool req_one, struct nbdkit_extents *extents) +{ + int64_t r = 0; + uint64_t end = offset + count; + + do { + off_t pos; + + pos = lseek (file->fd, offset, SEEK_DATA); + if (pos == -1) { + if (errno == ENXIO) { + /* The current man page does not describe this situation well, + * but a proposed change to POSIX adds these words for ENXIO: + * "or the whence argument is SEEK_DATA and the offset falls + * within the final hole of the file." + */ + pos = end; + } + else { + nbdkit_error ("lseek: SEEK_DATA: %" PRIu64 ": %m", offset); + return -1; + } + } + + /* We know there is a hole from offset to pos-1. */ + if (pos > offset) { + if (nbdkit_add_extent (extents, offset + file->offset, pos - offset, + NBDKIT_EXTENT_HOLE | NBDKIT_EXTENT_ZERO) == -1) + return -1; + r += pos - offset; + if (req_one) + break; + } + + offset = pos; + if (offset >= end) + break; + + pos = lseek (file->fd, offset, SEEK_HOLE); + if (pos == -1) { + nbdkit_error ("lseek: SEEK_HOLE: %" PRIu64 ": %m", offset); + return -1; + } + + /* We know there is data from offset to pos-1. */ + if (pos > offset) { + if (nbdkit_add_extent (extents, offset + file->offset, pos - offset, + 0 /* allocated data */) == -1) + return -1; + r += pos - offset; + if (req_one) + break; + } + + offset = pos; + } while (offset < end); + + return r; +} + +static int +split_extents (void *handle, uint32_t count, uint64_t offset, + uint32_t flags, struct nbdkit_extents *extents) +{ + struct handle *h = handle; + const bool req_one = flags & NBDKIT_FLAG_REQ_ONE; + + while (count > 0) { + struct file *file = get_file (h, offset); + uint64_t foffs = offset - file->offset; + uint64_t max; + int64_t r; + + max = file->size - foffs; + if (max > count) + max = count; + + if (file->can_extents) { + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lseek_lock); + max = r = do_extents (file, max, foffs, req_one, extents); + } + else + r = nbdkit_add_extent (extents, offset, max, 0 /* allocated data */); + if (r == -1) + return -1; + count -= max; + offset += max; + if (req_one) + break; + } + + return 0; +} +#endif /* SEEK_HOLE */ + static struct nbdkit_plugin plugin = { .name = "split", .version = PACKAGE_VERSION, @@ -334,6 +454,9 @@ static struct nbdkit_plugin plugin = { .pwrite = split_pwrite, #if HAVE_POSIX_FADVISE .cache = split_cache, +#endif +#ifdef SEEK_HOLE + .extents = split_extents, #endif /* In this plugin, errno is preserved properly along error return * paths from failed system calls. diff --git a/tests/Makefile.am b/tests/Makefile.am index 60583a0..ea6b147 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -193,6 +193,7 @@ EXTRA_DIST = \ test-sh-extents.sh \ test-single.sh \ test-single-from-file.sh \ + test-split-extents.sh \ test-start.sh \ test-random-sock.sh \ test-tls.sh \ @@ -672,6 +673,8 @@ test_split_SOURCES = test-split.c test_split_CFLAGS = $(WARNINGS_CFLAGS) $(LIBNBD_CFLAGS) test_split_LDADD = $(LIBNBD_LIBS) +TESTS += test-split-extents.sh + # ssh plugin test. if HAVE_SSH # Uses ‘disk’ so we have to make it conditional on guestfish. diff --git a/tests/test-split-extents.sh b/tests/test-split-extents.sh new file mode 100755 index 0000000..da385a4 --- /dev/null +++ b/tests/test-split-extents.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# nbdkit +# Copyright (C) 2018-2020 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. + +# Concatenating sparse and data files should have observable extents. + +source ./functions.sh +set -e +set -x + +requires nbdsh --base-allocation -c 'exit(not h.supports_uri())' +requires truncate --help +requires stat --help + +files="test-split-extents.1 test-split-extents.2" +rm -f $files +cleanup_fn rm -f $files + +# Create two files, each half data and half sparse +truncate --size=512k test-split-extents.1 +if test "$(stat -c %b test-split-extents.1)" != 0; then + echo "$0: unable to create sparse file, skipping this test" + exit 77 +fi +printf %$((512*1024))d 1 >> test-split-extents.1 +printf %$((512*1024))d 1 >> test-split-extents.2 +truncate --size=1M test-split-extents.2 + +# Test the split plugin +nbdkit -v -U - split test-split-extents.1 test-split-extents.2 \ + --run 'nbdsh --base-allocation --uri $uri -c " +entries = [] +def f (metacontext, offset, e, err): + global entries + assert err.value == 0 + assert metacontext == nbd.CONTEXT_BASE_ALLOCATION + entries = e +h.block_status (2 * 1024 * 1024, 0, f) +assert entries == [ 512 * 1024, 3, + 1024 * 1024, 0, + 512 * 1024, 3 ] +entries = [] +# With req one, extents stop at file boundaries +h.block_status (1024 * 1024, 768 * 1024, f, nbd.CMD_FLAG_REQ_ONE) +assert entries == [ 256 * 1024, 0 ] + "' -- 2.24.1 _______________________________________________ Libguestfs mailing list [email protected] https://www.redhat.com/mailman/listinfo/libguestfs
