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