/* * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 in the LICENSE file that * accompanied this code). * * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.lang; import java.io.*; import java.util.concurrent.TimeUnit; import java.security.AccessController; import java.security.PrivilegedAction; /* java.lang.Process subclass in the UNIX environment. * * @author Mario Wolczko and Ross Knippel. */ final class UNIXProcess extends Process { private static final sun.misc.JavaIOFileDescriptorAccess fdAccess = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); private final int pid; private int exitcode; private boolean hasExited; private OutputStream stdin_stream; private InputStream stdout_stream; private DeferredCloseInputStream stdout_inner_stream; private InputStream stderr_stream; private static String javahome; private static String arch; private static String helperpath; enum LaunchMechanism { CLONE(1), FORK(2), VFORK(3), POSIX_SPAWN(4); private int value; LaunchMechanism(int x) {value = x;} }; /* On Solaris, the default is to spawn */ private static LaunchMechanism launchMechanism = LaunchMechanism.POSIX_SPAWN; static { AccessController.doPrivileged(new PrivilegedAction() { public Void run() { String javahome = System.getProperty("java.home"); String osarch = System.getProperty("os.arch"); if (osarch.equals("x86")) { osarch = "i386"; } else if (osarch.equals("x86_64")) { osarch = "amd64"; } helperpath = javahome + "/lib/" + osarch + "/jspawnhelper"; String s = System.getProperty("java.lang.Process.useFork"); if (s != null) { launchMechanism = LaunchMechanism.FORK; } return null; } }); } /* this is for the reaping thread */ private native int waitForProcessExit(int pid); /** * Create a process. Depending on the mode flag, this is done by * one of the following mechanisms. * - fork(2) and exec(2) * - vfork(2) and exec(2) * - posix_spawn(2) * * @param std_fds array of file descriptors. Indexes 0, 1, and * 2 correspond to standard input, standard output and * standard error, respectively. On input, a value of -1 * means to create a pipe to connect child and parent * processes. On output, a value which is not -1 is the * parent pipe fd corresponding to the pipe which has * been created. An element of this array is -1 on input * if and only if it is not -1 on output. * @return the pid of the subprocess */ private native int forkAndExec(int mode, byte[] prog, byte[] argBlock, int argc, byte[] envBlock, int envc, byte[] dir, int[] std_fds, boolean redirectErrorStream) throws IOException; UNIXProcess(final byte[] prog, final byte[] argBlock, int argc, final byte[] envBlock, int envc, final byte[] dir, final int[] std_fds, final boolean redirectErrorStream) throws IOException { pid = forkAndExec(launchMechanism.value, prog, argBlock, argc, envBlock, envc, dir, std_fds, redirectErrorStream); java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Void run() { if (std_fds[0] == -1) stdin_stream = ProcessBuilder.NullOutputStream.INSTANCE; else { FileDescriptor stdin_fd = new FileDescriptor(); fdAccess.set(stdin_fd, std_fds[0]); stdin_stream = new BufferedOutputStream( new FileOutputStream(stdin_fd)); } if (std_fds[1] == -1) stdout_stream = ProcessBuilder.NullInputStream.INSTANCE; else { FileDescriptor stdout_fd = new FileDescriptor(); fdAccess.set(stdout_fd, std_fds[1]); stdout_inner_stream = new DeferredCloseInputStream(stdout_fd); stdout_stream = new BufferedInputStream(stdout_inner_stream); } if (std_fds[2] == -1) stderr_stream = ProcessBuilder.NullInputStream.INSTANCE; else { FileDescriptor stderr_fd = new FileDescriptor(); fdAccess.set(stderr_fd, std_fds[2]); 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 Void 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; } @Override public synchronized boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException { if (hasExited) return true; if (timeout <= 0) return false; long timeoutAsNanos = unit.toNanos(timeout); long startTime = System.nanoTime(); long rem = timeoutAsNanos; while (!hasExited && (rem > 0)) { wait(Math.max(TimeUnit.NANOSECONDS.toMillis(rem), 1)); rem = timeoutAsNanos - (System.nanoTime() - startTime); } return hasExited; } public synchronized int exitValue() { if (!hasExited) { throw new IllegalThreadStateException("process hasn't exited"); } return exitcode; } private static native void destroyProcess(int pid, boolean force); private synchronized void destroy(boolean force) { // There is a risk that pid will be recycled, causing us to // kill the wrong process! So we only terminate processes // that appear to still be running. Even with this check, // there is an unavoidable race condition here, but the window // is very small, and OSes try hard to not recycle pids too // soon, so this is quite safe. if (!hasExited) destroyProcess(pid, force); try { stdin_stream.close(); if (stdout_inner_stream != null) stdout_inner_stream.closeDeferred(stdout_stream); if (stderr_stream instanceof DeferredCloseInputStream) ((DeferredCloseInputStream) stderr_stream) .closeDeferred(stderr_stream); } catch (IOException e) { // ignore } } public void destroy() { destroy(false); } @Override public Process destroyForcibly() { destroy(true); return this; } @Override public synchronized boolean isAlive() { return !hasExited; } // 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(); } } } /* This routine initializes JNI field offsets for the class */ private static native void initIDs(); static { initIDs(); } }