Richard W.M. Jones
2018-Dec-15 11:01 UTC
[Libguestfs] [PATCH nbdkit v2 0/4] tests: Test export flags (eflags).
v1 was here: https://www.redhat.com/archives/libguestfs/2018-December/thread.html#00123 v2: - Document "-" instead of "script=-" and use it in the test; and verify this also works on FreeBSD; and verify that it doesn't depend on the particular behaviour of our wrapper script and should work with installed nbdkit too. - Fix handling of zero flags parameter. - Add "to stdout" for can_fua. - "In future" -> "In the future," in various places. - Make the test work on FreeBSD by avoiding use of endian swaps. - Use socat "cool-write" to avoid occasional EPIPE error seen in tests. - Retest on Linux & FreeBSD. - Extra commit for a small documentation change in nbdkit-sh-plugin. Rich.
Richard W.M. Jones
2018-Dec-15 11:01 UTC
[Libguestfs] [PATCH nbdkit v2 1/4] sh: Implement inline scripts.
This implements a readonly disk reading as zeroes: nbdkit sh - <<'EOF' case "$1" in get_size) echo 1M ;; pread) dd if=/dev/zero count=$3 iflag=count_bytes ;; *) exit 2 ;; esac EOF Use of "-" is analogous to reading passwords from stdin. --- plugins/sh/nbdkit-sh-plugin.pod | 20 ++++++++++++ plugins/sh/sh.c | 55 ++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/plugins/sh/nbdkit-sh-plugin.pod b/plugins/sh/nbdkit-sh-plugin.pod index 371de5f..2765841 100644 --- a/plugins/sh/nbdkit-sh-plugin.pod +++ b/plugins/sh/nbdkit-sh-plugin.pod @@ -6,6 +6,10 @@ nbdkit-sh-plugin - nbdkit shell, script or executable plugin nbdkit sh /path/to/script [arguments...] + nbdkit sh - <<'EOF' + ... shell script ... + EOF + =head1 DESCRIPTION C<nbdkit-sh-plugin> allows you to write plugins for L<nbdkit(1)> using @@ -26,6 +30,22 @@ like this: You may have to add further C<key=value> arguments to the command line. +=head2 Inline shell scripts + +It is also possible to write a shell script plugin "inline" using C<-> +as the name of the script, like this: + + nbdkit sh - <<'EOF' + case "$1" in + get_size) echo 1M ;; + pread) dd if=/dev/zero count=$3 iflag=count_bytes ;; + *) exit 2 ;; + esac + EOF + +By default the inline script runs under F</bin/sh>. You can add a +shebang (C<#!>) to use other scripting languages. + =head1 WRITING AN NBDKIT SH PLUGIN For an example plugin written in Bash, see: diff --git a/plugins/sh/sh.c b/plugins/sh/sh.c index 368f1eb..4ca394f 100644 --- a/plugins/sh/sh.c +++ b/plugins/sh/sh.c @@ -39,6 +39,7 @@ #include <string.h> #include <unistd.h> #include <errno.h> +#include <sys/stat.h> #include <nbdkit-plugin.h> @@ -115,6 +116,50 @@ sh_dump_plugin (void) } } +/* This implements the "inline script" feature. Read stdin into a + * temporary file and return the name of the file which the caller + * must free. For convenience we put the temporary file into tmpdir + * but that's an implementation detail. + */ +static char * +inline_script (void) +{ + const char scriptname[] = "inline-script.sh"; + char *filename = NULL; + char *cmd = NULL; + + if (asprintf (&filename, "%s/%s", tmpdir, scriptname) == -1) { + nbdkit_error ("asprintf: %m"); + goto err; + } + + /* Safe because both the tmpdir and script name are controlled by us + * and don't contain characters that need quoting. + */ + if (asprintf (&cmd, "cat > %s", filename) == -1) { + nbdkit_error ("asprintf: %m"); + goto err; + } + + if (system (cmd) != 0) { + nbdkit_error ("sh: failed to copy inline script to temporary file"); + goto err; + } + + if (chmod (filename, 0500) == -1) { + nbdkit_error ("chmod: %s: %m", filename); + goto err; + } + + free (cmd); + return filename; + + err: + free (filename); + free (cmd); + return NULL; +} + static int sh_config (const char *key, const char *value) { @@ -124,7 +169,15 @@ sh_config (const char *key, const char *value) nbdkit_error ("the first parameter must be script=/path/to/script"); return -1; } - script = nbdkit_realpath (value); + + /* If the script name is not "-" then it's expected to be a + * filename, otherwise it's an inline script which must be read + * into a temporary file. Either way we want an absolute path. + */ + if (strcmp (value, "-") != 0) + script = nbdkit_realpath (value); + else + script = inline_script (); if (script == NULL) return -1; -- 2.19.2
Richard W.M. Jones
2018-Dec-15 11:01 UTC
[Libguestfs] [PATCH nbdkit v2 2/4] sh: docs: Add note that the script must be executable.
--- plugins/sh/nbdkit-sh-plugin.pod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/sh/nbdkit-sh-plugin.pod b/plugins/sh/nbdkit-sh-plugin.pod index 2765841..14fe7b8 100644 --- a/plugins/sh/nbdkit-sh-plugin.pod +++ b/plugins/sh/nbdkit-sh-plugin.pod @@ -28,7 +28,7 @@ like this: nbdkit sh /path/to/script You may have to add further C<key=value> arguments to the command -line. +line. The script must be executable (C<chmod +x>). =head2 Inline shell scripts -- 2.19.2
Richard W.M. Jones
2018-Dec-15 11:01 UTC
[Libguestfs] [PATCH nbdkit v2 3/4] sh: Switch nbdkit-sh-plugin to use API version 2.
This allows us to add the following callbacks: - can_zero returns boolean - can_fua should print "none", "emulate" or "native" Furthermore the following callbacks are modified in a backwards compatible manner: - pwrite adding flags parameter - trim adding flags parameter This change is not backwards compatible: - zero may_trim parameter replaced by flags parameter --- plugins/sh/nbdkit-sh-plugin.pod | 43 +++++++--- plugins/sh/sh.c | 139 +++++++++++++++++++++++++++----- 2 files changed, 154 insertions(+), 28 deletions(-) diff --git a/plugins/sh/nbdkit-sh-plugin.pod b/plugins/sh/nbdkit-sh-plugin.pod index 14fe7b8..4cea076 100644 --- a/plugins/sh/nbdkit-sh-plugin.pod +++ b/plugins/sh/nbdkit-sh-plugin.pod @@ -199,17 +199,21 @@ This method is required. =item C<can_trim> +=item C<can_zero> + Unlike in other languages, you B<must> provide the C<can_*> methods otherwise they are assumed to all return false and your C<pwrite>, -C<flush> and C<trim> methods will never be called. The reason for -this is obscure: In other languages we can detect if (eg) a C<pwrite> -method is defined and synthesize an appropriate response if no actual -C<can_write> method is defined. However detecting if methods are -present without running them is not possible with this plugin. +C<flush>, C<trim> and C<zero> methods will never be called. The +reason for this is obscure: In other languages we can detect if (eg) a +C<pwrite> method is defined and synthesize an appropriate response if +no actual C<can_write> method is defined. However detecting if +methods are present without running them is not possible with this +plugin. /path/to/script can_write <handle> /path/to/script can_flush <handle> /path/to/script can_trim <handle> + /path/to/script can_zero <handle> The script should exit with code C<0> for true or code C<3> for false. @@ -219,6 +223,16 @@ The script should exit with code C<0> for true or code C<3> for false. The script should exit with code C<0> for true or code C<3> for false. +=item C<can_fua> + + /path/to/script can_fua <handle> + +This controls Forced Unit Access (FUA) behaviour of the core server. + +Unlike the other C<can_*> callbacks, this one is I<not> a boolean. It +must print either "none", "emulate" or "native" to stdout. The +meaning of these is described in L<nbdkit-plugin(3)>. + =item C<pread> /path/to/script pread <handle> <count> <offset> @@ -230,10 +244,13 @@ This method is required. =item C<pwrite> - /path/to/script pwrite <handle> <count> <offset> + /path/to/script pwrite <handle> <count> <offset> <flags> The script should read the binary data to be written from stdin. +The C<flags> parameter can be an empty string or C<"fua">. In the +future, a comma-separated list of flags may be present. + Unlike in other languages, if you provide a C<pwrite> method you B<must> also provide a C<can_write> method which exits with code C<0> (true). @@ -248,16 +265,24 @@ B<must> also provide a C<can_flush> method which exits with code C<0> =item C<trim> - /path/to/script trim <handle> <count> <offset> + /path/to/script trim <handle> <count> <offset> <flags> + +The C<flags> parameter can be an empty string or C<"fua">. In the +future, a comma-separated list of flags may be present. Unlike in other languages, if you provide a C<trim> method you B<must> also provide a C<can_trim> method which exits with code C<0> (true). =item C<zero> - /path/to/script zero <handle> <count> <offset> <may_trim> + /path/to/script zero <handle> <count> <offset> <flags> -C<may_trim> will be either C<true> or C<false>. +The C<flags> parameter can be an empty string or a comma-separated +list of the flags: C<"fua"> and C<"may_trim"> (eg. C<"">, C<"fua">, +C<"fua,may_trim"> are all possible values). + +Unlike in other languages, if you provide a C<zero> method you B<must> +also provide a C<can_zero> method which exits with code C<0> (true). =back diff --git a/plugins/sh/sh.c b/plugins/sh/sh.c index 4ca394f..159052e 100644 --- a/plugins/sh/sh.c +++ b/plugins/sh/sh.c @@ -35,12 +35,15 @@ #include <stdio.h> #include <stdlib.h> +#include <stdbool.h> #include <inttypes.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/stat.h> +#define NBDKIT_API_VERSION 2 + #include <nbdkit-plugin.h> #include "call.h" @@ -360,7 +363,8 @@ sh_get_size (void *handle) } static int -sh_pread (void *handle, void *buf, uint32_t count, uint64_t offset) +sh_pread (void *handle, void *buf, uint32_t count, uint64_t offset, + uint32_t flags) { char *h = handle; char cbuf[32], obuf[32]; @@ -403,16 +407,53 @@ sh_pread (void *handle, void *buf, uint32_t count, uint64_t offset) } } +/* Convert NBDKIT_FLAG_* to flags string. */ +static void flag_append (const char *str, bool *comma, char **buf, size_t *len); + +static void +flags_string (uint32_t flags, char *buf, size_t len) +{ + bool comma = false; + + if (flags & NBDKIT_FLAG_FUA) + flag_append ("fua", &comma, &buf, &len); + + if (flags & NBDKIT_FLAG_MAY_TRIM) + flag_append ("may_trim", &comma, &buf, &len); +} + +static void +flag_append (const char *str, bool *comma, char **buf, size_t *len) +{ + size_t slen = strlen (str); + + if (*comma) { + /* Too short flags buffer is an internal error so abort. */ + if (*len <= 1) abort (); + strcpy (*buf, ","); + (*buf)++; + (*len)--; + } + + if (*len <= slen) abort (); + strcpy (*buf, str); + (*buf) += slen; + (*len) -= slen; + + *comma = true; +} + static int -sh_pwrite (void *handle, const void *buf, - uint32_t count, uint64_t offset) +sh_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset, + uint32_t flags) { char *h = handle; - char cbuf[32], obuf[32]; - const char *args[] = { script, "pwrite", h, cbuf, obuf, NULL }; + char cbuf[32], obuf[32], fbuf[32]; + const char *args[] = { script, "pwrite", h, cbuf, obuf, fbuf, NULL }; snprintf (cbuf, sizeof cbuf, "%" PRIu32, count); snprintf (obuf, sizeof obuf, "%" PRIu64, offset); + flags_string (flags, fbuf, sizeof fbuf); switch (call_write (buf, count, args)) { case OK: @@ -467,12 +508,6 @@ sh_can_flush (void *handle) return boolean_method (handle, "can_flush"); } -static int -sh_can_trim (void *handle) -{ - return boolean_method (handle, "can_trim"); -} - static int sh_is_rotational (void *handle) { @@ -480,7 +515,70 @@ sh_is_rotational (void *handle) } static int -sh_flush (void *handle) +sh_can_trim (void *handle) +{ + return boolean_method (handle, "can_trim"); +} + +static int +sh_can_zero (void *handle) +{ + return boolean_method (handle, "can_zero"); +} + +static int +sh_can_fua (void *handle) +{ + char *h = handle; + const char *args[] = { script, "can_fua", h, NULL }; + char *s = NULL; + size_t slen; + int r; + + switch (call_read (&s, &slen, args)) { + case OK: + if (slen > 0 && s[slen-1] == '\n') + s[slen-1] = '\0'; + if (strcmp (s, "none") == 0) + r = NBDKIT_FUA_NONE; + else if (strcmp (s, "emulate") == 0) + r = NBDKIT_FUA_EMULATE; + else if (strcmp (s, "native") == 0) + r = NBDKIT_FUA_NATIVE; + else { + nbdkit_error ("%s: could not parse output from can_fua method: %s", + script, s); + free (s); + return -1; + } + free (s); + return r; + + case MISSING: + free (s); + /* NBDKIT_FUA_EMULATE means that nbdkit will call .flush. However + * we cannot know if that callback exists, so the safest default + * is to return NBDKIT_FUA_NONE. + */ + return NBDKIT_FUA_NONE; + + case ERROR: + free (s); + return -1; + + case RET_FALSE: + free (s); + nbdkit_error ("%s: %s method returned unexpected code (3/false)", + script, "can_fua"); + errno = EIO; + return -1; + + default: abort (); + } +} + +static int +sh_flush (void *handle, uint32_t flags) { char *h = handle; const char *args[] = { script, "flush", h, NULL }; @@ -507,14 +605,15 @@ sh_flush (void *handle) } static int -sh_trim (void *handle, uint32_t count, uint64_t offset) +sh_trim (void *handle, uint32_t count, uint64_t offset, uint32_t flags) { char *h = handle; - char cbuf[32], obuf[32]; - const char *args[] = { script, "trim", h, cbuf, obuf, NULL }; + char cbuf[32], obuf[32], fbuf[32]; + const char *args[] = { script, "trim", h, cbuf, obuf, fbuf, NULL }; snprintf (cbuf, sizeof cbuf, "%" PRIu32, count); snprintf (obuf, sizeof obuf, "%" PRIu64, offset); + flags_string (flags, fbuf, sizeof fbuf); switch (call (args)) { case OK: @@ -538,15 +637,15 @@ sh_trim (void *handle, uint32_t count, uint64_t offset) } static int -sh_zero (void *handle, uint32_t count, uint64_t offset, int may_trim) +sh_zero (void *handle, uint32_t count, uint64_t offset, uint32_t flags) { char *h = handle; - char cbuf[32], obuf[32]; - const char *args[] = { script, "zero", h, cbuf, obuf, - may_trim ? "true" : "false", NULL }; + char cbuf[32], obuf[32], fbuf[32]; + const char *args[] = { script, "zero", h, cbuf, obuf, fbuf, NULL }; snprintf (cbuf, sizeof cbuf, "%" PRIu32, count); snprintf (obuf, sizeof obuf, "%" PRIu64, offset); + flags_string (flags, fbuf, sizeof fbuf); switch (call (args)) { case OK: @@ -596,6 +695,8 @@ static struct nbdkit_plugin plugin = { .can_flush = sh_can_flush, .is_rotational = sh_is_rotational, .can_trim = sh_can_trim, + .can_zero = sh_can_zero, + .can_fua = sh_can_fua, .pread = sh_pread, .pwrite = sh_pwrite, -- 2.19.2
Richard W.M. Jones
2018-Dec-15 11:01 UTC
[Libguestfs] [PATCH nbdkit v2 4/4] tests: Test export flags (eflags).
--- tests/Makefile.am | 3 + tests/test-eflags.sh | 221 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) diff --git a/tests/Makefile.am b/tests/Makefile.am index d463d6b..4e3e179 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -282,6 +282,9 @@ test_oldstyle_LDADD = libtest.la $(LIBGUESTFS_LIBS) endif HAVE_LIBGUESTFS +# Test export flags. +TESTS += test-eflags.sh + # common disk image shared with several tests if HAVE_GUESTFISH check_DATA += disk diff --git a/tests/test-eflags.sh b/tests/test-eflags.sh new file mode 100755 index 0000000..e3f2813 --- /dev/null +++ b/tests/test-eflags.sh @@ -0,0 +1,221 @@ +#!/usr/bin/env bash +# nbdkit +# Copyright (C) 2018 Red Hat Inc. +# All rights reserved. +# +# 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. + +# Test export flags. +# +# Run nbdkit with various can_* callbacks defined and with or without +# the -r flag, and check that nbdkit constructs the export flags +# controlling READ_ONLY, ROTATIONAL, SEND_TRIM, etc. as expected. +# +# We use the oldstyle (-o) protocol here because it's simpler to read +# out the eflags. We use the shell plugin because it gives maximum +# control over the can_* callbacks (at least, max without having to +# write a C plugin). + +source ./functions.sh +set -e + +if ! socat -h >/dev/null; then + echo "$0: 'socat' command not available" + exit 77 +fi + +# Check 'od' command exists. +if ! od </dev/null >/dev/null; then + echo "$0: 'od' command not available" + exit 77 +fi + +files="eflags.out" +rm -f $files +cleanup_fn rm -f $files + +# The export flags. +# See also src/protocol.h +HAS_FLAGS=$(( 1 << 0 )) +READ_ONLY=$(( 1 << 1 )) +SEND_FLUSH=$(( 1 << 2 )) +SEND_FUA=$(( 1 << 3 )) +ROTATIONAL=$(( 1 << 4 )) +SEND_TRIM=$(( 1 << 5 )) +SEND_WRITE_ZEROES=$(( 1 << 6 )) + +all_flags="HAS_FLAGS READ_ONLY SEND_FLUSH SEND_FUA ROTATIONAL SEND_TRIM SEND_WRITE_ZEROES" + +do_nbdkit () +{ + nbdkit -v -U - -o "$@" sh - --run ' + socat -b 28 unix-connect:$unixsocket \ + exec:"dd bs=1 skip=26 count=2 of=eflags.out",cool-write + ' + + # Protocol is big endian, we want native endian. + eflags=$(( $(od -An -N1 -tu1 eflags.out) << 8 | + $(od -An -N1 -j1 -tu1 eflags.out) )) + printf "eflags 0x%04x" $eflags + for f in $all_flags; do + [ $(( eflags & ${!f} )) -ne 0 ] && echo -n " $f" + done + echo +} + +fail () +{ + echo "$@" + exit 1 +} + +# no -r +# can_write=false + +do_nbdkit <<'EOF' +case "$1" in + get_size) echo 1M ;; + *) exit 2 ;; +esac +EOF + +[ $eflags -eq $(( HAS_FLAGS|READ_ONLY )) ] || + fail "expected HAS_FLAGS|READ_ONLY" + +# -r +# can_write=false + +do_nbdkit -r <<'EOF' +case "$1" in + get_size) echo 1M ;; + *) exit 2 ;; +esac +EOF + +[ $eflags -eq $(( HAS_FLAGS|READ_ONLY )) ] || + fail "expected HAS_FLAGS|READ_ONLY" + +# no -r +# can_write=true +# +# NBD_FLAG_SEND_WRITE_ZEROES is always set on writable connections +# even if can_zero returns false, because nbdkit reckons it can +# emulate zeroing using pwrite. + +do_nbdkit <<'EOF' +case "$1" in + get_size) echo 1M ;; + can_write) exit 0 ;; + *) exit 2 ;; +esac +EOF + +[ $eflags -eq $(( HAS_FLAGS|SEND_WRITE_ZEROES )) ] || + fail "expected HAS_FLAGS|SEND_WRITE_ZEROES" + +# -r +# can_write=true +# +# The -r flag overrides the plugin so this behaves as if can_write is +# false. + +do_nbdkit -r <<'EOF' +case "$1" in + get_size) echo 1M ;; + can_write) exit 0 ;; + *) exit 2 ;; +esac +EOF + +[ $eflags -eq $(( HAS_FLAGS|READ_ONLY )) ] || + fail "expected HAS_FLAGS|READ_ONLY" + +# can_write=false +# can_trim=true +# can_zero=true +# +# If writing is not possible then trim and zero are always disabled. + +do_nbdkit <<'EOF' +case "$1" in + get_size) echo 1M ;; + can_write) exit 3 ;; + can_trim) exit 0 ;; + can_zero) exit 0 ;; + *) exit 2 ;; +esac +EOF + +[ $eflags -eq $(( HAS_FLAGS|READ_ONLY )) ] || + fail "expected HAS_FLAGS|READ_ONLY" + +# can_write=true +# can_trim=true + +do_nbdkit <<'EOF' +case "$1" in + get_size) echo 1M ;; + can_write) exit 0 ;; + can_trim) exit 0 ;; + *) exit 2 ;; +esac +EOF + +[ $eflags -eq $(( HAS_FLAGS|SEND_TRIM|SEND_WRITE_ZEROES )) ] || + fail "expected HAS_FLAGS|SEND_TRIM|SEND_WRITE_ZEROES" + +# can_write=true +# is_rotational=true + +do_nbdkit <<'EOF' +case "$1" in + get_size) echo 1M ;; + can_write) exit 0 ;; + is_rotational) exit 0 ;; + *) exit 2 ;; +esac +EOF + +[ $eflags -eq $(( HAS_FLAGS|ROTATIONAL|SEND_WRITE_ZEROES )) ] || + fail "expected HAS_FLAGS|ROTATIONAL|SEND_WRITE_ZEROES" + +# can_write=true +# can_fua=native + +do_nbdkit <<'EOF' +case "$1" in + get_size) echo 1M ;; + can_write) exit 0 ;; + can_fua) echo "native" ;; + *) exit 2 ;; +esac +EOF + +[ $eflags -eq $(( HAS_FLAGS|SEND_FUA|SEND_WRITE_ZEROES )) ] || + fail "expected HAS_FLAGS|SEND_FUA|SEND_WRITE_ZEROES" -- 2.19.2
Eric Blake
2018-Dec-15 13:43 UTC
Re: [Libguestfs] [PATCH nbdkit v2 3/4] sh: Switch nbdkit-sh-plugin to use API version 2.
On 12/15/18 5:01 AM, Richard W.M. Jones wrote:> This allows us to add the following callbacks: > > - can_zero returns boolean > - can_fua should print "none", "emulate" or "native" > > Furthermore the following callbacks are modified in a backwards > compatible manner: > > - pwrite adding flags parameter > - trim adding flags parameter > > This change is not backwards compatible: > > - zero may_trim parameter replaced by flags parameter > ---> +=item C<can_fua> > + > + /path/to/script can_fua <handle> > + > +This controls Forced Unit Access (FUA) behaviour of the core server. > + > +Unlike the other C<can_*> callbacks, this one is I<not> a boolean. It > +must print either "none", "emulate" or "native" to stdout. The > +meaning of these is described in L<nbdkit-plugin(3)>.Presumably we don't care if the user includes or omits a trailing newline on their output?> +static int > +sh_can_fua (void *handle) > +{ > + char *h = handle; > + const char *args[] = { script, "can_fua", h, NULL }; > + char *s = NULL; > + size_t slen; > + int r; > + > + switch (call_read (&s, &slen, args)) { > + case OK: > + if (slen > 0 && s[slen-1] == '\n') > + s[slen-1] = '\0';Good - you do ignore trailing newline. Makes it easier for the author to not worry about it either way.> + > + case RET_FALSE: > + free (s); > + nbdkit_error ("%s: %s method returned unexpected code (3/false)", > + script, "can_fua"); > + errno = EIO; > + return -1; > +So you decided not to take the suggestion of a false return being equivalent to a successful output of "none". I can live with that. Series is looking good on my quick glance through. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org