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