1 // PosixProcess.java - Subclass of Process for POSIX systems.
2 /* Copyright (C) 1998, 1999, 2004, 2006, 2007  Free Software Foundation
3 
4    This file is part of libgcj.
5 
6 This software is copyrighted work licensed under the terms of the
7 Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
8 details.  */
9 
10 package java.lang;
11 
12 import java.io.File;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.OutputStream;
16 import java.util.Iterator;
17 import java.util.LinkedList;
18 
19 import gnu.gcj.RawDataManaged;
20 
21 /**
22  * @author Tom Tromey <tromey@cygnus.com>
23  * @date May 3, 1999
24  * @author David Daney <ddaney@avtrex.com> Rewrote using
25  * ProcessManager
26  */
27 final class PosixProcess extends Process
28 {
29   static final class ProcessManager extends Thread
30   {
31     /**
32      * A list of {@link PosixProcess PosixProcesses} to be
33      * started.  The queueLock object is used as the lock Object
34      * for all process related operations. To avoid dead lock
35      * ensure queueLock is obtained before PosixProcess.
36      */
37     private LinkedList<PosixProcess> queue = new LinkedList<PosixProcess>();
38     private LinkedList<PosixProcess> liveProcesses =
39       new LinkedList<PosixProcess>();
40     private boolean ready = false;
41 
42     static RawDataManaged nativeData;
43 
ProcessManager()44     ProcessManager()
45     {
46       // Use package private Thread constructor to place us in the
47       // root ThreadGroup with no InheritableThreadLocal.  If the
48       // InheritableThreadLocals were allowed to initialize, they could
49       // cause a Runtime.exec() to be called causing infinite
50       // recursion.
51       super("ProcessManager", true);
52       // Don't keep the (main) process from exiting on our account.
53       this.setDaemon(true);
54     }
55 
56     /**
57      * Add a process to the list of running processes.  This must only
58      * be called with the queueLock held.
59      *
60      * @param p The PosixProcess.
61      */
addToLiveProcesses(PosixProcess p)62     void addToLiveProcesses(PosixProcess p)
63     {
64       liveProcesses.add(p);
65     }
66 
67     /**
68      * Queue up the PosixProcess and awake the ProcessManager.
69      * The ProcessManager will start the PosixProcess from its
70      * thread so it can be reaped when it terminates.
71      *
72      * @param p The PosixProcess.
73      */
startExecuting(PosixProcess p)74     void startExecuting(PosixProcess p)
75     {
76       synchronized (queueLock)
77         {
78 	  queue.add(p);
79 	  signalReaper(); // If blocked in waitForSignal().
80 	  queueLock.notifyAll(); // If blocked in wait();
81         }
82     }
83 
84     /**
85      * Block until the ProcessManager thread is ready to accept
86      * commands.
87      */
waitUntilReady()88     void waitUntilReady()
89     {
90       synchronized (this)
91         {
92 	  try
93 	    {
94 	      while (! ready)
95 		wait();
96 	    }
97 	  catch (InterruptedException ie)
98 	    {
99 	      // Ignore.
100 	    }
101         }
102     }
103 
104     /**
105      * Main Process starting/reaping loop.
106      */
run()107     public void run()
108     {
109       init();
110       // Now ready to accept requests.
111       synchronized (this)
112         {
113           ready = true;
114           this.notifyAll();
115         }
116 
117       for (;;)
118         {
119           try
120             {
121               synchronized (queueLock)
122                 {
123                   Iterator<PosixProcess> processIterator =
124                     liveProcesses.iterator();
125                   while (processIterator.hasNext())
126                     {
127                       boolean reaped = reap(processIterator.next());
128                       if (reaped)
129                         processIterator.remove();
130                     }
131                   if (liveProcesses.size() == 0 && queue.size() == 0)
132                     {
133                       // This reaper thread could exit, but we keep it
134                       // alive for a while in case someone wants to
135                       // start more Processes.
136                       try
137                         {
138                           queueLock.wait(1000L);
139                           if (queue.size() == 0)
140                             {
141                               processManager = null;
142                               return; // Timed out.
143                             }
144                         }
145                       catch (InterruptedException ie)
146                         {
147                           // Ignore and exit the thread.
148                           return;
149                         }
150                     }
151                   while (queue.size() > 0)
152                     {
153                       PosixProcess p = queue.remove(0);
154                       p.spawn(this);
155                     }
156                 }
157 
158               // Wait for a SIGCHLD from either an exiting process or
159               // the startExecuting() method.  This is done outside of
160               // the synchronized block to allow other threads to
161               // enter and submit more jobs.
162               waitForSignal();
163             }
164           catch (Exception ex)
165             {
166               ex.printStackTrace(System.err);
167             }
168         }
169     }
170 
171     /**
172      * Setup native signal handlers and other housekeeping things.
173      */
init()174     private native void init();
175 
176     /**
177      * Block waiting for SIGCHLD.
178      *
179      */
waitForSignal()180     private native void waitForSignal();
181 
182     /**
183      * Try to reap the specified child without blocking.
184      *
185      * @param p the process to try to reap.
186      *
187      * @return true if the process terminated.
188      *
189      */
reap(PosixProcess p)190     private native boolean reap(PosixProcess p);
191 
192     /**
193      * Send SIGCHLD to the reaper thread.
194      */
signalReaper()195     private native void signalReaper();
196   }
197 
destroy()198   public void destroy()
199   {
200     // Synchronized on the queueLock.  This ensures that the reaper
201     // thread cannot be doing a wait() on the child.
202     // Otherwise there would be a race where the OS could
203     // create a process with the same pid between the wait()
204     // and the update of the state which would cause a kill to
205     // the wrong process.
206     synchronized (queueLock)
207       {
208 	synchronized (this)
209 	  {
210 	    // If there is no ProcessManager we cannot kill.
211 	    if (state != STATE_TERMINATED)
212 	      {
213 		if (processManager == null)
214 		  throw new InternalError();
215 		nativeDestroy();
216 	      }
217 	  }
218       }
219   }
220 
nativeDestroy()221   private native void nativeDestroy();
222 
exitValue()223   public int exitValue()
224   {
225     synchronized (this)
226       {
227 	if (state != STATE_TERMINATED)
228 	  throw new IllegalThreadStateException("Process has not exited");
229       }
230     return status;
231   }
232 
233   /**
234    * Called by native code when process exits.
235    *
236    * Already synchronized (this).  Close any streams that we can to
237    * conserve file descriptors.
238    *
239    * The outputStream can be closed as any future writes will
240    * generate an IOException due to EPIPE.
241    *
242    * The inputStream and errorStream can only be closed if the user
243    * has not obtained a reference to them AND they have no bytes
244    * available.  Since the process has terminated they will never have
245    * any more data available and can safely be replaced by
246    * EOFInputStreams.
247    */
processTerminationCleanup()248   void processTerminationCleanup()
249   {
250     try
251       {
252         outputStream.close();
253       }
254     catch (IOException ioe)
255       {
256         // Ignore.
257       }
258     try
259       {
260         if (returnedErrorStream == null && errorStream.available() == 0)
261           {
262             errorStream.close();
263             errorStream = null;
264           }
265       }
266     catch (IOException ioe)
267       {
268         // Ignore.
269       }
270     try
271       {
272         if (returnedInputStream == null && inputStream.available() == 0)
273           {
274             inputStream.close();
275             inputStream = null;
276           }
277       }
278     catch (IOException ioe)
279       {
280         // Ignore.
281       }
282   }
283 
getErrorStream()284   public synchronized InputStream getErrorStream()
285   {
286     if (returnedErrorStream != null)
287       return returnedErrorStream;
288 
289     if (errorStream == null)
290       returnedErrorStream = EOFInputStream.instance;
291     else
292       returnedErrorStream = errorStream;
293 
294     return returnedErrorStream;
295   }
296 
getInputStream()297   public synchronized InputStream getInputStream()
298   {
299     if (returnedInputStream != null)
300       return returnedInputStream;
301 
302     if (inputStream == null)
303       returnedInputStream = EOFInputStream.instance;
304     else
305       returnedInputStream = inputStream;
306 
307     return returnedInputStream;
308   }
309 
getOutputStream()310   public OutputStream getOutputStream()
311   {
312     return outputStream;
313   }
314 
waitFor()315   public int waitFor() throws InterruptedException
316   {
317     synchronized (this)
318       {
319 	while (state != STATE_TERMINATED)
320 	  wait();
321       }
322     return status;
323   }
324 
325   /**
326    * Start this process running.  This should only be called by the
327    * ProcessManager with the queueLock held.
328    *
329    * @param pm The ProcessManager that made the call.
330    */
spawn(ProcessManager pm)331   void spawn(ProcessManager pm)
332   {
333     synchronized (this)
334       {
335 	// Do the fork/exec magic.
336 	nativeSpawn();
337 	// There is no race with reap() in the pidToProcess map
338 	// because this is always called from the same thread
339 	// doing the reaping.
340 	pm.addToLiveProcesses(this);
341 	state = STATE_RUNNING;
342 	// Notify anybody waiting on state change.
343 	this.notifyAll();
344       }
345   }
346 
347   /**
348    * Do the fork and exec.
349    */
nativeSpawn()350   private native void nativeSpawn();
351 
PosixProcess(String[] progarray, String[] envp, File dir, boolean redirect)352   PosixProcess(String[] progarray, String[] envp, File dir, boolean redirect)
353     throws IOException
354   {
355     // Check to ensure there is something to run, and avoid
356     // dereferencing null pointers in native code.
357     if (progarray[0] == null)
358       throw new NullPointerException();
359 
360     this.progarray = progarray;
361     this.envp = envp;
362     this.dir = dir;
363     this.redirect = redirect;
364 
365     // Start a ProcessManager if there is not one already running.
366     synchronized (queueLock)
367       {
368 	if (processManager == null)
369 	  {
370 	    processManager = new ProcessManager();
371 	    processManager.start();
372 	    processManager.waitUntilReady();
373 	  }
374 
375 	// Queue this PosixProcess for starting by the ProcessManager.
376 	processManager.startExecuting(this);
377       }
378 
379     // Wait until ProcessManager has started us.
380     synchronized (this)
381       {
382 	while (state == STATE_WAITING_TO_START)
383 	  {
384 	    try
385 	      {
386 		wait();
387 	      }
388 	    catch (InterruptedException ie)
389 	      {
390 		// FIXME: What to do when interrupted while blocking in a constructor?
391 		// Ignore.
392 	      }
393 	  }
394       }
395 
396     // If there was a problem, re-throw it.
397     if (exception != null)
398       {
399 	if (exception instanceof IOException)
400 	  {
401 	    IOException ioe = new IOException(exception.toString());
402 	    ioe.initCause(exception);
403 	    throw ioe;
404 	  }
405 
406 	// Not an IOException.  Something bad happened.
407 	InternalError ie = new InternalError(exception.toString());
408 	ie.initCause(exception);
409 	throw ie;
410       }
411 
412     // If we get here, all is well, the Process has started.
413   }
414 
415   private String[] progarray;
416   private String[] envp;
417   private File dir;
418   private boolean redirect;
419 
420   /** Set by the ProcessManager on problems starting. */
421   private Throwable exception;
422 
423   /** The process id.  This is cast to a pid_t on the native side. */
424   long pid;
425 
426   // FIXME: Why doesn't the friend declaration in PosixProcess.h
427   // allow PosixProcess$ProcessManager native code access these
428   // when they are private?
429 
430   /** Before the process is forked. */
431   static final int STATE_WAITING_TO_START = 0;
432 
433   /** After the fork. */
434   static final int STATE_RUNNING = 1;
435 
436   /** After exit code has been collected. */
437   static final int STATE_TERMINATED = 2;
438 
439   /** One of STATE_WAITING_TO_START, STATE_RUNNING, STATE_TERMINATED. */
440   int state;
441 
442   /** The exit status, if the child has exited. */
443   int status;
444 
445   /** The streams. */
446   private InputStream errorStream;
447   private InputStream inputStream;
448   private OutputStream outputStream;
449 
450   /** InputStreams obtained by the user.  Not null indicates that the
451    *  user has obtained the stream.
452    */
453   private InputStream returnedErrorStream;
454   private InputStream returnedInputStream;
455 
456   /**
457    * Lock Object for all processManager related locking.
458    */
459   private static Object queueLock = new Object();
460   private static ProcessManager processManager;
461 
462   static class EOFInputStream extends InputStream
463   {
464     static EOFInputStream instance = new EOFInputStream();
read()465     public int read()
466     {
467       return -1;
468     }
469   }
470 }
471