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 }