| /* java_lang_VMProcess.c -- native code for java.lang.VMProcess |
| Copyright (C) 1998, 1999, 2000, 2002, 2004, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 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 "gnu_java_nio_channels_FileChannelImpl.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 <jcl.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); |
| |
| /* |
| * Internal helper function to copy a String in UTF-8 format. |
| */ |
| static char * |
| copy_string (JNIEnv * env, jobject string) |
| { |
| char errbuf[64]; |
| const char *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, |
| jboolean redirect) |
| { |
| 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; |
| int pipe_count = redirect ? 2 : 3; |
| |
| /* 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 (cmdArrayLen == 0) |
| goto null_pointer_exception; |
| 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 < pipe_count; 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); |
| if (pipe_count == 3) |
| (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 (pipe_count == 2) |
| { |
| /* Duplicate stdout to stderr. */ |
| if (dup2 (1, 2) == -1) |
| { |
| fprintf (stderr, "dup2: %s", strerror (errno)); |
| exit (127); |
| } |
| } |
| else 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 < pipe_count; 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) |
| ? gnu_java_nio_channels_FileChannelImpl_WRITE |
| : gnu_java_nio_channels_FileChannelImpl_READ); |
| jclass sclazz; |
| jmethodID smethod; |
| |
| jobject channel = (*env)->NewObject (env, clazz, method, fd, mode); |
| if ((*env)->ExceptionOccurred (env)) |
| goto done; |
| |
| if (mode == gnu_java_nio_channels_FileChannelImpl_WRITE) |
| 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; |
| |
| 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 < pipe_count; 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 < pipe_count; 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); |
| } |
| } |