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