It's easy to expose new callbacks to sh plugins, by borrowing tri-state code from can_fua. It's possible that nbdkit emulate will actually work well (in our example.sh script, the kernel caching a pread from one dd invocation may indeed speed up the next access), but for the sake of the example, I demonstrated advertising a no-op handler. The shell plugin, coupled with Rich's work on libnbd as a client-side library for actually exercising calls to NBD_CMD_CACHE, will be a useful way to prove that cache commands even make it through the stack. (Remember, qemu 3.0 was released with a fatally flawed NBD_CMD_CACHE server implementation, because there were no open source clients at the time that could actually send the command to test the server with). Signed-off-by: Eric Blake <eblake@redhat.com> --- plugins/sh/nbdkit-sh-plugin.pod | 27 ++++++++--- plugins/sh/sh.c | 81 +++++++++++++++++++++++++++++++++ plugins/sh/example.sh | 7 +++ 3 files changed, 109 insertions(+), 6 deletions(-) diff --git a/plugins/sh/nbdkit-sh-plugin.pod b/plugins/sh/nbdkit-sh-plugin.pod index 8af88b4..39b99a2 100644 --- a/plugins/sh/nbdkit-sh-plugin.pod +++ b/plugins/sh/nbdkit-sh-plugin.pod @@ -220,7 +220,7 @@ This method is required. 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>, C<trim>, C<zero> and C<extents> methods will never be +C<flush>, C<trim>, C<zero>, and C<extents> 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. @@ -243,13 +243,20 @@ The script should exit with code C<0> for true or code C<3> for false. =item C<can_fua> +=item C<can_cache> + /path/to/script can_fua <handle> + /path/to/script can_cache <handle> -This controls Forced Unit Access (FUA) behaviour of the core server. +These control Forced Unit Access (FUA) and caching 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)>. +Unlike the other C<can_*> callbacks, these two are I<not> a boolean. +They must print either "none", "emulate" or "native" to stdout. The +meaning of these is described in L<nbdkit-plugin(3)>. Furthermore, +you B<must> provide a C<can_cache> method if you desire the C<cache> +callback to be utilized, similar to the reasoning behind requiring +C<can_write> to utilize C<pwrite>. =item C<can_multi_conn> @@ -334,6 +341,14 @@ Unlike in other languages, if you provide an C<extents> method you B<must> also provide a C<can_extents> method which exits with code C<0> (true). +=item C<cache> + + /path/to/script cache <handle> <count> <offset> + +Unlike in other languages, if you provide a C<cache> method you +B<must> also provide a C<can_cache> method which prints "native" and +exits with code C<0> (true). + =back =head2 Missing callbacks @@ -365,4 +380,4 @@ Richard W.M. Jones =head1 COPYRIGHT -Copyright (C) 2018 Red Hat Inc. +Copyright (C) 2018-2019 Red Hat Inc. diff --git a/plugins/sh/sh.c b/plugins/sh/sh.c index a5beb57..862be21 100644 --- a/plugins/sh/sh.c +++ b/plugins/sh/sh.c @@ -578,6 +578,53 @@ sh_can_multi_conn (void *handle) return boolean_method (handle, "can_multi_conn"); } +/* Not a boolean method, the method prints "none", "emulate" or "native". */ +static int +sh_can_cache (void *handle) +{ + char *h = handle; + const char *args[] = { script, "can_cache", h, NULL }; + CLEANUP_FREE 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 (strcasecmp (s, "none") == 0) + r = NBDKIT_CACHE_NONE; + else if (strcasecmp (s, "emulate") == 0) + r = NBDKIT_CACHE_EMULATE; + else if (strcasecmp (s, "native") == 0) + r = NBDKIT_CACHE_NATIVE; + else { + nbdkit_error ("%s: could not parse output from can_cache method: %s", + script, s); + r = -1; + } + return r; + + case MISSING: + /* NBDKIT_CACHE_EMULATE means that nbdkit will call .pread. However + * we cannot know if that fallback would be efficient, so the safest + * default is to return NBDKIT_CACHE_NONE. + */ + return NBDKIT_CACHE_NONE; + + case ERROR: + return -1; + + case RET_FALSE: + nbdkit_error ("%s: %s method returned unexpected code (3/false)", + script, "can_cache"); + errno = EIO; + return -1; + + default: abort (); + } +} + static int sh_flush (void *handle, uint32_t flags) { @@ -782,6 +829,38 @@ sh_extents (void *handle, uint32_t count, uint64_t offset, uint32_t flags, } } +static int +sh_cache (void *handle, uint32_t count, uint64_t offset, uint32_t flags) +{ + char *h = handle; + char cbuf[32], obuf[32]; + const char *args[] = { script, "cache", h, cbuf, obuf, NULL }; + + snprintf (cbuf, sizeof cbuf, "%" PRIu32, count); + snprintf (obuf, sizeof obuf, "%" PRIu64, offset); + assert (!flags); + + switch (call (args)) { + case OK: + return 0; + + case MISSING: + /* Ignore lack of cache callback. */ + return 0; + + case ERROR: + return -1; + + case RET_FALSE: + nbdkit_error ("%s: %s method returned unexpected code (3/false)", + script, "cache"); + errno = EIO; + return -1; + + default: abort (); + } +} + #define sh_config_help \ "script=<FILENAME> (required) The shell script to run.\n" \ "[other arguments may be used by the plugin that you load]" @@ -812,6 +891,7 @@ static struct nbdkit_plugin plugin = { .can_extents = sh_can_extents, .can_fua = sh_can_fua, .can_multi_conn = sh_can_multi_conn, + .can_cache = sh_can_cache, .pread = sh_pread, .pwrite = sh_pwrite, @@ -819,6 +899,7 @@ static struct nbdkit_plugin plugin = { .trim = sh_trim, .zero = sh_zero, .extents = sh_extents, + .cache = sh_cache, .errno_is_preserved = 1, }; diff --git a/plugins/sh/example.sh b/plugins/sh/example.sh index 63228c8..60c46ce 100755 --- a/plugins/sh/example.sh +++ b/plugins/sh/example.sh @@ -133,6 +133,13 @@ case "$1" in fallocate --help >/dev/null 2>&1 || exit 3 ;; + can_cache) + # Caching is not advertised to the client unless can_cache prints + # a tri-state value. Here, we choose for caching to be a no-op, + # by omitting counterpart handling for 'cache'. + echo native + ;; + *) # Unknown methods must exit with code 2. exit 2 -- 2.20.1