[Libguestfs] [PATCH nbdkit] filters: Add caching filter.

Richard W.M. Jones rjones at redhat.com
Mon Jan 22 16:12:29 UTC 2018


---
 TODO                                  |   3 +
 configure.ac                          |   1 +
 docs/nbdkit.pod                       |   1 +
 filters/Makefile.am                   |   1 +
 filters/cache/Makefile.am             |  62 ++++
 filters/cache/cache.c                 | 525 ++++++++++++++++++++++++++++++++++
 filters/cache/nbdkit-cache-filter.pod | 122 ++++++++
 tests/Makefile.am                     |   3 +
 tests/test-cache.sh                   |  88 ++++++
 9 files changed, 806 insertions(+)

diff --git a/TODO b/TODO
index a00d4fb..b8ca74c 100644
--- a/TODO
+++ b/TODO
@@ -39,6 +39,9 @@ Suggestions for filters
 
 * injecting artificial errors for testing clients
 
+* nbdkit-cache-filter needs limits on the maximum size of the cache;
+  it could also do with a sensible replacement policy, etc.
+
 Composing nbdkit
 ----------------
 
diff --git a/configure.ac b/configure.ac
index 1091d27..2e69688 100644
--- a/configure.ac
+++ b/configure.ac
@@ -513,6 +513,7 @@ AC_CONFIG_FILES([Makefile
                  plugins/vddk/Makefile
                  plugins/xz/Makefile
                  filters/Makefile
+                 filters/cache/Makefile
                  filters/cow/Makefile
                  filters/delay/Makefile
                  filters/offset/Makefile
diff --git a/docs/nbdkit.pod b/docs/nbdkit.pod
index c7a1bd7..7674138 100644
--- a/docs/nbdkit.pod
+++ b/docs/nbdkit.pod
@@ -898,6 +898,7 @@ L<nbdkit-xz-plugin(1)>.
 
 Filters:
 
+L<nbdkit-cache-filter(1)>,
 L<nbdkit-cow-filter(1)>,
 L<nbdkit-delay-filter(1)>,
 L<nbdkit-offset-filter(1)>,
diff --git a/filters/Makefile.am b/filters/Makefile.am
index 7e6fe5a..9996d77 100644
--- a/filters/Makefile.am
+++ b/filters/Makefile.am
@@ -31,6 +31,7 @@
 # SUCH DAMAGE.
 
 SUBDIRS = \
+	cache \
 	cow \
 	delay \
 	offset \
diff --git a/filters/cache/Makefile.am b/filters/cache/Makefile.am
new file mode 100644
index 0000000..a371c37
--- /dev/null
+++ b/filters/cache/Makefile.am
@@ -0,0 +1,62 @@
+# nbdkit
+# Copyright (C) 2018 Red Hat Inc.
+# All rights reserved.
+#
+# 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.
+
+EXTRA_DIST = nbdkit-cache-filter.pod
+
+CLEANFILES = *~
+
+filterdir = $(libdir)/nbdkit/filters
+
+filter_LTLIBRARIES = nbdkit-cache-filter.la
+
+nbdkit_cache_filter_la_SOURCES = \
+	cache.c \
+	$(top_srcdir)/include/nbdkit-filter.h
+
+nbdkit_cache_filter_la_CPPFLAGS = \
+	-I$(top_srcdir)/include
+nbdkit_cache_filter_la_CFLAGS = \
+	$(WARNINGS_CFLAGS)
+nbdkit_cache_filter_la_LDFLAGS = \
+	-module -avoid-version -shared
+
+if HAVE_POD2MAN
+
+man_MANS = nbdkit-cache-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-cache-filter.1: nbdkit-cache-filter.pod
+	$(POD2MAN) $(POD2MAN_ARGS) --section=1 --name=`basename $@ .1` $< $@.t && \
+	if grep 'POD ERROR' $@.t; then rm $@.t; exit 1; fi && \
+	mv $@.t $@
+
+endif
diff --git a/filters/cache/cache.c b/filters/cache/cache.c
new file mode 100644
index 0000000..7410f0d
--- /dev/null
+++ b/filters/cache/cache.c
@@ -0,0 +1,525 @@
+/* nbdkit
+ * Copyright (C) 2018 Red Hat Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+#include <alloca.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <nbdkit-filter.h>
+
+#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
+
+/* XXX See design comment in filters/cow/cow.c. */
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS
+
+/* Size of a block in the cache.  A 4K block size means that we need
+ * 64 MB of memory to store the bitmaps for a 1 TB underlying image.
+ */
+#define BLKSIZE 4096
+
+/* The cache. */
+static int fd = -1;
+
+/* Bitmap.  There are two bits per block which are updated as we read,
+ * write back or write through blocks.
+ *
+ * 00 = not in cache
+ * 01 = block cached and clean
+ * 10 = <unused>
+ * 11 = block cached and dirty
+ */
+static uint8_t *bitmap;
+
+/* Size of the bitmap in bytes. */
+static uint64_t bm_size;
+
+enum bm_entry {
+  BLOCK_NOT_CACHED = 0,
+  BLOCK_CLEAN = 1,
+  BLOCK_DIRTY = 3,
+};
+
+/* Caching mode. */
+static enum cache_mode {
+  CACHE_MODE_WRITEBACK,
+  CACHE_MODE_WRITETHROUGH,
+  CACHE_MODE_UNSAFE,
+} cache_mode = CACHE_MODE_WRITEBACK;
+
+static void
+cache_load (void)
+{
+  const char *tmpdir;
+  size_t len;
+  char *template;
+
+  tmpdir = getenv ("TMPDIR");
+  if (!tmpdir)
+    tmpdir = "/var/tmp";
+
+  nbdkit_debug ("cache: temporary directory for cache: %s", tmpdir);
+
+  len = strlen (tmpdir) + 8;
+  template = alloca (len);
+  snprintf (template, len, "%s/XXXXXX", tmpdir);
+
+  fd = mkostemp (template, O_CLOEXEC);
+  if (fd == -1) {
+    nbdkit_error ("mkostemp: %s: %m", tmpdir);
+    exit (EXIT_FAILURE);
+  }
+
+  unlink (template);
+}
+
+static void
+cache_unload (void)
+{
+  if (fd >= 0)
+    close (fd);
+}
+
+static int
+cache_config (nbdkit_next_config *next, void *nxdata,
+              const char *key, const char *value)
+{
+  if (strcmp (key, "cache") == 0) {
+    if (strcmp (value, "writeback") == 0) {
+      cache_mode = CACHE_MODE_WRITEBACK;
+      return 0;
+    }
+    else if (strcmp (value, "writethrough") == 0) {
+      cache_mode = CACHE_MODE_WRITETHROUGH;
+      return 0;
+    }
+    else if (strcmp (value, "unsafe") == 0) {
+      cache_mode = CACHE_MODE_UNSAFE;
+      return 0;
+    }
+    else {
+      nbdkit_error ("invalid cache parameter, should be writeback|writethrough|unsafe");
+      return -1;
+    }
+  }
+  else {
+    return next (nxdata, key, value);
+  }
+}
+
+static void *
+cache_open (nbdkit_next_open *next, void *nxdata, int readonly)
+{
+  /* We don't use the handle, so this just provides a non-NULL
+   * pointer that we can return.
+   */
+  static int handle;
+
+  if (next (nxdata, readonly) == -1)
+    return NULL;
+
+  return &handle;
+}
+
+/* Allocate or resize the cache file and bitmap. */
+static int
+blk_set_size (uint64_t new_size)
+{
+  uint8_t *new_bm;
+  const size_t old_bm_size = bm_size;
+  size_t new_bm_size = DIV_ROUND_UP (new_size, BLKSIZE*8/2);
+
+  new_bm = realloc (bitmap, new_bm_size);
+  if (new_bm == NULL) {
+    nbdkit_error ("realloc: %m");
+    return -1;
+  }
+  bitmap = new_bm;
+  bm_size = new_bm_size;
+  if (old_bm_size < new_bm_size)
+    memset (&bitmap[old_bm_size], 0, new_bm_size-old_bm_size);
+
+  nbdkit_debug ("cache: bitmap resized to %" PRIu64 " bytes", new_bm_size);
+
+  if (ftruncate (fd, new_size) == -1) {
+    nbdkit_error ("ftruncate: %m");
+    return -1;
+  }
+
+  return 0;
+}
+
+/* Get the file size and ensure the cache is the correct size. */
+static int64_t
+cache_get_size (struct nbdkit_next_ops *next_ops, void *nxdata,
+              void *handle)
+{
+  int64_t size;
+
+  size = next_ops->get_size (nxdata);
+  if (size == -1)
+    return -1;
+
+  nbdkit_debug ("cache: underlying file size: %" PRIi64, size);
+
+  if (blk_set_size (size))
+    return -1;
+
+  return size;
+}
+
+/* Force an early call to cache_get_size, consequently truncating the
+ * cache to the correct size.
+ */
+static int
+cache_prepare (struct nbdkit_next_ops *next_ops, void *nxdata,
+               void *handle)
+{
+  int64_t r;
+
+  r = cache_get_size (next_ops, nxdata, handle);
+  return r >= 0 ? 0 : -1;
+}
+
+/* Return true if the block is allocated.  Consults the bitmap. */
+static enum bm_entry
+blk_get_bitmap_entry (uint64_t blknum)
+{
+  uint64_t bm_offset = blknum / 4;
+  uint64_t bm_bit = 2 * (blknum % 4);
+
+  if (bm_offset >= bm_size) {
+    nbdkit_debug ("blk_get_bitmap_entry: block number is out of range");
+    return BLOCK_NOT_CACHED;
+  }
+
+  return (bitmap[bm_offset] & (3 << bm_bit)) >> bm_bit;
+}
+
+/* Update cache state of a block. */
+static void
+blk_set_bitmap_entry (uint64_t blknum, enum bm_entry state)
+{
+  uint64_t bm_offset = blknum / 4;
+  uint64_t bm_bit = 2 * (blknum % 4);
+
+  if (bm_offset >= bm_size) {
+    nbdkit_debug ("blk_set_bitmap_entry: block number is out of range");
+    return;
+  }
+
+  bitmap[bm_offset] |= (unsigned) state << bm_bit;
+}
+
+/* These are the block operations.  They always read or write a single
+ * whole block of size ‘blksize’.
+ */
+static int
+blk_read (struct nbdkit_next_ops *next_ops, void *nxdata,
+          uint64_t blknum, uint8_t *block)
+{
+  off_t offset = blknum * BLKSIZE;
+  enum bm_entry state = blk_get_bitmap_entry (blknum);
+
+  nbdkit_debug ("cache: blk_read block %" PRIu64 " (offset %" PRIu64 ") is %s",
+                blknum, (uint64_t) offset,
+                state == BLOCK_NOT_CACHED ? "not cached" :
+                state == BLOCK_CLEAN ? "clean" :
+                state == BLOCK_DIRTY ? "dirty" :
+                "unknown");
+
+  if (state == BLOCK_NOT_CACHED) /* Read underlying plugin. */
+    return next_ops->pread (nxdata, block, BLKSIZE, offset);
+  else {                         /* Read cache. */
+    if (pread (fd, block, BLKSIZE, offset) == -1) {
+      nbdkit_error ("pread: %m");
+      return -1;
+    }
+    return 0;
+  }
+}
+
+/* Write to the cache and the plugin. */
+static int
+blk_writethrough (struct nbdkit_next_ops *next_ops, void *nxdata,
+                  uint64_t blknum, const uint8_t *block)
+{
+  off_t offset = blknum * BLKSIZE;
+
+  nbdkit_debug ("cache: blk_writethrough block %" PRIu64
+                " (offset %" PRIu64 ")",
+                blknum, (uint64_t) offset);
+
+  if (pwrite (fd, block, BLKSIZE, offset) == -1) {
+    nbdkit_error ("pwrite: %m");
+    return -1;
+  }
+
+  if (next_ops->pwrite (nxdata, block, BLKSIZE, offset) == -1)
+    return -1;
+
+  blk_set_bitmap_entry (blknum, BLOCK_CLEAN);
+
+  return 0;
+}
+
+/* Write to the cache only. */
+static int
+blk_writeback (struct nbdkit_next_ops *next_ops, void *nxdata,
+               uint64_t blknum, const uint8_t *block)
+{
+  off_t offset;
+
+  if (cache_mode == CACHE_MODE_WRITETHROUGH)
+    return blk_writethrough (next_ops, nxdata, blknum, block);
+
+  offset = blknum * BLKSIZE;
+
+  nbdkit_debug ("cache: blk_writeback block %" PRIu64
+                " (offset %" PRIu64 ")",
+                blknum, (uint64_t) offset);
+
+  if (pwrite (fd, block, BLKSIZE, offset) == -1) {
+    nbdkit_error ("pwrite: %m");
+    return -1;
+  }
+  blk_set_bitmap_entry (blknum, BLOCK_DIRTY);
+
+  return 0;
+}
+
+/* Read data. */
+static int
+cache_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
+             void *handle, void *buf, uint32_t count, uint64_t offset)
+{
+  uint8_t *block;
+
+  block = malloc (BLKSIZE);
+  if (block == NULL) {
+    nbdkit_error ("malloc: %m");
+    return -1;
+  }
+
+  while (count > 0) {
+    uint64_t blknum, blkoffs, n;
+
+    blknum = offset / BLKSIZE;  /* block number */
+    blkoffs = offset % BLKSIZE; /* offset within the block */
+    n = BLKSIZE - blkoffs;      /* max bytes we can read from this block */
+    if (n > count)
+      n = count;
+
+    if (blk_read (next_ops, nxdata, blknum, block) == -1) {
+      free (block);
+      return -1;
+    }
+
+    memcpy (buf, &block[blkoffs], n);
+
+    buf += n;
+    count -= n;
+    offset += n;
+  }
+
+  free (block);
+  return 0;
+}
+
+/* Write data. */
+static int
+cache_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata,
+              void *handle, const void *buf, uint32_t count, uint64_t offset)
+{
+  uint8_t *block;
+
+  block = malloc (BLKSIZE);
+  if (block == NULL) {
+    nbdkit_error ("malloc: %m");
+    return -1;
+  }
+
+  while (count > 0) {
+    uint64_t blknum, blkoffs, n;
+
+    blknum = offset / BLKSIZE;  /* block number */
+    blkoffs = offset % BLKSIZE; /* offset within the block */
+    n = BLKSIZE - blkoffs;      /* max bytes we can read from this block */
+    if (n > count)
+      n = count;
+
+    /* Do a read-modify-write operation on the current block. */
+    if (blk_read (next_ops, nxdata, blknum, block) == -1) {
+      free (block);
+      return -1;
+    }
+    memcpy (&block[blkoffs], buf, n);
+    if (blk_writeback (next_ops, nxdata, blknum, block) == -1) {
+      free (block);
+      return -1;
+    }
+
+    buf += n;
+    count -= n;
+    offset += n;
+  }
+
+  free (block);
+  return 0;
+}
+
+/* Zero data. */
+static int
+cache_zero (struct nbdkit_next_ops *next_ops, void *nxdata,
+            void *handle, uint32_t count, uint64_t offset, int may_trim)
+{
+  uint8_t *block;
+
+  block = malloc (BLKSIZE);
+  if (block == NULL) {
+    nbdkit_error ("malloc: %m");
+    return -1;
+  }
+
+  while (count > 0) {
+    uint64_t blknum, blkoffs, n;
+
+    blknum = offset / BLKSIZE;  /* block number */
+    blkoffs = offset % BLKSIZE; /* offset within the block */
+    n = BLKSIZE - blkoffs;      /* max bytes we can read from this block */
+    if (n > count)
+      n = count;
+
+    if (blk_read (next_ops, nxdata, blknum, block) == -1) {
+      free (block);
+      return -1;
+    }
+    memset (&block[blkoffs], 0, n);
+    if (blk_writeback (next_ops, nxdata, blknum, block) == -1) {
+      free (block);
+      return -1;
+    }
+
+    count -= n;
+    offset += n;
+  }
+
+  free (block);
+  return 0;
+}
+
+/* Flush: Go through all the dirty blocks, flushing them to disk. */
+static int
+cache_flush (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+  uint8_t *block = NULL;
+  uint64_t i, j;
+  uint64_t blknum;
+  enum bm_entry state;
+  unsigned errors = 0;
+
+  if (cache_mode == CACHE_MODE_UNSAFE)
+    return 0;
+
+  /* In theory if cache_mode == CACHE_MODE_WRITETHROUGH then there
+   * should be no dirty blocks.  However we go through the cache here
+   * to be sure.  Also we still need to issue the flush to the
+   * underlying storage.
+   */
+
+  for (i = 0; i < bm_size; ++i) {
+    if (bitmap[i] != 0) {
+      /* The bitmap stores information about 4 blocks per byte,
+       * therefore ...
+       */
+      for (j = 0; j < 4; ++j) {
+        blknum = i*4+j;
+        state = blk_get_bitmap_entry (blknum);
+        if (state == BLOCK_DIRTY) {
+          /* Lazily allocate the bounce buffer. */
+          if (!block) {
+            block = malloc (BLKSIZE);
+            if (block == NULL) {
+              nbdkit_error ("malloc: %m");
+              return -1;
+            }
+          }
+          /* Perform a read + writethrough which will read from the
+           * cache and write it through to the underlying storage.
+           */
+          if (blk_read (next_ops, nxdata, blknum, block) == -1 ||
+              blk_writethrough (next_ops, nxdata, blknum, block)) {
+            nbdkit_error ("cache: flush of block %" PRIu64 " failed", blknum);
+            errors++;
+          }
+        }
+      }
+    }
+  }
+
+  free (block);
+
+  /* Now issue a flush request to the underlying storage. */
+  if (next_ops->flush (nxdata) == -1)
+    errors++;
+
+  return errors == 0 ? 0 : -1;
+}
+
+static struct nbdkit_filter filter = {
+  .name              = "cache",
+  .longname          = "nbdkit caching filter",
+  .version           = PACKAGE_VERSION,
+  .load              = cache_load,
+  .unload            = cache_unload,
+  .config            = cache_config,
+  .open              = cache_open,
+  .prepare           = cache_prepare,
+  .get_size          = cache_get_size,
+  .pread             = cache_pread,
+  .pwrite            = cache_pwrite,
+  .zero              = cache_zero,
+  .flush             = cache_flush,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
diff --git a/filters/cache/nbdkit-cache-filter.pod b/filters/cache/nbdkit-cache-filter.pod
new file mode 100644
index 0000000..a59c54e
--- /dev/null
+++ b/filters/cache/nbdkit-cache-filter.pod
@@ -0,0 +1,122 @@
+=encoding utf8
+
+=head1 NAME
+
+nbdkit-cache-filter - nbdkit caching filter
+
+=head1 SYNOPSIS
+
+ nbdkit --filter=cache plugin [cache=writeback|writethrough|unsafe]
+                              [plugin-args...]
+
+=head1 DESCRIPTION
+
+C<nbdkit-cache-filter> is a filter that adds caching on top of a
+plugin.  This is useful if a plugin is slow or expensive to use,
+because nbdkit will try to minimize requests to the plugin by caching
+previous requests.
+
+Note that many NBD I<clients> are able to do caching, and because the
+caching happens on the client side it will usually be more effective
+than caching inside the server.  This filter can be used if the client
+does not have effective caching, or (with C<cache=unsafe>) to defeat
+flush requests from the client (which is unsafe and can cause data
+loss, as the name suggests).
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<cache=writeback>
+
+Store writes in the cache.  They are not written to the plugin unless
+an explicit flush is done by the client.
+
+This is the default caching mode, and is safe if your client issues
+flush requests correctly (which is true for modern Linux and other
+well-written NBD clients).
+
+=item B<cache=writethrough>
+
+Always force writes through to the plugin.
+
+This makes the cache less effective, but is necessary if your client
+does not issue correct flush requests.
+
+=item B<cache=unsafe>
+
+Ignore flush requests.  Never write to the plugin unless the cache
+grows too large.
+
+This is dangerous and can cause data loss, but this may be acceptable
+if you only use it for testing or with data that you don't care about
+or can cheaply reconstruct.
+
+=back
+
+=head1 ENVIRONMENT VARIABLES
+
+=over 4
+
+=item C<TMPDIR>
+
+The cache is stored in a temporary file located in C</var/tmp> by
+default.  You can override this location by setting the C<TMPDIR>
+environment variable before starting nbdkit.
+
+=back
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-file-plugin(1)>,
+L<nbdkit-filter(3)>,
+L<qemu-img(1)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2018 Red Hat Inc.
+
+=head1 LICENSE
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+=over 4
+
+=item *
+
+Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+=item *
+
+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.
+
+=item *
+
+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.
+
+=back
+
+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.
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b073f22..950f711 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -412,6 +412,9 @@ endif HAVE_RUBY
 #----------------------------------------------------------------------
 # Tests of filters.
 
+# cache filter test.
+TESTS += test-cache.sh
+
 # cow filter test.
 TESTS += test-cow.sh
 
diff --git a/tests/test-cache.sh b/tests/test-cache.sh
new file mode 100755
index 0000000..c950236
--- /dev/null
+++ b/tests/test-cache.sh
@@ -0,0 +1,88 @@
+#!/bin/bash -
+# nbdkit
+# Copyright (C) 2018 Red Hat Inc.
+# All rights reserved.
+#
+# 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.
+
+set -e
+
+files="cache.img cache.sock cache.pid"
+rm -f $files
+
+# Create an empty base image.
+truncate -s 1G cache.img
+
+# Run nbdkit with the caching filter.
+nbdkit -P cache.pid -U cache.sock --filter cache file file=cache.img
+
+# We may have to wait a short time for the pid file to appear.
+for i in `seq 1 10`; do
+    if test -f cache.pid; then
+        break
+    fi
+    sleep 1
+done
+if ! test -f cache.pid; then
+    echo "$0: PID file was not created"
+    exit 1
+fi
+
+pid="$(cat cache.pid)"
+
+# Kill the nbdkit process on exit.
+cleanup ()
+{
+    status=$?
+
+    kill $pid
+    rm -f $files
+
+    exit $status
+}
+trap cleanup INT QUIT TERM EXIT ERR
+
+# Open the overlay and perform some operations.
+guestfish --format=raw -a 'nbd://?socket=cache.sock' <<'EOF'
+  run
+  part-disk /dev/sda gpt
+  mkfs ext4 /dev/sda1
+  mount /dev/sda1 /
+  fill-dir / 10000
+  fill-pattern "abcde" 5M /large
+  write /hello "hello, world"
+EOF
+
+# Check the last files we created exist.
+guestfish --ro -a cache.img -m /dev/sda1 <<'EOF'
+  cat /hello
+  cat /large | cat >/dev/null
+EOF
+
+# The cleanup() function is called implicitly on exit.
-- 
2.15.1




More information about the Libguestfs mailing list