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
Apparently Analagous Threads
- [nbdkit PATCH 2/2] plugins: Permit ENOTSUP as synonym for EOPNOTSUPP
- [nbdkit PATCH 0/2] Ruby bindings for .zero
- [nbdkit PATCH v2 6/6] python: Support zero callback
- [nbdkit PATCH v3 4/4] python: Support zero callback
- [PATCH 1/2] Define .errno_is_preserved constant instead of a .errno_is_reliable callback.