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.VFORK, LaunchMechanism.POSIX_SPAWN, 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 @SuppressWarnings("fallthrough") 110 private String helperPath(String javahome, String osArch) { 111 switch (this) { 112 case SOLARIS: 113 // fall through... 114 case LINUX: 115 case AIX: 116 case BSD: 117 return javahome + "/lib/jspawnhelper"; 118 119 default: 120 throw new AssertionError("Unsupported platform: " + this); 121 } 122 } 123 124 String helperPath() { 125 Properties props = GetPropertyAction.privilegedGetProperties(); 126 return helperPath(StaticProperty.javaHome(), 127 props.getProperty("os.arch")); 128 } 129 130 LaunchMechanism launchMechanism() { 131 return AccessController.doPrivileged( 132 (PrivilegedAction<LaunchMechanism>) () -> { 133 String s = System.getProperty( 134 "jdk.lang.Process.launchMechanism"); 135 LaunchMechanism lm; 136 if (s == null) { 137 lm = defaultLaunchMechanism; 138 s = lm.name().toLowerCase(Locale.ENGLISH); 139 } else { 140 try { 141 lm = LaunchMechanism.valueOf( 142 s.toUpperCase(Locale.ENGLISH)); 143 } catch (IllegalArgumentException e) { 144 lm = null; 145 } 146 } 147 if (lm == null || !validLaunchMechanisms.contains(lm)) { 148 throw new Error( 149 s + " is not a supported " + 150 "process launch mechanism on this platform." 151 ); 152 } 153 return lm; 154 } 155 ); 156 } 157 158 static Platform get() { 159 String osName = GetPropertyAction.privilegedGetProperty("os.name"); 160 161 if (osName.equals("Linux")) { return LINUX; } 162 if (osName.contains("OS X")) { return BSD; } 163 if (osName.equals("SunOS")) { return SOLARIS; } 164 if (osName.equals("AIX")) { return AIX; } 165 166 throw new Error(osName + " is not a supported OS platform."); 167 } 168 } 169 170 private static final Platform platform = Platform.get(); 171 private static final LaunchMechanism launchMechanism = platform.launchMechanism(); 172 private static final byte[] helperpath = toCString(platform.helperPath()); 173 174 private static byte[] toCString(String s) { 175 if (s == null) 176 return null; 177 byte[] bytes = s.getBytes(); 178 byte[] result = new byte[bytes.length + 1]; 179 System.arraycopy(bytes, 0, 180 result, 0, 181 bytes.length); 182 result[result.length-1] = (byte)0; 183 return result; 184 } 185 186 // Only for use by ProcessBuilder.start() 187 static Process start(String[] cmdarray, 188 java.util.Map<String,String> environment, 189 String dir, 190 ProcessBuilder.Redirect[] redirects, 191 boolean redirectErrorStream) 192 throws IOException 193 { 194 assert cmdarray != null && cmdarray.length > 0; 195 196 // Convert arguments to a contiguous block; it's easier to do 197 // memory management in Java than in C. 198 byte[][] args = new byte[cmdarray.length-1][]; 199 int size = args.length; // For added NUL bytes 200 for (int i = 0; i < args.length; i++) { 201 args[i] = cmdarray[i+1].getBytes(); 202 size += args[i].length; 203 } 204 byte[] argBlock = new byte[size]; 205 int i = 0; 206 for (byte[] arg : args) { 207 System.arraycopy(arg, 0, argBlock, i, arg.length); 208 i += arg.length + 1; 209 // No need to write NUL bytes explicitly 210 } 211 212 int[] envc = new int[1]; 213 byte[] envBlock = ProcessEnvironment.toEnvironmentBlock(environment, envc); 214 215 int[] std_fds; 216 217 FileInputStream f0 = null; 218 FileOutputStream f1 = null; 219 FileOutputStream f2 = null; 220 221 try { 222 boolean forceNullOutputStream = false; 223 if (redirects == null) { 224 std_fds = new int[] { -1, -1, -1 }; 225 } else { 226 std_fds = new int[3]; 227 228 if (redirects[0] == Redirect.PIPE) { 229 std_fds[0] = -1; 230 } else if (redirects[0] == Redirect.INHERIT) { 231 std_fds[0] = 0; 232 } else if (redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) { 233 std_fds[0] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd()); 234 } else { 235 f0 = new FileInputStream(redirects[0].file()); 236 std_fds[0] = fdAccess.get(f0.getFD()); 237 } 238 239 if (redirects[1] == Redirect.PIPE) { 240 std_fds[1] = -1; 241 } else if (redirects[1] == Redirect.INHERIT) { 242 std_fds[1] = 1; 243 } else if (redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) { 244 std_fds[1] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd()); 245 // Force getInputStream to return a null stream, 246 // the fd is directly assigned to the next process. 247 forceNullOutputStream = true; 248 } else { 249 f1 = new FileOutputStream(redirects[1].file(), 250 redirects[1].append()); 251 std_fds[1] = fdAccess.get(f1.getFD()); 252 } 253 254 if (redirects[2] == Redirect.PIPE) { 255 std_fds[2] = -1; 256 } else if (redirects[2] == Redirect.INHERIT) { 257 std_fds[2] = 2; 258 } else if (redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) { 259 std_fds[2] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd()); 260 } else { 261 f2 = new FileOutputStream(redirects[2].file(), 262 redirects[2].append()); 263 std_fds[2] = fdAccess.get(f2.getFD()); 264 } 265 } 266 267 Process p = new ProcessImpl 268 (toCString(cmdarray[0]), 269 argBlock, args.length, 270 envBlock, envc[0], 271 toCString(dir), 272 std_fds, 273 forceNullOutputStream, 274 redirectErrorStream); 275 if (redirects != null) { 276 // Copy the fd's if they are to be redirected to another process 277 if (std_fds[0] >= 0 && 278 redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) { 279 fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd(), std_fds[0]); 280 } 281 if (std_fds[1] >= 0 && 282 redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) { 283 fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd(), std_fds[1]); 284 } 285 if (std_fds[2] >= 0 && 286 redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) { 287 fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd(), std_fds[2]); 288 } 289 } 290 return p; 291 } finally { 292 // In theory, close() can throw IOException 293 // (although it is rather unlikely to happen here) 294 try { if (f0 != null) f0.close(); } 295 finally { 296 try { if (f1 != null) f1.close(); } 297 finally { if (f2 != null) f2.close(); } 298 } 299 } 300 } 301 302 303 /** 304 * Creates a process. Depending on the {@code mode} flag, this is done by 305 * one of the following mechanisms: 306 * <pre> 307 * 1 - fork(2) and exec(2) 308 * 2 - posix_spawn(3P) 309 * 3 - vfork(2) and exec(2) 310 * </pre> 311 * @param fds an array of three file descriptors. 312 * Indexes 0, 1, and 2 correspond to standard input, 313 * standard output and standard error, respectively. On 314 * input, a value of -1 means to create a pipe to connect 315 * child and parent processes. On output, a value which 316 * is not -1 is the parent pipe fd corresponding to the 317 * pipe which has been created. An element of this array 318 * is -1 on input if and only if it is <em>not</em> -1 on 319 * output. 320 * @return the pid of the subprocess 321 */ 322 private native int forkAndExec(int mode, byte[] helperpath, 323 byte[] prog, 324 byte[] argBlock, int argc, 325 byte[] envBlock, int envc, 326 byte[] dir, 327 int[] fds, 328 boolean redirectErrorStream) 329 throws IOException; 330 331 private ProcessImpl(final byte[] prog, 332 final byte[] argBlock, final int argc, 333 final byte[] envBlock, final int envc, 334 final byte[] dir, 335 final int[] fds, 336 final boolean forceNullOutputStream, 337 final boolean redirectErrorStream) 338 throws IOException { 339 340 pid = forkAndExec(launchMechanism.ordinal() + 1, 341 helperpath, 342 prog, 343 argBlock, argc, 344 envBlock, envc, 345 dir, 346 fds, 347 redirectErrorStream); 348 processHandle = ProcessHandleImpl.getInternal(pid); 349 350 try { 351 doPrivileged((PrivilegedExceptionAction<Void>) () -> { 352 initStreams(fds, forceNullOutputStream); 353 return null; 354 }); 355 } catch (PrivilegedActionException ex) { 356 throw (IOException) ex.getException(); 357 } 358 } 359 360 static FileDescriptor newFileDescriptor(int fd) { 361 FileDescriptor fileDescriptor = new FileDescriptor(); 362 fdAccess.set(fileDescriptor, fd); 363 return fileDescriptor; 364 } 365 366 /** 367 * Initialize the streams from the file descriptors. 368 * @param fds array of stdin, stdout, stderr fds 369 * @param forceNullOutputStream true if the stdout is being directed to 370 * a subsequent process. The stdout stream should be a null output stream . 371 * @throws IOException 372 */ 373 void initStreams(int[] fds, boolean forceNullOutputStream) throws IOException { 374 switch (platform) { 375 case LINUX: 376 case BSD: 377 stdin = (fds[0] == -1) ? 378 ProcessBuilder.NullOutputStream.INSTANCE : 379 new ProcessPipeOutputStream(fds[0]); 380 381 stdout = (fds[1] == -1 || forceNullOutputStream) ? 382 ProcessBuilder.NullInputStream.INSTANCE : 383 new ProcessPipeInputStream(fds[1]); 384 385 stderr = (fds[2] == -1) ? 386 ProcessBuilder.NullInputStream.INSTANCE : 387 new ProcessPipeInputStream(fds[2]); 388 389 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> { 390 synchronized (this) { 391 this.exitcode = (exitcode == null) ? -1 : exitcode.intValue(); 392 this.hasExited = true; 393 this.notifyAll(); 394 } 395 396 if (stdout instanceof ProcessPipeInputStream) 397 ((ProcessPipeInputStream) stdout).processExited(); 398 399 if (stderr instanceof ProcessPipeInputStream) 400 ((ProcessPipeInputStream) stderr).processExited(); 401 402 if (stdin instanceof ProcessPipeOutputStream) 403 ((ProcessPipeOutputStream) stdin).processExited(); 404 405 return null; 406 }); 407 break; 408 409 case SOLARIS: 410 stdin = (fds[0] == -1) ? 411 ProcessBuilder.NullOutputStream.INSTANCE : 412 new BufferedOutputStream( 413 new FileOutputStream(newFileDescriptor(fds[0]))); 414 415 stdout = (fds[1] == -1) ? 416 ProcessBuilder.NullInputStream.INSTANCE : 417 new BufferedInputStream( 418 stdout_inner_stream = 419 new DeferredCloseInputStream( 420 newFileDescriptor(fds[1]))); 421 422 stderr = (fds[2] == -1) ? 423 ProcessBuilder.NullInputStream.INSTANCE : 424 new DeferredCloseInputStream(newFileDescriptor(fds[2])); 425 426 /* 427 * For each subprocess forked a corresponding reaper task 428 * is submitted. That task is the only thread which waits 429 * for the subprocess to terminate and it doesn't hold any 430 * locks while doing so. This design allows waitFor() and 431 * exitStatus() to be safely executed in parallel (and they 432 * need no native code). 433 */ 434 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> { 435 synchronized (this) { 436 this.exitcode = (exitcode == null) ? -1 : exitcode.intValue(); 437 this.hasExited = true; 438 this.notifyAll(); 439 } 440 return null; 441 }); 442 break; 443 444 case AIX: 445 stdin = (fds[0] == -1) ? 446 ProcessBuilder.NullOutputStream.INSTANCE : 447 new ProcessPipeOutputStream(fds[0]); 448 449 stdout = (fds[1] == -1 || forceNullOutputStream) ? 450 ProcessBuilder.NullInputStream.INSTANCE : 451 new DeferredCloseProcessPipeInputStream(fds[1]); 452 453 stderr = (fds[2] == -1) ? 454 ProcessBuilder.NullInputStream.INSTANCE : 455 new DeferredCloseProcessPipeInputStream(fds[2]); 456 457 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> { 458 synchronized (this) { 459 this.exitcode = (exitcode == null) ? -1 : exitcode.intValue(); 460 this.hasExited = true; 461 this.notifyAll(); 462 } 463 464 if (stdout instanceof DeferredCloseProcessPipeInputStream) 465 ((DeferredCloseProcessPipeInputStream) stdout).processExited(); 466 467 if (stderr instanceof DeferredCloseProcessPipeInputStream) 468 ((DeferredCloseProcessPipeInputStream) stderr).processExited(); 469 470 if (stdin instanceof ProcessPipeOutputStream) 471 ((ProcessPipeOutputStream) stdin).processExited(); 472 473 return null; 474 }); 475 break; 476 477 default: throw new AssertionError("Unsupported platform: " + platform); 478 } 479 } 480 481 public OutputStream getOutputStream() { 482 return stdin; 483 } 484 485 public InputStream getInputStream() { 486 return stdout; 487 } 488 489 public InputStream getErrorStream() { 490 return stderr; 491 } 492 493 public synchronized int waitFor() throws InterruptedException { 494 while (!hasExited) { 495 wait(); 496 } 497 return exitcode; 498 } 499 500 @Override 501 public synchronized boolean waitFor(long timeout, TimeUnit unit) 502 throws InterruptedException 503 { 504 long remainingNanos = unit.toNanos(timeout); // throw NPE before other conditions 505 if (hasExited) return true; 506 if (timeout <= 0) return false; 507 508 long deadline = System.nanoTime() + remainingNanos; 509 do { 510 TimeUnit.NANOSECONDS.timedWait(this, remainingNanos); 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 pid() { 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 }