[Libguestfs] [PATCH nbdkit] filters: Add copy-on-write filter.

Richard W.M. Jones rjones at redhat.com
Sat Jan 20 16:58:44 UTC 2018


---
 configure.ac                      |   5 +
 filters/Makefile.am               |   4 +
 filters/cow/Makefile.am           |  65 +++++++
 filters/cow/cow.c                 | 392 ++++++++++++++++++++++++++++++++++++++
 filters/cow/nbdkit-cow-filter.pod | 162 ++++++++++++++++
 tests/Makefile.am                 |   6 +
 tests/test-cow.sh                 |  98 ++++++++++
 7 files changed, 732 insertions(+)

diff --git a/configure.ac b/configure.ac
index 367b2ba..aa7f406 100644
--- a/configure.ac
+++ b/configure.ac
@@ -483,6 +483,10 @@ AC_SUBST([VDDK_LIBS])
 AC_DEFINE_UNQUOTED([VDDK_LIBDIR],["$VDDK_LIBDIR"],[VDDK 'libDir'.])
 AM_CONDITIONAL([HAVE_VDDK],[test "x$VDDK_LIBS" != "x"])
 
+dnl Check for <linux/fs.h>, optional but needed for COW filter.
+AC_CHECK_HEADER([linux/fs.h], [have_linux_fs_h=yes])
+AM_CONDITIONAL([HAVE_COW_FILTER], [test "x$have_linux_fs_h" = "xyes"])
+
 dnl Produce output files.
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_FILES([nbdkit],
@@ -513,6 +517,7 @@ AC_CONFIG_FILES([Makefile
                  plugins/vddk/Makefile
                  plugins/xz/Makefile
                  filters/Makefile
+                 filters/cow/Makefile
                  filters/delay/Makefile
                  filters/offset/Makefile
                  filters/partition/Makefile
diff --git a/filters/Makefile.am b/filters/Makefile.am
index d918b81..15a1995 100644
--- a/filters/Makefile.am
+++ b/filters/Makefile.am
@@ -34,3 +34,7 @@ SUBDIRS = \
 	delay \
 	offset \
 	partition
+
+if HAVE_COW_FILTER
+SUBDIRS += cow
+endif
diff --git a/filters/cow/Makefile.am b/filters/cow/Makefile.am
new file mode 100644
index 0000000..5b8ae5a
--- /dev/null
+++ b/filters/cow/Makefile.am
@@ -0,0 +1,65 @@
+# 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-cow-filter.pod
+
+if HAVE_COW_FILTER
+
+CLEANFILES = *~
+
+filterdir = $(libdir)/nbdkit/filters
+
+filter_LTLIBRARIES = nbdkit-cow-filter.la
+
+nbdkit_cow_filter_la_SOURCES = \
+	cow.c \
+	$(top_srcdir)/include/nbdkit-filter.h
+
+nbdkit_cow_filter_la_CPPFLAGS = \
+	-I$(top_srcdir)/include
+nbdkit_cow_filter_la_CFLAGS = \
+	$(WARNINGS_CFLAGS)
+nbdkit_cow_filter_la_LDFLAGS = \
+	-module -avoid-version -shared
+
+if HAVE_POD2MAN
+
+man_MANS = nbdkit-cow-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-cow-filter.1: nbdkit-cow-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
+endif
diff --git a/filters/cow/cow.c b/filters/cow/cow.c
new file mode 100644
index 0000000..6744772
--- /dev/null
+++ b/filters/cow/cow.c
@@ -0,0 +1,392 @@
+/* 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.
+ */
+
+/* Notes on the design and implementation of this filter:
+ *
+ * The filter works by creating a large, sparse temporary file, the
+ * same size as the underlying device.  Being sparse, initially this
+ * takes up no space.
+ *
+ * We confine all pread/pwrite operations to the filesystem block
+ * size.  The blk_read and blk_write functions below always happen on
+ * whole filesystem block boundaries.  A smaller-than-block-size
+ * pwrite will turn into a read-modify-write of a whole block.  We
+ * also assume that the plugin returns the same immutable data for
+ * each pread call we make, and optimize on this basis.
+ *
+ * When reading a block we first check the temporary file to see if
+ * that file block is allocated or a hole.  If allocated, we return it
+ * from the temporary file.  If a hole, we issue a pread to the
+ * underlying plugin.
+ *
+ * When writing a block we unconditionally write the data to the
+ * temporary file (allocating a block in that file if it wasn't
+ * before).
+ *
+ * No locking is needed for blk_* calls, but there is a potential
+ * problem of multiple pwrite calls are doing a read-modify-write
+ * cycle because the last write would win, erasing earlier writes.  To
+ * avoid this we limit the thread model to SERIALIZE_ALL_REQUESTS so
+ * that there cannot be concurrent pwrite requests.  We could relax
+ * this restriction with a bit of work.
+ */
+
+#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 <linux/fs.h>
+
+#include <nbdkit-filter.h>
+
+/* XXX See design comment above. */
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS
+
+/* The temporary overlay. */
+static int fd = -1;
+
+/* The filesystem block size. */
+static int blksize;
+
+/* Because all requests are serialized, we can use a globally
+ * allocated block as a temporary place to store blocks when
+ * reading and writing, instead of stack allocation or awkward
+ * temporary mallocs.
+ */
+static uint8_t *block;
+
+static void
+cow_load (void)
+{
+  const char *tmpdir;
+  size_t len;
+  char *template;
+
+  tmpdir = getenv ("TMPDIR");
+  if (!tmpdir)
+    tmpdir = "/var/tmp";
+
+  nbdkit_debug ("cow: temporary directory for overlay: %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);
+
+  if (ioctl (fd, FIGETBSZ, &blksize) == -1) {
+    nbdkit_error ("ioctl: FIGETBSZ: %m");
+    exit (EXIT_FAILURE);
+  }
+  if (blksize <= 0) {
+    nbdkit_error ("filesystem block size is < 0 or cannot be read");
+    exit (EXIT_FAILURE);
+  }
+
+  nbdkit_debug ("cow: filesystem block size: %d", blksize);
+
+  block = malloc (blksize);
+  if (block == NULL) {
+    nbdkit_error ("malloc: %m");
+    exit (EXIT_FAILURE);
+  }
+}
+
+static void
+cow_unload (void)
+{
+  if (fd >= 0)
+    close (fd);
+}
+
+static void *
+cow_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;
+
+  /* Always pass readonly=1 to the underlying plugin. */
+  if (next (nxdata, 1) == -1)
+    return NULL;
+
+  return &handle;
+}
+
+/* Get the file size and ensure the overlay is the correct size. */
+static int64_t
+cow_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;
+
+  if (ftruncate (fd, size) == -1)
+    return -1;
+
+  nbdkit_debug ("cow: underlying file size: %" PRIi64, size);
+
+  return size;
+}
+
+/* Force an early call to cow_get_size, consequently truncating the
+ * overlay to the correct size.
+ */
+static int
+cow_prepare (struct nbdkit_next_ops *next_ops, void *nxdata,
+             void *handle)
+{
+  int64_t r;
+
+  r = cow_get_size (next_ops, nxdata, handle);
+  return r >= 0 ? 0 : -1;
+}
+
+/* Whatever the underlying plugin can or can't do, we can write, we
+ * cannot trim, and we can flush.
+ */
+static int
+cow_can_write (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+  return 1;
+}
+
+static int
+cow_can_trim (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+  return 0;
+}
+
+static int
+cow_can_flush (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+  return 1;
+}
+
+/* These are the block operations.  Note they implicitly read or write
+ * into the global ‘block’ array.
+ */
+static int
+blk_read (struct nbdkit_next_ops *next_ops, void *nxdata, uint64_t blknum)
+{
+  off_t offset = blknum * blksize, roffset;
+  bool hole;
+
+  nbdkit_debug ("cow: blk_read block %" PRIu64 " (offset %" PRIu64 ")",
+                blknum, (uint64_t) offset);
+
+  /* Find out if the current block contains data or is a hole. */
+  roffset = lseek (fd, offset, SEEK_DATA);
+  if (roffset == -1) {
+    /* Undocumented?  Anyway if SEEK_DATA returns ENXIO it means
+     * "there are no more data regions past the supplied offset", ie.
+     * we're in a hole.
+     */
+    if (errno == ENXIO)
+      hole = true;
+    else {
+      nbdkit_error ("lseek: SEEK_DATA: %m");
+      return -1;
+    }
+  }
+  else
+    hole = offset != roffset;
+
+  nbdkit_debug ("cow: block %" PRIu64 " is %s",
+                blknum, hole ? "a hole" : "allocated");
+
+  if (hole)                     /* Read underlying plugin. */
+    return next_ops->pread (nxdata, block, blksize, offset);
+  else {                        /* Read overlay. */
+    if (pread (fd, block, blksize, offset) == -1) {
+      nbdkit_error ("pread: %m");
+      return -1;
+    }
+    return 0;
+  }
+}
+
+static int
+blk_write (uint64_t blknum)
+{
+  off_t offset = blknum * blksize;
+
+  nbdkit_debug ("cow: blk_write block %" PRIu64 " (offset %" PRIu64 ")",
+                blknum, (uint64_t) offset);
+
+  if (pwrite (fd, block, blksize, offset) == -1) {
+    nbdkit_error ("pwrite: %m");
+    return -1;
+  }
+  return 0;
+}
+
+/* Read data. */
+static int
+cow_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
+           void *handle, void *buf, uint32_t count, uint64_t offset)
+{
+  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) == -1)
+      return -1;
+
+    memcpy (buf, &block[blkoffs], n);
+
+    buf += n;
+    count -= n;
+    offset += n;
+  }
+
+  return 0;
+}
+
+/* Write data. */
+static int
+cow_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata,
+            void *handle, const void *buf, uint32_t count, uint64_t offset)
+{
+  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) == -1)
+      return -1;
+    memcpy (&block[blkoffs], buf, n);
+    if (blk_write (blknum) == -1)
+      return -1;
+
+    buf += n;
+    count -= n;
+    offset += n;
+  }
+
+  return 0;
+}
+
+/* Zero data. */
+static int
+cow_zero (struct nbdkit_next_ops *next_ops, void *nxdata,
+          void *handle, uint32_t count, uint64_t offset, int may_trim)
+{
+  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;
+
+    /* XXX There is the possibility of optimizing this: ONLY if we are
+     * writing a whole, aligned block, then use FALLOC_FL_ZERO_RANGE.
+     */
+    if (blk_read (next_ops, nxdata, blknum) == -1)
+      return -1;
+    memset (&block[blkoffs], 0, n);
+    if (blk_write (blknum) == -1)
+      return -1;
+
+    count -= n;
+    offset += n;
+  }
+
+  return 0;
+}
+
+static int
+cow_flush (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle)
+{
+  /* I think we don't care about file metadata for this temporary
+   * file, so only flush the data.
+   */
+  if (fdatasync (fd) == -1) {
+    nbdkit_error ("fdatasync: %m");
+    return -1;
+  }
+
+  return 0;
+}
+
+static struct nbdkit_filter filter = {
+  .name              = "cow",
+  .longname          = "nbdkit copy-on-write (COW) filter",
+  .version           = PACKAGE_VERSION,
+  .load              = cow_load,
+  .unload            = cow_unload,
+  .open              = cow_open,
+  .prepare           = cow_prepare,
+  .get_size          = cow_get_size,
+  .can_write         = cow_can_write,
+  .can_flush         = cow_can_flush,
+  .can_trim          = cow_can_trim,
+  .pread             = cow_pread,
+  .pwrite            = cow_pwrite,
+  .zero              = cow_zero,
+  .flush             = cow_flush,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
diff --git a/filters/cow/nbdkit-cow-filter.pod b/filters/cow/nbdkit-cow-filter.pod
new file mode 100644
index 0000000..accf81c
--- /dev/null
+++ b/filters/cow/nbdkit-cow-filter.pod
@@ -0,0 +1,162 @@
+=encoding utf8
+
+=head1 NAME
+
+nbdkit-cow-filter - nbdkit copy-on-write (COW) filter
+
+=head1 SYNOPSIS
+
+ nbdkit --filter=cow plugin [plugin-args...]
+
+=head1 DESCRIPTION
+
+C<nbdkit-cow-filter> is a filter that makes a temporary writable copy
+on top of a read-only plugin.  It can be used to enable writes for
+plugins which only implement read-only access.  Note that:
+
+=over 4
+
+=item *
+
+B<Anything written is thrown away as soon as nbdkit exits.>
+
+=item *
+
+All connections to the nbdkit instance see the same view of the disk.
+
+This is different from L<nbd-server(1)> where each connection sees its
+own copy-on-write overlay and simply disconnecting the client throws
+that away.  It also allows us to create diffs, see below.
+
+=item *
+
+The plugin is opened read-only (as if the I<-r> flag was passed), but
+you should B<not> pass the I<-r> flag to nbdkit.
+
+=back
+
+Limitations of the filter include:
+
+=over 4
+
+=item *
+
+The underlying file/device must not be resized.
+
+=item *
+
+The underlying plugin must behave “normally”, meaning that it must
+serve the same data to each client.
+
+=back
+
+=head1 PARAMETERS
+
+There are no parameters specific to nbdkit-cow-filter.  Any parameters
+are passed through to and processed by the underlying plugin in the
+normal way.
+
+=head1 EXAMPLES
+
+Serve the file F<disk.img>, allowing writes, but do not save any
+changes into the file:
+
+ nbdkit --filter=cow file file=disk.img
+
+L<nbdkit-xz-plugin(1)> only supports read access, but you can provide
+temporary write access by doing (although this does B<not> save
+changes to the file):
+
+ nbdkit --filter=cow xz file=disk.xz
+
+=head1 CREATING A DIFF WITH QEMU-IMG
+
+Although nbdkit-cow-filter itself cannot save the differences, it is
+possible to do this using an obscure feature of L<qemu-img(1)>.
+B<nbdkit must remain continuously running during the whole operation,
+otherwise all changes will be lost>.
+
+Run nbdkit:
+
+ nbdkit --filter=cow file file=disk.img
+
+and then connect with a client and make whatever changes you need.
+At the end, disconnect the client.
+
+Run these C<qemu-img> commands to construct a qcow2 file containing
+the differences:
+
+ qemu-img create -f qcow2 -b nbd:localhost diff.qcow2
+ qemu-img rebase -b disk.img diff.qcow2
+
+F<diff.qcow2> now contains the differences between the base
+(F<disk.img>) and the changes stored in nbdkit-cow-filter.  C<nbdkit>
+can now be killed.
+
+=head1 ENVIRONMENT VARIABLES
+
+=over 4
+
+=item C<TMPDIR>
+
+The copy-on-write changes are 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-xz-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 a3029a7..ae22801 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -41,6 +41,7 @@ EXTRA_DIST = \
 	shebang.py \
 	shebang.rb \
 	test-captive.sh \
+	test-cow.sh \
 	test-cxx.sh \
 	test-dump-config.sh \
 	test-dump-plugin.sh \
@@ -411,6 +412,11 @@ endif HAVE_RUBY
 #----------------------------------------------------------------------
 # Tests of filters.
 
+# cow filter test.
+if HAVE_COW_FILTER
+TESTS += test-cow.sh
+endif HAVE_COW_FILTER
+
 # delay filter test.
 check_PROGRAMS += test-delay
 TESTS += test-delay
diff --git a/tests/test-cow.sh b/tests/test-cow.sh
new file mode 100755
index 0000000..ea4e5c0
--- /dev/null
+++ b/tests/test-cow.sh
@@ -0,0 +1,98 @@
+#!/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="cow-base.img cow-diff.qcow2 cow.sock cow.pid"
+rm -f $files
+
+# Create a base image which is partitioned with an empty filesystem.
+guestfish -N cow-base.img=fs exit
+lastmod="$(stat -c "%y" cow-base.img)"
+
+# Run nbdkit with a COW overlay.
+nbdkit -P cow.pid -U cow.sock --filter cow file file=cow-base.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 cow.pid; then
+        break
+    fi
+    sleep 1
+done
+if ! test -f cow.pid; then
+    echo "$0: PID file was not created"
+    exit 1
+fi
+
+pid="$(cat cow.pid)"
+
+# Kill the nbdkit process on exit.
+cleanup ()
+{
+    status=$?
+
+    kill $pid
+    rm -f $files
+
+    exit $status
+}
+trap cleanup INT QUIT TERM EXIT ERR
+
+# Write some data into the overlay.
+guestfish --format=raw -a 'nbd://?socket=cow.sock' -m /dev/sda1 <<EOF
+  fill-dir / 10000
+  fill-pattern "abcde" 5M /large
+  write /hello "hello, world"
+EOF
+
+# The original file must not be modified.
+currmod="$(stat -c "%y" cow-base.img)"
+
+if [ "$lastmod" != "$currmod" ]; then
+    echo "$0: FAILED last modified time of base file changed"
+    exit 1
+fi
+
+# If we have qemu-img, try the hairy rebase operation documented
+# in the nbdkit-cow-filter manual.
+if qemu-img --version >/dev/null 2>&1; then
+    qemu-img create -f qcow2 -b nbd:unix:cow.sock cow-diff.qcow2
+    time qemu-img rebase -b cow-base.img cow-diff.qcow2
+    qemu-img info cow-diff.qcow2
+
+    # This checks the file we created exists.
+    guestfish --ro -a cow-diff.qcow2 -m /dev/sda1 cat /hello
+fi
+
+# The cleanup() function is called implicitly on exit.
-- 
2.15.1




More information about the Libguestfs mailing list