nbdkit/SOURCES/0011-New-filter-extentlist....

791 lines
23 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From e744dcb38cc52cbe64977efcdd4bc60e802d1b17 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Thu, 23 Jan 2020 19:52:00 +0000
Subject: [PATCH 11/19] New filter: extentlist.
Allows a list of extents to be placed on top of an existing plugin.
(cherry picked from commit 3e770b6d6620a62546849a2863638041c0b00640)
---
TODO | 4 +
configure.ac | 2 +
.../nbdkit-cacheextents-filter.pod | 1 +
filters/extentlist/Makefile.am | 67 ++++
filters/extentlist/extentlist.c | 326 ++++++++++++++++++
.../extentlist/nbdkit-extentlist-filter.pod | 90 +++++
filters/noextents/nbdkit-noextents-filter.pod | 1 +
tests/Makefile.am | 4 +
tests/test-extentlist.sh | 175 ++++++++++
9 files changed, 670 insertions(+)
create mode 100644 filters/extentlist/Makefile.am
create mode 100644 filters/extentlist/extentlist.c
create mode 100644 filters/extentlist/nbdkit-extentlist-filter.pod
create mode 100755 tests/test-extentlist.sh
diff --git a/TODO b/TODO
index d2aca440..2a3e89dc 100644
--- a/TODO
+++ b/TODO
@@ -187,6 +187,10 @@ Suggestions for filters
MBs of extra data)
https://github.com/facebook/zstd/issues/395#issuecomment-535875379
+* nbdkit-extentlist-filter could read the extents generated by
+ qemu-img map, allowing extents to be ported from a qemu block
+ device.
+
nbdkit-rate-filter:
* allow other kinds of traffic shaping such as VBR
diff --git a/configure.ac b/configure.ac
index fde498b8..41e68de3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -896,6 +896,7 @@ filters="\
cow \
delay \
error \
+ extentlist \
fua \
log \
nocache \
@@ -979,6 +980,7 @@ AC_CONFIG_FILES([Makefile
filters/cow/Makefile
filters/delay/Makefile
filters/error/Makefile
+ filters/extentlist/Makefile
filters/fua/Makefile
filters/log/Makefile
filters/nocache/Makefile
diff --git a/filters/cacheextents/nbdkit-cacheextents-filter.pod b/filters/cacheextents/nbdkit-cacheextents-filter.pod
index fdd2285a..bb2514a4 100644
--- a/filters/cacheextents/nbdkit-cacheextents-filter.pod
+++ b/filters/cacheextents/nbdkit-cacheextents-filter.pod
@@ -52,6 +52,7 @@ C<nbdkit-cacheextents-filter> first appeared in nbdkit 1.14.
L<nbdkit(1)>,
L<nbdkit-cache-filter(1)>,
+L<nbdkit-extentlist-filter(1)>,
L<nbdkit-readahead-filter(1)>,
L<nbdkit-vddk-plugin(1)>,
L<nbdkit-filter(3)>,
diff --git a/filters/extentlist/Makefile.am b/filters/extentlist/Makefile.am
new file mode 100644
index 00000000..88a9afe1
--- /dev/null
+++ b/filters/extentlist/Makefile.am
@@ -0,0 +1,67 @@
+# nbdkit
+# Copyright (C) 2019-2020 Red Hat Inc.
+#
+# 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 $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = nbdkit-extentlist-filter.pod
+
+filter_LTLIBRARIES = nbdkit-extentlist-filter.la
+
+nbdkit_extentlist_filter_la_SOURCES = \
+ extentlist.c \
+ $(top_srcdir)/include/nbdkit-filter.h \
+ $(NULL)
+
+nbdkit_extentlist_filter_la_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/common/include \
+ -I$(top_srcdir)/common/utils \
+ $(NULL)
+nbdkit_extentlist_filter_la_CFLAGS = $(WARNINGS_CFLAGS)
+nbdkit_extentlist_filter_la_LDFLAGS = \
+ -module -avoid-version -shared \
+ -Wl,--version-script=$(top_srcdir)/filters/filters.syms \
+ $(NULL)
+nbdkit_extentlist_filter_la_LIBADD = \
+ $(top_builddir)/common/utils/libutils.la \
+ $(NULL)
+
+if HAVE_POD
+
+man_MANS = nbdkit-extentlist-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-extentlist-filter.1: nbdkit-extentlist-filter.pod
+ $(PODWRAPPER) --section=1 --man $@ \
+ --html $(top_builddir)/html/$@.html \
+ $<
+
+endif HAVE_POD
diff --git a/filters/extentlist/extentlist.c b/filters/extentlist/extentlist.c
new file mode 100644
index 00000000..5f4990b3
--- /dev/null
+++ b/filters/extentlist/extentlist.c
@@ -0,0 +1,326 @@
+/* nbdkit
+ * Copyright (C) 2019-2020 Red Hat Inc.
+ *
+ * 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 <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <nbdkit-filter.h>
+
+#include "cleanup.h"
+#include "minmax.h"
+
+#define HOLE (NBDKIT_EXTENT_HOLE|NBDKIT_EXTENT_ZERO)
+
+static const char *extentlist;
+
+/* List of extents. Once we've finally parsed them this will be
+ * ordered, non-overlapping and have no gaps.
+ */
+struct extent {
+ uint64_t offset, length;
+ uint32_t type;
+};
+static struct extent *extents;
+static size_t nr_extents, allocated;
+
+/* Insert an extent before i. If i = nr_extents, inserts at the end. */
+static void
+insert_extent (size_t i, struct extent new_extent)
+{
+ if (nr_extents >= allocated) {
+ allocated = allocated == 0 ? 1 : allocated * 2;
+ extents = realloc (extents, (sizeof (struct extent) * allocated));
+ if (extents == NULL) {
+ nbdkit_error ("realloc: %m");
+ exit (EXIT_FAILURE);
+ }
+ }
+ memmove (&extents[i+1], &extents[i],
+ sizeof (struct extent) * (nr_extents-i));
+ extents[i] = new_extent;
+ nr_extents++;
+}
+
+static void
+extentlist_unload (void)
+{
+ free (extents);
+}
+
+/* Called for each key=value passed on the command line. */
+static int
+extentlist_config (nbdkit_next_config *next, void *nxdata,
+ const char *key, const char *value)
+{
+ if (strcmp (key, "extentlist") == 0) {
+ if (extentlist != NULL) {
+ nbdkit_error ("extentlist cannot appear twice");
+ exit (EXIT_FAILURE);
+ }
+ extentlist = value;
+ return 0;
+ }
+ else
+ return next (nxdata, key, value);
+}
+
+static int
+compare_offsets (const void *ev1, const void *ev2)
+{
+ const struct extent *e1 = ev1;
+ const struct extent *e2 = ev2;
+
+ if (e1->offset < e2->offset)
+ return -1;
+ else if (e1->offset > e2->offset)
+ return 1;
+ else
+ return 0;
+}
+
+static int
+compare_ranges (const void *ev1, const void *ev2)
+{
+ const struct extent *e1 = ev1;
+ const struct extent *e2 = ev2;
+
+ if (e1->offset < e2->offset)
+ return -1;
+ else if (e1->offset >= e2->offset + e2->length)
+ return 1;
+ else
+ return 0;
+}
+
+/* Similar to parse_extents in plugins/sh/methods.c */
+static void
+parse_extentlist (void)
+{
+ FILE *fp;
+ CLEANUP_FREE char *line = NULL;
+ size_t linelen = 0;
+ ssize_t len;
+ size_t i;
+ uint64_t end;
+
+ assert (extentlist != NULL);
+ assert (extents == NULL);
+ assert (nr_extents == 0);
+
+ fp = fopen (extentlist, "r");
+ if (!fp) {
+ nbdkit_error ("open: %s: %m", extentlist);
+ exit (EXIT_FAILURE);
+ }
+
+ while ((len = getline (&line, &linelen, fp)) != -1) {
+ const char *delim = " \t";
+ char *sp, *p;
+ int64_t offset, length;
+ uint32_t type;
+
+ if (len > 0 && line[len-1] == '\n') {
+ line[len-1] = '\0';
+ len--;
+ }
+
+ if ((p = strtok_r (line, delim, &sp)) == NULL) {
+ parse_error:
+ nbdkit_error ("%s: cannot parse %s", extentlist, line);
+ exit (EXIT_FAILURE);
+ }
+ offset = nbdkit_parse_size (p);
+ if (offset == -1)
+ exit (EXIT_FAILURE);
+
+ if ((p = strtok_r (NULL, delim, &sp)) == NULL)
+ goto parse_error;
+ length = nbdkit_parse_size (p);
+ if (length == -1)
+ exit (EXIT_FAILURE);
+
+ /* Skip zero length extents. Makes the rest of the code easier. */
+ if (length == 0)
+ continue;
+
+ if ((p = strtok_r (NULL, delim, &sp)) == NULL)
+ /* empty type field means allocated data (0) */
+ type = 0;
+ else if (sscanf (p, "%" SCNu32, &type) == 1)
+ ;
+ else {
+ type = 0;
+ if (strstr (p, "hole") != NULL)
+ type |= NBDKIT_EXTENT_HOLE;
+ if (strstr (p, "zero") != NULL)
+ type |= NBDKIT_EXTENT_ZERO;
+ }
+
+ insert_extent (nr_extents,
+ (struct extent){.offset = offset, .length=length,
+ .type=type});
+ }
+
+ fclose (fp);
+
+ /* Sort the extents by offset. */
+ qsort (extents, nr_extents, sizeof (struct extent), compare_offsets);
+
+ /* There must not be overlaps at this point. */
+ end = 0;
+ for (i = 0; i < nr_extents; ++i) {
+ if (extents[i].offset < end ||
+ extents[i].offset + extents[i].length < extents[i].offset) {
+ nbdkit_error ("extents in the extent list are overlapping");
+ exit (EXIT_FAILURE);
+ }
+ end = extents[i].offset + extents[i].length;
+ }
+
+ /* If there's a gap at the beginning, insert a hole|zero extent. */
+ if (nr_extents == 0 || extents[0].offset > 0) {
+ end = nr_extents == 0 ? UINT64_MAX : extents[0].offset;
+ insert_extent (0, (struct extent){.offset = 0, .length = end,
+ .type = HOLE});
+ }
+
+ /* Now insert hole|zero extents after every extent where there
+ * is a gap between that extent and the next one.
+ */
+ for (i = 0; i < nr_extents-1; ++i) {
+ end = extents[i].offset + extents[i].length;
+ if (end < extents[i+1].offset)
+ insert_extent (i+1, (struct extent){.offset = end,
+ .length = extents[i+1].offset - end,
+ .type = HOLE});
+ }
+
+ /* If there's a gap at the end, insert a hole|zero extent. */
+ end = extents[nr_extents-1].offset + extents[nr_extents-1].length;
+ if (end < UINT64_MAX)
+ insert_extent (nr_extents, (struct extent){.offset = end,
+ .length = UINT64_MAX-end,
+ .type = HOLE});
+
+ /* Debug the final list. */
+ for (i = 0; i < nr_extents; ++i) {
+ nbdkit_debug ("extentlist: "
+ "extent[%zu] = %" PRIu64 "-%" PRIu64 " (length %" PRIu64 ")"
+ " type %" PRIu32,
+ i, extents[i].offset,
+ extents[i].offset + extents[i].length - 1,
+ extents[i].length,
+ extents[i].type);
+ }
+}
+
+static int
+extentlist_config_complete (nbdkit_next_config_complete *next, void *nxdata)
+{
+ if (extentlist == NULL) {
+ nbdkit_error ("you must supply the extentlist parameter "
+ "on the command line");
+ return -1;
+ }
+
+ parse_extentlist ();
+
+ return next (nxdata);
+}
+
+static int
+extentlist_can_extents (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ return 1;
+}
+
+/* Use -D extentlist.lookup=1 to debug the function below. */
+int extentlist_debug_lookup = 0;
+
+/* Read extents. */
+static int
+extentlist_extents (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, uint32_t count, uint64_t offset,
+ uint32_t flags,
+ struct nbdkit_extents *ret_extents,
+ int *err)
+{
+ const struct extent eoffset = { .offset = offset };
+ struct extent *p;
+ ssize_t i;
+ uint64_t end;
+
+ /* Find the starting point in the extents list. */
+ p = bsearch (&eoffset, extents,
+ nr_extents, sizeof (struct extent), compare_ranges);
+ assert (p != NULL);
+ i = p - extents;
+
+ /* Add extents to the output. */
+ while (count > 0) {
+ if (extentlist_debug_lookup)
+ nbdkit_debug ("extentlist lookup: "
+ "loop i=%zd count=%" PRIu32 " offset=%" PRIu64,
+ i, count, offset);
+
+ end = extents[i].offset + extents[i].length;
+ if (nbdkit_add_extent (ret_extents, offset, end - offset,
+ extents[i].type) == -1)
+ return -1;
+
+ count -= MIN (count, end-offset);
+ offset = end;
+ i++;
+ }
+
+ return 0;
+}
+
+static struct nbdkit_filter filter = {
+ .name = "extentlist",
+ .longname = "nbdkit extentlist filter",
+ .unload = extentlist_unload,
+ .config = extentlist_config,
+ .config_complete = extentlist_config_complete,
+ .can_extents = extentlist_can_extents,
+ .extents = extentlist_extents,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
diff --git a/filters/extentlist/nbdkit-extentlist-filter.pod b/filters/extentlist/nbdkit-extentlist-filter.pod
new file mode 100644
index 00000000..adfb4ad8
--- /dev/null
+++ b/filters/extentlist/nbdkit-extentlist-filter.pod
@@ -0,0 +1,90 @@
+=head1 NAME
+
+nbdkit-extentlist-filter - place extent list over a plugin
+
+=head1 SYNOPSIS
+
+ nbdkit --filter=extentlist plugin extentlist=FILENAME
+
+=head1 DESCRIPTION
+
+C<nbdkit-extentlist-filter> is an nbdkit filter lets you place a
+static list of extents on top of an existing plugin. Extents record
+whether or not specific parts of the disk are allocated or sparse.
+
+You can use this with plugins which cannot get extent information
+themselves, but you can get this information from another source. One
+place where it is useful is with L<nbdkit-ssh-plugin(1)> because the
+sftp protocol does not support reading sparseness information, but you
+may be able to get this information directly from the source disk on
+the remote server.
+
+=head1 FILE FORMAT
+
+The list of extents is specified in a text file. There is one extent
+specified per line. Each line has the format:
+
+ offset length type
+
+The C<offset> and C<length> fields may use any format understood by
+C<nbdkit_parse_size>. The optional C<type> field may be an integer,
+missing (same as 0), or a comma-separated list of the words C<hole>
+and C<zero>. (The fields correspond to the inputs of the
+C<nbdkit_add_extent> function, see L<nbdkit-plugin(3)>).
+
+An example of a valid set of extents covering a C<10M> disk where the
+first megabyte only is allocated data:
+
+ 0 1M
+ 1M 9M hole,zero
+
+Or you could omit the C<hole,zero> extent since any gaps are assumed
+to be holes with that type:
+
+ 0 1M
+
+The extent list need not cover the whole disk, and does not need to be
+in ascending order, but it must I<not> contain overlapping extents.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<extentlist=>FILENAME
+
+Specify the file containing the extent list, in the format described
+in L</FILE FORMAT> above.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item F<$filterdir/nbdkit-extentlist-filter.so>
+
+The filter.
+
+Use C<nbdkit --dump-config> to find the location of C<$filterdir>.
+
+=back
+
+=head1 VERSION
+
+C<nbdkit-extentlist-filter> first appeared in nbdkit 1.18.
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-cacheextents-filter(1)>,
+L<nbdkit-noextents-filter(1)>,
+L<nbdkit-filter(3)>,
+L<nbdkit-plugin(3)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2020 Red Hat Inc.
diff --git a/filters/noextents/nbdkit-noextents-filter.pod b/filters/noextents/nbdkit-noextents-filter.pod
index 991ecfe8..0260a5cf 100644
--- a/filters/noextents/nbdkit-noextents-filter.pod
+++ b/filters/noextents/nbdkit-noextents-filter.pod
@@ -47,6 +47,7 @@ C<nbdkit-noextents-filter> first appeared in nbdkit 1.14.
L<nbdkit(1)>,
L<nbdkit-filter(3)>,
+L<nbdkit-extentlist-filter(1)>,
L<nbdkit-fua-filter(1)>,
L<nbdkit-nocache-filter(1)>,
L<nbdkit-noparallel-filter(1)>,
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 09103fbb..b99952f4 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -110,6 +110,7 @@ EXTRA_DIST = \
test-error100.sh \
test-error-triggered.sh \
test-export-name.sh \
+ test-extentlist.sh \
test-file-extents.sh \
test-floppy.sh \
test-foreground.sh \
@@ -1009,6 +1010,9 @@ TESTS += \
test-error-triggered.sh \
$(NULL)
+# extentlist filter test.
+TESTS += test-extentlist.sh
+
# fua filter test.
TESTS += test-fua.sh
diff --git a/tests/test-extentlist.sh b/tests/test-extentlist.sh
new file mode 100755
index 00000000..7d05de4f
--- /dev/null
+++ b/tests/test-extentlist.sh
@@ -0,0 +1,175 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2016-2020 Red Hat Inc.
+#
+# 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.
+
+# Test the extentlist filter.
+
+source ./functions.sh
+set -e
+set -x
+
+requires jq --version
+requires qemu-img --version
+requires qemu-img map --help
+
+out=test-extentlist.out
+input=test-extentlist.in
+expected=test-extentlist.expected
+files="$out $input $expected"
+rm -f $files
+cleanup_fn rm $files
+
+test ()
+{
+ nbdkit -v -D extentlist.lookup=1 \
+ -U - \
+ --filter=extentlist \
+ null size=$1 extentlist=$input \
+ --run 'qemu-img map -f raw --output=json $nbd' |
+ jq -c '.[] | {start:.start, length:.length, data:.data, zero:.zero}' \
+ > $out
+ diff -u $out $expected
+}
+
+# Empty extent list.
+cat > $input <<'EOF'
+EOF
+
+cat > $expected <<'EOF'
+{"start":0,"length":0,"data":false,"zero":false}
+EOF
+test 0
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":false,"zero":true}
+EOF
+test 1M
+
+# Extent list covering 0-1M with data.
+cat > $input <<'EOF'
+0 1M
+EOF
+
+cat > $expected <<'EOF'
+{"start":0,"length":0,"data":false,"zero":false}
+EOF
+test 0
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":true,"zero":false}
+EOF
+test 1M
+
+# Extent list covering 1-2M with data.
+cat > $input <<'EOF'
+1M 1M
+EOF
+
+cat > $expected <<'EOF'
+{"start":0,"length":0,"data":false,"zero":false}
+EOF
+test 0
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":false,"zero":true}
+EOF
+test 1M
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":false,"zero":true}
+{"start":1048576,"length":1048576,"data":true,"zero":false}
+EOF
+test 2M
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":false,"zero":true}
+{"start":1048576,"length":1048576,"data":true,"zero":false}
+{"start":2097152,"length":1048576,"data":false,"zero":true}
+EOF
+test 3M
+
+# Extent list covering 1-2M with data, but in a more fragmented
+# way than the above.
+cat > $input <<'EOF'
+1024K 512K
+1536K 512K
+EOF
+
+cat > $expected <<'EOF'
+{"start":0,"length":0,"data":false,"zero":false}
+EOF
+test 0
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":false,"zero":true}
+EOF
+test 1M
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":false,"zero":true}
+{"start":1048576,"length":1048576,"data":true,"zero":false}
+EOF
+test 2M
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":false,"zero":true}
+{"start":1048576,"length":1048576,"data":true,"zero":false}
+{"start":2097152,"length":1048576,"data":false,"zero":true}
+EOF
+test 3M
+
+# Adjacent data and holes.
+cat > $input <<'EOF'
+0 1M
+2M 1M
+4M 1M
+EOF
+
+cat > $expected <<'EOF'
+{"start":0,"length":0,"data":false,"zero":false}
+EOF
+test 0
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":true,"zero":false}
+EOF
+test 1M
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":true,"zero":false}
+{"start":1048576,"length":1048576,"data":false,"zero":true}
+EOF
+test 2M
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":true,"zero":false}
+{"start":1048576,"length":1048576,"data":false,"zero":true}
+{"start":2097152,"length":1048576,"data":true,"zero":false}
+EOF
+test 3M
+cat > $expected <<'EOF'
+{"start":0,"length":1048576,"data":true,"zero":false}
+{"start":1048576,"length":1048576,"data":false,"zero":true}
+{"start":2097152,"length":1048576,"data":true,"zero":false}
+{"start":3145728,"length":1048576,"data":false,"zero":true}
+{"start":4194304,"length":1048576,"data":true,"zero":false}
+{"start":5242880,"length":1048576,"data":false,"zero":true}
+EOF
+test 6M
--
2.18.2