# HG changeset patch # User rriggs # Date 1422287727 18000 # Node ID fe8344cf6496a404e69769ddbda8af72e1550c4b # Parent 2f79ecb05ada61b8b6d07a8ad9345ffbe1045937 8071481: (Process) Merge UNIXProcess.java into ProcessImpl.java Summary: simplify source files for Unix Process implementation Reviewed-by: plevart, chegar diff -r 2f79ecb05ada -r fe8344cf6496 jdk/make/mapfiles/libjava/mapfile-vers --- a/jdk/make/mapfiles/libjava/mapfile-vers Mon Jan 26 21:55:05 2015 +0800 +++ b/jdk/make/mapfiles/libjava/mapfile-vers Mon Jan 26 10:55:27 2015 -0500 @@ -1,5 +1,5 @@ # -# Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1997, 2015, 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 @@ -214,10 +214,10 @@ Java_java_lang_Throwable_fillInStackTrace; Java_java_lang_Throwable_getStackTraceDepth; Java_java_lang_Throwable_getStackTraceElement; - Java_java_lang_UNIXProcess_init; - Java_java_lang_UNIXProcess_waitForProcessExit; - Java_java_lang_UNIXProcess_forkAndExec; - Java_java_lang_UNIXProcess_destroyProcess; + Java_java_lang_ProcessImpl_init; + Java_java_lang_ProcessImpl_waitForProcessExit; + Java_java_lang_ProcessImpl_forkAndExec; + Java_java_lang_ProcessImpl_destroyProcess; Java_java_nio_Bits_copyFromShortArray; Java_java_nio_Bits_copyToShortArray; Java_java_nio_Bits_copyFromIntArray; diff -r 2f79ecb05ada -r fe8344cf6496 jdk/src/java.base/unix/classes/java/lang/ProcessImpl.java --- a/jdk/src/java.base/unix/classes/java/lang/ProcessImpl.java Mon Jan 26 21:55:05 2015 +0800 +++ b/jdk/src/java.base/unix/classes/java/lang/ProcessImpl.java Mon Jan 26 10:55:27 2015 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2015, 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 @@ -25,24 +25,156 @@ package java.lang; -import java.io.IOException; +import java.lang.ProcessBuilder.Redirect; +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.lang.ProcessBuilder.Redirect; -import java.lang.ProcessBuilder.Redirect; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.security.AccessController; +import static java.security.AccessController.doPrivileged; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; /** - * This class is for the exclusive use of ProcessBuilder.start() to - * create new processes. + * This java.lang.Process subclass in the UNIX environment is for the exclusive use of + * ProcessBuilder.start() to create new processes. * + * @author Mario Wolczko and Ross Knippel. + * @author Konstantin Kladko (ported to Linux and Bsd) * @author Martin Buchholz + * @author Volker Simonis (ported to AIX) * @since 1.5 */ -final class ProcessImpl { +final class ProcessImpl extends Process { private static final sun.misc.JavaIOFileDescriptorAccess fdAccess = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); - private ProcessImpl() {} // Not instantiable + private final int pid; + private int exitcode; + private boolean hasExited; + + private /* final */ OutputStream stdin; + private /* final */ InputStream stdout; + private /* final */ InputStream stderr; + + // only used on Solaris + private /* final */ DeferredCloseInputStream stdout_inner_stream; + + private static enum LaunchMechanism { + // order IS important! + FORK, + POSIX_SPAWN, + VFORK + } + + private static enum Platform { + + LINUX(LaunchMechanism.VFORK, LaunchMechanism.FORK), + + BSD(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK), + + SOLARIS(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK), + + AIX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK); + + final LaunchMechanism defaultLaunchMechanism; + final Set validLaunchMechanisms; + + Platform(LaunchMechanism ... launchMechanisms) { + this.defaultLaunchMechanism = launchMechanisms[0]; + this.validLaunchMechanisms = + EnumSet.copyOf(Arrays.asList(launchMechanisms)); + } + + @SuppressWarnings("fallthrough") + private String helperPath(String javahome, String osArch) { + switch (this) { + case SOLARIS: + if (osArch.equals("x86")) { osArch = "i386"; } + else if (osArch.equals("x86_64")) { osArch = "amd64"; } + // fall through... + case LINUX: + case AIX: + return javahome + "/lib/" + osArch + "/jspawnhelper"; + + case BSD: + return javahome + "/lib/jspawnhelper"; + + default: + throw new AssertionError("Unsupported platform: " + this); + } + } + + String helperPath() { + return AccessController.doPrivileged( + (PrivilegedAction) () -> + helperPath(System.getProperty("java.home"), + System.getProperty("os.arch")) + ); + } + + LaunchMechanism launchMechanism() { + return AccessController.doPrivileged( + (PrivilegedAction) () -> { + String s = System.getProperty( + "jdk.lang.Process.launchMechanism"); + LaunchMechanism lm; + if (s == null) { + lm = defaultLaunchMechanism; + s = lm.name().toLowerCase(Locale.ENGLISH); + } else { + try { + lm = LaunchMechanism.valueOf( + s.toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException e) { + lm = null; + } + } + if (lm == null || !validLaunchMechanisms.contains(lm)) { + throw new Error( + s + " is not a supported " + + "process launch mechanism on this platform." + ); + } + return lm; + } + ); + } + + static Platform get() { + String osName = AccessController.doPrivileged( + (PrivilegedAction) () -> System.getProperty("os.name") + ); + + if (osName.equals("Linux")) { return LINUX; } + if (osName.contains("OS X")) { return BSD; } + if (osName.equals("SunOS")) { return SOLARIS; } + if (osName.equals("AIX")) { return AIX; } + + throw new Error(osName + " is not a supported OS platform."); + } + } + + private static final Platform platform = Platform.get(); + private static final LaunchMechanism launchMechanism = platform.launchMechanism(); + private static final byte[] helperpath = toCString(platform.helperPath()); + + /* this is for the reaping thread */ + private native int waitForProcessExit(int pid); private static byte[] toCString(String s) { if (s == null) @@ -50,8 +182,8 @@ byte[] bytes = s.getBytes(); byte[] result = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, - result, 0, - bytes.length); + result, 0, + bytes.length); result[result.length-1] = (byte)0; return result; } @@ -62,7 +194,7 @@ String dir, ProcessBuilder.Redirect[] redirects, boolean redirectErrorStream) - throws IOException + throws IOException { assert cmdarray != null && cmdarray.length > 0; @@ -112,7 +244,7 @@ std_fds[1] = 1; else { f1 = new FileOutputStream(redirects[1].file(), - redirects[1].append()); + redirects[1].append()); std_fds[1] = fdAccess.get(f1.getFD()); } @@ -122,18 +254,18 @@ std_fds[2] = 2; else { f2 = new FileOutputStream(redirects[2].file(), - redirects[2].append()); + redirects[2].append()); std_fds[2] = fdAccess.get(f2.getFD()); } } - return new UNIXProcess - (toCString(cmdarray[0]), - argBlock, args.length, - envBlock, envc[0], - toCString(dir), - std_fds, - redirectErrorStream); + return new ProcessImpl + (toCString(cmdarray[0]), + argBlock, args.length, + envBlock, envc[0], + toCString(dir), + std_fds, + redirectErrorStream); } finally { // In theory, close() can throw IOException // (although it is rather unlikely to happen here) @@ -144,4 +276,654 @@ } } } + + + /** + * Creates a process. Depending on the {@code mode} flag, this is done by + * one of the following mechanisms: + *
+     *   1 - fork(2) and exec(2)
+     *   2 - posix_spawn(3P)
+     *   3 - vfork(2) and exec(2)
+     *
+     *  (4 - clone(2) and exec(2) - obsolete and currently disabled in native code)
+     * 
+ * @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(int mode, byte[] helperpath, + byte[] prog, + byte[] argBlock, int argc, + byte[] envBlock, int envc, + byte[] dir, + int[] fds, + boolean redirectErrorStream) + throws IOException; + + /** + * The thread pool of "process reaper" daemon threads. + */ + private static final Executor processReaperExecutor = + doPrivileged((PrivilegedAction) () -> { + + ThreadGroup tg = Thread.currentThread().getThreadGroup(); + while (tg.getParent() != null) tg = tg.getParent(); + ThreadGroup systemThreadGroup = tg; + + ThreadFactory threadFactory = grimReaper -> { + // Our thread stack requirement is quite modest. + Thread t = new Thread(systemThreadGroup, grimReaper, + "process reaper", 32768); + t.setDaemon(true); + // A small attempt (probably futile) to avoid priority inversion + t.setPriority(Thread.MAX_PRIORITY); + return t; + }; + + return Executors.newCachedThreadPool(threadFactory); + }); + + private ProcessImpl(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(launchMechanism.ordinal() + 1, + helperpath, + prog, + argBlock, argc, + envBlock, envc, + dir, + fds, + redirectErrorStream); + + try { + doPrivileged((PrivilegedExceptionAction) () -> { + 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 { + switch (platform) { + case LINUX: + case BSD: + 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(() -> { + int exitcode = waitForProcessExit(pid); + + synchronized (this) { + this.exitcode = exitcode; + this.hasExited = true; + this.notifyAll(); + } + + if (stdout instanceof ProcessPipeInputStream) + ((ProcessPipeInputStream) stdout).processExited(); + + if (stderr instanceof ProcessPipeInputStream) + ((ProcessPipeInputStream) stderr).processExited(); + + if (stdin instanceof ProcessPipeOutputStream) + ((ProcessPipeOutputStream) stdin).processExited(); + }); + break; + + case SOLARIS: + stdin = (fds[0] == -1) ? + ProcessBuilder.NullOutputStream.INSTANCE : + new BufferedOutputStream( + new FileOutputStream(newFileDescriptor(fds[0]))); + + stdout = (fds[1] == -1) ? + ProcessBuilder.NullInputStream.INSTANCE : + new BufferedInputStream( + stdout_inner_stream = + new DeferredCloseInputStream( + newFileDescriptor(fds[1]))); + + stderr = (fds[2] == -1) ? + ProcessBuilder.NullInputStream.INSTANCE : + new DeferredCloseInputStream(newFileDescriptor(fds[2])); + + /* + * For each subprocess forked a corresponding reaper task + * is submitted. That task 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). + */ + processReaperExecutor.execute(() -> { + int exitcode = waitForProcessExit(pid); + + synchronized (this) { + this.exitcode = exitcode; + this.hasExited = true; + this.notifyAll(); + } + }); + break; + + case AIX: + stdin = (fds[0] == -1) ? + ProcessBuilder.NullOutputStream.INSTANCE : + new ProcessPipeOutputStream(fds[0]); + + stdout = (fds[1] == -1) ? + ProcessBuilder.NullInputStream.INSTANCE : + new DeferredCloseProcessPipeInputStream(fds[1]); + + stderr = (fds[2] == -1) ? + ProcessBuilder.NullInputStream.INSTANCE : + new DeferredCloseProcessPipeInputStream(fds[2]); + + processReaperExecutor.execute(() -> { + int exitcode = waitForProcessExit(pid); + + synchronized (this) { + this.exitcode = exitcode; + this.hasExited = true; + this.notifyAll(); + } + + if (stdout instanceof DeferredCloseProcessPipeInputStream) + ((DeferredCloseProcessPipeInputStream) stdout).processExited(); + + if (stderr instanceof DeferredCloseProcessPipeInputStream) + ((DeferredCloseProcessPipeInputStream) stderr).processExited(); + + if (stdin instanceof ProcessPipeOutputStream) + ((ProcessPipeOutputStream) stdin).processExited(); + }); + break; + + default: throw new AssertionError("Unsupported platform: " + platform); + } + } + + 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; + } + + @Override + public synchronized boolean waitFor(long timeout, TimeUnit unit) + throws InterruptedException + { + if (hasExited) return true; + if (timeout <= 0) return false; + + long remainingNanos = unit.toNanos(timeout); + long deadline = System.nanoTime() + remainingNanos; + + do { + // Round up to next millisecond + wait(TimeUnit.NANOSECONDS.toMillis(remainingNanos + 999_999L)); + if (hasExited) { + return true; + } + remainingNanos = deadline - System.nanoTime(); + } while (remainingNanos > 0); + 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 void destroy(boolean force) { + switch (platform) { + case LINUX: + case BSD: + case AIX: + // 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, force); + } + try { stdin.close(); } catch (IOException ignored) {} + try { stdout.close(); } catch (IOException ignored) {} + try { stderr.close(); } catch (IOException ignored) {} + break; + + case SOLARIS: + // 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, force); + try { + stdin.close(); + if (stdout_inner_stream != null) + stdout_inner_stream.closeDeferred(stdout); + if (stderr instanceof DeferredCloseInputStream) + ((DeferredCloseInputStream) stderr) + .closeDeferred(stderr); + } catch (IOException e) { + // ignore + } + } + break; + + default: throw new AssertionError("Unsupported platform: " + platform); + } + } + + public void destroy() { + destroy(false); + } + + @Override + public Process destroyForcibly() { + destroy(true); + return this; + } + + @Override + public long getPid() { + return pid; + } + + @Override + public synchronized boolean isAlive() { + return !hasExited; + } + + private static native void init(); + + static { + init(); + } + + /** + * 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. + */ + private static class ProcessPipeInputStream extends BufferedInputStream { + private final Object closeLock = new Object(); + + ProcessPipeInputStream(int fd) { + super(new FileInputStream(newFileDescriptor(fd))); + } + private static byte[] drainInputStream(InputStream in) + throws IOException { + 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() { + synchronized (closeLock) { + try { + InputStream in = this.in; + // this stream is closed if and only if: in == null + if (in != null) { + byte[] stragglers = drainInputStream(in); + in.close(); + this.in = (stragglers == null) ? + ProcessBuilder.NullInputStream.INSTANCE : + new ByteArrayInputStream(stragglers); + } + } catch (IOException ignored) {} + } + } + + @Override + public void close() throws IOException { + // BufferedInputStream#close() is not synchronized unlike most other + // methods. Synchronizing helps avoid race with processExited(). + synchronized (closeLock) { + super.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. + */ + private 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; + } + } + } + + // 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 + { + 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(); + } + } + } + + /** + * 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. + * + * On AIX this is especially tricky, because the 'close()' system call + * will block if another thread is at the same time blocked in a file + * operation (e.g. 'read()') on the same file descriptor. We therefore + * combine 'ProcessPipeInputStream' approach used on Linux and Bsd + * with the DeferredCloseInputStream approach used on Solaris. This means + * that every potentially blocking operation on the file descriptor + * increments a counter before it is executed and decrements it once it + * finishes. The 'close()' operation will only be executed if there are + * no pending operations. Otherwise it is deferred after the last pending + * operation has finished. + * + */ + private static class DeferredCloseProcessPipeInputStream + extends BufferedInputStream { + + private final Object closeLock = new Object(); + private int useCount = 0; + private boolean closePending = false; + + DeferredCloseProcessPipeInputStream(int fd) { + super(new FileInputStream(newFileDescriptor(fd))); + } + + private InputStream drainInputStream(InputStream in) + throws IOException { + int n = 0; + int j; + byte[] a = null; + synchronized (closeLock) { + if (buf == null) // asynchronous close()? + return null; // discard + j = in.available(); + } + while (j > 0) { + a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j); + synchronized (closeLock) { + if (buf == null) // asynchronous close()? + return null; // discard + n += in.read(a, n, j); + j = in.available(); + } + } + return (a == null) ? + ProcessBuilder.NullInputStream.INSTANCE : + new ByteArrayInputStream(n == a.length ? a : Arrays.copyOf(a, n)); + } + + /** Called by the process reaper thread when the process exits. */ + synchronized void processExited() { + try { + InputStream in = this.in; + if (in != null) { + InputStream stragglers = drainInputStream(in); + in.close(); + this.in = stragglers; + } + } catch (IOException ignored) { } + } + + private void raise() { + synchronized (closeLock) { + useCount++; + } + } + + private void lower() throws IOException { + synchronized (closeLock) { + useCount--; + if (useCount == 0 && closePending) { + closePending = false; + super.close(); + } + } + } + + @Override + public int read() throws IOException { + raise(); + try { + return super.read(); + } finally { + lower(); + } + } + + @Override + public int read(byte[] b) throws IOException { + raise(); + try { + return super.read(b); + } finally { + lower(); + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + raise(); + try { + return super.read(b, off, len); + } finally { + lower(); + } + } + + @Override + public long skip(long n) throws IOException { + raise(); + try { + return super.skip(n); + } finally { + lower(); + } + } + + @Override + public int available() throws IOException { + raise(); + try { + return super.available(); + } finally { + lower(); + } + } + + @Override + public void close() throws IOException { + // BufferedInputStream#close() is not synchronized unlike most other + // methods. Synchronizing helps avoid racing with drainInputStream(). + synchronized (closeLock) { + if (useCount == 0) { + super.close(); + } + else { + closePending = true; + } + } + } + } } diff -r 2f79ecb05ada -r fe8344cf6496 jdk/src/java.base/unix/classes/java/lang/UNIXProcess.java --- a/jdk/src/java.base/unix/classes/java/lang/UNIXProcess.java Mon Jan 26 21:55:05 2015 +0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,836 +0,0 @@ -/* - * Copyright (c) 1995, 2014, 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.EnumSet; -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.Executors; -import java.util.concurrent.Executor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -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 and Bsd) - * @author Martin Buchholz - * @author Volker Simonis (ported to AIX) - */ -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; - - // only used on Solaris - private /* final */ DeferredCloseInputStream stdout_inner_stream; - - private static enum LaunchMechanism { - // order IS important! - FORK, - POSIX_SPAWN, - VFORK - } - - private static enum Platform { - - LINUX(LaunchMechanism.VFORK, LaunchMechanism.FORK), - - BSD(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK), - - SOLARIS(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK), - - AIX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK); - - final LaunchMechanism defaultLaunchMechanism; - final Set validLaunchMechanisms; - - Platform(LaunchMechanism ... launchMechanisms) { - this.defaultLaunchMechanism = launchMechanisms[0]; - this.validLaunchMechanisms = - EnumSet.copyOf(Arrays.asList(launchMechanisms)); - } - - @SuppressWarnings("fallthrough") - private String helperPath(String javahome, String osArch) { - switch (this) { - case SOLARIS: - if (osArch.equals("x86")) { osArch = "i386"; } - else if (osArch.equals("x86_64")) { osArch = "amd64"; } - // fall through... - case LINUX: - case AIX: - return javahome + "/lib/" + osArch + "/jspawnhelper"; - - case BSD: - return javahome + "/lib/jspawnhelper"; - - default: - throw new AssertionError("Unsupported platform: " + this); - } - } - - String helperPath() { - return AccessController.doPrivileged( - (PrivilegedAction) () -> - helperPath(System.getProperty("java.home"), - System.getProperty("os.arch")) - ); - } - - LaunchMechanism launchMechanism() { - return AccessController.doPrivileged( - (PrivilegedAction) () -> { - String s = System.getProperty( - "jdk.lang.Process.launchMechanism"); - LaunchMechanism lm; - if (s == null) { - lm = defaultLaunchMechanism; - s = lm.name().toLowerCase(Locale.ENGLISH); - } else { - try { - lm = LaunchMechanism.valueOf( - s.toUpperCase(Locale.ENGLISH)); - } catch (IllegalArgumentException e) { - lm = null; - } - } - if (lm == null || !validLaunchMechanisms.contains(lm)) { - throw new Error( - s + " is not a supported " + - "process launch mechanism on this platform." - ); - } - return lm; - } - ); - } - - static Platform get() { - String osName = AccessController.doPrivileged( - (PrivilegedAction) () -> System.getProperty("os.name") - ); - - if (osName.equals("Linux")) { return LINUX; } - if (osName.contains("OS X")) { return BSD; } - if (osName.equals("SunOS")) { return SOLARIS; } - if (osName.equals("AIX")) { return AIX; } - - throw new Error(osName + " is not a supported OS platform."); - } - } - - private static final Platform platform = Platform.get(); - private static final LaunchMechanism launchMechanism = platform.launchMechanism(); - private static final byte[] helperpath = toCString(platform.helperPath()); - - private static byte[] toCString(String s) { - if (s == null) - return null; - byte[] bytes = s.getBytes(); - byte[] result = new byte[bytes.length + 1]; - System.arraycopy(bytes, 0, - result, 0, - bytes.length); - result[result.length-1] = (byte)0; - return result; - } - - /* this is for the reaping thread */ - private native int waitForProcessExit(int pid); - - /** - * Creates a process. Depending on the {@code mode} flag, this is done by - * one of the following mechanisms: - *
-     *   1 - fork(2) and exec(2)
-     *   2 - posix_spawn(3P)
-     *   3 - vfork(2) and exec(2)
-     *
-     *  (4 - clone(2) and exec(2) - obsolete and currently disabled in native code)
-     * 
- * @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(int mode, byte[] helperpath, - byte[] prog, - byte[] argBlock, int argc, - byte[] envBlock, int envc, - byte[] dir, - int[] fds, - boolean redirectErrorStream) - throws IOException; - - /** - * The thread pool of "process reaper" daemon threads. - */ - private static final Executor processReaperExecutor = - doPrivileged((PrivilegedAction) () -> { - - ThreadGroup tg = Thread.currentThread().getThreadGroup(); - while (tg.getParent() != null) tg = tg.getParent(); - ThreadGroup systemThreadGroup = tg; - - ThreadFactory threadFactory = grimReaper -> { - // Our thread stack requirement is quite modest. - Thread t = new Thread(systemThreadGroup, grimReaper, - "process reaper", 32768); - t.setDaemon(true); - // A small attempt (probably futile) to avoid priority inversion - t.setPriority(Thread.MAX_PRIORITY); - return t; - }; - - return Executors.newCachedThreadPool(threadFactory); - }); - - 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(launchMechanism.ordinal() + 1, - helperpath, - prog, - argBlock, argc, - envBlock, envc, - dir, - fds, - redirectErrorStream); - - try { - doPrivileged((PrivilegedExceptionAction) () -> { - 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 { - switch (platform) { - case LINUX: - case BSD: - 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(() -> { - int exitcode = waitForProcessExit(pid); - - synchronized (this) { - this.exitcode = exitcode; - this.hasExited = true; - this.notifyAll(); - } - - if (stdout instanceof ProcessPipeInputStream) - ((ProcessPipeInputStream) stdout).processExited(); - - if (stderr instanceof ProcessPipeInputStream) - ((ProcessPipeInputStream) stderr).processExited(); - - if (stdin instanceof ProcessPipeOutputStream) - ((ProcessPipeOutputStream) stdin).processExited(); - }); - break; - - case SOLARIS: - stdin = (fds[0] == -1) ? - ProcessBuilder.NullOutputStream.INSTANCE : - new BufferedOutputStream( - new FileOutputStream(newFileDescriptor(fds[0]))); - - stdout = (fds[1] == -1) ? - ProcessBuilder.NullInputStream.INSTANCE : - new BufferedInputStream( - stdout_inner_stream = - new DeferredCloseInputStream( - newFileDescriptor(fds[1]))); - - stderr = (fds[2] == -1) ? - ProcessBuilder.NullInputStream.INSTANCE : - new DeferredCloseInputStream(newFileDescriptor(fds[2])); - - /* - * For each subprocess forked a corresponding reaper task - * is submitted. That task 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). - */ - processReaperExecutor.execute(() -> { - int exitcode = waitForProcessExit(pid); - - synchronized (this) { - this.exitcode = exitcode; - this.hasExited = true; - this.notifyAll(); - } - }); - break; - - case AIX: - stdin = (fds[0] == -1) ? - ProcessBuilder.NullOutputStream.INSTANCE : - new ProcessPipeOutputStream(fds[0]); - - stdout = (fds[1] == -1) ? - ProcessBuilder.NullInputStream.INSTANCE : - new DeferredCloseProcessPipeInputStream(fds[1]); - - stderr = (fds[2] == -1) ? - ProcessBuilder.NullInputStream.INSTANCE : - new DeferredCloseProcessPipeInputStream(fds[2]); - - processReaperExecutor.execute(() -> { - int exitcode = waitForProcessExit(pid); - - synchronized (this) { - this.exitcode = exitcode; - this.hasExited = true; - this.notifyAll(); - } - - if (stdout instanceof DeferredCloseProcessPipeInputStream) - ((DeferredCloseProcessPipeInputStream) stdout).processExited(); - - if (stderr instanceof DeferredCloseProcessPipeInputStream) - ((DeferredCloseProcessPipeInputStream) stderr).processExited(); - - if (stdin instanceof ProcessPipeOutputStream) - ((ProcessPipeOutputStream) stdin).processExited(); - }); - break; - - default: throw new AssertionError("Unsupported platform: " + platform); - } - } - - 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; - } - - @Override - public synchronized boolean waitFor(long timeout, TimeUnit unit) - throws InterruptedException - { - if (hasExited) return true; - if (timeout <= 0) return false; - - long remainingNanos = unit.toNanos(timeout); - long deadline = System.nanoTime() + remainingNanos; - - do { - // Round up to next millisecond - wait(TimeUnit.NANOSECONDS.toMillis(remainingNanos + 999_999L)); - if (hasExited) { - return true; - } - remainingNanos = deadline - System.nanoTime(); - } while (remainingNanos > 0); - 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 void destroy(boolean force) { - switch (platform) { - case LINUX: - case BSD: - case AIX: - // 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, force); - } - try { stdin.close(); } catch (IOException ignored) {} - try { stdout.close(); } catch (IOException ignored) {} - try { stderr.close(); } catch (IOException ignored) {} - break; - - case SOLARIS: - // 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, force); - try { - stdin.close(); - if (stdout_inner_stream != null) - stdout_inner_stream.closeDeferred(stdout); - if (stderr instanceof DeferredCloseInputStream) - ((DeferredCloseInputStream) stderr) - .closeDeferred(stderr); - } catch (IOException e) { - // ignore - } - } - break; - - default: throw new AssertionError("Unsupported platform: " + platform); - } - } - - public void destroy() { - destroy(false); - } - - @Override - public Process destroyForcibly() { - destroy(true); - return this; - } - - @Override - public long getPid() { - return pid; - } - - @Override - public synchronized boolean isAlive() { - return !hasExited; - } - - private static native void init(); - - static { - init(); - } - - /** - * 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. - */ - private static class ProcessPipeInputStream extends BufferedInputStream { - private final Object closeLock = new Object(); - - ProcessPipeInputStream(int fd) { - super(new FileInputStream(newFileDescriptor(fd))); - } - private static byte[] drainInputStream(InputStream in) - throws IOException { - 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() { - synchronized (closeLock) { - try { - InputStream in = this.in; - // this stream is closed if and only if: in == null - if (in != null) { - byte[] stragglers = drainInputStream(in); - in.close(); - this.in = (stragglers == null) ? - ProcessBuilder.NullInputStream.INSTANCE : - new ByteArrayInputStream(stragglers); - } - } catch (IOException ignored) {} - } - } - - @Override - public void close() throws IOException { - // BufferedInputStream#close() is not synchronized unlike most other - // methods. Synchronizing helps avoid race with processExited(). - synchronized (closeLock) { - super.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. - */ - private 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; - } - } - } - - // 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 - { - 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(); - } - } - } - - /** - * 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. - * - * On AIX this is especially tricky, because the 'close()' system call - * will block if another thread is at the same time blocked in a file - * operation (e.g. 'read()') on the same file descriptor. We therefore - * combine 'ProcessPipeInputStream' approach used on Linux and Bsd - * with the DeferredCloseInputStream approach used on Solaris. This means - * that every potentially blocking operation on the file descriptor - * increments a counter before it is executed and decrements it once it - * finishes. The 'close()' operation will only be executed if there are - * no pending operations. Otherwise it is deferred after the last pending - * operation has finished. - * - */ - private static class DeferredCloseProcessPipeInputStream - extends BufferedInputStream { - - private final Object closeLock = new Object(); - private int useCount = 0; - private boolean closePending = false; - - DeferredCloseProcessPipeInputStream(int fd) { - super(new FileInputStream(newFileDescriptor(fd))); - } - - private InputStream drainInputStream(InputStream in) - throws IOException { - int n = 0; - int j; - byte[] a = null; - synchronized (closeLock) { - if (buf == null) // asynchronous close()? - return null; // discard - j = in.available(); - } - while (j > 0) { - a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j); - synchronized (closeLock) { - if (buf == null) // asynchronous close()? - return null; // discard - n += in.read(a, n, j); - j = in.available(); - } - } - return (a == null) ? - ProcessBuilder.NullInputStream.INSTANCE : - new ByteArrayInputStream(n == a.length ? a : Arrays.copyOf(a, n)); - } - - /** Called by the process reaper thread when the process exits. */ - synchronized void processExited() { - try { - InputStream in = this.in; - if (in != null) { - InputStream stragglers = drainInputStream(in); - in.close(); - this.in = stragglers; - } - } catch (IOException ignored) { } - } - - private void raise() { - synchronized (closeLock) { - useCount++; - } - } - - private void lower() throws IOException { - synchronized (closeLock) { - useCount--; - if (useCount == 0 && closePending) { - closePending = false; - super.close(); - } - } - } - - @Override - public int read() throws IOException { - raise(); - try { - return super.read(); - } finally { - lower(); - } - } - - @Override - public int read(byte[] b) throws IOException { - raise(); - try { - return super.read(b); - } finally { - lower(); - } - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - raise(); - try { - return super.read(b, off, len); - } finally { - lower(); - } - } - - @Override - public long skip(long n) throws IOException { - raise(); - try { - return super.skip(n); - } finally { - lower(); - } - } - - @Override - public int available() throws IOException { - raise(); - try { - return super.available(); - } finally { - lower(); - } - } - - @Override - public void close() throws IOException { - // BufferedInputStream#close() is not synchronized unlike most other - // methods. Synchronizing helps avoid racing with drainInputStream(). - synchronized (closeLock) { - if (useCount == 0) { - super.close(); - } - else { - closePending = true; - } - } - } - } -} diff -r 2f79ecb05ada -r fe8344cf6496 jdk/src/java.base/unix/native/libjava/ProcessImpl_md.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.base/unix/native/libjava/ProcessImpl_md.c Mon Jan 26 10:55:27 2015 -0500 @@ -0,0 +1,724 @@ +/* + * Copyright (c) 1995, 2015, 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. + */ + +#undef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE 1 + +#include "jni.h" +#include "jvm.h" +#include "jvm_md.h" +#include "jni_util.h" +#include "io_util.h" + +/* + * Platform-specific support for java.lang.Process + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__solaris__) || defined(_ALLBSD_SOURCE) || defined(_AIX) +#include +#endif + +#include "childproc.h" + +/* + * There are 4 possible strategies we might use to "fork": + * + * - fork(2). Very portable and reliable but subject to + * failure due to overcommit (see the documentation on + * /proc/sys/vm/overcommit_memory in Linux proc(5)). + * This is the ancient problem of spurious failure whenever a large + * process starts a small subprocess. + * + * - vfork(). Using this is scary because all relevant man pages + * contain dire warnings, e.g. Linux vfork(2). But at least it's + * documented in the glibc docs and is standardized by XPG4. + * http://www.opengroup.org/onlinepubs/000095399/functions/vfork.html + * On Linux, one might think that vfork() would be implemented using + * the clone system call with flag CLONE_VFORK, but in fact vfork is + * a separate system call (which is a good sign, suggesting that + * vfork will continue to be supported at least on Linux). + * Another good sign is that glibc implements posix_spawn using + * vfork whenever possible. Note that we cannot use posix_spawn + * ourselves because there's no reliable way to close all inherited + * file descriptors. + * + * - clone() with flags CLONE_VM but not CLONE_THREAD. clone() is + * Linux-specific, but this ought to work - at least the glibc + * sources contain code to handle different combinations of CLONE_VM + * and CLONE_THREAD. However, when this was implemented, it + * appeared to fail on 32-bit i386 (but not 64-bit x86_64) Linux with + * the simple program + * Runtime.getRuntime().exec("/bin/true").waitFor(); + * with: + * # Internal Error (os_linux_x86.cpp:683), pid=19940, tid=2934639536 + * # Error: pthread_getattr_np failed with errno = 3 (ESRCH) + * We believe this is a glibc bug, reported here: + * http://sources.redhat.com/bugzilla/show_bug.cgi?id=10311 + * but the glibc maintainers closed it as WONTFIX. + * + * - posix_spawn(). While posix_spawn() is a fairly elaborate and + * complicated system call, it can't quite do everything that the old + * fork()/exec() combination can do, so the only feasible way to do + * this, is to use posix_spawn to launch a new helper executable + * "jprochelper", which in turn execs the target (after cleaning + * up file-descriptors etc.) The end result is the same as before, + * a child process linked to the parent in the same way, but it + * avoids the problem of duplicating the parent (VM) process + * address space temporarily, before launching the target command. + * + * Based on the above analysis, we are currently using vfork() on + * Linux and spawn() on other Unix systems, but the code to use clone() + * and fork() remains. + */ + + +static void +setSIGCHLDHandler(JNIEnv *env) +{ + /* There is a subtle difference between having the signal handler + * for SIGCHLD be SIG_DFL and SIG_IGN. We cannot obtain process + * termination information for child processes if the signal + * handler is SIG_IGN. It must be SIG_DFL. + * + * We used to set the SIGCHLD handler only on Linux, but it's + * safest to set it unconditionally. + * + * Consider what happens if java's parent process sets the SIGCHLD + * handler to SIG_IGN. Normally signal handlers are inherited by + * children, but SIGCHLD is a controversial case. Solaris appears + * to always reset it to SIG_DFL, but this behavior may be + * non-standard-compliant, and we shouldn't rely on it. + * + * References: + * http://www.opengroup.org/onlinepubs/7908799/xsh/exec.html + * http://www.pasc.org/interps/unofficial/db/p1003.1/pasc-1003.1-132.html + */ + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; + if (sigaction(SIGCHLD, &sa, NULL) < 0) + JNU_ThrowInternalError(env, "Can't set SIGCHLD handler"); +} + +static void* +xmalloc(JNIEnv *env, size_t size) +{ + void *p = malloc(size); + if (p == NULL) + JNU_ThrowOutOfMemoryError(env, NULL); + return p; +} + +#define NEW(type, n) ((type *) xmalloc(env, (n) * sizeof(type))) + +/** + * If PATH is not defined, the OS provides some default value. + * Unfortunately, there's no portable way to get this value. + * Fortunately, it's only needed if the child has PATH while we do not. + */ +static const char* +defaultPath(void) +{ +#ifdef __solaris__ + /* These really are the Solaris defaults! */ + return (geteuid() == 0 || getuid() == 0) ? + "/usr/xpg4/bin:/usr/ccs/bin:/usr/bin:/opt/SUNWspro/bin:/usr/sbin" : + "/usr/xpg4/bin:/usr/ccs/bin:/usr/bin:/opt/SUNWspro/bin:"; +#else + return ":/bin:/usr/bin"; /* glibc */ +#endif +} + +static const char* +effectivePath(void) +{ + const char *s = getenv("PATH"); + return (s != NULL) ? s : defaultPath(); +} + +static int +countOccurrences(const char *s, char c) +{ + int count; + for (count = 0; *s != '\0'; s++) + count += (*s == c); + return count; +} + +static const char * const * +effectivePathv(JNIEnv *env) +{ + char *p; + int i; + const char *path = effectivePath(); + int count = countOccurrences(path, ':') + 1; + size_t pathvsize = sizeof(const char *) * (count+1); + size_t pathsize = strlen(path) + 1; + const char **pathv = (const char **) xmalloc(env, pathvsize + pathsize); + + if (pathv == NULL) + return NULL; + p = (char *) pathv + pathvsize; + memcpy(p, path, pathsize); + /* split PATH by replacing ':' with NULs; empty components => "." */ + for (i = 0; i < count; i++) { + char *q = p + strcspn(p, ":"); + pathv[i] = (p == q) ? "." : p; + *q = '\0'; + p = q + 1; + } + pathv[count] = NULL; + return pathv; +} + +JNIEXPORT void JNICALL +Java_java_lang_ProcessImpl_init(JNIEnv *env, jclass clazz) +{ + parentPathv = effectivePathv(env); + CHECK_NULL(parentPathv); + setSIGCHLDHandler(env); +} + + +#ifndef WIFEXITED +#define WIFEXITED(status) (((status)&0xFF) == 0) +#endif + +#ifndef WEXITSTATUS +#define WEXITSTATUS(status) (((status)>>8)&0xFF) +#endif + +#ifndef WIFSIGNALED +#define WIFSIGNALED(status) (((status)&0xFF) > 0 && ((status)&0xFF00) == 0) +#endif + +#ifndef WTERMSIG +#define WTERMSIG(status) ((status)&0x7F) +#endif + +/* Block until a child process exits and return its exit code. + Note, can only be called once for any given pid. */ +JNIEXPORT jint JNICALL +Java_java_lang_ProcessImpl_waitForProcessExit(JNIEnv* env, + jobject junk, + jint pid) +{ + /* We used to use waitid() on Solaris, waitpid() on Linux, but + * waitpid() is more standard, so use it on all POSIX platforms. */ + int status; + /* Wait for the child process to exit. This returns immediately if + the child has already exited. */ + while (waitpid(pid, &status, 0) < 0) { + switch (errno) { + case ECHILD: return 0; + case EINTR: break; + default: return -1; + } + } + + if (WIFEXITED(status)) { + /* + * The child exited normally; get its exit code. + */ + return WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + /* The child exited because of a signal. + * The best value to return is 0x80 + signal number, + * because that is what all Unix shells do, and because + * it allows callers to distinguish between process exit and + * process death by signal. + * Unfortunately, the historical behavior on Solaris is to return + * the signal number, and we preserve this for compatibility. */ +#ifdef __solaris__ + return WTERMSIG(status); +#else + return 0x80 + WTERMSIG(status); +#endif + } else { + /* + * Unknown exit code; pass it through. + */ + return status; + } +} + +static const char * +getBytes(JNIEnv *env, jbyteArray arr) +{ + return arr == NULL ? NULL : + (const char*) (*env)->GetByteArrayElements(env, arr, NULL); +} + +static void +releaseBytes(JNIEnv *env, jbyteArray arr, const char* parr) +{ + if (parr != NULL) + (*env)->ReleaseByteArrayElements(env, arr, (jbyte*) parr, JNI_ABORT); +} + +static void +throwIOException(JNIEnv *env, int errnum, const char *defaultDetail) +{ + static const char * const format = "error=%d, %s"; + const char *detail = defaultDetail; + char *errmsg; + jstring s; + + if (errnum != 0) { + const char *s = strerror(errnum); + if (strcmp(s, "Unknown error") != 0) + detail = s; + } + /* ASCII Decimal representation uses 2.4 times as many bits as binary. */ + errmsg = NEW(char, strlen(format) + strlen(detail) + 3 * sizeof(errnum)); + if (errmsg == NULL) + return; + + sprintf(errmsg, format, errnum, detail); + s = JNU_NewStringPlatform(env, errmsg); + if (s != NULL) { + jobject x = JNU_NewObjectByName(env, "java/io/IOException", + "(Ljava/lang/String;)V", s); + if (x != NULL) + (*env)->Throw(env, x); + } + free(errmsg); +} + +#ifdef DEBUG_PROCESS +/* Debugging process code is difficult; where to write debug output? */ +static void +debugPrint(char *format, ...) +{ + FILE *tty = fopen("/dev/tty", "w"); + va_list ap; + va_start(ap, format); + vfprintf(tty, format, ap); + va_end(ap); + fclose(tty); +} +#endif /* DEBUG_PROCESS */ + +static void +copyPipe(int from[2], int to[2]) +{ + to[0] = from[0]; + to[1] = from[1]; +} + +/* arg is an array of pointers to 0 terminated strings. array is terminated + * by a null element. + * + * *nelems and *nbytes receive the number of elements of array (incl 0) + * and total number of bytes (incl. 0) + * Note. An empty array will have one null element + * But if arg is null, then *nelems set to 0, and *nbytes to 0 + */ +static void arraysize(const char * const *arg, int *nelems, int *nbytes) +{ + int i, bytes, count; + const char * const *a = arg; + char *p; + int *q; + if (arg == 0) { + *nelems = 0; + *nbytes = 0; + return; + } + /* count the array elements and number of bytes */ + for (count=0, bytes=0; *a != 0; count++, a++) { + bytes += strlen(*a)+1; + } + *nbytes = bytes; + *nelems = count+1; +} + +/* copy the strings from arg[] into buf, starting at given offset + * return new offset to next free byte + */ +static int copystrings(char *buf, int offset, const char * const *arg) { + char *p; + const char * const *a; + int count=0; + + if (arg == 0) { + return offset; + } + for (p=buf+offset, a=arg; *a != 0; a++) { + int len = strlen(*a) +1; + memcpy(p, *a, len); + p += len; + count += len; + } + return offset+count; +} + +/** + * We are unusually paranoid; use of clone/vfork is + * especially likely to tickle gcc/glibc bugs. + */ +#ifdef __attribute_noinline__ /* See: sys/cdefs.h */ +__attribute_noinline__ +#endif + +#define START_CHILD_USE_CLONE 0 /* clone() currently disabled; see above. */ + +#ifdef START_CHILD_USE_CLONE +static pid_t +cloneChild(ChildStuff *c) { +#ifdef __linux__ +#define START_CHILD_CLONE_STACK_SIZE (64 * 1024) + /* + * See clone(2). + * Instead of worrying about which direction the stack grows, just + * allocate twice as much and start the stack in the middle. + */ + if ((c->clone_stack = malloc(2 * START_CHILD_CLONE_STACK_SIZE)) == NULL) + /* errno will be set to ENOMEM */ + return -1; + return clone(childProcess, + c->clone_stack + START_CHILD_CLONE_STACK_SIZE, + CLONE_VFORK | CLONE_VM | SIGCHLD, c); +#else +/* not available on Solaris / Mac */ + assert(0); + return -1; +#endif +} +#endif + +static pid_t +vforkChild(ChildStuff *c) { + volatile pid_t resultPid; + + /* + * We separate the call to vfork into a separate function to make + * very sure to keep stack of child from corrupting stack of parent, + * as suggested by the scary gcc warning: + * warning: variable 'foo' might be clobbered by 'longjmp' or 'vfork' + */ + resultPid = vfork(); + + if (resultPid == 0) { + childProcess(c); + } + assert(resultPid != 0); /* childProcess never returns */ + return resultPid; +} + +static pid_t +forkChild(ChildStuff *c) { + pid_t resultPid; + + /* + * From Solaris fork(2): In Solaris 10, a call to fork() is + * identical to a call to fork1(); only the calling thread is + * replicated in the child process. This is the POSIX-specified + * behavior for fork(). + */ + resultPid = fork(); + + if (resultPid == 0) { + childProcess(c); + } + assert(resultPid != 0); /* childProcess never returns */ + return resultPid; +} + +#if defined(__solaris__) || defined(_ALLBSD_SOURCE) || defined(_AIX) +static pid_t +spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath) { + pid_t resultPid; + jboolean isCopy; + int i, offset, rval, bufsize, magic; + char *buf, buf1[16]; + char *hlpargs[2]; + SpawnInfo sp; + + /* need to tell helper which fd is for receiving the childstuff + * and which fd to send response back on + */ + snprintf(buf1, sizeof(buf1), "%d:%d", c->childenv[0], c->fail[1]); + /* put the fd string as argument to the helper cmd */ + hlpargs[0] = buf1; + hlpargs[1] = 0; + + /* Following items are sent down the pipe to the helper + * after it is spawned. + * All strings are null terminated. All arrays of strings + * have an empty string for termination. + * - the ChildStuff struct + * - the SpawnInfo struct + * - the argv strings array + * - the envv strings array + * - the home directory string + * - the parentPath string + * - the parentPathv array + */ + /* First calculate the sizes */ + arraysize(c->argv, &sp.nargv, &sp.argvBytes); + bufsize = sp.argvBytes; + arraysize(c->envv, &sp.nenvv, &sp.envvBytes); + bufsize += sp.envvBytes; + sp.dirlen = c->pdir == 0 ? 0 : strlen(c->pdir)+1; + bufsize += sp.dirlen; + arraysize(parentPathv, &sp.nparentPathv, &sp.parentPathvBytes); + bufsize += sp.parentPathvBytes; + /* We need to clear FD_CLOEXEC if set in the fds[]. + * Files are created FD_CLOEXEC in Java. + * Otherwise, they will be closed when the target gets exec'd */ + for (i=0; i<3; i++) { + if (c->fds[i] != -1) { + int flags = fcntl(c->fds[i], F_GETFD); + if (flags & FD_CLOEXEC) { + fcntl(c->fds[i], F_SETFD, flags & (~1)); + } + } + } + + rval = posix_spawn(&resultPid, helperpath, 0, 0, (char * const *) hlpargs, environ); + + if (rval != 0) { + return -1; + } + + /* now the lengths are known, copy the data */ + buf = NEW(char, bufsize); + if (buf == 0) { + return -1; + } + offset = copystrings(buf, 0, &c->argv[0]); + offset = copystrings(buf, offset, &c->envv[0]); + memcpy(buf+offset, c->pdir, sp.dirlen); + offset += sp.dirlen; + offset = copystrings(buf, offset, parentPathv); + assert(offset == bufsize); + + magic = magicNumber(); + + /* write the two structs and the data buffer */ + write(c->childenv[1], (char *)&magic, sizeof(magic)); // magic number first + write(c->childenv[1], (char *)c, sizeof(*c)); + write(c->childenv[1], (char *)&sp, sizeof(sp)); + write(c->childenv[1], buf, bufsize); + free(buf); + + /* In this mode an external main() in invoked which calls back into + * childProcess() in this file, rather than directly + * via the statement below */ + return resultPid; +} +#endif + +/* + * Start a child process running function childProcess. + * This function only returns in the parent. + */ +static pid_t +startChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath) { + switch (c->mode) { + case MODE_VFORK: + return vforkChild(c); + case MODE_FORK: + return forkChild(c); +#if defined(__solaris__) || defined(_ALLBSD_SOURCE) || defined(_AIX) + case MODE_POSIX_SPAWN: + return spawnChild(env, process, c, helperpath); +#endif + default: + return -1; + } +} + +JNIEXPORT jint JNICALL +Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env, + jobject process, + jint mode, + jbyteArray helperpath, + jbyteArray prog, + jbyteArray argBlock, jint argc, + jbyteArray envBlock, jint envc, + jbyteArray dir, + jintArray std_fds, + jboolean redirectErrorStream) +{ + int errnum; + int resultPid = -1; + int in[2], out[2], err[2], fail[2], childenv[2]; + jint *fds = NULL; + const char *phelperpath = NULL; + const char *pprog = NULL; + const char *pargBlock = NULL; + const char *penvBlock = NULL; + ChildStuff *c; + + in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1; + childenv[0] = childenv[1] = -1; + + if ((c = NEW(ChildStuff, 1)) == NULL) return -1; + c->argv = NULL; + c->envv = NULL; + c->pdir = NULL; + c->clone_stack = NULL; + + /* Convert prog + argBlock into a char ** argv. + * Add one word room for expansion of argv for use by + * execve_as_traditional_shell_script. + * This word is also used when using spawn mode + */ + assert(prog != NULL && argBlock != NULL); + if ((phelperpath = getBytes(env, helperpath)) == NULL) goto Catch; + if ((pprog = getBytes(env, prog)) == NULL) goto Catch; + if ((pargBlock = getBytes(env, argBlock)) == NULL) goto Catch; + if ((c->argv = NEW(const char *, argc + 3)) == NULL) goto Catch; + c->argv[0] = pprog; + c->argc = argc + 2; + initVectorFromBlock(c->argv+1, pargBlock, argc); + + if (envBlock != NULL) { + /* Convert envBlock into a char ** envv */ + if ((penvBlock = getBytes(env, envBlock)) == NULL) goto Catch; + if ((c->envv = NEW(const char *, envc + 1)) == NULL) goto Catch; + initVectorFromBlock(c->envv, penvBlock, envc); + } + + if (dir != NULL) { + if ((c->pdir = getBytes(env, dir)) == NULL) goto Catch; + } + + assert(std_fds != NULL); + fds = (*env)->GetIntArrayElements(env, std_fds, NULL); + if (fds == NULL) goto Catch; + + if ((fds[0] == -1 && pipe(in) < 0) || + (fds[1] == -1 && pipe(out) < 0) || + (fds[2] == -1 && pipe(err) < 0) || + (pipe(childenv) < 0) || + (pipe(fail) < 0)) { + throwIOException(env, errno, "Bad file descriptor"); + goto Catch; + } + c->fds[0] = fds[0]; + c->fds[1] = fds[1]; + c->fds[2] = fds[2]; + + copyPipe(in, c->in); + copyPipe(out, c->out); + copyPipe(err, c->err); + copyPipe(fail, c->fail); + copyPipe(childenv, c->childenv); + + c->redirectErrorStream = redirectErrorStream; + c->mode = mode; + + resultPid = startChild(env, process, c, phelperpath); + assert(resultPid != 0); + + if (resultPid < 0) { + switch (c->mode) { + case MODE_VFORK: + throwIOException(env, errno, "vfork failed"); + break; + case MODE_FORK: + throwIOException(env, errno, "fork failed"); + break; + case MODE_POSIX_SPAWN: + throwIOException(env, errno, "spawn failed"); + break; + } + goto Catch; + } + close(fail[1]); fail[1] = -1; /* See: WhyCantJohnnyExec (childproc.c) */ + + switch (readFully(fail[0], &errnum, sizeof(errnum))) { + case 0: break; /* Exec succeeded */ + case sizeof(errnum): + waitpid(resultPid, NULL, 0); + throwIOException(env, errnum, "Exec failed"); + goto Catch; + default: + throwIOException(env, errno, "Read failed"); + goto Catch; + } + + fds[0] = (in [1] != -1) ? in [1] : -1; + fds[1] = (out[0] != -1) ? out[0] : -1; + fds[2] = (err[0] != -1) ? err[0] : -1; + + Finally: + free(c->clone_stack); + + /* Always clean up the child's side of the pipes */ + closeSafely(in [0]); + closeSafely(out[1]); + closeSafely(err[1]); + + /* Always clean up fail and childEnv descriptors */ + closeSafely(fail[0]); + closeSafely(fail[1]); + closeSafely(childenv[0]); + closeSafely(childenv[1]); + + releaseBytes(env, helperpath, phelperpath); + releaseBytes(env, prog, pprog); + releaseBytes(env, argBlock, pargBlock); + releaseBytes(env, envBlock, penvBlock); + releaseBytes(env, dir, c->pdir); + + free(c->argv); + free(c->envv); + free(c); + + if (fds != NULL) + (*env)->ReleaseIntArrayElements(env, std_fds, fds, 0); + + return resultPid; + + Catch: + /* Clean up the parent's side of the pipes in case of failure only */ + closeSafely(in [1]); in[1] = -1; + closeSafely(out[0]); out[0] = -1; + closeSafely(err[0]); err[0] = -1; + goto Finally; +} + +JNIEXPORT void JNICALL +Java_java_lang_ProcessImpl_destroyProcess(JNIEnv *env, + jobject junk, + jint pid, + jboolean force) +{ + int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM; + kill(pid, sig); +} diff -r 2f79ecb05ada -r fe8344cf6496 jdk/src/java.base/unix/native/libjava/UNIXProcess_md.c --- a/jdk/src/java.base/unix/native/libjava/UNIXProcess_md.c Mon Jan 26 21:55:05 2015 +0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,724 +0,0 @@ -/* - * Copyright (c) 1995, 2013, 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. - */ - -#undef _LARGEFILE64_SOURCE -#define _LARGEFILE64_SOURCE 1 - -#include "jni.h" -#include "jvm.h" -#include "jvm_md.h" -#include "jni_util.h" -#include "io_util.h" - -/* - * Platform-specific support for java.lang.Process - */ -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__solaris__) || defined(_ALLBSD_SOURCE) || defined(_AIX) -#include -#endif - -#include "childproc.h" - -/* - * There are 4 possible strategies we might use to "fork": - * - * - fork(2). Very portable and reliable but subject to - * failure due to overcommit (see the documentation on - * /proc/sys/vm/overcommit_memory in Linux proc(5)). - * This is the ancient problem of spurious failure whenever a large - * process starts a small subprocess. - * - * - vfork(). Using this is scary because all relevant man pages - * contain dire warnings, e.g. Linux vfork(2). But at least it's - * documented in the glibc docs and is standardized by XPG4. - * http://www.opengroup.org/onlinepubs/000095399/functions/vfork.html - * On Linux, one might think that vfork() would be implemented using - * the clone system call with flag CLONE_VFORK, but in fact vfork is - * a separate system call (which is a good sign, suggesting that - * vfork will continue to be supported at least on Linux). - * Another good sign is that glibc implements posix_spawn using - * vfork whenever possible. Note that we cannot use posix_spawn - * ourselves because there's no reliable way to close all inherited - * file descriptors. - * - * - clone() with flags CLONE_VM but not CLONE_THREAD. clone() is - * Linux-specific, but this ought to work - at least the glibc - * sources contain code to handle different combinations of CLONE_VM - * and CLONE_THREAD. However, when this was implemented, it - * appeared to fail on 32-bit i386 (but not 64-bit x86_64) Linux with - * the simple program - * Runtime.getRuntime().exec("/bin/true").waitFor(); - * with: - * # Internal Error (os_linux_x86.cpp:683), pid=19940, tid=2934639536 - * # Error: pthread_getattr_np failed with errno = 3 (ESRCH) - * We believe this is a glibc bug, reported here: - * http://sources.redhat.com/bugzilla/show_bug.cgi?id=10311 - * but the glibc maintainers closed it as WONTFIX. - * - * - posix_spawn(). While posix_spawn() is a fairly elaborate and - * complicated system call, it can't quite do everything that the old - * fork()/exec() combination can do, so the only feasible way to do - * this, is to use posix_spawn to launch a new helper executable - * "jprochelper", which in turn execs the target (after cleaning - * up file-descriptors etc.) The end result is the same as before, - * a child process linked to the parent in the same way, but it - * avoids the problem of duplicating the parent (VM) process - * address space temporarily, before launching the target command. - * - * Based on the above analysis, we are currently using vfork() on - * Linux and spawn() on other Unix systems, but the code to use clone() - * and fork() remains. - */ - - -static void -setSIGCHLDHandler(JNIEnv *env) -{ - /* There is a subtle difference between having the signal handler - * for SIGCHLD be SIG_DFL and SIG_IGN. We cannot obtain process - * termination information for child processes if the signal - * handler is SIG_IGN. It must be SIG_DFL. - * - * We used to set the SIGCHLD handler only on Linux, but it's - * safest to set it unconditionally. - * - * Consider what happens if java's parent process sets the SIGCHLD - * handler to SIG_IGN. Normally signal handlers are inherited by - * children, but SIGCHLD is a controversial case. Solaris appears - * to always reset it to SIG_DFL, but this behavior may be - * non-standard-compliant, and we shouldn't rely on it. - * - * References: - * http://www.opengroup.org/onlinepubs/7908799/xsh/exec.html - * http://www.pasc.org/interps/unofficial/db/p1003.1/pasc-1003.1-132.html - */ - struct sigaction sa; - sa.sa_handler = SIG_DFL; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; - if (sigaction(SIGCHLD, &sa, NULL) < 0) - JNU_ThrowInternalError(env, "Can't set SIGCHLD handler"); -} - -static void* -xmalloc(JNIEnv *env, size_t size) -{ - void *p = malloc(size); - if (p == NULL) - JNU_ThrowOutOfMemoryError(env, NULL); - return p; -} - -#define NEW(type, n) ((type *) xmalloc(env, (n) * sizeof(type))) - -/** - * If PATH is not defined, the OS provides some default value. - * Unfortunately, there's no portable way to get this value. - * Fortunately, it's only needed if the child has PATH while we do not. - */ -static const char* -defaultPath(void) -{ -#ifdef __solaris__ - /* These really are the Solaris defaults! */ - return (geteuid() == 0 || getuid() == 0) ? - "/usr/xpg4/bin:/usr/ccs/bin:/usr/bin:/opt/SUNWspro/bin:/usr/sbin" : - "/usr/xpg4/bin:/usr/ccs/bin:/usr/bin:/opt/SUNWspro/bin:"; -#else - return ":/bin:/usr/bin"; /* glibc */ -#endif -} - -static const char* -effectivePath(void) -{ - const char *s = getenv("PATH"); - return (s != NULL) ? s : defaultPath(); -} - -static int -countOccurrences(const char *s, char c) -{ - int count; - for (count = 0; *s != '\0'; s++) - count += (*s == c); - return count; -} - -static const char * const * -effectivePathv(JNIEnv *env) -{ - char *p; - int i; - const char *path = effectivePath(); - int count = countOccurrences(path, ':') + 1; - size_t pathvsize = sizeof(const char *) * (count+1); - size_t pathsize = strlen(path) + 1; - const char **pathv = (const char **) xmalloc(env, pathvsize + pathsize); - - if (pathv == NULL) - return NULL; - p = (char *) pathv + pathvsize; - memcpy(p, path, pathsize); - /* split PATH by replacing ':' with NULs; empty components => "." */ - for (i = 0; i < count; i++) { - char *q = p + strcspn(p, ":"); - pathv[i] = (p == q) ? "." : p; - *q = '\0'; - p = q + 1; - } - pathv[count] = NULL; - return pathv; -} - -JNIEXPORT void JNICALL -Java_java_lang_UNIXProcess_init(JNIEnv *env, jclass clazz) -{ - parentPathv = effectivePathv(env); - CHECK_NULL(parentPathv); - setSIGCHLDHandler(env); -} - - -#ifndef WIFEXITED -#define WIFEXITED(status) (((status)&0xFF) == 0) -#endif - -#ifndef WEXITSTATUS -#define WEXITSTATUS(status) (((status)>>8)&0xFF) -#endif - -#ifndef WIFSIGNALED -#define WIFSIGNALED(status) (((status)&0xFF) > 0 && ((status)&0xFF00) == 0) -#endif - -#ifndef WTERMSIG -#define WTERMSIG(status) ((status)&0x7F) -#endif - -/* Block until a child process exits and return its exit code. - Note, can only be called once for any given pid. */ -JNIEXPORT jint JNICALL -Java_java_lang_UNIXProcess_waitForProcessExit(JNIEnv* env, - jobject junk, - jint pid) -{ - /* We used to use waitid() on Solaris, waitpid() on Linux, but - * waitpid() is more standard, so use it on all POSIX platforms. */ - int status; - /* Wait for the child process to exit. This returns immediately if - the child has already exited. */ - while (waitpid(pid, &status, 0) < 0) { - switch (errno) { - case ECHILD: return 0; - case EINTR: break; - default: return -1; - } - } - - if (WIFEXITED(status)) { - /* - * The child exited normally; get its exit code. - */ - return WEXITSTATUS(status); - } else if (WIFSIGNALED(status)) { - /* The child exited because of a signal. - * The best value to return is 0x80 + signal number, - * because that is what all Unix shells do, and because - * it allows callers to distinguish between process exit and - * process death by signal. - * Unfortunately, the historical behavior on Solaris is to return - * the signal number, and we preserve this for compatibility. */ -#ifdef __solaris__ - return WTERMSIG(status); -#else - return 0x80 + WTERMSIG(status); -#endif - } else { - /* - * Unknown exit code; pass it through. - */ - return status; - } -} - -static const char * -getBytes(JNIEnv *env, jbyteArray arr) -{ - return arr == NULL ? NULL : - (const char*) (*env)->GetByteArrayElements(env, arr, NULL); -} - -static void -releaseBytes(JNIEnv *env, jbyteArray arr, const char* parr) -{ - if (parr != NULL) - (*env)->ReleaseByteArrayElements(env, arr, (jbyte*) parr, JNI_ABORT); -} - -static void -throwIOException(JNIEnv *env, int errnum, const char *defaultDetail) -{ - static const char * const format = "error=%d, %s"; - const char *detail = defaultDetail; - char *errmsg; - jstring s; - - if (errnum != 0) { - const char *s = strerror(errnum); - if (strcmp(s, "Unknown error") != 0) - detail = s; - } - /* ASCII Decimal representation uses 2.4 times as many bits as binary. */ - errmsg = NEW(char, strlen(format) + strlen(detail) + 3 * sizeof(errnum)); - if (errmsg == NULL) - return; - - sprintf(errmsg, format, errnum, detail); - s = JNU_NewStringPlatform(env, errmsg); - if (s != NULL) { - jobject x = JNU_NewObjectByName(env, "java/io/IOException", - "(Ljava/lang/String;)V", s); - if (x != NULL) - (*env)->Throw(env, x); - } - free(errmsg); -} - -#ifdef DEBUG_PROCESS -/* Debugging process code is difficult; where to write debug output? */ -static void -debugPrint(char *format, ...) -{ - FILE *tty = fopen("/dev/tty", "w"); - va_list ap; - va_start(ap, format); - vfprintf(tty, format, ap); - va_end(ap); - fclose(tty); -} -#endif /* DEBUG_PROCESS */ - -static void -copyPipe(int from[2], int to[2]) -{ - to[0] = from[0]; - to[1] = from[1]; -} - -/* arg is an array of pointers to 0 terminated strings. array is terminated - * by a null element. - * - * *nelems and *nbytes receive the number of elements of array (incl 0) - * and total number of bytes (incl. 0) - * Note. An empty array will have one null element - * But if arg is null, then *nelems set to 0, and *nbytes to 0 - */ -static void arraysize(const char * const *arg, int *nelems, int *nbytes) -{ - int i, bytes, count; - const char * const *a = arg; - char *p; - int *q; - if (arg == 0) { - *nelems = 0; - *nbytes = 0; - return; - } - /* count the array elements and number of bytes */ - for (count=0, bytes=0; *a != 0; count++, a++) { - bytes += strlen(*a)+1; - } - *nbytes = bytes; - *nelems = count+1; -} - -/* copy the strings from arg[] into buf, starting at given offset - * return new offset to next free byte - */ -static int copystrings(char *buf, int offset, const char * const *arg) { - char *p; - const char * const *a; - int count=0; - - if (arg == 0) { - return offset; - } - for (p=buf+offset, a=arg; *a != 0; a++) { - int len = strlen(*a) +1; - memcpy(p, *a, len); - p += len; - count += len; - } - return offset+count; -} - -/** - * We are unusually paranoid; use of clone/vfork is - * especially likely to tickle gcc/glibc bugs. - */ -#ifdef __attribute_noinline__ /* See: sys/cdefs.h */ -__attribute_noinline__ -#endif - -#define START_CHILD_USE_CLONE 0 /* clone() currently disabled; see above. */ - -#ifdef START_CHILD_USE_CLONE -static pid_t -cloneChild(ChildStuff *c) { -#ifdef __linux__ -#define START_CHILD_CLONE_STACK_SIZE (64 * 1024) - /* - * See clone(2). - * Instead of worrying about which direction the stack grows, just - * allocate twice as much and start the stack in the middle. - */ - if ((c->clone_stack = malloc(2 * START_CHILD_CLONE_STACK_SIZE)) == NULL) - /* errno will be set to ENOMEM */ - return -1; - return clone(childProcess, - c->clone_stack + START_CHILD_CLONE_STACK_SIZE, - CLONE_VFORK | CLONE_VM | SIGCHLD, c); -#else -/* not available on Solaris / Mac */ - assert(0); - return -1; -#endif -} -#endif - -static pid_t -vforkChild(ChildStuff *c) { - volatile pid_t resultPid; - - /* - * We separate the call to vfork into a separate function to make - * very sure to keep stack of child from corrupting stack of parent, - * as suggested by the scary gcc warning: - * warning: variable 'foo' might be clobbered by 'longjmp' or 'vfork' - */ - resultPid = vfork(); - - if (resultPid == 0) { - childProcess(c); - } - assert(resultPid != 0); /* childProcess never returns */ - return resultPid; -} - -static pid_t -forkChild(ChildStuff *c) { - pid_t resultPid; - - /* - * From Solaris fork(2): In Solaris 10, a call to fork() is - * identical to a call to fork1(); only the calling thread is - * replicated in the child process. This is the POSIX-specified - * behavior for fork(). - */ - resultPid = fork(); - - if (resultPid == 0) { - childProcess(c); - } - assert(resultPid != 0); /* childProcess never returns */ - return resultPid; -} - -#if defined(__solaris__) || defined(_ALLBSD_SOURCE) || defined(_AIX) -static pid_t -spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath) { - pid_t resultPid; - jboolean isCopy; - int i, offset, rval, bufsize, magic; - char *buf, buf1[16]; - char *hlpargs[2]; - SpawnInfo sp; - - /* need to tell helper which fd is for receiving the childstuff - * and which fd to send response back on - */ - snprintf(buf1, sizeof(buf1), "%d:%d", c->childenv[0], c->fail[1]); - /* put the fd string as argument to the helper cmd */ - hlpargs[0] = buf1; - hlpargs[1] = 0; - - /* Following items are sent down the pipe to the helper - * after it is spawned. - * All strings are null terminated. All arrays of strings - * have an empty string for termination. - * - the ChildStuff struct - * - the SpawnInfo struct - * - the argv strings array - * - the envv strings array - * - the home directory string - * - the parentPath string - * - the parentPathv array - */ - /* First calculate the sizes */ - arraysize(c->argv, &sp.nargv, &sp.argvBytes); - bufsize = sp.argvBytes; - arraysize(c->envv, &sp.nenvv, &sp.envvBytes); - bufsize += sp.envvBytes; - sp.dirlen = c->pdir == 0 ? 0 : strlen(c->pdir)+1; - bufsize += sp.dirlen; - arraysize(parentPathv, &sp.nparentPathv, &sp.parentPathvBytes); - bufsize += sp.parentPathvBytes; - /* We need to clear FD_CLOEXEC if set in the fds[]. - * Files are created FD_CLOEXEC in Java. - * Otherwise, they will be closed when the target gets exec'd */ - for (i=0; i<3; i++) { - if (c->fds[i] != -1) { - int flags = fcntl(c->fds[i], F_GETFD); - if (flags & FD_CLOEXEC) { - fcntl(c->fds[i], F_SETFD, flags & (~1)); - } - } - } - - rval = posix_spawn(&resultPid, helperpath, 0, 0, (char * const *) hlpargs, environ); - - if (rval != 0) { - return -1; - } - - /* now the lengths are known, copy the data */ - buf = NEW(char, bufsize); - if (buf == 0) { - return -1; - } - offset = copystrings(buf, 0, &c->argv[0]); - offset = copystrings(buf, offset, &c->envv[0]); - memcpy(buf+offset, c->pdir, sp.dirlen); - offset += sp.dirlen; - offset = copystrings(buf, offset, parentPathv); - assert(offset == bufsize); - - magic = magicNumber(); - - /* write the two structs and the data buffer */ - write(c->childenv[1], (char *)&magic, sizeof(magic)); // magic number first - write(c->childenv[1], (char *)c, sizeof(*c)); - write(c->childenv[1], (char *)&sp, sizeof(sp)); - write(c->childenv[1], buf, bufsize); - free(buf); - - /* In this mode an external main() in invoked which calls back into - * childProcess() in this file, rather than directly - * via the statement below */ - return resultPid; -} -#endif - -/* - * Start a child process running function childProcess. - * This function only returns in the parent. - */ -static pid_t -startChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath) { - switch (c->mode) { - case MODE_VFORK: - return vforkChild(c); - case MODE_FORK: - return forkChild(c); -#if defined(__solaris__) || defined(_ALLBSD_SOURCE) || defined(_AIX) - case MODE_POSIX_SPAWN: - return spawnChild(env, process, c, helperpath); -#endif - default: - return -1; - } -} - -JNIEXPORT jint JNICALL -Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, - jobject process, - jint mode, - jbyteArray helperpath, - jbyteArray prog, - jbyteArray argBlock, jint argc, - jbyteArray envBlock, jint envc, - jbyteArray dir, - jintArray std_fds, - jboolean redirectErrorStream) -{ - int errnum; - int resultPid = -1; - int in[2], out[2], err[2], fail[2], childenv[2]; - jint *fds = NULL; - const char *phelperpath = NULL; - const char *pprog = NULL; - const char *pargBlock = NULL; - const char *penvBlock = NULL; - ChildStuff *c; - - in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1; - childenv[0] = childenv[1] = -1; - - if ((c = NEW(ChildStuff, 1)) == NULL) return -1; - c->argv = NULL; - c->envv = NULL; - c->pdir = NULL; - c->clone_stack = NULL; - - /* Convert prog + argBlock into a char ** argv. - * Add one word room for expansion of argv for use by - * execve_as_traditional_shell_script. - * This word is also used when using spawn mode - */ - assert(prog != NULL && argBlock != NULL); - if ((phelperpath = getBytes(env, helperpath)) == NULL) goto Catch; - if ((pprog = getBytes(env, prog)) == NULL) goto Catch; - if ((pargBlock = getBytes(env, argBlock)) == NULL) goto Catch; - if ((c->argv = NEW(const char *, argc + 3)) == NULL) goto Catch; - c->argv[0] = pprog; - c->argc = argc + 2; - initVectorFromBlock(c->argv+1, pargBlock, argc); - - if (envBlock != NULL) { - /* Convert envBlock into a char ** envv */ - if ((penvBlock = getBytes(env, envBlock)) == NULL) goto Catch; - if ((c->envv = NEW(const char *, envc + 1)) == NULL) goto Catch; - initVectorFromBlock(c->envv, penvBlock, envc); - } - - if (dir != NULL) { - if ((c->pdir = getBytes(env, dir)) == NULL) goto Catch; - } - - assert(std_fds != NULL); - fds = (*env)->GetIntArrayElements(env, std_fds, NULL); - if (fds == NULL) goto Catch; - - if ((fds[0] == -1 && pipe(in) < 0) || - (fds[1] == -1 && pipe(out) < 0) || - (fds[2] == -1 && pipe(err) < 0) || - (pipe(childenv) < 0) || - (pipe(fail) < 0)) { - throwIOException(env, errno, "Bad file descriptor"); - goto Catch; - } - c->fds[0] = fds[0]; - c->fds[1] = fds[1]; - c->fds[2] = fds[2]; - - copyPipe(in, c->in); - copyPipe(out, c->out); - copyPipe(err, c->err); - copyPipe(fail, c->fail); - copyPipe(childenv, c->childenv); - - c->redirectErrorStream = redirectErrorStream; - c->mode = mode; - - resultPid = startChild(env, process, c, phelperpath); - assert(resultPid != 0); - - if (resultPid < 0) { - switch (c->mode) { - case MODE_VFORK: - throwIOException(env, errno, "vfork failed"); - break; - case MODE_FORK: - throwIOException(env, errno, "fork failed"); - break; - case MODE_POSIX_SPAWN: - throwIOException(env, errno, "spawn failed"); - break; - } - goto Catch; - } - close(fail[1]); fail[1] = -1; /* See: WhyCantJohnnyExec (childproc.c) */ - - switch (readFully(fail[0], &errnum, sizeof(errnum))) { - case 0: break; /* Exec succeeded */ - case sizeof(errnum): - waitpid(resultPid, NULL, 0); - throwIOException(env, errnum, "Exec failed"); - goto Catch; - default: - throwIOException(env, errno, "Read failed"); - goto Catch; - } - - fds[0] = (in [1] != -1) ? in [1] : -1; - fds[1] = (out[0] != -1) ? out[0] : -1; - fds[2] = (err[0] != -1) ? err[0] : -1; - - Finally: - free(c->clone_stack); - - /* Always clean up the child's side of the pipes */ - closeSafely(in [0]); - closeSafely(out[1]); - closeSafely(err[1]); - - /* Always clean up fail and childEnv descriptors */ - closeSafely(fail[0]); - closeSafely(fail[1]); - closeSafely(childenv[0]); - closeSafely(childenv[1]); - - releaseBytes(env, helperpath, phelperpath); - releaseBytes(env, prog, pprog); - releaseBytes(env, argBlock, pargBlock); - releaseBytes(env, envBlock, penvBlock); - releaseBytes(env, dir, c->pdir); - - free(c->argv); - free(c->envv); - free(c); - - if (fds != NULL) - (*env)->ReleaseIntArrayElements(env, std_fds, fds, 0); - - return resultPid; - - Catch: - /* Clean up the parent's side of the pipes in case of failure only */ - closeSafely(in [1]); in[1] = -1; - closeSafely(out[0]); out[0] = -1; - closeSafely(err[0]); err[0] = -1; - goto Finally; -} - -JNIEXPORT void JNICALL -Java_java_lang_UNIXProcess_destroyProcess(JNIEnv *env, - jobject junk, - jint pid, - jboolean force) -{ - int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM; - kill(pid, sig); -} diff -r 2f79ecb05ada -r fe8344cf6496 jdk/src/java.base/unix/native/libjava/childproc.h --- a/jdk/src/java.base/unix/native/libjava/childproc.h Mon Jan 26 21:55:05 2015 +0800 +++ b/jdk/src/java.base/unix/native/libjava/childproc.h Mon Jan 26 10:55:27 2015 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2015, 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 @@ -79,7 +79,7 @@ } while((_result == -1) && (errno == EINTR)); \ } while(0) -/* These numbers must be the same as the Enum in UNIXProcess.java +/* These numbers must be the same as the Enum in ProcessImpl.java * Must be a better way of doing this. */ #define MODE_FORK 1 diff -r 2f79ecb05ada -r fe8344cf6496 jdk/test/java/lang/ProcessBuilder/Basic.java --- a/jdk/test/java/lang/ProcessBuilder/Basic.java Mon Jan 26 21:55:05 2015 +0800 +++ b/jdk/test/java/lang/ProcessBuilder/Basic.java Mon Jan 26 10:55:27 2015 -0500 @@ -2042,7 +2042,7 @@ final Object deferred; Class c = s.getClass(); if (c.getName().equals( - "java.lang.UNIXProcess$DeferredCloseInputStream")) + "java.lang.ProcessImpl$DeferredCloseInputStream")) { deferred = s; } else {