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