1 /* java_lang_VMProcess.c -- native code for java.lang.VMProcess
2 Copyright (C) 1998, 1999, 2000, 2002, 2004, 2005 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
37
38 #include <config.h>
39
40 #include "java_lang_VMProcess.h"
41 #include "gnu_java_nio_FileChannelImpl.h"
42
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #include <signal.h>
46 #include <stdlib.h>
47 #include <unistd.h>
48 #include <string.h>
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <stdio.h>
52
53 #include "cpnative.h"
54 #include "cpproc.h"
55
56 /* Internal functions */
57 static char *copy_string (JNIEnv * env, jobject string);
58 static char *copy_elem (JNIEnv * env, jobject stringArray, jint i);
59
60 /*
61 * Internal helper function to copy a String in UTF-8 format.
62 */
63 static char *
copy_string(JNIEnv * env,jobject string)64 copy_string (JNIEnv * env, jobject string)
65 {
66 const char *utf;
67 jclass clazz;
68 char *copy;
69
70 /* Check for null */
71 if (string == NULL)
72 {
73 clazz = (*env)->FindClass (env, "java/lang/NullPointerException");
74 if ((*env)->ExceptionOccurred (env))
75 return NULL;
76 (*env)->ThrowNew (env, clazz, NULL);
77 (*env)->DeleteLocalRef (env, clazz);
78 return NULL;
79 }
80
81 /* Extract UTF-8 */
82 utf = (*env)->GetStringUTFChars (env, string, NULL);
83 if ((*env)->ExceptionOccurred (env))
84 return NULL;
85
86 /* Copy it */
87 if ((copy = strdup (utf)) == NULL)
88 {
89 clazz = (*env)->FindClass (env, "java/lang/InternalError");
90 if ((*env)->ExceptionOccurred (env))
91 return NULL;
92 (*env)->ThrowNew (env, clazz, "strdup returned NULL");
93 (*env)->DeleteLocalRef (env, clazz);
94 }
95
96 /* Done */
97 (*env)->ReleaseStringUTFChars (env, string, utf);
98 return copy;
99 }
100
101 /*
102 * Internal helper function to copy a String[] element in UTF-8 format.
103 */
104 static char *
copy_elem(JNIEnv * env,jobject stringArray,jint i)105 copy_elem (JNIEnv * env, jobject stringArray, jint i)
106 {
107 jobject elem;
108 char *rtn;
109
110 elem = (*env)->GetObjectArrayElement (env, stringArray, i);
111 if ((*env)->ExceptionOccurred (env))
112 return NULL;
113 if ((rtn = copy_string (env, elem)) == NULL)
114 return NULL;
115 (*env)->DeleteLocalRef (env, elem);
116 return rtn;
117 }
118
119 /*
120 * private final native void nativeSpawn(String[], String[], File)
121 * throws java/io/IOException
122 */
123 JNIEXPORT void JNICALL
Java_java_lang_VMProcess_nativeSpawn(JNIEnv * env,jobject this,jobjectArray cmdArray,jobjectArray envArray,jobject dirFile,jboolean redirect)124 Java_java_lang_VMProcess_nativeSpawn (JNIEnv * env, jobject this,
125 jobjectArray cmdArray,
126 jobjectArray envArray, jobject dirFile,
127 jboolean redirect)
128 {
129 int fds[CPIO_EXEC_NUM_PIPES];
130 jobject streams[CPIO_EXEC_NUM_PIPES] = { NULL, NULL, NULL };
131 jobject dirString = NULL;
132 char **newEnviron = NULL;
133 jsize cmdArrayLen = 0;
134 jsize envArrayLen = 0;
135 char **strings = NULL;
136 int num_strings = 0;
137 char *dir = NULL;
138 pid_t pid = -1;
139 char errbuf[64];
140 jmethodID method, vmmethod;
141 jclass clazz, vmclazz;
142 int i;
143 int pipe_count = redirect ? 2 : 3;
144 int err;
145
146 /* Check for null */
147 if (cmdArray == NULL)
148 goto null_pointer_exception;
149
150 /* Invoke dirFile.getPath() */
151 if (dirFile != NULL)
152 {
153 clazz = (*env)->FindClass (env, "java/io/File");
154 if ((*env)->ExceptionOccurred (env))
155 return;
156 method = (*env)->GetMethodID (env,
157 clazz, "getPath", "()Ljava/lang/String;");
158 if ((*env)->ExceptionOccurred (env))
159 return;
160 dirString = (*env)->CallObjectMethod (env, dirFile, method);
161 if ((*env)->ExceptionOccurred (env))
162 return;
163 (*env)->DeleteLocalRef (env, clazz);
164 }
165
166 /*
167 * Allocate array of C strings. We put all the C strings we need to
168 * handle the command parameters, the new environment, and the new
169 * directory into a single array for simplicity of (de)allocation.
170 */
171 cmdArrayLen = (*env)->GetArrayLength (env, cmdArray);
172 if (cmdArrayLen == 0)
173 goto null_pointer_exception;
174 if (envArray != NULL)
175 envArrayLen = (*env)->GetArrayLength (env, envArray);
176 if ((strings = malloc (((cmdArrayLen + 1)
177 + (envArray != NULL ? envArrayLen + 1 : 0)
178 + (dirString !=
179 NULL ? 1 : 0)) * sizeof (*strings))) == NULL)
180 {
181 strncpy (errbuf, "malloc failed", sizeof(errbuf));
182 goto out_of_memory;
183 }
184
185 /* Extract C strings from the various String parameters */
186 for (i = 0; i < cmdArrayLen; i++)
187 {
188 if ((strings[num_strings++] = copy_elem (env, cmdArray, i)) == NULL)
189 goto done;
190 }
191 strings[num_strings++] = NULL; /* terminate array with NULL */
192 if (envArray != NULL)
193 {
194 newEnviron = strings + num_strings;
195 for (i = 0; i < envArrayLen; i++)
196 {
197 if ((strings[num_strings++] = copy_elem (env, envArray, i)) == NULL)
198 goto done;
199 }
200 strings[num_strings++] = NULL; /* terminate array with NULL */
201 }
202 if (dirString != NULL)
203 {
204 if ((dir = copy_string (env, dirString)) == NULL)
205 goto done;
206 }
207
208 /* Create inter-process pipes */
209 err = cpproc_forkAndExec(strings, newEnviron, fds, pipe_count, &pid, dir);
210 if (err != 0)
211 {
212 strncpy(errbuf, cpnative_getErrorString (err), sizeof(errbuf));
213 goto system_error;
214 }
215
216 /* Create Input/OutputStream objects around parent file descriptors */
217 vmclazz = (*env)->FindClass (env, "gnu/java/nio/VMChannel");
218 clazz = (*env)->FindClass (env, "gnu/java/nio/FileChannelImpl");
219 if ((*env)->ExceptionOccurred (env))
220 goto done;
221 vmmethod = (*env)->GetMethodID (env, vmclazz, "<init>", "(I)V");
222 method = (*env)->GetMethodID (env, clazz, "<init>", "(Lgnu/java/nio/VMChannel;I)V");
223 if ((*env)->ExceptionOccurred (env))
224 goto done;
225 for (i = 0; i < pipe_count; i++)
226 {
227 /* Mode is WRITE (2) for in and READ (1) for out and err. */
228 const int fd = fds[i];
229 const int mode = ((i == CPIO_EXEC_STDIN) ? 2 : 1);
230 jclass sclazz;
231 jmethodID smethod;
232
233 jobject vmchannel;
234 jobject channel;
235 vmchannel = (*env)->NewObject (env, vmclazz, vmmethod, fd);
236 if ((*env)->ExceptionOccurred (env))
237 goto done;
238 channel = (*env)->NewObject (env, clazz, method, vmchannel, mode);
239 if ((*env)->ExceptionOccurred (env))
240 goto done;
241
242 if (mode == gnu_java_nio_FileChannelImpl_WRITE)
243 sclazz = (*env)->FindClass (env, "java/io/FileOutputStream");
244 else
245 sclazz = (*env)->FindClass (env, "java/io/FileInputStream");
246 if ((*env)->ExceptionOccurred (env))
247 goto done;
248
249 smethod = (*env)->GetMethodID (env, sclazz, "<init>",
250 "(Lgnu/java/nio/FileChannelImpl;)V");
251 if ((*env)->ExceptionOccurred (env))
252 goto done;
253
254 streams[i] = (*env)->NewObject (env, sclazz, smethod, channel);
255 if ((*env)->ExceptionOccurred (env))
256 goto done;
257
258 (*env)->DeleteLocalRef (env, sclazz);
259 }
260 (*env)->DeleteLocalRef (env, clazz);
261
262 /* Invoke VMProcess.setProcessInfo() to update VMProcess object */
263 method = (*env)->GetMethodID (env,
264 (*env)->GetObjectClass (env, this),
265 "setProcessInfo",
266 "(Ljava/io/OutputStream;Ljava/io/InputStream;Ljava/io/InputStream;J)V");
267 if ((*env)->ExceptionOccurred (env))
268 goto done;
269 (*env)->CallVoidMethod (env, this, method,
270 streams[CPIO_EXEC_STDIN],
271 streams[CPIO_EXEC_STDOUT],
272 streams[CPIO_EXEC_STDERR],
273 (jlong) pid);
274 if ((*env)->ExceptionOccurred (env))
275 goto done;
276
277 done:
278 /*
279 * We get here in both the success and failure cases in the
280 * parent process. Our goal is to clean up the mess we created.
281 */
282
283 /*
284 * Close parent's ends of pipes if Input/OutputStreams never got created.
285 * This can only happen in a failure case. If a Stream object
286 * was created for a file descriptor, we don't close it because it
287 * will get closed when the Stream object is finalized.
288 */
289 for (i = 0; i < pipe_count; i++)
290 {
291 const int fd = fds[i];
292
293 if (fd != -1 && streams[i] == NULL)
294 close (fd);
295 }
296
297 /* Free C strings */
298 while (num_strings > 0)
299 free (strings[--num_strings]);
300 free (strings);
301 if (dir != NULL)
302 free(dir);
303 /* Done */
304 return;
305
306 null_pointer_exception:
307 clazz = (*env)->FindClass (env, "java/lang/NullPointerException");
308 if ((*env)->ExceptionOccurred (env))
309 goto done;
310 (*env)->ThrowNew (env, clazz, NULL);
311 (*env)->DeleteLocalRef (env, clazz);
312 goto done;
313
314 out_of_memory:
315 clazz = (*env)->FindClass (env, "java/lang/InternalError");
316 if ((*env)->ExceptionOccurred (env))
317 goto done;
318 (*env)->ThrowNew (env, clazz, errbuf);
319 (*env)->DeleteLocalRef (env, clazz);
320 goto done;
321
322 system_error:
323 clazz = (*env)->FindClass (env, "java/io/IOException");
324 if ((*env)->ExceptionOccurred (env))
325 goto done;
326 (*env)->ThrowNew (env, clazz, errbuf);
327 (*env)->DeleteLocalRef (env, clazz);
328 goto done;
329 }
330
331 /*
332 * private static final native boolean nativeReap()
333 */
334 JNIEXPORT jboolean JNICALL
Java_java_lang_VMProcess_nativeReap(JNIEnv * env,jclass clazz)335 Java_java_lang_VMProcess_nativeReap (JNIEnv * env, jclass clazz)
336 {
337 char ebuf[64];
338 jfieldID field;
339 jint status;
340 pid_t pid;
341 int err;
342
343 /* Try to reap a child process, but don't block */
344 err = cpproc_waitpid((pid_t)-1, &status, &pid, WNOHANG);
345 if (err == 0 && pid == 0)
346 return JNI_FALSE;
347
348 /* Check result from waitpid() */
349 if (err != 0)
350 {
351 if (err == ECHILD || err == EINTR)
352 return JNI_FALSE;
353 snprintf(ebuf, sizeof (ebuf), "waitpid(%ld): %s",
354 (long) pid, cpnative_getErrorString(errno));
355 clazz = (*env)->FindClass (env, "java/lang/InternalError");
356 if ((*env)->ExceptionOccurred (env))
357 return JNI_FALSE;
358 (*env)->ThrowNew (env, clazz, ebuf);
359 (*env)->DeleteLocalRef (env, clazz);
360 return JNI_FALSE;
361 }
362
363 /* Get exit code; for signal termination return negative signal value XXX */
364 if (WIFEXITED (status))
365 status = (jint) (jbyte) WEXITSTATUS (status);
366 else if (WIFSIGNALED (status))
367 status = -(jint) WTERMSIG (status);
368 else
369 return JNI_FALSE; /* process merely stopped; ignore */
370
371 /* Return process pid and exit status */
372 field = (*env)->GetStaticFieldID (env, clazz, "reapedPid", "J");
373 if ((*env)->ExceptionOccurred (env))
374 return JNI_FALSE;
375 (*env)->SetStaticLongField (env, clazz, field, (jlong) pid);
376 if ((*env)->ExceptionOccurred (env))
377 return JNI_FALSE;
378 field = (*env)->GetStaticFieldID (env, clazz, "reapedExitValue", "I");
379 if ((*env)->ExceptionOccurred (env))
380 return JNI_FALSE;
381 (*env)->SetStaticIntField (env, clazz, field, status);
382 if ((*env)->ExceptionOccurred (env))
383 return JNI_FALSE;
384
385 /* Done */
386 return JNI_TRUE;
387 }
388
389 /*
390 * private static final native void nativeKill(long)
391 */
392 JNIEXPORT void JNICALL
Java_java_lang_VMProcess_nativeKill(JNIEnv * env,jclass clazz,jlong pid)393 Java_java_lang_VMProcess_nativeKill (JNIEnv * env, jclass clazz, jlong pid)
394 {
395 char ebuf[64];
396 int err;
397
398 err = cpproc_kill((pid_t) pid, SIGKILL);
399 if (err != 0)
400 {
401 snprintf (ebuf, sizeof (ebuf), "kill(%ld): %s",
402 (long) pid, cpnative_getErrorString (err));
403 clazz = (*env)->FindClass (env, "java/lang/InternalError");
404 if ((*env)->ExceptionOccurred (env))
405 return;
406 (*env)->ThrowNew (env, clazz, ebuf);
407 (*env)->DeleteLocalRef (env, clazz);
408 }
409 }
410