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
[email protected]
https://listman.redhat.com/mailman/listinfo/libguestfs