From c5dc657a0f52954ea222dc1b2f6b9afa1b82afbb Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 3 Feb 2020 16:36:37 -0600 Subject: [PATCH] plugins: Wire up file-based plugin support for NBD_INFO_INIT_STATE The NBD protocol is adding an extension to let servers advertise initialization state to the client: whether the image contains holes, and whether it is known to read as all zeroes. For file-based plugins, we are already probing lseek(SEEK_HOLE) to learn if extents are supported; a slight tweak to remember if that result is EOF tells us if we are sparse, and a similar lseek(SEEK_DATA) returning ENXIO tells us if the entire file is a hole, with at most two lseek calls during open (rather than one lseek for each of .can_extents, .init_sparse, and .init_zero). The split plugin is similar to file. For partitioning and linuxdisk, we know we are exposing a sparse image. For other file-based plugins, like ext2, we do not yet expose .extents so it is not worth trying to expose .init_sparse or .init_zero. A successful test depends on whether the current file system creates sparse files, but it was easy enough to ensure the test skips rather than fails when that is not the case. Signed-off-by: Eric Blake --- plugins/file/file.c | 82 +++++++++++++++++++++++++++++++------ plugins/linuxdisk/linuxdisk.c | 13 +++++- plugins/partitioning/partitioning.c | 13 +++++- plugins/split/split.c | 59 +++++++++++++++++++++++--- tests/Makefile.am | 3 +- tests/test-file-init.sh | 69 +++++++++++++++++++++++++++++++ 6 files changed, 219 insertions(+), 20 deletions(-) create mode 100755 tests/test-file-init.sh diff --git a/plugins/file/file.c b/plugins/file/file.c index 8c2ea077..ca7e2d9e 100644 --- a/plugins/file/file.c +++ b/plugins/file/file.c @@ -1,5 +1,5 @@ /* nbdkit - * Copyright (C) 2013-2019 Red Hat Inc. + * Copyright (C) 2013-2020 Red Hat Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -154,6 +154,9 @@ struct handle { bool can_zero_range; bool can_fallocate; bool can_zeroout; + bool can_extents; + bool init_sparse; + bool init_zero; }; /* Create the per-connection handle. */ @@ -214,6 +217,52 @@ file_open (int readonly) h->can_fallocate = true; h->can_zeroout = h->is_block_device; + h->can_extents = false; + h->init_sparse = false; + h->init_zero = false; +#ifdef SEEK_HOLE + if (!h->is_block_device) { + off_t r; + + /* A simple test to see whether SEEK_DATA/SEEK_HOLE are likely to work on + * the current filesystem, and to see if the image is sparse or zero. + */ + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lseek_lock); + r = lseek (h->fd, 0, SEEK_DATA); + if (r == -1) { + if (errno == ENXIO) { + nbdkit_debug ("extents enabled, entire image is hole"); + h->can_extents = true; + h->init_sparse = true; + h->init_zero = true; + } else { + nbdkit_debug ("extents disabled: lseek(SEEK_DATA): %m"); + } + } + else { + h->can_extents = true; + if (r > 0) { + nbdkit_debug ("extents enabled, image includes hole before data"); + h->init_sparse = true; + } + else { + r = lseek (h->fd, 0, SEEK_HOLE); + if (r == -1) { + nbdkit_debug ("extents disabled: lseek(SEEK_HOLE): %m"); + h->can_extents = false; + } + else if (r == statbuf.st_size) { + nbdkit_debug ("extents enabled, image currently all data"); + } + else { + nbdkit_debug ("extents enabled, image includes data before hole"); + h->init_sparse = true; + } + } + } + } +#endif + return h; } @@ -540,18 +589,8 @@ static int file_can_extents (void *handle) { struct handle *h = handle; - off_t r; - /* A simple test to see whether SEEK_HOLE etc is likely to work on - * the current filesystem. - */ - ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lseek_lock); - r = lseek (h->fd, 0, SEEK_HOLE); - if (r == -1) { - nbdkit_debug ("extents disabled: lseek: SEEK_HOLE: %m"); - return 0; - } - return 1; + return h->can_extents; } static int @@ -622,6 +661,23 @@ file_extents (void *handle, uint32_t count, uint64_t offset, ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lseek_lock); return do_extents (handle, count, offset, flags, extents); } + +/* Initial state. */ +static int +file_init_sparse (void *handle) +{ + struct handle *h = handle; + + return h->init_sparse; +} + +static int +file_init_zero (void *handle) +{ + struct handle *h = handle; + + return h->init_zero; +} #endif /* SEEK_HOLE */ #if HAVE_POSIX_FADVISE @@ -668,6 +724,8 @@ static struct nbdkit_plugin plugin = { #ifdef SEEK_HOLE .can_extents = file_can_extents, .extents = file_extents, + .init_sparse = file_init_sparse, + .init_zero = file_init_zero, #endif #if HAVE_POSIX_FADVISE .cache = file_cache, diff --git a/plugins/linuxdisk/linuxdisk.c b/plugins/linuxdisk/linuxdisk.c index 99dbc996..0907302e 100644 --- a/plugins/linuxdisk/linuxdisk.c +++ b/plugins/linuxdisk/linuxdisk.c @@ -1,5 +1,5 @@ /* nbdkit - * Copyright (C) 2019 Red Hat Inc. + * Copyright (C) 2019-2020 Red Hat Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -167,6 +167,16 @@ linuxdisk_can_cache (void *handle) return NBDKIT_CACHE_EMULATE; } +/* Initial state. */ +static int +linuxdisk_init_sparse (void *handle) +{ + /* region_zero regions mean we are sparse, even if we don't yet + * support .extents + */ + return 1; +} + /* Read data from the virtual disk. */ static int linuxdisk_pread (void *handle, void *buf, uint32_t count, uint64_t offset, @@ -230,6 +240,7 @@ static struct nbdkit_plugin plugin = { .get_size = linuxdisk_get_size, .can_multi_conn = linuxdisk_can_multi_conn, .can_cache = linuxdisk_can_cache, + .init_sparse = linuxdisk_init_sparse, .pread = linuxdisk_pread, .errno_is_preserved = 1, }; diff --git a/plugins/partitioning/partitioning.c b/plugins/partitioning/partitioning.c index 6e426b93..0b42b2e5 100644 --- a/plugins/partitioning/partitioning.c +++ b/plugins/partitioning/partitioning.c @@ -1,5 +1,5 @@ /* nbdkit - * Copyright (C) 2018-2019 Red Hat Inc. + * 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 @@ -307,6 +307,16 @@ partitioning_can_cache (void *handle) return NBDKIT_CACHE_EMULATE; } +/* Initial state. */ +static int +partitioning_init_sparse (void *handle) +{ + /* region_zero regions mean we are sparse, even if we don't yet + * support .extents + */ + return 1; +} + /* Read data. */ static int partitioning_pread (void *handle, void *buf, uint32_t count, uint64_t offset) @@ -437,6 +447,7 @@ static struct nbdkit_plugin plugin = { .get_size = partitioning_get_size, .can_multi_conn = partitioning_can_multi_conn, .can_cache = partitioning_can_cache, + .init_sparse = partitioning_init_sparse, .pread = partitioning_pread, .pwrite = partitioning_pwrite, .flush = partitioning_flush, diff --git a/plugins/split/split.c b/plugins/split/split.c index 70fd4d40..ebb5f5aa 100644 --- a/plugins/split/split.c +++ b/plugins/split/split.c @@ -97,6 +97,8 @@ split_config (const char *key, const char *value) struct handle { struct file *files; uint64_t size; /* Total concatenated size. */ + bool init_sparse; + bool init_zero; }; struct file { @@ -147,6 +149,8 @@ split_open (int readonly) } offset = 0; + h->init_sparse = false; /* set later as needed */ + h->init_zero = true; /* cleared later as needed */ for (i = 0; i < nr_files; ++i) { h->files[i].offset = offset; @@ -161,17 +165,44 @@ split_open (int readonly) i, filenames[i], h->files[i].offset, h->files[i].size); #ifdef SEEK_HOLE - /* Test if this file supports extents. */ + /* Test if this file supports extents, and for initial state. */ 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; + if (r == -1) { + if (errno == ENXIO) { + /* Entire file is hole */ + h->files[i].can_extents = true; + h->init_sparse = true; + } + else { + nbdkit_debug ("disabling extents: lseek(SEEK_DATA) on %s: %m", + filenames[i]); + h->files[i].can_extents = false; + h->init_zero = false; + } } - else + else { h->files[i].can_extents = true; + h->init_zero = false; + if (r > 0) + /* File includes hole before data */ + h->init_sparse = true; + else { + r = lseek (h->files[i].fd, 0, SEEK_HOLE); + if (r == -1) { + nbdkit_debug ("disabling extents: lseek(SEEK_HOLE) on %s: %m", + filenames[i]); + h->files[i].can_extents = false; + } + else if (r < statbuf.st_size) { + /* File includes data before hole */ + h->init_sparse = true; + } + } + } #else h->files[i].can_extents = false; + h->init_zero = false; #endif } h->size = offset; @@ -227,6 +258,22 @@ split_can_cache (void *handle) #endif } +static int +split_init_sparse (void *handle) +{ + struct handle *h = handle; + + return h->init_sparse; +} + +static int +split_init_zero (void *handle) +{ + struct handle *h = handle; + + return h->init_zero; +} + /* Helper function to map the offset to the correct file. */ static int compare_offset (const void *offsetp, const void *filep) @@ -450,6 +497,8 @@ static struct nbdkit_plugin plugin = { .close = split_close, .get_size = split_get_size, .can_cache = split_can_cache, + .init_sparse = split_init_sparse, + .init_zero = split_init_zero, .pread = split_pread, .pwrite = split_pwrite, #if HAVE_POSIX_FADVISE diff --git a/tests/Makefile.am b/tests/Makefile.am index 4e036a3d..a8ff6015 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -113,6 +113,7 @@ EXTRA_DIST = \ test-export-name.sh \ test-extentlist.sh \ test-file-extents.sh \ + test-file-init.sh \ test-floppy.sh \ test-foreground.sh \ test-fua.sh \ @@ -554,7 +555,7 @@ test_file_block_SOURCES = test-file-block.c test.h test_file_block_CFLAGS = $(WARNINGS_CFLAGS) $(LIBGUESTFS_CFLAGS) test_file_block_LDADD = libtest.la $(LIBGUESTFS_LIBS) -TESTS += test-file-extents.sh +TESTS += test-file-extents.sh test-file-init.sh # floppy plugin test. TESTS += test-floppy.sh diff --git a/tests/test-file-init.sh b/tests/test-file-init.sh new file mode 100755 index 00000000..c66ac65a --- /dev/null +++ b/tests/test-file-init.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# nbdkit +# Copyright (C) 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. + +# Test the initial state of the file plugin. + +source ./functions.sh +set -e + +requires nbdsh -c 'exit (not hasattr (h, "get_init_flags"))' +requires truncate --help +requires stat --help + +sock=`mktemp -u` +files="file-init.pid $sock file-init.out" +rm -f $files +cleanup_fn rm -f $files + +# See if the current file system creates sparse files +truncate --size=1M file-init.out +if test "$(stat -c %b file-init.out)" != 0; then + echo "$0: unable to create sparse file, skipping this test" + exit 77 +fi + +# Run nbdkit with the file plugin. +start_nbdkit -P file-init.pid -U $sock file file-init.out + +# The image should start as sparse/zero, before we write to it +nbdsh --connect "nbd+unix://?socket=$sock" \ + -c 'assert (h.get_init_flags () == nbd.INIT_SPARSE | nbd.INIT_ZERO)' \ + -c 'h.pwrite ("hello world".encode (), 0)' + +# The image is still sparse, but no longer zero, before we fill it +nbdsh --connect "nbd+unix://?socket=$sock" \ + -c 'assert (h.get_init_flags () == nbd.INIT_SPARSE)' \ + -c 'h.pwrite (("1" * (1024*1024)).encode (), 0)' + +# The image is neither sparse nor zero +nbdsh --connect "nbd+unix://?socket=$sock" \ + -c 'assert (h.get_init_flags () == 0)' -- 2.11.4.GIT