---
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