blob: a55a990f0d0c98d1057a20206101a7cd9456c084 [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* JNI utils. This is now organized as a class with virtual methods.
* This will allow a future split based on VM type, and simplification
* of the code ( no more #ifdefs, but different objects for different VMs).
*
* It should also allow to better work around VMs and support more
* ( I assume kaffe will need some special tricks, 1.4 will be different
* than 1.1 and 1.2, IBM vms have their own paths, etc ).
*
* That's future - for now we use the current ugly code ( result
* of few years of workarounds ). The refactoring should be done before adding
* anything new.
*
* @author: Gal Shachor <shachor@il.ibm.com>
* @author: Costin Manolache
*/
#include "jk_workerEnv.h"
#include "jk_env.h"
#include "jk_bean.h"
#ifdef HAVE_JNI
#include "jk_global.h"
#include "jk_vm.h"
#include "jk_config.h"
#if defined LINUX && defined APACHE2_SIGHACK
#include <pthread.h>
#include <signal.h>
#include <bits/signum.h>
#endif
#if defined NETWARE && !defined __NOVELL_LIBC__
#include <nwthread.h>
#include <nwadv.h>
#endif
#include <jni.h>
#ifdef APR_HAS_DSO
#include "apr_dso.h"
#else
#error "You will need the APR's dso support for JNI"
#endif
#ifndef JNI_VERSION_1_2
#warning -------------------------------------------------------
#warning JAVA 1.1 IS NO LONGER SUPPORTED
#warning -------------------------------------------------------
int JK_METHOD jk2_vm_factory(jk_env_t *env, jk_pool_t *pool,
jk_bean_t *result, char *type, char *name)
{
return JK_ERR;
}
#else
#ifndef JNI_VERSION_1_1
#define JNI_VERSION_1_1 0x00010001
#endif
/* probably on an older system that doesn't support RTLD_NOW or RTLD_LAZY.
* The below define is a lie since we are really doing RTLD_LAZY since the
* system doesn't support RTLD_NOW.
*/
#ifndef RTLD_NOW
#define RTLD_NOW 1
#endif
#ifndef RTLD_GLOBAL
#define RTLD_GLOBAL 0
#endif
#define null_check(e) if ((e) == 0) return JK_FALSE
static int jk2_detect_jvm_version(jk_env_t *env);
static int jk2_open_jvm2(jk_env_t *env, jk_vm_t * p);
jint(JNICALL * jni_get_default_java_vm_init_args) (void *) = NULL;
jint(JNICALL * jni_create_java_vm) (JavaVM **, JNIEnv **, void *) = NULL;
jint(JNICALL * jni_get_created_java_vms) (JavaVM **, int, int *) = NULL;
/* Guessing - try all those to find the right dll
*/
static const char *defaultVM_PATH[] = {
"${JAVA_HOME}${fs}jre${fs}bin${fs}classic${fs}libjvm.${so}",
"${JAVA_HOME}${fs}jre${fs}bin${fs}client${fs}jvm.${so}",
"${JAVA_HOME}${fs}jre${fs}lib${fs}${arch}${fs}classic${fs}libjvm.${so}",
"${JAVA_HOME}${fs}jre${fs}lib${fs}${arch}${fs}client${fs}libjvm.${so}",
"${JAVA_HOME}${fs}jre${fs}bin${fs}classic${fs}jvm.${so}",
NULL
};
#if defined LINUX && defined APACHE2_SIGHACK
static void jk2_linux_signal_hack()
{
sigset_t newM;
sigset_t old;
sigemptyset(&newM);
pthread_sigmask(SIG_SETMASK, &newM, &old);
sigdelset(&old, SIGUSR1);
sigdelset(&old, SIGUSR2);
sigdelset(&old, SIGUNUSED);
sigdelset(&old, SIGRTMIN);
sigdelset(&old, SIGRTMIN + 1);
sigdelset(&old, SIGRTMIN + 2);
pthread_sigmask(SIG_SETMASK, &old, NULL);
}
static void jk2_print_signals(sigset_t * sset)
{
int sig;
for (sig = 1; sig < 20; sig++) {
if (sigismember(sset, sig)) {
printf(" %d", sig);
}
}
printf("\n");
}
#endif
/* JVM hooks */
static int jk2_jni_exit_signaled = JK_FALSE;
static int jk2_jni_exit_code = 0;
static int jk2_jni_abort_signaled = JK_FALSE;
static void jk2_jni_exit_hook(int code)
{
jk2_jni_exit_signaled = JK_TRUE;
jk2_jni_abort_signaled = JK_TRUE;
jk2_jni_exit_code = code;
#ifdef DEBUG
fprintf(stderr, "JVM exit hook called %d\n", code);
#endif
}
static void jk2_jni_abort_hook()
{
jk2_jni_abort_signaled = JK_TRUE;
#ifdef DEBUG
fprintf(stderr, "JVM abort hook\n");
#endif
}
/** Load the VM. Must be called after init.
*/
static int jk2_vm_loadJvm(jk_env_t *env, jk_vm_t * jkvm)
{
apr_dso_handle_t *dsoHandle;
apr_status_t rc;
apr_pool_t *aprPool;
aprPool = (apr_pool_t *) env->getAprPool(env);
if (aprPool == NULL)
return JK_FALSE;
/* XXX How do I specify RTLD_NOW and RTLD_GLOBAL ? */
rc = apr_dso_load(&dsoHandle, jkvm->jvm_dll_path, aprPool);
if (rc == APR_SUCCESS) {
rc = apr_dso_sym((apr_dso_handle_sym_t *) & jni_create_java_vm,
dsoHandle, "JNI_CreateJavaVM");
}
if (rc == APR_SUCCESS) {
rc = apr_dso_sym((apr_dso_handle_sym_t *) &
jni_get_default_java_vm_init_args, dsoHandle,
"JNI_GetDefaultJavaVMInitArgs");
}
if (rc == APR_SUCCESS) {
rc = apr_dso_sym((apr_dso_handle_sym_t *) & jni_get_created_java_vms,
dsoHandle, "JNI_GetCreatedJavaVMs");
}
if (rc != APR_SUCCESS) {
char buf[256];
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"Can't load native library %s : %s\n",
jkvm->jvm_dll_path, apr_dso_error(dsoHandle, buf, 256));
return JK_ERR;
}
if (jni_create_java_vm == NULL ||
jni_get_default_java_vm_init_args == NULL ||
jni_get_created_java_vms == NULL) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"jni.loadJvm() Can't resolve symbols %s\n",
jkvm->jvm_dll_path);
apr_dso_unload(dsoHandle);
return JK_ERR;
}
if (jkvm->mbean->debug > 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"jni.loadJvm() %s symbols resolved\n",
jkvm->jvm_dll_path);
return JK_OK;
}
static void *jk2_vm_attach(jk_env_t *env, jk_vm_t * jkvm)
{
JNIEnv *rc = NULL;
int err;
JavaVM *jvm = (JavaVM *) jkvm->jvm;
if (jvm == NULL || jk2_jni_abort_signaled)
return NULL;
#if defined LINUX && defined APACHE2_SIGHACK
/* [V] This message is important. If there are signal mask issues, *
* the JVM usually hangs when a new thread tries to attach to it */
/* XXX do we need to do that on _each_ attach or only when we create
the vm ??? */
/* jk2_linux_signal_hack(); */
#endif
err = (*jvm)->GetEnv(jvm, (void **)&rc, JNI_VERSION_1_2);
/* If the current thread is allready attached to the VM return the
appropriate interface. There is no need to call the AttachCurrentThread.
*/
if (err == 0) {
if (jkvm->mbean->debug >= 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"vm.attach() allready attached\n");
return (void *)rc;
}
/* The error code is either JNI_OK (allready attached) or JNI_EDETACHED.
Othere possibility is that specified version is not supported,
and the returned err in that case is JNI_EVERSION.
*/
if (err != JNI_EDETACHED) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"vm.attach() GetEnv failed %d\n", err);
return NULL;
}
err = (*jvm)->AttachCurrentThread(jvm, (void **)
&rc, NULL);
if (err != 0) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"vm.attach() error %d\n", err);
return NULL;
}
if (jkvm->mbean->debug >= 0)
env->l->jkLog(env, env->l, JK_LOG_DEBUG, "vm.attach() ok\n");
return (void *)rc;
}
static void jk2_vm_detach(jk_env_t *env, jk_vm_t * jkvm)
{
int err;
JavaVM *jvm = (JavaVM *) jkvm->jvm;
if (jvm == NULL || jk2_jni_abort_signaled) {
return;
}
err = (*jvm)->DetachCurrentThread(jvm);
if (err == 0) {
env->l->jkLog(env, env->l, JK_LOG_INFO, "vm.detach() ok\n");
}
else {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"vm.detach() cannot detach from JVM.\n");
}
}
static int jk2_file_exists(jk_env_t *env, const char *f)
{
if (f) {
struct stat st;
if ((0 == stat(f, &st)) && (st.st_mode & S_IFREG)) {
return JK_TRUE;
}
}
return JK_FALSE;
}
/* Add more guessing, based on various vm layouts */
/* Some guessing - to spare the user ( who might know less
than we do ).
*/
#ifdef WIN32
/* On WIN32 use the Registry couse Java itself relies on that.
*/
#define JAVASOFT_REGKEY "SOFTWARE\\JavaSoft\\Java Runtime Environment\\"
static char *jk2_vm_guessJvmDll(jk_env_t *env, jk_map_t *props,
jk_vm_t * jkvm)
{
HKEY hkjs;
static char jvm[MAX_PATH + 1];
char reg[MAX_PATH + 1];
char *cver;
jk_pool_t *p = props->pool;
unsigned int err, klen = MAX_PATH;
strcpy(reg, JAVASOFT_REGKEY);
cver = &reg[sizeof(JAVASOFT_REGKEY) - 1];
if ((err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg,
0, KEY_READ, &hkjs)) != ERROR_SUCCESS) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.guessJvmDll() failed to open Registry key\n");
return NULL;
}
if ((err = RegQueryValueEx(hkjs, "CurrentVersion", NULL, NULL,
(unsigned char *)cver,
&klen)) != ERROR_SUCCESS) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.guessJvmDll() failed obtaining Current Version\n");
RegCloseKey(hkjs);
return NULL;
}
RegCloseKey(hkjs);
if ((err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg,
0, KEY_READ, &hkjs)) != ERROR_SUCCESS) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.guessJvmDll() failed to open Registry key\n");
return NULL;
}
klen = MAX_PATH;
if ((err = RegQueryValueEx(hkjs, "RuntimeLib", NULL, NULL,
(unsigned char *)jvm,
&klen)) != ERROR_SUCCESS) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.guessJvmDll() failed obtaining Runtime Library\n");
RegCloseKey(hkjs);
return NULL;
}
RegCloseKey(hkjs);
return jvm;
}
#else
static char *jk2_vm_guessJvmDll(jk_env_t *env, jk_map_t *props,
jk_vm_t * jkvm)
{
char *jvm;
jk_pool_t *p = props->pool;
const char **current = defaultVM_PATH;
/* We need at least JAVA_HOME ( either env or in settings )
*/
while (*current != NULL) {
jvm = jk2_config_replaceProperties(env, props, p,
(char *)p->pstrdup(env, p,
*current));
if (jvm != NULL && jk2_file_exists(env, jvm)) {
char *ldlib;
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.guessJvmDll() trying %s\n", jvm);
/* Check if the LD_LIBRARY_PATH points to the discovered jvm.
* XXX only tested on Linux.
*/
#if defined(LINUX)
ldlib = getenv(PATH_ENV_VARIABLE);
if (ldlib && strlen(ldlib)) {
char *token;
token = strtok(ldlib, PATH_SEPARATOR_STR);
while (token != NULL) {
if (strncmp(token, jvm, strlen(token)) == 0) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.guessJvmDll() found %s in %s.\n",
jvm, token);
return jvm;
}
token = strtok(NULL, PATH_SEPARATOR_STR);
}
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.guessJvmDll() could not find %s in the LD_LIBRARY_PATH\n",
jvm);
return NULL;
}
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.guessJvmDll() LD_LIBRARY_PATH environment var is not set\n");
return NULL;
#else
return jvm;
#endif
}
env->l->jkLog(env, env->l, JK_LOG_INFO,
"jni.guessJvmDll() failed %s\n", jvm);
current++;
}
env->l->jkLog(env, env->l, JK_LOG_INFO, "jni.guessJvmDll() failed\n");
return NULL;
}
#endif
static int jk2_vm_initVM(jk_env_t *env, jk_vm_t * jkvm)
{
int jvm_version;
JDK1_1InitArgs vm_args11;
jk_map_t *props = jkvm->properties;
JavaVMInitArgs vm_args;
JNIEnv *penv;
JavaVMOption options[JK2_MAXOPTIONS * 2];
JavaVM *jvm;
int optn = 0, err, classn = 0, classl = 0, i;
char *classpath = NULL;
/** Make sure we have the vm dll */
if (jkvm->jvm_dll_path == NULL ||
!jk2_file_exists(env, jkvm->jvm_dll_path)) {
jkvm->jvm_dll_path = jk2_vm_guessJvmDll(env, props, jkvm);
}
if (jkvm->jvm_dll_path == NULL) {
jkvm->jvm_dll_path =
jk2_config_replaceProperties(env, props, props->pool,
"libjvm.${so}");
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"vm.init(): no jvm_dll_path, will use LD_LIBRARY_PATH %s\n",
jkvm->jvm_dll_path);
}
else {
env->l->jkLog(env, env->l, JK_LOG_INFO, "vm.init(): Jni lib: %s\n",
jkvm->jvm_dll_path);
}
err = jk2_vm_loadJvm(env, jkvm);
if (err != JK_OK) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"jni.loadJvm() Error - can't load jvm dll\n");
/* [V] no detach needed here */
return JK_ERR;
}
#if defined LINUX && defined APACHE2_SIGHACK
/* [V] This message is important. If there are signal mask issues, *
* the JVM usually hangs when a new thread tries to attach to it */
/* XXX do we need to do that on _each_ attach or only when we create
the vm ??? */
jk2_linux_signal_hack();
#endif
/* That's kind of strange - if we have JNI_VERSION_1_2 I assume
we also have 1.2, what do we detect ???? */
/* [V] Idea: ask for 1.2. If the JVM is 1.1 it will return 1.1 instead */
/* Note: asking for 1.1 won't work, 'cause 1.2 JVMs will return 1.1 */
vm_args11.version = JNI_VERSION_1_2;
if (0 != jni_get_default_java_vm_init_args(&vm_args11)) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"vm.detect() Fail-> can't get default vm init args\n");
return JK_ERR;
}
jvm_version = vm_args11.version;
if (jvm_version != JNI_VERSION_1_2) {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"vm.detect() found: %X expecting 1.2\n", jvm_version);
return JK_ERR;
}
for (classn = 0; classn < jkvm->nClasspath; classn++)
classl += strlen(jkvm->classpath[classn]);
if (classl) {
classpath = jkvm->pool->calloc(env, jkvm->pool,
classl + classn +
sizeof("-Djava.class.path="));
strcpy(classpath, "-Djava.class.path=");
strcat(classpath, jkvm->classpath[0]);
for (i = 1; i < classn; i++) {
strcat(classpath, PATH_SEPARATOR_STR);
strcat(classpath, jkvm->classpath[i]);
}
}
while (jkvm->options[optn]) {
if (jkvm->mbean->debug > 1)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"vm.openJvm2() Option: %s\n", jkvm->options[optn]);
/* Pass it "as is" */
options[optn].optionString = jkvm->options[optn];
optn++;
}
if (classpath) {
if (jkvm->mbean->debug > 1)
env->l->jkLog(env, env->l, JK_LOG_DEBUG,
"vm.openJvm2() Classpath: %s\n", classpath);
options[optn++].optionString = classpath;
}
/* Set the abort and exit hooks */
options[optn].optionString = "exit";
options[optn++].extraInfo = jk2_jni_exit_hook;
options[optn].optionString = "abort";
options[optn++].extraInfo = jk2_jni_abort_hook;
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = optn;
vm_args.ignoreUnrecognized = JNI_TRUE;
err = jni_create_java_vm(&jvm, &penv, &vm_args);
if (JNI_EEXIST == err) {
int vmCount;
env->l->jkLog(env, env->l, JK_LOG_INFO,
"vm.open2() try to attach to existing vm.\n");
err = jni_get_created_java_vms(&jvm, 1, &vmCount);
if (NULL == jvm) {
env->l->jkLog(env, env->l, JK_LOG_INFO,
"vm.open1() error attaching %d\n", err);
return JK_ERR;
}
jkvm->jvm = (void *)jvm;
return JK_OK;
}
else if (err != 0) {
env->l->jkLog(env, env->l, JK_LOG_EMERG,
"Fail-> could not create JVM, code: %d \n", err);
return JK_ERR;
}
jkvm->jvm = (void *)jvm;
env->l->jkLog(env, env->l, JK_LOG_INFO, "vm.open2() done\n");
return JK_OK;
}
static void jk2_vm_destroy(jk_env_t *env, jk_vm_t * jkvm)
{
int err;
JavaVM *jvm = (JavaVM *) jkvm->jvm;
if (jvm == NULL || jk2_jni_abort_signaled) {
return;
}
err = (*jvm)->DestroyJavaVM(jvm);
if (err == 0) {
env->l->jkLog(env, env->l, JK_LOG_INFO, "vm.destroy() ok\n");
}
else {
env->l->jkLog(env, env->l, JK_LOG_ERROR,
"vm.destroy() cannot destroy the JVM.\n");
}
}
static int JK_METHOD
jk2_jk_vm_setProperty(jk_env_t *env, jk_bean_t *mbean, char *name,
void *valueP)
{
jk_vm_t *jkvm = mbean->object;
char *value = valueP;
if (strcmp(name, "OPT") == 0) {
if (jkvm->nOptions < JK2_MAXOPTIONS) {
jkvm->options[jkvm->nOptions] = value;
jkvm->nOptions++;
}
}
else if (strcmp(name, "JVM") == 0) {
jkvm->jvm_dll_path = value;
}
else if (strcmp(name, "classpath") == 0) {
if (jkvm->nClasspath < JK2_MAXOPTIONS) {
jkvm->classpath[jkvm->nClasspath] = value;
jkvm->nClasspath++;
}
}
else {
return JK_ERR;
}
return JK_OK;
}
int JK_METHOD jk2_vm_factory(jk_env_t *env, jk_pool_t *pool,
jk_bean_t *result, char *type, char *name)
{
jk_vm_t *jkvm;
jk_workerEnv_t *workerEnv;
workerEnv = env->getByName(env, "workerEnv");
jkvm = (jk_vm_t *) pool->calloc(env, pool, sizeof(jk_vm_t));
jkvm->pool = pool;
jkvm->jvm_dll_path = NULL;
jkvm->nOptions = 0;
jkvm->init = jk2_vm_initVM;
jkvm->attach = jk2_vm_attach;
jkvm->detach = jk2_vm_detach;
jkvm->destroy = jk2_vm_destroy;
result->object = jkvm;
result->setAttribute = jk2_jk_vm_setProperty;
jkvm->mbean = result;
jkvm->properties = workerEnv->initData;
workerEnv->vm = jkvm;
return JK_OK;
}
#endif /* Java2 */
#else /* HAVE_JNI */
int JK_METHOD jk2_vm_factory(jk_env_t *env, jk_pool_t *pool,
jk_bean_t *result, char *type, char *name)
{
result->disabled = 1;
return JK_OK;
}
#endif