---
.gitignore | 2 +
tests/Makefile.am | 5 +
tests/test-map-stochastic.c | 307 ++++++++++++++++++++++++++++++++++++
3 files changed, 314 insertions(+)
diff --git a/.gitignore b/.gitignore
index a84ad9a..da8b845 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,6 +54,7 @@ Makefile.in
/tests/ext2.img
/tests/file-data
/tests/keys.psk
+/tests/mapfile
/tests/offset-data
/tests/partition-disk
/tests/pki
@@ -67,6 +68,7 @@ Makefile.in
/tests/test-file
/tests/test-gzip
/tests/test-lua
+/tests/test-map-stochastic
/tests/test-memory
/tests/test-newstyle
/tests/test-nbd
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 2306506..40efd73 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -550,6 +550,11 @@ TESTS += test-log.sh
# map filter test.
TESTS += test-map-empty.sh
+LIBGUESTFS_TESTS += test-map-stochastic
+
+test_map_stochastic_SOURCES = test-map-stochastic.c test.h
+test_map_stochastic_CFLAGS = $(WARNINGS_CFLAGS) $(LIBGUESTFS_CFLAGS)
+test_map_stochastic_LDADD = libtest.la $(LIBGUESTFS_LIBS)
# nozero filter test.
TESTS += test-nozero.sh
diff --git a/tests/test-map-stochastic.c b/tests/test-map-stochastic.c
new file mode 100644
index 0000000..10b37de
--- /dev/null
+++ b/tests/test-map-stochastic.c
@@ -0,0 +1,307 @@
+/* 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 <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <guestfs.h>
+
+#include "test.h"
+
+#define PATTERN_SIZE 65536 /* size in bytes of the input pattern */
+#define FILTER_SIZE 131072 /* size in bytes of the output of the filter */
+#define MAPFILE "mapfile" /* in tests directory */
+
+#define XSTR(s) STR(s)
+#define STR(s) #s
+
+static char pattern[PATTERN_SIZE]; /* underlying pattern data */
+static char expected[FILTER_SIZE]; /* after mapping */
+static char actual[FILTER_SIZE]; /* what we read from nbdkit --filter=map */
+
+static void create_pattern (void);
+static void create_map (void);
+static void mismatch_error (size_t output_size) __attribute__((noreturn));
+
+int
+main (int argc, char *argv[])
+{
+ guestfs_h *g;
+ int r;
+ size_t i, output_size;
+
+ srandom (time (NULL));
+
+ create_pattern ();
+
+ /* Create the random mapfile and associated expected data. */
+ create_map ();
+
+ /* We place a truncate filter in front of the map to round up its
+ * size to the next multiple of 512 bytes. Otherwise qemu chokes on
+ * the non-standard size of most randomly generated maps.
+ */
+ if (test_start_nbdkit ("-r",
+ "--filter", "truncate",
+ "--filter", "map",
+ "pattern", "size=" XSTR(PATTERN_SIZE),
+ "map=" MAPFILE,
+ "round-up=512",
+ NULL) == -1)
+ exit (EXIT_FAILURE);
+
+ g = guestfs_create ();
+ if (g == NULL) {
+ perror ("guestfs_create");
+ exit (EXIT_FAILURE);
+ }
+
+ r = guestfs_add_drive_opts (g, "",
+ GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
+ GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
+ GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "nbd",
+ GUESTFS_ADD_DRIVE_OPTS_SERVER, server,
+ -1);
+ if (r == -1)
+ exit (EXIT_FAILURE);
+
+ if (guestfs_launch (g) == -1)
+ exit (EXIT_FAILURE);
+
+ /* The size of the final device is not necessarily FILTER_SIZE. It
+ * might be smaller if there are no mappings that happen to cover
+ * the end. We can ask nbdkit for the size.
+ */
+ output_size = guestfs_blockdev_getsize64 (g, "/dev/sda");
+ if (output_size == -1)
+ exit (EXIT_FAILURE);
+
+ if (output_size > FILTER_SIZE) {
+ fprintf (stderr,
+ "test-map-stochastic: unexpected size returned for device: "
+ "%zu > %d\n", output_size, FILTER_SIZE);
+ exit (EXIT_FAILURE);
+ }
+
+ i = 0;
+ while (i < output_size) {
+ /* Note that qemu will only issue reads on 512 byte boundaries
+ * so mostly this is pointless.
+ */
+ int n = random () % 2048;
+ size_t read;
+ char *buf;
+
+ if (n <= 0)
+ n = 1;
+ if (n > output_size-i)
+ n = output_size-i;
+
+ buf = guestfs_pread_device (g, "/dev/sda", n, (int64_t) i, &read);
+ if (buf == NULL)
+ exit (EXIT_FAILURE);
+ memcpy (&actual[i], buf, read);
+ free (buf);
+
+ i += read;
+ }
+
+ if (memcmp (actual, expected, output_size) != 0)
+ mismatch_error (output_size);
+
+ unlink (MAPFILE);
+
+ guestfs_close (g);
+ exit (EXIT_SUCCESS);
+}
+
+/* Create the pattern data in the same way as the pattern plugin
+ * works. See nbdkit-pattern-plugin(1).
+ */
+static void
+create_pattern (void)
+{
+ size_t i;
+ uint64_t d;
+
+ for (i = 0; i < PATTERN_SIZE; i += 8) {
+ d = i;
+ d = htobe64 (d);
+ memcpy (&pattern[i], &d, 8);
+ }
+}
+
+/* Create the random map, write it to the map file, and map
+ * the pattern data accordingly.
+ */
+static const int rows[] = { 0, 1, 3, 5, 7, 11, 13, 17, 19, 23 };
+static const int alignments[] = { 1, 1, 1, 2, 4, 8, 512 };
+static const int lens[] = { 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3,
+ 7, 8, 9,
+ 15, 16, 17,
+ 512, 1024, 2048, 8192,
+ 16384, 16384, 16384*2, 16384*2,
+ 16384*3, 16384*3, 16384*4, 16384*4,
+ 16384*5, 16384*6, 16384*7, 16384*8 };
+
+#define RANDOM_CHOICE(array) \
+ array[random () % (sizeof array / sizeof array[0])]
+
+static uint64_t
+random_alignment (uint64_t i)
+{
+ uint64_t alignment = RANDOM_CHOICE (alignments);
+
+ i &= ~(alignment - 1);
+ return i;
+}
+
+static void
+create_map (void)
+{
+ FILE *fp;
+ size_t i, j, nr_rows;
+ uint64_t start, length, to;
+ int end_form, open_end;
+
+ memset (expected, 0, sizeof expected);
+ memset (actual, 0, sizeof actual);
+
+ /* Pick a random number of mappings (rows in the map file). Can be 0. */
+ nr_rows = RANDOM_CHOICE (rows);
+
+ fp = fopen (MAPFILE, "w");
+ if (fp == NULL) {
+ perror (MAPFILE);
+ exit (EXIT_FAILURE);
+ }
+
+ fprintf (fp, "# %s for testing the map filter\n", MAPFILE);
+ fprintf (fp, "# generated randomly by %s\n", __FILE__);
+ fprintf (fp, "# pattern plugin (input) size: %d\n", PATTERN_SIZE);
+ fprintf (fp, "# map filter (max output) size: %d\n", FILTER_SIZE);
+ fprintf (fp, "# nr_rows: %zu\n", nr_rows);
+ fprintf (fp, "\n");
+
+ for (i = 0; i < nr_rows; ++i) {
+ /* Pick a random start point, length and destination (to). */
+ start = random () % PATTERN_SIZE;
+ start = random_alignment (start);
+ length = RANDOM_CHOICE (lens);
+ to = random () % FILTER_SIZE;
+ to = random_alignment (to);
+
+ /* Don't have a mapping going beyond the end of the output size
+ * we want.
+ */
+ if (to + length >= FILTER_SIZE)
+ length = FILTER_SIZE - to;
+
+ /* Choose randomly whether to use start-end or start,length. */
+ end_form = (random () % 2) == 1;
+
+ /* Randomly pick some open-ended mappings, but don't allow them if
+ * that would go beyond the output size we want.
+ */
+ open_end = 0;
+ if (PATTERN_SIZE - to <= FILTER_SIZE - start)
+ open_end = (random () % 8) == 1;
+
+ if (end_form) {
+ if (open_end)
+ fprintf (fp, "%" PRIu64 "-\t%" PRIu64 "\n", start,
to);
+ else
+ fprintf (fp, "%" PRIu64 "-%" PRIu64 "\t%" PRIu64
"\n",
+ start, start+length-1, to);
+ }
+ else {
+ if (open_end)
+ fprintf (fp, "%" PRIu64 "\t%" PRIu64 "\n", start,
to);
+ else
+ fprintf (fp, "%" PRIu64 ",%" PRIu64 "\t%" PRIu64
"\n",
+ start, length, to);
+ }
+
+ /* Perform the mapping into our expected[] array. */
+ for (j = 0; j < length; ++j) {
+ if (to+j <= FILTER_SIZE) {
+ if (start+j <= PATTERN_SIZE)
+ expected[to+j] = pattern[start+j];
+ else
+ expected[to+j] = 0; /* Bytes outside the source read as 0 */
+ }
+ }
+ }
+
+ fprintf (fp, "\n");
+ fprintf (fp, "# end of %s\n", MAPFILE);
+ fclose (fp);
+}
+
+static void
+dump_array (const char *array, size_t size)
+{
+ FILE *pp;
+
+ pp = popen ("hexdump -C", "w");
+ if (pp == NULL) {
+ perror ("popen: hexdump");
+ return;
+ }
+ fwrite (array, 1, size, pp);
+ pclose (pp);
+}
+
+static void
+mismatch_error (size_t output_size)
+{
+ fprintf (stderr, "test-map-stochastic: "
+ "actual data read back does not match expected data\n");
+ fprintf (stderr, "\n");
+ fprintf (stderr, "map contains:\n");
+ system ("cat " MAPFILE);
+ fprintf (stderr, "\n");
+ fprintf (stderr, "expected data:\n");
+ dump_array (expected, output_size);
+ fprintf (stderr, "\n");
+ fprintf (stderr, "actual data:\n");
+ dump_array (actual, output_size);
+ exit (EXIT_FAILURE);
+}
--
2.18.0