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);
+ }
+ }
+}