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