diff --git a/.gitignore b/.gitignore index 739b8b67b..48e6344e6 100644 --- a/.gitignore +++ b/.gitignore @@ -280,9 +280,9 @@ Makefile.in /inspector/virt-inspector.1 /installcheck.sh /install-sh +/java/actions-?.c /java/api /java/Bindtests.java -/java/com_redhat_et_libguestfs_GuestFS.c /java/com_redhat_et_libguestfs_GuestFS.h /java/com/redhat/et/libguestfs/GuestFS.java /java/doc-stamp diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES index 16e917934..875ce1bbe 100644 --- a/docs/C_SOURCE_FILES +++ b/docs/C_SOURCE_FILES @@ -199,7 +199,14 @@ fuse/test-guestmount-fd.c fuse/test-guestunmount-fd.c get-kernel/dummy.c inspector/inspector.c -java/com_redhat_et_libguestfs_GuestFS.c +java/actions-0.c +java/actions-1.c +java/actions-2.c +java/actions-3.c +java/actions-4.c +java/actions-5.c +java/actions-6.c +java/handle.c lua/lua-guestfs.c make-fs/make-fs.c mllib/dev_t-c.c diff --git a/generator/java.ml b/generator/java.ml index 044866ed2..260e28c9e 100644 --- a/generator/java.ml +++ b/generator/java.ml @@ -566,7 +566,7 @@ public class %s { pr "}\n" -and generate_java_c () = +and generate_java_c actions () = generate_header CStyle LGPLv2plus; pr "\ @@ -581,20 +581,6 @@ and generate_java_c () = #include \"guestfs.h\" #include \"guestfs-internal-frontend.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 (JNIEnv *env, guestfs_h *g, size_t *len_rtn); - /* Note that this function returns. The exception is not thrown * until after the wrapper function returns. */ @@ -618,241 +604,12 @@ throw_out_of_memory (JNIEnv *env, const char *msg) \"com/redhat/et/libguestfs/LibGuestFSOutOfMemory\"); (*env)->ThrowNew (env, cl, msg); } - -JNIEXPORT jlong JNICALL -Java_com_redhat_et_libguestfs_GuestFS__1create (JNIEnv *env, - jobject obj_unused, jint flags) -{ - guestfs_h *g; - - g = guestfs_create_flags ((int) flags); - if (g == NULL) { - throw_exception (env, \"GuestFS.create: failed to allocate handle\"); - return 0; - } - guestfs_set_error_handler (g, NULL, NULL); - return (jlong) (long) g; -} - -JNIEXPORT void JNICALL -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 (env, 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 -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; - - case JNI_EVERSION: - fprintf (stderr, \"%%s: event %%\" PRIu64 \" (eh %%d) failed because the JVM version is too old. JVM >= 1.6 is required.\\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 = malloc (sizeof *data); - if (data == NULL) { - throw_out_of_memory (env, \"malloc\"); - return -1; - } - (*env)->GetJavaVM (env, &data->jvm); - data->method = method; - - r = guestfs_set_event_callback (g, 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); - } -} - -JNIEXPORT jstring JNICALL -Java_com_redhat_et_libguestfs_GuestFS__1event_1to_1string - (JNIEnv *env, jclass cl, jlong jevents) -{ - uint64_t events = (uint64_t) jevents; - char *str; - jstring jr; - - str = guestfs_event_to_string (events); - if (str == NULL) { - perror (\"guestfs_event_to_string\"); - return NULL; - } - - jr = (*env)->NewStringUTF (env, str); - free (str); - - return jr; -} - -static struct callback_data ** -get_all_event_callbacks (JNIEnv *env, 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 = malloc (sizeof (struct callback_data *) * (*len_rtn)); - if (r == NULL) { - throw_out_of_memory (env, \"malloc\"); - return NULL; - } - - 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; -} - "; List.iter ( fun { name = name; style = (ret, args, optargs as style); c_function = c_function } -> + pr "\n"; pr "JNIEXPORT "; (match ret with | RErr -> pr "void "; diff --git a/generator/java.mli b/generator/java.mli index b24031477..1af26578c 100644 --- a/generator/java.mli +++ b/generator/java.mli @@ -16,7 +16,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *) -val generate_java_c : unit -> unit +val generate_java_c : Types.action list -> unit -> unit val generate_java_gitignore : unit -> unit val generate_java_java : unit -> unit val generate_java_makefile_inc : unit -> unit diff --git a/generator/main.ml b/generator/main.ml index ee1b96e3a..85bec599d 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -174,7 +174,7 @@ Run it from the top source directory using the command "java/com/redhat/et/libguestfs/*.java"; output_to "java/Makefile.inc" generate_java_makefile_inc; - output_to "java/com_redhat_et_libguestfs_GuestFS.c" generate_java_c; + output_to_subset "java/actions-%d.c" generate_java_c; output_to "java/com/redhat/et/libguestfs/.gitignore" generate_java_gitignore; output_to "java/Bindtests.java" generate_java_bindtests; output_to "haskell/Guestfs.hs" generate_haskell_hs; diff --git a/java/Makefile.am b/java/Makefile.am index 83c4a304e..f2a0e92ce 100644 --- a/java/Makefile.am +++ b/java/Makefile.am @@ -22,7 +22,13 @@ java_prefix = com/redhat/et/libguestfs generator_built = \ Makefile.inc \ $(java_built_sources) \ - com_redhat_et_libguestfs_GuestFS.c \ + actions-0.c \ + actions-1.c \ + actions-2.c \ + actions-3.c \ + actions-4.c \ + actions-5.c \ + actions-6.c \ $(srcdir)/Bindtests.java # Pull in automatically generated built sources @@ -91,7 +97,14 @@ jnilibdir = $(JNI_INSTALL_DIR) libguestfs_jni_la_SOURCES = \ com_redhat_et_libguestfs_GuestFS.h \ - com_redhat_et_libguestfs_GuestFS.c + actions-0.c \ + actions-1.c \ + actions-2.c \ + actions-3.c \ + actions-4.c \ + actions-5.c \ + actions-6.c \ + handle.c libguestfs_jni_la_CPPFLAGS = \ -DGUESTFS_PRIVATE=1 \ diff --git a/java/handle.c b/java/handle.c new file mode 100644 index 000000000..5d3324cd4 --- /dev/null +++ b/java/handle.c @@ -0,0 +1,295 @@ +/* libguestfs Java bindings. + * Copyright (C) 2009-2016 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 + */ + +#include + +#include +#include +#include +#include + +#include "com_redhat_et_libguestfs_GuestFS.h" +#include "guestfs.h" +#include "guestfs-internal-frontend.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 (JNIEnv *env, guestfs_h *g, size_t *len_rtn); + +/* Note that this function returns. The exception is not thrown + * until after the wrapper function returns. + */ +static void +throw_exception (JNIEnv *env, const char *msg) +{ + jclass cl; + cl = (*env)->FindClass (env, + "com/redhat/et/libguestfs/LibGuestFSException"); + (*env)->ThrowNew (env, cl, msg); +} + +/* Note that this function returns. The exception is not thrown + * until after the wrapper function returns. + */ +static void +throw_out_of_memory (JNIEnv *env, const char *msg) +{ + jclass cl; + cl = (*env)->FindClass (env, + "com/redhat/et/libguestfs/LibGuestFSOutOfMemory"); + (*env)->ThrowNew (env, cl, msg); +} + +JNIEXPORT jlong JNICALL +Java_com_redhat_et_libguestfs_GuestFS__1create (JNIEnv *env, + jobject obj_unused, jint flags) +{ + guestfs_h *g; + + g = guestfs_create_flags ((int) flags); + if (g == NULL) { + throw_exception (env, "GuestFS.create: failed to allocate handle"); + return 0; + } + guestfs_set_error_handler (g, NULL, NULL); + return (jlong) (long) g; +} + +JNIEXPORT void JNICALL +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 (env, 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 +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; + + case JNI_EVERSION: + fprintf (stderr, "%s: event %" PRIu64 " (eh %d) failed because the JVM version is too old. JVM >= 1.6 is required.\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 = malloc (sizeof *data); + if (data == NULL) { + throw_out_of_memory (env, "malloc"); + return -1; + } + (*env)->GetJavaVM (env, &data->jvm); + data->method = method; + + r = guestfs_set_event_callback (g, 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); + } +} + +JNIEXPORT jstring JNICALL +Java_com_redhat_et_libguestfs_GuestFS__1event_1to_1string + (JNIEnv *env, jclass cl, jlong jevents) +{ + uint64_t events = (uint64_t) jevents; + char *str; + jstring jr; + + str = guestfs_event_to_string (events); + if (str == NULL) { + perror ("guestfs_event_to_string"); + return NULL; + } + + jr = (*env)->NewStringUTF (env, str); + free (str); + + return jr; +} + +static struct callback_data ** +get_all_event_callbacks (JNIEnv *env, 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 = malloc (sizeof (struct callback_data *) * (*len_rtn)); + if (r == NULL) { + throw_out_of_memory (env, "malloc"); + return NULL; + } + + 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/po/POTFILES b/po/POTFILES index d0fe5c334..9aa13a238 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -294,7 +294,14 @@ gobject/src/struct-xattr.c gobject/src/struct-xfsinfo.c gobject/src/tristate.c inspector/inspector.c -java/com_redhat_et_libguestfs_GuestFS.c +java/actions-0.c +java/actions-1.c +java/actions-2.c +java/actions-3.c +java/actions-4.c +java/actions-5.c +java/actions-6.c +java/handle.c lua/lua-guestfs.c make-fs/make-fs.c mllib/dev_t-c.c