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