With a new enough qemu-nbd and a file system that supports a hole, we
can test that nbd_pread_callback for sane operation with
interop/structured-read.
It is also possible to test that the callback behaves sanely even for
a connection lacks structured replies, using nbdkit in tests/oldstyle.
---
.gitignore | 1 +
interop/Makefile.am | 11 ++-
interop/structured-read.c | 165 +++++++++++++++++++++++++++++++++
interop/structured-read.sh | 57 ++++++++++++
python/t/405-pread-callback.py | 36 +++++++
tests/oldstyle.c | 74 ++++++++++++++-
6 files changed, 339 insertions(+), 5 deletions(-)
create mode 100644 interop/structured-read.c
create mode 100755 interop/structured-read.sh
create mode 100644 python/t/405-pread-callback.py
diff --git a/.gitignore b/.gitignore
index 30438c1..ea496ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,7 @@ Makefile.in
/interop/interop-qemu-nbd
/interop/interop-qemu-nbd-tls-certs
/interop/interop-qemu-nbd-tls-psk
+/interop/structured-read
/lib/api.c
/lib/libnbd.pc
/lib/libnbd.syms
diff --git a/interop/Makefile.am b/interop/Makefile.am
index eb4b52b..6e1156d 100644
--- a/interop/Makefile.am
+++ b/interop/Makefile.am
@@ -47,10 +47,12 @@ check_PROGRAMS += \
interop-qemu-nbd \
interop-qemu-nbd-tls-certs \
interop-qemu-nbd-tls-psk \
- dirty-bitmap
+ dirty-bitmap \
+ structured-read
TESTS += \
interop-qemu-nbd \
- dirty-bitmap.sh
+ dirty-bitmap.sh \
+ structured-read.sh
# tls tests assume the pre-existence of files created in ../tests/Makefile.am,
# so we can only run them under the same conditions used there
@@ -107,6 +109,11 @@ dirty_bitmap_CPPFLAGS = -I$(top_srcdir)/include
dirty_bitmap_CFLAGS = $(WARNINGS_CFLAGS)
dirty_bitmap_LDADD = $(top_builddir)/lib/libnbd.la
+structured_read_SOURCES = structured-read.c
+structured_read_CPPFLAGS = -I$(top_srcdir)/include
+structured_read_CFLAGS = $(WARNINGS_CFLAGS)
+structured_read_LDADD = $(top_builddir)/lib/libnbd.la
+
endif HAVE_QEMU_NBD
check-valgrind:
diff --git a/interop/structured-read.c b/interop/structured-read.c
new file mode 100644
index 0000000..b740e98
--- /dev/null
+++ b/interop/structured-read.c
@@ -0,0 +1,165 @@
+/* NBD client library in userspace
+ * Copyright (C) 2013-2019 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Test structured reply read callback. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <libnbd.h>
+
+static const char *unixsocket;
+
+/* Depends on structured-read.sh setting things up so that qemu-nbd
+ * exposes an image with a 512-byte hole at offset 2048 followed by a
+ * 512-byte data section containing all '1' bytes at offset 2560
+ * (non-zero offsets to test that everything is calculated correctly).
+ */
+static char rbuf[1024];
+
+struct data {
+ //XXX bool df; /* input: true if DF flag was passed to request */
+ int count; /* input: count of expected remaining calls */
+ bool fail; /* input: true to return failure */
+ bool seen_hole; /* output: true if hole encountered */
+ bool seen_data; /* output: true if data encountered */
+};
+
+static int
+read_cb (void *opaque, const void *bufv, size_t count, uint64_t offset,
+ int status)
+{
+ struct data *data = opaque;
+ const char *buf = bufv;
+
+ /* The NBD spec allows chunks to be reordered; we are relying on the
+ * fact that qemu-nbd does not do so.
+ */
+ assert (data->count-- > 0);
+
+ switch (status) {
+ case LIBNBD_READ_DATA:
+ // XXX if (df...)
+ assert (buf == rbuf + 512);
+ assert (count == 512);
+ assert (offset == 2048 + 512);
+ assert (buf[0] == 1 && memcmp (buf, buf + 1, 511) == 0);
+ assert (!data->seen_data);
+ data->seen_data = true;
+ break;
+ case LIBNBD_READ_HOLE:
+ assert (buf == rbuf);
+ assert (count == 512);
+ assert (offset == 2048);
+ assert (buf[0] == 0 && memcmp (buf, buf + 1, 511) == 0);
+ assert (!data->seen_hole);
+ data->seen_hole = true;
+ break;
+ case LIBNBD_READ_ERROR:
+ /* For now, qemu-nbd cannot provoke this status. */
+ default:
+ assert (false);
+ }
+
+ if (data->fail) {
+ /* Something NBD servers can't send */
+ errno = data->count == 1 ? EPROTO : ECONNREFUSED;
+ return -1;
+ }
+ return 0;
+}
+
+int
+main (int argc, char *argv[])
+{
+ struct nbd_handle *nbd;
+ int64_t exportsize;
+ struct data data;
+ char c;
+
+ if (argc != 2) {
+ fprintf (stderr, "%s unixsocket\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+ unixsocket = argv[1];
+
+ nbd = nbd_create ();
+ if (nbd == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ if (nbd_connect_unix (nbd, unixsocket) == -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 != 3072) {
+ fprintf (stderr, "unexpected file size\n");
+ exit (EXIT_FAILURE);
+ }
+
+ memset (rbuf, 2, sizeof rbuf);
+ data = (struct data) { .count = 2, };
+ if (nbd_pread_callback (nbd, rbuf, sizeof rbuf, 2048, &data, read_cb,
+ 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ assert (data.seen_data && data.seen_hole);
+
+ // XXX Repeat with DF flag
+
+ /* Trigger a failed callback, to prove connection stays up. With
+ * reads, all chunks trigger a callback even after failure, but the
+ * first errno sticks.
+ */
+ memset (rbuf, 2, sizeof rbuf);
+ data = (struct data) { .count = 2, .fail = true, };
+ if (nbd_pread_callback (nbd, rbuf, sizeof rbuf, 2048, &data, read_cb,
+ 0) != -1) {
+ fprintf (stderr, "unexpected pread callback success\n");
+ exit (EXIT_FAILURE);
+ }
+ assert (nbd_get_errno () == EPROTO && nbd_aio_is_ready (nbd));
+ assert (data.seen_data && data.seen_hole);
+
+ if (nbd_pread (nbd, &c, 1, 0, 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ if (nbd_shutdown (nbd) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ nbd_close (nbd);
+
+ exit (EXIT_SUCCESS);
+}
diff --git a/interop/structured-read.sh b/interop/structured-read.sh
new file mode 100755
index 0000000..15a81c0
--- /dev/null
+++ b/interop/structured-read.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2019 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+# Test structured read callbacks.
+
+source ../tests/functions.sh
+set -e
+set -x
+
+requires qemu-img --version
+requires qemu-io --version
+requires qemu-nbd --version
+
+files="structured-read.sock structured-read.qcow2"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Create file with cluster size 512 and contents all 1 except for single
+# 512-byte hole at offset 2048
+qemu-img create -f qcow2 -o cluster_size=512,compat=v3 structured-read.qcow2 3k
+qemu-io -d unmap -f qcow2 -c 'w -P 1 0 3k' -c 'w -zu 2k 512' \
+ structured-read.qcow2
+
+qemu-nbd -k $PWD/structured-read.sock -f qcow2 structured-read.qcow2 &
+qemu_pid=$!
+cleanup_fn kill $qemu_pid
+
+# qemu-nbd --pid not available before 4.1, so ...
+for ((i = 0; i < 300; i++)); do
+ if [ -r $PWD/structured-read.sock ]; then
+ break
+ fi
+ kill -s 0 $qemu_pid 2>/dev/null
+ if test $? != 0; then
+ echo "qemu-nbd unexpectedly quit" 2>&1
+ exit 1
+ fi
+ sleep 0.1
+done
+
+# Run the test.
+$VG ./structured-read structured-read.sock
diff --git a/python/t/405-pread-callback.py b/python/t/405-pread-callback.py
new file mode 100644
index 0000000..7946dac
--- /dev/null
+++ b/python/t/405-pread-callback.py
@@ -0,0 +1,36 @@
+# libnbd Python bindings
+# Copyright (C) 2010-2019 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import nbd
+
+h = nbd.NBD ()
+h.connect_command (["nbdkit", "-s", "--exit-with-parent",
"-v",
+ "pattern", "size=512"])
+
+expected =
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00H\x00\x00\x00\x00\x00\x00\x00P\x00\x00\x00\x00\x00\x00\x00X\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x00\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00p\x00\x00\x00\x00\x00\x00\x00x\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x88\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00\x98\x00\x00\x00\x00\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x00\x00\xb0\x00\x00\x00\x00\x00\x00\x00\xb8\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x00\x00\xd8\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\xf8\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x08\x00\x00\x00\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00\x01\x18\x00\x00\x00\x00\x00\x00\x01
\x00\x00\x00\x00\x00\x00\x01(\x00\x00\x00\x00\x00\x00\x010\x00\x00\x00\x00\x00\x00\x018\x00\x00\x00\x00\x00\x00\x01@\x00\x00\x00\x00\x00\x00\x01H\x00\x00\x00\x00\x00\x00\x01P\x00\x00\x00\x00\x00\x00\x01X\x00\x00\x00\x00\x00\x00\x01`\x00\x00\x00\x00\x00\x00\x01h\x00\x00\x00\x00\x00\x00\x01p\x00\x00\x00\x00\x00\x00\x01x\x00\x00\x00\x00\x00\x00\x01\x80\x00\x00\x00\x00\x00\x00\x01\x88\x00\x00\x00\x00\x00\x00\x01\x90\x00\x00\x00\x00\x00\x00\x01\x98\x00\x00\x00\x00\x00\x00\x01\xa0\x00\x00\x00\x00\x00\x00\x01\xa8\x00\x00\x00\x00\x00\x00\x01\xb0\x00\x00\x00\x00\x00\x00\x01\xb8\x00\x00\x00\x00\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x00\x01\xc8\x00\x00\x00\x00\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x00\x01\xd8\x00\x00\x00\x00\x00\x00\x01\xe0\x00\x00\x00\x00\x00\x00\x01\xe8\x00\x00\x00\x00\x00\x00\x01\xf0\x00\x00\x00\x00\x00\x00\x01\xf8'
+
+def f (data, buf2, offset, s):
+ assert data == 42
+ assert buf2 == expected
+ assert offset == 0
+ assert s == nbd.READ_DATA
+
+buf = h.pread_callback (512, 0, 42, f)
+
+print ("%r" % buf)
+
+assert buf == expected
diff --git a/tests/oldstyle.c b/tests/oldstyle.c
index a0b594c..920136a 100644
--- a/tests/oldstyle.c
+++ b/tests/oldstyle.c
@@ -23,6 +23,7 @@
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
+#include <errno.h>
#include <libnbd.h>
@@ -30,14 +31,52 @@
#define XSTR(s) #s
#define STR(s) XSTR(s)
+static char wbuf[512] = { 1, 2, 3, 4 }, rbuf[512];
+static const char *progname;
+
+static int
+pread_cb (void *data, const void *buf, size_t count, uint64_t offset,
+ int status)
+{
+ int *calls = data;
+ ++*calls;
+
+ if (buf != rbuf || count != sizeof rbuf) {
+ fprintf (stderr, "%s: callback called with wrong buffer\n", progname);
+ exit (EXIT_FAILURE);
+ }
+ if (offset != 2 * sizeof rbuf) {
+ fprintf (stderr, "%s: callback called with wrong offset\n", progname);
+ exit (EXIT_FAILURE);
+ }
+ if (status != LIBNBD_READ_DATA) {
+ fprintf (stderr, "%s: callback called with wrong status\n", progname);
+ exit (EXIT_FAILURE);
+ }
+
+ if (memcmp (rbuf, wbuf, sizeof rbuf) != 0) {
+ fprintf (stderr, "%s: DATA INTEGRITY ERROR!\n", progname);
+ exit (EXIT_FAILURE);
+ }
+
+ if (*calls > 1) {
+ errno = EPROTO; /* Something NBD servers can't send */
+ return -1;
+ }
+
+ return 0;
+}
+
int
main (int argc, char *argv[])
{
struct nbd_handle *nbd;
- char wbuf[512] = { 1, 2, 3, 4 }, rbuf[512];
int64_t r;
char *args[] = { "nbdkit", "-s", "-o",
"--exit-with-parent", "-v",
"memory", "size=" STR(SIZE), NULL };
+ int calls = 0;
+
+ progname = argv[0];
nbd = nbd_create ();
if (nbd == NULL) {
@@ -61,12 +100,13 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
- if (nbd_pwrite (nbd, wbuf, sizeof wbuf, 0, 0) == -1) {
+ /* Plain I/O */
+ if (nbd_pwrite (nbd, wbuf, sizeof wbuf, 2 * sizeof wbuf, 0) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
}
- if (nbd_pread (nbd, rbuf, sizeof rbuf, 0, 0) == -1) {
+ if (nbd_pread (nbd, rbuf, sizeof rbuf, 2 * sizeof rbuf, 0) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
}
@@ -76,6 +116,34 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
+ /* Test again for callback operation. */
+ memset (rbuf, 0, sizeof rbuf);
+ if (nbd_pread_callback (nbd, rbuf, sizeof rbuf, 2 * sizeof rbuf,
+ &calls, pread_cb, 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ if (calls != 1) {
+ fprintf (stderr, "%s: callback called wrong number of times\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+ if (memcmp (rbuf, wbuf, sizeof rbuf) != 0) {
+ fprintf (stderr, "%s: DATA INTEGRITY ERROR!\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Also test that callback errors are reflected correctly. */
+ if (nbd_pread_callback (nbd, rbuf, sizeof rbuf, 2 * sizeof rbuf,
+ &calls, pread_cb, 0) != -1) {
+ fprintf (stderr, "%s: expected failure from callback\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+ if (nbd_get_errno () != EPROTO) {
+ fprintf (stderr, "%s: wrong errno value after failed callback\n",
argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
if (nbd_shutdown (nbd) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
--
2.20.1