Update Red Hat Copyright Notices
[nbdkit.git] / plugins / ruby / ruby.c
blob175a4cf6eed64bcdb194401e26e711138cdb2784
1 /* nbdkit
2 * Copyright Red Hat
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
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
30 * SUCH DAMAGE.
33 #include <config.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <assert.h>
38 #include <errno.h>
40 #include <nbdkit-plugin.h>
42 #include <ruby.h>
43 #ifdef HAVE_RUBY_VERSION_H
44 #include <ruby/version.h>
45 #endif
47 #include "array-size.h"
49 static VALUE nbdkit_module = Qnil;
50 static int last_error;
52 static VALUE
53 set_error (VALUE self, VALUE arg)
55 int err;
56 VALUE v;
58 if (TYPE (arg) == T_CLASS) {
59 v = rb_const_get (arg, rb_intern ("Errno"));
60 err = NUM2INT (v);
61 } else if (TYPE (arg) == T_OBJECT) {
62 v = rb_funcall (arg, rb_intern ("errno"), 0);
63 err = NUM2INT (v);
64 } else {
65 err = NUM2INT (arg);
67 last_error = err;
68 nbdkit_set_error (err);
69 return Qnil;
72 static void
73 plugin_rb_load (void)
75 RUBY_INIT_STACK;
76 ruby_init ();
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 */
84 #define MAX_ARGS 16
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 */
92 static VALUE
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 {
100 NO_EXCEPTION = 0,
101 EXCEPTION_NO_METHOD_ERROR,
102 EXCEPTION_OTHER,
105 static VALUE
106 funcall2 (VALUE receiver, ID method_id, int argc, volatile VALUE *argv,
107 enum exception_class *exception_happened)
109 struct callback_data data;
110 size_t i, len;
111 int state = 0;
112 volatile VALUE ret, exn, message, backtrace, b;
114 assert (argc <= MAX_ARGS);
116 data.receiver = receiver;
117 data.method_id = method_id;
118 data.argc = argc;
119 for (i = 0; i < argc; ++i)
120 data.argv[i] = argv[i];
122 ret = rb_protect (callback_dispatch, (VALUE) &data, &state);
123 if (state) {
124 /* An exception was thrown. Get the per-thread exception. */
125 exn = rb_errinfo ();
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;
132 else {
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);
153 return Qnil;
155 else {
156 if (exception_happened)
157 *exception_happened = NO_EXCEPTION;
158 return ret;
162 static const char *script = NULL;
163 static void *code = NULL;
165 static void
166 plugin_rb_unload (void)
168 if (ruby_cleanup (0) != 0)
169 nbdkit_error ("ruby_cleanup failed");
172 static void
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);
181 #endif
182 #endif
183 printf ("\n");
184 #endif
186 if (!script)
187 return;
189 assert (code != NULL);
191 (void) funcall2 (Qnil, rb_intern ("dump_plugin"), 0, NULL, NULL);
194 static int
195 plugin_rb_config (const char *key, const char *value)
197 /* The first parameter must be "script". */
198 if (!script) {
199 int state;
201 if (strcmp (key, "script") != 0) {
202 nbdkit_error ("the first parameter must be script=/path/to/ruby/script.rb");
203 return -1;
205 script = value;
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)",
216 script, state);
217 return -1;
220 /* Execute the Ruby script. */
221 state = ruby_exec_node (code);
222 if (state) {
223 nbdkit_error ("could not execute ruby script (%s, state=%d)",
224 script, state);
225 return -1;
228 return 0;
230 else {
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",
242 script);
243 return -1;
245 else if (exception_happened == EXCEPTION_OTHER)
246 return -1;
248 return 0;
252 static int
253 plugin_rb_config_complete (void)
255 enum exception_class exception_happened;
257 if (!script) {
258 nbdkit_error ("the first parameter must be script=/path/to/ruby/script.rb");
259 return -1;
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)
269 return -1;
271 return 0;
274 static void *
275 plugin_rb_open (int readonly)
277 volatile VALUE argv[1];
278 volatile VALUE rv;
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");
285 return NULL;
287 else if (exception_happened == EXCEPTION_OTHER)
288 return NULL;
290 return (void *) rv;
293 static void
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.
305 static int64_t
306 plugin_rb_get_size (void *handle)
308 volatile VALUE argv[1];
309 volatile VALUE rv;
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");
316 return -1;
318 else if (exception_happened == EXCEPTION_OTHER)
319 return -1;
321 return NUM2ULL (rv);
324 static int
325 plugin_rb_pread (void *handle, void *buf,
326 uint32_t count, uint64_t offset)
328 volatile VALUE argv[3];
329 volatile VALUE rv;
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");
338 return -1;
340 else if (exception_happened == EXCEPTION_OTHER)
341 return -1;
343 if (RSTRING_LEN (rv) < count) {
344 nbdkit_error ("%s: byte array returned from pread is too small",
345 script);
346 return -1;
349 memcpy (buf, RSTRING_PTR (rv), count);
350 return 0;
353 static int
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");
366 return -1;
368 else if (exception_happened == EXCEPTION_OTHER)
369 return -1;
371 return 0;
374 static int
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");
384 return -1;
386 else if (exception_happened == EXCEPTION_OTHER)
387 return -1;
389 return 0;
392 static int
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");
404 return -1;
406 else if (exception_happened == EXCEPTION_OTHER)
407 return -1;
409 return 0;
412 static int
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;
422 last_error = 0;
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);
428 return -1;
430 else if (exception_happened == EXCEPTION_OTHER)
431 return -1;
433 return 0;
436 static int
437 plugin_rb_can_write (void *handle)
439 volatile VALUE argv[1];
440 volatile VALUE rv;
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)
450 return -1;
452 return RTEST (rv);
455 static int
456 plugin_rb_can_flush (void *handle)
458 volatile VALUE argv[1];
459 volatile VALUE rv;
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)
469 return -1;
471 return RTEST (rv);
474 static int
475 plugin_rb_is_rotational (void *handle)
477 volatile VALUE argv[1];
478 volatile VALUE rv;
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)
485 return 0;
486 else if (exception_happened == EXCEPTION_OTHER)
487 return -1;
489 return RTEST (rv);
492 static int
493 plugin_rb_can_trim (void *handle)
495 volatile VALUE argv[1];
496 volatile VALUE rv;
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)
506 return -1;
508 return RTEST (rv);
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
516 * threads.
518 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS
520 static struct nbdkit_plugin plugin = {
521 .name = "ruby",
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)