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