/*
  Description: Java Language access routines.
               These routines provide for creation and access to
               the Java virtual machine.  They also provide JNI
               interface routines for Java programs to access
               Midas Fortran/C routines

               Based on java.c from Sun Java SDK

  The JSM (Java Session Manager) is used to manage a NeXtMidas session running
  in a shared-memory Java Virtual Machine (JVM). Running NeXtMidas in
  shared-memory JVM permits the use of pipes and shared resources within X-Midas
  (a.k.a. NeXtOpt).

  Author: WHK 2002-03 initial version of XMJVM
  Modifications:
    WHK 2004-05-xx - ??
    WHK/JPH/JXC/PRP
        2005-xx-xx - BUG 114: cleanup SYSV IPC and stop JVM relatively cleanly
                       and integration into NeXtMidas 2.1.0
    JXC 2006-02-xx - BUG 324: fix signal 11 if trying to access X-Midas control from NeXtMidas
                     BUG 338: implemented messaging support (nxm220)
    JXC 2006-11-22 - BUG 734: remove extra call to m_return() (which causes problems)
    JXC 2006-12-18 - BUG 809: support X-Midas 4.4.4 (nxm240)
    JXC 2007-02-28 - BUG 900: xmbopt does not pick up messages from path
    NTN 2007-08-30 - BUG 1095: fix compile errors using old gcc 2.96 version
    NTN 2007-09-26 - BUG 1101: change permission of crated IPC message queue to 744 from 700 to
                       allow users in group and others to "see" the message queues using the "ipcs"
                       program to help with diagnosis when hit number of message queue limit on OS
                       (default limit is 16)
    NTN 2008-10-22 - BUG 1274: add support for 64-bit machines (merge in Bill Reese's patch)
    NTN 2009-02-25 - BUG 1790: changed "ERROR: JNI.C: Unable to map %s section..." to an INFO message
    NTN 2009-07-09 - BUG 1550: allow using result name for /JVMOPTIONS so that user's
                       on X-Midas prior to 4.6.0 can set Mixed Cased JVM option trees (i.e. -Xmx<max_heap_size>
    NTN 2010-11-24 - BUG 1237: merge in a modified version of Peter Perrino (PRP) suggested enhancement patch
                       to help with the message queue cleanup (enabled via /CLEANUPATEXIT switch)
    KME-2019-02-12   adding variable declaration for MIDAS_COMMON_NAME[],MIDAS_UNIQUE_NAME[],MIDAS_TABLES_NAME[]
                     in order to support the GFC compilers. Since NeXtMidas 3.9.1
 */

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <pthread.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <dirent.h> /* opendir(), etc. */

/* NeXtMidas Includes */
#include <jni.h>
#include <cdefs.h>

/* X-Midas Includes */
#include <midas.h>
#include <mjackets.h>
#include <tables.h>


/* Include XM compatibility mappings, used so we can support various
   (incompatible) versions of X-Midas.
 */
#include "xmcompat.h"


/* Definitions to support Fortran calls to these functions on UNIX platforms. */
#define jni$run             jni$run_
#define jni$jsmrun          jni$jsmrun_
#define jni$addoption       jni$addoption_
#define jni$addarg          jni$addarg_
#define jni$debug           jni$debug_
#define jni$startjvm        jni$startjvm_
#define jni$stopjvm         jni$stopjvm_
#define jni$checkforjsm     jni$checkforjsm_
#define jni$startjsm        jni$startjsm_
#define jni$cleanupjsm      jni$cleanupjsm_
#define jni$processcommands jni$processcommands_
#define jni$stopjsm         jni$stopjsm_
#define jni$status          jni$status_
#define jni$startnm         jni$startnm_
#define jni$setclasspath    jni$setclasspath_
#define jni$getswitches     jni$getswitches_

/* Definition of message types */
#define JSM_NONE        0  /* NO MESSAGE */
#define JSM_SHUTDOWN    1
#define JSM_RUN         2
#define JSM_SYNC        3  /* CALL M$SYNC() */
#define JSM_ACK         4
#define JSM_RETURN      5  /* CALL M$RETURN() */

/* Set FORCE_DEBUG to 1 to force all debug messages to print. */
#define FORCE_DEBUG 0

/* Sets the map/unmap types used with M$MAP_SECTION (1=HardMap, 2=SoftMap) */
#define MAP_TYPE   2
#define UNMAP_TYPE (-MAP_TYPE)

#define SHORT_STR_LEN  255 /* The standard length of a short string. */
#define LONG_STR_LEN  2000 /* The standard length of a long  string. */
#define MAX_FILENAME_LEN 256
#define MAX_COMMAND_LEN  1024

#define IPCDIR  "/tmp/.nmjsm"
#define JSMQUEUE "jsm_sysv_queue"
#define JSMPRIVATEQUEUE "jsm_private_queue"

/* Static Structures */
typedef struct {
  char command[MAX_COMMAND_LEN];
  int  responseq;
} jsmstruct;

typedef struct {
  long mtype;
  jsmstruct jsm;
} jsmmsg;

/*******************************************************************************
  Convenience macro definitions
*******************************************************************************/
#define NULL_CHECK0(e) if ((e) == 0) return 0
#define NULL_CHECK(e)  if ((e) == 0) return

/* Checks a test condition (usually "foo==NULL") and if the condition is true,
   an error message is output and the method exits returning retVal. This
   assumes the check is in reference to a JVM error and the JNI env is available.
 */
#define RETURN_IF_JVM_ERROR(test, msg, retVal) \
  if((test)) {                                 \
    fprintf(stderr, msg);                      \
    (*env)->ExceptionDescribe(env);            \
                                               \
    return (retVal);                           \
  }

#define DEBUG(msg)  if((debug==1) || FORCE_DEBUG) { fprintf(stderr, msg); }

/* Clears a string. */
#define CLEAR_STR(str)  ((str)[0] = '\0')

/* Appends onto a LONG string. */
#define APPENDL(dest, src) append((dest), (src), LONG_STR_LEN)

/* Appends onto a SHORT string. */
#define APPENDS(dest, src) append((dest), (src), SHORT_STR_LEN)

/*******************************************************************************
  static variables
*******************************************************************************/
static int_u4 shutdown = 0;
static int msgq = 0;

int jsm_active = 0;
pthread_key_t thread_data_key;  /* Thread-specific data key */

/* List of VM options to be specified when the VM is created. */
static JavaVMOption *options;
static int numOptions=0, maxOptions;
static int_4 debug = 0;

static char** args;
static int    numargs, maxargs;

static JavaVM *jvm           = NULL; /* denotes a Java VM */
static jobject xmshellparent = NULL; /* Created when running JSM/ON */

static int_4  jsmcleaned = 0; /* flag to see if JSM is cleanup already (0 = no, 1 = yes) */

/* XM_PPID is appended to the below path in jni$checkforjsm  */
static char FULLIPCDIR[MAX_FILENAME_LEN] = IPCDIR;

char MIDAS_COMMON_NAME[];
char MIDAS_UNIQUE_NAME[];
char MIDAS_TABLES_NAME[];

/*******************************************************************************
  Method declarations
*******************************************************************************/
void cleanup_atexit(void); /* cleanup at exit for BUG 1237 */

/*******************************************************************************
 Method implementations
*******************************************************************************/

/* Automatically called by the VM when the library is loaded */
jint JNI_OnLoad (JavaVM * vm, void *reserved) {
  mc = (MCSTRUCT**)MIDAS_COMMON_NAME; /* Store the common block address */
#ifdef XM371
  mu = (MUSTRUCT*)MIDAS_UNIQUE_NAME;
  mt = (MTSTRUCT**)MIDAS_TABLES_NAME;
#else
  mu = (MUNIQUE*)MIDAS_UNIQUE_NAME;
  mt = (MTABLES**)MIDAS_TABLES_NAME;
#endif

  /* Tell Java that this library supports the 1.4 interface */
  return (JNI_VERSION_1_4);
}

/* Automatically called by the VM when the library is unloaded */
void JNI_OnUnload (JavaVM * vm, void *reserved) {
  /* This does not appear to ever be called */
  printf ("OnUnload was called\n");
  return;
}

/* Returns a pointer to a block of at least 'size' bytes of memory. Prints error message and exits
   if the memory could not be allocated.
 */
void *MemAlloc(size_t size) {
  void *p = malloc(size);
  if (p == 0) {
    perror("ERROR: MemAlloc: Out of Memory");
    exit(1);
  }
  return p;
}

#define MAP_SECTION(sect,type) map_section(#sect,sect,type)
static void map_section(char* name, int_4 sect, int_4 type) {
  int   dbg  = ((debug==1) || FORCE_DEBUG);
  char *todo = (type < 0)? "Unmap": "Map";

  if (dbg) {
    fprintf(stderr, "JNI.C: %s Section: %s (type=%d)\n", todo, name, type);
    /* %z - A following integer conversion corresponds to a size_t or ssize_t argument */
    fprintf(stderr, "JNI.C: %s Section: Mu = 0x%zx\n", todo, Mu);
  }
  m_map_section(sect, type);
  if (type > 0) {
    if (dbg) {
      fprintf(stderr, "JNI.C: %s Section: Mu->sec_ptr[%s-1] = %d = 0x%x\n",todo,name,
              GET_POINTER(Mu->sec_ptr[sect-1]),GET_POINTER(Mu->sec_ptr[sect-1]));
    }
    if (IS_NULL(Mu->sec_ptr[sect-1])) {
      /*************************************************************************
      // Bill Reese: Shouldn't really worry about this.  It turns out all primitives fail
      // to map to the sections because of the way they start up.  m$unique_ turns
      // out to be defined in <primitive>.so, and m$checkin_process_() references
      // that one instead of the one it should find in libxmidas.so.  This makes
      // routines that reference the libxmidas.so version (like m$map_section)
      // incompatible with ones that reference the copy in <primitive>.so.
      // If we really need access to those common blocks we should include a
      // routine in the primitive that looks up the sec_ptr array in m$unique
      // so it will reference the copy of m$unique in <primitive>.so.
      // fprintf(stderr, "ERROR: JNI.C: Unable to map %s section.\n",name);
      *************************************************************************/
    }
  }
}

/**************************************************************************************************/
/* START -- Methods based on java.c                                                               */
/**************************************************************************************************/

/*******************************************************************************
 Adds a new application argument.  Dynamically creates the memory for each argument.
 Heavily based on AddOption from java.c
*******************************************************************************/
static void AddArg(char *str) {
  char **tmp;
  if (debug == 1) {
    fprintf(stderr,"JNI.C: AddArg(%s)\n", str);
  }
  /* Expand args array if needed to accommodate at least one more application option. */
  if (numargs >= maxargs) {
    if (args == 0) {
      maxargs = 4;
      args = MemAlloc(maxargs * sizeof(char *));
    } else {
      maxargs *= 2;
      tmp = MemAlloc(maxargs * sizeof(char *));
      memcpy(tmp, args, numargs * sizeof(char *));
      free(args);
      args = tmp;
    }
  }
  args[numargs++] = str;
}

/*******************************************************************************
 Adds a new VM option with the given given name and value.
*******************************************************************************/
static void AddOption(char *str, void *info) {
  if (debug == 1) {
    fprintf(stderr,"JNI.C: AddOption('%s', info)\n", str);
  }

  /* Expand options array if needed to accommodate at least one more VM option. */
  if (numOptions >= maxOptions) {
    if (options == 0) {
      maxOptions = 4;
      options = MemAlloc(maxOptions * sizeof(JavaVMOption));
    } else {
      JavaVMOption *tmp;
      maxOptions *= 2;
      tmp = MemAlloc(maxOptions * sizeof(JavaVMOption));
      memcpy(tmp, options, numOptions * sizeof(JavaVMOption));
      free(options);
      options = tmp;
    }
  }
  options[numOptions].optionString = str;
  options[numOptions++].extraInfo = info;
}

/*******************************************************************************
 Returns a new Java string object for the specified platform string.
*******************************************************************************/
static jstring NewPlatformString(JNIEnv *env, char *s) {
  int len = (int)strlen(s);
  jclass cls;
  jmethodID mid;
  jbyteArray ary;

  NULL_CHECK0(cls = (*env)->FindClass(env, "java/lang/String"));
  NULL_CHECK0(mid = (*env)->GetMethodID(env, cls, "<init>", "([B)V"));
  ary = (*env)->NewByteArray(env, len);
  if (ary != 0) {
    jstring str = 0;
    (*env)->SetByteArrayRegion(env, ary, 0, len, (jbyte *)s);
    if (!(*env)->ExceptionOccurred(env)) {
      str = (*env)->NewObject(env, cls, mid, ary);
    }
    (*env)->DeleteLocalRef(env, ary);
    return str;
  }
  return 0;
}

/*******************************************************************************
 Returns a new array of Java string objects for the specified array of platform strings.
*******************************************************************************/
static jobjectArray NewPlatformStringArray(JNIEnv *env, char **strv, int strc) {
  jarray cls;
  jarray ary;
  int i;

  NULL_CHECK0(cls = (*env)->FindClass(env, "java/lang/String"));
  NULL_CHECK0(ary = (*env)->NewObjectArray(env, strc, cls, 0));
  for (i = 0; i < strc; i++) {
    jstring str = NewPlatformString(env, *strv++);
    NULL_CHECK0(str);
    (*env)->SetObjectArrayElement(env, ary, i, str);
    (*env)->DeleteLocalRef(env, str);
  }
  return ary;
}

/**************************************************************************************************/
/* END -- Methods based on java.c                                                                 */
/**************************************************************************************************/

/*******************************************************************************
 Adds a JNI option.
   Unlike using AddOption(str,NULL) this will make a duplicate of the string to
   add. This makes this method safe to use with strings that are allocated on
   the stack.

 @param str The JNI option to pass to the JVM on startup.
*******************************************************************************/
static void AddJniOption(char *str) {
  char *copy = strdup(str);

  if (copy == NULL) {
    fprintf(stderr, "ERROR: JNIF.C: AddJniOption: Can not add option '%s', out of memory.\n", str);
  } else {
    AddOption(copy, NULL);
  }
}

/*******************************************************************************
 Appends a string to another string. This is the same as strcat, except that
   is checks the length of the string first.

 @param dest     The destination string.
 @param src      The text to append to dest.
 @param len_dest The length of the dest buffer.
*******************************************************************************/
static void append(char* dest, char* src, int len_dest) {
  int avail = len_dest - strlen(dest) - 1;

  if(len_dest > strlen(src)) {
    strncat(dest, src, len_dest);
  } else {
    fprintf(stderr, "ERROR: JNI.C: Can not append '%s' to '%s', length of new "
                    "string exceeds size of output buffer.\n", src, dest);
  }
}

/*******************************************************************************
 Appends an element to the classpath.

 @param classpath The classpath.
 @param pathname  The element to add.
*******************************************************************************/
static void add_to_classpath(char* classpath, char* pathname) {
  if(strlen(classpath) == 0) {
    APPENDL(classpath, "-Djava.class.path=");
    APPENDL(classpath, pathname);
  } else {
    APPENDL(classpath, ":");
    APPENDL(classpath, pathname);
  }
}

/*******************************************************************************
 Appends an element to the librarypath.

 @param classpath The classpath.
 @param pathname  The element to add.
*******************************************************************************/
static void add_to_libpath(char* classpath, char* pathname) {
  if(strlen(classpath) == 0) {
    APPENDL(classpath, "-Djava.library.path=");
    APPENDL(classpath, pathname);
  } else {
    APPENDL(classpath, ":");
    APPENDL(classpath, pathname);
  }
}


/*******************************************************************************
 Creates a System V IPC Key from a filename (which is appended to the FULLIPCDIR
 path) so that it can be used as the key to the shared message queue to
 communicate to the JSM.
*******************************************************************************/
static key_t create_local_key(const char* name) {
  char  full_name[MAX_FILENAME_LEN];
  int fd = 0;
  key_t key;

  DEBUG("JNI.C: START: create_local_key\n");

  mkdir(FULLIPCDIR, 0777);
  strncpy(full_name, FULLIPCDIR, MAX_FILENAME_LEN);
  strncat(full_name ,"/", MAX_FILENAME_LEN);
  strncat(full_name, name, MAX_FILENAME_LEN);
  /*printf("create_local_key: Full_name: %s\n",full_name);*/
  fd = open(full_name, O_WRONLY|O_CREAT, 0777);
  if (fd < 0) {
    printf("create_local_key: couldn't create file to pass to ftok(): %s\n", full_name);
  }
  close(fd);

  key = ftok(full_name,'X');
  if (key == -1) {
    perror("WARNING: create_local_key: Problem generating key");
    fprintf(stderr, "\n");
  }

  DEBUG("JNI.C: END: create_local_key\n");

  return(key);
} /* end create_local_key() */

/*******************************************************************************
 create a "private" local SysV message queue (for IPC)
 @return id of new local message queue on success, -1 on failure
*******************************************************************************/
static int create_local_queue() {
  int newq = 0;
  char full_name[MAX_FILENAME_LEN];
  int file = 0;

  DEBUG("JNI.C: START: create_local_queue\n");

  /* S_IRWXU = 00700 user has read, write and execute permission */
  /* S_IRGRP = 00040 group has read permission */
  /* S_IROTH = 00004 others have read permission */
  newq = msgget(IPC_PRIVATE, IPC_CREAT|S_IRWXU|S_IRGRP|S_IROTH);
  if (newq == -1) {
    perror("WARNING: (1) create_local_queue() creating private SysV message queue");
    fprintf(stderr, "\n");
  } else {
    if ((debug==1) || FORCE_DEBUG) {
      fprintf(stderr, "JNI.C: create_local_queue: created private message queue: %d\n",newq);
    }

    mkdir(FULLIPCDIR, 0777);
    snprintf(full_name, MAX_FILENAME_LEN, "%s/%s_%d", FULLIPCDIR, JSMPRIVATEQUEUE, newq);
    /* O_CREAT  = create even if does not exist flag */
    /* O_WRONLY = open file write-only flag */
    file = open(full_name, O_CREAT|O_WRONLY, 0777);
    if (file == -1) {
      perror("WARNING: (2) create_local_queue: saving private message queue id to file");
      fprintf(stderr, "\n");
    } else {
      write(file, &newq, sizeof(newq));
      close(file);
      if ((debug==1) || FORCE_DEBUG) { fprintf(stderr, "JNI.C: create_local_queue: created: %s\n",full_name); }
    }

  }

  DEBUG("JNI.C: END: create_local_queue\n");
  return newq;
} /* end create_local_queue() */

/*******************************************************************************
 Remove the specified SysV message queue id
 @param queue id of message queue to remove
 @return always returns 1
*******************************************************************************/
static int remove_local_queue(int queue) {
  char full_name[MAX_FILENAME_LEN];
  int file = 0;

  DEBUG("JNI.C: START: remove_local_queue\n");

  if ((debug==1) || FORCE_DEBUG) { fprintf(stderr, "JNI.C: remove_local_queue: removing queue: %d\n",queue); }
  if (msgctl(queue,IPC_RMID,NULL)==-1) {
        /* Both the client and the JSM try to clean up all queues
           Don't print out the fact it doesn't exist here.
        */
/*
      fprintf(stderr,"tried to remove local queue %d\n",queue);
      perror("WARNING: remove_local_queue: Couldn't remove JSMQUEUE: ");
      fprintf(stderr, "\n");
*/
  }

  snprintf(full_name, MAX_FILENAME_LEN, "%s/%s_%d", FULLIPCDIR, JSMPRIVATEQUEUE, queue);
  if ((debug==1) || FORCE_DEBUG) { fprintf(stderr, "JNI.C: remove_local_queue:   Deleting: %s\n",full_name); }
  unlink(full_name);

  DEBUG("JNI.C: END: remove_local_queue\n");
  return 1;
} /* end remove_local_queue() */

/*******************************************************************************
 ?? runjavaprogram??
 @param command java program to run?
 @return ???
*******************************************************************************/
static int_u4 runjavaprogram(char* command) {
  JavaVMInitArgs vm_args;   /* VM initialization arguments */
  jclass mainClass;
  jmethodID mainID;
  jobjectArray mainArgs;
  jsize nVMs = 0;
  int forkstat;
  int res;
  int i;
  void *xmjvm;
  JNIEnv *env = NULL;       /* pointer to native method interface */

  DEBUG("JNI.C: START: runjavaprogram\n");

  for (i=0; command[i] != '\0'; i++) {
    if (command[i] == '.') command[i] = '/';
  }

  if (debug == 1) {
    fprintf(stderr,"JNI.C: runjavaprogram: Finding the Class: '%s'\n",command);
  }
  /* Get the ENV pointer */
  (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);

  /* invoke the Main.test method using the JNI */
  mainClass = (*env)->FindClass(env,command);
  if (mainClass == NULL) {
    (*env)->ExceptionDescribe(env);
  } else {
    DEBUG("JNI.C: runjavaprogram: Finding main\n");

/*
    Java_MC_test(env,mainClass);
    Java_MC_command_1string(env,mainClass);
*/
    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");
    if (mainID == NULL) {
      DEBUG("JNI.C: runjavaprogram: mainID == NULL\n");

      if ((*env)->ExceptionOccurred(env)) {
        DEBUG("JNI.C: runjavaprogram: Exception\n");

        (*env)->ExceptionDescribe(env);
      } else {
        fprintf(stderr, "No main method found in specified class.\n");
      }
    } else {

      if (debug==1) {
       fprintf(stderr,"JNI.C: runjavaprogram: Adding %d Args to Main\n",numargs,args);
       for (i=0;i<numargs;i++) {
         fprintf(stderr,"...Arg[%d]: '%s'\n",i,args[i]);
       }
      }

      /* Converts C-strings to a Java String array. Mainargs is then
       * a pointer
       */
      mainArgs = NewPlatformStringArray(env,args,numargs);

      DEBUG("JNI.C: runjavaprogram: Calling main\n");

      /* Call the main class.  Will block until program finishes */
      (*env)->CallStaticVoidMethod(env,mainClass, mainID,mainArgs);

      DEBUG("JNI.C: runjavaprogram: Main is done\n");

      if ((*env)->ExceptionOccurred(env)) {
        (*env)->ExceptionDescribe(env);
      }
    }
  }

  /* JVM is done ... */
  if ((*jvm)->DetachCurrentThread(jvm) != 0) {
    fprintf(stderr, "Could not detach main thread.\n");
  }

  DEBUG("JNI.C: END: runjavaprogram\n");
  return 0;
}

/*******************************************************************************
 Exit hook (callback) to be called by JVM when exiting.
 Frees allocated memory, etc.
*******************************************************************************/
void exit_hook(jint code) {
  int res;
  int i;
  JNIEnv *env = NULL;       /* pointer to native method interface */

  DEBUG("JNI.C: START: exit_hook (called by JVM on exit)\n");
  if((debug==1) || FORCE_DEBUG) { fprintf(stderr, "JNI.C: exit_hook: Exit hook called with %d\n",code); }

  /* KEEP: If Sun ever properly implements this then it needs to be revisited */
#if 0
  DEBUG("JNI.C: exit_hook:   Trying to Destroy the JavaVM\n");
  /* ... We are done. */
  /* Get the ENV pointer */
  (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
  res = (*jvm)->DestroyJavaVM(jvm);
  if (res < 0) {
    fprintf(stderr,"DestroyJavaVM still doesn't actually work. res = %d\n",res);
  }
#endif

  DEBUG("JNI.C: exit_hook:   Freeing Memory\n");
  /* Free Memory Allocated for Option Strings */
  for (i=0;i<numOptions;i++) {
    free(options[i].optionString);
  }
  free(options);
  for (i=0;i<numargs;i++) {
    free(args[i]);
  }
  free(args);

  /* Not sure on calling exit here but otherwise we get JVM errors on exit */
  DEBUG("JNI.C: exit_hook: calling m_return()\n");
  m_return();
  DEBUG("JNI.C: END: exit_hook\n");
  exit(code);
} /* end exit_hook() */

/*******************************************************************************
 Reports the status of the JSM.
    @return 0 - No shell running.
            1 - Shell is running.
           -1 - Error.
*******************************************************************************/
int_u4 jni$status() {
  int status = -1;

  DEBUG("JNI.C: START: jni$status\n");

  if((jvm == NULL) || (xmshellparent == NULL)) {
    status = 0;
  } else {
    status = 1;
  }

  DEBUG("JNI.C: END: jni$status\n");
  return status;
}

/*******************************************************************************
 Checks for a live JSM returns 1 for alive or
     -1 for no SYSV IPC MSGQ
     -2 for no response for JSM process
     -3 for problem with Java Virtual machine on the other side
*******************************************************************************/
int_u4 jni$checkforjsm() {
  int queue;
  jsmmsg message;
  void *msgp;
  jsmmsg msg;
  char *xm_ppid;
  int  flags = 0;
  int  counter;
  int  wait=50;

  msgp = &msg;
  DEBUG("JNI.C: START: jni$checkforjsm\n");

  strncpy(FULLIPCDIR, IPCDIR, MAX_FILENAME_LEN);
  xm_ppid = getenv("XM_PPID");
  strncat(FULLIPCDIR, xm_ppid, MAX_FILENAME_LEN);

  /* Check for the SYSV message queue */
  queue = msgget(create_local_key(JSMQUEUE),0);
  if (queue == -1) { /* Couldn't get message queue */
    DEBUG("JNI.C: jni$checkforjsm: Couldn't find the SysV Message queue to the JSM\n");
    return -1;
  } else {
    if((debug==1) || FORCE_DEBUG) {
      fprintf(stderr, "JNI.C: jni$checkforjsm: JSM SysV queue is %d\n",queue);
    }
  }

  message.mtype = JSM_ACK;
  message.jsm.responseq = create_local_queue();
  msgsnd(queue,&message,sizeof(jsmstruct),0);

  /* Make sure command is acknowledged
     Need to loop in case he is not there - how long
  */
  flags = IPC_NOWAIT;
  counter = 0;
  while (msgrcv(message.jsm.responseq,msgp,sizeof(jsmstruct),0,flags) == -1 &&
         counter < wait) {
    m_pause(0.1);
    counter++;
  }
  if (counter == -1 || msg.mtype != JSM_ACK) {
    DEBUG("JNI.C: JSM didn't respond to command\n");
    return -2;
  }
  remove_local_queue(message.jsm.responseq);

  DEBUG("JNI.C: END: jni$checkforjsm\n");
  return 1;
} /* end jni$checkforjsm() */


/*******************************************************************************
 Start the Java Session Manager (JSM) which is used to manage a NeXtMidas session
 running in a shared-memory Java Virtual Machine (JVM).
*******************************************************************************/
int_u4 jni$startjsm() {
  jsmstruct *environment;
  int_u4 id = 0;
  int status = 0;
  int_4 cleanupatexit;

  DEBUG("JNI.C: START: jni$startjsm\n");

  shutdown = 0;
  jsmcleaned = 0; /* reset flag that we cleaned up the JSM */
  cleanupatexit = m_get_switch("CLEANUPATEXIT");

  /* Create SysV shared message queue for JSM */
  /* S_IRWXU = 00700 user has read, write and execute permission */
  /* S_IRGRP  = 00040 group has read permission */
  /* S_IROTH = 00004 others have read permission */
  msgq = msgget(create_local_key(JSMQUEUE),IPC_CREAT|S_IRWXU|S_IRGRP|S_IROTH);
  if (msgq == -1) {
    perror("WARNING: jni$startjsm: unable to created shared SysV message queue for JSM");
    fprintf(stderr, "\n");
  }
  if (debug == 1) {
    fprintf(stderr,"JNI.C: jni$startjsm: msgq = %d\n",msgq);
  }

  jsm_active = 1; /* We are running in the jsm environment */

  if (cleanupatexit == 1) {
    status = atexit(cleanup_atexit); /* call cleanup_atexit() to be run at (most) hard exits for BUG 1237 */
    if (status != 0) {
      fprintf(stderr,"WARN: JNI.C: jni$startjsm: cannot set exit function to call cleanup_atexit()\n");
    }
  }

  DEBUG("JNI.C: END: jni$startjsm\n");
  return 0;
} /* end jni$startjsm() */

/*******************************************************************************
*******************************************************************************/
int_u4 jni$return(int responseq) {
  void *msgp;
  jsmmsg message;
  jsmmsg msg;
  int  flags = 0;
  int status;

  msgp = &msg;
  DEBUG("JNI.C: START: jni$return\n");

  if ((responseq == msgq) || (responseq == 0)) {
    DEBUG("JNI.C: jni$return: calling m_return() in shared VM\n");
    m_return();
    DEBUG("JNI.C: jni$return: returning from m_return()\n");
  } else {
    message.mtype = JSM_RETURN;
    message.jsm.responseq = create_local_queue();
    msgsnd(responseq,&message,sizeof(jsmstruct),0);

    /* Make sure command is acknowledged */
    status = msgrcv(message.jsm.responseq,msgp,sizeof(jsmstruct),0,flags);
    if (status < 0 ) {
      perror("WARNING: JNI.C: jni$return: ");
      fprintf(stderr,"\n");

    } else if (msg.mtype != JSM_ACK) {
      fprintf(stderr,"WARNING: JNI.C: jni$return: Got back a strange answer via IPC "
                     "(msg.mtype=%d)\n",msg.mtype);
    }
    remove_local_queue(message.jsm.responseq);
  }
  DEBUG("JNI.C: END: jni$return\n");
  return 0;
} /* end jni$return() */

/*******************************************************************************
*******************************************************************************/
int_u4 jni$sync(int responseq) {
  void *msgp;
  jsmmsg message;
  jsmmsg msg;
  int  flags = 0;
  int status;

  msgp = &msg;
  DEBUG("JNI.C: START: jni$sync\n");

  if ((responseq == msgq) || (responseq == 0)) {
    DEBUG("JNI.C: jni$sync: calling m_sync() in shared VM\n");
    m_sync();
    DEBUG("JNI.C: jni$sync: returning from m_sync()\n");
  } else {
    message.mtype = JSM_SYNC;
    message.jsm.responseq = create_local_queue();
    msgsnd(responseq,&message,sizeof(jsmstruct),0);

    /* Make sure command is acknowledged */
    status = msgrcv(message.jsm.responseq,msgp,sizeof(jsmstruct),0,flags);
    if (status < 0 ) {
      perror("WARNING: JNI.C: jni$sync: ");
      fprintf(stderr,"\n");
    } else if (msg.mtype != JSM_ACK) {
      fprintf(stderr,"WARNING: JNI.C: Got back a strange answer via IPC\n");
    }

    if(Mt->npipe >= 0) {
      if(Mc->mode == 3) {
        DEBUG("JNI.C: jni$sync:   Mapping to pipe buffers\n");
        MAP_SECTION(LS_pipe_buffers, MAP_TYPE);
        DEBUG("JNI.C: jni$sync:   Done mapping to pipe buffers\n");
      } else {
        fprintf(stderr, "WARNING: JNI.C: jni$sync:   Can not map pipe sections at"
                        "this time, got Mc->mode = %d\n", Mc->mode);
      }
    }
    remove_local_queue(message.jsm.responseq);
  }
  DEBUG("JNI.C: END: jni$sync\n");
  return 0;
} /* end jni$sync() */

/*******************************************************************************
*******************************************************************************/
int_u4 jni$processcommands() {
  jsmstruct *environment = NULL;
  int status;
  pthread_t thread_id;

  int error_count = 0;

  /* For SYSV Messages */
  void *msgp;
  size_t mtextsize;
  long msgtype;
  int  flags = 0;
  jsmmsg msg;
  jsmmsg ack_message;

  JNIEnv    *env = NULL;   /* pointer to native method interface */
  jclass     cls;          /* pointer to XmShellParent class */
  jmethodID  mid;          /* pointer to XmShellParent.runCommand(String,int) */

  DEBUG("JNI.C: START: jni$processcommands\n");

  /* Get the ENV pointer */
  (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);

  /* Create a NeXtMidas Shell to receive commands */
  if(xmshellparent == NULL) {
    /* Should never get to this. */
    fprintf(stderr,"ERROR: Unable to access XmShellParent\n");
    return -1;
  }

  cls = (*env)->GetObjectClass(env,xmshellparent);
  RETURN_IF_JVM_ERROR(cls == NULL, "ERROR: Unable to access XmShellParent\n",-1);

  mid = (*env)->GetMethodID(env,cls,"runCommand","(Ljava/lang/String;I)V");
  RETURN_IF_JVM_ERROR(mid == NULL, "ERROR: Unable to access XmShellParent.runCommand(..)\n",-1);

  ack_message.mtype = JSM_ACK;

  /* Call M$SYNC to allow other commands to start. */
  DEBUG("JNI.C: jni$processcommands: Calling m_sync() in shared JVM.\n");
  m_sync();
  DEBUG("JNI.C: jni$processcommands: Returning from m_sync() in shared JVM.\n");


  DEBUG("JNI.C: jni$processcommands: Shared JVM waiting for messages from other processes.\n");
  while( Mc->break_ == 0 && shutdown == 0 ) {
    msgp = &msg;
    if (msgrcv(msgq,msgp,sizeof(jsmstruct),0,flags) != -1) {
      /* Respond to sender */
      msgsnd(msg.jsm.responseq,&ack_message,sizeof(jsmstruct),0);

      if (msg.mtype == JSM_SHUTDOWN) {
        DEBUG("JNI.C: jni$processcommands: GOT JSM_SHUTDOWN MESSAGE\n");
        shutdown = 1;

      } else if (msg.mtype == JSM_RUN) {
        DEBUG("JNI.C: jni$processcommands: GOT JSM_RUN MESSAGE\n");

        if (Mt->npipe  >=0) { MAP_SECTION(LS_pipe_headers,  MAP_TYPE); }
        if (Mt->nwidget>=0) { MAP_SECTION(LS_cntrl_widgets, MAP_TYPE); }
        if (TRUE          ) { MAP_SECTION(LS_messages,      MAP_TYPE); }

        DEBUG("JNI.C: jni$processcommands: Calling XmShellParent.runCommand(..)\n");
        (*env)->CallVoidMethod(env,xmshellparent,mid,
                               (*env)->NewStringUTF(env, msg.jsm.command),
                               msg.jsm.responseq);

      } else if (msg.mtype == JSM_ACK) {
        /* Do nothing was already acknowledged above */

      } else {
        fprintf(stderr,"WARNING: JNI.C: jni$processcommands: Got unknown IPC message: %d\n",msg.mtype);
      }
      msg.mtype =0; /* Don't loop around if there are issues */

    } else {
       /* error on msgrcv */
       error_count++;
       perror("WARNING: jni$processcommands (error on msgrcv): ");
       if (error_count > 5) shutdown = 1;
       if (debug) fprintf(stderr, "  jni$processcommands error_count=%d mc.break=%d shutdown=%d\n", error_count, Mc->break_, shutdown);
    }
  } /* end while loop */

  /* JVM is done ... */
  if ((*jvm)->DetachCurrentThread(jvm) != 0) {
    fprintf(stderr, "WARNING: JNI.C: jni$processcommands: Could not detach main thread.\n");
  }

  DEBUG("JNI.C: END: jni$processcommands\n");
  return 0;
} /* end jni$processcommands() */

/*******************************************************************************
 Stop the Java Session Manager (JSM)'s IPC/message queue.
 Does this by sending a "JSM_SHUTDOWN" command message on the message queue.
 Then waits for the command to be acknowledged
*******************************************************************************/
int_u4 jni$stopjsm() {
  int queue;
  jsmmsg message;
  void *msgp;
  jsmmsg msg;
  int  flags = 0;

  msgp = &msg;
  DEBUG("JNI.C: START: jni$stopjsm\n");

  queue = msgget(create_local_key(JSMQUEUE),0);
  if (queue == -1) { /* Couldn't get message queue */
    fprintf(stderr,"JNI.C: jni$stopjsm: Couldn't find the SysV Message queue to the JSM\n");
    return(-1);
  } else {
    if((debug==1) || FORCE_DEBUG) {
      fprintf(stderr, "JNI.C: jni$stopjsm: JSM SysV queue is %d\n",queue);
    }
  }

  message.mtype = JSM_SHUTDOWN;
  message.jsm.responseq = create_local_queue();
  msgsnd(queue,&message,sizeof(jsmstruct),0);

  /* Make sure command is acknowledged */
    msgrcv(message.jsm.responseq,msgp,sizeof(jsmstruct),0,flags);
    if (msg.mtype != JSM_ACK) {
      fprintf(stderr,"JNI.C: jni$stopjsm: JSM didn't respond to SHUTDOWN command\n");
    }
    remove_local_queue(message.jsm.responseq);

  DEBUG("JNI.C: END: jni$stopjsm\n");
  return 0;
} /* end jni$stopjsm() */

/*******************************************************************************
 Clean up the shared JVM (JSM IPC Message queues/files)
*******************************************************************************/
int_u4 jni$cleanupjsm() {
  DIR *directory;
  struct dirent *entry;
  int L_emsg = 255;
  char emsg[L_emsg];
  char fullpath[MAX_FILENAME_LEN];
  int tempmsgq = 0;
  int file=0;
  DEBUG("JNI.C: START: jni$cleanupjsm\n");

  if (jsmcleaned == 1) {
    DEBUG("JNI.C: END: jni$cleanupjsm called already!\n");
    return 0;
  }
  if ((directory = opendir(FULLIPCDIR)) != NULL) {
    if((debug==1) || FORCE_DEBUG) { fprintf(stderr, "JNI.C: jni$cleanupjsm(): Processing: %s\n",FULLIPCDIR); }

    while ((entry = readdir(directory)) != NULL) {
      snprintf(fullpath, MAX_FILENAME_LEN, "%s/%s", FULLIPCDIR, entry->d_name);
      if((debug==1) || FORCE_DEBUG) { fprintf(stderr, "JNI.C: jni$cleanupjsm():   Found: %s\n",fullpath); }

      if (strcmp(entry->d_name,".")==0)  continue; /* ignore current directory */
      if (strcmp(entry->d_name,"..")==0) continue; /* ignore parent directory  */

      /* Remove main IPC if there */
      if (strcmp(entry->d_name,JSMQUEUE)==0) {
        DEBUG("JNI.C: jni$cleanupjsm():   Removing JSMQUEUE IPC queue\n");
        tempmsgq = msgget(create_local_key(JSMQUEUE),0);
        if (tempmsgq == -1) { /* Couldn't get message queue */
          perror("WARNING: jni$cleanupjsm: Couldn't get handle to JSMQUEUE: ");
          fprintf(stderr, "\n");
        } else {
          if (msgctl(tempmsgq,IPC_RMID,NULL)==-1) {
            perror("WARNING: jni$cleanupjsm: Couldn't remove JSMQUEUE: ");
            fprintf(stderr, "\n");
          }
        }
        if((debug==1) || FORCE_DEBUG) { fprintf(stderr, "JNI.C: jni$cleanupjsm():   Deleting: %s\n",fullpath); }
        unlink(fullpath);
      } else if (strncmp(entry->d_name,JSMPRIVATEQUEUE,sizeof(JSMPRIVATEQUEUE)-1)==0) {
        file = open(fullpath,O_RDONLY);
        if (file == -1) {
          perror("WARNING: jni$cleanupjsm: ");
          fprintf(stderr, "         fullpath=%s\n", fullpath);
        } else {
          if (read(file,&tempmsgq,sizeof(tempmsgq)) < 0) {
            perror("WARNING: jni$cleanupjsm: couldn't read private queue file: ");
            fprintf(stderr, "\n");
          } else {
            close(file);
            remove_local_queue(tempmsgq);
          }
        }
      }
    }
    closedir(directory);
  } else {
    snprintf(emsg, L_emsg, "WARNING: jni$cleanupjsm: unable to open the IPCDIR [%s]", FULLIPCDIR);
    perror(emsg);
    /* perror("WARNING: jni$cleanupjsm: unable to open the IPCDIR"); */
    fprintf(stderr, "\n");
  }

  if((debug==1) || FORCE_DEBUG) { fprintf(stderr, "JNI.C: jni$cleanupjsm():  Deleting: %s\n",FULLIPCDIR); }
  if (rmdir(FULLIPCDIR) == -1) {
    perror("WARNING: jni$cleanupjsm: Unable to remove the IPCDIR");
    fprintf(stderr, "\n");
  }

  jsmcleaned = 1; /* flag that we cleaned up the JSM */
  DEBUG("JNI.C: END: jni$cleanupjsm\n");
  return 0;
} /* end jni$cleanupjsm() */

/*******************************************************************************
 Run a NeXtMidas command
*******************************************************************************/
int_u4 jni$jsmrun (char *command, int_4 lcommand) {
  char *ccommand;
  int queue;
  jsmmsg message;

  /* For SYSV Messages */
  int myMessageId;
  size_t mtextsize;
  long msgtype;
  int  flags = 0;
  void *msgp;
  jsmmsg msg;
  jsmmsg ack_message;
  ssize_t retval;   /* keep track of msgrcv return value (added in 3.0.3/3.2.1/3.3.0 for Bug 2679)   */
  int errcnt = 0;   /* keep track of msgrcv errors (added in 3.0.3/3.2.1/3.3.0 for bug 2679)   */

  ack_message.mtype = JSM_ACK;
  msgp = &msg;

  DEBUG("JNI.C: START: jni$jsmrun\n");
/*
if (Mt->npipe   >= 0) m_map_section (LS_pipe_headers, 2);
if (Mt->npipe   >= 0) m_map_section (LS_pipe_buffers, 2);
if (Mt->nwidget >= 0) m_map_section (LS_cntrl_widgets,2);
*/

  ccommand=MemAlloc((lcommand+1)*sizeof(char));
  strf2c(command,lcommand,ccommand,(lcommand+1),F2C_TRIM);

  /* Find the Message Queue */
  queue = msgget(create_local_key(JSMQUEUE),0);

  if (queue == -1) { /* Couldn't get message queue */
    fprintf(stderr,"JNI.C: jni$jsmrun: Couldn't find the SysV Message queue to the JSM\n");
    return(-1);
  } else {
    if((debug==1) || FORCE_DEBUG) {
      fprintf(stderr, "JNI.C: jni$jsmrun: JSM SysV queue is %d\n",queue);
    }
  }

  /* Send the command to the JSM */
  message.mtype = JSM_RUN;
  myMessageId = create_local_queue();
  message.jsm.responseq = myMessageId;
  strncpy(message.jsm.command, ccommand, MAX_COMMAND_LEN);
  msgsnd(queue,&message,sizeof(jsmstruct),0);
  free(ccommand);

  /* Make sure command is acknowledged */
  msgrcv(myMessageId,msgp,sizeof(jsmstruct),0,flags);
  if (msg.mtype != JSM_ACK) {
    fprintf(stderr,"JNI.C: jni$jsmrun: JSM didn't respond to command, received %d\n",msg.mtype);
    remove_local_queue(myMessageId);
    return(0);
  }

  shutdown = 0;
  /* Here is where we would wait for shutdown or sync if desired
     Need to add option not to wait for Jeff
  */
  while( Mc->break_ == 0 && shutdown == 0 ) {
    retval = msgrcv(myMessageId,msgp,sizeof(jsmstruct),0,flags);
    if (msg.mtype == JSM_SHUTDOWN) {
      DEBUG("JNI.C: jni$jsmrun: jsm Client: Received shutdown\n");
      shutdown = 1;
    } else if (msg.mtype == JSM_SYNC) {
      DEBUG("JNI.C: jni$jsmrun: calling m_sync()\n");
      m_sync();
      DEBUG("JNI.C: jni$jsmrun: returning from m_sync()\n");
      msgsnd(msg.jsm.responseq,&ack_message,sizeof(jsmstruct),0);
      msg.mtype = JSM_NONE;
    } else if (msg.mtype == JSM_RETURN) {
      /* JC4: 22NOV06 Once this method returns M$RETURN is called from the
              Fortran code. This extra call to m_return() causes BIG problems
              with the piping system.
      DEBUG("JNI.C: jni$jsmrun: calling m_return()\n");
      m_return();
      DEBUG("JNI.C: jni$jsmrun: returning from m_return()\n");
      */
      msgsnd(msg.jsm.responseq,&ack_message,sizeof(jsmstruct),0);
      shutdown = 1;
      msg.mtype = JSM_NONE;
    } else {
      /* This block reached if the JVM was killed out from under a running nm command */
      if (retval == -1) {
        if (errno == EIDRM) {    /* Resource removed, so end immediately */
          errcnt = 5;
        } else {
          errcnt++;
        }
      }
      if (errcnt > 4) {
        fprintf(stderr,"JNI.C: jni$jsmrun: Returning due to no response from msg queue for command '%s', ERRNO=%d.\n",message.jsm.command,errno);
        shutdown = 1;
        msg.mtype = JSM_NONE;
      }
       m_pause(0.1);
    }
  } /* end while loop */

  remove_local_queue(myMessageId);

  DEBUG("JNI.C: END: jni$jsmrun\n");
  return 0;
} /* end jni$jsmrun() */

/*******************************************************************************
 Runs a JVM command.
 @param option  The command (FORTRAN STRING).
 @param loption Length of the command (FORTRAN STRING LENGTH).
*******************************************************************************/
int_u4 jni$run (char *command, int_4 lcommand) {
  char *ccommand;
  DEBUG("JNI.C: START: jni$run\n");

  ccommand=MemAlloc((lcommand+1)*sizeof(char));
  strf2c(command,lcommand,ccommand,(lcommand+1),F2C_TRIM);
  runjavaprogram(ccommand);
  free(ccommand);

  DEBUG("JNI.C: END: jni$run\n");
  return 0;
} /* end jni$run() */

/*******************************************************************************
 Add a JVM option.
 @param option  The option data (FORTRAN STRING).
 @param loption Length of the option (FORTRAN STRING LENGTH).
*******************************************************************************/
int_u4 jni$addoption (char *option, int_4 loption) {
  char *coption;
  /* Memory allocated and must be released in runjavaprogram */
  DEBUG("JNI.C: START: jni$addoption\n");

  coption=MemAlloc((loption+1)*sizeof(char));
  strf2c(option,loption,coption,(loption+1),F2C_TRIM);
  AddOption(coption,NULL);

  DEBUG("JNI.C: END: jni$addoption\n");
  return 0;
}

/*******************************************************************************
 Add a JVM argument, converts X-Midas arguments into Java Args.
 @param arg  The argument (FORTRAN STRING).
 @param larg Length of the argument (FORTRAN STRING LENGTH).
*******************************************************************************/
int_u4 jni$addarg (char *arg, int_4 larg) {
  char *carg;
  /* Memory allocated and must be released in runjavaprogram */
  DEBUG("JNI.C: START: jni$addarg\n");

  carg=MemAlloc((larg+1)*sizeof(char));
  strf2c(arg,larg,carg,(larg+1),F2C_TRIM);
  AddArg(carg);

  DEBUG("JNI.C: END: jni$addarg\n");
  return 0;
} /* end jni$addarg() */

/*******************************************************************************
 Turn on/off debugging in the JVM.
 @param value The debug status.
 @return The debug status (will be 0 or 1).
*******************************************************************************/
int_u4 jni$debug(int_4 *value) {
  debug = ((*value) != 0);

  return debug;
}

/*******************************************************************************
 Start NeXtMidas instance inside JVM
*******************************************************************************/
int_u4 jni$startnm() {
  JavaVMInitArgs vm_args;         /* VM initialization arguments */
  jclass         mainClass;       /* The class loader class. */
  jobjectArray   mainArgs = NULL; /* The arguments passed to the class loader.*/
  jobject        localRef = NULL; /* Local ref to an object. */
  jsize          nVMs     = 0;
  int forkstat;
  int res;
  int i;
  void *xmjvm;
  JNIEnv *env = NULL;       /* pointer to native method interface */
  jclass clazz;
  jmethodID mid;

  DEBUG("JNI.C: START: jni$startnm\n");

  /* Get the ENV pointer */
  (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);


  /* Create a NeXtMidas Shell to receive commands */
  mainClass = (*env)->FindClass(env,"NmClassLoader");
  RETURN_IF_JVM_ERROR(mainClass==NULL,
                      "ERROR: Can not get pointer to the NmClassLoader class.\n",
                      -1);

  mid = (*env)->GetStaticMethodID(env,mainClass,"initShell",
                                  "([Ljava/lang/String;)Ljava/lang/Object;");
  RETURN_IF_JVM_ERROR(mid==NULL,
                      "ERROR: Can not get pointer to the NmClassLoader.initShell(String) method.\n",
                      -1);

  /* Class will be an instance of nxm/sys/libxm/XmShellParent */
  localRef = (*env)->CallStaticObjectMethod(env,mainClass,mid,mainArgs);
  RETURN_IF_JVM_ERROR(localRef==NULL, "ERROR: Failed to initialize XmShellParent.\n", -1);

  /* Must use a global reference for xmshellparent, otherwise the garbage
     collector will quickly clean it up. However, we must clean it up manually
     when we are done with it.
   */
  xmshellparent = (*env)->NewGlobalRef(env,localRef);
  (*env)->DeleteLocalRef(env, localRef);
  RETURN_IF_JVM_ERROR(xmshellparent==NULL,
                      "ERROR: Can not create global reference to XmShellParent.\n",
                      -1);

  if((debug==1) || FORCE_DEBUG) { fprintf(stderr,"JNI.C: jni$startnm: xmshellparent=0x%x\n",xmshellparent);   }
  DEBUG("JNI.C: END: jni$startnm\n");
} /* end jni$startnm() */

/*******************************************************************************
 Start the JVM.
 @return 0 on success, non-zero on failure.
*******************************************************************************/
int_u4 jni$startjvm () {
  JavaVMInitArgs vm_args;         /* VM initialization arguments */
  jclass         mainClass;       /* The class loader class. */
  jobjectArray   mainArgs = NULL; /* The arguments passed to the class loader.*/
  jobject        localRef = NULL; /* Local ref to an object. */
  jsize          nVMs     = 0;
  int forkstat;
  int res;
  int i;
  void *xmjvm;
  JNIEnv *env = NULL;       /* pointer to native method interface */
  jclass clazz;
  jmethodID mid;
  char *exitstr;

  DEBUG("JNI.C: START: jni$startjvm\n");

  /* Setup the VM's arguments.
     vm_args.version = JNI_VERSION_1_4     - JNI version (see JNI docs).
     vm_args.ignoreUnrecognized = JNI_TRUE - Prevents the JVM from aborting
                                             due to an unknown argument.
   */
  vm_args.version  = JNI_VERSION_1_4;
  vm_args.ignoreUnrecognized = JNI_TRUE;

  if (debug==1) {
    fprintf(stderr,"Adding %d options(%d) to JVM\n",numOptions,options);
    for (i=0;i<numOptions;i++) {
      fprintf(stderr,"...Option: '%s'\n",options[i].optionString);
    }
  }
  exitstr = strdup("exit");
  AddOption(exitstr,(void *)(*exit_hook));
  vm_args.nOptions = numOptions;             /* JVM options */
  vm_args.options  = options;

  /* Create the JavaVM to be used */
  DEBUG("JNI.C: jni$startjvm: Creating the JVM\n");

  res = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
  if (res < 0) {
    fprintf(stderr,"Error here that we need to capture. res = %d\n",res);
    fprintf(stderr,"Probably caused by not running with /nodll\n");
    jvm = NULL;
  }

  DEBUG("JNI.C: END: jni$startjvm\n");
}

/*******************************************************************************
 Stop the JVM.
 @return 0 on success, non-zero on failure.
*******************************************************************************/
int_u4 jni$stopjvm() {
  jclass         mainClass;       /* The class loader class. */
  jobject        localRef = NULL; /* Local ref to an object. */
  jmethodID mid;
  JNIEnv *env = NULL;       /* pointer to native method interface */

  DEBUG("JNI.C: START: jni$stopjvm\n");

  /* Get the ENV pointer */
  (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);

  /* Get a reference to the java.lang.System class */
  mainClass = (*env)->FindClass(env,"java/lang/System");
  RETURN_IF_JVM_ERROR(mainClass==NULL,
                      "ERROR: Can not get reference to the java.lang.System class.",
                      -1);

  /* Get a reference to the System.exit() method */
  mid = (*env)->GetStaticMethodID(env,mainClass,"exit",
                                  "(I)V");
  RETURN_IF_JVM_ERROR(mid==NULL,
                      "ERROR: Can not get reference to the System.exit() method.",
                      -1);

  /* invoke the System.exit() method */
  localRef = (*env)->CallStaticObjectMethod(env,mainClass,mid,0);
  RETURN_IF_JVM_ERROR(localRef==NULL,
                      "ERROR: Failed to call System.exit().",
                      -1);

  DEBUG("JNI.C: END: jni$stopjvm\n");
  return 0;
} /* end jni$stopjvm() */

/******************************************************************************
  METHODS CONVERTED FROM FORTRAN
 ******************************************************************************/
int jni$setclasspath(int *debug_this) {
  int counter;
  int length;

  char *shell  = "nxm.sys.libxm.XmShellParent";
  char *nmsf   = "NULL"; /* Note this is the string 'NULL' not a null pointer */

  char option[SHORT_STR_LEN]      = "";
  char semi_option[SHORT_STR_LEN] = "";
  char pathname[SHORT_STR_LEN]    = "";
  char classpath[LONG_STR_LEN]    = "";
  char librarypath[LONG_STR_LEN]  = "";
  char tmpstr[LONG_STR_LEN]       = "";
  char nmroot[LONG_STR_LEN]       = "";
  char msgopt[LONG_STR_LEN]       = "";

  DEBUG("JNIF.C: START: jni$setclasspath\n");

  /* Check to debug the Java JVM */
  if (m_get_switch("JDEBUG") == 1) {
    debug = 1;
  } else {
    debug = 0;
  }

  /* Create the CLASSPATH */
  if (debug) {
    printf("JNIF.C: jni$setclasspath: Creating classpath and library path.\n");
  }

  /* Get $NMROOT */
  m_filename("HOST", ";NXM", "", nmroot, LONG_STR_LEN);
  if(nmroot[strlen(nmroot)-1] != '/') {
    APPENDL(nmroot, "/../../../");
  } else {
    APPENDL(nmroot, "../../../");
  }

  if (debug) {
    printf("JNIF.C: jni$setclasspath: $NMROOT=%s\n",nmroot);
  }

  /* If user specified a specific path use it */
  if (m_get_uswitch("CP",tmpstr,LONG_STR_LEN) > 0) {
    add_to_classpath(classpath, tmpstr);
  }

  /* Add $NMROOT/tools/ to classpath */
  snprintf(pathname, SHORT_STR_LEN, "%stools/", nmroot);
  add_to_classpath(classpath, pathname);

  /* Add $NMROOT/nxm/sys/libxm/ to libpath */
  snprintf(pathname, SHORT_STR_LEN, "%snxm/sys/libxm/", nmroot);
  add_to_libpath(librarypath, pathname);

  if (m_get_uswitch("MSGOPT",msgopt,LONG_STR_LEN) <= 0) {
    msgopt[0] = 0; /* Set to the empty string. */
  }

  snprintf(tmpstr, LONG_STR_LEN, "-DNMPID=%d",  getpid());  AddJniOption(tmpstr);
  snprintf(tmpstr, LONG_STR_LEN, "-DNMSF=%s",   nmsf    );  AddJniOption(tmpstr);
  snprintf(tmpstr, LONG_STR_LEN, "-DNMROOT=%s", nmroot  );  AddJniOption(tmpstr);
  snprintf(tmpstr, LONG_STR_LEN, "-DSHELL=%s",  shell   );  AddJniOption(tmpstr);
  snprintf(tmpstr, LONG_STR_LEN, "-DMSGOPT=%s", msgopt  );  AddJniOption(tmpstr);

  /* Add Jar files to the end of the classpath */
  if(m_get_uswitch("JARS",tmpstr,LONG_STR_LEN) > 0) {
    add_to_classpath(classpath, tmpstr);
  }

  if (debug) {
    printf("JNIF.C: jni$setclasspath: classpath   = %s\n", classpath);
    printf("JNIF.C: jni$setclasspath: librarypath = %s\n", librarypath);
  }
  AddJniOption(classpath);
  AddJniOption(librarypath);
  AddJniOption("--add-opens=java.logging/java.util.logging=ALL-UNNAMED");

  DEBUG("JNIF.C: END: jni$setclasspath\n");
  return TRUE;
}

/*******************************************************************************
*******************************************************************************/
int jni$getswitches(int *debug_this) {
  int numuseroptions;
  int counter;

  char space[10] = "<SPACE>";
  char tmpstr[LONG_STR_LEN]      = "";
  char useroptions[LONG_STR_LEN] = "";

  DEBUG("JNIF.C: START: jni$getswitches\n");

  /* Check if user wants to debug the Java VM */
  if (m_get_switch("VERBOSEJNI") == 1) { AddJniOption("-verbose:jni"); }
  if (m_get_switch("VERBOSEGC" ) == 1) { AddJniOption("-verbose:gc" ); }
  if (m_get_switch("VERIFYALL" ) == 1) { AddJniOption("-Xverify:all"); }
  if (m_get_switch("XCHECKJNI" ) == 1) { AddJniOption("-Xcheck:jni" ); }

  /* If user specified any additional JVM flags pass them in */
  if (m_get_uswitch("JVMOPTIONS",useroptions,LONG_STR_LEN) > 0) {
    char   aval[LONG_STR_LEN] = "";
    real_8 dval;
    if (debug==1) { fprintf(stderr, "JNIF.C: jni$getswitches: initial /JVMOPTIONS=%s\n",useroptions); }
    /* Bug 1550 to allow result parameter to be used in /JVMOPTIONS switch to get around xm445 *
     * and prior upper casing the string (since NM;NXM is configured with -2 for #parameters)  */
    if (m_rfind(useroptions,aval,&dval,LONG_STR_LEN, LONG_STR_LEN)=='A') {
      size_t lstr = (size_t) MIN(dval, LONG_STR_LEN);
      strncpy(useroptions,aval,lstr);
    }
    if (debug==1) { fprintf(stderr, "JNIF.C: jni$getswitches: final /JVMOPTIONS=%s\n",useroptions); }
    numuseroptions = m_nelements(useroptions, space);

    for(counter = 1; counter <= numuseroptions; counter++) {
      m_element(useroptions, tmpstr, counter, " ", LONG_STR_LEN);

      AddJniOption(tmpstr);
    }
  }

  /* Put additional Switches here */
  if(m_get_pswitch("PN")) { /* The process name */
    m_get_uswitch("PN",Mu->command_name,LONG_STR_LEN);
  }

  DEBUG("JNIF.C: END: jni$getswitches\n");
  return TRUE;
}

/*******************************************************************************
 Stop the JSM and cleanup the message queues at most hard exits when the
 /CLEANUPATEXIT switch is specified
 @since NeXtMidas 2.9.0 based on code provided by Pete Perrino with modifications
        from Duy Huang for BUG 1237
*******************************************************************************/
void cleanup_atexit (void) {
  int_u4 status;
  DEBUG("JNI.C: START: cleanup_atexit\n");

  status = jni$stopjsm();
  status = jni$cleanupjsm();

  DEBUG("JNI.C: END: cleanup_atexit\n");
} /* end cleanup_atexit() */
