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", 65536);
 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 timeoutAsNanos = unit.toNanos(timeout);
 410         long startTime = System.nanoTime();
 411         long rem = timeoutAsNanos;
 412 
 413         while (!hasExited && (rem > 0)) {
 414             wait(Math.max(TimeUnit.NANOSECONDS.toMillis(rem), 1));
 415             rem = timeoutAsNanos - (System.nanoTime() - startTime);
 416         }
 417         return hasExited;
 418     }
 419 
 420     public synchronized int exitValue() {
 421         if (!hasExited) {
 422             throw new IllegalThreadStateException("process hasn't exited");
 423         }
 424         return exitcode;
 425     }
 426 
 427     private static native void destroyProcess(int pid, boolean force);
 428 
 429     private void destroy(boolean force) {
 430         switch (platform) {
 431             case LINUX:
 432             case BSD:
 433             case AIX:
 434                 // There is a risk that pid will be recycled, causing us to
 435                 // kill the wrong process!  So we only terminate processes
 436                 // that appear to still be running.  Even with this check,
 437                 // there is an unavoidable race condition here, but the window
 438                 // is very small, and OSes try hard to not recycle pids too
 439                 // soon, so this is quite safe.
 440                 synchronized (this) {
 441                     if (!hasExited)
 442                         destroyProcess(pid, force);
 443                 }
 444                 try { stdin.close();  } catch (IOException ignored) {}
 445                 try { stdout.close(); } catch (IOException ignored) {}
 446                 try { stderr.close(); } catch (IOException ignored) {}
 447                 break;
 448 
 449             case SOLARIS:
 450                 // There is a risk that pid will be recycled, causing us to
 451                 // kill the wrong process!  So we only terminate processes
 452                 // that appear to still be running.  Even with this check,
 453                 // there is an unavoidable race condition here, but the window
 454                 // is very small, and OSes try hard to not recycle pids too
 455                 // soon, so this is quite safe.
 456                 synchronized (this) {
 457                     if (!hasExited)
 458                         destroyProcess(pid, force);
 459                     try {
 460                         stdin.close();
 461                         if (stdout_inner_stream != null)
 462                             stdout_inner_stream.closeDeferred(stdout);
 463                         if (stderr instanceof DeferredCloseInputStream)
 464                             ((DeferredCloseInputStream) stderr)
 465                                 .closeDeferred(stderr);
 466                     } catch (IOException e) {
 467                         // ignore
 468                     }
 469                 }
 470                 break;
 471 
 472             default: throw new AssertionError("Unsupported platform: " + platform);
 473         }
 474     }
 475 
 476     public void destroy() {
 477         destroy(false);
 478     }
 479 
 480     @Override
 481     public Process destroyForcibly() {
 482         destroy(true);
 483         return this;
 484     }
 485 
 486     @Override
 487     public long getPid() {
 488         return pid;
 489     }
 490 
 491     @Override
 492     public synchronized boolean isAlive() {
 493         return !hasExited;
 494     }
 495 
 496     private static native void init();
 497 
 498     static {
 499         init();
 500     }
 501 
 502     /**
 503      * A buffered input stream for a subprocess pipe file descriptor
 504      * that allows the underlying file descriptor to be reclaimed when
 505      * the process exits, via the processExited hook.
 506      *
 507      * This is tricky because we do not want the user-level InputStream to be
 508      * closed until the user invokes close(), and we need to continue to be
 509      * able to read any buffered data lingering in the OS pipe buffer.
 510      */
 511     private static class ProcessPipeInputStream extends BufferedInputStream {
 512         private final Object closeLock = new Object();
 513 
 514         ProcessPipeInputStream(int fd) {
 515             super(new FileInputStream(newFileDescriptor(fd)));
 516         }
 517         private static byte[] drainInputStream(InputStream in)
 518                 throws IOException {
 519             int n = 0;
 520             int j;
 521             byte[] a = null;
 522             while ((j = in.available()) > 0) {
 523                 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
 524                 n += in.read(a, n, j);
 525             }
 526             return (a == null || n == a.length) ? a : Arrays.copyOf(a, n);
 527         }
 528 
 529         /** Called by the process reaper thread when the process exits. */
 530         synchronized void processExited() {
 531             synchronized (closeLock) {
 532                 try {
 533                     InputStream in = this.in;
 534                     // this stream is closed if and only if: in == null
 535                     if (in != null) {
 536                         byte[] stragglers = drainInputStream(in);
 537                         in.close();
 538                         this.in = (stragglers == null) ?
 539                             ProcessBuilder.NullInputStream.INSTANCE :
 540                             new ByteArrayInputStream(stragglers);
 541                     }
 542                 } catch (IOException ignored) {}
 543             }
 544         }
 545 
 546         @Override
 547         public void close() throws IOException {
 548             // BufferedInputStream#close() is not synchronized unlike most other
 549             // methods. Synchronizing helps avoid race with processExited().
 550             synchronized (closeLock) {
 551                 super.close();
 552             }
 553         }
 554     }
 555 
 556     /**
 557      * A buffered output stream for a subprocess pipe file descriptor
 558      * that allows the underlying file descriptor to be reclaimed when
 559      * the process exits, via the processExited hook.
 560      */
 561     private static class ProcessPipeOutputStream extends BufferedOutputStream {
 562         ProcessPipeOutputStream(int fd) {
 563             super(new FileOutputStream(newFileDescriptor(fd)));
 564         }
 565 
 566         /** Called by the process reaper thread when the process exits. */
 567         synchronized void processExited() {
 568             OutputStream out = this.out;
 569             if (out != null) {
 570                 try {
 571                     out.close();
 572                 } catch (IOException ignored) {
 573                     // We know of no reason to get an IOException, but if
 574                     // we do, there's nothing else to do but carry on.
 575                 }
 576                 this.out = ProcessBuilder.NullOutputStream.INSTANCE;
 577             }
 578         }
 579     }
 580 
 581     // A FileInputStream that supports the deferment of the actual close
 582     // operation until the last pending I/O operation on the stream has
 583     // finished.  This is required on Solaris because we must close the stdin
 584     // and stdout streams in the destroy method in order to reclaim the
 585     // underlying file descriptors.  Doing so, however, causes any thread
 586     // currently blocked in a read on one of those streams to receive an
 587     // IOException("Bad file number"), which is incompatible with historical
 588     // behavior.  By deferring the close we allow any pending reads to see -1
 589     // (EOF) as they did before.
 590     //
 591     private static class DeferredCloseInputStream extends FileInputStream
 592     {
 593         DeferredCloseInputStream(FileDescriptor fd) {
 594             super(fd);
 595         }
 596 
 597         private Object lock = new Object();     // For the following fields
 598         private boolean closePending = false;
 599         private int useCount = 0;
 600         private InputStream streamToClose;
 601 
 602         private void raise() {
 603             synchronized (lock) {
 604                 useCount++;
 605             }
 606         }
 607 
 608         private void lower() throws IOException {
 609             synchronized (lock) {
 610                 useCount--;
 611                 if (useCount == 0 && closePending) {
 612                     streamToClose.close();
 613                 }
 614             }
 615         }
 616 
 617         // stc is the actual stream to be closed; it might be this object, or
 618         // it might be an upstream object for which this object is downstream.
 619         //
 620         private void closeDeferred(InputStream stc) throws IOException {
 621             synchronized (lock) {
 622                 if (useCount == 0) {
 623                     stc.close();
 624                 } else {
 625                     closePending = true;
 626                     streamToClose = stc;
 627                 }
 628             }
 629         }
 630 
 631         public void close() throws IOException {
 632             synchronized (lock) {
 633                 useCount = 0;
 634                 closePending = false;
 635             }
 636             super.close();
 637         }
 638 
 639         public int read() throws IOException {
 640             raise();
 641             try {
 642                 return super.read();
 643             } finally {
 644                 lower();
 645             }
 646         }
 647 
 648         public int read(byte[] b) throws IOException {
 649             raise();
 650             try {
 651                 return super.read(b);
 652             } finally {
 653                 lower();
 654             }
 655         }
 656 
 657         public int read(byte[] b, int off, int len) throws IOException {
 658             raise();
 659             try {
 660                 return super.read(b, off, len);
 661             } finally {
 662                 lower();
 663             }
 664         }
 665 
 666         public long skip(long n) throws IOException {
 667             raise();
 668             try {
 669                 return super.skip(n);
 670             } finally {
 671                 lower();
 672             }
 673         }
 674 
 675         public int available() throws IOException {
 676             raise();
 677             try {
 678                 return super.available();
 679             } finally {
 680                 lower();
 681             }
 682         }
 683     }
 684 
 685     /**
 686      * A buffered input stream for a subprocess pipe file descriptor
 687      * that allows the underlying file descriptor to be reclaimed when
 688      * the process exits, via the processExited hook.
 689      *
 690      * This is tricky because we do not want the user-level InputStream to be
 691      * closed until the user invokes close(), and we need to continue to be
 692      * able to read any buffered data lingering in the OS pipe buffer.
 693      *
 694      * On AIX this is especially tricky, because the 'close()' system call
 695      * will block if another thread is at the same time blocked in a file
 696      * operation (e.g. 'read()') on the same file descriptor. We therefore
 697      * combine 'ProcessPipeInputStream' approach used on Linux and Bsd
 698      * with the DeferredCloseInputStream approach used on Solaris. This means
 699      * that every potentially blocking operation on the file descriptor
 700      * increments a counter before it is executed and decrements it once it
 701      * finishes. The 'close()' operation will only be executed if there are
 702      * no pending operations. Otherwise it is deferred after the last pending
 703      * operation has finished.
 704      *
 705      */
 706     private static class DeferredCloseProcessPipeInputStream
 707         extends BufferedInputStream {
 708 
 709         private final Object closeLock = new Object();
 710         private int useCount = 0;
 711         private boolean closePending = false;
 712 
 713         DeferredCloseProcessPipeInputStream(int fd) {
 714             super(new FileInputStream(newFileDescriptor(fd)));
 715         }
 716 
 717         private InputStream drainInputStream(InputStream in)
 718                 throws IOException {
 719             int n = 0;
 720             int j;
 721             byte[] a = null;
 722             synchronized (closeLock) {
 723                 if (buf == null) // asynchronous close()?
 724                     return null; // discard
 725                 j = in.available();
 726             }
 727             while (j > 0) {
 728                 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
 729                 synchronized (closeLock) {
 730                     if (buf == null) // asynchronous close()?
 731                         return null; // discard
 732                     n += in.read(a, n, j);
 733                     j = in.available();
 734                 }
 735             }
 736             return (a == null) ?
 737                     ProcessBuilder.NullInputStream.INSTANCE :
 738                     new ByteArrayInputStream(n == a.length ? a : Arrays.copyOf(a, n));
 739         }
 740 
 741         /** Called by the process reaper thread when the process exits. */
 742         synchronized void processExited() {
 743             try {
 744                 InputStream in = this.in;
 745                 if (in != null) {
 746                     InputStream stragglers = drainInputStream(in);
 747                     in.close();
 748                     this.in = stragglers;
 749                 }
 750             } catch (IOException ignored) { }
 751         }
 752 
 753         private void raise() {
 754             synchronized (closeLock) {
 755                 useCount++;
 756             }
 757         }
 758 
 759         private void lower() throws IOException {
 760             synchronized (closeLock) {
 761                 useCount--;
 762                 if (useCount == 0 && closePending) {
 763                     closePending = false;
 764                     super.close();
 765                 }
 766             }
 767         }
 768 
 769         @Override
 770         public int read() throws IOException {
 771             raise();
 772             try {
 773                 return super.read();
 774             } finally {
 775                 lower();
 776             }
 777         }
 778 
 779         @Override
 780         public int read(byte[] b) throws IOException {
 781             raise();
 782             try {
 783                 return super.read(b);
 784             } finally {
 785                 lower();
 786             }
 787         }
 788 
 789         @Override
 790         public int read(byte[] b, int off, int len) throws IOException {
 791             raise();
 792             try {
 793                 return super.read(b, off, len);
 794             } finally {
 795                 lower();
 796             }
 797         }
 798 
 799         @Override
 800         public long skip(long n) throws IOException {
 801             raise();
 802             try {
 803                 return super.skip(n);
 804             } finally {
 805                 lower();
 806             }
 807         }
 808 
 809         @Override
 810         public int available() throws IOException {
 811             raise();
 812             try {
 813                 return super.available();
 814             } finally {
 815                 lower();
 816             }
 817         }
 818 
 819         @Override
 820         public void close() throws IOException {
 821             // BufferedInputStream#close() is not synchronized unlike most other
 822             // methods. Synchronizing helps avoid racing with drainInputStream().
 823             synchronized (closeLock) {
 824                 if (useCount == 0) {
 825                     super.close();
 826                 }
 827                 else {
 828                     closePending = true;
 829                 }
 830             }
 831         }
 832     }
 833 }