2 * Copyright (C) 2013-2018 Red Hat Inc.
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 static VALUE nbdkit_module
= Qnil
;
48 static int last_error
;
51 set_error (VALUE self
, VALUE arg
)
56 if (TYPE(arg
) == T_CLASS
) {
57 v
= rb_const_get (arg
, rb_intern ("Errno"));
59 } else if (TYPE (arg
) == T_OBJECT
) {
60 v
= rb_funcall (arg
, rb_intern ("errno"), 0);
66 nbdkit_set_error (err
);
75 ruby_init_loadpath ();
77 nbdkit_module
= rb_define_module ("Nbdkit");
78 rb_define_module_function (nbdkit_module
, "set_error", set_error
, 1);
81 /* https://stackoverflow.com/questions/11086549/how-to-rb-protect-everything-in-ruby */
83 struct callback_data
{
84 VALUE receiver
; /* object being called */
85 ID method_id
; /* method on object being called */
86 int argc
; /* number of args */
87 VALUE argv
[MAX_ARGS
]; /* list of args */
91 callback_dispatch (VALUE datav
)
93 struct callback_data
*data
= (struct callback_data
*) datav
;
94 return rb_funcall2 (data
->receiver
, data
->method_id
, data
->argc
, data
->argv
);
97 enum exception_class
{
99 EXCEPTION_NO_METHOD_ERROR
,
104 funcall2 (VALUE receiver
, ID method_id
, int argc
, volatile VALUE
*argv
,
105 enum exception_class
*exception_happened
)
107 struct callback_data data
;
110 volatile VALUE ret
, exn
, message
, backtrace
, b
;
112 assert (argc
<= MAX_ARGS
);
114 data
.receiver
= receiver
;
115 data
.method_id
= method_id
;
117 for (i
= 0; i
< argc
; ++i
)
118 data
.argv
[i
] = argv
[i
];
120 ret
= rb_protect (callback_dispatch
, (VALUE
) &data
, &state
);
122 /* An exception was thrown. Get the per-thread exception. */
125 /* We treat NoMethodError specially. */
126 if (rb_obj_is_kind_of (exn
, rb_eNoMethodError
)) {
127 if (exception_happened
)
128 *exception_happened
= EXCEPTION_NO_METHOD_ERROR
;
131 if (exception_happened
)
132 *exception_happened
= EXCEPTION_OTHER
;
134 /* Print the exception. */
135 message
= rb_funcall (exn
, rb_intern ("to_s"), 0);
136 nbdkit_error ("ruby: %s", StringValueCStr (message
));
138 /* Try to print the backtrace (a list of strings) if it exists. */
139 backtrace
= rb_funcall (exn
, rb_intern ("backtrace"), 0);
140 if (! NIL_P (backtrace
)) {
141 len
= RARRAY_LEN (backtrace
);
142 for (i
= 0; i
< len
; ++i
) {
143 b
= rb_ary_entry (backtrace
, i
);
144 nbdkit_error ("ruby: frame #%zu %s", i
, StringValueCStr (b
));
149 /* Reset the current thread exception. */
150 rb_set_errinfo (Qnil
);
154 if (exception_happened
)
155 *exception_happened
= NO_EXCEPTION
;
160 static const char *script
= NULL
;
161 static void *code
= NULL
;
164 plugin_rb_unload (void)
166 if (ruby_cleanup (0) != 0)
167 nbdkit_error ("ruby_cleanup failed");
171 plugin_rb_dump_plugin (void)
173 #ifdef RUBY_API_VERSION_MAJOR
174 printf ("ruby_api_version=%d", RUBY_API_VERSION_MAJOR
);
175 #ifdef RUBY_API_VERSION_MINOR
176 printf (".%d", RUBY_API_VERSION_MINOR
);
177 #ifdef RUBY_API_VERSION_TEENY
178 printf (".%d", RUBY_API_VERSION_TEENY
);
187 assert (code
!= NULL
);
189 (void) funcall2 (Qnil
, rb_intern ("dump_plugin"), 0, NULL
, NULL
);
193 plugin_rb_config (const char *key
, const char *value
)
195 /* The first parameter must be "script". */
199 if (strcmp (key
, "script") != 0) {
200 nbdkit_error ("the first parameter must be script=/path/to/ruby/script.rb");
205 nbdkit_debug ("ruby: loading script %s", script
);
207 /* Load the Ruby script into the interpreter. */
208 const char *options
[] = { "--", script
};
209 code
= ruby_options (sizeof options
/ sizeof options
[0],
212 /* Check if we managed to compile the Ruby script to code. */
213 if (!ruby_executable_node (code
, &state
)) {
214 nbdkit_error ("could not compile ruby script (%s, state=%d)",
219 /* Execute the Ruby script. */
220 state
= ruby_exec_node (code
);
222 nbdkit_error ("could not execute ruby script (%s, state=%d)",
230 volatile VALUE argv
[2];
231 enum exception_class exception_happened
;
233 argv
[0] = rb_str_new2 (key
);
234 argv
[1] = rb_str_new2 (value
);
235 (void) funcall2 (Qnil
, rb_intern ("config"), 2, argv
, &exception_happened
);
236 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
237 /* No config method, emulate what core nbdkit does if the
238 * config callback is NULL.
240 nbdkit_error ("%s: this plugin does not need command line configuration",
244 else if (exception_happened
== EXCEPTION_OTHER
)
252 plugin_rb_config_complete (void)
254 enum exception_class exception_happened
;
257 nbdkit_error ("the first parameter must be script=/path/to/ruby/script.rb");
261 assert (code
!= NULL
);
263 (void) funcall2 (Qnil
, rb_intern ("config_complete"), 0, NULL
,
264 &exception_happened
);
265 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
)
266 return 0; /* no config_complete method defined, ignore */
267 else if (exception_happened
== EXCEPTION_OTHER
)
274 plugin_rb_open (int readonly
)
276 volatile VALUE argv
[1];
278 enum exception_class exception_happened
;
280 argv
[0] = readonly
? Qtrue
: Qfalse
;
281 rv
= funcall2 (Qnil
, rb_intern ("open"), 1, argv
, &exception_happened
);
282 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
283 nbdkit_error ("%s: missing callback: %s", script
, "open");
286 else if (exception_happened
== EXCEPTION_OTHER
)
293 plugin_rb_close (void *handle
)
295 volatile VALUE argv
[1];
297 argv
[0] = (VALUE
) handle
;
298 (void) funcall2 (Qnil
, rb_intern ("close"), 1, argv
, NULL
);
299 /* OK to ignore exceptions here, if they are important then an error
300 * was printed already.
305 plugin_rb_get_size (void *handle
)
307 volatile VALUE argv
[1];
309 enum exception_class exception_happened
;
311 argv
[0] = (VALUE
) handle
;
312 rv
= funcall2 (Qnil
, rb_intern ("get_size"), 1, argv
, &exception_happened
);
313 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
314 nbdkit_error ("%s: missing callback: %s", script
, "get_size");
317 else if (exception_happened
== EXCEPTION_OTHER
)
324 plugin_rb_pread (void *handle
, void *buf
,
325 uint32_t count
, uint64_t offset
)
327 volatile VALUE argv
[3];
329 enum exception_class exception_happened
;
331 argv
[0] = (VALUE
) handle
;
332 argv
[1] = ULL2NUM (count
);
333 argv
[2] = ULL2NUM (offset
);
334 rv
= funcall2 (Qnil
, rb_intern ("pread"), 3, argv
, &exception_happened
);
335 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
336 nbdkit_error ("%s: missing callback: %s", script
, "pread");
339 else if (exception_happened
== EXCEPTION_OTHER
)
342 if (RSTRING_LEN (rv
) < count
) {
343 nbdkit_error ("%s: byte array returned from pread is too small",
348 memcpy (buf
, RSTRING_PTR (rv
), count
);
353 plugin_rb_pwrite (void *handle
, const void *buf
,
354 uint32_t count
, uint64_t offset
)
356 volatile VALUE argv
[3];
357 enum exception_class exception_happened
;
359 argv
[0] = (VALUE
) handle
;
360 argv
[1] = rb_str_new (buf
, count
);
361 argv
[2] = ULL2NUM (offset
);
362 (void) funcall2 (Qnil
, rb_intern ("pwrite"), 3, argv
, &exception_happened
);
363 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
364 nbdkit_error ("%s: missing callback: %s", script
, "pwrite");
367 else if (exception_happened
== EXCEPTION_OTHER
)
374 plugin_rb_flush (void *handle
)
376 volatile VALUE argv
[1];
377 enum exception_class exception_happened
;
379 argv
[0] = (VALUE
) handle
;
380 (void) funcall2 (Qnil
, rb_intern ("flush"), 1, argv
, &exception_happened
);
381 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
382 nbdkit_error ("%s: not implemented: %s", script
, "flush");
385 else if (exception_happened
== EXCEPTION_OTHER
)
392 plugin_rb_trim (void *handle
, uint32_t count
, uint64_t offset
)
394 volatile VALUE argv
[3];
395 enum exception_class exception_happened
;
397 argv
[0] = (VALUE
) handle
;
398 argv
[1] = ULL2NUM (count
);
399 argv
[2] = ULL2NUM (offset
);
400 (void) funcall2 (Qnil
, rb_intern ("trim"), 3, argv
, &exception_happened
);
401 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
402 nbdkit_error ("%s: not implemented: %s", script
, "trim");
405 else if (exception_happened
== EXCEPTION_OTHER
)
412 plugin_rb_zero (void *handle
, uint32_t count
, uint64_t offset
, int may_trim
)
414 volatile VALUE argv
[4];
415 enum exception_class exception_happened
;
417 argv
[0] = (VALUE
) handle
;
418 argv
[1] = ULL2NUM (count
);
419 argv
[2] = ULL2NUM (offset
);
420 argv
[3] = may_trim
? Qtrue
: Qfalse
;
422 (void) funcall2 (Qnil
, rb_intern ("zero"), 4, argv
, &exception_happened
);
423 if (last_error
== EOPNOTSUPP
|| last_error
== ENOTSUP
||
424 exception_happened
== EXCEPTION_NO_METHOD_ERROR
) {
425 nbdkit_debug ("zero falling back to pwrite");
426 nbdkit_set_error (EOPNOTSUPP
);
429 else if (exception_happened
== EXCEPTION_OTHER
)
436 plugin_rb_can_write (void *handle
)
438 volatile VALUE argv
[1];
440 enum exception_class exception_happened
;
442 argv
[0] = (VALUE
) handle
;
443 rv
= funcall2 (Qnil
, rb_intern ("can_write"), 1, argv
, &exception_happened
);
444 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
)
445 /* Fall back to checking if the pwrite method exists. */
446 rv
= rb_funcall (Qnil
, rb_intern ("respond_to?"),
447 2, ID2SYM (rb_intern ("pwrite")), Qtrue
);
448 else if (exception_happened
== EXCEPTION_OTHER
)
455 plugin_rb_can_flush (void *handle
)
457 volatile VALUE argv
[1];
459 enum exception_class exception_happened
;
461 argv
[0] = (VALUE
) handle
;
462 rv
= funcall2 (Qnil
, rb_intern ("can_flush"), 1, argv
, &exception_happened
);
463 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
)
464 /* Fall back to checking if the flush method exists. */
465 rv
= rb_funcall (Qnil
, rb_intern ("respond_to?"),
466 2, ID2SYM (rb_intern ("flush")), Qtrue
);
467 else if (exception_happened
== EXCEPTION_OTHER
)
474 plugin_rb_is_rotational (void *handle
)
476 volatile VALUE argv
[1];
478 enum exception_class exception_happened
;
480 argv
[0] = (VALUE
) handle
;
481 rv
= funcall2 (Qnil
, rb_intern ("is_rotational"), 1, argv
,
482 &exception_happened
);
483 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
)
485 else if (exception_happened
== EXCEPTION_OTHER
)
492 plugin_rb_can_trim (void *handle
)
494 volatile VALUE argv
[1];
496 enum exception_class exception_happened
;
498 argv
[0] = (VALUE
) handle
;
499 rv
= funcall2 (Qnil
, rb_intern ("can_trim"), 1, argv
, &exception_happened
);
500 if (exception_happened
== EXCEPTION_NO_METHOD_ERROR
)
501 /* Fall back to checking if the trim method exists. */
502 rv
= rb_funcall (Qnil
, rb_intern ("respond_to?"),
503 2, ID2SYM (rb_intern ("trim")), Qtrue
);
504 else if (exception_happened
== EXCEPTION_OTHER
)
510 #define plugin_rb_config_help \
511 "script=<FILENAME> (required) The Ruby plugin to run.\n" \
512 "[other arguments may be used by the plugin that you load]"
514 /* Ruby is inherently unsafe to call in parallel from multiple
517 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS
519 static struct nbdkit_plugin plugin
= {
521 .version
= PACKAGE_VERSION
,
523 .load
= plugin_rb_load
,
524 .unload
= plugin_rb_unload
,
525 .dump_plugin
= plugin_rb_dump_plugin
,
527 .config
= plugin_rb_config
,
528 .config_complete
= plugin_rb_config_complete
,
529 .config_help
= plugin_rb_config_help
,
531 .open
= plugin_rb_open
,
532 .close
= plugin_rb_close
,
534 .get_size
= plugin_rb_get_size
,
535 .can_write
= plugin_rb_can_write
,
536 .can_flush
= plugin_rb_can_flush
,
537 .is_rotational
= plugin_rb_is_rotational
,
538 .can_trim
= plugin_rb_can_trim
,
540 .pread
= plugin_rb_pread
,
541 .pwrite
= plugin_rb_pwrite
,
542 .flush
= plugin_rb_flush
,
543 .trim
= plugin_rb_trim
,
544 .zero
= plugin_rb_zero
,
547 NBDKIT_REGISTER_PLUGIN(plugin
)