/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <assert.h>
#include <strings.h>
#include <libproc.h>
#include <pthread.h>
#include <dtrace_jni.h>
/* generated by javah */
#include <LocalConsumer.h>

/*
 * dtrace_jni.c defines all the native methods of the Java DTrace API.  Every
 * native method is declared in a single class, LocalConsumer.java.
 *
 * Notes:
 *
 * The data generating loop must explicitly release every object reference it
 * obtains in order to avoid a memory leak.  A local JNI object reference is not
 * automatically released until control returns to java, which never happens as
 * long as the data generating loop runs.  This applies to any JNI function that
 * obtains an object reference (such as CallObjectMethod() or NewObject()).  A
 * local reference is released by calling DeleteLocalRef(), which is safe to
 * call with an exception pending.
 *
 * It is important to check for an exception after calling java code from native
 * C, such as after notifying the java consumer of new data.  Failure to do this
 * makes it possible for users of the interface to crash the JVM by throwing an
 * exception in java code.
 *
 * Some JNI functions, like GetIntField() or ReleaseStringUTFChars(), do not
 * need to be checked for exceptions.
 *
 * GetStringUTFChars() returns NULL if and only if an exception was thrown.
 *
 * It is important to stop a DTrace consumer and remove it if an exception
 * occurs.  This API guarantees that a consumer is stopped automatically if it
 * throws an exception.  An application running multiple DTrace consumers
 * simultaneously should be able to continue running the others normally if any
 * fail.
 *
 * Calls to libdtrace are not guaranteed to be MT-safe.  Even if they are
 * currently MT-safe, they are not guaranteed to remain that way.  To address
 * this, a global lock (the LocalConsumer.class reference) is used around calls
 * to libdtrace.  In many cases, the locking is done in java, which should be
 * indicated in this file by a comment above the function that assumes prior
 * locking.  To access the same global lock from native C code, the JNI function
 * MonitorEnter() is used.  Each MonitorEnter() must have a matching
 * MonitorExit() or the application will hang (all consumer threads).  The
 * consumer loop and the getAggregate() method require a per-consumer lock
 * rather than a global lock; in that case the argument to MonitorEnter() and
 * MonitorExit() is the consumerLock member of the LocalConsumer, not the
 * LocalConsumer itself.
 */

#define	DTRACE_JNI_VERSION 2

#define	FIRST_HANDLE 0		/* sequence-generated consumer ID */
#define	NO_HANDLE -1
#define	INITIAL_CAPACITY 8	/* initial size of consumer array */
#define	MAX_CAPACITY_INCREMENT 1024

static boolean_t g_dtj_load = B_FALSE;
static int g_handle_seq = NO_HANDLE;
/*
 * key: caller's consumer handle (int)
 * value: per-consumer data includes dtrace handle (consumer_t *)
 */
static dtj_consumer_t **g_consumer_table = NULL;
static size_t g_consumer_capacity = 0;
static size_t g_consumer_count = 0;
static size_t g_max_capacity_increment = MAX_CAPACITY_INCREMENT;
static size_t g_max_consumers = 0; /* no maximum */
static boolean_t g_init = B_FALSE;
static pthread_mutex_t g_table_lock;
static pthread_mutexattr_t g_table_lock_attr;
pthread_key_t g_dtj_consumer_key;

static int dtj_get_handle(JNIEnv *, jobject);
static dtj_status_t dtj_get_java_consumer(JNIEnv *, jobject,
    dtj_java_consumer_t *);
static const char *dtj_getexecname(void);
static jobject dtj_get_program_info(dtj_java_consumer_t *, dtrace_proginfo_t *);
static jobject dtj_add_program(dtj_java_consumer_t *, dtj_program_t *);
static void dtj_flag(uint_t *, uint_t, boolean_t *, boolean_t *);
static boolean_t dtj_cflag(dtj_java_consumer_t *, const char *, boolean_t *,
    boolean_t *);
static void dtj_list_probes(JNIEnv *, jobject, jobject, jobject,
    dtrace_probe_f *);
static void dtj_list_compiled_probes(JNIEnv *, jobject, jobject, jobject,
    dtrace_probe_f *);
static int dtj_list_probe(dtrace_hdl_t *, const dtrace_probedesc_t *, void *);
static int dtj_list_probe_detail(dtrace_hdl_t *, const dtrace_probedesc_t *,
    void *);
static int dtj_list_stmt(dtrace_hdl_t *, dtrace_prog_t *, dtrace_stmtdesc_t *,
    void *);
static boolean_t dtj_add_consumer(JNIEnv *, dtj_consumer_t *, int *);
static dtj_consumer_t *dtj_remove_consumer(JNIEnv *, jobject);
static dtj_consumer_t *dtj_remove_consumer_at(int);

/*
 * Gets a sequence-generated consumer ID, or NO_HANDLE if exception pending
 */
static int
dtj_get_handle(JNIEnv *jenv, jobject caller)
{
	int handle;

	if (!g_dtj_load) {
		dtj_throw_illegal_state(jenv, "JNI table not loaded");
		return (NO_HANDLE);
	}
	handle = (*jenv)->CallIntMethod(jenv, caller, g_gethandle_jm);
	if ((*jenv)->ExceptionCheck(jenv)) {
		return (NO_HANDLE);
	}
	if (handle == NO_HANDLE) {
		dtj_throw_illegal_state(jenv, "no consumer handle");
	}
	return (handle);
}

/*
 * Populates the given java consumer created for use in the current native
 * method call.  If the return value is DTJ_ERR, a java exception is pending.
 * Throws IllegalStateException if the caller does not have a valid handle.
 * Throws NoSuchElementException if the caller's handle is not in the global
 * caller table.
 */
static dtj_status_t
dtj_get_java_consumer(JNIEnv *jenv, jobject caller, dtj_java_consumer_t *jc)
{
	dtj_consumer_t *consumer;
	int handle = dtj_get_handle(jenv, caller);
	if (handle == NO_HANDLE) {
		return (DTJ_ERR); /* java exception pending */
	}
	(void) pthread_mutex_lock(&g_table_lock);
	if (g_consumer_table) {
		if ((handle >= 0) && (handle < g_consumer_capacity)) {
			consumer = g_consumer_table[handle];
		} else {
			consumer = NULL;
		}
	} else {
		consumer = NULL;
	}
	(void) pthread_mutex_unlock(&g_table_lock);
	if (consumer == NULL) {
		dtj_throw_no_such_element(jenv, "consumer handle %d", handle);
		return (DTJ_ERR);
	}

	/* Initialize java consumer */
	bzero(jc, sizeof (dtj_java_consumer_t));

	/* Attach per-consumer data */
	jc->dtjj_consumer = consumer;

	/* Attach per-JNI-invocation data */
	jc->dtjj_caller = caller;
	jc->dtjj_jenv = jenv;

	return (DTJ_OK);
}

/*
 * Adds a consumer to the global consumer table.
 * Returns B_TRUE if successful; a java exception is pending otherwise.
 * Postcondition: if successful, g_handle_seq is the handle of the consumer just
 * added.
 */
static boolean_t
dtj_add_consumer(JNIEnv *jenv, dtj_consumer_t *c, int *seq)
{
	int start;

	if (!g_init) {
		(void) pthread_key_create(&g_dtj_consumer_key, NULL);
		(void) pthread_mutexattr_init(&g_table_lock_attr);
		(void) pthread_mutexattr_settype(&g_table_lock_attr,
		    PTHREAD_MUTEX_RECURSIVE);
		(void) pthread_mutex_init(&g_table_lock,
		    &g_table_lock_attr);
		g_init = B_TRUE;
	}

	*seq = NO_HANDLE;
	(void) pthread_mutex_lock(&g_table_lock);
	if (g_consumer_table == NULL) {
		g_consumer_table = malloc(INITIAL_CAPACITY *
		    sizeof (dtj_consumer_t *));
		if (!g_consumer_table) {
			g_handle_seq = NO_HANDLE;
			dtj_throw_out_of_memory(jenv,
			    "could not allocate consumer table");
			(void) pthread_mutex_unlock(&g_table_lock);
			return (B_FALSE);
		}
		bzero(g_consumer_table, (INITIAL_CAPACITY *
		    sizeof (dtj_consumer_t *)));
		g_consumer_capacity = INITIAL_CAPACITY;
	} else if (g_consumer_count >= g_consumer_capacity) {
		dtj_consumer_t **t;
		size_t new_capacity;

		if ((g_max_consumers > 0) && (g_consumer_count >=
		    g_max_consumers)) {
			dtj_throw_resource_limit(jenv, "Too many consumers");
			(void) pthread_mutex_unlock(&g_table_lock);
			return (B_FALSE);
		}

		if (g_consumer_capacity <= g_max_capacity_increment) {
			new_capacity = (g_consumer_capacity * 2);
		} else {
			new_capacity = (g_consumer_capacity +
			    g_max_capacity_increment);
		}

		if ((g_max_consumers > 0) && (new_capacity > g_max_consumers)) {
			new_capacity = g_max_consumers;
		}

		t = realloc(g_consumer_table,
		    new_capacity * sizeof (dtj_consumer_t *));
		if (!t) {
			dtj_throw_out_of_memory(jenv,
			    "could not reallocate consumer table");
			(void) pthread_mutex_unlock(&g_table_lock);
			return (B_FALSE);
		}

		g_consumer_table = t;
		bzero(g_consumer_table + g_consumer_capacity, ((new_capacity -
		    g_consumer_capacity) * sizeof (dtj_consumer_t *)));
		g_consumer_capacity = new_capacity;
	}

	/* Look for an empty slot in the table */
	g_handle_seq = (g_handle_seq == NO_HANDLE
	    ? FIRST_HANDLE : g_handle_seq + 1);
	if (g_handle_seq >= g_consumer_capacity) {
		g_handle_seq = FIRST_HANDLE;
	}
	start = g_handle_seq; /* guard against infinite loop */
	while (g_consumer_table[g_handle_seq] != NULL) {
		++g_handle_seq;
		if (g_handle_seq == start) {
			dtj_throw_illegal_state(jenv, "consumer table full,"
			    " but count %d < capacity %d",
			    g_consumer_count, g_consumer_capacity);
			(void) pthread_mutex_unlock(&g_table_lock);
			return (B_FALSE);
		} else if (g_handle_seq >= g_consumer_capacity) {
			g_handle_seq = FIRST_HANDLE;
		}
	}
	g_consumer_table[g_handle_seq] = c;
	*seq = g_handle_seq;
	++g_consumer_count;
	(void) pthread_mutex_unlock(&g_table_lock);
	return (B_TRUE);
}

/*
 * Removes a consumer from the global consumer table.  The call may be initiated
 * from Java code or from native code (because an exception has occurred).
 * Precondition: no exception pending (any pending exception must be temporarily
 * cleared)
 * Returns NULL if the caller is not in the table or if this function throws an
 * exception; either case leaves the global consumer table unchanged.
 * Throws IllegalStateException if the caller does not have a valid handle.
 */
static dtj_consumer_t *
dtj_remove_consumer(JNIEnv *jenv, jobject caller)
{
	dtj_consumer_t *consumer;
	int handle = dtj_get_handle(jenv, caller);
	if (handle == NO_HANDLE) {
		return (NULL); /* java exception pending */
	}
	consumer = dtj_remove_consumer_at(handle);
	return (consumer);
}

/*
 * Returns NULL if there is no consumer with the given handle.  Does not throw
 * exceptions.
 */
static dtj_consumer_t *
dtj_remove_consumer_at(int handle)
{
	dtj_consumer_t *consumer;
	(void) pthread_mutex_lock(&g_table_lock);
	if (g_consumer_table) {
		if ((handle >= 0) && (handle < g_consumer_capacity)) {
			consumer = g_consumer_table[handle];
			if (consumer != NULL) {
				g_consumer_table[handle] = NULL;
				--g_consumer_count;
				if (g_consumer_count == 0) {
					free(g_consumer_table);
					g_consumer_table = NULL;
					g_consumer_capacity = 0;
					g_handle_seq = NO_HANDLE;
				}
			}
		} else {
			consumer = NULL;
		}
	} else {
		consumer = NULL;
	}
	(void) pthread_mutex_unlock(&g_table_lock);
	return (consumer);
}

/*
 * Gets the name of the executable in case it is an application with an embedded
 * JVM and not "java".  Implementation is copied from lib/mpss/common/mpss.c.
 * The use of static auxv_t makes the MT-level unsafe.  The caller is expected
 * to use the global lock (LocalConsumer.class).
 */
static const char *
dtj_getexecname(void)
{
	const char	*execname = NULL;
	static auxv_t	auxb;

	/*
	 * The first time through, read the initial aux vector that was
	 * passed to the process at exec(2).  Only do this once.
	 */
	int fd = open("/proc/self/auxv", O_RDONLY);

	if (fd >= 0) {
		while (read(fd, &auxb, sizeof (auxv_t)) == sizeof (auxv_t)) {
			if (auxb.a_type == AT_SUN_EXECNAME) {
				execname = auxb.a_un.a_ptr;
				break;
			}
		}
		(void) close(fd);
	}
	return (execname);
}

/*
 * Add the compiled program to a list of programs the API expects to enable.
 * Returns the Program instance identifying the listed program, or NULL if the
 * Program constructor fails (exception pending in that case).
 */
static jobject
dtj_add_program(dtj_java_consumer_t *jc, dtj_program_t *p)
{
	JNIEnv *jenv = jc->dtjj_jenv;

	jobject jprogram = NULL;

	switch (p->dtjp_type) {
	    case DTJ_PROGRAM_STRING:
		jprogram = (*jenv)->NewObject(jenv, g_program_jc,
		    g_proginit_jm);
		break;
	    case DTJ_PROGRAM_FILE:
		jprogram = (*jenv)->NewObject(jenv, g_programfile_jc,
		    g_fproginit_jm);
		break;
	    default:
		dtj_throw_illegal_argument(jenv, "unexpected program type %d\n",
		    p->dtjp_type);
	}
	if ((*jenv)->ExceptionCheck(jenv)) {
		return (NULL);
	}

	/* Does not throw exceptions */
	(*jenv)->SetIntField(jenv, jprogram, g_progid_jf,
	    uu_list_numnodes(jc->dtjj_consumer->dtjc_program_list));

	if (!dtj_list_add(jc->dtjj_consumer->dtjc_program_list, p)) {
		(*jenv)->DeleteLocalRef(jenv, jprogram);
		dtj_throw_out_of_memory(jenv,
		    "could not add program");
		return (NULL);
	}

	return (jprogram);
}

/*
 * Returns a new ProgramInfo, or NULL if the constructor fails (java exception
 * pending in that case).
 */
static jobject
dtj_get_program_info(dtj_java_consumer_t *jc, dtrace_proginfo_t *pinfo)
{
	JNIEnv *jenv = jc->dtjj_jenv;

	jobject minProbeAttributes = NULL;
	jobject minStatementAttributes = NULL;
	jobject programInfo = NULL; /* return value */

	minProbeAttributes = dtj_new_attribute(jc, &pinfo->dpi_descattr);
	if (!minProbeAttributes) {
		return (NULL); /* java exception pending */
	}
	minStatementAttributes = dtj_new_attribute(jc, &pinfo->dpi_stmtattr);
	if (!minStatementAttributes) {
		(*jenv)->DeleteLocalRef(jenv, minProbeAttributes);
		return (NULL); /* java exception pending */
	}

	programInfo = (*jenv)->NewObject(jenv, g_proginfo_jc,
	    g_proginfoinit_jm, minProbeAttributes, minStatementAttributes,
	    pinfo->dpi_matches);
	(*jenv)->DeleteLocalRef(jenv, minProbeAttributes);
	(*jenv)->DeleteLocalRef(jenv, minStatementAttributes);

	return (programInfo);
}

/*
 * Called by LocalConsumer static initializer.
 */
JNIEXPORT void JNICALL
/* ARGSUSED */
Java_org_opensolaris_os_dtrace_LocalConsumer__1checkVersion(JNIEnv *env,
    jclass class, jint version)
{
	if (version != DTRACE_JNI_VERSION) {
		dtj_throw_illegal_state(env,
		    "LocalConsumer version %d incompatible with native "
		    "implementation version %d", version, DTRACE_JNI_VERSION);
	}
}

/*
 * Called by LocalConsumer static initializer.
 */
JNIEXPORT void JNICALL
/* ARGSUSED */
Java_org_opensolaris_os_dtrace_LocalConsumer__1loadJniTable(JNIEnv *env,
    jclass class)
{
	if (g_dtj_load) {
		/*
		 * JNI table includes a global reference to the LocalConsumer
		 * class, preventing the class from being unloaded.  The
		 * LocalConsumer static initializer should never execute more
		 * than once.
		 */
		dtj_throw_illegal_state(env, "JNI table already loaded");
		return;
	}

	/* If this fails, a Java Error (e.g. NoSuchMethodError) is pending */
	if (dtj_load(env) == DTJ_OK) {
		g_dtj_load = B_TRUE;
	}
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1open(JNIEnv *env, jobject obj,
    jobjectArray flags)
{
	dtrace_hdl_t *dtp;
	dtj_consumer_t *c;
	const char *f; /* flag name */
	int oflags = 0;
	int i, len;
	int id;
	int err;

	jobject flag = NULL;
	jstring flagname = NULL;

	if (!g_dtj_load) {
		dtj_throw_illegal_state(env, "JNI table not loaded");
		return;
	}

	c = dtj_consumer_create(env);
	if (!c) {
		return; /* java exception pending */
	}

	/* Get open flags */
	len = (flags ? (*env)->GetArrayLength(env, flags) : 0);
	for (i = 0; i < len; ++i) {
		flag = (*env)->GetObjectArrayElement(env, flags, i);
		if ((*env)->ExceptionCheck(env)) {
			dtj_consumer_destroy(c);
			return;
		}

		flagname = (*env)->CallObjectMethod(env, flag, g_enumname_jm);
		(*env)->DeleteLocalRef(env, flag);
		if ((*env)->ExceptionCheck(env)) {
			dtj_consumer_destroy(c);
			return;
		}
		f = (*env)->GetStringUTFChars(env, flagname, NULL);
		if ((*env)->ExceptionCheck(env)) {
			(*env)->DeleteLocalRef(env, flagname);
			dtj_consumer_destroy(c);
			return;
		}
		if (strcmp(f, "ILP32") == 0) {
			oflags |= DTRACE_O_ILP32;
		} else if (strcmp(f, "LP64") == 0) {
			oflags |= DTRACE_O_LP64;
		}
		(*env)->ReleaseStringUTFChars(env, flagname, f);
		(*env)->DeleteLocalRef(env, flagname);
	}

	/* Check for mutually exclusive flags */
	if ((oflags & DTRACE_O_ILP32) && (oflags & DTRACE_O_LP64)) {
		dtj_throw_illegal_argument(env,
		    "Cannot set both ILP32 and LP64");
		dtj_consumer_destroy(c);
		return;
	}

	/*
	 * Make sure we can add the consumer before calling dtrace_open().
	 * Repeated calls to open() when the consumer table is maxed out should
	 * avoid calling dtrace_open().  (Normally there is no limit to the size
	 * of the consumer table, but the undocumented JAVA_DTRACE_MAX_CONSUMERS
	 * system property lets you set a limit after which
	 * ResourceLimitException is thrown.)
	 */
	if (!dtj_add_consumer(env, c, &id)) {
		dtj_consumer_destroy(c);
		return; /* java exception pending */
	}

	(*env)->CallVoidMethod(env, obj, g_sethandle_jm, id);
	if ((*env)->ExceptionCheck(env)) {
		(void) dtj_remove_consumer_at(id);
		dtj_consumer_destroy(c);
		return;
	}

	if ((dtp = dtrace_open(DTRACE_VERSION, oflags, &err)) == NULL) {
		dtj_java_consumer_t jc;
		jc.dtjj_jenv = env;
		dtj_throw_dtrace_exception(&jc, dtrace_errmsg(NULL, err));
		(void) dtj_remove_consumer_at(id);
		dtj_consumer_destroy(c);
		return;
	}
	c->dtjc_dtp = dtp; /* set consumer handle to native DTrace library */
}

static void
dtj_flag(uint_t *flags, uint_t flag, boolean_t *get, boolean_t *set)
{
	assert((get && !set) || (set && !get));

	if (get) {
		*get = (*flags & flag);
	} else {
		if (*set) {
			*flags |= flag;
		} else {
			*flags &= ~flag;
		}
	}
}

/*
 * Returns B_TRUE if opt is a recognized compile flag, B_FALSE otherwise.
 */
static boolean_t
dtj_cflag(dtj_java_consumer_t *jc, const char *opt, boolean_t *get,
    boolean_t *set)
{
	boolean_t is_cflag = B_TRUE;
	uint_t *flags = &jc->dtjj_consumer->dtjc_cflags;

	/* see lib/libdtrace/common/dt_option.c */
	if (strcmp(opt, "argref") == 0) {
		dtj_flag(flags, DTRACE_C_ARGREF, get, set);
	} else if (strcmp(opt, "cpp") == 0) {
		dtj_flag(flags, DTRACE_C_CPP, get, set);
	} else if (strcmp(opt, "defaultargs") == 0) {
		dtj_flag(flags, DTRACE_C_DEFARG, get, set);
	} else if (strcmp(opt, "empty") == 0) {
		dtj_flag(flags, DTRACE_C_EMPTY, get, set);
	} else if (strcmp(opt, "errtags") == 0) {
		dtj_flag(flags, DTRACE_C_ETAGS, get, set);
	} else if (strcmp(opt, "knodefs") == 0) {
		dtj_flag(flags, DTRACE_C_KNODEF, get, set);
	} else if (strcmp(opt, "nolibs") == 0) {
		dtj_flag(flags, DTRACE_C_NOLIBS, get, set);
	} else if (strcmp(opt, "pspec") == 0) {
		dtj_flag(flags, DTRACE_C_PSPEC, get, set);
	} else if (strcmp(opt, "unodefs") == 0) {
		dtj_flag(flags, DTRACE_C_UNODEF, get, set);
	} else if (strcmp(opt, "verbose") == 0) {
		dtj_flag(flags, DTRACE_C_DIFV, get, set);
	} else if (strcmp(opt, "zdefs") == 0) {
		dtj_flag(flags, DTRACE_C_ZDEFS, get, set);
	} else {
		is_cflag = B_FALSE;
	}

	if (is_cflag && set &&
	    (jc->dtjj_consumer->dtjc_state != DTJ_CONSUMER_INIT)) {
		dtj_throw_illegal_state(jc->dtjj_jenv,
		    "cannot set compile time option \"%s\" after calling go()",
		    opt);
		return (is_cflag);
	}

	return (is_cflag);
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT jobject JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1compileString(JNIEnv *env,
    jobject obj, jstring program, jobjectArray args)
{
	const char *prog;
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;
	dtj_program_t *p;
	int argc = 0;
	char **argv = NULL;

	jstring jprogram = NULL;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return (NULL); /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	prog = (*env)->GetStringUTFChars(env, program, 0);
	if ((*env)->ExceptionCheck(env)) {
		return (NULL);
	}

	p = dtj_program_create(env, DTJ_PROGRAM_STRING, prog);
	if (!p) {
		(*env)->ReleaseStringUTFChars(env, program, prog);
		return (NULL); /* java exception pending */
	}

	if (args) {
		argv = dtj_get_argv(env, args, &argc);
		if ((*env)->ExceptionCheck(env)) {
			(*env)->ReleaseStringUTFChars(env, program, prog);
			dtj_program_destroy(p, NULL);
			return (NULL);
		}
	}

	if ((p->dtjp_program = dtrace_program_strcompile(dtp,
	    prog, DTRACE_PROBESPEC_NAME, jc.dtjj_consumer->dtjc_cflags,
	    argc, argv)) == NULL) {
		dtj_throw_dtrace_exception(&jc,
		    "invalid probe specifier %s: %s",
		    prog, dtrace_errmsg(dtp, dtrace_errno(dtp)));
		(*env)->ReleaseStringUTFChars(env, program, prog);
		dtj_program_destroy(p, NULL);
		dtj_free_argv(argv);
		return (NULL);
	}
	(*env)->ReleaseStringUTFChars(env, program, prog);
	dtj_free_argv(argv);

	jprogram = dtj_add_program(&jc, p);
	return (jprogram); /* NULL if exception pending */
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT jobject JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1compileFile(JNIEnv *env,
    jobject obj, jstring filename, jobjectArray args)
{
	FILE *fp;
	const char *file;
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;
	dtj_program_t *p;
	int argc = 0;
	char **argv = NULL;

	jstring jprogram = NULL;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return (NULL); /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	file = dtj_GetStringNativeChars(env, filename);
	if ((fp = fopen(file, "r")) == NULL) {
		dtj_throw_dtrace_exception(&jc, "failed to open %s", file);
		dtj_ReleaseStringNativeChars(env, filename, file);
		return (NULL);
	}

	p = dtj_program_create(env, DTJ_PROGRAM_FILE, file);
	if (!p) {
		(void) fclose(fp);
		dtj_ReleaseStringNativeChars(env, filename, file);
		return (NULL); /* java exception pending */
	}

	if (args) {
		argv = dtj_get_argv(env, args, &argc);
		if ((*env)->ExceptionCheck(env)) {
			(void) fclose(fp);
			dtj_ReleaseStringNativeChars(env, filename, file);
			dtj_program_destroy(p, NULL);
			return (NULL);
		}
	}

	if ((p->dtjp_program = dtrace_program_fcompile(dtp,
	    fp, jc.dtjj_consumer->dtjc_cflags, argc, argv)) == NULL) {
		dtj_throw_dtrace_exception(&jc,
		    "failed to compile script %s: %s", file,
		    dtrace_errmsg(dtp, dtrace_errno(dtp)));
		(void) fclose(fp);
		dtj_ReleaseStringNativeChars(env, filename, file);
		dtj_program_destroy(p, NULL);
		dtj_free_argv(argv);
		return (NULL);
	}
	(void) fclose(fp);
	dtj_ReleaseStringNativeChars(env, filename, file);
	dtj_free_argv(argv);

	jprogram = dtj_add_program(&jc, p);
	return (jprogram); /* NULL if exception pending */
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1exec(JNIEnv *env, jobject obj,
    jobject program)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;
	int progid = -1;
	uu_list_walk_t *itr;
	dtj_program_t *p;
	dtrace_proginfo_t *pinfo = NULL;
	int i;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	if (program) {
		progid = (*env)->GetIntField(env, program, g_progid_jf);
	}

	if (dtj_list_empty(jc.dtjj_consumer->dtjc_program_list)) {
		dtj_throw_illegal_state(env, "no program compiled");
		return;
	}

	itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_program_list, 0);
	for (i = 0; (p = uu_list_walk_next(itr)) != NULL; ++i) {
		/* enable all probes or those of given program only */
		if ((progid != -1) && (progid != i)) {
			continue;
		}

		if (p->dtjp_enabled) {
			dtj_throw_illegal_state(env, "program already enabled");
			uu_list_walk_end(itr);
			return;
		}

		pinfo = &p->dtjp_info;
		if (dtrace_program_exec(dtp, p->dtjp_program, pinfo) == -1) {
			dtj_throw_dtrace_exception(&jc,
			    "failed to enable %s: %s", p->dtjp_name,
			    dtrace_errmsg(dtp, dtrace_errno(dtp)));
			uu_list_walk_end(itr);
			return;
		}
		p->dtjp_enabled = B_TRUE;
	}
	uu_list_walk_end(itr);

	if (program) {
		jobject programInfo = NULL;

		if (!pinfo) {
			/*
			 * Consumer.enable() has already checked that the
			 * program was compiled by this consumer.  This is an
			 * implementation error, not a user error.
			 */
			dtj_throw_illegal_state(env, "program not found");
			return;
		}

		programInfo = dtj_get_program_info(&jc, pinfo);
		if (!programInfo) {
			return; /* java exception pending */
		}

		(*env)->SetObjectField(env, program, g_proginfo_jf,
		    programInfo);
		(*env)->DeleteLocalRef(env, programInfo);
	}
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1getProgramInfo(JNIEnv *env,
    jobject obj, jobject program)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;
	int progid;
	uu_list_walk_t *itr;
	dtj_program_t *p;
	dtrace_proginfo_t *pinfo = NULL;
	int i;

	jobject programInfo = NULL;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	progid = (*env)->GetIntField(env, program, g_progid_jf);

	if (dtj_list_empty(jc.dtjj_consumer->dtjc_program_list)) {
		dtj_throw_illegal_state(env, "no program compiled");
		return;
	}

	itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_program_list, 0);
	for (i = 0; ((p = uu_list_walk_next(itr)) != NULL) && !pinfo; ++i) {
		if (progid != i) {
			/* get info of given program only */
			continue;
		}

		pinfo = &p->dtjp_info;
		dtrace_program_info(dtp, p->dtjp_program, pinfo);
	}
	uu_list_walk_end(itr);

	programInfo = dtj_get_program_info(&jc, pinfo);
	if (!programInfo) {
		return; /* java exception pending */
	}

	(*env)->SetObjectField(env, program, g_proginfo_jf,
	    programInfo);
	(*env)->DeleteLocalRef(env, programInfo);
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1setOption(JNIEnv *env,
    jobject obj, jstring option, jstring value)
{
	dtj_java_consumer_t jc;
	const char *opt;
	const char *val;
	boolean_t on;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}

	opt = (*env)->GetStringUTFChars(env, option, 0);
	if (!opt) {
		dtj_throw_out_of_memory(env,
		    "could not allocate option string");
		return;
	}

	if (value) {
		val = (*env)->GetStringUTFChars(env, value, 0);
		if (!val) {
			dtj_throw_out_of_memory(env,
			    "could not allocate option value string");
			(*env)->ReleaseStringUTFChars(env, option, opt);
			return;
		}
	} else {
		/*
		 * dtrace_setopt() sets option to 0 if value is NULL.  That's
		 * not the same thing as unsetting a boolean option, since
		 * libdtrace uses -2 to mean unset.  We'll leave it to
		 * LocalConsumer.java to disallow null or not.
		 */
		val = NULL;
	}

	/*
	 * Check for boolean compile-time options not handled by
	 * dtrace_setopt().
	 */
	on = (!val || (strcmp(val, "unset") != 0));
	if (dtj_cflag(&jc, opt, NULL, &on)) {
		(*env)->ReleaseStringUTFChars(env, option, opt);
		if (value) {
			(*env)->ReleaseStringUTFChars(env, value, val);
		}
		return;
	}

	/*
	 * The transition from INIT to GO is protected by synchronization
	 * (a per-consumer lock) in LocalConsumer.java, ensuring that go() and
	 * setOption() execute sequentially.
	 */
	if (jc.dtjj_consumer->dtjc_state != DTJ_CONSUMER_INIT) {
		/*
		 * If the consumer is already running, defer setting the option
		 * until we wake up from dtrace_sleep.
		 */
		dtj_request_t *request;


		(void) pthread_mutex_lock(
		    &jc.dtjj_consumer->dtjc_request_list_lock);
		request = dtj_request_create(env, DTJ_REQUEST_OPTION, opt, val);
		if (request) {
			if (!dtj_list_add(jc.dtjj_consumer->dtjc_request_list,
			    request)) {
				dtj_throw_out_of_memory(env,
				    "Failed to add setOption request");
			}
		} /* else java exception pending */
		(void) pthread_mutex_unlock(
		    &jc.dtjj_consumer->dtjc_request_list_lock);
	} else {
		dtrace_hdl_t *dtp = jc.dtjj_consumer->dtjc_dtp;
		if (dtrace_setopt(dtp, opt, val) == -1) {
			dtj_throw_dtrace_exception(&jc, dtrace_errmsg(dtp,
			    dtrace_errno(dtp)));
		}
	}

	(*env)->ReleaseStringUTFChars(env, option, opt);
	if (value) {
		(*env)->ReleaseStringUTFChars(env, value, val);
	}
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT jlong JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1getOption(JNIEnv *env,
    jobject obj, jstring option)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;
	const char *opt;
	dtrace_optval_t optval;
	boolean_t cflag;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return (0); /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	opt = (*env)->GetStringUTFChars(env, option, 0);
	if (!opt) {
		dtj_throw_out_of_memory(env,
		    "could not allocate option string");
		return (0);
	}

	/*
	 * Check for boolean compile-time options not handled by
	 * dtrace_setopt().
	 */
	if (dtj_cflag(&jc, opt, &cflag, NULL)) {
		(*env)->ReleaseStringUTFChars(env, option, opt);
		return (cflag ? 1 : DTRACEOPT_UNSET);
	}

	if (dtrace_getopt(dtp, opt, &optval) == -1) {
		dtj_throw_dtrace_exception(&jc,
		    "couldn't get option %s: %s", opt,
		    dtrace_errmsg(dtp, dtrace_errno(dtp)));
		(*env)->ReleaseStringUTFChars(env, option, opt);
		return (0);
	}
	(*env)->ReleaseStringUTFChars(env, option, opt);
	return (optval);
}

/*
 * Throws IllegalStateException if not all compiled programs are enabled.
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1checkProgramEnabling(JNIEnv *env,
    jobject obj)
{
	dtj_java_consumer_t jc;
	dtj_program_t *p;
	uu_list_walk_t *itr;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}

	if (dtj_list_empty(jc.dtjj_consumer->dtjc_program_list)) {
		dtj_throw_illegal_state(env, "no program compiled");
		return;
	}

	itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_program_list, 0);
	while ((p = uu_list_walk_next(itr)) != NULL) {
		if (!p->dtjp_enabled) {
			const char *type;
			switch (p->dtjp_type) {
			case DTJ_PROGRAM_STRING:
				type = "description";
				break;
			case DTJ_PROGRAM_FILE:
				type = "script";
				break;
			default:
				assert(p->dtjp_type == DTJ_PROGRAM_STRING ||
				    p->dtjp_type == DTJ_PROGRAM_FILE);
			}
			dtj_throw_illegal_state(env,
			    "Not all compiled probes are enabled. "
			    "Compiled %s %s not enabled.",
			    type, p->dtjp_name);
			uu_list_walk_end(itr);
			return;
		}
	}
	uu_list_walk_end(itr);
}

JNIEXPORT jboolean JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1isEnabled(JNIEnv *env,
    jobject obj)
{
	dtj_java_consumer_t jc;
	dtj_program_t *p;
	uu_list_walk_t *itr;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return (JNI_FALSE); /* java exception pending */
	}

	if (dtj_list_empty(jc.dtjj_consumer->dtjc_program_list)) {
		return (JNI_FALSE); /* no program compiled */
	}

	itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_program_list, 0);
	while ((p = uu_list_walk_next(itr)) != NULL) {
		if (!p->dtjp_enabled) {
			uu_list_walk_end(itr);
			return (JNI_FALSE);
		}
	}
	uu_list_walk_end(itr);

	return (JNI_TRUE);
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1go(JNIEnv *env, jobject obj)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	if (dtj_set_callback_handlers(&jc) != DTJ_OK) {
		return; /* java exception pending */
	}

	if (dtrace_go(dtp) != 0) {
		dtj_throw_dtrace_exception(&jc,
		    "could not enable tracing: %s",
		    dtrace_errmsg(dtp, dtrace_errno(dtp)));
		return;
	}

	jc.dtjj_consumer->dtjc_state = DTJ_CONSUMER_GO;
}

/*
 * Protected by global lock (LocalConsumer.class).  Called when aborting the
 * consumer loop before it starts.
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1stop(JNIEnv *env,
    jobject obj)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	if (dtrace_stop(dtp) == -1) {
		dtj_throw_dtrace_exception(&jc,
		    "couldn't stop tracing: %s",
		    dtrace_errmsg(jc.dtjj_consumer->dtjc_dtp,
		    dtrace_errno(jc.dtjj_consumer->dtjc_dtp)));
	} else {
		jc.dtjj_consumer->dtjc_state = DTJ_CONSUMER_STOP;
	}
}

JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1consume(JNIEnv *env,
    jobject obj)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;
	jc.dtjj_consumer->dtjc_state = DTJ_CONSUMER_START;

	if (dtj_java_consumer_init(env, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}

	/*
	 * Must set the thread-specific java consumer before starting the
	 * dtrace_work() loop because the bufhandler can also be invoked by
	 * getAggregate() from another thread.  The bufhandler needs access to
	 * the correct JNI state specific to either the consumer loop or the
	 * getAggregate() call.
	 */
	(void) pthread_setspecific(g_dtj_consumer_key, &jc);

	if (jc.dtjj_consumer->dtjc_process_list != NULL) {
		struct ps_prochandle *P;
		uu_list_walk_t *itr;

		(*env)->MonitorEnter(env, g_caller_jc);
		if ((*env)->ExceptionCheck(env)) {
			dtj_java_consumer_fini(env, &jc);
			return; /* java exception pending */
		}

		itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_process_list,
		    0);
		while ((P = dtj_pointer_list_walk_next(itr)) !=
		    DTJ_INVALID_PTR) {
			dtrace_proc_continue(dtp, P);
		}
		uu_list_walk_end(itr);

		(*env)->MonitorExit(env, g_caller_jc);
		if ((*env)->ExceptionCheck(env)) {
			dtj_java_consumer_fini(env, &jc);
			return; /* java exception pending */
		}
	}

	/*
	 * Blocking call starts consumer loop.
	 */
	(void) dtj_consume(&jc);

	dtj_java_consumer_fini(env, &jc);
	/*
	 * Stop the consumer after the consumer loop terminates, whether
	 * normally because of the exit() action or LocalConsumer.stop(), or
	 * abnormally because of an exception.  The call is ignored if the
	 * consumer is already stopped.  It is safe to call dtj_stop() with a
	 * pending exception.
	 */
	dtj_stop(&jc);
}

/*
 * Interrupts a running consumer.  May be called from any thread.
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1interrupt(JNIEnv *env,
    jobject obj)
{
	dtj_java_consumer_t jc;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}

	jc.dtjj_consumer->dtjc_interrupt = B_TRUE;
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1close(JNIEnv *env, jobject obj)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	/*
	 * Need to release any created procs here, since the consumer_t
	 * destructor (called by _destroy) will not have access to the dtrace
	 * handle needed to release them (this function closes the dtrace
	 * handle).
	 */
	if (jc.dtjj_consumer->dtjc_process_list != NULL) {
		struct ps_prochandle *P;
		uu_list_walk_t *itr;
		itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_process_list,
		    0);
		while ((P = dtj_pointer_list_walk_next(itr)) !=
		    DTJ_INVALID_PTR) {
			dtrace_proc_release(dtp, P);
		}
		uu_list_walk_end(itr);
	}

	dtrace_close(dtp);
}

/*
 * Static Consumer.java method
 */
JNIEXPORT jint JNICALL
/* ARGSUSED */
Java_org_opensolaris_os_dtrace_LocalConsumer__1openCount(JNIEnv *env, jclass c)
{
	int open;
	(void) pthread_mutex_lock(&g_table_lock);
	if (g_consumer_table == NULL) {
		open = 0;
	} else {
		open = g_consumer_count;
	}
	(void) pthread_mutex_unlock(&g_table_lock);
	return (open);
}

/*
 * Static Consumer.java method
 */
JNIEXPORT jlong JNICALL
/* ARGSUSED */
Java_org_opensolaris_os_dtrace_LocalConsumer__1quantizeBucket(JNIEnv *env,
    jclass c, jint bucket)
{
	return (DTRACE_QUANTIZE_BUCKETVAL(bucket));
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT jstring JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1lookupKernelFunction(
    JNIEnv *jenv, jobject caller, jobject address)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;

	jstring jfunc; /* return value */

	GElf_Addr addr;
	char dummy;
	char *s;
	int rc;

	if (dtj_get_java_consumer(jenv, caller, &jc) != DTJ_OK) {
		return (NULL); /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	/* Does not throw exceptions */
	if ((*jenv)->IsInstanceOf(jenv, address, g_int_jc)) {
		/* intValue() of class Number does not throw exceptions */
		addr = (GElf_Addr)(uint32_t)(*jenv)->CallIntMethod(jenv,
		    address, g_intval_jm);
	} else if ((*jenv)->IsInstanceOf(jenv, address, g_number_jc)) {
		/* longValue() of class Number does not throw exceptions */
		addr = (GElf_Addr)(*jenv)->CallLongMethod(jenv,
		    address, g_longval_jm);
	} else {
		dtj_throw_class_cast(jenv, "Expected Number address");
		return (NULL);
	}

	rc = dtrace_addr2str(dtp, addr, &dummy, 1);
	s = malloc(rc + 1);
	if (!s) {
		dtj_throw_out_of_memory(jenv,
		    "Failed to allocate kernel function name");
		return (NULL);
	}
	(void) dtrace_addr2str(dtp, addr, s, rc + 1);

	jfunc = (*jenv)->NewStringUTF(jenv, s);
	free(s);
	return (jfunc);
}

/*
 * Protected by global lock in Consumer.java
 */
JNIEXPORT jstring JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1lookupUserFunction(JNIEnv *jenv,
    jobject caller, jint pid, jobject address)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;

	jstring jfunc; /* return value */

	GElf_Addr addr;
	char dummy;
	char *s;
	int rc;

	if (dtj_get_java_consumer(jenv, caller, &jc) != DTJ_OK) {
		return (NULL); /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	/* Does not throw exceptions */
	if ((*jenv)->IsInstanceOf(jenv, address, g_int_jc)) {
		/* intValue() of class Number does not throw exceptions */
		addr = (GElf_Addr)(uint32_t)(*jenv)->CallIntMethod(jenv,
		    address, g_intval_jm);
	} else if ((*jenv)->IsInstanceOf(jenv, address, g_number_jc)) {
		/* longValue() of class Number does not throw exceptions */
		addr = (GElf_Addr)(*jenv)->CallLongMethod(jenv,
		    address, g_longval_jm);
	} else {
		dtj_throw_class_cast(jenv, "Expected Number address");
		return (NULL);
	}

	rc = dtrace_uaddr2str(dtp, pid, addr, &dummy, 1);
	s = malloc(rc + 1);
	if (!s) {
		dtj_throw_out_of_memory(jenv,
		    "Failed to allocate user function name");
		return (NULL);
	}
	(void) dtrace_uaddr2str(dtp, pid, addr, s, rc + 1);

	jfunc = (*jenv)->NewStringUTF(jenv, s);
	free(s);
	return (jfunc);
}

JNIEXPORT jobject JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1getAggregate(JNIEnv *env,
    jobject obj, jobject spec)
{
	dtj_java_consumer_t jc;

	jobject aggregate = NULL;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return (NULL); /* java exception pending */
	}

	if (dtj_java_consumer_init(env, &jc) != DTJ_OK) {
		return (NULL); /* java exception pending */
	}
	jc.dtjj_aggregate_spec = spec;

	/*
	 * Must set the thread-specific java consumer before calling any
	 * function that triggers callbacks to the bufhandler set by
	 * dtrace_handle_buffered().  The bufhandler needs access to the correct
	 * JNI state specific to either the consumer loop or the
	 * getAggregate() call.
	 */
	(void) pthread_setspecific(g_dtj_consumer_key, &jc);
	aggregate = dtj_get_aggregate(&jc);
	if (!aggregate) {
		/* java exception pending */
		jc.dtjj_consumer->dtjc_interrupt = B_TRUE;
	}
	/*
	 * Cleans up only references created by this JNI invocation.  Leaves
	 * cached per-consumer state untouched.
	 */
	dtj_java_consumer_fini(env, &jc);
	return (aggregate);
}

/*
 * Returns the pid of the created process, or -1 in case of an error (java
 * exception pending).
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT int JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1createProcess(JNIEnv *jenv,
    jobject caller, jstring command)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;
	struct ps_prochandle *P;
	char **argv;
	int argc;

	if (dtj_get_java_consumer(jenv, caller, &jc) != DTJ_OK) {
		return (-1); /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	if (jc.dtjj_consumer->dtjc_process_list == NULL) {
		jc.dtjj_consumer->dtjc_process_list = dtj_pointer_list_create();
		if (!jc.dtjj_consumer->dtjc_process_list) {
			dtj_throw_out_of_memory(jenv,
			    "Could not allocate process list");
			return (-1);
		}
	}

	argv = dtj_make_argv(jenv, command, &argc);
	if ((*jenv)->ExceptionCheck(jenv)) {
		return (-1);
	}

	P = dtrace_proc_create(dtp, argv[0], argv);
	dtj_free_argv(argv);

	if (!P) {
		dtj_throw_dtrace_exception(&jc, dtrace_errmsg(dtp,
		    dtrace_errno(dtp)));
		return (-1);
	}

	if (!dtj_pointer_list_add(jc.dtjj_consumer->dtjc_process_list, P)) {
		dtj_throw_out_of_memory(jenv,
		    "Failed to add process to process list");
		dtrace_proc_release(dtp, P);
		return (-1);
	}
	return (Pstatus(P)->pr_pid);
}

/*
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1grabProcess(JNIEnv *jenv,
    jobject caller, jint pid)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;
	struct ps_prochandle *P;

	if (dtj_get_java_consumer(jenv, caller, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	if (jc.dtjj_consumer->dtjc_process_list == NULL) {
		jc.dtjj_consumer->dtjc_process_list = dtj_pointer_list_create();
		if (jc.dtjj_consumer->dtjc_process_list == NULL) {
			dtj_throw_out_of_memory(jenv,
			    "Could not allocate process list");
			return;
		}
	}

	P = dtrace_proc_grab(dtp, (pid_t)pid, 0);
	if (!P) {
		dtj_throw_dtrace_exception(&jc, dtrace_errmsg(dtp,
		    dtrace_errno(dtp)));
		return;
	}

	if (!dtj_pointer_list_add(jc.dtjj_consumer->dtjc_process_list, P)) {
		dtj_throw_out_of_memory(jenv,
		    "Failed to add process to process list");
		dtrace_proc_release(dtp, P);
		return;
	}
}

/*
 * Lists all probes, or lists matching probes (using the matching rules from
 * Table 4-1 of the DTrace manual).
 *
 * In the future it may be desirable to support an array of probe filters rather
 * than a single filter.  It could be that if a probe matched any of the given
 * filters, it would be included (implied logical OR).
 *
 * Protected by global lock (LocalConsumer.class)
 * param list: an empty list to populate (this function empties the list if it
 * is not empty already)
 * param filter: a ProbeDescription instance; the list will include only probes
 * that match the filter (match all probes if filter is null)
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1listProbes(JNIEnv *env,
    jobject obj, jobject list, jobject filter)
{
	dtj_list_probes(env, obj, list, filter, dtj_list_probe);
}

JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1listProbeDetail(JNIEnv *env,
    jobject obj, jobject list, jobject filter)
{
	dtj_list_probes(env, obj, list, filter, dtj_list_probe_detail);
}

static void
dtj_list_probes(JNIEnv *env, jobject obj, jobject list, jobject filter,
    dtrace_probe_f *func)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;
	dtrace_probedesc_t probe;
	dtrace_probedesc_t *pdp = NULL;
	const char *probestr;
	int rc;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;

	jc.dtjj_probelist = list;

	/* clear in-out list parameter */
	(*env)->CallVoidMethod(env, list, g_listclear_jm);
	if ((*env)->ExceptionCheck(env)) {
		return;
	}

	if (filter) {
		jstring jprobestr = NULL;

		jprobestr = (*env)->CallObjectMethod(env, filter,
		    g_tostring_jm);
		if ((*env)->ExceptionCheck(env)) {
			return;
		}
		probestr = (*env)->GetStringUTFChars(env, jprobestr, NULL);
		if (!probestr) {
			(*env)->DeleteLocalRef(env, jprobestr);
			return; /* java exception pending */
		}

		bzero(&probe, sizeof (probe));
		rc = dtrace_str2desc(dtp, DTRACE_PROBESPEC_NAME, probestr,
		    &probe);
		(*env)->ReleaseStringUTFChars(env, jprobestr, probestr);
		(*env)->DeleteLocalRef(env, jprobestr);
		if (rc == -1) {
			dtj_throw_dtrace_exception(&jc,
			    "%s is not a valid probe description: %s",
			    probestr, dtrace_errmsg(dtp,
			    dtrace_errno(dtp)));
			return;
		}

		pdp = &probe;
	}

	(void) dtrace_probe_iter(dtp, pdp, func, &jc);
}

/*
 * Returns 0 to indicate success, or -1 to cause dtrace_probe_iter() to return a
 * negative value prematurely (indicating no match or failure).
 */
static int
/* ARGSUSED */
dtj_list_probe(dtrace_hdl_t *dtp, const dtrace_probedesc_t *pdp, void *arg)
{
	dtj_java_consumer_t *jc = arg;
	JNIEnv *jenv = jc->dtjj_jenv;

	jobject jprobedesc = NULL;

	jprobedesc = dtj_new_probedesc(jc, pdp);
	if (!jprobedesc) {
		return (-1); /* java exception pending */
	}

	/* add probe to list */
	(*jenv)->CallVoidMethod(jenv, jc->dtjj_probelist, g_listadd_jm,
	    jprobedesc);
	(*jenv)->DeleteLocalRef(jenv, jprobedesc);
	if ((*jenv)->ExceptionCheck(jenv)) {
		return (-1);
	}

	return (0);
}

/*ARGSUSED*/
static int
dtj_list_probe_detail(dtrace_hdl_t *dtp, const dtrace_probedesc_t *pdp,
    void *arg)
{
	dtj_java_consumer_t *jc = arg;
	JNIEnv *jenv = jc->dtjj_jenv;
	dtrace_probeinfo_t p;

	jobject jprobedesc = NULL;
	jobject jprobeinfo = NULL;
	jobject jprobe = NULL;

	jprobedesc = dtj_new_probedesc(jc, pdp);
	if (!jprobedesc) {
		return (-1); /* java exception pending */
	}

	/*
	 * If dtrace_probe_info() returns a non-zero value, dtrace_errno is set
	 * for us.  In that case, ignore the dtrace error and simply omit probe
	 * info.  That error is implicitly cleared the next time a call is made
	 * using the same dtrace handle.
	 */
	if (dtrace_probe_info(dtp, pdp, &p) == 0) {
		/* create probe info instance */
		jprobeinfo = dtj_new_probeinfo(jc, &p);
		if (!jprobeinfo) {
			(*jenv)->DeleteLocalRef(jenv, jprobedesc);
			return (-1); /* java exception pending */
		}
	}

	/* create listed probe instance */
	jprobe = (*jenv)->NewObject(jenv, g_probe_jc, g_probeinit_jm,
	    jprobedesc, jprobeinfo);
	(*jenv)->DeleteLocalRef(jenv, jprobedesc);
	(*jenv)->DeleteLocalRef(jenv, jprobeinfo);
	if (!jprobe) {
		return (-1); /* java exception pending */
	}

	/* add probe to list */
	(*jenv)->CallVoidMethod(jenv, jc->dtjj_probelist, g_listadd_jm,
	    jprobe);
	(*jenv)->DeleteLocalRef(jenv, jprobe);
	if ((*jenv)->ExceptionCheck(jenv)) {
		return (-1);
	}

	return (0);
}

/*ARGSUSED*/
static int
dtj_list_stmt(dtrace_hdl_t *dtp, dtrace_prog_t *pgp,
    dtrace_stmtdesc_t *stp, void *arg)
{
	dtj_java_consumer_t *jc = arg;
	dtrace_ecbdesc_t *edp = stp->dtsd_ecbdesc;

	if (edp == jc->dtjj_consumer->dtjc_last_probe) {
		return (0);
	}

	if (dtrace_probe_iter(dtp, &edp->dted_probe,
	    jc->dtjj_consumer->dtjc_plistfunc, arg) != 0) {
		dtj_throw_dtrace_exception(jc,
		    "failed to match %s:%s:%s:%s: %s",
		    edp->dted_probe.dtpd_provider, edp->dted_probe.dtpd_mod,
		    edp->dted_probe.dtpd_func, edp->dted_probe.dtpd_name,
		    dtrace_errmsg(dtp, dtrace_errno(dtp)));
		return (1);
	}

	jc->dtjj_consumer->dtjc_last_probe = edp;
	return (0);
}

/*
 * Protected by global lock in Consumer.java
 */
JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1listCompiledProbes(JNIEnv *env,
    jobject obj, jobject list, jobject program)
{
	dtj_list_compiled_probes(env, obj, list, program, dtj_list_probe);
}

JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1listCompiledProbeDetail(
    JNIEnv *env, jobject obj, jobject list, jobject program)
{
	dtj_list_compiled_probes(env, obj, list, program,
	    dtj_list_probe_detail);
}

static void
dtj_list_compiled_probes(JNIEnv *env, jobject obj, jobject list,
    jobject program, dtrace_probe_f *func)
{
	dtj_java_consumer_t jc;
	dtrace_hdl_t *dtp;
	uu_list_walk_t *itr;
	dtj_program_t *p;
	boolean_t found;
	int progid = -1;
	int i;

	if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) {
		return; /* java exception pending */
	}
	dtp = jc.dtjj_consumer->dtjc_dtp;
	jc.dtjj_probelist = list;

	(*env)->CallVoidMethod(env, list, g_listclear_jm);
	if ((*env)->ExceptionCheck(env)) {
		return;
	}

	if (program) {
		if (dtj_list_empty(jc.dtjj_consumer->dtjc_program_list)) {
			dtj_throw_no_such_element(env, "no compiled program");
			return;
		}
		progid = (*env)->GetIntField(env, program, g_progid_jf);
		if (progid == -1) {
			dtj_throw_illegal_argument(env, "invalid program");
			return;
		}
	}

	jc.dtjj_consumer->dtjc_plistfunc = func;
	found = B_FALSE;
	itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_program_list, 0);
	for (i = 0; (p = uu_list_walk_next(itr)) != NULL; ++i) {
		if ((progid != -1) && (progid != i)) {
			continue;
		}

		found = B_TRUE;
		(void) dtrace_stmt_iter(dtp, p->dtjp_program,
		    (dtrace_stmt_f *)dtj_list_stmt, &jc);
	}
	uu_list_walk_end(itr);

	if (program && !found) {
		dtj_throw_no_such_element(env, "program not found");
	}
}

/*
 * Static LocalConsumer.java method
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT jstring JNICALL
/* ARGSUSED */
Java_org_opensolaris_os_dtrace_LocalConsumer__1getVersion(JNIEnv *env,
    jclass class)
{
	/*
	 * Handles the case of locale-specific encoding of the user-visible
	 * version string containing non-ASCII characters.
	 */
	return (dtj_NewStringNative(env, _dtrace_version));
}

/*
 * Static LocalConsumer.java method
 * Protected by global lock (LocalConsumer.class)
 */
JNIEXPORT jstring JNICALL
/* ARGSUSED */
Java_org_opensolaris_os_dtrace_LocalConsumer__1getExecutableName(JNIEnv *env,
    jclass class)
{
	jstring jname = NULL;
	const char *name = NULL;
	char *s;
	int len;

	name = dtj_getexecname();
	len = strlen(name);
	s = malloc(len + 1);
	if (!s) {
		dtj_throw_out_of_memory(env, "Failed to allocate execname");
		return (NULL);
	}
	(void) strcpy(s, name);
	name = basename(s);
	free(s);
	jname = (*env)->NewStringUTF(env, name);
	return (jname);
}

/*
 * Static LocalConsumer.java method
 */
JNIEXPORT void JNICALL
/* ARGSUSED */
Java_org_opensolaris_os_dtrace_LocalConsumer__1setMaximumConsumers(JNIEnv *env,
    jclass class, jint max)
{
	g_max_consumers = max;
}

/*
 * Static LocalConsumer.java method
 */
JNIEXPORT void JNICALL
/* ARGSUSED */
Java_org_opensolaris_os_dtrace_LocalConsumer__1setDebug(JNIEnv *env,
    jclass class, jboolean debug)
{
	g_dtj_util_debug = debug;
}

JNIEXPORT void JNICALL
Java_org_opensolaris_os_dtrace_LocalConsumer__1destroy(JNIEnv *env, jobject obj)
{
	dtj_consumer_t *c;

	c = dtj_remove_consumer(env, obj);
	if (c == NULL) {
		return; /* java exception pending */
	}
	dtj_consumer_destroy(c);
}