/*
 * DECnet object for executing java class files as script requests.
 * The http server connects to this object when requests of
 * the form /jbin/class/... occur and begins a dialog using the scriptserver
 * protocol.  This program creates a client thread to process the
 * connection:
 *
 *   1. Client thread reads scriptserver prologue and parses the url_path
 *      to extract the name of the java class to execute (first path element
 *      following /jbin/).  Periods in class name are replaced with slashes.
 *
 *   2. Client thread attaches itself to the java virtual machine and
 *      attempts to find the requested class, aborting with an error message
 *      if not found.  Location for class files is determined by
 *      the www_jvm_classpath logical.  If periods were present in the
 *      class name, the class file location is in corresponding sub-directories
 *      relative to the classpath.
 *
 *   3a. If the requested class name contained periods, the piece before the
 *       first period is considered the 'shell' class and an attempt to
 *       load this class is made and searched for the method 
 *	 WWWrun(WWWJexec,java.lang.Class).  If a shell class is found,
 *	 proceed to step 4.
 *
 *   3b.The requested class is searched for the method WWWrun(WWWJexec),
 *      again aborting with an error message if not found.
 *
 *   4. A WWWJexec object instance is created and initialized with the
 *      the prologue values and the DECnet connection handle.
 *
 *   5. The WWWrun method located in step 3a or 3b is invoked with the object 
 *      created in step 4 and (step 3a) the target class as it's argument(s).
 *
 *   6. Client thread deattaches itself from the java VM, closes the
 *      connection,  and exits.
 *
 * Usage:
 *    $ mcr disk:[dir]JAVA_SCRIPT log-file
 *
 * HTTP server configuration:
 *    exec /jbin/* 0::"0=WWWJAVA"wwwrun
 *
 * DECnet proxy configuration:
 *     add/proxy node::* *  /default	! lets user proxy to themselves.
 *
 * Required logical names:
 *    WWW_JAVA_ACCESS		Multi-valued logical name, each equivalence name
 *				is of form NODE::USERNAME, where node is the
 *				nodename of the server and username is the
 *				username of the HTTP server account.
 *
 * Required environment variables:
 *    WWW_JAVA_CLIENT_LIMIT	Maximum number of concurrent threads allowed.
 *    WWW_JAVA_OBJECT		Decnet object (e.g. WWWJAVA) to declare, must
 *				match object name for 'exec /htbin~*' rule
 *				used by server.
 *    WWW_JVM_CLASSPATH		Search list used by Java VM to locate class
 *				files.
 *
 * Other environment variables:
 *    WWW_JAVA_LOG_LEVEL	Set logging level for trace file, default: 0.
 *    WWW_JVM_xxxx		Sets parameters for Java Virtual Machine.
 *
 * Related files:
 *    WWWJAVA.COM		DECnet task file which allows automatic
 *				startup of the server process on demand.
 *
 *    WWWJEXEC.JAVA (.CLASS)	Java class for interfacing the java script
 *				with the DECnet connection.  If declares native
 *				methods that this server implements.
 *
 *    HTTP_SYSTARTUP.COM	Should be modified to install this image with
 *				SYSNAM privilege to allow it declare a
 *				permanent DECnet object.
 *
 * Required privileges:
 *    SYSNAM
 *
 * Author:   David Jones
 * Date:     24-OCT_1997
 * Revised:  28-OCT-1997	Support 'shell' classes.
 * REvised:  8-JUN-2000		Update to JDK 1.2 JNI interface.
 */
#define sys$setprv SYS$SETPRV
 
#include "PTHREAD_1C_NP.H"
#include <stdio.h>
#include <stdlib.h>
#include <prvdef.h>
#include <descrip.h>
#include <jni.h>		/* sys$common:[java.include]jni.h */
#include "tutil.h"
#include "tserver_decnet.h"
#include "decnet_access.h"
int tlog_putlog(int,char *, ...);
int tlog_init(char *), tlog_flush();
int sys$setprv();

typedef struct { char *s; int l; } string;

static int new_client 
	( ts_client_ctx ctx, struct ncbdef *ncb, int ndx, int available );

static JavaVM *jvm;			/* Java Virtual machine context */
static JDK1_1InitArgs vm_args;
static jstring exec_read_string ( JNIEnv  *env, jclass this );
static jint exec_read_raw ( JNIEnv  *env, jclass this, 
	jbyteArray buffer, jint count );
static jint exec_write_string ( JNIEnv *env, jclass this, jstring string );
static jint exec_write_raw ( JNIEnv *env, jclass this, 
		jbyteArray string, jint count );
static jint exec_close ( JNIEnv *env, jclass this );
static jint exec_put_log ( JNIEnv *env, jclass this, jint lvl, jstring string );
#define NATIVE_METHODS 6
static JNINativeMethod wwwjexec_natives[NATIVE_METHODS] = {
   { "read", "()Ljava/lang/String;", (void *) &exec_read_string },
   { "read", "([BI)I", (void *) &exec_read_raw },
   { "write", "(Ljava/lang/String;)I", (void *) &exec_write_string },
   { "write", "([BI)I", (void *) &exec_write_raw },
   { "close", "()I", (void *) &exec_close },
   { "putLog", "(ILjava/lang/String;)I", (void *) &exec_put_log }
};
static jclass wwwjexec;			/* Class for carrying scriptserver
					 * protocol. */

static struct {
   jfieldID func[1];		/* field for func field in WWWJexec */
   jfieldID method;		/* field for method field in WWWJexec */
   jfieldID protocol;		/* field for url_path field in WWWJexec */
   jfieldID url_path;		/* field for url_path field in WWWJexec */
   jfieldID script_path;	/* field to hold DNETPATH response */
   jfieldID cnx;		/* field for cnx field in WWWJexec */
} field_id;
#define string_sig "Ljava/lang/String;"
static char *field_id_names[6] = 
	{ "func", "method", "protocol", "url_path", "script_path", "cnx" };
static char *field_id_sig[6] = 
	{ string_sig, string_sig, string_sig, string_sig, string_sig, "J" };
static int java_log_level;
/******************************************************************************/
/* Modify operating parameters for Java Virt. Machine, modifying the
 * parameter structure used in creating the VM.  Return 0 if fatal error.
 *
 * Parameters are adjusted by looking for the environment
 */
static int configure_jvm_parameter ( char *name, jint *value )
{
    char vname[100], *new_value;
    tu_strcpy ( vname, "WWW_JVM_" );
    tu_strcpy ( &vname[8], name );
    new_value = getenv ( vname );
    if ( new_value ) {
	jint original_value;
	original_value = *value;
	*value = (jint) atoi ( new_value );
	tlog_putlog ( 0, "   !AZ = !SL (!SL)!/", name, *value, original_value );
    } else {
	tlog_putlog ( 0, "   !AZ = !SL!/", name, *value );
    }
    return 1;
}

static int configure_JavaVM ( JDK1_1InitArgs *vm_args ) 
{
    char *path_override;
    /*
     * Check for overrides of integer parameters.
     */
    tlog_putlog ( 0, "Java VM parameters, version !XL(!XL) (default value):!/",
		vm_args->version, JNI_VERSION_1_1 );

    configure_jvm_parameter ( "check_source", &vm_args->checkSource );
    configure_jvm_parameter ( "native_stack_size", &vm_args->nativeStackSize );
    configure_jvm_parameter ( "java_stack_size", &vm_args->javaStackSize );
    configure_jvm_parameter ( "min_heap_size", &vm_args->minHeapSize );
    configure_jvm_parameter ( "max_heap_size", &vm_args->maxHeapSize );
    configure_jvm_parameter ( "verify", &vm_args->verifyMode );
    configure_jvm_parameter ( "enable_class_GC", &vm_args->enableClassGC );
    configure_jvm_parameter ( "enable_verbose_GC", &vm_args->enableVerboseGC );
    configure_jvm_parameter ( "disable_async_GC", &vm_args->disableAsyncGC );
    configure_jvm_parameter ( "verbose", &vm_args->verbose );
    /* configure_jvm_parameter ( "debug_agent", &vm_args->debugAgent ); */
    configure_jvm_parameter ( "debug_port", &vm_args->debugPort );
    /*
     * See if classpath override specified.
     */
    path_override = getenv ( "WWW_JVM_CLASSPATH" );
    if ( path_override ) {
	char *original_path;
	original_path = vm_args->classpath ? vm_args->classpath : "{NULL}";
	vm_args->classpath = malloc ( tu_strlen(path_override)+1 );
	tu_strcpy ( vm_args->classpath, path_override );
	tlog_putlog ( 0, "   classpath = '!AZ' (!AZ)!/", vm_args->classpath,
		original_path );
    } else {
	tlog_putlog ( 0, "   classpath = '!AZ'!/", 
	    vm_args->classpath ? vm_args->classpath : "{NULL}" );
    }
    return 1;
}
/******************************************************************************/
/* Main entry point.
 */
int main ( int argc, char **argv )
{
    int client_limit, i, status, *final_status, pthread_create();
    int server_mode, is_privileged;
    char *object, *log_fname, *arg, taskname[256];
    pthread_attr_t attr;
    pthread_t listener, writer, clock;
    long priv_mask[2], prev_priv[2];
    JNIEnv *env;			/* per-thread environment context */
    jint result;
    jclass cls;
    /*
     * Disable SYSNAM and determine if was set.
     */
    priv_mask[0] = PRV$M_SYSNAM; priv_mask[1] = 0;
    status = sys$setprv ( 0, priv_mask, 0, prev_priv );
    is_privileged = (prev_priv[0]&PRV$M_SYSNAM);
    /*
     * Get operating parameters:
     *    DECnet object name		WWW_JAVA_OBJECT logical.
     *    log file			argv[1] (default = sys$output)
     *    Access control                WWW_JAVA_ACCESS logical (node::user).
     *    Client limit			WWW_JAVA_CLIENT_LIMIT logical
     */
    server_mode = ts_get_taskname ( "WWW_JAVA_OBJECT", taskname, 
	sizeof(taskname) );

    java_log_level = 0;
    if ( argc > 1 ) 
        tlog_init ( argv[1] );
    else
        tlog_init ( "sys$output" );
    tlog_putlog(0,"Java scriptserver 0.2 started at !%D, objectname: '!AZ'!/", 0,
	taskname );
    if ( server_mode ) {
	if ( !is_privileged ) {
	    tlog_putlog ( 0, "Insufficient privilege to declare persistent task!/");
	    server_mode = 0;
	}
	status = ts_set_access ( "WWW_JAVA_ACCESS" );
	if ( server_mode && (status&1) == 0 ) {
	    tlog_putlog ( 0, "Error setting access list (WWW_JAVA_ACCESS)!/");
	    exit ( status );
	}
    }
    arg = getenv ( "WWW_JAVA_CLIENT_LIMIT" );
    if ( arg ) {
	client_limit = atoi ( arg );
    } client_limit = 16;
    arg = getenv ( "WWW_JAVA_LOG_LEVEL" );
    if ( arg ) {
	java_log_level = atoi ( arg );
    } client_limit = 16;
    /*
     * Create Java Virtual Machine (VM).  Save handle in global storage.
     */
    JNI_GetDefaultJavaVMInitArgs(&vm_args);	/* parameters for creating VM */
    if ( !configure_JavaVM ( &vm_args ) ) return 0;

    result = JNI_CreateJavaVM ( &jvm, (void *) &env, &vm_args );
    tlog_putlog(0, "Java VM compeleted boot at !%T, client limit: !SL!/",
	0, client_limit );
    if ( result < 0 ) {
	tlog_putlog(0,"Could not create Java VM, aborting!/");
	return 0;
    }
    /*
     * Load the wwwjexec class and bind its native methods to routines
     * defined in this module.
     */
    cls = (*env)->FindClass(env, "WWWJexec");
    if ( cls == 0 ) {
	tlog_putlog ( 0, "Could not find WWWJexec class definition!/" );
	return 0;
    }
    wwwjexec = (*env)->NewGlobalRef ( env, cls );	/* make permanent */
    if ( cls == 0 ) {
	tlog_putlog ( 0, "Could not make WWWJexec class a global reference!/" );
	return 0;
    }
    status = (*env)->RegisterNatives 
	( env, wwwjexec, wwwjexec_natives, NATIVE_METHODS );
    if ( status < 0 ) {
	tlog_putlog ( 0, "Could not register 1 or more Native methods:!/" );
	for ( i = 0; i < NATIVE_METHODS; i++ ) {
	    status = (*env)->RegisterNatives ( env, wwwjexec,
		&wwwjexec_natives[i], 1 );
	    tlog_putlog ( 0, "  !AZpublic native !AZ !AZ!/",
		(status<0) ? "* " : "  ", wwwjexec_natives[i].name,
		wwwjexec_natives[i].signature );
	}
	tlog_putlog ( 0, 
	    "use javap -s -p WWWJexec to compare with class file!/" );
	return 0;
    }
    /*
     * Get field IDs to used to access fields ifn wwwjexec class
     */
    for ( i = 0; i < 6; i++ ) {
        field_id.func[i] = (*env)->GetFieldID ( env, wwwjexec, 
		field_id_names[i], field_id_sig[i] );
	if ( field_id.func[i] == 0 ) tlog_putlog ( 0,
		"Error looking up field ID for !AZ (!AZ) in WWWJexec class!/",
		field_id_names[i], field_id_sig[i] );
    }
    if ( i < 6 ) return 0;	/*   abort. */
    /*
     * Create threads:
     *   Listener thread listens for client connects, creating new client
     *   threads.
     */
    INITIALIZE_THREAD_ATTR ( &attr );
    pthread_attr_setstacksize ( &attr, 100000 + vm_args.javaStackSize );

    status = sys$setprv ( 1, priv_mask, 0, 0 );
    status = ts_declare_decnet_object 
		( taskname, client_limit, &attr, &listener, new_client );
    (void) sys$setprv ( 0, priv_mask, 0, 0 );
    if ( (status&1) == 0 ) {
	tlog_putlog ( 0, "Error declaring DECnet object: !SL!/", status );
	fprintf(stderr, "Error declaring object %s: %d\n", taskname, status );
	exit(status);
    }
    /*
     * Wait for listener thread exit.
     */
    pthread_join ( listener, (void *)  &final_status );

    exit ( *final_status );
}
/*****************************************************************************/
/* Compose standard error response.
 */
static struct {
    char *cue;			/* sts_text */
    char *detail;
} extended_err[] = {
{ "Requested script (", "\n\
The requested script was either improperly specified in the URL or the\n\
script or its directory was inaccessible by the script process" },

{ "Invalid classname", "\n\
The java classname you specified was too long to fit in the buffer." },

{"Could not attach client thread", "\n\
The internal thread created to handle your request could not make itself\n\
known to the Java Virtual Machine.  This error indicates a problem with\n\
the server software and should be reported to the site's webmaster." },

{"Could not load requested class", "\n\
The classname you requested to execute could not be loaded by the Java \n\
Virtual Machine.  Either the class file does not exist or is not a valid\n\
file for loading." },

{"Could not locate method WWWrun in shell for specified class", "\n\
The 'shell' class for the classname you request to execute does not contain\n\
a WWWrun method (or method is not present with the right arguments) and is\n\
therefore not runnable by this service.  The shell class is the class whose\n\
name matches the requested classname to the first period." },

{"Could not locate method WWWrun", "\n\
The Java class you requested for execution does not contain a WWWrun method\n\
(or no WWWrun method is present with the right arguments) and is therefore \n\
not runnable by this service." },

{ "", "" }		/* end marker */
};
static void abort_client ( ts_client_ctx ctx, char *sts_line, char *sts_text )
{
    int status;
    /*
     * Place into text mode and send specified text.
     */
    status = ts_decnet_write ( ctx, "<DNETTEXT>", 10 );
    status = ts_decnet_write ( ctx, sts_line, tu_strlen(sts_line) );
    status = ts_decnet_write ( ctx, sts_text, tu_strlen(sts_text) );
    /*
     * Look for extended detail.
     */
    if ( (status&1) ) {
	int i, length;
	for ( i = 0; extended_err[i].cue[0]; i++ ) {
	    length = tu_strlen ( extended_err[i].cue );
	    if ( 0 == tu_strncmp (extended_err[i].cue, sts_text, length) ) {
		status = ts_decnet_write ( ctx, extended_err[i].detail,
			tu_strlen ( extended_err[i].detail ) );
		break;
	    }
	}
    }
}
/*****************************************************************************/
static int new_client ( ts_client_ctx ctx, 
	struct ncbdef *ncb, 
	int ndx, 
	int available )
{
    int status, length, opcode, i, j, script_user_len, hierarchy_level;
    char taskname[256], remote_node[256], remote_user[64];
    char source[64];
    string prologue[5];
    JNIEnv *jenv;
    JDK1_1AttachArgs targs;
    jint result;
    jclass cls, shell;
    jobjectArray arg_list;
    jobject link, string;
    jmethodID mid;
    char *url, *bp, *classname, *subclassname;
    void *script_cnx;
    char prolog_buf[1104], script_path[256], script_dir[256];
    char script_user[256];

    ts_decnet_info ( taskname, remote_node, remote_user );

    tu_strcpy ( source, remote_node );
    tu_strcpy ( &source[tu_strlen(source)], remote_user );
    if ( java_log_level > 0 ) tlog_putlog(0,
	"!AZ/!SL Connect from !AZ at !%D!/", taskname, ndx, source, 0 );
    /*                   0      1        2        3
     * Read prologue (module, method, protocol, ident) sent by HTTP server.
     */
    for ( i = 0, bp = prolog_buf; i < 4; i++ ) {
	status = ts_decnet_read ( ctx, bp, 255, &length );
	if ( (status&1) == 1 ) {
	    if ( length == 4 && tu_strncmp ( bp, "STOP", 4 ) == 0 ) {
		/* Special hack to force shutdown, brutal */
		tlog_putlog(0,
			"STOP function requested, shutting down at !%D!/", 0 );
		exit ( 3 );
	    }
	    prologue[i].l = length;
	    prologue[i].s = bp;
	    bp[length++] = '\0';	/* safety first, terminate string */
	    bp = &bp[length];
	} else {
	    if ( java_log_level > 0 ) tlog_putlog ( 0, 
		"!%D Error reading prologue: !SL!/", 0,	status );
	    return status;
	}
    }
    url = prologue[3].s;
    /*
     * Query server for script path (DNETPATH), make prologue[4].
     */
    status = ts_decnet_write ( ctx, "<DNETPATH>", 10 );
    if ( (status&1) != 1 ) return status;
    status = ts_decnet_read ( ctx, script_path, 255, &length );
    if ( (status&1) != 1 ) return status;
    for ( i = 0; i < length; i++ ) if ( script_path[i] != url[i] ) {
	/*
	 * Consistency check failed, first part of url should match script path.
	 */
	abort_client (ctx,"500 consistency check failed",
		"Internal path consistency failure" );
	return 20;
    }
    script_path[length] = '\0';
    prologue[4].s = script_path;
    prologue[4].l = length;
    /*
     * Parse class name out of url (same position normal script name).
     * Count number of periods in class name (hierarchy_level).
     */
    hierarchy_level = 0;
    subclassname = (char *) 0;
    for ( i = 0; url[i+length]; i++ ) {
	if ( i >= sizeof(script_user) ) {	/* name too long */
	    abort_client (ctx,"400 invalid class(too long)", 
		"Invalid classname" );
	    return 20;
	} else if ( url[i+length] == '/' ) {
	    script_user[i] = '\0';
	    break;
	} else if ( url[i+length] == '.' ) {
	    hierarchy_level++;
	    script_user[i] = '/';
	    subclassname = &script_user[i+1];
	} else {
	    script_user[i] = url[i+length];
	}
    }
    script_user_len = i;
    script_user[i] = '\0';
    classname = (script_user[0] == '/') ? &script_user[1] : script_user;
    /*
     * Attach thread to Java machine, return jenv pointer
     */
    result = (*jvm)->AttachCurrentThread(jvm, (void *) &jenv, &targs );
    if ( result < 0 ) {
	abort_client ( ctx,"500 Thread attach failure",
		"Could not attach client thread to Java Virtual Machine" );
	tlog_putlog ( 0, 
	   "!AZ/!SL error: failed to attach thread to java VM!/",
	    taskname, ndx );
        ts_decnet_close ( ctx );
	return 0;
    }
    /*
     * Load class specified by client.
     */
    if ( java_log_level > 0 ) tlog_putlog(0, 
		"!AZ/!SL class name: !AZ (!AZ)!/", taskname, ndx, classname,
		subclassname ? subclassname : classname );
    cls = (*jenv)->FindClass(jenv,subclassname ? subclassname : classname);
    if ( cls == 0 ) {
	abort_client ( ctx,"400 class not found",
		"Could not load requested class" );
	if ( java_log_level > 0 ) tlog_putlog(0,
	   "!AZ/!SL error: failed to load class '!AZ'!/",
	    taskname, ndx, classname );
	mid = 0;
	hierarchy_level = -1;
    } else if ( hierarchy_level > 0 ) {
	/*
	 * Try to locate shell class, which is first part of class name.
	 */
	char *slash;
	slash = tu_strstr ( classname, "/" );
	*slash = '\0';
	shell = (*jenv)->FindClass(jenv,classname);
	*slash = '/';
	if ( shell == 0 ) {
	    hierarchy_level = 0;	/* revert to regular call */
	} else {
	    /*
	     * Get method
	     */
	    mid = (*jenv)->GetStaticMethodID(jenv,shell, "WWWrun",
		"(LWWWJexec;Ljava/lang/Class;)V" );
	    if ( mid == 0 ) {
	        abort_client ( ctx,"400 shell method not found",
		    "Could not locate method WWWrun in shell for specified class" );
	    }
	}
    }
    if ( hierarchy_level == 0 ) {
        /*
         * Locate WWWrun method
         */
        mid = (*jenv)->GetStaticMethodID(jenv,cls, "WWWrun", "(LWWWJexec;)V" );
        if ( mid == 0 ) {
             if ( java_log_level > 0 ) tlog_putlog ( 0,
	   "!AZ/!SL error: failed to find WWWrun method in '!AZ'!/",
	    taskname, ndx, classname );
	    abort_client ( ctx,"400 method not found",
		"Could not locate method WWWrun in specified class" );
	}
    }
    /*
     * Build argument list and invoke method.
     */
    if ( mid != 0 ) {
	link = (*jenv)->AllocObject ( jenv, wwwjexec );
        if ( link == 0 ) {
	    tlog_putlog(0,"Error creating array!/");
	    abort_client ( ctx, "500 Error instatiating WWWJexec object",
		"Could not allocate new WWWJexec object" );
	} else {
	    (*jenv)->SetIntField ( jenv, link, field_id.cnx, (long) ctx );

	    for ( i = 0; i < 5; i++ ) {
	        string = (*jenv)->NewStringUTF(jenv,prologue[i].s);
		if ( string == 0 ) break;
		(*jenv)->SetObjectField(jenv,link, field_id.func[i], string);
	    }
            if ( i < 5 ) {
		tlog_putlog(0,"Error setting field[!SL]!/", i);
		abort_client ( ctx, "500 Error initializing WWWJexec",
		    "Could not initialize prologue on WWWJexec object" );
	    } else if ( hierarchy_level == 0 ) {
		/*
		 * Direct call to WWWrun of specified class, 1 argument.
		 */
	    	(*jenv)->CallStaticVoidMethod(jenv, cls, mid, link );
	    } else {
		/*
		 * Call WWWrun in shell class with target class as 2nd argument.
		 */
		(*jenv)->CallStaticVoidMethod(jenv, shell, mid, link, cls );
	    }
	}
    }

    /*
     * Cleanup and log activity.
     */
    (*jvm)->DetachCurrentThread(jvm);
    ts_decnet_close ( ctx );
    tlog_flush();
    return status;
}
/*****************************************************************************/
/* Native methods for wwwjexec class:
 *    exec_write		write string to client of link.
 *
 */
static jstring exec_read_string ( JNIEnv *env, jclass this )
{ 
    ts_client_ctx link;
    char buffer[4100];
    int seglen, i, length, status;
    jstring result;
    /*
     * Retrieve context for decnet link access.
     */
    link = (ts_client_ctx) (*env)->GetIntField(env, this, field_id.cnx);
    /*
     * Read from link.
     */
    status = ts_decnet_read ( link, buffer, sizeof(buffer)-4, &length );
    if ( (status&1) == 1 ) {
	/*
	 * Convert string.
         */
	buffer[length] = '\0';
	result = (*env)->NewStringUTF(env,(const char *) buffer);
	if ( result != 0 ) return result;
	/*
	 * Allocation failure on string.
         */
	tlog_putlog ( 0, "Failed to create string!/" );
    } else {
	/*
	 * Throw an exception.
         */
	jclass exc_class;
	if ( java_log_level > 1 ) tlog_putlog ( 0, "Read failure on link!/" );
	
	exc_class = (*env)->FindClass ( env, "java/io/IOException" );
	if ( exc_class != 0 ) {
	    (*env)->ThrowNew(env, exc_class, "DECnet read failure");
	} else tlog_putlog ( 0, "Could not throw exception" );
    }
    return (jstring) 0;		/* null result */
}

static jint exec_read_raw ( JNIEnv *env, jclass this, jbyteArray string,
	jint count )
{ 
    ts_client_ctx link;
    jbyte *buffer;
    int seglen, i, length, status;
    jstring result;
    /*
     * Retrieve context for decnet link access.
     */
    link = (ts_client_ctx) (*env)->GetIntField(env, this, field_id.cnx);
    /*
     * Get buffer address and read.  We don't stay in this routine long
     * enough to worry about releasing it.
     */
    length = (*env)->GetArrayLength(env,string);
    if ( length < count ) count = length;

    buffer = (*env)->GetByteArrayElements(env, string, 0 );
    status = ts_decnet_read ( link, (char *) buffer, count, &length );
    if ( (status&1) == 0 ) {
	/*
	 * Throw an exception.
         */
	jclass exc_class;
	if ( java_log_level > 1 ) tlog_putlog ( 0, "Read failure on link!/" );
	
	exc_class = (*env)->FindClass ( env, "java/io/IOException" );
	if ( exc_class != 0 ) {
	    (*env)->ThrowNew(env, exc_class, "DECnet read failure");
	} else tlog_putlog ( 0, "Could not throw exception" );
	length = -1;
    }
    return length;
}

static jint exec_write_string ( JNIEnv *env, jclass this, jstring string )
{
    ts_client_ctx link;
    char *buffer;
    int seglen, i, length, status;
    /*
     * Retrieve context for decnet link access.
     */
    link = (ts_client_ctx) (*env)->GetIntField(env, this, field_id.cnx);
    /*
     * Convert string to 7-bit ASCII and write.
     */
    buffer = (char *) (*env)->GetStringUTFChars(env, string, 0 );
    length = tu_strlen ( buffer );
    if ( length == 0 ) {	/* zero length writes allowed */
        status = ts_decnet_write ( link, "", 0 );
    } else for ( i = 0; i < length; i += seglen ) {
	/*
	 * Write to server in 4K chunks.
	 */
	seglen = (length >4096) ? 4096 : length;
	status = ts_decnet_write ( link, &buffer[i], seglen );
    }
    (*env)->ReleaseStringUTFChars ( env, string, buffer );
    return status;
}

static jint exec_write_raw ( JNIEnv *env, jclass this, jbyteArray string,
	jint count )
{
    ts_client_ctx link;
    jbyte *buffer;
    jsize length;
    int seglen, i, status;
    /*
     * Retrieve context for decnet link access.
     */
    link = (ts_client_ctx) (*env)->GetIntField(env, this, field_id.cnx);
    /*
     * Make length to write the minimum of array size or count argument.
     */
    length = (*env)->GetArrayLength(env,string);
    if ( length > count ) length = count;

    buffer = (*env)->GetByteArrayElements(env, string, 0 );
    if ( length == 0 ) {	/* zero length writes allowed */
        status = ts_decnet_write ( link, "", 0 );
    } else for ( i = 0; i < length; i += seglen ) {
	/*
	 * Write to server in 4K chunks.
	 */
	seglen = (length >4096) ? 4096 : length;
	status = ts_decnet_write ( link, (char *) &buffer[i], seglen );
	if ( java_log_level > 4 )
		tlog_putlog(0,"Status of decnet write: !SL!/", status );
    }
    (*env)->ReleaseByteArrayElements ( env, string, buffer, 0 );
    return status;
}

static jint exec_close ( JNIEnv *env, jclass this )
{
    ts_client_ctx link;
    link = (ts_client_ctx) (*env)->GetIntField(env, this, field_id.cnx);
    return (jint) ts_decnet_close ( link );
}

static jint exec_put_log ( JNIEnv *env, jclass this, jint lvl, jstring string )
{
    ts_client_ctx link;
    char *buffer;
    int seglen, i, length, status;
    /*
     * Ignore request if level too high.
     */
    if ( lvl > java_log_level ) return 1;
    /*
     * Retrieve context for decnet link access.
     */
    link = (ts_client_ctx) (*env)->GetIntField(env, this, field_id.cnx);
    /*
     * Convert string to 7-bit ASCII and write with !AF to replace
     * non-printables with periods.
     */
    buffer = (char *) (*env)->GetStringUTFChars(env, string, 0 );
    length = tu_strlen ( buffer );
    status = tlog_putlog ( 0, "!AF!/", length, buffer );
    (*env)->ReleaseStringUTFChars ( env, string, buffer );
    return status;
}
