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