With a new enough qemu-nbd, we can test that nbd_pread_structured for sane operation with interop/structured-read. It is also possible to test that the callback gets used sanely even for a connection lacks structured replies, by using nbdkit in tests/oldstyle. --- Not done here, because I'm not comfortable enough with OCaml, and because we don't have any pre-existing ocaml/examples using pread for doing actual buffer content validation. However, I did experiment enough to prove that this change: | diff --git i/ocaml/examples/extents.ml w/ocaml/examples/extents.ml | index b49f98e..78e6c4d 100644 | --- i/ocaml/examples/extents.ml | +++ w/ocaml/examples/extents.ml | @@ -30,4 +30,12 @@ let () = | printf "%d:\t%ld\t%s\n" i entries.(i*2) flags | done | ) | - ) | + ); | + | + let buf = Bytes.create 512 in | + NBD.pread_structured nbd buf 1024_L () ( | + fun () sub offs err stat -> | + let l = Bytes.length sub in | + printf "%d %Ld %d %d\n" l offs err stat | + ); | + printf "done\n" was sufficient to get './run ocaml/examples/extents.bc' to output the desired '512 1024 0 1', so it looks like the callback is wired up correctly. --- .gitignore | 1 + interop/Makefile.am | 11 +- interop/structured-read.c | 166 +++++++++++++++++++++++++++++++ interop/structured-read.sh | 57 +++++++++++ python/t/405-pread-structured.py | 37 +++++++ tests/oldstyle.c | 78 ++++++++++++++- 6 files changed, 345 insertions(+), 5 deletions(-) create mode 100644 interop/structured-read.c create mode 100755 interop/structured-read.sh create mode 100644 python/t/405-pread-structured.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..ea47254 --- /dev/null +++ b/interop/structured-read.c @@ -0,0 +1,166 @@ +/* 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 error, 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 (!error || (data->fail && data->count == 1)); + 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_structured (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_structured (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-structured.py b/python/t/405-pread-structured.py new file mode 100644 index 0000000..51f0144 --- /dev/null +++ b/python/t/405-pread-structured.py @@ -0,0 +1,37 @@ +# 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, err, s): + assert data == 42 + assert buf2 == expected + assert offset == 0 + assert err == 0 + assert s == nbd.READ_DATA + +buf = h.pread_structured (512, 0, 42, f) + +print ("%r" % buf) + +assert buf == expected diff --git a/tests/oldstyle.c b/tests/oldstyle.c index a0b594c..38f5130 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,56 @@ #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 error, 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 (error != 0) { + fprintf (stderr, "%s: callback called with unexpected error\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 +104,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 +120,34 @@ main (int argc, char *argv[]) exit (EXIT_FAILURE); } + /* Test again for callback operation. */ + memset (rbuf, 0, sizeof rbuf); + if (nbd_pread_structured (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_structured (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