fallocate(FALLOC_FL_ZERO_RANGE) is supportd for block devices with modern kernel, but when it is not, we fall back to manual zeroing. For block device, try also to use ioctl(BLKZEROOUT) if offset and count are aligned to block device sector size. Here is an example run without this change on RHEL 7.5: $ export SOCK=/tmp/nbd.sock $ export BLOCK=/dev/e30bfac2-8e13-479d-8cd6-c6da5e306c4e/c9864222-bc52-4359-80d7-76e47d619b15 $ src/nbdkit plugins/file/.libs/nbdkit-file-plugin.so file=$BLOCK -U $SOCK $ time qemu-img convert -n -f raw -O raw /var/tmp/fedora-27.img nbd:unix:/tmp/nbd.sock real 0m11.563s user 0m0.219s sys 0m0.746s $ time qemu-img convert -n -f raw -O raw -W /var/tmp/fedora-27.img nbd:unix:/tmp/nbd.sock real 0m9.841s user 0m0.167s sys 0m0.715s With this change: $ time qemu-img convert -n -f raw -O raw /var/tmp/fedora-27.img nbd:unix:/tmp/nbd.sock real 0m2.796s user 0m0.202s sys 0m0.700s $ time qemu-img convert -n -f raw -O raw -W /var/tmp/fedora-27.img nbd:unix:/tmp/nbd.sock real 0m1.908s user 0m0.166s sys 0m0.728s --- plugins/file/Makefile.am | 3 +- plugins/file/file.c | 70 +++++++++++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/plugins/file/Makefile.am b/plugins/file/Makefile.am index 00eaf59..91adbcb 100644 --- a/plugins/file/Makefile.am +++ b/plugins/file/Makefile.am @@ -41,7 +41,8 @@ nbdkit_file_plugin_la_SOURCES = \ $(top_srcdir)/include/nbdkit-plugin.h nbdkit_file_plugin_la_CPPFLAGS = \ - -I$(top_srcdir)/include + -I$(top_srcdir)/include \ + -I$(top_srcdir)/common/include nbdkit_file_plugin_la_CFLAGS = \ $(WARNINGS_CFLAGS) nbdkit_file_plugin_la_LDFLAGS = \ diff --git a/plugins/file/file.c b/plugins/file/file.c index a2cea4a..6c8c78e 100644 --- a/plugins/file/file.c +++ b/plugins/file/file.c @@ -41,14 +41,21 @@ #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> +#include <sys/ioctl.h> #include <errno.h> #if defined(__linux__) && !defined(FALLOC_FL_PUNCH_HOLE) #include <linux/falloc.h> /* For FALLOC_FL_*, glibc < 2.18 */ #endif +#if defined(__linux__) +#include <linux/fs.h> /* For BLKZEROOUT */ +#endif + #include <nbdkit-plugin.h> +#include "isaligned.h" + #ifndef O_CLOEXEC #define O_CLOEXEC 0 #endif @@ -119,9 +126,12 @@ file_config_complete (void) /* The per-connection handle. */ struct handle { int fd; + bool is_block_device; + int sector_size; bool can_punch_hole; bool can_zero_range; bool can_fallocate; + bool can_zeroout; }; /* Create the per-connection handle. */ @@ -129,6 +139,7 @@ static void * file_open (int readonly) { struct handle *h; + struct stat statbuf; int flags; h = malloc (sizeof *h); @@ -150,6 +161,22 @@ file_open (int readonly) return NULL; } + if (fstat (h->fd, &statbuf) == -1) { + nbdkit_error ("fstat: %s: %m", filename); + free (h); + return NULL; + } + + h->is_block_device = S_ISBLK(statbuf.st_mode); + h->sector_size = 4096; /* Start with safe guess */ + +#ifdef BLKSSZGET + if (h->is_block_device) { + if (ioctl (h->fd, BLKSSZGET, &h->sector_size)) + nbdkit_debug ("cannot get sector size: %s: %m", filename); + } +#endif + #ifdef FALLOC_FL_PUNCH_HOLE h->can_punch_hole = true; #else @@ -163,6 +190,7 @@ file_open (int readonly) #endif h->can_fallocate = true; + h->can_zeroout = h->is_block_device; return h; } @@ -184,27 +212,29 @@ static int64_t file_get_size (void *handle) { struct handle *h = handle; - struct stat statbuf; - if (fstat (h->fd, &statbuf) == -1) { - nbdkit_error ("stat: %m"); - return -1; - } - - if (S_ISBLK (statbuf.st_mode)) { + if (h->is_block_device) { + /* Block device, so st_size will not be the true size. */ off_t size; - /* Block device, so st_size will not be the true size. */ size = lseek (h->fd, 0, SEEK_END); if (size == -1) { nbdkit_error ("lseek (to find device size): %m"); return -1; } + return size; - } + } else { + /* Regular file. */ + struct stat statbuf; - /* Else regular file. */ - return statbuf.st_size; + if (fstat (h->fd, &statbuf) == -1) { + nbdkit_error ("fstat: %m"); + return -1; + } + + return statbuf.st_size; + } } static int @@ -333,6 +363,24 @@ file_zero (void *handle, uint32_t count, uint64_t offset, int may_trim) } #endif +#ifdef BLKZEROOUT + /* For aligned range and block device, we can use BLKZEROOUT. */ + if (h->can_zeroout && is_aligned (offset | count, h->sector_size)) { + uint64_t range[2] = {offset, count}; + + r = ioctl (h->fd, BLKZEROOUT, &range); + if (r == 0) + return r; + + if (errno != ENOTTY) { + nbdkit_error ("zero: %m"); + return r; + } + + h->can_zeroout = false; + } +#endif + /* Trigger a fall back to writing */ errno = EOPNOTSUPP; return r; -- 2.17.1