Add a ruby language binding for the .zero callback, used for implementing NBD_CMD_WRITE_ZEROES. The caller doesn't have to return anything, but should use Nbdkit.set_error(errno::EOPNOTSUPP) to get an automatic fallback to pwrite. Enhance the example to show the use of the fallback mechanism, and to serve as a test of Nbdkit.set_error(). Signed-off-by: Eric Blake <eblake@redhat.com> --- plugins/ruby/example.rb | 11 +++++++++++ plugins/ruby/nbdkit-ruby-plugin.pod | 20 ++++++++++++++++++++ plugins/ruby/ruby.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/plugins/ruby/example.rb b/plugins/ruby/example.rb index 3238031..9a88f60 100644 --- a/plugins/ruby/example.rb +++ b/plugins/ruby/example.rb @@ -24,6 +24,8 @@ # ><fs> mount /dev/sda1 / # ><fs> [etc] +include Nbdkit + $disk = "\0" * (1024 * 1024) def config(key, value) @@ -50,3 +52,12 @@ def pwrite(h, buf, offset) # Hmm, is this using bytes or chars? XXX $disk[offset, buf.length] = buf end + +def zero(h, count, offset, may_trim) + if may_trim + $disk[offset, count] = "\0" * count + else + set_error(Errno::EOPNOTSUPP) + raise "falling back to pwrite" + end +end diff --git a/plugins/ruby/nbdkit-ruby-plugin.pod b/plugins/ruby/nbdkit-ruby-plugin.pod index 0131574..6cf8e97 100644 --- a/plugins/ruby/nbdkit-ruby-plugin.pod +++ b/plugins/ruby/nbdkit-ruby-plugin.pod @@ -220,6 +220,26 @@ The body of your C<trim> function should "punch a hole" in the backing store. If the trim fails, your function should throw an exception, optionally using C<Nbdkit.set_error> first. +=item C<zero> + +(Optional) + + def zero(h, count, offset, may_trim) + # no return value + +The body of your C<zero> function should ensure that C<count> bytes +of the disk, starting at C<offset>, will read back as zero. If +C<may_trim> is true, the operation may be optimized as a trim as long +as subsequent reads see zeroes. + +NBD only supports whole writes, so your function should try to +write the whole region (perhaps requiring a loop). If the write +fails or is partial, your function should throw an exception, +optionally using C<Nbdkit.set_error> first. In particular, if +you would like to automatically fall back to C<pwrite> (perhaps +because there is nothing to optimize if C<may_trim> is false), +use C<Nbdkit.set_error(Errno::EOPNOTSUPP)>. + =back =head2 MISSING CALLBACKS diff --git a/plugins/ruby/ruby.c b/plugins/ruby/ruby.c index fc2e8ad..33d7968 100644 --- a/plugins/ruby/ruby.c +++ b/plugins/ruby/ruby.c @@ -36,12 +36,14 @@ #include <stdio.h> #include <stdlib.h> #include <assert.h> +#include <errno.h> #include <nbdkit-plugin.h> #include <ruby.h> static VALUE nbdkit_module = Qnil; +static int last_error; static VALUE set_error(VALUE self, VALUE arg) @@ -58,6 +60,7 @@ set_error(VALUE self, VALUE arg) } else { err = NUM2INT(arg); } + last_error = err; nbdkit_set_error(err); return Qnil; } @@ -367,6 +370,30 @@ plugin_rb_trim (void *handle, uint32_t count, uint64_t offset) } static int +plugin_rb_zero (void *handle, uint32_t count, uint64_t offset, int may_trim) +{ + volatile VALUE argv[4]; + + argv[0] = (VALUE) handle; + argv[1] = ULL2NUM (count); + argv[2] = ULL2NUM (offset); + argv[3] = may_trim ? Qtrue : Qfalse; + exception_happened = 0; + last_error = 0; + (void) funcall2 (Qnil, rb_intern ("zero"), 4, argv); + if (last_error == EOPNOTSUPP || + exception_happened == EXCEPTION_NO_METHOD_ERROR) { + nbdkit_debug ("zero falling back to pwrite"); + nbdkit_set_error (EOPNOTSUPP); + return -1; + } + else if (exception_happened == EXCEPTION_OTHER) + return -1; + + return 0; +} + +static int plugin_rb_can_write (void *handle) { volatile VALUE argv[1]; @@ -483,6 +510,7 @@ static struct nbdkit_plugin plugin = { .pwrite = plugin_rb_pwrite, .flush = plugin_rb_flush, .trim = plugin_rb_trim, + .zero = plugin_rb_zero, .errno_is_reliable = plugin_rb_errno_is_reliable, }; -- 2.9.3