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