4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 #include <nbdkit-plugin.h>
43 #ifdef HAVE_RUBY_VERSION_H
44 #include <ruby/version.h>
47 #include "array-size.h"
49 static VALUE nbdkit_module
= Qnil
;
50 static int last_error
;
53 set_error (VALUE self
, VALUE arg
)
58 if (TYPE (arg
) == T_CLASS
) {
59 v
= rb_const_get (arg
, rb_intern ("Errno"));
61 } else if (TYPE (arg
) == T_OBJECT
) {
62 v
= rb_funcall (arg
, rb_intern ("errno"), 0);
68 nbdkit_set_error (err
);
77 ruby_init_loadpath ();
79 nbdkit_module
= rb_define_module ("Nbdkit");
80 rb_define_module_function (nbdkit_module
, "set_error", set_error
, 1);
83 /* https://stackoverflow.com/questions/11086549/how-to-rb-protect-everything-in-ruby */
85 struct callback_data
{
86 VALUE receiver
; /* object being called */
87 ID method_id
; /* method on object being called */
88 int argc
; /* number of args */
89 VALUE argv
[MAX_ARGS
]; /* list of args */
93 callback_dispatch (VALUE datav
)
95 struct callback_data
*data
= (struct callback_data
*) datav
;
96 return rb_funcall2 (data
->receiver
, data
->method_id
, data
->argc
, data
->argv
);
99 enum exception_class
{
101 EXCEPTION_NO_METHOD_ERROR
,
106 funcall2 (VALUE receiver
, ID method_id
, int argc
, volatile VALUE
*argv
,
107 enum exception_class
*exception_happened
)
109 struct callback_data data
;
112 volatile VALUE ret
, exn
, message
, backtrace
, b
;
114 assert (argc
<= MAX_ARGS
);
116 data
.receiver
= receiver
;
117 data
.method_id
= method_id
;
119 for (i
= 0; i
< argc
; ++i
)
120 data
.argv
[i
] = argv
[i
];
122 ret
= rb_protect (callback_dispatch
, (VALUE
) &data
, &state
);
124 /* An exception was thrown. Get the per-thread exception. */
127 /* We treat NoMethodError specially. */
128 if (rb_obj_is_kind_of (exn
, rb_eNoMethodError
)) {
129 if (exception_happened
)
130 *exception_happened
= EXCEPTION_NO_METHOD_ERROR
;
133 if (exception_happened
)
134 *exception_happened
= EXCEPTION_OTHER
;
136 /* Print the exception. */
137 message
= rb_funcall (exn
, rb_intern ("to_s"), 0);
138 nbdkit_error ("ruby: %s", StringValueCStr (message
));
140 /* Try to print the backtrace (a list of strings) if it exists. */
141 backtrace
= rb_funcall (exn
, rb_intern ("backtrace"), 0);
142 if (! NIL_P (backtrace
)) {
143 len
= RARRAY_LEN (backtrace
);
144 for (i
= 0; i
< len
; ++i
) {
145 b
= rb_ary_entry (backtrace
, i
);
146 nbdkit_error ("ruby: frame #%zu %s", i
, StringValueCStr (b
));
151 /* Reset the current thread exception. */
152 rb_set_errinfo (Qnil
);
156 if (exception_happened
)
157 *exception_happened
= NO_EXCEPTION
;
162 static const char *script
= NULL
;
163 static void *code
= NULL
;
166 plugin_rb_unload (void)
168 if (ruby_cleanup (0) != 0)
169 nbdkit_error ("ruby_cleanup failed");
173 plugin_rb_dump_plugin (void)
175 #ifdef RUBY_API_VERSION_MAJOR
176 printf ("ruby_api_version=%d", RUBY_API_VERSION_MAJOR
);
177 #ifdef RUBY_API_VERSION_MINOR
178 printf (".%d", RUBY_API_VERSION_MINOR
);
179 #ifdef RUBY_API_VERSION_TEENY
180 printf (".%d", RUBY_API_VERSION_TEENY
);
189 assert (code
!= NULL
);
191 (void) funcall2 (Qnil
, rb_intern ("dump_plugin"), 0, NULL
, NULL
);
195 plugin_rb_config (const char *key
, const char *value
)
197 /* The first parameter must be "script". */
201 if (strcmp (key
, "script") != 0) {
202 nbdkit_error ("the first parameter must be script=/path/to/ruby/script.rb");
207 nbdkit_debug ("ruby: loading script %s", script
);
209 /* Load the Ruby script into the interpreter. */
210 const char *options
[] = { "--", script
};
211 code
= ruby_options (ARRAY_SIZE (options
), (char **) options
);
213 /* Check if we managed to compile the Ruby script to code. */
214 if (!ruby_executable_node (code
, &state
)) {
215 nbdkit_error ("could not compile ruby script (%s, state=%d)",
220 /* Execute the Ruby script. */
221 state
= ruby_exec_node (code
);
223 nbdkit_error ("could not execute ruby script (%s, state=%d)",
231 volatile VALUE argv
[2];
232 enum exception_class exception_happened
;
234 argv
[0] = rb_str_new2 (key
);
235 argv
[1] = rb_str_new2 (value
);
236 (void) funcall2 (Qnil
, rb_intern ("config"), 2, argv
, &exception_happened
);
237 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
238 /* No config method, emulate what core nbdkit does if the
239 * config callback is NULL.
241 nbdkit_error ("%s: this plugin does not need command line configuration",
245 else if (exception_happened
== EXCEPTION_OTHER
)
253 plugin_rb_config_complete (void)
255 enum exception_class exception_happened
;
258 nbdkit_error ("the first parameter must be script=/path/to/ruby/script.rb");
262 assert (code
!= NULL
);
264 (void) funcall2 (Qnil
, rb_intern ("config_complete"), 0, NULL
,
265 &exception_happened
);
266 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
)
267 return 0; /* no config_complete method defined, ignore */
268 else if (exception_happened
== EXCEPTION_OTHER
)
275 plugin_rb_open (int readonly
)
277 volatile VALUE argv
[1];
279 enum exception_class exception_happened
;
281 argv
[0] = readonly
? Qtrue
: Qfalse
;
282 rv
= funcall2 (Qnil
, rb_intern ("open"), 1, argv
, &exception_happened
);
283 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
284 nbdkit_error ("%s: missing callback: %s", script
, "open");
287 else if (exception_happened
== EXCEPTION_OTHER
)
294 plugin_rb_close (void *handle
)
296 volatile VALUE argv
[1];
298 argv
[0] = (VALUE
) handle
;
299 (void) funcall2 (Qnil
, rb_intern ("close"), 1, argv
, NULL
);
300 /* OK to ignore exceptions here, if they are important then an error
301 * was printed already.
306 plugin_rb_get_size (void *handle
)
308 volatile VALUE argv
[1];
310 enum exception_class exception_happened
;
312 argv
[0] = (VALUE
) handle
;
313 rv
= funcall2 (Qnil
, rb_intern ("get_size"), 1, argv
, &exception_happened
);
314 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
315 nbdkit_error ("%s: missing callback: %s", script
, "get_size");
318 else if (exception_happened
== EXCEPTION_OTHER
)
325 plugin_rb_pread (void *handle
, void *buf
,
326 uint32_t count
, uint64_t offset
)
328 volatile VALUE argv
[3];
330 enum exception_class exception_happened
;
332 argv
[0] = (VALUE
) handle
;
333 argv
[1] = ULL2NUM (count
);
334 argv
[2] = ULL2NUM (offset
);
335 rv
= funcall2 (Qnil
, rb_intern ("pread"), 3, argv
, &exception_happened
);
336 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
337 nbdkit_error ("%s: missing callback: %s", script
, "pread");
340 else if (exception_happened
== EXCEPTION_OTHER
)
343 if (RSTRING_LEN (rv
) < count
) {
344 nbdkit_error ("%s: byte array returned from pread is too small",
349 memcpy (buf
, RSTRING_PTR (rv
), count
);
354 plugin_rb_pwrite (void *handle
, const void *buf
,
355 uint32_t count
, uint64_t offset
)
357 volatile VALUE argv
[3];
358 enum exception_class exception_happened
;
360 argv
[0] = (VALUE
) handle
;
361 argv
[1] = rb_str_new (buf
, count
);
362 argv
[2] = ULL2NUM (offset
);
363 (void) funcall2 (Qnil
, rb_intern ("pwrite"), 3, argv
, &exception_happened
);
364 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
365 nbdkit_error ("%s: missing callback: %s", script
, "pwrite");
368 else if (exception_happened
== EXCEPTION_OTHER
)
375 plugin_rb_flush (void *handle
)
377 volatile VALUE argv
[1];
378 enum exception_class exception_happened
;
380 argv
[0] = (VALUE
) handle
;
381 (void) funcall2 (Qnil
, rb_intern ("flush"), 1, argv
, &exception_happened
);
382 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
383 nbdkit_error ("%s: not implemented: %s", script
, "flush");
386 else if (exception_happened
== EXCEPTION_OTHER
)
393 plugin_rb_trim (void *handle
, uint32_t count
, uint64_t offset
)
395 volatile VALUE argv
[3];
396 enum exception_class exception_happened
;
398 argv
[0] = (VALUE
) handle
;
399 argv
[1] = ULL2NUM (count
);
400 argv
[2] = ULL2NUM (offset
);
401 (void) funcall2 (Qnil
, rb_intern ("trim"), 3, argv
, &exception_happened
);
402 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
403 nbdkit_error ("%s: not implemented: %s", script
, "trim");
406 else if (exception_happened
== EXCEPTION_OTHER
)
413 plugin_rb_zero (void *handle
, uint32_t count
, uint64_t offset
, int may_trim
)
415 volatile VALUE argv
[4];
416 enum exception_class exception_happened
;
418 argv
[0] = (VALUE
) handle
;
419 argv
[1] = ULL2NUM (count
);
420 argv
[2] = ULL2NUM (offset
);
421 argv
[3] = may_trim
? Qtrue
: Qfalse
;
423 (void) funcall2 (Qnil
, rb_intern ("zero"), 4, argv
, &exception_happened
);
424 if (last_error
== EOPNOTSUPP
|| last_error
== ENOTSUP
||
425 exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
426 nbdkit_debug ("zero falling back to pwrite");
427 nbdkit_set_error (EOPNOTSUPP
);
430 else if (exception_happened
== EXCEPTION_OTHER
)
437 plugin_rb_can_write (void *handle
)
439 volatile VALUE argv
[1];
441 enum exception_class exception_happened
;
443 argv
[0] = (VALUE
) handle
;
444 rv
= funcall2 (Qnil
, rb_intern ("can_write"), 1, argv
, &exception_happened
);
445 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
)
446 /* Fall back to checking if the pwrite method exists. */
447 rv
= rb_funcall (Qnil
, rb_intern ("respond_to?"),
448 2, ID2SYM (rb_intern ("pwrite")), Qtrue
);
449 else if (exception_happened
== EXCEPTION_OTHER
)
456 plugin_rb_can_flush (void *handle
)
458 volatile VALUE argv
[1];
460 enum exception_class exception_happened
;
462 argv
[0] = (VALUE
) handle
;
463 rv
= funcall2 (Qnil
, rb_intern ("can_flush"), 1, argv
, &exception_happened
);
464 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
)
465 /* Fall back to checking if the flush method exists. */
466 rv
= rb_funcall (Qnil
, rb_intern ("respond_to?"),
467 2, ID2SYM (rb_intern ("flush")), Qtrue
);
468 else if (exception_happened
== EXCEPTION_OTHER
)
475 plugin_rb_is_rotational (void *handle
)
477 volatile VALUE argv
[1];
479 enum exception_class exception_happened
;
481 argv
[0] = (VALUE
) handle
;
482 rv
= funcall2 (Qnil
, rb_intern ("is_rotational"), 1, argv
,
483 &exception_happened
);
484 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
)
486 else if (exception_happened
== EXCEPTION_OTHER
)
493 plugin_rb_can_trim (void *handle
)
495 volatile VALUE argv
[1];
497 enum exception_class exception_happened
;
499 argv
[0] = (VALUE
) handle
;
500 rv
= funcall2 (Qnil
, rb_intern ("can_trim"), 1, argv
, &exception_happened
);
501 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
)
502 /* Fall back to checking if the trim method exists. */
503 rv
= rb_funcall (Qnil
, rb_intern ("respond_to?"),
504 2, ID2SYM (rb_intern ("trim")), Qtrue
);
505 else if (exception_happened
== EXCEPTION_OTHER
)
511 #define plugin_rb_config_help \
512 "script=<FILENAME> (required) The Ruby plugin to run.\n" \
513 "[other arguments may be used by the plugin that you load]"
515 /* Ruby is inherently unsafe to call in parallel from multiple
518 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS
520 static struct nbdkit_plugin plugin
= {
522 .version
= PACKAGE_VERSION
,
524 .load
= plugin_rb_load
,
525 .unload
= plugin_rb_unload
,
526 .dump_plugin
= plugin_rb_dump_plugin
,
528 .config
= plugin_rb_config
,
529 .config_complete
= plugin_rb_config_complete
,
530 .config_help
= plugin_rb_config_help
,
532 .open
= plugin_rb_open
,
533 .close
= plugin_rb_close
,
535 .get_size
= plugin_rb_get_size
,
536 .can_write
= plugin_rb_can_write
,
537 .can_flush
= plugin_rb_can_flush
,
538 .is_rotational
= plugin_rb_is_rotational
,
539 .can_trim
= plugin_rb_can_trim
,
541 .pread
= plugin_rb_pread
,
542 .pwrite
= plugin_rb_pwrite
,
543 .flush
= plugin_rb_flush
,
544 .trim
= plugin_rb_trim
,
545 .zero
= plugin_rb_zero
,
548 NBDKIT_REGISTER_PLUGIN (plugin
)