jdk/src/java.base/unix/classes/java/lang/UNIXProcess.java
changeset 28688 91c9e972559b
parent 28687 08067f67dde0
parent 28686 24873fd58c8a
child 28692 cf8fe951ca93
equal deleted inserted replaced
28687:08067f67dde0 28688:91c9e972559b
     1 /*
       
     2  * Copyright (c) 1995, 2014, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package java.lang;
       
    27 
       
    28 import java.io.BufferedInputStream;
       
    29 import java.io.BufferedOutputStream;
       
    30 import java.io.ByteArrayInputStream;
       
    31 import java.io.FileDescriptor;
       
    32 import java.io.FileInputStream;
       
    33 import java.io.FileOutputStream;
       
    34 import java.io.IOException;
       
    35 import java.io.InputStream;
       
    36 import java.io.OutputStream;
       
    37 import java.util.Arrays;
       
    38 import java.util.EnumSet;
       
    39 import java.util.Locale;
       
    40 import java.util.Set;
       
    41 import java.util.concurrent.Executors;
       
    42 import java.util.concurrent.Executor;
       
    43 import java.util.concurrent.ThreadFactory;
       
    44 import java.util.concurrent.TimeUnit;
       
    45 import java.security.AccessController;
       
    46 import static java.security.AccessController.doPrivileged;
       
    47 import java.security.PrivilegedAction;
       
    48 import java.security.PrivilegedActionException;
       
    49 import java.security.PrivilegedExceptionAction;
       
    50 
       
    51 /**
       
    52  * java.lang.Process subclass in the UNIX environment.
       
    53  *
       
    54  * @author Mario Wolczko and Ross Knippel.
       
    55  * @author Konstantin Kladko (ported to Linux and Bsd)
       
    56  * @author Martin Buchholz
       
    57  * @author Volker Simonis (ported to AIX)
       
    58  */
       
    59 final class UNIXProcess extends Process {
       
    60     private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
       
    61         = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
       
    62 
       
    63     private final int pid;
       
    64     private int exitcode;
       
    65     private boolean hasExited;
       
    66 
       
    67     private /* final */ OutputStream stdin;
       
    68     private /* final */ InputStream  stdout;
       
    69     private /* final */ InputStream  stderr;
       
    70 
       
    71     // only used on Solaris
       
    72     private /* final */ DeferredCloseInputStream stdout_inner_stream;
       
    73 
       
    74     private static enum LaunchMechanism {
       
    75         // order IS important!
       
    76         FORK,
       
    77         POSIX_SPAWN,
       
    78         VFORK
       
    79     }
       
    80 
       
    81     private static enum Platform {
       
    82 
       
    83         LINUX(LaunchMechanism.VFORK, LaunchMechanism.FORK),
       
    84 
       
    85         BSD(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK),
       
    86 
       
    87         SOLARIS(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK),
       
    88 
       
    89         AIX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK);
       
    90 
       
    91         final LaunchMechanism defaultLaunchMechanism;
       
    92         final Set<LaunchMechanism> validLaunchMechanisms;
       
    93 
       
    94         Platform(LaunchMechanism ... launchMechanisms) {
       
    95             this.defaultLaunchMechanism = launchMechanisms[0];
       
    96             this.validLaunchMechanisms =
       
    97                 EnumSet.copyOf(Arrays.asList(launchMechanisms));
       
    98         }
       
    99 
       
   100         @SuppressWarnings("fallthrough")
       
   101         private String helperPath(String javahome, String osArch) {
       
   102             switch (this) {
       
   103                 case SOLARIS:
       
   104                     if (osArch.equals("x86")) { osArch = "i386"; }
       
   105                     else if (osArch.equals("x86_64")) { osArch = "amd64"; }
       
   106                     // fall through...
       
   107                 case LINUX:
       
   108                 case AIX:
       
   109                     return javahome + "/lib/" + osArch + "/jspawnhelper";
       
   110 
       
   111                 case BSD:
       
   112                     return javahome + "/lib/jspawnhelper";
       
   113 
       
   114                 default:
       
   115                     throw new AssertionError("Unsupported platform: " + this);
       
   116             }
       
   117         }
       
   118 
       
   119         String helperPath() {
       
   120             return AccessController.doPrivileged(
       
   121                 (PrivilegedAction<String>) () ->
       
   122                     helperPath(System.getProperty("java.home"),
       
   123                                System.getProperty("os.arch"))
       
   124             );
       
   125         }
       
   126 
       
   127         LaunchMechanism launchMechanism() {
       
   128             return AccessController.doPrivileged(
       
   129                 (PrivilegedAction<LaunchMechanism>) () -> {
       
   130                     String s = System.getProperty(
       
   131                         "jdk.lang.Process.launchMechanism");
       
   132                     LaunchMechanism lm;
       
   133                     if (s == null) {
       
   134                         lm = defaultLaunchMechanism;
       
   135                         s = lm.name().toLowerCase(Locale.ENGLISH);
       
   136                     } else {
       
   137                         try {
       
   138                             lm = LaunchMechanism.valueOf(
       
   139                                 s.toUpperCase(Locale.ENGLISH));
       
   140                         } catch (IllegalArgumentException e) {
       
   141                             lm = null;
       
   142                         }
       
   143                     }
       
   144                     if (lm == null || !validLaunchMechanisms.contains(lm)) {
       
   145                         throw new Error(
       
   146                             s + " is not a supported " +
       
   147                             "process launch mechanism on this platform."
       
   148                         );
       
   149                     }
       
   150                     return lm;
       
   151                 }
       
   152             );
       
   153         }
       
   154 
       
   155         static Platform get() {
       
   156             String osName = AccessController.doPrivileged(
       
   157                 (PrivilegedAction<String>) () -> System.getProperty("os.name")
       
   158             );
       
   159 
       
   160             if (osName.equals("Linux")) { return LINUX; }
       
   161             if (osName.contains("OS X")) { return BSD; }
       
   162             if (osName.equals("SunOS")) { return SOLARIS; }
       
   163             if (osName.equals("AIX")) { return AIX; }
       
   164 
       
   165             throw new Error(osName + " is not a supported OS platform.");
       
   166         }
       
   167     }
       
   168 
       
   169     private static final Platform platform = Platform.get();
       
   170     private static final LaunchMechanism launchMechanism = platform.launchMechanism();
       
   171     private static final byte[] helperpath = toCString(platform.helperPath());
       
   172 
       
   173     private static byte[] toCString(String s) {
       
   174         if (s == null)
       
   175             return null;
       
   176         byte[] bytes = s.getBytes();
       
   177         byte[] result = new byte[bytes.length + 1];
       
   178         System.arraycopy(bytes, 0,
       
   179                          result, 0,
       
   180                          bytes.length);
       
   181         result[result.length-1] = (byte)0;
       
   182         return result;
       
   183     }
       
   184 
       
   185     /* this is for the reaping thread */
       
   186     private native int waitForProcessExit(int pid);
       
   187 
       
   188     /**
       
   189      * Creates a process. Depending on the {@code mode} flag, this is done by
       
   190      * one of the following mechanisms:
       
   191      * <pre>
       
   192      *   1 - fork(2) and exec(2)
       
   193      *   2 - posix_spawn(3P)
       
   194      *   3 - vfork(2) and exec(2)
       
   195      *
       
   196      *  (4 - clone(2) and exec(2) - obsolete and currently disabled in native code)
       
   197      * </pre>
       
   198      * @param fds an array of three file descriptors.
       
   199      *        Indexes 0, 1, and 2 correspond to standard input,
       
   200      *        standard output and standard error, respectively.  On
       
   201      *        input, a value of -1 means to create a pipe to connect
       
   202      *        child and parent processes.  On output, a value which
       
   203      *        is not -1 is the parent pipe fd corresponding to the
       
   204      *        pipe which has been created.  An element of this array
       
   205      *        is -1 on input if and only if it is <em>not</em> -1 on
       
   206      *        output.
       
   207      * @return the pid of the subprocess
       
   208      */
       
   209     private native int forkAndExec(int mode, byte[] helperpath,
       
   210                                    byte[] prog,
       
   211                                    byte[] argBlock, int argc,
       
   212                                    byte[] envBlock, int envc,
       
   213                                    byte[] dir,
       
   214                                    int[] fds,
       
   215                                    boolean redirectErrorStream)
       
   216         throws IOException;
       
   217 
       
   218     /**
       
   219      * The thread pool of "process reaper" daemon threads.
       
   220      */
       
   221     private static final Executor processReaperExecutor =
       
   222         doPrivileged((PrivilegedAction<Executor>) () -> {
       
   223 
       
   224             ThreadGroup tg = Thread.currentThread().getThreadGroup();
       
   225             while (tg.getParent() != null) tg = tg.getParent();
       
   226             ThreadGroup systemThreadGroup = tg;
       
   227 
       
   228             ThreadFactory threadFactory = grimReaper -> {
       
   229                 // Our thread stack requirement is quite modest.
       
   230                 Thread t = new Thread(systemThreadGroup, grimReaper,
       
   231                                       "process reaper", 32768);
       
   232                 t.setDaemon(true);
       
   233                 // A small attempt (probably futile) to avoid priority inversion
       
   234                 t.setPriority(Thread.MAX_PRIORITY);
       
   235                 return t;
       
   236             };
       
   237 
       
   238             return Executors.newCachedThreadPool(threadFactory);
       
   239         });
       
   240 
       
   241     UNIXProcess(final byte[] prog,
       
   242                 final byte[] argBlock, final int argc,
       
   243                 final byte[] envBlock, final int envc,
       
   244                 final byte[] dir,
       
   245                 final int[] fds,
       
   246                 final boolean redirectErrorStream)
       
   247             throws IOException {
       
   248 
       
   249         pid = forkAndExec(launchMechanism.ordinal() + 1,
       
   250                           helperpath,
       
   251                           prog,
       
   252                           argBlock, argc,
       
   253                           envBlock, envc,
       
   254                           dir,
       
   255                           fds,
       
   256                           redirectErrorStream);
       
   257 
       
   258         try {
       
   259             doPrivileged((PrivilegedExceptionAction<Void>) () -> {
       
   260                 initStreams(fds);
       
   261                 return null;
       
   262             });
       
   263         } catch (PrivilegedActionException ex) {
       
   264             throw (IOException) ex.getException();
       
   265         }
       
   266     }
       
   267 
       
   268     static FileDescriptor newFileDescriptor(int fd) {
       
   269         FileDescriptor fileDescriptor = new FileDescriptor();
       
   270         fdAccess.set(fileDescriptor, fd);
       
   271         return fileDescriptor;
       
   272     }
       
   273 
       
   274     void initStreams(int[] fds) throws IOException {
       
   275         switch (platform) {
       
   276             case LINUX:
       
   277             case BSD:
       
   278                 stdin = (fds[0] == -1) ?
       
   279                         ProcessBuilder.NullOutputStream.INSTANCE :
       
   280                         new ProcessPipeOutputStream(fds[0]);
       
   281 
       
   282                 stdout = (fds[1] == -1) ?
       
   283                          ProcessBuilder.NullInputStream.INSTANCE :
       
   284                          new ProcessPipeInputStream(fds[1]);
       
   285 
       
   286                 stderr = (fds[2] == -1) ?
       
   287                          ProcessBuilder.NullInputStream.INSTANCE :
       
   288                          new ProcessPipeInputStream(fds[2]);
       
   289 
       
   290                 processReaperExecutor.execute(() -> {
       
   291                     int exitcode = waitForProcessExit(pid);
       
   292 
       
   293                     synchronized (this) {
       
   294                         this.exitcode = exitcode;
       
   295                         this.hasExited = true;
       
   296                         this.notifyAll();
       
   297                     }
       
   298 
       
   299                     if (stdout instanceof ProcessPipeInputStream)
       
   300                         ((ProcessPipeInputStream) stdout).processExited();
       
   301 
       
   302                     if (stderr instanceof ProcessPipeInputStream)
       
   303                         ((ProcessPipeInputStream) stderr).processExited();
       
   304 
       
   305                     if (stdin instanceof ProcessPipeOutputStream)
       
   306                         ((ProcessPipeOutputStream) stdin).processExited();
       
   307                 });
       
   308                 break;
       
   309 
       
   310             case SOLARIS:
       
   311                 stdin = (fds[0] == -1) ?
       
   312                         ProcessBuilder.NullOutputStream.INSTANCE :
       
   313                         new BufferedOutputStream(
       
   314                             new FileOutputStream(newFileDescriptor(fds[0])));
       
   315 
       
   316                 stdout = (fds[1] == -1) ?
       
   317                          ProcessBuilder.NullInputStream.INSTANCE :
       
   318                          new BufferedInputStream(
       
   319                              stdout_inner_stream =
       
   320                                  new DeferredCloseInputStream(
       
   321                                      newFileDescriptor(fds[1])));
       
   322 
       
   323                 stderr = (fds[2] == -1) ?
       
   324                          ProcessBuilder.NullInputStream.INSTANCE :
       
   325                          new DeferredCloseInputStream(newFileDescriptor(fds[2]));
       
   326 
       
   327                 /*
       
   328                  * For each subprocess forked a corresponding reaper task
       
   329                  * is submitted.  That task is the only thread which waits
       
   330                  * for the subprocess to terminate and it doesn't hold any
       
   331                  * locks while doing so.  This design allows waitFor() and
       
   332                  * exitStatus() to be safely executed in parallel (and they
       
   333                  * need no native code).
       
   334                  */
       
   335                 processReaperExecutor.execute(() -> {
       
   336                     int exitcode = waitForProcessExit(pid);
       
   337 
       
   338                     synchronized (this) {
       
   339                         this.exitcode = exitcode;
       
   340                         this.hasExited = true;
       
   341                         this.notifyAll();
       
   342                     }
       
   343                 });
       
   344                 break;
       
   345 
       
   346             case AIX:
       
   347                 stdin = (fds[0] == -1) ?
       
   348                         ProcessBuilder.NullOutputStream.INSTANCE :
       
   349                         new ProcessPipeOutputStream(fds[0]);
       
   350 
       
   351                 stdout = (fds[1] == -1) ?
       
   352                          ProcessBuilder.NullInputStream.INSTANCE :
       
   353                          new DeferredCloseProcessPipeInputStream(fds[1]);
       
   354 
       
   355                 stderr = (fds[2] == -1) ?
       
   356                          ProcessBuilder.NullInputStream.INSTANCE :
       
   357                          new DeferredCloseProcessPipeInputStream(fds[2]);
       
   358 
       
   359                 processReaperExecutor.execute(() -> {
       
   360                     int exitcode = waitForProcessExit(pid);
       
   361 
       
   362                     synchronized (this) {
       
   363                         this.exitcode = exitcode;
       
   364                         this.hasExited = true;
       
   365                         this.notifyAll();
       
   366                     }
       
   367 
       
   368                     if (stdout instanceof DeferredCloseProcessPipeInputStream)
       
   369                         ((DeferredCloseProcessPipeInputStream) stdout).processExited();
       
   370 
       
   371                     if (stderr instanceof DeferredCloseProcessPipeInputStream)
       
   372                         ((DeferredCloseProcessPipeInputStream) stderr).processExited();
       
   373 
       
   374                     if (stdin instanceof ProcessPipeOutputStream)
       
   375                         ((ProcessPipeOutputStream) stdin).processExited();
       
   376                 });
       
   377                 break;
       
   378 
       
   379             default: throw new AssertionError("Unsupported platform: " + platform);
       
   380         }
       
   381     }
       
   382 
       
   383     public OutputStream getOutputStream() {
       
   384         return stdin;
       
   385     }
       
   386 
       
   387     public InputStream getInputStream() {
       
   388         return stdout;
       
   389     }
       
   390 
       
   391     public InputStream getErrorStream() {
       
   392         return stderr;
       
   393     }
       
   394 
       
   395     public synchronized int waitFor() throws InterruptedException {
       
   396         while (!hasExited) {
       
   397             wait();
       
   398         }
       
   399         return exitcode;
       
   400     }
       
   401 
       
   402     @Override
       
   403     public synchronized boolean waitFor(long timeout, TimeUnit unit)
       
   404         throws InterruptedException
       
   405     {
       
   406         if (hasExited) return true;
       
   407         if (timeout <= 0) return false;
       
   408 
       
   409         long remainingNanos = unit.toNanos(timeout);
       
   410         long deadline = System.nanoTime() + remainingNanos;
       
   411 
       
   412         do {
       
   413             // Round up to next millisecond
       
   414             wait(TimeUnit.NANOSECONDS.toMillis(remainingNanos + 999_999L));
       
   415             if (hasExited) {
       
   416                 return true;
       
   417             }
       
   418             remainingNanos = deadline - System.nanoTime();
       
   419         } while (remainingNanos > 0);
       
   420         return hasExited;
       
   421     }
       
   422 
       
   423     public synchronized int exitValue() {
       
   424         if (!hasExited) {
       
   425             throw new IllegalThreadStateException("process hasn't exited");
       
   426         }
       
   427         return exitcode;
       
   428     }
       
   429 
       
   430     private static native void destroyProcess(int pid, boolean force);
       
   431 
       
   432     private void destroy(boolean force) {
       
   433         switch (platform) {
       
   434             case LINUX:
       
   435             case BSD:
       
   436             case AIX:
       
   437                 // There is a risk that pid will be recycled, causing us to
       
   438                 // kill the wrong process!  So we only terminate processes
       
   439                 // that appear to still be running.  Even with this check,
       
   440                 // there is an unavoidable race condition here, but the window
       
   441                 // is very small, and OSes try hard to not recycle pids too
       
   442                 // soon, so this is quite safe.
       
   443                 synchronized (this) {
       
   444                     if (!hasExited)
       
   445                         destroyProcess(pid, force);
       
   446                 }
       
   447                 try { stdin.close();  } catch (IOException ignored) {}
       
   448                 try { stdout.close(); } catch (IOException ignored) {}
       
   449                 try { stderr.close(); } catch (IOException ignored) {}
       
   450                 break;
       
   451 
       
   452             case SOLARIS:
       
   453                 // There is a risk that pid will be recycled, causing us to
       
   454                 // kill the wrong process!  So we only terminate processes
       
   455                 // that appear to still be running.  Even with this check,
       
   456                 // there is an unavoidable race condition here, but the window
       
   457                 // is very small, and OSes try hard to not recycle pids too
       
   458                 // soon, so this is quite safe.
       
   459                 synchronized (this) {
       
   460                     if (!hasExited)
       
   461                         destroyProcess(pid, force);
       
   462                     try {
       
   463                         stdin.close();
       
   464                         if (stdout_inner_stream != null)
       
   465                             stdout_inner_stream.closeDeferred(stdout);
       
   466                         if (stderr instanceof DeferredCloseInputStream)
       
   467                             ((DeferredCloseInputStream) stderr)
       
   468                                 .closeDeferred(stderr);
       
   469                     } catch (IOException e) {
       
   470                         // ignore
       
   471                     }
       
   472                 }
       
   473                 break;
       
   474 
       
   475             default: throw new AssertionError("Unsupported platform: " + platform);
       
   476         }
       
   477     }
       
   478 
       
   479     public void destroy() {
       
   480         destroy(false);
       
   481     }
       
   482 
       
   483     @Override
       
   484     public Process destroyForcibly() {
       
   485         destroy(true);
       
   486         return this;
       
   487     }
       
   488 
       
   489     @Override
       
   490     public long getPid() {
       
   491         return pid;
       
   492     }
       
   493 
       
   494     @Override
       
   495     public synchronized boolean isAlive() {
       
   496         return !hasExited;
       
   497     }
       
   498 
       
   499     private static native void init();
       
   500 
       
   501     static {
       
   502         init();
       
   503     }
       
   504 
       
   505     /**
       
   506      * A buffered input stream for a subprocess pipe file descriptor
       
   507      * that allows the underlying file descriptor to be reclaimed when
       
   508      * the process exits, via the processExited hook.
       
   509      *
       
   510      * This is tricky because we do not want the user-level InputStream to be
       
   511      * closed until the user invokes close(), and we need to continue to be
       
   512      * able to read any buffered data lingering in the OS pipe buffer.
       
   513      */
       
   514     private static class ProcessPipeInputStream extends BufferedInputStream {
       
   515         private final Object closeLock = new Object();
       
   516 
       
   517         ProcessPipeInputStream(int fd) {
       
   518             super(new FileInputStream(newFileDescriptor(fd)));
       
   519         }
       
   520         private static byte[] drainInputStream(InputStream in)
       
   521                 throws IOException {
       
   522             int n = 0;
       
   523             int j;
       
   524             byte[] a = null;
       
   525             while ((j = in.available()) > 0) {
       
   526                 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
       
   527                 n += in.read(a, n, j);
       
   528             }
       
   529             return (a == null || n == a.length) ? a : Arrays.copyOf(a, n);
       
   530         }
       
   531 
       
   532         /** Called by the process reaper thread when the process exits. */
       
   533         synchronized void processExited() {
       
   534             synchronized (closeLock) {
       
   535                 try {
       
   536                     InputStream in = this.in;
       
   537                     // this stream is closed if and only if: in == null
       
   538                     if (in != null) {
       
   539                         byte[] stragglers = drainInputStream(in);
       
   540                         in.close();
       
   541                         this.in = (stragglers == null) ?
       
   542                             ProcessBuilder.NullInputStream.INSTANCE :
       
   543                             new ByteArrayInputStream(stragglers);
       
   544                     }
       
   545                 } catch (IOException ignored) {}
       
   546             }
       
   547         }
       
   548 
       
   549         @Override
       
   550         public void close() throws IOException {
       
   551             // BufferedInputStream#close() is not synchronized unlike most other
       
   552             // methods. Synchronizing helps avoid race with processExited().
       
   553             synchronized (closeLock) {
       
   554                 super.close();
       
   555             }
       
   556         }
       
   557     }
       
   558 
       
   559     /**
       
   560      * A buffered output stream for a subprocess pipe file descriptor
       
   561      * that allows the underlying file descriptor to be reclaimed when
       
   562      * the process exits, via the processExited hook.
       
   563      */
       
   564     private static class ProcessPipeOutputStream extends BufferedOutputStream {
       
   565         ProcessPipeOutputStream(int fd) {
       
   566             super(new FileOutputStream(newFileDescriptor(fd)));
       
   567         }
       
   568 
       
   569         /** Called by the process reaper thread when the process exits. */
       
   570         synchronized void processExited() {
       
   571             OutputStream out = this.out;
       
   572             if (out != null) {
       
   573                 try {
       
   574                     out.close();
       
   575                 } catch (IOException ignored) {
       
   576                     // We know of no reason to get an IOException, but if
       
   577                     // we do, there's nothing else to do but carry on.
       
   578                 }
       
   579                 this.out = ProcessBuilder.NullOutputStream.INSTANCE;
       
   580             }
       
   581         }
       
   582     }
       
   583 
       
   584     // A FileInputStream that supports the deferment of the actual close
       
   585     // operation until the last pending I/O operation on the stream has
       
   586     // finished.  This is required on Solaris because we must close the stdin
       
   587     // and stdout streams in the destroy method in order to reclaim the
       
   588     // underlying file descriptors.  Doing so, however, causes any thread
       
   589     // currently blocked in a read on one of those streams to receive an
       
   590     // IOException("Bad file number"), which is incompatible with historical
       
   591     // behavior.  By deferring the close we allow any pending reads to see -1
       
   592     // (EOF) as they did before.
       
   593     //
       
   594     private static class DeferredCloseInputStream extends FileInputStream
       
   595     {
       
   596         DeferredCloseInputStream(FileDescriptor fd) {
       
   597             super(fd);
       
   598         }
       
   599 
       
   600         private Object lock = new Object();     // For the following fields
       
   601         private boolean closePending = false;
       
   602         private int useCount = 0;
       
   603         private InputStream streamToClose;
       
   604 
       
   605         private void raise() {
       
   606             synchronized (lock) {
       
   607                 useCount++;
       
   608             }
       
   609         }
       
   610 
       
   611         private void lower() throws IOException {
       
   612             synchronized (lock) {
       
   613                 useCount--;
       
   614                 if (useCount == 0 && closePending) {
       
   615                     streamToClose.close();
       
   616                 }
       
   617             }
       
   618         }
       
   619 
       
   620         // stc is the actual stream to be closed; it might be this object, or
       
   621         // it might be an upstream object for which this object is downstream.
       
   622         //
       
   623         private void closeDeferred(InputStream stc) throws IOException {
       
   624             synchronized (lock) {
       
   625                 if (useCount == 0) {
       
   626                     stc.close();
       
   627                 } else {
       
   628                     closePending = true;
       
   629                     streamToClose = stc;
       
   630                 }
       
   631             }
       
   632         }
       
   633 
       
   634         public void close() throws IOException {
       
   635             synchronized (lock) {
       
   636                 useCount = 0;
       
   637                 closePending = false;
       
   638             }
       
   639             super.close();
       
   640         }
       
   641 
       
   642         public int read() throws IOException {
       
   643             raise();
       
   644             try {
       
   645                 return super.read();
       
   646             } finally {
       
   647                 lower();
       
   648             }
       
   649         }
       
   650 
       
   651         public int read(byte[] b) throws IOException {
       
   652             raise();
       
   653             try {
       
   654                 return super.read(b);
       
   655             } finally {
       
   656                 lower();
       
   657             }
       
   658         }
       
   659 
       
   660         public int read(byte[] b, int off, int len) throws IOException {
       
   661             raise();
       
   662             try {
       
   663                 return super.read(b, off, len);
       
   664             } finally {
       
   665                 lower();
       
   666             }
       
   667         }
       
   668 
       
   669         public long skip(long n) throws IOException {
       
   670             raise();
       
   671             try {
       
   672                 return super.skip(n);
       
   673             } finally {
       
   674                 lower();
       
   675             }
       
   676         }
       
   677 
       
   678         public int available() throws IOException {
       
   679             raise();
       
   680             try {
       
   681                 return super.available();
       
   682             } finally {
       
   683                 lower();
       
   684             }
       
   685         }
       
   686     }
       
   687 
       
   688     /**
       
   689      * A buffered input stream for a subprocess pipe file descriptor
       
   690      * that allows the underlying file descriptor to be reclaimed when
       
   691      * the process exits, via the processExited hook.
       
   692      *
       
   693      * This is tricky because we do not want the user-level InputStream to be
       
   694      * closed until the user invokes close(), and we need to continue to be
       
   695      * able to read any buffered data lingering in the OS pipe buffer.
       
   696      *
       
   697      * On AIX this is especially tricky, because the 'close()' system call
       
   698      * will block if another thread is at the same time blocked in a file
       
   699      * operation (e.g. 'read()') on the same file descriptor. We therefore
       
   700      * combine 'ProcessPipeInputStream' approach used on Linux and Bsd
       
   701      * with the DeferredCloseInputStream approach used on Solaris. This means
       
   702      * that every potentially blocking operation on the file descriptor
       
   703      * increments a counter before it is executed and decrements it once it
       
   704      * finishes. The 'close()' operation will only be executed if there are
       
   705      * no pending operations. Otherwise it is deferred after the last pending
       
   706      * operation has finished.
       
   707      *
       
   708      */
       
   709     private static class DeferredCloseProcessPipeInputStream
       
   710         extends BufferedInputStream {
       
   711 
       
   712         private final Object closeLock = new Object();
       
   713         private int useCount = 0;
       
   714         private boolean closePending = false;
       
   715 
       
   716         DeferredCloseProcessPipeInputStream(int fd) {
       
   717             super(new FileInputStream(newFileDescriptor(fd)));
       
   718         }
       
   719 
       
   720         private InputStream drainInputStream(InputStream in)
       
   721                 throws IOException {
       
   722             int n = 0;
       
   723             int j;
       
   724             byte[] a = null;
       
   725             synchronized (closeLock) {
       
   726                 if (buf == null) // asynchronous close()?
       
   727                     return null; // discard
       
   728                 j = in.available();
       
   729             }
       
   730             while (j > 0) {
       
   731                 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
       
   732                 synchronized (closeLock) {
       
   733                     if (buf == null) // asynchronous close()?
       
   734                         return null; // discard
       
   735                     n += in.read(a, n, j);
       
   736                     j = in.available();
       
   737                 }
       
   738             }
       
   739             return (a == null) ?
       
   740                     ProcessBuilder.NullInputStream.INSTANCE :
       
   741                     new ByteArrayInputStream(n == a.length ? a : Arrays.copyOf(a, n));
       
   742         }
       
   743 
       
   744         /** Called by the process reaper thread when the process exits. */
       
   745         synchronized void processExited() {
       
   746             try {
       
   747                 InputStream in = this.in;
       
   748                 if (in != null) {
       
   749                     InputStream stragglers = drainInputStream(in);
       
   750                     in.close();
       
   751                     this.in = stragglers;
       
   752                 }
       
   753             } catch (IOException ignored) { }
       
   754         }
       
   755 
       
   756         private void raise() {
       
   757             synchronized (closeLock) {
       
   758                 useCount++;
       
   759             }
       
   760         }
       
   761 
       
   762         private void lower() throws IOException {
       
   763             synchronized (closeLock) {
       
   764                 useCount--;
       
   765                 if (useCount == 0 && closePending) {
       
   766                     closePending = false;
       
   767                     super.close();
       
   768                 }
       
   769             }
       
   770         }
       
   771 
       
   772         @Override
       
   773         public int read() throws IOException {
       
   774             raise();
       
   775             try {
       
   776                 return super.read();
       
   777             } finally {
       
   778                 lower();
       
   779             }
       
   780         }
       
   781 
       
   782         @Override
       
   783         public int read(byte[] b) throws IOException {
       
   784             raise();
       
   785             try {
       
   786                 return super.read(b);
       
   787             } finally {
       
   788                 lower();
       
   789             }
       
   790         }
       
   791 
       
   792         @Override
       
   793         public int read(byte[] b, int off, int len) throws IOException {
       
   794             raise();
       
   795             try {
       
   796                 return super.read(b, off, len);
       
   797             } finally {
       
   798                 lower();
       
   799             }
       
   800         }
       
   801 
       
   802         @Override
       
   803         public long skip(long n) throws IOException {
       
   804             raise();
       
   805             try {
       
   806                 return super.skip(n);
       
   807             } finally {
       
   808                 lower();
       
   809             }
       
   810         }
       
   811 
       
   812         @Override
       
   813         public int available() throws IOException {
       
   814             raise();
       
   815             try {
       
   816                 return super.available();
       
   817             } finally {
       
   818                 lower();
       
   819             }
       
   820         }
       
   821 
       
   822         @Override
       
   823         public void close() throws IOException {
       
   824             // BufferedInputStream#close() is not synchronized unlike most other
       
   825             // methods. Synchronizing helps avoid racing with drainInputStream().
       
   826             synchronized (closeLock) {
       
   827                 if (useCount == 0) {
       
   828                     super.close();
       
   829                 }
       
   830                 else {
       
   831                     closePending = true;
       
   832                 }
       
   833             }
       
   834         }
       
   835     }
       
   836 }