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