On Wed, Oct 26, 2022 at 05:18:01PM -0500, Eric Blake wrote: > We have already exposed nbdkit_shutdown() as a way for a plugin to > request that nbdkit itself shuts down when convenient (taking away the > current and all other client connections). But we lacked a way for a > plugin (or filter) to drop the current client, while still leaving > nbdkit up for other clients. This has utility for testing client > reconnect logic, as well as for emulating things like qemu's > propensity to hard-disconnect on NBD_CMD_WRITE with more than 32M of > data (even though nbdkit would normally accept up to 64M of data). > Add a new API nbdkit_disconnect(int force) for that purpose, utilizing > the logic already built in to connections.c. If force is true, we > close the socket immediately (the client won't get responses to the > current or any other parallel commands); if force is false, we mark > the socket as not accepting further commands (current commands can > still give their response to the client, but new requests from the > current client are ignored with ESHUTDOWN until the client gives > NBD_CMD_DISC). > > This patch provides the C interface (using int, to allow for future > extensions beyond our current two values), and straightforward > bindings to languages that already have nbdkit_shutdown bound (here, > we use bool where it exists, since we don't have to worry as much > about potential future API breaks if we need a third state in the > future). Upcoming patches will then work on extending the sh/eval > plugin to take advantage of the API, as well as enhancing the > blocksize-policy filter to more closely emulate qemu. > > The logic for connection status changes slightly - there is now a > fourth state tracking when we want a soft shutdown, but are still > waiting for the client to send NBD_CMD_DISC - existing commands still > get to send their reply unmolested, but future commands get an > immediate ESHUTDOWN error. Rather than re-checking the state between > dropping the read lock and grabbing the write lock, we now go by the > state present after recv() first gives us a result at the start of a > transaction. > > The testsuite covers both hard and soft disconnects, and in both > plaintext and tls. > --- > docs/nbdkit-plugin.pod | 17 ++++- > include/nbdkit-common.h | 3 +- > tests/Makefile.am | 28 ++++++++ > server/internal.h | 1 + > server/connections.c | 3 +- > server/nbdkit.syms | 3 +- > server/protocol.c | 4 +- > server/public.c | 14 +++- > server/test-public.c | 10 ++- > plugins/ocaml/NBDKit.mli | 5 +- > plugins/ocaml/NBDKit.ml | 1 + > plugins/ocaml/bindings.c | 12 +++- > plugins/python/modfunctions.c | 14 ++++ > plugins/rust/src/lib.rs | 6 ++ > tests/test-disconnect-tls.sh | 126 +++++++++++++++++++++++++++++++++ > tests/test-disconnect.sh | 100 ++++++++++++++++++++++++++ > tests/test-disconnect-plugin.c | 95 +++++++++++++++++++++++++ > 17 files changed, 431 insertions(+), 11 deletions(-) > create mode 100755 tests/test-disconnect-tls.sh > create mode 100755 tests/test-disconnect.sh > create mode 100644 tests/test-disconnect-plugin.c > > diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod > index 6e74ccad..d338cde8 100644 > --- a/docs/nbdkit-plugin.pod > +++ b/docs/nbdkit-plugin.pod > @@ -1339,7 +1339,8 @@ Plugins and filters can call L<exit(3)> in the > configuration phase > > Once nbdkit has started serving connections, plugins and filters > should not call L<exit(3)>. However they may instruct nbdkit to shut > -down by calling C<nbdkit_shutdown>: > +down by calling C<nbdkit_shutdown>, or to disconnect a single client > +by calling C<nbdkit_disconnect>: > > void nbdkit_shutdown (void); > > @@ -1349,6 +1350,20 @@ the plugin and all filters are unloaded cleanly which > may take some > time. Further callbacks from nbdkit into the plugin or filter may > occur after you have called this. > > + void nbdkit_disconnect (int force); > + > +This function requests that the current connection be disconnected > +(I<note> that it does not affect other connections, and the client may > +try to reconnect). It is only useful from connected callbacks (that > +is, after C<.open> and before C<.close>). If C<force> is true, nbdkit > +will disconnect the client immediately, and the client will not > +receive any response to the current command or any other commands in > +flight in parallel threads; otherwise, nbdkit will not accept any new > +commands from the client (failing all commands other than > +C<NBD_CMD_DISC> with C<ESHUTDOWN>), but will allow existing commands > +to complete gracefully. Either way, the next callback for the current > +connection should be C<.close>. > + > =head1 PARSING COMMAND LINE PARAMETERS > > =head2 Parsing numbers > diff --git a/include/nbdkit-common.h b/include/nbdkit-common.h > index b0dcf715..dfdbab68 100644 > --- a/include/nbdkit-common.h > +++ b/include/nbdkit-common.h > @@ -1,5 +1,5 @@ > /* nbdkit > - * Copyright (C) 2013-2020 Red Hat Inc. > + * Copyright (C) 2013-2022 Red Hat Inc. > * > * Redistribution and use in source and binary forms, with or without > * modification, are permitted provided that the following conditions are > @@ -137,6 +137,7 @@ NBDKIT_EXTERN_DECL (int64_t, nbdkit_peer_pid, (void)); > NBDKIT_EXTERN_DECL (int64_t, nbdkit_peer_uid, (void)); > NBDKIT_EXTERN_DECL (int64_t, nbdkit_peer_gid, (void)); > NBDKIT_EXTERN_DECL (void, nbdkit_shutdown, (void)); > +NBDKIT_EXTERN_DECL (void, nbdkit_disconnect, (int force)); > > NBDKIT_EXTERN_DECL (const char *, nbdkit_strdup_intern, > (const char *str)); > diff --git a/tests/Makefile.am b/tests/Makefile.am > index 530b22bd..e951381d 100644 > --- a/tests/Makefile.am > +++ b/tests/Makefile.am > @@ -253,6 +253,8 @@ TESTS += \ > test-long-name.sh \ > test-flush.sh \ > test-swap.sh \ > + test-disconnect.sh \ > + test-disconnect-tls.sh \ > test-shutdown.sh \ > test-nbdkit-backend-debug.sh \ > test-read-password.sh \ > @@ -293,6 +295,8 @@ EXTRA_DIST += \ > test-read-password.sh \ > test-read-password-interactive.sh \ > test-read-password-plugin.c \ > + test-disconnect.sh \ > + test-disconnect-tls.sh \ > test-shutdown.sh \ > test-single-from-file.sh \ > test-single-sh.sh \ > @@ -376,6 +380,30 @@ test_flush_plugin_la_LDFLAGS = \ > $(NULL) > test_flush_plugin_la_LIBADD = $(IMPORT_LIBRARY_ON_WINDOWS) > > +# check_LTLIBRARIES won't build a shared library (see automake manual). > +# So we have to do this and add a dependency. > +noinst_LTLIBRARIES += \ > + test-disconnect-plugin.la \ > + $(NULL) > +test-disconnect.sh: test-disconnect-plugin.la > +test-disconnect-tls.sh: test-disconnect-plugin.la keys.psk > + > +test_disconnect_plugin_la_SOURCES = \ > + test-disconnect-plugin.c \ > + $(top_srcdir)/include/nbdkit-plugin.h \ > + $(NULL) > +test_disconnect_plugin_la_CPPFLAGS = \ > + -I$(top_srcdir)/include \ > + -I$(top_builddir)/include \ > + $(NULL) > +test_disconnect_plugin_la_CFLAGS = $(WARNINGS_CFLAGS) > +# For use of the -rpath option, see: > +# https://lists.gnu.org/archive/html/libtool/2007-07/msg00067.html > +test_disconnect_plugin_la_LDFLAGS = \ > + -module -avoid-version -shared $(NO_UNDEFINED_ON_WINDOWS) -rpath > /nowhere \ > + $(NULL) > +test_disconnect_plugin_la_LIBADD = $(IMPORT_LIBRARY_ON_WINDOWS) > + > # check_LTLIBRARIES won't build a shared library (see automake manual). > # So we have to do this and add a dependency. > noinst_LTLIBRARIES += \ > diff --git a/server/internal.h b/server/internal.h > index 69b4302c..229d707a 100644 > --- a/server/internal.h > +++ b/server/internal.h > @@ -235,6 +235,7 @@ struct context { > typedef enum { > STATUS_DEAD, /* Connection is closed */ > STATUS_CLIENT_DONE, /* Client has sent NBD_CMD_DISC */ > + STATUS_SHUTDOWN, /* Server wants soft shutdown */ > STATUS_ACTIVE, /* Client can make requests */ > } conn_status; > > diff --git a/server/connections.c b/server/connections.c > index 1b6183df..4d776f2a 100644 > --- a/server/connections.c > +++ b/server/connections.c > @@ -90,7 +90,8 @@ connection_set_status (conn_status value) > pthread_mutex_lock (&conn->status_lock)) > abort (); > if (value < conn->status) { > - if (conn->nworkers && conn->status > STATUS_CLIENT_DONE) { > + if (conn->nworkers && conn->status > STATUS_CLIENT_DONE && > + value <= STATUS_CLIENT_DONE) { > char c = 0; > > assert (conn->status_pipe[1] >= 0); > diff --git a/server/nbdkit.syms b/server/nbdkit.syms > index 0e897680..45cf3b45 100644 > --- a/server/nbdkit.syms > +++ b/server/nbdkit.syms > @@ -1,5 +1,5 @@ > # nbdkit > -# Copyright (C) 2018-2021 Red Hat Inc. > +# Copyright (C) 2018-2022 Red Hat Inc. > # > # Redistribution and use in source and binary forms, with or without > # modification, are permitted provided that the following conditions are > @@ -44,6 +44,7 @@ > nbdkit_context_get_backend; > nbdkit_context_set_next; > nbdkit_debug; > + nbdkit_disconnect; > nbdkit_error; > nbdkit_export_name; > nbdkit_exports_count; > diff --git a/server/protocol.c b/server/protocol.c > index d1e01502..cc1e4ed8 100644 > --- a/server/protocol.c > +++ b/server/protocol.c > @@ -631,10 +631,10 @@ protocol_recv_request_send_reply (void) > /* Read the request packet. */ > { > ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&conn->read_lock); > + r = conn->recv (&request, sizeof request); > cs = connection_get_status (); > if (cs <= STATUS_CLIENT_DONE) > return; > - r = conn->recv (&request, sizeof request); > if (r == -1) { > nbdkit_error ("read request: %m"); > connection_set_status (STATUS_DEAD); > @@ -718,7 +718,7 @@ protocol_recv_request_send_reply (void) > } > > /* Perform the request. Only this part happens inside the request lock. */ > - if (quit || connection_get_status () == STATUS_CLIENT_DONE) { > + if (quit || cs < STATUS_ACTIVE) { > error = ESHUTDOWN; > } > else { > diff --git a/server/public.c b/server/public.c > index 6a9840bb..c2f67451 100644 > --- a/server/public.c > +++ b/server/public.c > @@ -728,7 +728,7 @@ nbdkit_nanosleep (unsigned sec, unsigned nsec) > bool has_quit = quit; > assert (has_quit || > (conn && conn->nworkers > 0 && > - connection_get_status () < STATUS_ACTIVE) || > + connection_get_status () < STATUS_SHUTDOWN) || > (conn && (fds[2].revents & (POLLRDHUP | POLLHUP | POLLERR | > POLLNVAL)))); > if (has_quit) > @@ -1097,3 +1097,15 @@ nbdkit_printf_intern (const char *fmt, ...) > va_end (ap); > return ret; > } > + > +NBDKIT_DLL_PUBLIC void > +nbdkit_disconnect (int force) > +{ > + struct connection *conn = threadlocal_get_conn (); > + > + if (!conn) { > + debug ("no connection in this thread, ignoring disconnect request"); > + return; > + } > + connection_set_status (force ? STATUS_DEAD : STATUS_SHUTDOWN); > +} > diff --git a/server/test-public.c b/server/test-public.c > index 1d83354f..4e4d8a2e 100644 > --- a/server/test-public.c > +++ b/server/test-public.c > @@ -63,6 +63,8 @@ nbdkit_debug (const char *fs, ...) > > bool listen_stdin; > bool configured; > +bool verbose; > +int tls; > > volatile int quit; > #ifndef WIN32 > @@ -89,14 +91,18 @@ connection_get_status (void) > abort (); > } > > +void > +connection_set_status (conn_status v) > +{ > + abort (); > +} > + > const char * > backend_default_export (struct backend *b, int readonly) > { > abort (); > } > > -int tls; > - > /* Unit tests. */ > > static bool > diff --git a/plugins/ocaml/NBDKit.mli b/plugins/ocaml/NBDKit.mli > index cc389ca0..cdfacf69 100644 > --- a/plugins/ocaml/NBDKit.mli > +++ b/plugins/ocaml/NBDKit.mli > @@ -1,6 +1,6 @@ > (* hey emacs, this is OCaml code: -*- tuareg -*- *) > (* nbdkit OCaml interface > - * Copyright (C) 2014-2020 Red Hat Inc. > + * Copyright (C) 2014-2022 Red Hat Inc. > * > * Redistribution and use in source and binary forms, with or without > * modification, are permitted provided that the following conditions are > @@ -166,6 +166,9 @@ val export_name : unit -> string > (** Binding for [nbdkit_shutdown]. Requests the server shut down. *) > val shutdown : unit -> unit > > +(** Binding for [nbdkit_disconnect]. Requests disconnecting current client. > *) > +val disconnect : bool -> unit > + > (** Print a debug message when nbdkit is in verbose mode. *) > val debug : ('a, unit, string, unit) format4 -> 'a > > diff --git a/plugins/ocaml/NBDKit.ml b/plugins/ocaml/NBDKit.ml > index 3ad72acb..c94f4d57 100644 > --- a/plugins/ocaml/NBDKit.ml > +++ b/plugins/ocaml/NBDKit.ml > @@ -166,6 +166,7 @@ external realpath : string -> string = > "ocaml_nbdkit_realpath" > external nanosleep : int -> int -> unit = "ocaml_nbdkit_nanosleep" > external export_name : unit -> string = "ocaml_nbdkit_export_name" > external shutdown : unit -> unit = "ocaml_nbdkit_shutdown" [@@noalloc] > +external disconnect : bool -> unit = "ocaml_nbdkit_disconnect" [@@noalloc] > external _debug : string -> unit = "ocaml_nbdkit_debug" [@@noalloc] > let debug fs = ksprintf _debug fs > external version : unit -> string = "ocaml_nbdkit_version" > diff --git a/plugins/ocaml/bindings.c b/plugins/ocaml/bindings.c > index ba95fb4a..c6c152b9 100644 > --- a/plugins/ocaml/bindings.c > +++ b/plugins/ocaml/bindings.c > @@ -1,5 +1,5 @@ > /* nbdkit > - * Copyright (C) 2014-2020 Red Hat Inc. > + * Copyright (C) 2014-2022 Red Hat Inc. > * > * Redistribution and use in source and binary forms, with or without > * modification, are permitted provided that the following conditions are > @@ -165,6 +165,16 @@ ocaml_nbdkit_shutdown (value unitv) > CAMLreturn (Val_unit); > } > > +/* NB: noalloc function. */ > +NBDKIT_DLL_PUBLIC value > +ocaml_nbdkit_disconnect (value boolv) > +{ > + CAMLparam1 (boolv); > + > + nbdkit_disconnect (Bool_val (boolv)); > + CAMLreturn (Val_unit); > +} > + > /* NB: noalloc function. */ > NBDKIT_DLL_PUBLIC value > ocaml_nbdkit_debug (value strv) > diff --git a/plugins/python/modfunctions.c b/plugins/python/modfunctions.c > index 4cd45c3b..ac693923 100644 > --- a/plugins/python/modfunctions.c > +++ b/plugins/python/modfunctions.c > @@ -93,6 +93,18 @@ do_shutdown (PyObject *self, PyObject *args) > Py_RETURN_NONE; > } > > +/* nbdkit.disconnect */ > +static PyObject * > +do_disconnect (PyObject *self, PyObject *args) > +{ > + int force; > + > + if (!PyArg_ParseTuple (args, "p:disconnect", &force)) > + return NULL; > + nbdkit_disconnect (force); > + Py_RETURN_NONE; > +} > + > /* nbdkit.parse_size */ > static PyObject * > parse_size (PyObject *self, PyObject *args) > @@ -121,6 +133,8 @@ static PyMethodDef NbdkitMethods[] = { > "Store an errno value prior to throwing an exception" }, > { "shutdown", do_shutdown, METH_NOARGS, > "Request asynchronous shutdown" }, > + { "disconnect", do_disconnect, METH_VARARGS, > + "Request disconnection from current client" }, > { NULL } > }; > > diff --git a/plugins/rust/src/lib.rs b/plugins/rust/src/lib.rs > index 128334ef..a5b88e85 100644 > --- a/plugins/rust/src/lib.rs > +++ b/plugins/rust/src/lib.rs > @@ -1046,6 +1046,7 @@ extern "C" { > fn nbdkit_peer_name( addr: *mut libc::sockaddr, > addrlen: *mut libc::socklen_t) -> c_int; > fn nbdkit_shutdown(); > + fn nbdkit_disconnect(force: bool); > fn nbdkit_stdio_safe() -> c_int; > } > > @@ -1106,6 +1107,11 @@ pub fn shutdown() { > unsafe { nbdkit_shutdown() }; > } > > +/// Request nbdkit to disconnect the current client. > +pub fn disconnect(force: bool) { > + unsafe { nbdkit_disconnect(force) }; > +} > + > #[doc(hidden)] > #[repr(C)] > pub struct Plugin { > diff --git a/tests/test-disconnect-tls.sh b/tests/test-disconnect-tls.sh > new file mode 100755 > index 00000000..00049b07 > --- /dev/null > +++ b/tests/test-disconnect-tls.sh > @@ -0,0 +1,126 @@ > +#!/usr/bin/env bash > +# nbdkit > +# Copyright (C) 2019-2022 Red Hat Inc. > +# > +# 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. > + > +source ./functions.sh > +set -x > + > +requires nbdsh -c 'exit(not h.supports_tls())' > + > +# Does the nbdkit binary support TLS? > +if ! nbdkit --dump-config | grep -sq tls=yes; then > + echo "$0: nbdkit built without TLS support" > + exit 77 > +fi > + > +# Did we create the PSK keys file? > +# Probably 'certtool' is missing. > +if [ ! -s keys.psk ]; then > + echo "$0: PSK keys file was not created by the test harness" > + exit 77 > +fi > + > +plugin=.libs/test-disconnect-plugin.$SOEXT > +requires test -f $plugin > + > +sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) > +files="disconnect-tls.pid $sock" > +cleanup_fn rm -f $files > + > +# Start nbdkit with the disconnect plugin, which has delayed reads and > +# does disconnect on write based on export name. > +start_nbdkit -P disconnect-tls.pid --tls require --tls-psk=keys.psk \ > + -U $sock $plugin > + > +pid=`cat disconnect-tls.pid` > + > +# We can't use 'nbdsh -u "$uri" because of nbd_set_uri_allow_local_file. > +# Empty export name does soft disconnect on write; the write and the > +# pending read should still succeed, but second read attempt should fail. > +nbdsh -c ' > +import errno > + > +h.set_tls(nbd.TLS_REQUIRE) > +h.set_tls_psk_file("keys.psk") > +h.set_tls_username("qemu") > +h.connect_unix("'"$sock"'") > + > +buf = nbd.Buffer(1) > +c1 = h.aio_pread(buf, 1) > +c2 = h.aio_pwrite(buf, 2) > +h.poll(-1) > +assert h.aio_peek_command_completed() == c2 > +h.aio_command_completed(c2) > +c3 = h.aio_pread(buf, 3) > +h.poll(-1) > +assert h.aio_peek_command_completed() == c3 > +try: > + h.aio_command_completed(c3) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.ESHUTDOWN > +h.poll(-1) > +assert h.aio_peek_command_completed() == c1 > +h.aio_command_completed(c1) > +h.shutdown() > +' > + > +# Non-empty export name does hard disconnect on write. The write and the > +# pending read should fail with lost connection. > +nbdsh -c ' > +import errno > + > +h.set_tls(nbd.TLS_REQUIRE) > +h.set_tls_psk_file("keys.psk") > +h.set_tls_username("qemu") > +h.set_export_name("a") > +h.connect_unix("'"$sock"'") > + > +buf = nbd.Buffer(1) > +c1 = h.aio_pread(buf, 1) > +c2 = h.aio_pwrite(buf, 2) > +while h.aio_in_flight() > 1: > + h.poll(-1) > +assert h.aio_is_ready() is False > +try: > + h.aio_command_completed(c1) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.ENOTCONN > +try: > + h.aio_command_completed(c2) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.ENOTCONN > +' > + > +# nbdkit should still be running > +kill -s 0 $pid > diff --git a/tests/test-disconnect.sh b/tests/test-disconnect.sh > new file mode 100755 > index 00000000..1551dc03 > --- /dev/null > +++ b/tests/test-disconnect.sh > @@ -0,0 +1,100 @@ > +#!/usr/bin/env bash > +# nbdkit > +# Copyright (C) 2019-2022 Red Hat Inc. > +# > +# 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. > + > +source ./functions.sh > +set -x > + > +requires_nbdsh_uri > + > +plugin=.libs/test-disconnect-plugin.$SOEXT > +requires test -f $plugin > + > +sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) > +files="disconnect.pid $sock" > +cleanup_fn rm -f $files > + > +# Start nbdkit with the disconnect plugin, which has delayed reads and > +# does disconnect on write based on export name. > +start_nbdkit -P disconnect.pid -U $sock $plugin > + > +pid=`cat disconnect.pid` > + > +# Empty export name does soft disconnect on write; the write and the > +# pending read should still succeed, but second read attempt should fail. > +nbdsh -u "nbd+unix:///?socket=$sock" -c ' > +import errno > + > +buf = nbd.Buffer(1) > +c1 = h.aio_pread(buf, 1) > +c2 = h.aio_pwrite(buf, 2) > +h.poll(-1) > +assert h.aio_peek_command_completed() == c2 > +h.aio_command_completed(c2) > +c3 = h.aio_pread(buf, 3) > +h.poll(-1) > +assert h.aio_peek_command_completed() == c3 > +try: > + h.aio_command_completed(c3) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.ESHUTDOWN > +h.poll(-1) > +assert h.aio_peek_command_completed() == c1 > +h.aio_command_completed(c1) > +h.shutdown() > +' > + > +# Non-empty export name does hard disconnect on write. The write and the > +# pending read should fail with lost connection. > +nbdsh -u "nbd+unix:///a?socket=$sock" -c ' > +import errno > + > +buf = nbd.Buffer(1) > +c1 = h.aio_pread(buf, 1) > +c2 = h.aio_pwrite(buf, 2) > +while h.aio_in_flight() > 1: > + h.poll(-1) > +assert h.aio_is_ready() is False > +try: > + h.aio_command_completed(c1) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.ENOTCONN > +try: > + h.aio_command_completed(c2) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.ENOTCONN > +' > + > +# nbdkit should still be running > +kill -s 0 $pid > diff --git a/tests/test-disconnect-plugin.c b/tests/test-disconnect-plugin.c > new file mode 100644 > index 00000000..181b262f > --- /dev/null > +++ b/tests/test-disconnect-plugin.c > @@ -0,0 +1,95 @@ > +/* nbdkit > + * Copyright (C) 2013-2022 Red Hat Inc. > + * > + * 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 <string.h> > +#include <stdbool.h> > + > +#include <nbdkit-plugin.h> > + > +static void > +disconnect_unload (void) > +{ > + nbdkit_debug ("clean disconnect"); > +} > + > +static void * > +disconnect_open (int readonly) > +{ > + return NBDKIT_HANDLE_NOT_NEEDED; > +} > + > +static int64_t > +disconnect_get_size (void *handle) > +{ > + return 1024*1024; > +} > + > +#define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL > + > +/* Reads are delayed to show effect of disconnect on in-flight commands */ > +static int > +disconnect_pread (void *handle, void *buf, uint32_t count, uint64_t offset) > +{ > + memset (buf, 0, count); > + if (nbdkit_nanosleep (2, 0) == -1) > + nbdkit_debug ("read delay ended early, returning success anyway"); > + return 0; > +} > + > +/* Writing causes a disconnect; export name determines severity. */ > +static int > +disconnect_pwrite (void *handle, const void *buf, uint32_t count, > + uint64_t offset) > +{ > + const char *name = nbdkit_export_name (); > + bool hard = name && *name; > + nbdkit_debug ("%s disconnect triggered!", hard ? "hard" : "soft"); > + nbdkit_disconnect (hard); > + /* Despite the disconnect, we still claim the write succeeded */ > + return 0; > +} > + > +static struct nbdkit_plugin plugin = { > + .name = "disconnect", > + .version = PACKAGE_VERSION, > + .unload = disconnect_unload, > + .open = disconnect_open, > + .get_size = disconnect_get_size, > + .pread = disconnect_pread, > + .pwrite = disconnect_pwrite, > +}; > + > +NBDKIT_REGISTER_PLUGIN(plugin)
Reviewed-by: Richard W.M. Jones <rjo...@redhat.com> Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://people.redhat.com/~rjones/virt-top _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://listman.redhat.com/mailman/listinfo/libguestfs