/* * Copyright (c) 1995, 2008, 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.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.concurrent.Executors; import java.util.concurrent.Executor; import java.util.concurrent.ThreadFactory; import java.security.AccessController; import static java.security.AccessController.doPrivileged; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; /** * java.lang.Process subclass in the UNIX environment. * * @author Mario Wolczko and Ross Knippel. * @author Konstantin Kladko (ported to Linux) * @author Martin Buchholz */ 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 /* final */ OutputStream stdin; private /* final */ InputStream stdout; private /* final */ InputStream stderr; /* this is for the reaping thread */ private native int waitForProcessExit(int pid); /** * Create a process using fork(2) and exec(2). * * @param fds an array of three 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(byte[] prog, byte[] argBlock, int argc, byte[] envBlock, int envc, byte[] dir, int[] fds, boolean redirectErrorStream) throws IOException; /** * The thread factory used to create "process reaper" daemon threads. */ private static class ProcessReaperThreadFactory implements ThreadFactory { private final static ThreadGroup group = getRootThreadGroup(); private static ThreadGroup getRootThreadGroup() { return doPrivileged(new PrivilegedAction () { public ThreadGroup run() { ThreadGroup root = Thread.currentThread().getThreadGroup(); while (root.getParent() != null) root = root.getParent(); return root; }}); } public Thread newThread(Runnable grimReaper) { // Our thread stack requirement is quite modest. Thread t = new Thread(group, grimReaper, "process reaper", 32768); t.setDaemon(true); // A small attempt (probably futile) to avoid priority inversion t.setPriority(Thread.MAX_PRIORITY); return t; } } /** * The thread pool of "process reaper" daemon threads. */ private static final Executor processReaperExecutor = doPrivileged(new PrivilegedAction() { public Executor run() { return Executors.newCachedThreadPool (new ProcessReaperThreadFactory()); }}); UNIXProcess(final byte[] prog, final byte[] argBlock, final int argc, final byte[] envBlock, final int envc, final byte[] dir, final int[] fds, final boolean redirectErrorStream) throws IOException { pid = forkAndExec(prog, argBlock, argc, envBlock, envc, dir, fds, redirectErrorStream); try { doPrivileged(new PrivilegedExceptionAction() { public Void run() throws IOException { initStreams(fds); return null; }}); } catch (PrivilegedActionException ex) { throw (IOException) ex.getException(); } } static FileDescriptor newFileDescriptor(int fd) { FileDescriptor fileDescriptor = new FileDescriptor(); fdAccess.set(fileDescriptor, fd); return fileDescriptor; } void initStreams(int[] fds) throws IOException { stdin = (fds[0] == -1) ? ProcessBuilder.NullOutputStream.INSTANCE : new ProcessPipeOutputStream(fds[0]); stdout = (fds[1] == -1) ? ProcessBuilder.NullInputStream.INSTANCE : new ProcessPipeInputStream(fds[1]); stderr = (fds[2] == -1) ? ProcessBuilder.NullInputStream.INSTANCE : new ProcessPipeInputStream(fds[2]); processReaperExecutor.execute(new Runnable() { public void run() { int exitcode = waitForProcessExit(pid); UNIXProcess.this.processExited(exitcode); }}); } synchronized void processExited(int exitcode) { if (stdout instanceof ProcessPipeInputStream) ((ProcessPipeInputStream) stdout).processExited(); if (stderr instanceof ProcessPipeInputStream) ((ProcessPipeInputStream) stderr).processExited(); if (stdin instanceof ProcessPipeOutputStream) ((ProcessPipeOutputStream) stdin).processExited(); this.exitcode = exitcode; hasExited = true; notifyAll(); } public OutputStream getOutputStream() { return stdin; } public InputStream getInputStream() { return stdout; } public InputStream getErrorStream() { return stderr; } 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 void destroy() { // 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. synchronized (this) { if (!hasExited) destroyProcess(pid); } try { stdin.close(); } catch (IOException ignored) {} try { stdout.close(); } catch (IOException ignored) {} try { stderr.close(); } catch (IOException ignored) {} } /* This routine initializes JNI field offsets for the class */ private static native void initIDs(); static { initIDs(); } /** * A buffered input stream for a subprocess pipe file descriptor * that allows the underlying file descriptor to be reclaimed when * the process exits, via the processExited hook. * * This is tricky because we do not want the user-level InputStream to be * closed until the user invokes close(), and we need to continue to be * able to read any buffered data lingering in the OS pipe buffer. */ static class ProcessPipeInputStream extends BufferedInputStream { ProcessPipeInputStream(int fd) { super(new FileInputStream(newFileDescriptor(fd))); } private static byte[] drainInputStream(InputStream in) throws IOException { if (in == null) return null; int n = 0; int j; byte[] a = null; while ((j = in.available()) > 0) { a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j); n += in.read(a, n, j); } return (a == null || n == a.length) ? a : Arrays.copyOf(a, n); } /** Called by the process reaper thread when the process exits. */ synchronized void processExited() { // Most BufferedInputStream methods are synchronized, but close() // is not, and so we have to handle concurrent racing close(). try { InputStream in = this.in; if (in != null) { byte[] stragglers = drainInputStream(in); in.close(); this.in = (stragglers == null) ? ProcessBuilder.NullInputStream.INSTANCE : new ByteArrayInputStream(stragglers); if (buf == null) // asynchronous close()? this.in = null; } } catch (IOException ignored) { // probably an asynchronous close(). } } } /** * A buffered output stream for a subprocess pipe file descriptor * that allows the underlying file descriptor to be reclaimed when * the process exits, via the processExited hook. */ static class ProcessPipeOutputStream extends BufferedOutputStream { ProcessPipeOutputStream(int fd) { super(new FileOutputStream(newFileDescriptor(fd))); } /** Called by the process reaper thread when the process exits. */ synchronized void processExited() { OutputStream out = this.out; if (out != null) { try { out.close(); } catch (IOException ignored) { // We know of no reason to get an IOException, but if // we do, there's nothing else to do but carry on. } this.out = ProcessBuilder.NullOutputStream.INSTANCE; } } } }