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