New event API - Ruby bindings (RHBZ#664558).

This commit is contained in:
Richard W.M. Jones
2011-03-14 22:37:21 +00:00
parent bc468c87d0
commit 6a64114929
3 changed files with 231 additions and 7 deletions

View File

@@ -21,6 +21,8 @@ generator_optgroups.cmx: generator_types.cmx generator_actions.cmx
generator_prepopts.cmi:
generator_prepopts.cmo: generator_prepopts.cmi
generator_prepopts.cmx: generator_prepopts.cmi
generator_events.cmo: generator_utils.cmi
generator_events.cmx: generator_utils.cmx
generator_pr.cmi:
generator_pr.cmo: generator_utils.cmi generator_pr.cmi
generator_pr.cmx: generator_utils.cmx generator_pr.cmi
@@ -90,10 +92,12 @@ generator_python.cmx: generator_utils.cmx generator_types.cmx \
generator_docstrings.cmx generator_c.cmx generator_actions.cmx
generator_ruby.cmo: generator_utils.cmi generator_types.cmo \
generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \
generator_docstrings.cmo generator_c.cmo generator_actions.cmi
generator_events.cmo generator_docstrings.cmo generator_c.cmo \
generator_actions.cmi
generator_ruby.cmx: generator_utils.cmx generator_types.cmx \
generator_structs.cmx generator_pr.cmx generator_optgroups.cmx \
generator_docstrings.cmx generator_c.cmx generator_actions.cmx
generator_events.cmx generator_docstrings.cmx generator_c.cmx \
generator_actions.cmx
generator_java.cmo: generator_utils.cmi generator_types.cmo \
generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \
generator_docstrings.cmo generator_c.cmo generator_actions.cmi

View File

@@ -1,5 +1,5 @@
(* libguestfs
* Copyright (C) 2009-2010 Red Hat Inc.
* Copyright (C) 2009-2011 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,6 +28,7 @@ open Generator_optgroups
open Generator_actions
open Generator_structs
open Generator_c
open Generator_events
(* Generate ruby bindings. *)
let rec generate_ruby_c () =
@@ -36,6 +37,7 @@ let rec generate_ruby_c () =
pr "\
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ruby.h>
@@ -52,10 +54,34 @@ static VALUE m_guestfs; /* guestfs module */
static VALUE c_guestfs; /* guestfs_h handle */
static VALUE e_Error; /* used for all errors */
static void ruby_guestfs_free (void *p)
static void ruby_event_callback_wrapper (guestfs_h *g, void *data, uint64_t event, int event_handle, int flags, const char *buf, size_t buf_len, const uint64_t *array, size_t array_len);
static VALUE **get_all_event_callbacks (guestfs_h *g, size_t *len_rtn);
static void ruby_guestfs_free (void *gvp)
{
if (!p) return;
guestfs_close ((guestfs_h *) p);
guestfs_h *g = gvp;
if (g) {
/* As in the OCaml binding, there is a nasty, difficult to
* solve case here where the user deletes events in one of
* the callbacks that we are about to invoke, resulting in
* a double-free. XXX
*/
size_t len, i;
VALUE **roots = get_all_event_callbacks (g, &len);
/* Close the handle: this could invoke callbacks from the list
* above, which is why we don't want to delete them before
* closing the handle.
*/
guestfs_close (g);
/* Now unregister the global roots. */
for (i = 0; i < len; ++i) {
rb_gc_unregister_address (roots[i]);
free (roots[i]);
}
}
}
static VALUE ruby_guestfs_create (VALUE m)
@@ -86,6 +112,122 @@ static VALUE ruby_guestfs_close (VALUE gv)
return Qnil;
}
static VALUE
ruby_set_event_callback (VALUE gv, VALUE cbv, VALUE event_bitmaskv)
{
guestfs_h *g;
uint64_t event_bitmask;
int eh;
VALUE *root;
char key[64];
Data_Get_Struct (gv, guestfs_h, g);
event_bitmask = NUM2ULL (event_bitmaskv);
root = guestfs_safe_malloc (g, sizeof *root);
*root = cbv;
eh = guestfs_set_event_callback (g, ruby_event_callback_wrapper,
event_bitmask, 0, root);
if (eh == -1) {
free (root);
rb_raise (e_Error, \"%%s\", guestfs_last_error (g));
}
rb_gc_register_address (root);
snprintf (key, sizeof key, \"_ruby_event_%%d\", eh);
guestfs_set_private (g, key, root);
return INT2NUM (eh);
}
static VALUE
ruby_delete_event_callback (VALUE gv, VALUE event_handlev)
{
guestfs_h *g;
char key[64];
int eh = NUM2INT (event_handlev);
VALUE *root;
Data_Get_Struct (gv, guestfs_h, g);
snprintf (key, sizeof key, \"_ruby_event_%%d\", eh);
root = guestfs_get_private (g, key);
if (root) {
rb_gc_unregister_address (root);
free (root);
guestfs_set_private (g, key, NULL);
guestfs_delete_event_callback (g, eh);
}
return Qnil;
}
static void
ruby_event_callback_wrapper (guestfs_h *g,
void *data,
uint64_t event,
int event_handle,
int flags,
const char *buf, size_t buf_len,
const uint64_t *array, size_t array_len)
{
size_t i;
VALUE eventv, event_handlev, bufv, arrayv;
eventv = ULL2NUM (event);
event_handlev = INT2NUM (event_handle);
bufv = rb_str_new (buf, buf_len);
arrayv = rb_ary_new2 (array_len);
for (i = 0; i < array_len; ++i)
rb_ary_push (arrayv, ULL2NUM (array[i]));
/* XXX If the Ruby callback raises any sort of exception then
* it causes the process to segfault. I don't understand how
* to catch exceptions here.
*/
rb_funcall (*(VALUE *) data, rb_intern (\"call\"), 4,
eventv, event_handlev, bufv, arrayv);
}
static VALUE **
get_all_event_callbacks (guestfs_h *g, size_t *len_rtn)
{
VALUE **r;
size_t i;
const char *key;
VALUE *root;
/* Count the length of the array that will be needed. */
*len_rtn = 0;
root = guestfs_first_private (g, &key);
while (root != NULL) {
if (strncmp (key, \"_ruby_event_\", strlen (\"_ruby_event_\")) == 0)
(*len_rtn)++;
root = guestfs_next_private (g, &key);
}
/* Copy them into the return array. */
r = guestfs_safe_malloc (g, sizeof (VALUE *) * (*len_rtn));
i = 0;
root = guestfs_first_private (g, &key);
while (root != NULL) {
if (strncmp (key, \"_ruby_event_\", strlen (\"_ruby_event_\")) == 0) {
r[i] = root;
i++;
}
root = guestfs_next_private (g, &key);
}
return r;
}
";
List.iter (
@@ -295,9 +437,23 @@ void Init__guestfs ()
rb_define_module_function (m_guestfs, \"create\", ruby_guestfs_create, 0);
rb_define_method (c_guestfs, \"close\", ruby_guestfs_close, 0);
rb_define_method (c_guestfs, \"set_event_callback\",
ruby_set_event_callback, 2);
rb_define_method (c_guestfs, \"delete_event_callback\",
ruby_delete_event_callback, 1);
";
(* Define the rest of the methods. *)
(* Constants. *)
List.iter (
fun (name, bitmask) ->
pr " rb_define_const (m_guestfs, \"EVENT_%s\",\n"
(String.uppercase name);
pr " ULL2NUM (UINT64_C (0x%x)));\n" bitmask;
) events;
pr "\n";
(* Methods. *)
List.iter (
fun (name, (_, args, optargs), _, _, _, _, _) ->
let nr_args = List.length args + if optargs <> [] then 1 else 0 in

View File

@@ -0,0 +1,64 @@
# libguestfs Ruby bindings -*- ruby -*-
# Copyright (C) 2011 Red Hat Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
require 'test/unit'
$:.unshift(File::join(File::dirname(__FILE__), "..", "lib"))
$:.unshift(File::join(File::dirname(__FILE__), "..", "ext", "guestfs"))
require 'guestfs'
class TestLoad < Test::Unit::TestCase
def test_events
g = Guestfs::create()
log = Proc.new {| event, event_handle, buf, array |
if event == Guestfs::EVENT_APPLIANCE
buf.chomp!
end
puts "ruby event logged: event=#{event} eh=#{event_handle} buf='#{buf}' array=#{array}"
}
close_invoked = 0
close = Proc.new {| event, event_handle, buf, array |
close_invoked += 1
log.call(event, event_handle, buf, array)
}
# Grab log, trace and daemon messages into our custom callback.
event_bitmask = Guestfs::EVENT_APPLIANCE | Guestfs::EVENT_LIBRARY |
Guestfs::EVENT_TRACE
g.set_event_callback(log, event_bitmask)
# Check that the close event is called.
g.set_event_callback(close, Guestfs::EVENT_CLOSE)
# Make sure we see some messages.
g.set_trace(1)
g.set_verbose(1)
# Do some stuff.
g.add_drive_ro("/dev/null")
g.set_autosync(1)
if close_invoked != 0
raise "close_invoked should be 0"
end
g.close()
if close_invoked != 1
raise "close_invoked should be 1"
end
end
end