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(a)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