[Libguestfs] [PATCH nbdkit v2 3/3] tmpdisk: Implement this plugin using fileops.

Richard W.M. Jones
Thu, 09 Apr 2020 08:36:31 -0000

This plugin now implements efficient zeroing, pre-fetch and extents,
which should give quite a performance boost.

XXX
On the flip side, it no longer ignores flush and FUA (which we can
ignore because these are temporary disks), which very likely has a
large negative impact on performance.  Fixing this would involve
generalising fileops a little.
---
 plugins/tmpdisk/Makefile.am |   2 +
 plugins/tmpdisk/tmpdisk.c   | 247 +++++-------------------------------
 2 files changed, 32 insertions(+), 217 deletions(-)

diff --git a/plugins/tmpdisk/Makefile.am b/plugins/tmpdisk/Makefile.am
index 20aeb05f..471a0f6a 100644
--- a/plugins/tmpdisk/Makefile.am
+++ b/plugins/tmpdisk/Makefile.am
@@ -57,6 +57,7 @@ nbdkit_tmpdisk_plugin_la_SOURCES = \

 nbdkit_tmpdisk_plugin_la_CPPFLAGS = \
 	-I$(top_srcdir)/include \
+	-I$(top_srcdir)/common/fileops \
 	-I$(top_srcdir)/common/utils \
 	$(NULL)
 nbdkit_tmpdisk_plugin_la_CFLAGS = $(WARNINGS_CFLAGS)
@@ -65,6 +66,7 @@ nbdkit_tmpdisk_plugin_la_LDFLAGS = \
 	-Wl,--version-script=$(top_srcdir)/plugins/plugins.syms \
 	$(NULL)
 nbdkit_tmpdisk_plugin_la_LIBADD = \
+	$(top_builddir)/common/fileops/libfileops.la \
 	$(top_builddir)/common/utils/libutils.la \
 	$(NULL)

diff --git a/plugins/tmpdisk/tmpdisk.c b/plugins/tmpdisk/tmpdisk.c
index 6d8efc08..23f335ac 100644
--- a/plugins/tmpdisk/tmpdisk.c
+++ b/plugins/tmpdisk/tmpdisk.c
@@ -43,13 +43,13 @@
 #include <errno.h>
 #include <assert.h>
 #include <sys/types.h>
-#include <sys/stat.h>
 #include <sys/wait.h>

 #define NBDKIT_API_VERSION 2
 #include <nbdkit-plugin.h>

 #include "cleanup.h"
+#include "fileops.h"
 #include "utils.h"

 static const char *tmpdir = "/var/tmp";
@@ -153,12 +153,6 @@ tmpdisk_config_complete (void)
   "type=ext4|...               The filesystem type.\n" \
   "command=<COMMAND>           Alternate command instead of
mkfs."

-struct handle {
-  int fd;
-  int64_t size;
-  bool can_punch_hole;
-};
-
 /* Multi-conn is absolutely unsafe!  In this callback it is simply
  * returning the default value (no multi-conn), that's to make it
  * clear for future authors.
@@ -169,33 +163,6 @@ tmpdisk_can_multi_conn (void *handle)
   return 0;
 }

-static int
-tmpdisk_can_trim (void *handle)
-{
-#ifdef FALLOC_FL_PUNCH_HOLE
-  return 1;
-#else
-  return 0;
-#endif
-}
-
-/* Pretend we have native FUA support, but actually because all disks
- * are temporary we will deliberately ignore flush/FUA operations.
- */
-static int
-tmpdisk_can_fua (void *handle)
-{
-  return NBDKIT_FUA_NATIVE;
-}
-
-static int64_t
-tmpdisk_get_size (void *handle)
-{
-  struct handle *h = handle;
-
-  return h->size;
-}
-
 /* This creates and runs the full "mkfs" (or whatever) command. */
 static int
 run_command (const char *disk)
@@ -263,37 +230,18 @@ run_command (const char *disk)
   return 0;
 }

-/* For block devices, stat->st_size is not the true size. */
-static int64_t
-block_device_size (int fd)
-{
-  off_t size;
-
-  size = lseek (fd, 0, SEEK_END);
-  if (size == -1) {
-    nbdkit_error ("lseek: %m");
-    return -1;
-  }
-
-  return size;
-}
-
 static void *
 tmpdisk_open (int readonly)
 {
-  struct handle *h;
+  struct fileops *fops;
   CLEANUP_FREE char *dir = NULL, *disk = NULL;
-  int flags;
-  struct stat statbuf;
+  int fd, flags;

-  h = malloc (sizeof *h);
-  if (h == NULL) {
+  fops = malloc (sizeof *fops);
+  if (fops == NULL) {
     nbdkit_error ("malloc: %m");
-    goto error;
+    return NULL;
   }
-  h->fd = -1;
-  h->size = -1;
-  h->can_punch_hole = true;

   /* For security reasons we have to create a temporary directory
    * under tmpdir that only the current user can access.  If we
@@ -303,20 +251,20 @@ tmpdisk_open (int readonly)
    */
   if (asprintf (&dir, "%s/tmpdiskXXXXXX", tmpdir) == -1) {
     nbdkit_error ("asprintf: %m");
-    goto error;
+    goto error0;
   }
   if (mkdtemp (dir) == NULL) {
     nbdkit_error ("%s: %m", dir);
-    goto error;
+    goto error0;
   }
   if (asprintf (&disk, "%s/disk", dir) == -1) {
     nbdkit_error ("asprintf: %m");
-    goto error;
+    goto error1;
   }

   /* Now run the mkfs command. */
   if (run_command (disk) == -1)
-    goto error;
+    goto error2;

   /* The external command must have created the disk, and then we must
    * find the true size.
@@ -325,29 +273,14 @@ tmpdisk_open (int readonly)
     flags = O_RDONLY | O_CLOEXEC;
   else
     flags = O_RDWR | O_CLOEXEC;
-  h->fd = open (disk, flags);
-  if (h->fd == -1) {
+  fd = open (disk, flags);
+  if (fd == -1) {
     nbdkit_error ("open: %s: %m", disk);
-    goto error;
+    goto error2;
   }

-  if (fstat (h->fd, &statbuf) == -1) {
-    nbdkit_error ("fstat: %s: %m", disk);
-    goto error;
-  }
-
-  /* The command could set $disk to a regular file or a block device
-   * (or a symlink to either), so we must check that here.
-   */
-  if (S_ISBLK (statbuf.st_mode)) {
-    h->size = block_device_size (h->fd);
-    if (h->size == -1)
-      goto error;
-  }
-  else                          /* Regular file. */
-    h->size = statbuf.st_size;
-  nbdkit_debug ("tmpdisk: requested_size = %" PRIi64 ", size =
%" PRIi64,
-                requested_size, h->size);
+  if (init_fileops (fd, fops) == -1)
+    goto error3;

   /* We don't need the disk to appear in the filesystem since we hold
    * a file descriptor and access it through that, so unlink the disk.
@@ -357,139 +290,26 @@ tmpdisk_open (int readonly)
   rmdir (dir);

   /* Return the handle. */
-  return h;
+  return fops;

- error:
-  if (h) {
-    if (h->fd >= 0) {
-      close (h->fd);
-      unlink (disk);
-    }
-    free (h);
-  }
+ error3:
+  close (fd);
+ error2:
+  unlink (disk);
+ error1:
+  rmdir (dir);
+ error0:
+  free (fops);
   return NULL;
 }

 static void
 tmpdisk_close (void *handle)
 {
-  struct handle *h = handle;
+  struct fileops *fops = handle;

-  close (h->fd);
-  free (h);
-}
-
-/* Read data from the file. */
-static int
-tmpdisk_pread (void *handle, void *buf,
-               uint32_t count, uint64_t offset,
-               uint32_t flags)
-{
-  struct handle *h = handle;
-
-  while (count > 0) {
-    ssize_t r = pread (h->fd, buf, count, offset);
-    if (r == -1) {
-      nbdkit_error ("pread: %m");
-      return -1;
-    }
-    if (r == 0) {
-      nbdkit_error ("pread: unexpected end of file");
-      return -1;
-    }
-    buf += r;
-    count -= r;
-    offset += r;
-  }
-
-  return 0;
-}
-
-/* Write data to the file. */
-static int
-tmpdisk_pwrite (void *handle, const void *buf,
-                uint32_t count, uint64_t offset,
-                uint32_t flags)
-{
-  struct handle *h = handle;
-
-  while (count > 0) {
-    ssize_t r = pwrite (h->fd, buf, count, offset);
-    if (r == -1) {
-      nbdkit_error ("pwrite: %m");
-      return -1;
-    }
-    buf += r;
-    count -= r;
-    offset += r;
-  }
-
-  /* Deliberately ignore FUA if present in flags. */
-
-  return 0;
-}
-
-/* This plugin deliberately provides a null flush operation, because
- * all of the disks created are temporary.
- */
-static int
-tmpdisk_flush (void *handle, uint32_t flags)
-{
-  return 0;
-}
-
-#if defined (FALLOC_FL_PUNCH_HOLE)
-static int
-do_fallocate (int fd, int mode, off_t offset, off_t len)
-{
-  int r = fallocate (fd, mode, offset, len);
-  if (r == -1 && errno == ENODEV) {
-    /* kernel 3.10 fails with ENODEV for block device. Kernel >= 4.9 fails
-     * with EOPNOTSUPP in this case. Normalize errno to simplify callers.
-     */
-    errno = EOPNOTSUPP;
-  }
-  return r;
-}
-
-static bool
-is_enotsup (int err)
-{
-  return err == ENOTSUP || err == EOPNOTSUPP;
-}
-#endif
-
-/* Punch a hole in the file. */
-static int
-tmpdisk_trim (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
-{
-#ifdef FALLOC_FL_PUNCH_HOLE
-  struct handle *h = handle;
-  int r;
-
-  if (h->can_punch_hole) {
-    r = do_fallocate (h->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
-                      offset, count);
-    if (r == -1) {
-      /* Trim is advisory; we don't care if it fails for anything other
-       * than EIO or EPERM.
-       */
-      if (errno == EPERM || errno == EIO) {
-        nbdkit_error ("fallocate: %m");
-        return -1;
-      }
-
-      if (is_enotsup (EOPNOTSUPP))
-        h->can_punch_hole = false;
-
-      nbdkit_debug ("ignoring failed fallocate during trim: %m");
-    }
-  }
-#endif
-
-  /* Deliberately ignore FUA if present in flags. */
-
-  return 0;
+  close_fileops (fops);
+  free (fops);
 }

 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
@@ -505,19 +325,12 @@ static struct nbdkit_plugin plugin = {
   .config_help       = tmpdisk_config_help,
   .magic_config_key  = "size",

-  .can_multi_conn    = tmpdisk_can_multi_conn,
-  .can_trim          = tmpdisk_can_trim,
-  .can_fua           = tmpdisk_can_fua,
-  .get_size          = tmpdisk_get_size,
-
   .open              = tmpdisk_open,
   .close             = tmpdisk_close,
-  .pread             = tmpdisk_pread,
-  .pwrite            = tmpdisk_pwrite,
-  .flush             = tmpdisk_flush,
-  .trim              = tmpdisk_trim,
+  .can_multi_conn    = tmpdisk_can_multi_conn,

-  .errno_is_preserved = 1,
+  /* Data serving is implemented in common/fileops/fileops.c */
+  FILEOPS_READ_WRITE_CALLBACKS
 };

 NBDKIT_REGISTER_PLUGIN(plugin)
--
2.25.0