rev 14210 : 8154231: Simplify access to System properties from JDK code
Reviewed-by: rriggs

   1 /*
   2  * Copyright (c) 2003, 2015, 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 jdk.internal.misc.JavaIOFileDescriptorAccess;
  50 import jdk.internal.misc.SharedSecrets;

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