1 /* 2 * Copyright (c) 1995, 2013, 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.io.BufferedInputStream; 29 import java.io.BufferedOutputStream; 30 import java.io.ByteArrayInputStream; 31 import java.io.FileDescriptor; 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 import java.util.Arrays; 38 import java.util.concurrent.Executors; 39 import java.util.concurrent.Executor; 40 import java.util.concurrent.ThreadFactory; 41 import java.util.concurrent.TimeUnit; 42 import java.security.AccessController; 43 import static java.security.AccessController.doPrivileged; 44 import java.security.PrivilegedAction; 45 import java.security.PrivilegedActionException; 46 import java.security.PrivilegedExceptionAction; 47 48 /** 49 * java.lang.Process subclass in the UNIX environment. 50 * 51 * @author Mario Wolczko and Ross Knippel. 52 * @author Konstantin Kladko (ported to Linux) 53 * @author Martin Buchholz 54 */ 55 final class UNIXProcess extends Process { 56 private static final sun.misc.JavaIOFileDescriptorAccess fdAccess 57 = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); 58 59 private final int pid; 60 private int exitcode; 61 private boolean hasExited; 62 63 private /* final */ OutputStream stdin; 64 private /* final */ InputStream stdout; 65 private /* final */ InputStream stderr; 66 67 private static enum LaunchMechanism { 68 FORK(1), 69 VFORK(3); 70 71 private int value; 72 LaunchMechanism(int x) {value = x;} 73 }; 74 75 /* default is VFORK on Linux */ 76 private static final LaunchMechanism launchMechanism; 77 private static byte[] helperpath; 78 79 private static byte[] toCString(String s) { 80 if (s == null) 81 return null; 82 byte[] bytes = s.getBytes(); 83 byte[] result = new byte[bytes.length + 1]; 84 System.arraycopy(bytes, 0, 85 result, 0, 86 bytes.length); 87 result[result.length-1] = (byte)0; 88 return result; 89 } 90 91 static { 92 launchMechanism = AccessController.doPrivileged( 93 new PrivilegedAction<LaunchMechanism>() 94 { 95 public LaunchMechanism run() { 96 String javahome = System.getProperty("java.home"); 97 String osArch = System.getProperty("os.arch"); 98 99 helperpath = toCString(javahome + "/lib/" + osArch + "/jspawnhelper"); 100 String s = System.getProperty( 101 "jdk.lang.Process.launchMechanism", "vfork"); 102 103 try { 104 return LaunchMechanism.valueOf(s.toUpperCase()); 105 } catch (IllegalArgumentException e) { 106 throw new Error(s + " is not a supported " + 107 "process launch mechanism on this platform."); 108 } 109 } 110 }); 111 } 112 113 /* this is for the reaping thread */ 114 private native int waitForProcessExit(int pid); 115 116 /** 117 * Create a process. Depending on the mode flag, this is done by 118 * one of the following mechanisms. 119 * - fork(2) and exec(2) 120 * - clone(2) and exec(2) 121 * - vfork(2) and exec(2) 122 * 123 * @param fds an array of three file descriptors. 124 * Indexes 0, 1, and 2 correspond to standard input, 125 * standard output and standard error, respectively. On 126 * input, a value of -1 means to create a pipe to connect 127 * child and parent processes. On output, a value which 128 * is not -1 is the parent pipe fd corresponding to the 129 * pipe which has been created. An element of this array 130 * is -1 on input if and only if it is <em>not</em> -1 on 131 * output. 132 * @return the pid of the subprocess 133 */ 134 private native int forkAndExec(int mode, byte[] helperpath, 135 byte[] prog, 136 byte[] argBlock, int argc, 137 byte[] envBlock, int envc, 138 byte[] dir, 139 int[] fds, 140 boolean redirectErrorStream) 141 throws IOException; 142 143 /** 144 * The thread factory used to create "process reaper" daemon threads. 145 */ 146 private static class ProcessReaperThreadFactory implements ThreadFactory { 147 private final static ThreadGroup group = getRootThreadGroup(); 148 149 private static ThreadGroup getRootThreadGroup() { 150 return doPrivileged(new PrivilegedAction<ThreadGroup> () { 151 public ThreadGroup run() { 152 ThreadGroup root = Thread.currentThread().getThreadGroup(); 153 while (root.getParent() != null) 154 root = root.getParent(); 155 return root; 156 }}); 157 } 158 159 public Thread newThread(Runnable grimReaper) { 160 // Our thread stack requirement is quite modest. 161 Thread t = new Thread(group, grimReaper, "process reaper", 32768); 162 t.setDaemon(true); 163 // A small attempt (probably futile) to avoid priority inversion 164 t.setPriority(Thread.MAX_PRIORITY); 165 return t; 166 } 167 } 168 169 /** 170 * The thread pool of "process reaper" daemon threads. 171 */ 172 private static final Executor processReaperExecutor = 173 doPrivileged(new PrivilegedAction<Executor>() { 174 public Executor run() { 175 return Executors.newCachedThreadPool 176 (new ProcessReaperThreadFactory()); 177 }}); 178 179 UNIXProcess(final byte[] prog, 180 final byte[] argBlock, final int argc, 181 final byte[] envBlock, final int envc, 182 final byte[] dir, 183 final int[] fds, 184 final boolean redirectErrorStream) 185 throws IOException { 186 187 pid = forkAndExec(launchMechanism.value, 188 helperpath, 189 prog, 190 argBlock, argc, 191 envBlock, envc, 192 dir, 193 fds, 194 redirectErrorStream); 195 196 try { 197 doPrivileged(new PrivilegedExceptionAction<Void>() { 198 public Void run() throws IOException { 199 initStreams(fds); 200 return null; 201 }}); 202 } catch (PrivilegedActionException ex) { 203 throw (IOException) ex.getException(); 204 } 205 } 206 207 static FileDescriptor newFileDescriptor(int fd) { 208 FileDescriptor fileDescriptor = new FileDescriptor(); 209 fdAccess.set(fileDescriptor, fd); 210 return fileDescriptor; 211 } 212 213 void initStreams(int[] fds) throws IOException { 214 stdin = (fds[0] == -1) ? 215 ProcessBuilder.NullOutputStream.INSTANCE : 216 new ProcessPipeOutputStream(fds[0]); 217 218 stdout = (fds[1] == -1) ? 219 ProcessBuilder.NullInputStream.INSTANCE : 220 new ProcessPipeInputStream(fds[1]); 221 222 stderr = (fds[2] == -1) ? 223 ProcessBuilder.NullInputStream.INSTANCE : 224 new ProcessPipeInputStream(fds[2]); 225 226 processReaperExecutor.execute(new Runnable() { 227 public void run() { 228 int exitcode = waitForProcessExit(pid); 229 UNIXProcess.this.processExited(exitcode); 230 }}); 231 } 232 233 void processExited(int exitcode) { 234 synchronized (this) { 235 this.exitcode = exitcode; 236 hasExited = true; 237 notifyAll(); 238 } 239 240 if (stdout instanceof ProcessPipeInputStream) 241 ((ProcessPipeInputStream) stdout).processExited(); 242 243 if (stderr instanceof ProcessPipeInputStream) 244 ((ProcessPipeInputStream) stderr).processExited(); 245 246 if (stdin instanceof ProcessPipeOutputStream) 247 ((ProcessPipeOutputStream) stdin).processExited(); 248 } 249 250 public OutputStream getOutputStream() { 251 return stdin; 252 } 253 254 public InputStream getInputStream() { 255 return stdout; 256 } 257 258 public InputStream getErrorStream() { 259 return stderr; 260 } 261 262 public synchronized int waitFor() throws InterruptedException { 263 while (!hasExited) { 264 wait(); 265 } 266 return exitcode; 267 } 275 276 long timeoutAsNanos = unit.toNanos(timeout); 277 long startTime = System.nanoTime(); 278 long rem = timeoutAsNanos; 279 280 while (!hasExited && (rem > 0)) { 281 wait(Math.max(TimeUnit.NANOSECONDS.toMillis(rem), 1)); 282 rem = timeoutAsNanos - (System.nanoTime() - startTime); 283 } 284 return hasExited; 285 } 286 287 public synchronized int exitValue() { 288 if (!hasExited) { 289 throw new IllegalThreadStateException("process hasn't exited"); 290 } 291 return exitcode; 292 } 293 294 private static native void destroyProcess(int pid, boolean force); 295 private void destroy(boolean force) { 296 // There is a risk that pid will be recycled, causing us to 297 // kill the wrong process! So we only terminate processes 298 // that appear to still be running. Even with this check, 299 // there is an unavoidable race condition here, but the window 300 // is very small, and OSes try hard to not recycle pids too 301 // soon, so this is quite safe. 302 synchronized (this) { 303 if (!hasExited) 304 destroyProcess(pid, force); 305 } 306 try { stdin.close(); } catch (IOException ignored) {} 307 try { stdout.close(); } catch (IOException ignored) {} 308 try { stderr.close(); } catch (IOException ignored) {} 309 } 310 311 public void destroy() { 312 destroy(false); 313 } 314 315 @Override 316 public Process destroyForcibly() { 317 destroy(true); 318 return this; 319 } 320 321 @Override 322 public synchronized boolean isAlive() { 323 return !hasExited; 324 } 325 326 private static native void init(); 327 328 static { 329 init(); 330 } 331 332 /** 333 * A buffered input stream for a subprocess pipe file descriptor 334 * that allows the underlying file descriptor to be reclaimed when 335 * the process exits, via the processExited hook. 336 * 337 * This is tricky because we do not want the user-level InputStream to be 338 * closed until the user invokes close(), and we need to continue to be 339 * able to read any buffered data lingering in the OS pipe buffer. 340 */ 341 static class ProcessPipeInputStream extends BufferedInputStream { 342 private final Object closeLock = new Object(); 343 344 ProcessPipeInputStream(int fd) { 345 super(new FileInputStream(newFileDescriptor(fd))); 346 } 347 private static byte[] drainInputStream(InputStream in) 348 throws IOException { 349 int n = 0; 350 int j; 351 byte[] a = null; 352 while ((j = in.available()) > 0) { 353 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j); 354 n += in.read(a, n, j); 355 } 356 return (a == null || n == a.length) ? a : Arrays.copyOf(a, n); 357 } 358 359 /** Called by the process reaper thread when the process exits. */ 360 synchronized void processExited() { 361 synchronized (closeLock) { 362 try { 363 InputStream in = this.in; 364 // this stream is closed if and only if: in == null 365 if (in != null) { 366 byte[] stragglers = drainInputStream(in); 367 in.close(); 368 this.in = (stragglers == null) ? 369 ProcessBuilder.NullInputStream.INSTANCE : 370 new ByteArrayInputStream(stragglers); 371 } 372 } catch (IOException ignored) {} 373 } 374 } 375 376 @Override 377 public void close() throws IOException { 378 // BufferedInputStream#close() is not synchronized unlike most other methods. 379 // Synchronizing helps avoid race with processExited(). 380 synchronized (closeLock) { 381 super.close(); 382 } 383 } 384 } 385 386 /** 387 * A buffered output stream for a subprocess pipe file descriptor 388 * that allows the underlying file descriptor to be reclaimed when 389 * the process exits, via the processExited hook. 390 */ 391 static class ProcessPipeOutputStream extends BufferedOutputStream { 392 ProcessPipeOutputStream(int fd) { 393 super(new FileOutputStream(newFileDescriptor(fd))); 394 } 395 396 /** Called by the process reaper thread when the process exits. */ 397 synchronized void processExited() { 398 OutputStream out = this.out; 399 if (out != null) { 400 try { 401 out.close(); 402 } catch (IOException ignored) { 403 // We know of no reason to get an IOException, but if 404 // we do, there's nothing else to do but carry on. 405 } 406 this.out = ProcessBuilder.NullOutputStream.INSTANCE; 407 } 408 } 409 } 410 } | 1 /* 2 * Copyright (c) 1995, 2014, 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.io.BufferedInputStream; 29 import java.io.BufferedOutputStream; 30 import java.io.ByteArrayInputStream; 31 import java.io.FileDescriptor; 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 import java.util.Arrays; 38 import java.util.EnumSet; 39 import java.util.Locale; 40 import java.util.Set; 41 import java.util.concurrent.Executors; 42 import java.util.concurrent.Executor; 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 50 /** 51 * java.lang.Process subclass in the UNIX environment. 52 * 53 * @author Mario Wolczko and Ross Knippel. 54 * @author Konstantin Kladko (ported to Linux and Bsd) 55 * @author Martin Buchholz 56 * @author Volker Simonis (ported to AIX) 57 */ 58 final class UNIXProcess extends Process { 59 private static final sun.misc.JavaIOFileDescriptorAccess fdAccess 60 = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); 61 62 private final int pid; 63 private int exitcode; 64 private boolean hasExited; 65 66 private /* final */ OutputStream stdin; 67 private /* final */ InputStream stdout; 68 private /* final */ InputStream stderr; 69 70 // only used on Solaris 71 private /* final */ DeferredCloseInputStream stdout_inner_stream; 72 73 private static enum LaunchMechanism { 74 // order IS important! 75 FORK, 76 POSIX_SPAWN, 77 VFORK 78 } 79 80 private static enum Platform { 81 82 LINUX(LaunchMechanism.VFORK, LaunchMechanism.FORK), 83 84 BSD(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK), 85 86 SOLARIS(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK), 87 88 AIX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK); 89 90 final LaunchMechanism defaultLaunchMechanism; 91 final Set<LaunchMechanism> validLaunchMechanisms; 92 93 Platform(LaunchMechanism ... launchMechanisms) { 94 this.defaultLaunchMechanism = launchMechanisms[0]; 95 this.validLaunchMechanisms = 96 EnumSet.copyOf(Arrays.asList(launchMechanisms)); 97 } 98 99 private String helperPath(String javahome, String osArch) { 100 switch (this) { 101 case SOLARIS: 102 if (osArch.equals("x86")) { osArch = "i386"; } 103 else if (osArch.equals("x86_64")) { osArch = "amd64"; } 104 // fall through... 105 case LINUX: 106 case AIX: 107 return javahome + "/lib/" + osArch + "/jspawnhelper"; 108 109 case BSD: 110 return javahome + "/lib/jspawnhelper"; 111 112 default: 113 throw new AssertionError("Unsupported platform: " + this); 114 } 115 } 116 117 String helperPath() { 118 return AccessController.doPrivileged( 119 (PrivilegedAction<String>) () -> 120 helperPath(System.getProperty("java.home"), 121 System.getProperty("os.arch")) 122 ); 123 } 124 125 LaunchMechanism launchMechanism() { 126 return AccessController.doPrivileged( 127 (PrivilegedAction<LaunchMechanism>) () -> { 128 String s = System.getProperty( 129 "jdk.lang.Process.launchMechanism"); 130 LaunchMechanism lm; 131 if (s == null) { 132 lm = defaultLaunchMechanism; 133 s = lm.name().toLowerCase(Locale.ENGLISH); 134 } else { 135 try { 136 lm = LaunchMechanism.valueOf( 137 s.toUpperCase(Locale.ENGLISH)); 138 } catch (IllegalArgumentException e) { 139 lm = null; 140 } 141 } 142 if (lm == null || !validLaunchMechanisms.contains(lm)) { 143 throw new Error( 144 s + " is not a supported " + 145 "process launch mechanism on this platform." 146 ); 147 } 148 return lm; 149 } 150 ); 151 } 152 153 static Platform get() { 154 String osName = AccessController.doPrivileged( 155 (PrivilegedAction<String>) () -> System.getProperty("os.name") 156 ); 157 158 if (osName.equals("Linux")) { return LINUX; } 159 if (osName.contains("OS X")) { return BSD; } 160 if (osName.equals("SunOS")) { return SOLARIS; } 161 if (osName.equals("AIX")) { return AIX; } 162 163 throw new Error(osName + " is not a supported OS platform."); 164 } 165 } 166 167 private static final Platform platform = Platform.get(); 168 private static final LaunchMechanism launchMechanism = platform.launchMechanism(); 169 private static final byte[] helperpath = toCString(platform.helperPath()); 170 171 private static byte[] toCString(String s) { 172 if (s == null) 173 return null; 174 byte[] bytes = s.getBytes(); 175 byte[] result = new byte[bytes.length + 1]; 176 System.arraycopy(bytes, 0, 177 result, 0, 178 bytes.length); 179 result[result.length-1] = (byte)0; 180 return result; 181 } 182 183 /* this is for the reaping thread */ 184 private native int waitForProcessExit(int pid); 185 186 /** 187 * Create a process. Depending on the mode flag, this is done by 188 * one of the following mechanisms. 189 * - fork(2) and exec(2) 190 * - clone(2) and exec(2) 191 * - vfork(2) and exec(2) 192 * 193 * @param fds an array of three file descriptors. 194 * Indexes 0, 1, and 2 correspond to standard input, 195 * standard output and standard error, respectively. On 196 * input, a value of -1 means to create a pipe to connect 197 * child and parent processes. On output, a value which 198 * is not -1 is the parent pipe fd corresponding to the 199 * pipe which has been created. An element of this array 200 * is -1 on input if and only if it is <em>not</em> -1 on 201 * output. 202 * @return the pid of the subprocess 203 */ 204 private native int forkAndExec(int mode, byte[] helperpath, 205 byte[] prog, 206 byte[] argBlock, int argc, 207 byte[] envBlock, int envc, 208 byte[] dir, 209 int[] fds, 210 boolean redirectErrorStream) 211 throws IOException; 212 213 /** 214 * Root thread group for threads created by processReaperExecutor's 215 * ThreadFactory 216 */ 217 private static final ThreadGroup rootThreadGroup = 218 doPrivileged((PrivilegedAction<ThreadGroup>) () -> { 219 ThreadGroup root = Thread.currentThread().getThreadGroup(); 220 while (root.getParent() != null) 221 root = root.getParent(); 222 return root; 223 }); 224 225 /** 226 * The thread pool of "process reaper" daemon threads. 227 */ 228 private static final Executor processReaperExecutor = 229 Executors.newCachedThreadPool(grimReaper -> { 230 // Our thread stack requirement is quite modest. 231 Thread t = new Thread(rootThreadGroup, grimReaper, 232 "process reaper", 32768); 233 t.setDaemon(true); 234 // A small attempt (probably futile) to avoid priority inversion 235 t.setPriority(Thread.MAX_PRIORITY); 236 return t; 237 }); 238 239 UNIXProcess(final byte[] prog, 240 final byte[] argBlock, final int argc, 241 final byte[] envBlock, final int envc, 242 final byte[] dir, 243 final int[] fds, 244 final boolean redirectErrorStream) 245 throws IOException { 246 247 pid = forkAndExec(launchMechanism.ordinal() + 1, 248 helperpath, 249 prog, 250 argBlock, argc, 251 envBlock, envc, 252 dir, 253 fds, 254 redirectErrorStream); 255 256 try { 257 doPrivileged((PrivilegedExceptionAction<Void>) () -> { 258 initStreams(fds); 259 return null; 260 }); 261 } catch (PrivilegedActionException ex) { 262 throw (IOException) ex.getException(); 263 } 264 } 265 266 static FileDescriptor newFileDescriptor(int fd) { 267 FileDescriptor fileDescriptor = new FileDescriptor(); 268 fdAccess.set(fileDescriptor, fd); 269 return fileDescriptor; 270 } 271 272 void initStreams(int[] fds) throws IOException { 273 switch (platform) { 274 case LINUX: 275 case BSD: 276 stdin = (fds[0] == -1) ? 277 ProcessBuilder.NullOutputStream.INSTANCE : 278 new ProcessPipeOutputStream(fds[0]); 279 280 stdout = (fds[1] == -1) ? 281 ProcessBuilder.NullInputStream.INSTANCE : 282 new ProcessPipeInputStream(fds[1]); 283 284 stderr = (fds[2] == -1) ? 285 ProcessBuilder.NullInputStream.INSTANCE : 286 new ProcessPipeInputStream(fds[2]); 287 288 processReaperExecutor.execute(() -> { 289 int exitcode = waitForProcessExit(pid); 290 291 synchronized (this) { 292 this.exitcode = exitcode; 293 this.hasExited = true; 294 this.notifyAll(); 295 } 296 297 if (stdout instanceof ProcessPipeInputStream) 298 ((ProcessPipeInputStream) stdout).processExited(); 299 300 if (stderr instanceof ProcessPipeInputStream) 301 ((ProcessPipeInputStream) stderr).processExited(); 302 303 if (stdin instanceof ProcessPipeOutputStream) 304 ((ProcessPipeOutputStream) stdin).processExited(); 305 }); 306 break; 307 308 case SOLARIS: 309 stdin = (fds[0] == -1) ? 310 ProcessBuilder.NullOutputStream.INSTANCE : 311 new BufferedOutputStream( 312 new FileOutputStream(newFileDescriptor(fds[0]))); 313 314 stdout = (fds[1] == -1) ? 315 ProcessBuilder.NullInputStream.INSTANCE : 316 new BufferedInputStream( 317 stdout_inner_stream = 318 new DeferredCloseInputStream( 319 newFileDescriptor(fds[1]))); 320 321 stderr = (fds[2] == -1) ? 322 ProcessBuilder.NullInputStream.INSTANCE : 323 new DeferredCloseInputStream(newFileDescriptor(fds[2])); 324 325 /* 326 * For each subprocess forked a corresponding reaper task 327 * is submitted. That task is the only thread which waits 328 * for the subprocess to terminate and it doesn't hold any 329 * locks while doing so. This design allows waitFor() and 330 * exitStatus() to be safely executed in parallel (and they 331 * need no native code). 332 */ 333 processReaperExecutor.execute(() -> { 334 int exitcode = waitForProcessExit(pid); 335 336 synchronized (this) { 337 this.exitcode = exitcode; 338 this.hasExited = true; 339 this.notifyAll(); 340 } 341 }); 342 break; 343 344 case AIX: 345 stdin = (fds[0] == -1) ? 346 ProcessBuilder.NullOutputStream.INSTANCE : 347 new ProcessPipeOutputStream(fds[0]); 348 349 stdout = (fds[1] == -1) ? 350 ProcessBuilder.NullInputStream.INSTANCE : 351 new DeferredCloseProcessPipeInputStream(fds[1]); 352 353 stderr = (fds[2] == -1) ? 354 ProcessBuilder.NullInputStream.INSTANCE : 355 new DeferredCloseProcessPipeInputStream(fds[2]); 356 357 processReaperExecutor.execute(() -> { 358 int exitcode = waitForProcessExit(pid); 359 360 synchronized (this) { 361 this.exitcode = exitcode; 362 this.hasExited = true; 363 this.notifyAll(); 364 } 365 366 if (stdout instanceof DeferredCloseProcessPipeInputStream) 367 ((DeferredCloseProcessPipeInputStream) stdout).processExited(); 368 369 if (stderr instanceof DeferredCloseProcessPipeInputStream) 370 ((DeferredCloseProcessPipeInputStream) stderr).processExited(); 371 372 if (stdin instanceof ProcessPipeOutputStream) 373 ((ProcessPipeOutputStream) stdin).processExited(); 374 }); 375 break; 376 377 default: throw new AssertionError("Unsupported platform: " + platform); 378 } 379 } 380 381 public OutputStream getOutputStream() { 382 return stdin; 383 } 384 385 public InputStream getInputStream() { 386 return stdout; 387 } 388 389 public InputStream getErrorStream() { 390 return stderr; 391 } 392 393 public synchronized int waitFor() throws InterruptedException { 394 while (!hasExited) { 395 wait(); 396 } 397 return exitcode; 398 } 406 407 long timeoutAsNanos = unit.toNanos(timeout); 408 long startTime = System.nanoTime(); 409 long rem = timeoutAsNanos; 410 411 while (!hasExited && (rem > 0)) { 412 wait(Math.max(TimeUnit.NANOSECONDS.toMillis(rem), 1)); 413 rem = timeoutAsNanos - (System.nanoTime() - startTime); 414 } 415 return hasExited; 416 } 417 418 public synchronized int exitValue() { 419 if (!hasExited) { 420 throw new IllegalThreadStateException("process hasn't exited"); 421 } 422 return exitcode; 423 } 424 425 private static native void destroyProcess(int pid, boolean force); 426 427 private void destroy(boolean force) { 428 switch (platform) { 429 case LINUX: 430 case BSD: 431 case AIX: 432 // There is a risk that pid will be recycled, causing us to 433 // kill the wrong process! So we only terminate processes 434 // that appear to still be running. Even with this check, 435 // there is an unavoidable race condition here, but the window 436 // is very small, and OSes try hard to not recycle pids too 437 // soon, so this is quite safe. 438 synchronized (this) { 439 if (!hasExited) 440 destroyProcess(pid, force); 441 } 442 try { stdin.close(); } catch (IOException ignored) {} 443 try { stdout.close(); } catch (IOException ignored) {} 444 try { stderr.close(); } catch (IOException ignored) {} 445 break; 446 447 case SOLARIS: 448 // There is a risk that pid will be recycled, causing us to 449 // kill the wrong process! So we only terminate processes 450 // that appear to still be running. Even with this check, 451 // there is an unavoidable race condition here, but the window 452 // is very small, and OSes try hard to not recycle pids too 453 // soon, so this is quite safe. 454 synchronized (this) { 455 if (!hasExited) 456 destroyProcess(pid, force); 457 try { 458 stdin.close(); 459 if (stdout_inner_stream != null) 460 stdout_inner_stream.closeDeferred(stdout); 461 if (stderr instanceof DeferredCloseInputStream) 462 ((DeferredCloseInputStream) stderr) 463 .closeDeferred(stderr); 464 } catch (IOException e) { 465 // ignore 466 } 467 } 468 break; 469 470 default: throw new AssertionError("Unsupported platform: " + platform); 471 } 472 } 473 474 public void destroy() { 475 destroy(false); 476 } 477 478 @Override 479 public Process destroyForcibly() { 480 destroy(true); 481 return this; 482 } 483 484 @Override 485 public synchronized boolean isAlive() { 486 return !hasExited; 487 } 488 489 private static native void init(); 490 491 static { 492 init(); 493 } 494 495 /** 496 * A buffered input stream for a subprocess pipe file descriptor 497 * that allows the underlying file descriptor to be reclaimed when 498 * the process exits, via the processExited hook. 499 * 500 * This is tricky because we do not want the user-level InputStream to be 501 * closed until the user invokes close(), and we need to continue to be 502 * able to read any buffered data lingering in the OS pipe buffer. 503 */ 504 private static class ProcessPipeInputStream extends BufferedInputStream { 505 private final Object closeLock = new Object(); 506 507 ProcessPipeInputStream(int fd) { 508 super(new FileInputStream(newFileDescriptor(fd))); 509 } 510 private static byte[] drainInputStream(InputStream in) 511 throws IOException { 512 int n = 0; 513 int j; 514 byte[] a = null; 515 while ((j = in.available()) > 0) { 516 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j); 517 n += in.read(a, n, j); 518 } 519 return (a == null || n == a.length) ? a : Arrays.copyOf(a, n); 520 } 521 522 /** Called by the process reaper thread when the process exits. */ 523 synchronized void processExited() { 524 synchronized (closeLock) { 525 try { 526 InputStream in = this.in; 527 // this stream is closed if and only if: in == null 528 if (in != null) { 529 byte[] stragglers = drainInputStream(in); 530 in.close(); 531 this.in = (stragglers == null) ? 532 ProcessBuilder.NullInputStream.INSTANCE : 533 new ByteArrayInputStream(stragglers); 534 } 535 } catch (IOException ignored) {} 536 } 537 } 538 539 @Override 540 public void close() throws IOException { 541 // BufferedInputStream#close() is not synchronized unlike most other 542 // methods. Synchronizing helps avoid race with processExited(). 543 synchronized (closeLock) { 544 super.close(); 545 } 546 } 547 } 548 549 /** 550 * A buffered output stream for a subprocess pipe file descriptor 551 * that allows the underlying file descriptor to be reclaimed when 552 * the process exits, via the processExited hook. 553 */ 554 private static class ProcessPipeOutputStream extends BufferedOutputStream { 555 ProcessPipeOutputStream(int fd) { 556 super(new FileOutputStream(newFileDescriptor(fd))); 557 } 558 559 /** Called by the process reaper thread when the process exits. */ 560 synchronized void processExited() { 561 OutputStream out = this.out; 562 if (out != null) { 563 try { 564 out.close(); 565 } catch (IOException ignored) { 566 // We know of no reason to get an IOException, but if 567 // we do, there's nothing else to do but carry on. 568 } 569 this.out = ProcessBuilder.NullOutputStream.INSTANCE; 570 } 571 } 572 } 573 574 // A FileInputStream that supports the deferment of the actual close 575 // operation until the last pending I/O operation on the stream has 576 // finished. This is required on Solaris because we must close the stdin 577 // and stdout streams in the destroy method in order to reclaim the 578 // underlying file descriptors. Doing so, however, causes any thread 579 // currently blocked in a read on one of those streams to receive an 580 // IOException("Bad file number"), which is incompatible with historical 581 // behavior. By deferring the close we allow any pending reads to see -1 582 // (EOF) as they did before. 583 // 584 private static class DeferredCloseInputStream extends FileInputStream 585 { 586 DeferredCloseInputStream(FileDescriptor fd) { 587 super(fd); 588 } 589 590 private Object lock = new Object(); // For the following fields 591 private boolean closePending = false; 592 private int useCount = 0; 593 private InputStream streamToClose; 594 595 private void raise() { 596 synchronized (lock) { 597 useCount++; 598 } 599 } 600 601 private void lower() throws IOException { 602 synchronized (lock) { 603 useCount--; 604 if (useCount == 0 && closePending) { 605 streamToClose.close(); 606 } 607 } 608 } 609 610 // stc is the actual stream to be closed; it might be this object, or 611 // it might be an upstream object for which this object is downstream. 612 // 613 private void closeDeferred(InputStream stc) throws IOException { 614 synchronized (lock) { 615 if (useCount == 0) { 616 stc.close(); 617 } else { 618 closePending = true; 619 streamToClose = stc; 620 } 621 } 622 } 623 624 public void close() throws IOException { 625 synchronized (lock) { 626 useCount = 0; 627 closePending = false; 628 } 629 super.close(); 630 } 631 632 public int read() throws IOException { 633 raise(); 634 try { 635 return super.read(); 636 } finally { 637 lower(); 638 } 639 } 640 641 public int read(byte[] b) throws IOException { 642 raise(); 643 try { 644 return super.read(b); 645 } finally { 646 lower(); 647 } 648 } 649 650 public int read(byte[] b, int off, int len) throws IOException { 651 raise(); 652 try { 653 return super.read(b, off, len); 654 } finally { 655 lower(); 656 } 657 } 658 659 public long skip(long n) throws IOException { 660 raise(); 661 try { 662 return super.skip(n); 663 } finally { 664 lower(); 665 } 666 } 667 668 public int available() throws IOException { 669 raise(); 670 try { 671 return super.available(); 672 } finally { 673 lower(); 674 } 675 } 676 } 677 678 /** 679 * A buffered input stream for a subprocess pipe file descriptor 680 * that allows the underlying file descriptor to be reclaimed when 681 * the process exits, via the processExited hook. 682 * 683 * This is tricky because we do not want the user-level InputStream to be 684 * closed until the user invokes close(), and we need to continue to be 685 * able to read any buffered data lingering in the OS pipe buffer. 686 * 687 * On AIX this is especially tricky, because the 'close()' system call 688 * will block if another thread is at the same time blocked in a file 689 * operation (e.g. 'read()') on the same file descriptor. We therefore 690 * combine 'ProcessPipeInputStream' approach used on Linux and Bsd 691 * with the DeferredCloseInputStream approach used on Solaris. This means 692 * that every potentially blocking operation on the file descriptor 693 * increments a counter before it is executed and decrements it once it 694 * finishes. The 'close()' operation will only be executed if there are 695 * no pending operations. Otherwise it is deferred after the last pending 696 * operation has finished. 697 * 698 */ 699 private static class DeferredCloseProcessPipeInputStream 700 extends BufferedInputStream { 701 702 private final Object closeLock = new Object(); 703 private int useCount = 0; 704 private boolean closePending = false; 705 706 DeferredCloseProcessPipeInputStream(int fd) { 707 super(new FileInputStream(newFileDescriptor(fd))); 708 } 709 710 private InputStream drainInputStream(InputStream in) 711 throws IOException { 712 int n = 0; 713 int j; 714 byte[] a = null; 715 synchronized (closeLock) { 716 if (buf == null) // asynchronous close()? 717 return null; // discard 718 j = in.available(); 719 } 720 while (j > 0) { 721 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j); 722 synchronized (closeLock) { 723 if (buf == null) // asynchronous close()? 724 return null; // discard 725 n += in.read(a, n, j); 726 j = in.available(); 727 } 728 } 729 return (a == null) ? 730 ProcessBuilder.NullInputStream.INSTANCE : 731 new ByteArrayInputStream(n == a.length ? a : Arrays.copyOf(a, n)); 732 } 733 734 /** Called by the process reaper thread when the process exits. */ 735 synchronized void processExited() { 736 try { 737 InputStream in = this.in; 738 if (in != null) { 739 InputStream stragglers = drainInputStream(in); 740 in.close(); 741 this.in = stragglers; 742 } 743 } catch (IOException ignored) { } 744 } 745 746 private void raise() { 747 synchronized (closeLock) { 748 useCount++; 749 } 750 } 751 752 private void lower() throws IOException { 753 synchronized (closeLock) { 754 useCount--; 755 if (useCount == 0 && closePending) { 756 closePending = false; 757 super.close(); 758 } 759 } 760 } 761 762 @Override 763 public int read() throws IOException { 764 raise(); 765 try { 766 return super.read(); 767 } finally { 768 lower(); 769 } 770 } 771 772 @Override 773 public int read(byte[] b) throws IOException { 774 raise(); 775 try { 776 return super.read(b); 777 } finally { 778 lower(); 779 } 780 } 781 782 @Override 783 public int read(byte[] b, int off, int len) throws IOException { 784 raise(); 785 try { 786 return super.read(b, off, len); 787 } finally { 788 lower(); 789 } 790 } 791 792 @Override 793 public long skip(long n) throws IOException { 794 raise(); 795 try { 796 return super.skip(n); 797 } finally { 798 lower(); 799 } 800 } 801 802 @Override 803 public int available() throws IOException { 804 raise(); 805 try { 806 return super.available(); 807 } finally { 808 lower(); 809 } 810 } 811 812 @Override 813 public void close() throws IOException { 814 // BufferedInputStream#close() is not synchronized unlike most other 815 // methods. Synchronizing helps avoid racing with drainInputStream(). 816 synchronized (closeLock) { 817 if (useCount == 0) { 818 super.close(); 819 } 820 else { 821 closePending = true; 822 } 823 } 824 } 825 } 826 } |