On Tue, Nov 01, 2022 at 02:56:09PM -0500, Eric Blake wrote: ... > +=item S<6> > + > +Triggers a call to the C function C<nbdkit_disconnect> with C<force> > +set to false, which requests a soft disconnect of the current client > +(future client requests are rejected with C<ESHUTDOWN> without calling > +into the plugin, but current requests may complete). Since the client > +will likely get the response to this command, nbdkit then treats empty > +stderr as success for the current callback, and parses non-empty > +stderr as if the script had exited with code S<1>.
OK .. seems like complicated behaviour, but I can sort of see the reasoning behind it. I do wonder if we just want to use separate codes for the "soft disconnect + OK" and the "soft disconnect + error" cases. We have reserved more so we won't run out :-) > +=item 7-15 > + > +These exit codes are reserved for future use. Note that versions of > +nbdkit E<lt> 1.34 documented that codes S<8> through S<15> behaved This is actually a case where S<> around the nbdkit E<lt> 1.34 makes sense. But it should be removed around S<8> etc and in the next line below. > +like code S<1>; although it is unlikely that many scripts relied on > +this similarity in practice. [...] > diff --git a/tests/Makefile.am b/tests/Makefile.am > index d59b797c..3994fc6a 100644 > --- a/tests/Makefile.am > +++ b/tests/Makefile.am > @@ -768,12 +768,14 @@ TESTS += \ > test-eval-file.sh \ > test-eval-exports.sh \ > test-eval-cache.sh \ > + test-eval-disconnect.sh \ > $(NULL) > EXTRA_DIST += \ > test-eval.sh \ > test-eval-file.sh \ > test-eval-exports.sh \ > test-eval-cache.sh \ > + test-eval-disconnect.sh \ > $(NULL) > # file plugin test. > diff --git a/plugins/sh/call.h b/plugins/sh/call.h > index 76de23a3..44767e81 100644 > --- a/plugins/sh/call.h > +++ b/plugins/sh/call.h > @@ -1,5 +1,5 @@ > /* nbdkit > - * Copyright (C) 2018 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 > @@ -51,7 +51,12 @@ typedef enum exit_code { > OK = 0, > ERROR = 1, /* all script error codes are mapped to this */ > MISSING = 2, /* method missing */ > - RET_FALSE = 3 /* script exited with code 3 meaning false */ > + RET_FALSE = 3, /* script exited with code 3 meaning false */ > + SHUTDOWN = 4, /* call nbdkit_shutdown() before parsing stderr */ > + DISC_FORCE = 5, /* call nbdkit_disconnect(true) */ > + DISC_SOFT = 6, /* call nbdkit_disconnect(false) */ > + /* 7 is reserved since 1.8; handle like ERROR for now */ > + /* 8-15 are reserved since 1.34; handle like ERROR for now */ > } exit_code; > > extern exit_code call (const char **argv) > diff --git a/plugins/sh/call.c b/plugins/sh/call.c > index 2fa94d88..7d0f2b16 100644 > --- a/plugins/sh/call.c > +++ b/plugins/sh/call.c > @@ -1,5 +1,5 @@ > /* nbdkit > - * Copyright (C) 2018-2020 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 > @@ -397,17 +397,47 @@ call3 (const char *wbuf, size_t wbuflen, /* sent to > stdin (can be NULL) */ > return ret; > } > > -static void > -handle_script_error (const char *argv0, string *ebuf) > +/* Normalize return codes and parse error string. */ > +static exit_code > +handle_script_error (const char *argv0, string *ebuf, exit_code code) > { > int err; > size_t skip = 0; > char *p; > > - if (ebuf->len == 0) { > + switch (code) { > + case OK: > + case MISSING: > + case RET_FALSE: > + /* Script successful. */ > + return code; > + > + case SHUTDOWN: > + /* Use trigger, then handle empty ebuf specially below */ > + nbdkit_shutdown (); > + break; > + > + case DISC_FORCE: > + case DISC_SOFT: > + /* Use trigger, then handle empty ebuf specially below */ > + nbdkit_disconnect (code == DISC_FORCE); > + break; > + > + case ERROR: > + default: > + /* Error even if ebuf is empty */ > err = EIO; > + goto parse; > + } > + > + assert (code >= SHUTDOWN && code <= DISC_SOFT); > + if (ebuf->len == 0) > + return OK; > + err = ESHUTDOWN; > + > + parse: > + if (ebuf->len == 0) > goto no_error_message; > - } I guess if we had the separate codes then we wouldn't need the goto? > /* Recognize the errno values that match NBD protocol errors */ > if (ascii_strncasecmp (ebuf->ptr, "EPERM", 5) == 0) { > @@ -502,6 +532,7 @@ handle_script_error (const char *argv0, string *ebuf) > > /* Set errno. */ > errno = err; > + return ERROR; > } > > /* Call the script with parameters. Don't write to stdin or read from > @@ -516,19 +547,7 @@ call (const char **argv) > CLEANUP_FREE_STRING string ebuf = empty_vector; > > r = call3 (NULL, 0, &rbuf, &ebuf, argv); > - switch (r) { > - case OK: > - case MISSING: > - case RET_FALSE: > - /* Script successful. */ > - return r; > - > - case ERROR: > - default: > - /* Error case. */ > - handle_script_error (argv[0], &ebuf); > - return ERROR; > - } > + return handle_script_error (argv[0], &ebuf, r); > } > > /* Call the script with parameters. Read from stdout and return the > @@ -541,20 +560,10 @@ call_read (string *rbuf, const char **argv) > CLEANUP_FREE_STRING string ebuf = empty_vector; > > r = call3 (NULL, 0, rbuf, &ebuf, argv); > - switch (r) { > - case OK: > - case MISSING: > - case RET_FALSE: > - /* Script successful. */ > - return r; > - > - case ERROR: > - default: > - /* Error case. */ > + r = handle_script_error (argv[0], &ebuf, r); > + if (r == ERROR) > string_reset (rbuf); > - handle_script_error (argv[0], &ebuf); > - return ERROR; > - } > + return r; > } > > /* Call the script with parameters. Write to stdin of the script. > @@ -568,17 +577,5 @@ call_write (const char *wbuf, size_t wbuflen, const char > **argv) > CLEANUP_FREE_STRING string ebuf = empty_vector; > > r = call3 (wbuf, wbuflen, &rbuf, &ebuf, argv); > - switch (r) { > - case OK: > - case MISSING: > - case RET_FALSE: > - /* Script successful. */ > - return r; > - > - case ERROR: > - default: > - /* Error case. */ > - handle_script_error (argv[0], &ebuf); > - return ERROR; > - } > + return handle_script_error (argv[0], &ebuf, r); > } > diff --git a/tests/test-eval-disconnect.sh b/tests/test-eval-disconnect.sh > new file mode 100755 > index 00000000..8427e6fd > --- /dev/null > +++ b/tests/test-eval-disconnect.sh > @@ -0,0 +1,185 @@ > +#!/usr/bin/env bash > +# nbdkit > +# Copyright (C) 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. > + > +# Check shutdown/disconnect behavior triggered by special status values > + > +source ./functions.sh > +set -e > +set -x > + > +requires_plugin eval > +requires_nbdsh_uri > + > +sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) > +files="eval-disconnect.pid $sock" > +rm -f $files > +cleanup_fn rm -f $files > + > +# Start nbdkit with a plugin that fails writes according to the export > +# name which must be numeric: a positive value leaves stderr empty, a > +# non-positive one outputs EPERM first. Serve multiple clients. > +serve() > +{ > + rm -f $files > + start_nbdkit -P eval-disconnect.pid -U $sock eval \ > + get_size=' echo 1024 ' \ > + open=' if test $3 -le 0; then \ > + echo EPERM > $tmpdir/err; echo $((-$3)); \ > + else \ > + : > $tmpdir/err; echo $3; \ > + fi ' \ > + flush=' exit 0 ' \ > + pwrite=' cat >/dev/null; cat $tmpdir/err >&2; exit $2 ' > +} > +serve > + > +# Noisy status 0 succeeds, despite text to stderr > +nbdsh -u "nbd+unix:///0?socket=$sock" -c - <<\EOF > +h.pwrite(b"1", 0) > +h.flush() > +h.shutdown() > +EOF > + > +# Silent status 1 fails; nbdkit turns lack of error into EIO > +nbdsh -u "nbd+unix:///1?socket=$sock" -c - <<\EOF > +import errno > +try: > + h.pwrite(b"1", 0) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.EIO > +h.flush() > +h.shutdown() > +EOF > + > +# Noisy status 1 fails with supplied EPERM > +nbdsh -u "nbd+unix:///-1?socket=$sock" -c - <<\EOF > +import errno > +try: > + h.pwrite(b"1", 0) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.EPERM > +h.flush() > +h.shutdown() > +EOF > + > +# Silent status 5 kills socket; libnbd detects dead socket as ENOTCONN > +nbdsh -u "nbd+unix:///5?socket=$sock" -c - <<\EOF > +import errno > +try: > + h.pwrite(b"1", 0) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.ENOTCONN > +assert h.aio_is_ready() is False > +EOF > + > +# Noisy status 5 kills socket; libnbd detects dead socket as ENOTCONN > +nbdsh -u "nbd+unix:///-5?socket=$sock" -c - <<\EOF > +import errno > +try: > + h.pwrite(b"1", 0) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.ENOTCONN > +assert h.aio_is_ready() is False > +EOF > + > +# Silent status 6 succeeds, but next command fails with ESHUTDOWN > +nbdsh -u "nbd+unix:///6?socket=$sock" -c - <<\EOF > +import errno > +h.pwrite(b"1", 0) > +try: > + h.flush() > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.ESHUTDOWN > +h.shutdown() > +EOF > + > +# Noisy status 6 fails with supplied EPERM, next command fails with ESHUTDOWN > +nbdsh -u "nbd+unix:///-6?socket=$sock" -c - <<\EOF > +import errno > +try: > + h.pwrite(b"1", 0) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.EPERM > +try: > + h.flush() > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.ESHUTDOWN > +h.shutdown() > +EOF > + > +# Silent status 4 kills the server. It is racy whether client sees a reply > +# of success or sees the connection killed with libnbd giving ENOTCONN > +nbdsh -u "nbd+unix:///4?socket=$sock" -c - <<\EOF > +import errno > +try: > + h.pwrite(b"1", 0) > +except nbd.Error as ex: > + assert ex.errnum == errno.ENOTCONN > +EOF > + > +# Server should die shortly, if not already dead at this point. > +for (( i=0; i < 5; i++ )); do > + kill -s 0 "$(cat eval-disconnect.pid)" || break > + sleep 1 > +done > +if [ $i = 5 ]; then > + echo "nbdkit didn't exit fast enough" > + exit 1 > +fi > + > +# Restart server; noisy status 4 races between EPERM or ENOTCONN > +serve > +nbdsh -u "nbd+unix:///-4?socket=$sock" -c - <<\EOF > +import errno > +try: > + h.pwrite(b"1", 0) > + assert False > +except nbd.Error as ex: > + assert ex.errnum == errno.EPERM or ex.errnum == errno.ENOTCONN > +EOF > + > +# Server should die shortly, if not already dead at this point. > +for (( i=0; i < 5; i++ )); do > + kill -s 0 "$(cat eval-disconnect.pid)" || break > + sleep 1 > +done > +if [ $i = 5 ]; then > + echo "nbdkit didn't exit fast enough" > + exit 1 > +fi I'm not a big fan of these "omni-tests" (tests that test many related or even unrelated features). But I guess it's fine unless you wanted to split it. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 100 libraries supported. http://fedoraproject.org/wiki/MinGW _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://listman.redhat.com/mailman/listinfo/libguestfs