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