--- .gitignore | 1 + configure.ac | 6 ++- interop/Makefile.am | 24 ++++++++++++ interop/interop.c | 89 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index aa9e0bb..199e445 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,7 @@ Makefile.in /interop/interop-qemu-nbd /interop/interop-qemu-nbd-tls-certs /interop/interop-qemu-nbd-tls-psk +/interop/interop-qemu-storage-daemon /interop/list-exports-nbd-server /interop/list-exports-qemu-nbd /interop/socket-activation-nbdkit diff --git a/configure.ac b/configure.ac index 328c6cb..6bb057f 100644 --- a/configure.ac +++ b/configure.ac @@ -182,11 +182,15 @@ AS_IF([test "x$NBDKIT" != "x"], [ AM_CONDITIONAL([HAVE_NBDKIT], [test "x$NBDKIT" != "x" && test "x$have_nbdkit_features" = "xyes"]) -dnl nbd-server and qemu-nbd for interop testing. +dnl nbd-server, qemu-nbd and qemu-storage-daemon for interop testing. AC_CHECK_PROG([NBD_SERVER], [nbd-server], [nbd-server]) AM_CONDITIONAL([HAVE_NBD_SERVER], [test "x$NBD_SERVER" != "x"]) AC_CHECK_PROG([QEMU_NBD], [qemu-nbd], [qemu-nbd]) AM_CONDITIONAL([HAVE_QEMU_NBD], [test "x$QEMU_NBD" != "x"]) +AC_CHECK_PROG([QEMU_STORAGE_DAEMON], + [qemu-storage-daemon], [qemu-storage-daemon]) +AM_CONDITIONAL([HAVE_QEMU_STORAGE_DAEMON], + [test "x$QEMU_STORAGE_DAEMON" != "x"]) dnl glib2 main loop for examples that interoperate with the glib main loop. PKG_CHECK_MODULES([GLIB], [glib-2.0], [ diff --git a/interop/Makefile.am b/interop/Makefile.am index 9787c26..7712777 100644 --- a/interop/Makefile.am +++ b/interop/Makefile.am @@ -174,6 +174,30 @@ structured_read_LDADD = $(top_builddir)/lib/libnbd.la endif HAVE_QEMU_NBD +# We treat qemu-storage-daemon as effectively the same as qemu-nbd +# since they use the same code, but there is a single test to make +# sure we can use it. +if HAVE_QEMU_STORAGE_DAEMON + +check_PROGRAMS += interop-qemu-storage-daemon +TESTS += interop-qemu-storage-daemon + +# NB for upstream we must change --export parameters: +# device= -> node.name= +# name= -> id= +interop_qemu_storage_daemon_SOURCES = interop.c +interop_qemu_storage_daemon_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -DUNIXSOCKET=\"`mktemp -u /tmp/libnbd-interop-qsd.XXXXXX`\" \ + -DSERVER=\"$(QEMU_STORAGE_DAEMON)\" \ + -DSERVER_PARAMS='"--blockdev", SPRINTF ("raw,file.driver=file,file.filename=%s,node-name=disk0", tmpfile), "--nbd-server", SPRINTF ("addr.type=unix,addr.path=%s", UNIXSOCKET), "--export", "nbd,device=disk0,name=/"' \ + -DEXPORT_NAME='"/"' \ + $(NULL) +interop_qemu_storage_daemon_CFLAGS = $(WARNINGS_CFLAGS) +interop_qemu_storage_daemon_LDADD = $(top_builddir)/lib/libnbd.la + +endif HAVE_QEMU_STORAGE_DAEMON + if HAVE_NBDKIT check_PROGRAMS += \ diff --git a/interop/interop.c b/interop/interop.c index ec3e735..64608fc 100644 --- a/interop/interop.c +++ b/interop/interop.c @@ -35,6 +35,27 @@ #define SIZE (1024*1024) +struct free_on_exit { + struct free_on_exit *next; + void *v; +}; + +/* Macro which can be used in SERVER_PARAMS to deal with the case + * where a parameter must be embedded inside a parameter. The return + * value is automatically freed. + */ +#define SPRINTF(fs,...) \ + ({ \ + char *ret; \ + if (asprintf (&ret, (fs), ##__VA_ARGS__) == -1) abort (); \ + struct free_on_exit *f = malloc (sizeof *f); \ + if (!f) abort (); \ + f->next = free_on_exit_list; \ + f->v = ret; \ + free_on_exit_list = f; \ + ret; \ + }) + #if CERTS || PSK #define TLS 1 #ifndef TLS_MODE @@ -45,13 +66,17 @@ int main (int argc, char *argv[]) { + struct free_on_exit *free_on_exit_list = NULL; struct nbd_handle *nbd; char tmpfile[] = "/tmp/nbdXXXXXX"; - char *args[] = { SERVER, SERVER_PARAMS, NULL }; + size_t i; int fd; int64_t actual_size; char buf[512]; int r = -1; +#ifdef UNIXSOCKET + pid_t pid = 0; +#endif /* Create a large sparse temporary file. */ fd = mkstemp (tmpfile); @@ -62,6 +87,16 @@ main (int argc, char *argv[]) goto out; } + /* Server name and parameters. These come from -D options defined + * in Makefile.am. + */ + char *args[] = { SERVER, SERVER_PARAMS, NULL }; + printf ("server:"); + for (i = 0; args[i] != NULL; ++i) + printf (" %s", args[i]); + printf ("\n"); + fflush (stdout); + nbd = nbd_create (); if (nbd == NULL) { fprintf (stderr, "%s\n", nbd_get_error ()); @@ -103,6 +138,7 @@ main (int argc, char *argv[]) #endif /* Start the server. */ +#ifndef UNIXSOCKET #if SOCKET_ACTIVATION #define NBD_CONNECT nbd_connect_systemd_socket_activation #else @@ -112,6 +148,40 @@ main (int argc, char *argv[]) fprintf (stderr, "%s\n", nbd_get_error ()); goto out; } +#else /* UNIXSOCKET */ + /* This is for servers which don't support either stdin/stdout or + * systemd socket activation. We have to run the command in the + * background and connect to the Unix domain socket. + */ + pid = fork (); + if (pid == -1) { + perror ("fork"); + goto out; + } + if (pid == 0) { + execvp (args[0], args); + perror (args[0]); + _exit (EXIT_FAILURE); + } + /* Unfortunately qemu-storage-daemon doesn't support pidfiles, so we + * have to wait for the socket to be created then sleep "a bit". + */ + for (i = 0; i < 60; ++i) { + if (access (UNIXSOCKET, R_OK) == 0) + break; + sleep (1); + } + sleep (1); + /* Connect to the socket. */ + if (nbd_connect_unix (nbd, UNIXSOCKET) == -1) { + fprintf (stderr, "%s\n", nbd_get_error ()); + goto out; + } + /* We don't need the socket to exist in the filesystem any more, so + * we can remove it early. + */ + unlink (UNIXSOCKET); +#endif /* UNIXSOCKET */ #if TLS if (TLS_MODE == LIBNBD_TLS_REQUIRE) { @@ -179,5 +249,22 @@ main (int argc, char *argv[]) out: unlink (tmpfile); +#ifdef UNIXSOCKET + /* Kill the background server. */ + if (pid > 0) { + printf ("killing %s PID %d ...\n", args[0], (int) pid); + fflush (stdout); + kill (pid, SIGTERM); + } +#endif + + /* Free strings allocated by SPRINTF macro. */ + while (free_on_exit_list != NULL) { + struct free_on_exit *next = free_on_exit_list->next; + free (free_on_exit_list->v); + free (free_on_exit_list); + free_on_exit_list = next; + } + exit (r == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } -- 2.27.0