/* * @(#)UNIXProcess.java 1.39 06/10/10 * * Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program 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 version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package java.lang; import java.io.*; /* java.lang.Process subclass in the UNIX environment. * * @author Mario Wolczko and Ross Knippel. */ class UNIXProcess extends Process { private FileDescriptor stdin_fd; private FileDescriptor stdout_fd; private FileDescriptor stderr_fd; private int pid; private int exitcode; private boolean hasExited; private OutputStream stdin_stream; private BufferedInputStream stdout_stream; private DeferredCloseInputStream stdout_inner_stream; private DeferredCloseInputStream stderr_stream; /* this is for the reaping thread */ private native int waitForProcessExit(int pid); private UNIXProcess() {} private native int forkAndExec(String cmd[], String env[], String path, FileDescriptor stdin_fd, FileDescriptor stdout_fd, FileDescriptor stderr_fd) throws java.io.IOException; UNIXProcess(String cmdarray[], String env[]) throws java.io.IOException { this(cmdarray, env, null); } UNIXProcess(String cmdarray[], String env[], String path) throws java.io.IOException { stdin_fd = new FileDescriptor(); stdout_fd = new FileDescriptor(); stderr_fd = new FileDescriptor(); pid = forkAndExec(cmdarray, env, path, stdin_fd, stdout_fd, stderr_fd); java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { stdin_stream = new BufferedOutputStream(new FileOutputStream(stdin_fd)); stdout_inner_stream = new DeferredCloseInputStream(stdout_fd); stdout_stream = new BufferedInputStream(stdout_inner_stream); stderr_stream = new DeferredCloseInputStream(stderr_fd); return null; } }); /* * For each subprocess forked a corresponding reaper thread * is started. That thread is the only thread which waits * for the subprocess to terminate and it doesn't hold any * locks while doing so. This design allows waitFor() and * exitStatus() to be safely executed in parallel (and they * need no native code). */ java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { Thread t = new Thread("process reaper") { public void run() { int res = waitForProcessExit(pid); synchronized (UNIXProcess.this) { hasExited = true; exitcode = res; UNIXProcess.this.notifyAll(); } } }; t.setDaemon(true); t.start(); return null; } }); } public OutputStream getOutputStream() { return stdin_stream; } public InputStream getInputStream() { return stdout_stream; } public InputStream getErrorStream() { return stderr_stream; } public synchronized int waitFor() throws InterruptedException { while (!hasExited) { wait(); } return exitcode; } public synchronized int exitValue() { if (!hasExited) { throw new IllegalThreadStateException("process hasn't exited"); } return exitcode; } private static native void destroyProcess(int pid); public synchronized void destroy() { destroyProcess(pid); try { stdin_stream.close(); stdout_inner_stream.closeDeferred(stdout_stream); stderr_stream.closeDeferred(stderr_stream); } catch (IOException e) { // ignore } } // A FileInputStream that supports the deferment of the actual close // operation until the last pending I/O operation on the stream has // finished. This is required on Solaris because we must close the stdin // and stdout streams in the destroy method in order to reclaim the // underlying file descriptors. Doing so, however, causes any thread // currently blocked in a read on one of those streams to receive an // IOException("Bad file number"), which is incompatible with historical // behavior. By deferring the close we allow any pending reads to see -1 // (EOF) as they did before. // private static class DeferredCloseInputStream extends FileInputStream { private DeferredCloseInputStream(FileDescriptor fd) { super(fd); } private Object lock = new Object(); // For the following fields private boolean closePending = false; private int useCount = 0; private InputStream streamToClose; private void raise() { synchronized (lock) { useCount++; } } private void lower() throws IOException { synchronized (lock) { useCount--; if (useCount == 0 && closePending) { streamToClose.close(); } } } // stc is the actual stream to be closed; it might be this object, or // it might be an upstream object for which this object is downstream. // private void closeDeferred(InputStream stc) throws IOException { synchronized (lock) { if (useCount == 0) { stc.close(); } else { closePending = true; streamToClose = stc; } } } public void close() throws IOException { synchronized (lock) { useCount = 0; closePending = false; } super.close(); } public int read() throws IOException { raise(); try { return super.read(); } finally { lower(); } } public int read(byte[] b) throws IOException { raise(); try { return super.read(b); } finally { lower(); } } public int read(byte[] b, int off, int len) throws IOException { raise(); try { return super.read(b, off, len); } finally { lower(); } } public long skip(long n) throws IOException { raise(); try { return super.skip(n); } finally { lower(); } } public int available() throws IOException { raise(); try { return super.available(); } finally { lower(); } } } }