From 0d2d26d8e7edf16d51435f10b7f26f800b2c689b Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Fri, 4 Jan 2013 05:07:16 +0900 Subject: [PATCH] java: Implement the event API. --- generator/java.ml | 302 +++++++++++++++++- generator/main.ml | 3 +- java/Makefile.am | 6 +- .../redhat/et/libguestfs/EventCallback.java | 33 ++ java/examples/guestfs-java.pod | 30 ++ java/t/GuestFS400Events.java | 95 ++++++ 6 files changed, 465 insertions(+), 4 deletions(-) create mode 100644 java/com/redhat/et/libguestfs/EventCallback.java create mode 100644 java/t/GuestFS400Events.java diff --git a/generator/java.ml b/generator/java.ml index 1879f500f..9ef09ada8 100644 --- a/generator/java.ml +++ b/generator/java.ml @@ -1,5 +1,5 @@ (* libguestfs - * Copyright (C) 2009-2012 Red Hat Inc. + * Copyright (C) 2009-2013 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 @@ -27,6 +27,7 @@ open Docstrings open Optgroups open Actions open Structs +open Events open C (* Generate Java bindings GuestFS.java file. *) @@ -133,6 +134,117 @@ public class GuestFS { "; + (* Events. *) + pr " // Event bitmasks.\n\n"; + List.iter ( + fun (name, bitmask) -> + pr " /**\n"; + pr " * Event '%s'.\n" name; + pr " *\n"; + pr " * @see #set_event_callback\n"; + pr " */\n"; + pr " public static final long EVENT_%s = 0x%x;\n" (String.uppercase name) bitmask; + pr "\n"; + ) events; + + pr " /** Bitmask of all events. */\n"; + pr " public static final long EVENT_ALL = 0x%x;\n" ((1 lsl List.length events) - 1); + pr "\n"; + + pr " /** Utility function to turn an event number or bitmask into a string. */\n"; + pr " public static String eventToString (long events)\n"; + pr " {\n"; + pr " if (events == 0)\n"; + pr " return \"\";\n"; + pr "\n"; + pr " String ret = \"\";\n"; + pr "\n"; + List.iter ( + fun (name, bitmask) -> + pr " if ((events & EVENT_%s) != 0) {\n" (String.uppercase name); + pr " ret = ret + \"|EVENT_%s\";\n" (String.uppercase name); + pr " events &= ~0x%x;\n" bitmask; + pr " }\n"; + ) events; + pr "\n"; + pr " if (events != 0)\n"; + pr " ret = events + ret;\n"; + pr " else\n"; + pr " ret = ret.substring (1);\n"; + pr "\n"; + pr " return ret;\n"; + pr " }\n"; + + pr " + /** + * Set an event handler. + *

+ * Set an event handler (callback) which is called when any + * event from the set (events) is raised by the API. + * events is one or more EVENT_* constants, + * bitwise ORed together. + *

+ * When an event happens, the callback object's event method + * is invoked like this: + *

+   * callback.event (event,    // the specific event which fired (long)
+   *                 eh,       // the event handle (int)
+   *                 buffer,   // event data (String)
+   *                 array     // event data (long[])
+   *                 );
+   * 
+ * Note that you can pass arbitrary data from the main program to the + * callback by putting it into your {@link EventCallback callback object}, + * then accessing it in the callback via this. + *

+ * This function returns an event handle which may be used to delete + * the event. Note that event handlers are deleted automatically when + * the libguestfs handle is closed. + * + * @throws LibGuestFSException + * @see The section \"EVENTS\" in the guestfs(3) manual + * @see #delete_event_callback + */ + public int set_event_callback (EventCallback callback, long events) + throws LibGuestFSException + { + if (g == 0) + throw new LibGuestFSException (\"set_event_callback: handle is closed\"); + + return _set_event_callback (g, callback, events); + } + + private native int _set_event_callback (long g, EventCallback callback, + long events) + throws LibGuestFSException; + + /** + * Delete an event handler. + *

+ * Delete a previously registered event handler. The 'eh' parameter is + * the event handle returned from a previous call to + * {@link #set_event_callback set_event_callback}. + *

+ * Note that event handlers are deleted automatically when the + * libguestfs handle is closed. + * + * @throws LibGuestFSException + * @see #set_event_callback + */ + public void delete_event_callback (int eh) + throws LibGuestFSException + { + if (g == 0) + throw new LibGuestFSException (\"delete_event_callback: handle is closed\"); + + _delete_event_callback (g, eh); + } + + private native void _delete_event_callback (long g, int eh); + +"; + + (* Methods. *) List.iter ( fun ({ name = name; style = (ret, args, optargs as style); in_docs = in_docs; shortdesc = shortdesc; @@ -433,10 +545,25 @@ and generate_java_c () = #include #include #include +#include #include \"com_redhat_et_libguestfs_GuestFS.h\" #include \"guestfs.h\" +/* This is the opaque data passed between _set_event_callback and + * the C wrapper which calls the Java event callback. + * + * NB: The 'callback' in the following struct is registered as a global + * reference. It must be freed along with the struct. + */ +struct callback_data { + JavaVM *jvm; // JVM + jobject callback; // object supporting EventCallback interface + jmethodID method; // callback.event method +}; + +static struct callback_data **get_all_event_callbacks (guestfs_h *g, size_t *len_rtn); + /* Note that this function returns. The exception is not thrown * until after the wrapper function returns. */ @@ -469,7 +596,180 @@ Java_com_redhat_et_libguestfs_GuestFS__1close (JNIEnv *env, jobject obj, jlong jg) { guestfs_h *g = (guestfs_h *) (long) jg; + size_t len, i; + struct callback_data **data; + + /* 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 + */ + data = get_all_event_callbacks (g, &len); + guestfs_close (g); + + for (i = 0; i < len; ++i) { + (*env)->DeleteGlobalRef (env, data[i]->callback); + free (data[i]); + } + free (data); +} + +/* See EventCallback interface. */ +#define METHOD_NAME \"event\" +#define METHOD_SIGNATURE \"(JILjava/lang/String;[J)V\" + +static void +guestfs_java_callback (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + struct callback_data *data = opaque; + JavaVM *jvm = data->jvm; + JNIEnv *env; + int r; + jstring jbuf; + jlongArray jarray; + size_t i; + jlong jl; + + /* Get the Java environment. See: + * http://stackoverflow.com/questions/12900695/how-to-obtain-jni-interface-pointer-jnienv-for-asynchronous-calls + */ + r = (*jvm)->GetEnv (jvm, (void **) &env, JNI_VERSION_1_6); + if (r != JNI_OK) { + switch (r) { + case JNI_EDETACHED: + /* This can happen when the close event is generated during an atexit + * cleanup. The JVM has probably been destroyed so I doubt it is + * safe to run Java code at this point. + */ + fprintf (stderr, \"%%s: event %%\" PRIu64 \" (eh %%d) ignored because the thread is not attached to the JVM. This can happen when libguestfs handles are cleaned up at program exit after the JVM has been destroyed.\\n\", + __func__, event, event_handle); + return; + + default: + fprintf (stderr, \"%%s: jvm->GetEnv failed! (JNI_* error code = %%d)\\n\", + __func__, r); + return; + } + } + + /* Convert the buffer and array to Java objects. */ + jbuf = (*env)->NewStringUTF (env, buf); // XXX size + + jarray = (*env)->NewLongArray (env, array_len); + for (i = 0; i < array_len; ++i) { + jl = array[i]; + (*env)->SetLongArrayRegion (env, jarray, i, 1, &jl); + } + + /* Call the event method. If it throws an exception, all we can do is + * print it on stderr. + */ + (*env)->ExceptionClear (env); + (*env)->CallVoidMethod (env, data->callback, data->method, + (jlong) event, (jint) event_handle, + jbuf, jarray); + if ((*env)->ExceptionOccurred (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + } +} + +JNIEXPORT jint JNICALL +Java_com_redhat_et_libguestfs_GuestFS__1set_1event_1callback + (JNIEnv *env, jobject obj, jlong jg, jobject jcallback, jlong jevents) +{ + guestfs_h *g = (guestfs_h *) (long) jg; + int r; + struct callback_data *data; + jclass callback_class; + jmethodID method; + char key[64]; + + callback_class = (*env)->GetObjectClass (env, jcallback); + method = (*env)->GetMethodID (env, callback_class, METHOD_NAME, METHOD_SIGNATURE); + if (method == 0) { + throw_exception (env, \"GuestFS.set_event_callback: callback class does not implement the EventCallback interface\"); + return -1; + } + + data = guestfs___safe_malloc (g, sizeof *data); + (*env)->GetJavaVM (env, &data->jvm); + data->method = method; + + r = guestfs_set_event_callback (g, guestfs_java_callback, + (uint64_t) jevents, 0, data); + if (r == -1) { + free (data); + throw_exception (env, guestfs_last_error (g)); + return -1; + } + + /* Register jcallback as a global reference so the GC won't free it. */ + data->callback = (*env)->NewGlobalRef (env, jcallback); + + /* Store 'data' in the handle, so we can free it at some point. */ + snprintf (key, sizeof key, \"_java_event_%%d\", r); + guestfs_set_private (g, key, data); + + return (jint) r; +} + +JNIEXPORT void JNICALL +Java_com_redhat_et_libguestfs_GuestFS__1delete_1event_1callback + (JNIEnv *env, jobject obj, jlong jg, jint eh) +{ + guestfs_h *g = (guestfs_h *) (long) jg; + char key[64]; + struct callback_data *data; + + snprintf (key, sizeof key, \"_java_event_%%d\", eh); + + data = guestfs_get_private (g, key); + if (data) { + (*env)->DeleteGlobalRef (env, data->callback); + free (data); + guestfs_set_private (g, key, NULL); + guestfs_delete_event_callback (g, eh); + } +} + +static struct callback_data ** +get_all_event_callbacks (guestfs_h *g, size_t *len_rtn) +{ + struct callback_data **r; + size_t i; + const char *key; + struct callback_data *data; + + /* Count the length of the array that will be needed. */ + *len_rtn = 0; + data = guestfs_first_private (g, &key); + while (data != NULL) { + if (strncmp (key, \"_java_event_\", strlen (\"_java_event_\")) == 0) + (*len_rtn)++; + data = guestfs_next_private (g, &key); + } + + /* Copy them into the return array. */ + r = guestfs___safe_malloc (g, sizeof (struct callback_data *) * (*len_rtn)); + + i = 0; + data = guestfs_first_private (g, &key); + while (data != NULL) { + if (strncmp (key, \"_java_event_\", strlen (\"_java_event_\")) == 0) { + r[i] = data; + i++; + } + data = guestfs_next_private (g, &key); + } + + return r; } "; diff --git a/generator/main.ml b/generator/main.ml index 618052b48..69ebe0576 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -138,7 +138,8 @@ Run it from the top source directory using the command output_to filename (generate_java_struct jtyp cols) ) structs; delete_except_generated - ~skip:["java/com/redhat/et/libguestfs/LibGuestFSException.java"] + ~skip:["java/com/redhat/et/libguestfs/LibGuestFSException.java"; + "java/com/redhat/et/libguestfs/EventCallback.java"] "java/com/redhat/et/libguestfs/*.java"; output_to "java/Makefile.inc" generate_java_makefile_inc; diff --git a/java/Makefile.am b/java/Makefile.am index b655ae9c1..449f40b15 100644 --- a/java/Makefile.am +++ b/java/Makefile.am @@ -1,5 +1,5 @@ # libguestfs Java bindings -# Copyright (C) 2009-2012 Red Hat Inc. +# Copyright (C) 2009-2013 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 @@ -33,13 +33,15 @@ include $(srcdir)/Makefile.inc java_sources = \ $(java_built_sources) \ + com/redhat/et/libguestfs/EventCallback.java \ com/redhat/et/libguestfs/LibGuestFSException.java java_tests = \ Bindtests.java \ t/GuestFS005Load.java \ t/GuestFS010Basic.java \ - t/GuestFS080OptArgs.java + t/GuestFS080OptArgs.java \ + t/GuestFS400Events.java EXTRA_DIST = \ $(java_sources) \ diff --git a/java/com/redhat/et/libguestfs/EventCallback.java b/java/com/redhat/et/libguestfs/EventCallback.java new file mode 100644 index 000000000..a5e84e104 --- /dev/null +++ b/java/com/redhat/et/libguestfs/EventCallback.java @@ -0,0 +1,33 @@ +/* libguestfs Java bindings + * Copyright (C) 2009-2013 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.redhat.et.libguestfs; + +/** + * Event callback interface. + *

+ * This is the interface for event callbacks. See the + * {@link GuestFS#set_event_callback set_event_callback method} + * for details. + * + * @author rjones + * @see GuestFS + */ +public interface EventCallback { + public void event (long event, int eh, String buffer, long[] array); +} diff --git a/java/examples/guestfs-java.pod b/java/examples/guestfs-java.pod index 6e032b482..c777e054a 100644 --- a/java/examples/guestfs-java.pod +++ b/java/examples/guestfs-java.pod @@ -40,6 +40,36 @@ is the error message (a C). Calling any method on a closed handle raises the same exception. +=head2 EVENTS + +The L is fully supported from +Java. Create a class which implements the C interface, +create an instance of this class, and then call the C +method to register this instance. The C method of the class is +called when libguestfs generates an event. + +For example, this will print all trace events: + + GuestFS g = new GuestFS (); + g.set_trace (true); + g.set_event_callback ( + new EventCallback () { + public void event (long event, int eh, + String buffer, long[] array) { + System.out.println (GuestFS.eventToString (event) + + ": " + buffer); + } + }, + GuestFS.EVENT_TRACE); + g.add_drive_ro ("disk.img"); + // etc. + +The output looks similar to this: + + EVENT_TRACE: add_drive_ro "disk.img" + EVENT_TRACE: add_drive_ro = 0 + // etc. + =head1 EXAMPLE 1: CREATE A DISK IMAGE @EXAMPLE1@ diff --git a/java/t/GuestFS400Events.java b/java/t/GuestFS400Events.java new file mode 100644 index 000000000..98236d868 --- /dev/null +++ b/java/t/GuestFS400Events.java @@ -0,0 +1,95 @@ +/* libguestfs Java bindings + * Copyright (C) 2013 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +import java.io.*; +import java.util.HashMap; +import com.redhat.et.libguestfs.*; + +public class GuestFS400Events +{ + static class PrintEvent implements EventCallback + { + public void event (long event, int eh, String buffer, long[] array) + { + String msg = "event=" + GuestFS.eventToString (event) + " " + + "eh=" + eh + " "; + + if (buffer != null) + msg += "buffer='" + buffer + "' "; + + if (array.length > 0) { + msg += "array[" + array.length + "]={"; + for (int i = 0; i < array.length; ++i) + msg += " " + array[i]; + msg += " }"; + } + + System.out.println ("java event logged: " + msg); + } + } + + static class CloseInvoked extends PrintEvent + { + private int close_invoked = 0; + + public void event (long event, int eh, String buffer, long[] array) + { + super.event (event, eh, buffer, array); + close_invoked++; + } + + public int getCloseInvoked () + { + return close_invoked; + } + } + + public static void main (String[] argv) + { + try { + GuestFS g = new GuestFS (); + + // Grab all messages into an event handler that just + // prints each event. + g.set_event_callback (new PrintEvent (), + GuestFS.EVENT_APPLIANCE|GuestFS.EVENT_LIBRARY| + GuestFS.EVENT_TRACE); + + // Check that the close event is invoked. + CloseInvoked ci = new CloseInvoked (); + g.set_event_callback (ci, GuestFS.EVENT_CLOSE); + + // Now make sure we see some messages. + g.set_trace (true); + g.set_verbose (true); + + // Do some stuff. + g.add_drive_ro ("/dev/null"); + g.set_autosync (true); + + // Close the handle. + assert ci.getCloseInvoked() == 0; + g.close (); + assert ci.getCloseInvoked() == 1; + } + catch (Exception exn) { + System.err.println (exn); + System.exit (1); + } + } +}