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