/* libguestfs Java bindings. * Copyright (C) 2009-2017 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; 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); if (len > 0) { size_t i; 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); } /* No events, so no need to allocate anything. */ if (*len_rtn == 0) return NULL; /* 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; }