[Libguestfs] [libnbd PATCH 6/6] examples: New example for strict read validations

Eric Blake eblake at redhat.com
Sat Jun 29 13:28:29 UTC 2019


Demonstrate a use of the new nbd_pread_structured_verify API by
writing a strict validation that a server's structured replies comply
with the specification (well, 99% strict, as I did not check that the
server does not return an error at the same offset twice).

I was able to test that qemu-nbd is compliant. An example run:

$ qemu-img create -f qcow2 file 32m
$ for i in `seq 32`; do
  qemu-io -f qcow2 -d unmap -c "w -zu $((i-1))m 512k" file; done
$ qemu-nbd -f qcow2 -p 10888 file
$ ./examples/strict-structured-reads nbd://localhost:10888
totals:
 data chunks:       1768
 data bytes:  1559232512
 hole chunks:       1284
 hole bytes:   537919488
 all chunks:        3052
 reads:             1000
 bytes read:  2097152000
 compliant:         1000

But since qemu-nbd always returns chunks in order, there may still be
lurking bugs in my code to handle out-of-order replies. Maybe someday
nbdkit will make it easy to write a server that returns out-of-order
chunks.
---
 .gitignore                         |   1 +
 examples/Makefile.am               |  14 ++
 examples/strict-structured-reads.c | 270 +++++++++++++++++++++++++++++
 3 files changed, 285 insertions(+)
 create mode 100644 examples/strict-structured-reads.c

diff --git a/.gitignore b/.gitignore
index d4828fa..edbf941 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,6 +44,7 @@ Makefile.in
 /examples/threaded-reads-and-writes
 /examples/simple-fetch-first-sector
 /examples/simple-reads-and-writes
+/examples/strict-structured-reads
 /generator/generator-cache.v1
 /generator/stamp-generator
 /html/*.?.html
diff --git a/examples/Makefile.am b/examples/Makefile.am
index f0d03f1..7560855 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -24,6 +24,7 @@ noinst_PROGRAMS = \
 	simple-fetch-first-sector \
 	simple-reads-and-writes \
 	threaded-reads-and-writes \
+	strict-structured-reads \
 	$(NULL)

 simple_fetch_first_sector_SOURCES = \
@@ -52,6 +53,19 @@ simple_reads_and_writes_LDADD = \
 	$(top_builddir)/lib/libnbd.la \
 	$(NULL)

+strict_structured_reads_SOURCES = \
+	strict-structured-reads.c \
+	$(NULL)
+strict_structured_reads_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	$(NULL)
+strict_structured_reads_CFLAGS = \
+	$(WARNINGS_CFLAGS) \
+	$(NULL)
+strict_structured_reads_LDADD = \
+	$(top_builddir)/lib/libnbd.la \
+	$(NULL)
+
 threaded_reads_and_writes_SOURCES = \
 	threaded-reads-and-writes.c \
 	$(NULL)
diff --git a/examples/strict-structured-reads.c b/examples/strict-structured-reads.c
new file mode 100644
index 0000000..e75f5a3
--- /dev/null
+++ b/examples/strict-structured-reads.c
@@ -0,0 +1,270 @@
+/* Example usage with qemu-nbd:
+ *
+ * sock=`mktemp -u`
+ * qemu-nbd -f $format -k $sock -r image
+ * ./strict-structured-reads $sock
+ *
+ * This will perform read randomly over the image and check that all
+ * structured replies comply with the NBD spec (chunks may be out of
+ * order or interleaved, but no read succeeds unless chunks cover the
+ * entire region, with no overlapping or zero-length chunks).
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <time.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+#include <libnbd.h>
+
+/* A linked list of ranges still not seen. */
+struct range {
+  uint64_t first;
+  uint64_t last;
+  struct range *next;
+};
+
+/* Per-read data. */
+struct data {
+  uint64_t offset;
+  size_t count;
+  uint32_t flags;
+  size_t chunks;
+  struct range *remaining;
+};
+
+#define MAX_BUF (2 * 1024 * 1024)
+static char buf[MAX_BUF];
+
+/* Various statistics */
+static int total_data_chunks;
+static int total_data_bytes;
+static int total_hole_chunks;
+static int total_hole_bytes;
+static int total_chunks;
+static int total_df_reads;
+static int total_reads;
+static int64_t total_bytes;
+static int total_success;
+
+static int
+read_chunk (void *opaque, const void *bufv, size_t count, uint64_t offset,
+            int *error, int status)
+{
+  struct data *data = opaque;
+  struct range *r, **prev;
+
+  /* libnbd guarantees this: */
+  assert (offset >= data->offset);
+  assert (offset + count <= data->offset + data->count);
+
+  switch (status) {
+  case LIBNBD_READ_DATA:
+    total_data_chunks++;
+    total_data_bytes += count;
+    break;
+  case LIBNBD_READ_HOLE:
+    total_hole_chunks++;
+    total_hole_bytes += count;
+    break;
+  case LIBNBD_READ_ERROR:
+    assert (count == 0);
+    count = 1; /* Ensure no further chunks visit that offset */
+    break;
+  default:
+    goto error;
+  }
+  data->chunks++;
+  if (count == 0) {
+    fprintf (stderr, "buggy server: chunk must have non-zero size\n");
+    goto error;
+  }
+
+  /* Find element in remaining, or the server is in error */
+  for (prev = &data->remaining, r = *prev; r; prev = &r->next, r = r->next) {
+    if (offset >= r->first)
+      break;
+  }
+  if (r == NULL || offset + count > r->last) {
+    /* we fail to detect double errors reported at the same offset,
+     * but at least the read is already going to fail.
+     */
+    if (status == LIBNBD_READ_ERROR)
+      return 0;
+    fprintf (stderr, "buggy server: chunk with overlapping range\n");
+    goto error;
+  }
+
+  /* Resize or split r to track new remaining bytes */
+  if (offset == r->first) {
+    if (offset + count == r->last) {
+      *prev = r->next;
+      free (r);
+    }
+    else
+      r->first += count;
+  }
+  else if (offset + count == r->last) {
+    r->last -= count;
+  }
+  else {
+    struct range *n = malloc (sizeof *n);
+    assert (n);
+    n->next = r->next;
+    r->next = n;
+    n->last = r->last;
+    r->last = offset - r->first;
+    n->first = offset + count;
+  }
+
+  return 0;
+ error:
+  *error = EPROTO;
+  return -1;
+}
+
+static int
+read_verify (void *opaque, int64_t handle, int *error)
+{
+  struct data *data = opaque;
+  int ret = -1;
+
+  total_reads++;
+  total_chunks += data->chunks;
+  if (*error)
+    goto cleanup;
+  assert (data->chunks > 0);
+  if (data->flags & LIBNBD_CMD_FLAG_DF) {
+    total_df_reads++;
+    if (data->chunks > 1) {
+      fprintf (stderr, "buggy server: too many chunks for DF flag\n");
+      *error = EPROTO;
+      goto cleanup;
+    }
+  }
+  if (data->remaining && !*error) {
+    fprintf (stderr, "buggy server: not enough chunks on success\n");
+    *error = EPROTO;
+    goto cleanup;
+  }
+  total_bytes += data->count;
+  total_success++;
+  ret = 0;
+
+ cleanup:
+  while (data->remaining) {
+    struct range *r = data->remaining;
+    data->remaining = r->next;
+    free (r);
+  }
+  free (data);
+  return ret;
+}
+
+int
+main (int argc, char *argv[])
+{
+  struct nbd_handle *nbd;
+  size_t i;
+  int64_t exportsize;
+  int64_t maxsize = MAX_BUF;
+  uint64_t offset;
+
+  srand (time (NULL));
+
+  if (argc != 2) {
+    fprintf (stderr, "%s socket|uri\n", argv[0]);
+    exit (EXIT_FAILURE);
+  }
+
+  nbd = nbd_create ();
+  if (nbd == NULL) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if (strstr (argv[1], "://")) {
+    if (nbd_connect_uri (nbd, argv[1]) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+  }
+  else if (nbd_connect_unix (nbd, argv[1]) == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+
+  exportsize = nbd_get_size (nbd);
+  if (exportsize == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if (exportsize < 512) {
+    fprintf (stderr, "image is too small for useful testing\n");
+    exit (EXIT_FAILURE);
+  }
+  if (exportsize <= maxsize)
+    maxsize = exportsize - 1;
+
+  /* Queue up 1000 parallel reads. We are reusing the same buffer,
+   * which is not safe in real life, but okay here because we aren't
+   * validating contents, only server behavior.
+   */
+  for (i = 0; i < 1000; ++i) {
+    uint32_t flags = 0;
+    struct data *d = malloc (sizeof *d);
+    struct range *r = malloc (sizeof *r);
+
+    assert (d && r);
+    offset = rand () % (exportsize - maxsize);
+    if (rand() & 1)
+      flags = LIBNBD_CMD_FLAG_DF;
+    *r = (struct range) { .first = offset, .last = offset + maxsize, };
+    *d = (struct data) { .offset = offset, .count = maxsize, .flags = flags,
+                         .remaining = r, };
+    if (nbd_aio_pread_structured_notify (nbd, buf, sizeof buf, offset, d,
+                                         read_chunk, read_verify,
+                                         flags) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+  }
+
+  while (nbd_aio_in_flight (nbd) > 0) {
+    int64_t handle = nbd_aio_peek_command_completed (nbd);
+
+    if (handle == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+    if (handle == 0) {
+      if (nbd_poll (nbd, -1) == -1) {
+        fprintf (stderr, "%s\n", nbd_get_error ());
+        exit (EXIT_FAILURE);
+      }
+    }
+    else
+      nbd_aio_command_completed (nbd, handle);
+  }
+
+  if (nbd_shutdown (nbd) == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+
+  nbd_close (nbd);
+
+  printf ("totals:\n");
+  printf (" data chunks: %10d\n", total_data_chunks);
+  printf (" data bytes:  %10d\n", total_data_bytes);
+  printf (" hole chunks: %10d\n", total_hole_chunks);
+  printf (" hole bytes:  %10d\n", total_hole_bytes);
+  printf (" all chunks:  %10d\n", total_chunks);
+  printf (" reads:       %10d\n", total_reads);
+  printf (" bytes read:  %10" PRId64 "\n", total_bytes);
+  printf (" compliant:   %10d\n", total_success);
+
+  exit (EXIT_SUCCESS);
+}
-- 
2.20.1




More information about the Libguestfs mailing list