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