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