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