| /* java.lang.VMProcess -- VM implementation of java.lang.Process |
| Copyright (C) 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. */ |
| |
| package java.lang; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Represents one external process. Each instance of this class is in |
| * one of three states: INITIAL, RUNNING, or TERMINATED. The instance |
| * is {@link Object#notifyAll notifyAll()}'d each time the state changes. |
| * The state of all instances is managed by a single dedicated thread |
| * which does the actual fork()/exec() and wait() system calls. User |
| * threads {@link Object#wait()} on the instance when creating the |
| * process or waiting for it to terminate. |
| * |
| * <p> |
| * See |
| * <a href="http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11801">GCC bug |
| * #11801</a> for the motivation behind the design of this class. |
| * |
| * @author Archie Cobbs |
| * @see Process |
| * @see Runtime#exec(String) |
| */ |
| final class VMProcess extends Process |
| { |
| |
| // Possible states for a VMProcess |
| private static final int INITIAL = 0; |
| private static final int RUNNING = 1; |
| private static final int TERMINATED = 2; |
| |
| // Dedicated thread that does all the fork()'ing and wait()'ing. |
| static Thread processThread; |
| |
| // New processes waiting to be spawned by processThread. |
| static final LinkedList workList = new LinkedList(); |
| |
| // Return values set by nativeReap() when a child is reaped. |
| // These are only accessed by processThread so no locking required. |
| static long reapedPid; |
| static int reapedExitValue; |
| |
| // Information about this process |
| int state; // current state of process |
| final String[] cmd; // copied from Runtime.exec() |
| final String[] env; // copied from Runtime.exec() |
| final File dir; // copied from Runtime.exec() |
| Throwable exception; // if process failed to start |
| long pid; // process id |
| OutputStream stdin; // process input stream |
| InputStream stdout; // process output stream |
| InputStream stderr; // process error stream |
| int exitValue; // process exit value |
| boolean redirect; // redirect stderr -> stdout |
| |
| // |
| // Dedicated thread that does all the fork()'ing and wait()'ing |
| // for external processes. This is needed because some systems like |
| // Linux use a process-per-thread model, which means the same thread |
| // that did the fork()/exec() must also do the wait(). |
| // |
| private static class ProcessThread extends Thread |
| { |
| |
| // Max time (in ms) we'll delay before trying to reap another child. |
| private static final int MAX_REAP_DELAY = 1000; |
| |
| // Processes created but not yet terminated; maps Long(pid) -> VMProcess |
| // Only used in run() and spawn() method from this Thread, so no locking. |
| private final HashMap activeMap = new HashMap(); |
| |
| // We have an explicit constructor, because the default |
| // constructor will be private, which means the compiler will have |
| // to generate a second package-private constructor, which is |
| // bogus. |
| ProcessThread () |
| { |
| } |
| |
| public void run() |
| { |
| final LinkedList workList = VMProcess.workList; |
| while (true) |
| { |
| |
| // Get the next process to spawn (if any) and spawn it. Spawn |
| // at most one at a time before checking for reapable children. |
| VMProcess process = null; |
| synchronized (workList) |
| { |
| if (!workList.isEmpty()) |
| process = (VMProcess)workList.removeFirst(); |
| } |
| |
| if (process != null) |
| spawn(process); |
| |
| |
| // Check for termination of active child processes |
| while (!activeMap.isEmpty() && VMProcess.nativeReap()) |
| { |
| long pid = VMProcess.reapedPid; |
| int exitValue = VMProcess.reapedExitValue; |
| process = (VMProcess)activeMap.remove(new Long(pid)); |
| if (process != null) |
| { |
| synchronized (process) |
| { |
| process.exitValue = exitValue; |
| process.state = TERMINATED; |
| process.notify(); |
| } |
| } |
| else |
| System.err.println("VMProcess WARNING reaped unknown process: " |
| + pid); |
| } |
| |
| |
| // If there are more new processes to create, go do that now. |
| // If there is nothing left to do, exit this thread. Otherwise, |
| // sleep a little while, and then check again for reapable children. |
| // We will get woken up immediately if there are new processes to |
| // spawn, but not if there are new children to reap. So we only |
| // sleep a short time, in effect polling while processes are active. |
| synchronized (workList) |
| { |
| if (!workList.isEmpty()) |
| continue; |
| if (activeMap.isEmpty()) |
| { |
| processThread = null; |
| break; |
| } |
| |
| try |
| { |
| workList.wait(MAX_REAP_DELAY); |
| } |
| catch (InterruptedException e) |
| { |
| /* ignore */ |
| } |
| } |
| } |
| } |
| |
| // Spawn a process |
| private void spawn(VMProcess process) |
| { |
| |
| // Spawn the process and put it in our active map indexed by pid. |
| // If the spawn operation fails, store the exception with the process. |
| // In either case, wake up thread that created the process. |
| synchronized (process) |
| { |
| try |
| { |
| process.nativeSpawn(process.cmd, process.env, process.dir, |
| process.redirect); |
| process.state = RUNNING; |
| activeMap.put(new Long(process.pid), process); |
| } |
| catch (ThreadDeath death) |
| { |
| throw death; |
| } |
| catch (Throwable t) |
| { |
| process.state = TERMINATED; |
| process.exception = t; |
| } |
| process.notify(); |
| } |
| } |
| } |
| |
| // Constructor |
| private VMProcess(String[] cmd, String[] env, File dir, boolean redirect) |
| throws IOException |
| { |
| |
| // Initialize this process |
| this.state = INITIAL; |
| this.cmd = cmd; |
| this.env = env; |
| this.dir = dir; |
| this.redirect = redirect; |
| |
| // Add process to the new process work list and wakeup processThread |
| synchronized (workList) |
| { |
| workList.add(this); |
| if (processThread == null) |
| { |
| processThread = new ProcessThread(); |
| processThread.setDaemon(true); |
| processThread.start(); |
| } |
| else |
| { |
| workList.notify(); |
| } |
| } |
| |
| // Wait for processThread to spawn this process and update its state |
| synchronized (this) |
| { |
| while (state == INITIAL) |
| { |
| try |
| { |
| wait(); |
| } |
| catch (InterruptedException e) |
| { |
| /* ignore */ |
| } |
| } |
| } |
| |
| // If spawning failed, rethrow the exception in this thread |
| if (exception != null) |
| { |
| exception.fillInStackTrace(); |
| if (exception instanceof IOException) |
| throw (IOException)exception; |
| |
| if (exception instanceof Error) |
| throw (Error)exception; |
| |
| if (exception instanceof RuntimeException) |
| throw (RuntimeException)exception; |
| |
| throw new RuntimeException(exception); |
| } |
| } |
| |
| // Invoked by native code (from nativeSpawn()) to record process info. |
| private void setProcessInfo(OutputStream stdin, |
| InputStream stdout, InputStream stderr, long pid) |
| { |
| this.stdin = stdin; |
| this.stdout = stdout; |
| if (stderr == null) |
| this.stderr = new InputStream() |
| { |
| public int read() throws IOException |
| { |
| return -1; |
| } |
| }; |
| else |
| this.stderr = stderr; |
| this.pid = pid; |
| } |
| |
| /** |
| * Entry point from Runtime.exec(). |
| */ |
| static Process exec(String[] cmd, String[] env, File dir) throws IOException |
| { |
| return new VMProcess(cmd, env, dir, false); |
| } |
| |
| static Process exec(List cmd, Map env, |
| File dir, boolean redirect) throws IOException |
| { |
| String[] acmd = (String[]) cmd.toArray(new String[cmd.size()]); |
| String[] aenv = new String[env.size()]; |
| |
| int i = 0; |
| Iterator iter = env.entrySet().iterator(); |
| while (iter.hasNext()) |
| { |
| Map.Entry entry = (Map.Entry) iter.next(); |
| aenv[i++] = entry.getKey() + "=" + entry.getValue(); |
| } |
| |
| return new VMProcess(acmd, aenv, dir, redirect); |
| } |
| |
| public OutputStream getOutputStream() |
| { |
| return stdin; |
| } |
| |
| public InputStream getInputStream() |
| { |
| return stdout; |
| } |
| |
| public InputStream getErrorStream() |
| { |
| return stderr; |
| } |
| |
| public synchronized int waitFor() throws InterruptedException |
| { |
| while (state != TERMINATED) |
| wait(); |
| return exitValue; |
| } |
| |
| public synchronized int exitValue() |
| { |
| if (state != TERMINATED) |
| throw new IllegalThreadStateException(); |
| return exitValue; |
| } |
| |
| public synchronized void destroy() |
| { |
| if (state == TERMINATED) |
| return; |
| |
| nativeKill(pid); |
| |
| while (state != TERMINATED) |
| { |
| try |
| { |
| wait(); |
| } |
| catch (InterruptedException e) |
| { |
| /* ignore */ |
| } |
| } |
| } |
| |
| /** |
| * Does the fork()/exec() thing to create the O/S process. |
| * Must invoke setProcessInfo() before returning successfully. |
| * This method is only invoked by processThread. |
| * |
| * @throws IOException if the O/S process could not be created. |
| */ |
| native void nativeSpawn(String[] cmd, String[] env, File dir, |
| boolean redirect) |
| throws IOException; |
| |
| /** |
| * Test for a reapable child process, and reap if so. Does not block. |
| * If a child was reaped, this method must set reapedPid and |
| * reapedExitValue appropriately before returning. |
| * This method is only invoked by processThread. |
| * |
| * @return true if a child was reaped, otherwise false |
| */ |
| // This is not private as it is called from an inner class. |
| static native boolean nativeReap(); |
| |
| /** |
| * Kill a process. This sends it a fatal signal but does not reap it. |
| */ |
| private static native void nativeKill(long pid); |
| } |