summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArchie Cobbs <archie@dellroad.org>2004-04-27 22:56:05 +0000
committerArchie Cobbs <archie@dellroad.org>2004-04-27 22:56:05 +0000
commitb149b837b5affac11a10a0695f3aab35327e08d6 (patch)
tree074703954078bd54b325a0dd491c3cd836924333
parent4de3b581e34f2cca4b932be02b5007f470a2db50 (diff)
downloadclasspath-b149b837b5affac11a10a0695f3aab35327e08d6.tar.gz
2004-04-27 Archie Cobbs <archie@dellroad.org>
* include/Makefile.am: add java_lang_VMProcess.h * include/java_lang_VMProcess.h: new file * include/java_lang_VMRuntime.h: regenerate * native/jni/java-lang/Makefile.am: add java_lang_VMProcess.c * native/jni/java-lang/java_lang_VMProcess.c: new file * vm/reference/java/lang/Makefile.am: add VMProcess.java * vm/reference/java/lang/VMProcess.java: new file * vm/reference/java/lang/VMRuntime.java (exec): use VMProcess
-rw-r--r--ChangeLog11
-rw-r--r--include/Makefile.am3
-rw-r--r--include/java_lang_VMProcess.h21
-rw-r--r--include/java_lang_VMRuntime.h1
-rw-r--r--native/jni/java-lang/Makefile.am3
-rw-r--r--native/jni/java-lang/java_lang_VMProcess.c477
-rw-r--r--vm/reference/java/lang/Makefile.am1
-rw-r--r--vm/reference/java/lang/VMProcess.java356
-rw-r--r--vm/reference/java/lang/VMRuntime.java6
9 files changed, 876 insertions, 3 deletions
diff --git a/ChangeLog b/ChangeLog
index 049afa3ee..485b753c4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2004-04-27 Archie Cobbs <archie@dellroad.org>
+
+ * include/Makefile.am: add java_lang_VMProcess.h
+ * include/java_lang_VMProcess.h: new file
+ * include/java_lang_VMRuntime.h: regenerate
+ * native/jni/java-lang/Makefile.am: add java_lang_VMProcess.c
+ * native/jni/java-lang/java_lang_VMProcess.c: new file
+ * vm/reference/java/lang/Makefile.am: add VMProcess.java
+ * vm/reference/java/lang/VMProcess.java: new file
+ * vm/reference/java/lang/VMRuntime.java (exec): use VMProcess
+
2004-04-27 Michael Koch <konqueror@gmx.de>
* javax/print/Doc.java
diff --git a/include/Makefile.am b/include/Makefile.am
index 0c0328062..cb04470f3 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -49,6 +49,7 @@ $(top_srcdir)/include/java_lang_Double.h \
$(top_srcdir)/include/java_lang_Math.h \
$(top_srcdir)/include/java_lang_Object.h \
$(top_srcdir)/include/java_lang_VMFloat.h \
+$(top_srcdir)/include/java_lang_VMProcess.h \
$(top_srcdir)/include/java_lang_VMRuntime.h \
$(top_srcdir)/include/java_lang_VMSystem.h \
$(top_srcdir)/include/java_lang_reflect_Array.h \
@@ -151,6 +152,8 @@ $(top_srcdir)/include/java_lang_Object.h: $(top_srcdir)/java/lang/Object.java
$(JAVAH) -o $@ java.lang.Object
$(top_srcdir)/include/java_lang_VMFloat.h: $(top_srcdir)/vm/reference/java/lang/VMFloat.java
$(JAVAH) -o $@ java.lang.VMFloat
+$(top_srcdir)/include/java_lang_VMProcess.h: $(top_srcdir)/vm/reference/java/lang/VMProcess.java
+ $(JAVAH) -o $@ java.lang.VMProcess
$(top_srcdir)/include/java_lang_VMRuntime.h: $(top_srcdir)/vm/reference/java/lang/VMRuntime.java
$(JAVAH) -o $@ java.lang.VMRuntime
$(top_srcdir)/include/java_lang_VMSystem.h: $(top_srcdir)/vm/reference/java/lang/VMSystem.java
diff --git a/include/java_lang_VMProcess.h b/include/java_lang_VMProcess.h
new file mode 100644
index 000000000..e5ca6cf9c
--- /dev/null
+++ b/include/java_lang_VMProcess.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+
+#ifndef __java_lang_VMProcess__
+#define __java_lang_VMProcess__
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+extern JNIEXPORT void JNICALL Java_java_lang_VMProcess_nativeSpawn (JNIEnv *env, jobject, jobjectArray, jobjectArray, jobject);
+extern JNIEXPORT jboolean JNICALL Java_java_lang_VMProcess_nativeReap (JNIEnv *env, jclass);
+extern JNIEXPORT void JNICALL Java_java_lang_VMProcess_nativeKill (JNIEnv *env, jclass, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __java_lang_VMProcess__ */
diff --git a/include/java_lang_VMRuntime.h b/include/java_lang_VMRuntime.h
index bcc80643f..8999a3603 100644
--- a/include/java_lang_VMRuntime.h
+++ b/include/java_lang_VMRuntime.h
@@ -23,7 +23,6 @@ extern JNIEXPORT void JNICALL Java_java_lang_VMRuntime_runFinalizersOnExit (JNIE
extern JNIEXPORT void JNICALL Java_java_lang_VMRuntime_exit (JNIEnv *env, jclass, jint);
extern JNIEXPORT jint JNICALL Java_java_lang_VMRuntime_nativeLoad (JNIEnv *env, jclass, jstring);
extern JNIEXPORT jstring JNICALL Java_java_lang_VMRuntime_nativeGetLibname (JNIEnv *env, jclass, jstring, jstring);
-extern JNIEXPORT jobject JNICALL Java_java_lang_VMRuntime_exec (JNIEnv *env, jclass, jobjectArray, jobjectArray, jobject);
extern JNIEXPORT void JNICALL Java_java_lang_VMRuntime_insertSystemProperties (JNIEnv *env, jclass, jobject);
#ifdef __cplusplus
diff --git a/native/jni/java-lang/Makefile.am b/native/jni/java-lang/Makefile.am
index 4fb222345..43d39366a 100644
--- a/native/jni/java-lang/Makefile.am
+++ b/native/jni/java-lang/Makefile.am
@@ -5,7 +5,8 @@ libjavalang_la_SOURCES = java_lang_VMSystem.c \
java_lang_VMFloat.c \
java_lang_Double.c \
java_lang_VMDouble.c \
- java_lang_Math.c
+ java_lang_Math.c \
+ java_lang_VMProcess.c
libjavalangreflect_la_SOURCES = java_lang_reflect_Array.c
diff --git a/native/jni/java-lang/java_lang_VMProcess.c b/native/jni/java-lang/java_lang_VMProcess.c
new file mode 100644
index 000000000..31202d56b
--- /dev/null
+++ b/native/jni/java-lang/java_lang_VMProcess.c
@@ -0,0 +1,477 @@
+/* java_lang_VMProcess.c -- native code for java.lang.VMProcess
+ Copyright (C) 1998, 1999, 2000, 2002, 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+#include <config.h>
+
+#include "java_lang_VMProcess.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+#include "target_native.h"
+#include "target_native_misc.h"
+
+/* Internal functions */
+static char *copy_string(JNIEnv *env, jobject string);
+static char *copy_elem(JNIEnv *env, jobject stringArray, jint i);
+
+/* Some O/S's don't declare this */
+extern char **environ;
+
+/*
+ * Internal helper function to copy a String in UTF-8 format.
+ */
+static char *
+copy_string(JNIEnv *env, jobject string)
+{
+ char errbuf[64];
+ const jbyte *utf;
+ jclass clazz;
+ char *copy;
+
+ /* Check for null */
+ if (string == NULL)
+ {
+ clazz = (*env)->FindClass(env, "java/lang/NullPointerException");
+ if ((*env)->ExceptionOccurred(env))
+ return NULL;
+ (*env)->ThrowNew(env, clazz, NULL);
+ (*env)->DeleteLocalRef(env, clazz);
+ return NULL;
+ }
+
+ /* Extract UTF-8 */
+ utf = (*env)->GetStringUTFChars(env, string, NULL);
+ if ((*env)->ExceptionOccurred(env))
+ return NULL;
+
+ /* Copy it */
+ if ((copy = strdup(utf)) == NULL)
+ {
+ TARGET_NATIVE_MISC_FORMAT_STRING1(errbuf, sizeof(errbuf),
+ "strdup: %s", strerror(errno));
+ clazz = (*env)->FindClass(env, "java/lang/InternalError");
+ if ((*env)->ExceptionOccurred(env))
+ return NULL;
+ (*env)->ThrowNew(env, clazz, errbuf);
+ (*env)->DeleteLocalRef(env, clazz);
+ }
+
+ /* Done */
+ (*env)->ReleaseStringUTFChars(env, string, utf);
+ return copy;
+}
+
+/*
+ * Internal helper function to copy a String[] element in UTF-8 format.
+ */
+static char *
+copy_elem(JNIEnv *env, jobject stringArray, jint i)
+{
+ jobject elem;
+ char *rtn;
+
+ elem = (*env)->GetObjectArrayElement(env, stringArray, i);
+ if ((*env)->ExceptionOccurred(env))
+ return NULL;
+ if ((rtn = copy_string(env, elem)) == NULL)
+ return NULL;
+ (*env)->DeleteLocalRef(env, elem);
+ return rtn;
+}
+
+/*
+ * private final native void nativeSpawn(String[], String[], File)
+ * throws java/io/IOException
+ */
+JNIEXPORT void JNICALL
+Java_java_lang_VMProcess_nativeSpawn(JNIEnv *env, jobject this,
+ jobjectArray cmdArray, jobjectArray envArray, jobject dirFile)
+{
+ int fds[3][2] = { { -1, -1 }, { -1, -1 }, { -1, -1 } };
+ jobject streams[3] = { NULL, NULL, NULL };
+ jobject dirString = NULL;
+ char **newEnviron = NULL;
+ jsize cmdArrayLen = 0;
+ jsize envArrayLen = 0;
+ char **strings = NULL;
+ int num_strings = 0;
+ char *dir = NULL;
+ pid_t pid = -1;
+ char errbuf[64];
+ jmethodID method;
+ jclass clazz;
+ int i;
+
+ /* Check for null */
+ if (cmdArray == NULL)
+ goto null_pointer_exception;
+
+ /* Invoke dirFile.getPath() */
+ if (dirFile != NULL)
+ {
+ clazz = (*env)->FindClass(env, "java/io/File");
+ if ((*env)->ExceptionOccurred(env))
+ return;
+ method = (*env)->GetMethodID(env,
+ clazz, "getPath", "()Ljava/lang/String;");
+ if ((*env)->ExceptionOccurred(env))
+ return;
+ dirString = (*env)->CallObjectMethod(env, dirFile, method);
+ if ((*env)->ExceptionOccurred(env))
+ return;
+ (*env)->DeleteLocalRef(env, clazz);
+ }
+
+ /*
+ * Allocate array of C strings. We put all the C strings we need to
+ * handle the command parameters, the new environment, and the new
+ * directory into a single array for simplicity of (de)allocation.
+ */
+ cmdArrayLen = (*env)->GetArrayLength(env, cmdArray);
+ if (envArray != NULL)
+ envArrayLen = (*env)->GetArrayLength(env, envArray);
+ if ((strings = malloc(((cmdArrayLen + 1)
+ + (envArray != NULL ? envArrayLen + 1 : 0)
+ + (dirString != NULL ? 1 : 0)) * sizeof(*strings))) == NULL)
+ {
+ TARGET_NATIVE_MISC_FORMAT_STRING1(errbuf,
+ sizeof(errbuf), "malloc: %s", strerror(errno));
+ goto out_of_memory;
+ }
+
+ /* Extract C strings from the various String parameters */
+ for (i = 0; i < cmdArrayLen; i++)
+ {
+ if ((strings[num_strings++] = copy_elem(env, cmdArray, i)) == NULL)
+ goto done;
+ }
+ strings[num_strings++] = NULL; /* terminate array with NULL */
+ if (envArray != NULL)
+ {
+ newEnviron = strings + num_strings;
+ for (i = 0; i < envArrayLen; i++)
+ {
+ if ((strings[num_strings++] = copy_elem(env, envArray, i)) == NULL)
+ goto done;
+ }
+ strings[num_strings++] = NULL; /* terminate array with NULL */
+ }
+ if (dirString != NULL)
+ {
+ if ((dir = copy_string(env, dirString)) == NULL)
+ goto done;
+ strings[num_strings++] = dir;
+ }
+
+ /* Create inter-process pipes */
+ for (i = 0; i < 3; i++)
+ {
+ if (pipe(fds[i]) == -1)
+ {
+ TARGET_NATIVE_MISC_FORMAT_STRING1(errbuf,
+ sizeof(errbuf), "pipe: %s", strerror(errno));
+ goto system_error;
+ }
+ }
+
+ /* Set close-on-exec flag for parent's ends of pipes */
+ (void)fcntl(fds[0][1], F_SETFD, 1);
+ (void)fcntl(fds[1][0], F_SETFD, 1);
+ (void)fcntl(fds[2][0], F_SETFD, 1);
+
+ /* Fork into parent and child processes */
+ if ((pid = fork()) == (pid_t)-1)
+ {
+ TARGET_NATIVE_MISC_FORMAT_STRING1(errbuf,
+ sizeof(errbuf), "fork: %s", strerror(errno));
+ goto system_error;
+ }
+
+ /* Child becomes the new process */
+ if (pid == 0)
+ {
+ char *const path = strings[0];
+
+ /* Move file descriptors to standard locations */
+ if (fds[0][0] != 0)
+ {
+ if (dup2(fds[0][0], 0) == -1)
+ {
+ fprintf(stderr, "dup2: %s", strerror(errno));
+ exit(127);
+ }
+ close(fds[0][0]);
+ }
+ if (fds[1][1] != 1)
+ {
+ if (dup2(fds[1][1], 1) == -1)
+ {
+ fprintf(stderr, "dup2: %s", strerror(errno));
+ exit(127);
+ }
+ close(fds[1][1]);
+ }
+ if (fds[2][1] != 2)
+ {
+ if (dup2(fds[2][1], 2) == -1)
+ {
+ fprintf(stderr, "dup2: %s", strerror(errno));
+ exit(127);
+ }
+ close(fds[2][1]);
+ }
+
+ /* Change into destination directory */
+ if (dir != NULL && chdir(dir) == -1)
+ {
+ fprintf(stderr, "%s: %s", dir, strerror(errno));
+ exit(127);
+ }
+
+ /* Make argv[0] last component of executable pathname */
+ /* XXX should use "file.separator" property here XXX */
+ for (i = strlen(path); i > 0 && path[i - 1] != '/'; i--);
+ strings[0] = path + i;
+
+ /* Set new environment */
+ if (newEnviron != NULL)
+ environ = newEnviron;
+
+ /* Execute new program (this will close the parent end of the pipes) */
+ execvp(path, strings);
+
+ /* Failed */
+ fprintf(stderr, "%s: %s", path, strerror(errno));
+ exit(127);
+ }
+
+ /* Create Input/OutputStream objects around parent file descriptors */
+ clazz = (*env)->FindClass(env, "gnu/java/nio/channels/FileChannelImpl");
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+ method = (*env)->GetMethodID(env, clazz, "<init>", "(II)V");
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+ for (i = 0; i < 3; i++)
+ {
+ /* Mode is WRITE (2) for in and READ (1) for out and err. */
+ const int fd = fds[i][i == 0];
+ const int mode = (i == 0) ? 2 : 1;
+ jclass sclazz;
+ jmethodID smethod;
+
+ jobject channel = (*env)->NewObject(env, clazz, method, fd, mode);
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+
+ if (mode == 2)
+ sclazz = (*env)->FindClass(env, "java/io/FileOutputStream");
+ else
+ sclazz = (*env)->FindClass(env, "java/io/FileInputStream");
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+
+ smethod = (*env)->GetMethodID(env, sclazz, "<init>",
+ "(Lgnu/java/nio/channels/FileChannelImpl;)V");
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+
+ streams[i] = (*env)->NewObject(env, sclazz, smethod, channel);
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+
+ (*env)->DeleteLocalRef(env, sclazz);
+ }
+ (*env)->DeleteLocalRef(env, clazz);
+
+ /* Invoke VMProcess.setProcessInfo() to update VMProcess object */
+ method = (*env)->GetMethodID(env,
+ (*env)->GetObjectClass(env, this), "setProcessInfo",
+ "(Ljava/io/OutputStream;Ljava/io/InputStream;Ljava/io/InputStream;J)V");
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+ (*env)->CallVoidMethod(env, this, method,
+ streams[0], streams[1], streams[2], (jlong)pid);
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+ (*env)->DeleteLocalRef(env, clazz);
+
+done:
+ /*
+ * We get here in both the success and failure cases in the
+ * parent process. Our goal is to clean up the mess we created.
+ */
+
+ /* Close child's ends of pipes */
+ for (i = 0; i < 3; i++)
+ {
+ const int fd = fds[i][i != 0];
+
+ if (fd != -1)
+ close(fd);
+ }
+
+ /*
+ * Close parent's ends of pipes if Input/OutputStreams never got created.
+ * This can only happen in a failure case. If a Stream object
+ * was created for a file descriptor, we don't close it because it
+ * will get closed when the Stream object is finalized.
+ */
+ for (i = 0; i < 3; i++)
+ {
+ const int fd = fds[i][i == 0];
+
+ if (fd != -1 && streams[i] == NULL)
+ close(fd);
+ }
+
+ /* Free C strings */
+ while (num_strings > 0)
+ free(strings[--num_strings]);
+ free(strings);
+
+ /* Done */
+ return;
+
+null_pointer_exception:
+ clazz = (*env)->FindClass(env, "java/lang/NullPointerException");
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+ (*env)->ThrowNew(env, clazz, NULL);
+ (*env)->DeleteLocalRef(env, clazz);
+ goto done;
+
+out_of_memory:
+ clazz = (*env)->FindClass(env, "java/lang/InternalError");
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+ (*env)->ThrowNew(env, clazz, errbuf);
+ (*env)->DeleteLocalRef(env, clazz);
+ goto done;
+
+system_error:
+ clazz = (*env)->FindClass(env, "java/io/IOException");
+ if ((*env)->ExceptionOccurred(env))
+ goto done;
+ (*env)->ThrowNew(env, clazz, errbuf);
+ (*env)->DeleteLocalRef(env, clazz);
+ goto done;
+}
+
+/*
+ * private static final native boolean nativeReap()
+ */
+JNIEXPORT jboolean JNICALL
+Java_java_lang_VMProcess_nativeReap(JNIEnv *env, jclass clazz)
+{
+ char ebuf[64];
+ jfieldID field;
+ jint status;
+ pid_t pid;
+
+ /* Try to reap a child process, but don't block */
+ if ((pid = waitpid((pid_t)-1, &status, WNOHANG)) == 0)
+ return JNI_FALSE;
+
+ /* Check result from waitpid() */
+ if (pid == (pid_t)-1)
+ {
+ if (errno == ECHILD || errno == EINTR)
+ return JNI_FALSE;
+ TARGET_NATIVE_MISC_FORMAT_STRING2(ebuf,
+ sizeof(ebuf), "waitpid(%ld): %s", (long)pid, strerror(errno));
+ clazz = (*env)->FindClass(env, "java/lang/InternalError");
+ if ((*env)->ExceptionOccurred(env))
+ return JNI_FALSE;
+ (*env)->ThrowNew(env, clazz, ebuf);
+ (*env)->DeleteLocalRef(env, clazz);
+ return JNI_FALSE;
+ }
+
+ /* Get exit code; for signal termination return negative signal value XXX */
+ if (WIFEXITED(status))
+ status = (jint)(jbyte)WEXITSTATUS(status);
+ else if (WIFSIGNALED(status))
+ status = -(jint)WTERMSIG(status);
+ else
+ return JNI_FALSE; /* process merely stopped; ignore */
+
+ /* Return process pid and exit status */
+ field = (*env)->GetStaticFieldID(env, clazz, "reapedPid", "J");
+ if ((*env)->ExceptionOccurred(env))
+ return JNI_FALSE;
+ (*env)->SetStaticLongField(env, clazz, field, (jlong)pid);
+ if ((*env)->ExceptionOccurred(env))
+ return JNI_FALSE;
+ field = (*env)->GetStaticFieldID(env, clazz, "reapedExitValue", "I");
+ if ((*env)->ExceptionOccurred(env))
+ return JNI_FALSE;
+ (*env)->SetStaticIntField(env, clazz, field, status);
+ if ((*env)->ExceptionOccurred(env))
+ return JNI_FALSE;
+
+ /* Done */
+ return JNI_TRUE;
+}
+
+/*
+ * private static final native void nativeKill(long)
+ */
+JNIEXPORT void JNICALL
+Java_java_lang_VMProcess_nativeKill(JNIEnv *env, jclass clazz, jlong pid)
+{
+ char ebuf[64];
+
+ if (kill((pid_t)pid, SIGKILL) == -1)
+ {
+ TARGET_NATIVE_MISC_FORMAT_STRING2(ebuf,
+ sizeof(ebuf), "kill(%ld): %s", (long)pid, strerror(errno));
+ clazz = (*env)->FindClass(env, "java/lang/InternalError");
+ if ((*env)->ExceptionOccurred(env))
+ return;
+ (*env)->ThrowNew(env, clazz, ebuf);
+ (*env)->DeleteLocalRef(env, clazz);
+ }
+}
+
diff --git a/vm/reference/java/lang/Makefile.am b/vm/reference/java/lang/Makefile.am
index 709230345..ed2050aa2 100644
--- a/vm/reference/java/lang/Makefile.am
+++ b/vm/reference/java/lang/Makefile.am
@@ -8,6 +8,7 @@ VMClassLoader.java \
VMDouble.java \
VMFloat.java \
VMObject.java \
+VMProcess.java \
VMRuntime.java \
VMSecurityManager.java \
VMString.java \
diff --git a/vm/reference/java/lang/VMProcess.java b/vm/reference/java/lang/VMProcess.java
new file mode 100644
index 000000000..095c7496b
--- /dev/null
+++ b/vm/reference/java/lang/VMProcess.java
@@ -0,0 +1,356 @@
+/* java.lang.VMProcess -- VM implementation of java.lang.Process
+ Copyright (C) 2004 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package java.lang;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+/**
+ * Represents one external process. Each instance of this class is in
+ * one of three states: INITIAL, RUNNING, or TERMINATED. The instance
+ * is {@link Object#notifyAll notifyAll()}'d each time the state changes.
+ * The state of all instances is managed by a single dedicated thread
+ * which does the actual fork()/exec() and wait() system calls. User
+ * threads {@link Object#wait wait()} on the instance when creating the
+ * process or waiting for it to terminate.
+ *
+ * <p>
+ * See
+ * <a href="http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11801">GCC bug
+ * #11801</a> for the motivation behind the design of this class.
+ *
+ * @author Archie Cobbs
+ * @see Process
+ * @see Runtime#exec
+ */
+final class VMProcess extends Process
+{
+
+ // Possible states for a VMProcess
+ private static final int INITIAL = 0;
+ private static final int RUNNING = 1;
+ private static final int TERMINATED = 2;
+
+ // Dedicated thread that does all the fork()'ing and wait()'ing.
+ private static Thread processThread;
+
+ // New processes waiting to be spawned by processThread.
+ private static final LinkedList workList = new LinkedList();
+
+ // Return values set by nativeReap() when a child is reaped.
+ // These are only accessed by processThread so no locking required.
+ private static long reapedPid;
+ private static int reapedExitValue;
+
+ // Information about this process
+ private int state; // current state of process
+ private final String[] cmd; // copied from Runtime.exec()
+ private final String[] env; // copied from Runtime.exec()
+ private final File dir; // copied from Runtime.exec()
+ private Throwable exception; // if process failed to start
+ private long pid; // process id
+ private OutputStream stdin; // process input stream
+ private InputStream stdout; // process output stream
+ private InputStream stderr; // process error stream
+ private int exitValue; // process exit value
+
+ //
+ // Dedicated thread that does all the fork()'ing and wait()'ing
+ // for external processes. This is needed because some systems like
+ // Linux use a process-per-thread model, which means the same thread
+ // that did the fork()/exec() must also do the wait().
+ //
+ private static class ProcessThread extends Thread
+ {
+
+ // Max time (in ms) we'll delay before trying to reap another child.
+ private static final int MAX_REAP_DELAY = 1000;
+
+ // Processes created but not yet terminated; maps Long(pid) -> VMProcess
+ // Only used in run() and spawn() method from this Thread, so no locking.
+ private final HashMap activeMap = new HashMap();
+
+ public void run()
+ {
+ final LinkedList workList = VMProcess.workList;
+ while (true)
+ {
+
+ // Get the next process to spawn (if any) and spawn it. Spawn
+ // at most one at a time before checking for reapable children.
+ VMProcess process = null;
+ synchronized (workList)
+ {
+ if (!workList.isEmpty())
+ process = (VMProcess)workList.removeFirst();
+ }
+
+ if (process != null)
+ spawn(process);
+
+
+ // Check for termination of active child processes
+ while (!activeMap.isEmpty() && VMProcess.nativeReap())
+ {
+ long pid = VMProcess.reapedPid;
+ int exitValue = VMProcess.reapedExitValue;
+ process = (VMProcess)activeMap.remove(new Long(pid));
+ if (process != null)
+ {
+ synchronized (process)
+ {
+ process.exitValue = exitValue;
+ process.state = TERMINATED;
+ process.notify();
+ }
+ }
+ else
+ System.err.println("VMProcess WARNING reaped unknown process: "
+ + pid);
+ }
+
+
+ // If there are more new processes to create, go do that now.
+ // If there is nothing left to do, exit this thread. Otherwise,
+ // sleep a little while, and then check again for reapable children.
+ // We will get woken up immediately if there are new processes to
+ // spawn, but not if there are new children to reap. So we only
+ // sleep a short time, in effect polling while processes are active.
+ synchronized (workList)
+ {
+ if (!workList.isEmpty())
+ continue;
+ if (activeMap.isEmpty())
+ {
+ processThread = null;
+ break;
+ }
+
+ try
+ {
+ workList.wait(MAX_REAP_DELAY);
+ }
+ catch (InterruptedException e)
+ {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ // Spawn a process
+ private void spawn(VMProcess process)
+ {
+
+ // Spawn the process and put it in our active map indexed by pid.
+ // If the spawn operation fails, store the exception with the process.
+ // In either case, wake up thread that created the process.
+ synchronized (process)
+ {
+ try
+ {
+ process.nativeSpawn(process.cmd, process.env, process.dir);
+ process.state = RUNNING;
+ activeMap.put(new Long(process.pid), process);
+ }
+ catch (Throwable t)
+ {
+ process.state = TERMINATED;
+ process.exception = t;
+ }
+ process.notify();
+ }
+ }
+ }
+
+ // Constructor
+ private VMProcess(String[] cmd, String[] env, File dir) throws IOException
+ {
+
+ // Initialize this process
+ this.state = INITIAL;
+ this.cmd = cmd;
+ this.env = env;
+ this.dir = dir;
+
+ // Add process to the new process work list and wakeup processThread
+ synchronized (workList)
+ {
+ workList.add(this);
+ if (processThread == null)
+ {
+ processThread = new ProcessThread();
+ processThread.setDaemon(true);
+ processThread.start();
+ }
+ else
+ {
+ workList.notify();
+ }
+ }
+
+ // Wait for processThread to spawn this process and update its state
+ synchronized (this)
+ {
+ while (state == INITIAL)
+ {
+ try
+ {
+ wait();
+ }
+ catch (InterruptedException e)
+ {
+ /* ignore */
+ }
+ }
+ }
+
+ // If spawning failed, rethrow the exception in this thread
+ if (exception != null)
+ {
+ exception.fillInStackTrace();
+ if (exception instanceof IOException)
+ throw (IOException)exception;
+
+ if (exception instanceof Error)
+ throw (Error)exception;
+
+ if (exception instanceof RuntimeException)
+ throw (RuntimeException)exception;
+
+ throw new RuntimeException(exception);
+ }
+ }
+
+ // Invoked by native code (from nativeSpawn()) to record process info.
+ private void setProcessInfo(OutputStream stdin,
+ InputStream stdout, InputStream stderr, long pid)
+ {
+ this.stdin = stdin;
+ this.stdout = stdout;
+ this.stderr = stderr;
+ this.pid = pid;
+ }
+
+ /**
+ * Entry point from Runtime.exec().
+ */
+ static Process exec(String[] cmd, String[] env, File dir) throws IOException
+ {
+ return new VMProcess(cmd, env, dir);
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return stdin;
+ }
+
+ public InputStream getInputStream()
+ {
+ return stdout;
+ }
+
+ public InputStream getErrorStream()
+ {
+ return stderr;
+ }
+
+ public synchronized int waitFor() throws InterruptedException
+ {
+ while (state != TERMINATED)
+ wait();
+ return exitValue;
+ }
+
+ public synchronized int exitValue()
+ {
+ if (state != TERMINATED)
+ throw new IllegalThreadStateException();
+ return exitValue;
+ }
+
+ public synchronized void destroy()
+ {
+ if (state == TERMINATED)
+ return;
+
+ nativeKill(pid);
+ while (true)
+ {
+ try
+ {
+ waitFor();
+ }
+ catch (InterruptedException e)
+ {
+ /* ignore */
+ }
+ }
+ }
+
+ /**
+ * Does the fork()/exec() thing to create the O/S process.
+ * Must invoke setProcessInfo() before returning successfully.
+ * This method is only invoked by processThread.
+ *
+ * @throws IOException if the O/S process could not be created.
+ */
+ private native void nativeSpawn(String[] cmd, String[] env, File dir)
+ throws IOException;
+
+ /**
+ * Test for a reapable child process, and reap if so. Does not block.
+ * If a child was reaped, this method must set reapedPid and
+ * reapedExitValue appropriately before returning.
+ * This method is only invoked by processThread.
+ *
+ * @return true if a child was reaped, otherwise false
+ */
+ private static native boolean nativeReap();
+
+ /**
+ * Kill a process. This sends it a fatal signal but does not reap it.
+ */
+ private static native void nativeKill(long pid);
+}
diff --git a/vm/reference/java/lang/VMRuntime.java b/vm/reference/java/lang/VMRuntime.java
index 73303f31a..ce45a0c2a 100644
--- a/vm/reference/java/lang/VMRuntime.java
+++ b/vm/reference/java/lang/VMRuntime.java
@@ -38,6 +38,7 @@ exception statement from your version. */
package java.lang;
import java.io.File;
+import java.io.IOException;
import java.util.Properties;
/**
@@ -178,7 +179,10 @@ final class VMRuntime
* @return the newly created process
* @throws NullPointerException if cmd or env have null elements
*/
- static native Process exec(String[] cmd, String[] env, File dir);
+ static Process exec(String[] cmd, String[] env, File dir)
+ throws IOException {
+ return VMProcess.exec(cmd, env, dir);
+ }
/**
* Get the system properties. This is done here, instead of in System,