1 /* java.lang.VMProcess -- VM implementation of java.lang.Process
2    Copyright (C) 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 package java.lang;
39 
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.OutputStream;
44 import java.util.HashMap;
45 import java.util.Iterator;
46 import java.util.LinkedList;
47 import java.util.List;
48 import java.util.Map;
49 
50 /**
51  * Represents one external process. Each instance of this class is in
52  * one of three states: INITIAL, RUNNING, or TERMINATED. The instance
53  * is {@link Object#notifyAll notifyAll()}'d each time the state changes.
54  * The state of all instances is managed by a single dedicated thread
55  * which does the actual fork()/exec() and wait() system calls. User
56  * threads {@link Object#wait()} on the instance when creating the
57  * process or waiting for it to terminate.
58  *
59  * <p>
60  * See
61  * <a href="http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11801">GCC bug
62  * #11801</a> for the motivation behind the design of this class.
63  *
64  * @author Archie Cobbs
65  * @see Process
66  * @see Runtime#exec(String)
67  */
68 final class VMProcess extends Process
69 {
70 
71   // Possible states for a VMProcess
72   private static final int INITIAL = 0;
73   private static final int RUNNING = 1;
74   private static final int TERMINATED = 2;
75 
76   // Dedicated thread that does all the fork()'ing and wait()'ing.
77   static Thread processThread;
78 
79   // New processes waiting to be spawned by processThread.
80   static final LinkedList workList = new LinkedList();
81 
82   // Return values set by nativeReap() when a child is reaped.
83   // These are only accessed by processThread so no locking required.
84   static long reapedPid;
85   static int reapedExitValue;
86 
87   // Information about this process
88   int state;                                   // current state of process
89   final String[] cmd;                          // copied from Runtime.exec()
90   final String[] env;                          // copied from Runtime.exec()
91   final File dir;                              // copied from Runtime.exec()
92   Throwable exception;                         // if process failed to start
93   long pid;                                    // process id
94   OutputStream stdin;                          // process input stream
95   InputStream stdout;                          // process output stream
96   InputStream stderr;                          // process error stream
97   int exitValue;                               // process exit value
98   boolean redirect;                            // redirect stderr -> stdout
99 
100   //
101   // Dedicated thread that does all the fork()'ing and wait()'ing
102   // for external processes. This is needed because some systems like
103   // Linux use a process-per-thread model, which means the same thread
104   // that did the fork()/exec() must also do the wait().
105   //
106   private static class ProcessThread extends Thread
107   {
108 
109     // Max time (in ms) we'll delay before trying to reap another child.
110     private static final int MAX_REAP_DELAY = 1000;
111 
112     // Processes created but not yet terminated; maps Long(pid) -> VMProcess
113     // Only used in run() and spawn() method from this Thread, so no locking.
114     private final HashMap activeMap = new HashMap();
115 
116     // We have an explicit constructor, because the default
117     // constructor will be private, which means the compiler will have
118     // to generate a second package-private constructor, which is
119     // bogus.
ProcessThread()120     ProcessThread ()
121     {
122     }
123 
run()124     public void run()
125     {
126       final LinkedList workList = VMProcess.workList;
127       while (true)
128         {
129 
130           // Get the next process to spawn (if any) and spawn it. Spawn
131           // at most one at a time before checking for reapable children.
132           VMProcess process = null;
133           synchronized (workList)
134             {
135               if (!workList.isEmpty())
136                 process = (VMProcess)workList.removeFirst();
137             }
138 
139           if (process != null)
140             spawn(process);
141 
142 
143           // Check for termination of active child processes
144           while (!activeMap.isEmpty() && VMProcess.nativeReap())
145             {
146               long pid = VMProcess.reapedPid;
147               int exitValue = VMProcess.reapedExitValue;
148               process = (VMProcess)activeMap.remove(new Long(pid));
149               if (process != null)
150                 {
151                   synchronized (process)
152                     {
153                       process.exitValue = exitValue;
154                       process.state = TERMINATED;
155                       process.notify();
156                     }
157                 }
158               else
159                 System.err.println("VMProcess WARNING reaped unknown process: "
160                                    + pid);
161             }
162 
163 
164           // If there are more new processes to create, go do that now.
165           // If there is nothing left to do, exit this thread. Otherwise,
166           // sleep a little while, and then check again for reapable children.
167           // We will get woken up immediately if there are new processes to
168           // spawn, but not if there are new children to reap. So we only
169           // sleep a short time, in effect polling while processes are active.
170           synchronized (workList)
171             {
172               if (!workList.isEmpty())
173                 continue;
174               if (activeMap.isEmpty())
175                 {
176                   processThread = null;
177                   break;
178                 }
179 
180               try
181                 {
182                   workList.wait(MAX_REAP_DELAY);
183                 }
184               catch (InterruptedException e)
185                 {
186                   /* ignore */
187                 }
188             }
189         }
190     }
191 
192     // Spawn a process
spawn(VMProcess process)193     private void spawn(VMProcess process)
194     {
195 
196       // Spawn the process and put it in our active map indexed by pid.
197       // If the spawn operation fails, store the exception with the process.
198       // In either case, wake up thread that created the process.
199       synchronized (process)
200         {
201           try
202             {
203               process.nativeSpawn(process.cmd, process.env, process.dir,
204                                   process.redirect);
205               process.state = RUNNING;
206               activeMap.put(new Long(process.pid), process);
207             }
208           catch (ThreadDeath death)
209             {
210               throw death;
211             }
212           catch (Throwable t)
213             {
214               process.state = TERMINATED;
215               process.exception = t;
216             }
217           process.notify();
218         }
219     }
220   }
221 
222   // Constructor
VMProcess(String[] cmd, String[] env, File dir, boolean redirect)223   private VMProcess(String[] cmd, String[] env, File dir, boolean redirect)
224     throws IOException
225   {
226 
227     // Initialize this process
228     this.state = INITIAL;
229     this.cmd = cmd;
230     this.env = env;
231     this.dir = dir;
232     this.redirect = redirect;
233 
234     // Add process to the new process work list and wakeup processThread
235     synchronized (workList)
236       {
237         workList.add(this);
238         if (processThread == null)
239           {
240             processThread = new ProcessThread();
241             processThread.setDaemon(true);
242             processThread.start();
243           }
244         else
245           {
246             workList.notify();
247           }
248       }
249 
250     // Wait for processThread to spawn this process and update its state
251     synchronized (this)
252       {
253         while (state == INITIAL)
254           {
255             try
256               {
257                 wait();
258               }
259             catch (InterruptedException e)
260               {
261                 /* ignore */
262               }
263           }
264       }
265 
266     // If spawning failed, rethrow the exception in this thread
267     if (exception != null)
268       {
269         exception.fillInStackTrace();
270         if (exception instanceof IOException)
271           throw (IOException)exception;
272 
273         if (exception instanceof Error)
274           throw (Error)exception;
275 
276         if (exception instanceof RuntimeException)
277           throw (RuntimeException)exception;
278 
279         throw new RuntimeException(exception);
280       }
281   }
282 
283   // Invoked by native code (from nativeSpawn()) to record process info.
setProcessInfo(OutputStream stdin, InputStream stdout, InputStream stderr, long pid)284   private void setProcessInfo(OutputStream stdin,
285                               InputStream stdout, InputStream stderr, long pid)
286   {
287     this.stdin = stdin;
288     this.stdout = stdout;
289     if (stderr == null)
290       this.stderr = new InputStream()
291         {
292           public int read() throws IOException
293           {
294             return -1;
295           }
296         };
297     else
298       this.stderr = stderr;
299     this.pid = pid;
300   }
301 
302   /**
303    * Entry point from Runtime.exec().
304    */
exec(String[] cmd, String[] env, File dir)305   static Process exec(String[] cmd, String[] env, File dir) throws IOException
306   {
307     return new VMProcess(cmd, env, dir, false);
308   }
309 
exec(List cmd, Map env, File dir, boolean redirect)310   static Process exec(List cmd, Map env,
311                       File dir, boolean redirect) throws IOException
312   {
313     String[] acmd = (String[]) cmd.toArray(new String[cmd.size()]);
314     String[] aenv = new String[env.size()];
315 
316     int i = 0;
317     Iterator iter = env.entrySet().iterator();
318     while (iter.hasNext())
319       {
320         Map.Entry entry = (Map.Entry) iter.next();
321         aenv[i++] = entry.getKey() + "=" + entry.getValue();
322       }
323 
324     return new VMProcess(acmd, aenv, dir, redirect);
325   }
326 
getOutputStream()327   public OutputStream getOutputStream()
328   {
329     return stdin;
330   }
331 
getInputStream()332   public InputStream getInputStream()
333   {
334     return stdout;
335   }
336 
getErrorStream()337   public InputStream getErrorStream()
338   {
339     return stderr;
340   }
341 
waitFor()342   public synchronized int waitFor() throws InterruptedException
343   {
344     while (state != TERMINATED)
345       wait();
346     return exitValue;
347   }
348 
exitValue()349   public synchronized int exitValue()
350   {
351     if (state != TERMINATED)
352       throw new IllegalThreadStateException();
353     return exitValue;
354   }
355 
destroy()356   public synchronized void destroy()
357   {
358     if (state == TERMINATED)
359       return;
360 
361     nativeKill(pid);
362 
363     while (state != TERMINATED)
364       {
365         try
366           {
367             wait();
368           }
369         catch (InterruptedException e)
370           {
371             /* ignore */
372           }
373       }
374   }
375 
376   /**
377    * Does the fork()/exec() thing to create the O/S process.
378    * Must invoke setProcessInfo() before returning successfully.
379    * This method is only invoked by processThread.
380    *
381    * @throws IOException if the O/S process could not be created.
382    */
nativeSpawn(String[] cmd, String[] env, File dir, boolean redirect)383   native void nativeSpawn(String[] cmd, String[] env, File dir,
384                           boolean redirect)
385     throws IOException;
386 
387   /**
388    * Test for a reapable child process, and reap if so. Does not block.
389    * If a child was reaped, this method must set reapedPid and
390    * reapedExitValue appropriately before returning.
391    * This method is only invoked by processThread.
392    *
393    * @return true if a child was reaped, otherwise false
394    */
395   // This is not private as it is called from an inner class.
nativeReap()396   static native boolean nativeReap();
397 
398   /**
399    * Kill a process. This sends it a fatal signal but does not reap it.
400    */
nativeKill(long pid)401   private static native void nativeKill(long pid);
402 }
403