fallocate(FALLOC_FL_ZERO_RANGE) is supportd for block devices with modern kernel, but when it is not, we fall back to manual zeroing. Check if the underlying file is a block device when opening the file, and fall back to ioctl(BLKZEROOUT) for aligned zero requests for a block device. 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/file.c | 68 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/plugins/file/file.c b/plugins/file/file.c index aa05492..8877db2 100644 --- a/plugins/file/file.c +++ b/plugins/file/file.c @@ -45,6 +45,7 @@ #if defined(__linux__) && !defined(FALLOC_FL_PUNCH_HOLE) #include <linux/falloc.h> /* For FALLOC_FL_*, glibc < 2.18 */ +#include <linux/fs.h> /* For BLKZEROOUT */ #endif #include <nbdkit-plugin.h> @@ -119,16 +120,25 @@ 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; }; +static bool +is_aligned(struct handle *h, uint64_t n) +{ + return n % h->sector_size == 0; +} + /* Create the per-connection handle. */ static void * file_open (int readonly) { struct handle *h; + struct stat statbuf; int flags; h = malloc (sizeof *h); @@ -150,6 +160,26 @@ 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); + +#ifdef BLKSSZGET + if (h->is_block_device) { + if (ioctl (h->fd, BLKSSZGET, &h->sector_size)) { + nbdkit_error ("ioctl(BLKSSZGET): %s: %m", filename); + free (h); + return NULL; + } + } +#else + h->sector_size = 4096; /* Safe guess */ +#endif + #ifdef FALLOC_FL_PUNCH_HOLE h->can_punch_hole = true; #else @@ -184,27 +214,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; + + if (fstat (h->fd, &statbuf) == -1) { + nbdkit_error ("fstat: %m"); + return -1; + } - /* Else regular file. */ - return statbuf.st_size; + return statbuf.st_size; + } } /* Trim is advisory, but we prefer to advertise it only when we can actually @@ -329,6 +361,20 @@ file_zero (void *handle, uint32_t count, uint64_t offset, int may_trim) } #endif +#ifdef BLKZEROOUT + /* For aligned range and block devices, we can use BLKZEROOUT. */ + if (h->is_block_device && is_aligned (h, offset) && is_aligned (h, count)) { + uint64_t range[2] = {offset, count}; + + r = ioctl (h->fd, BLKZEROOUT, &range); + if (r == 0) + return r; + + nbdkit_error ("zero: %m"); + return r; + } +#endif + /* Trigger a fall back to writing */ errno = EOPNOTSUPP; return r; -- 2.17.1