Similar to python and perl. But MUCH easier (especially considering that this is the first time I've every tried to run Ruby). I even had fun making set_error() polymorphic. Eric Blake (2): ruby: Expose nbdkit_set_error to ruby script ruby: Support zero callback plugins/ruby/example.rb | 11 ++++++++ plugins/ruby/nbdkit-ruby-plugin.pod | 54 +++++++++++++++++++++++++++++++++---- plugins/ruby/ruby.c | 51 +++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 5 deletions(-) -- 2.9.3
Eric Blake
2017-Feb-02 19:45 UTC
[Libguestfs] [nbdkit PATCH 1/2] ruby: Expose nbdkit_set_error to ruby script
In addition to calling ruby functions from C, we want to make script writing easier by exposing C functions to ruby. For now, just wrap nbdkit_set_error(), as that will be needed for an optimal implementation of a zero() callback. Note that Ruby makes it fairly easy to support polymorphic input, so that a user can write any of: err = Errno::EINVAL::Errno Nbdkit.set_error(err) Nbdkit.set_error(Errno::EINVAL) begin #... rescue SystemCallError => err Nbdkit.set_error(err) end Signed-off-by: Eric Blake <eblake@redhat.com> --- plugins/ruby/nbdkit-ruby-plugin.pod | 34 +++++++++++++++++++++++++++++----- plugins/ruby/ruby.c | 23 +++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/plugins/ruby/nbdkit-ruby-plugin.pod b/plugins/ruby/nbdkit-ruby-plugin.pod index 3f49cb4..0131574 100644 --- a/plugins/ruby/nbdkit-ruby-plugin.pod +++ b/plugins/ruby/nbdkit-ruby-plugin.pod @@ -56,9 +56,26 @@ does not need to be executable. In fact it's a good idea not to do that, because running the plugin directly as a Ruby script won't work. +=head2 METHODS + +Your script has access to the C<Nbdkit> module, with the following +singleton methods: + + Nbdkit.set_error(I<err>) + +Record C<err> as the reason you are about to raise an +exception. C<err> should either be a class that defines an C<Errno> +constant (all of the subclasses of C<SystemCallError> in module +C<Errno> have this property), an object that defines an C<errno> +method with no arguments (all instances of C<SystemCallError> have +this property), or an integer value corresponding to the usual errno +values. + =head2 EXCEPTIONS -Ruby callbacks should throw exceptions to indicate errors. +Ruby callbacks should throw exceptions to indicate errors. Remember +to use C<Nbdkit.set_error> if you need to control which error is sent +back to the client; if omitted, the client will see an error of C<EIO>. =head2 RUBY CALLBACKS @@ -156,7 +173,8 @@ disk starting at C<offset>. NBD only supports whole reads, so your function should try to read the whole region (perhaps requiring a loop). If the read fails or -is partial, your function should throw an exception. +is partial, your function should throw an exception, optionally using +C<Nbdkit.set_error> first. =item C<pwrite> @@ -173,7 +191,8 @@ C<offset>. 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. +fails or is partial, your function should throw an exception, optionally +using C<Nbdkit.set_error> first. =item C<flush> @@ -186,6 +205,9 @@ fails or is partial, your function should throw an exception. The body of your C<flush> function should do a L<sync(2)> or L<fdatasync(2)> or equivalent on the backing store. +If the flush fails, your function should throw an exception, optionally +using C<Nbdkit.set_error> first. + =item C<trim> (Optional) @@ -195,7 +217,8 @@ L<fdatasync(2)> or equivalent on the backing store. end The body of your C<trim> function should "punch a hole" in the -backing store. +backing store. If the trim fails, your function should throw an +exception, optionally using C<Nbdkit.set_error> first. =back @@ -211,7 +234,8 @@ constructs. =item Missing: C<errno_is_reliable> This is not needed because the process of gluing Ruby code into C cannot -reliably use C<errno>. +reliably use C<errno>. Instead, call C<Nbdkit.set_error> when reporting +a failure. =item Missing: C<name>, C<version>, C<longname>, C<description>, C<config_help> diff --git a/plugins/ruby/ruby.c b/plugins/ruby/ruby.c index 2da14f7..fc2e8ad 100644 --- a/plugins/ruby/ruby.c +++ b/plugins/ruby/ruby.c @@ -41,11 +41,34 @@ #include <ruby.h> +static VALUE nbdkit_module = Qnil; + +static VALUE +set_error(VALUE self, VALUE arg) +{ + int err; + VALUE v; + + if (TYPE(arg) == T_CLASS) { + v = rb_const_get(arg, rb_intern("Errno")); + err = NUM2INT(v); + } else if (TYPE(arg) == T_OBJECT) { + v = rb_funcall(arg, rb_intern("errno"), 0); + err = NUM2INT(v); + } else { + err = NUM2INT(arg); + } + nbdkit_set_error(err); + return Qnil; +} + static void plugin_rb_load (void) { ruby_init (); //ruby_init_loadpath (); - needed? XXX + nbdkit_module = rb_define_module("Nbdkit"); + rb_define_module_function(nbdkit_module, "set_error", set_error, 1); } /* Wrapper to make fb_funcall2 (only slightly) less insane. -- 2.9.3
Eric Blake
2017-Feb-02 19:45 UTC
[Libguestfs] [nbdkit PATCH 2/2] ruby: Support zero callback
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
Richard W.M. Jones
2017-Feb-03 15:30 UTC
Re: [Libguestfs] [nbdkit PATCH 2/2] ruby: Support zero callback
On Thu, Feb 02, 2017 at 01:45:38PM -0600, Eric Blake wrote:> 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().Thanks - pushed both. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://people.redhat.com/~rjones/virt-top