1 /* 2 * Copyright (c) 1995, 2023, 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.File; 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.lang.ProcessBuilder.Redirect; 38 import java.security.AccessController; 39 import java.security.PrivilegedAction; 40 import java.util.ArrayList; 41 import java.util.Locale; 42 import java.util.concurrent.CompletableFuture; 43 import java.util.concurrent.TimeUnit; 44 import java.util.regex.Matcher; 45 import java.util.regex.Pattern; 46 47 import jdk.internal.access.JavaIOFileDescriptorAccess; 48 import jdk.internal.access.SharedSecrets; 49 import jdk.internal.ref.CleanerFactory; 50 import jdk.internal.misc.Blocker; 51 import sun.security.action.GetPropertyAction; 52 53 /* This class is for the exclusive use of ProcessBuilder.start() to 54 * create new processes. 55 * 56 * @author Martin Buchholz 57 * @since 1.5 58 */ 59 60 final class ProcessImpl extends Process { 61 private static final JavaIOFileDescriptorAccess fdAccess 62 = SharedSecrets.getJavaIOFileDescriptorAccess(); 63 64 // Windows platforms support a forcible kill signal. 65 static final boolean SUPPORTS_NORMAL_TERMINATION = false; 66 67 /** 68 * Open a file for writing. If {@code append} is {@code true} then the file 69 * is opened for atomic append directly and a FileOutputStream constructed 70 * with the resulting handle. This is because a FileOutputStream created 71 * to append to a file does not open the file in a manner that guarantees 72 * that writes by the child process will be atomic. 73 */ 74 @SuppressWarnings("removal") 75 private static FileOutputStream newFileOutputStream(File f, boolean append) 76 throws IOException 77 { 78 if (append) { 79 String path = f.getPath(); 80 SecurityManager sm = System.getSecurityManager(); 81 if (sm != null) 82 sm.checkWrite(path); 83 long handle = openForAtomicAppend(path); 84 final FileDescriptor fd = new FileDescriptor(); 85 fdAccess.setHandle(fd, handle); 86 return AccessController.doPrivileged( 87 new PrivilegedAction<FileOutputStream>() { 88 public FileOutputStream run() { 89 return new FileOutputStream(fd); 90 } 91 } 92 ); 93 } else { 94 return new FileOutputStream(f); 95 } 96 } 97 98 // System-dependent portion of ProcessBuilder.start() 99 static Process start(String cmdarray[], 100 java.util.Map<String,String> environment, 101 String dir, 102 ProcessBuilder.Redirect[] redirects, 103 boolean redirectErrorStream) 104 throws IOException 105 { 106 String envblock = ProcessEnvironment.toEnvironmentBlock(environment); 107 108 FileInputStream f0 = null; 109 FileOutputStream f1 = null; 110 FileOutputStream f2 = null; 111 112 try { 113 boolean forceNullOutputStream = false; 114 long[] stdHandles; 115 if (redirects == null) { 116 stdHandles = new long[] { -1L, -1L, -1L }; 117 } else { 118 stdHandles = new long[3]; 119 120 if (redirects[0] == Redirect.PIPE) { 121 stdHandles[0] = -1L; 122 } else if (redirects[0] == Redirect.INHERIT) { 123 stdHandles[0] = fdAccess.getHandle(FileDescriptor.in); 124 } else if (redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) { 125 stdHandles[0] = fdAccess.getHandle(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd()); 126 } else { 127 f0 = new FileInputStream(redirects[0].file()); 128 stdHandles[0] = fdAccess.getHandle(f0.getFD()); 129 } 130 131 if (redirects[1] == Redirect.PIPE) { 132 stdHandles[1] = -1L; 133 } else if (redirects[1] == Redirect.INHERIT) { 134 stdHandles[1] = fdAccess.getHandle(FileDescriptor.out); 135 } else if (redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) { 136 stdHandles[1] = fdAccess.getHandle(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd()); 137 // Force getInputStream to return a null stream, 138 // the handle is directly assigned to the next process. 139 forceNullOutputStream = true; 140 } else { 141 f1 = newFileOutputStream(redirects[1].file(), 142 redirects[1].append()); 143 stdHandles[1] = fdAccess.getHandle(f1.getFD()); 144 } 145 146 if (redirects[2] == Redirect.PIPE) { 147 stdHandles[2] = -1L; 148 } else if (redirects[2] == Redirect.INHERIT) { 149 stdHandles[2] = fdAccess.getHandle(FileDescriptor.err); 150 } else if (redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) { 151 stdHandles[2] = fdAccess.getHandle(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd()); 152 } else { 153 f2 = newFileOutputStream(redirects[2].file(), 154 redirects[2].append()); 155 stdHandles[2] = fdAccess.getHandle(f2.getFD()); 156 } 157 } 158 159 Process p = new ProcessImpl(cmdarray, envblock, dir, 160 stdHandles, forceNullOutputStream, redirectErrorStream); 161 if (redirects != null) { 162 // Copy the handles's if they are to be redirected to another process 163 if (stdHandles[0] >= 0 164 && redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) { 165 fdAccess.setHandle(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd(), 166 stdHandles[0]); 167 } 168 if (stdHandles[1] >= 0 169 && redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) { 170 fdAccess.setHandle(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd(), 171 stdHandles[1]); 172 } 173 if (stdHandles[2] >= 0 174 && redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) { 175 fdAccess.setHandle(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd(), 176 stdHandles[2]); 177 } 178 } 179 return p; 180 } finally { 181 // In theory, close() can throw IOException 182 // (although it is rather unlikely to happen here) 183 try { if (f0 != null) f0.close(); } 184 finally { 185 try { if (f1 != null) f1.close(); } 186 finally { if (f2 != null) f2.close(); } 187 } 188 } 189 190 } 191 192 private static class LazyPattern { 193 // Escape-support version: 194 // "(\")((?:\\\\\\1|.)+?)\\1|([^\\s\"]+)"; 195 private static final Pattern PATTERN = 196 Pattern.compile("[^\\s\"]+|\"[^\"]*\""); 197 }; 198 199 /* Parses the command string parameter into the executable name and 200 * program arguments. 201 * 202 * The command string is broken into tokens. The token separator is a space 203 * or quota character. The space inside quotation is not a token separator. 204 * There are no escape sequences. 205 */ 206 private static String[] getTokensFromCommand(String command) { 207 ArrayList<String> matchList = new ArrayList<>(8); 208 Matcher regexMatcher = LazyPattern.PATTERN.matcher(command); 209 while (regexMatcher.find()) 210 matchList.add(regexMatcher.group()); 211 return matchList.toArray(new String[matchList.size()]); 212 } 213 214 private static final int VERIFICATION_CMD_BAT = 0; 215 private static final int VERIFICATION_WIN32 = 1; 216 private static final int VERIFICATION_WIN32_SAFE = 2; // inside quotes not allowed 217 private static final int VERIFICATION_LEGACY = 3; 218 // See Command shell overview for documentation of special characters. 219 // https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490954(v=technet.10) 220 private static final char ESCAPE_VERIFICATION[][] = { 221 // We guarantee the only command file execution for implicit [cmd.exe] run. 222 // http://technet.microsoft.com/en-us/library/bb490954.aspx 223 {' ', '\t', '\"', '<', '>', '&', '|', '^'}, 224 {' ', '\t', '\"', '<', '>'}, 225 {' ', '\t', '\"', '<', '>'}, 226 {' ', '\t'} 227 }; 228 229 private static String createCommandLine(int verificationType, 230 final String executablePath, 231 final String cmd[]) 232 { 233 StringBuilder cmdbuf = new StringBuilder(80); 234 235 cmdbuf.append(executablePath); 236 237 for (int i = 1; i < cmd.length; ++i) { 238 cmdbuf.append(' '); 239 String s = cmd[i]; 240 if (needsEscaping(verificationType, s)) { 241 cmdbuf.append('"'); 242 243 if (verificationType == VERIFICATION_WIN32_SAFE) { 244 // Insert the argument, adding '\' to quote any interior quotes 245 int length = s.length(); 246 for (int j = 0; j < length; j++) { 247 char c = s.charAt(j); 248 if (c == DOUBLEQUOTE) { 249 int count = countLeadingBackslash(verificationType, s, j); 250 while (count-- > 0) { 251 cmdbuf.append(BACKSLASH); // double the number of backslashes 252 } 253 cmdbuf.append(BACKSLASH); // backslash to quote the quote 254 } 255 cmdbuf.append(c); 256 } 257 } else { 258 cmdbuf.append(s); 259 } 260 // The code protects the [java.exe] and console command line 261 // parser, that interprets the [\"] combination as an escape 262 // sequence for the ["] char. 263 // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx 264 // 265 // If the argument is an FS path, doubling of the tail [\] 266 // char is not a problem for non-console applications. 267 // 268 // The [\"] sequence is not an escape sequence for the [cmd.exe] 269 // command line parser. The case of the [""] tail escape 270 // sequence could not be realized due to the argument validation 271 // procedure. 272 if (verificationType == VERIFICATION_WIN32_SAFE || 273 verificationType == VERIFICATION_LEGACY) { 274 int count = countLeadingBackslash(verificationType, s, s.length()); 275 while (count-- > 0) { 276 cmdbuf.append(BACKSLASH); // double the number of backslashes 277 } 278 } 279 cmdbuf.append('"'); 280 } else if (verificationType == VERIFICATION_WIN32_SAFE && 281 (s.startsWith("\"") && s.endsWith("\"") && s.length() > 2)) { 282 // Check that quoted argument does not escape the final quote 283 cmdbuf.append(s); 284 int count = countLeadingBackslash(verificationType, s, s.length() - 1); 285 while (count-- > 0) { 286 cmdbuf.insert(cmdbuf.length() - 1, BACKSLASH); // double the number of backslashes 287 } 288 } else { 289 cmdbuf.append(s); 290 } 291 } 292 return cmdbuf.toString(); 293 } 294 295 /** 296 * Return the argument without quotes (first and last) if quoted, otherwise the arg. 297 * @param str a string 298 * @return the string without quotes 299 */ 300 private static String unQuote(String str) { 301 if (!str.startsWith("\"") || !str.endsWith("\"") || str.length() < 2) 302 return str; // no beginning or ending quote, or too short not quoted 303 304 // Strip leading and trailing quotes 305 return str.substring(1, str.length() - 1); 306 } 307 308 private static boolean needsEscaping(int verificationType, String arg) { 309 if (arg.isEmpty()) 310 return true; // Empty string is to be quoted 311 312 // Switch off MS heuristic for internal ["]. 313 // Please, use the explicit [cmd.exe] call 314 // if you need the internal ["]. 315 // Example: "cmd.exe", "/C", "Extended_MS_Syntax" 316 317 // For [.exe] or [.com] file the unpaired/internal ["] 318 // in the argument is not a problem. 319 String unquotedArg = unQuote(arg); 320 boolean argIsQuoted = !arg.equals(unquotedArg); 321 boolean embeddedQuote = unquotedArg.indexOf(DOUBLEQUOTE) >= 0; 322 323 switch (verificationType) { 324 case VERIFICATION_CMD_BAT: 325 if (embeddedQuote) { 326 throw new IllegalArgumentException("Argument has embedded quote, " + 327 "use the explicit CMD.EXE call."); 328 } 329 break; // break determine whether to quote 330 case VERIFICATION_WIN32_SAFE: 331 if (argIsQuoted && embeddedQuote) { 332 throw new IllegalArgumentException("Malformed argument has embedded quote: " 333 + unquotedArg); 334 } 335 break; 336 default: 337 break; 338 } 339 340 if (!argIsQuoted) { 341 char testEscape[] = ESCAPE_VERIFICATION[verificationType]; 342 for (int i = 0; i < testEscape.length; ++i) { 343 if (arg.indexOf(testEscape[i]) >= 0) { 344 return true; 345 } 346 } 347 } 348 return false; 349 } 350 351 private static String getExecutablePath(String path) 352 throws IOException 353 { 354 String name = unQuote(path); 355 if (name.indexOf(DOUBLEQUOTE) >= 0) { 356 throw new IllegalArgumentException("Executable name has embedded quote, " + 357 "split the arguments: " + name); 358 } 359 // Win32 CreateProcess requires path to be normalized 360 File fileToRun = new File(name); 361 362 // From the [CreateProcess] function documentation: 363 // 364 // "If the file name does not contain an extension, .exe is appended. 365 // Therefore, if the file name extension is .com, this parameter 366 // must include the .com extension. If the file name ends in 367 // a period (.) with no extension, or if the file name contains a path, 368 // .exe is not appended." 369 // 370 // "If the file name !does not contain a directory path!, 371 // the system searches for the executable file in the following 372 // sequence:..." 373 // 374 // In practice ANY non-existent path is extended by [.exe] extension 375 // in the [CreateProcess] function with the only exception: 376 // the path ends by (.) 377 378 return fileToRun.getPath(); 379 } 380 381 /** 382 * An executable is any program that is an EXE or does not have an extension 383 * and the Windows createProcess will be looking for .exe. 384 * The comparison is case insensitive based on the name. 385 * @param executablePath the executable file 386 * @return true if the path ends in .exe or does not have an extension. 387 */ 388 private boolean isExe(String executablePath) { 389 File file = new File(executablePath); 390 String upName = file.getName().toUpperCase(Locale.ROOT); 391 return (upName.endsWith(".EXE") || upName.indexOf('.') < 0); 392 } 393 394 // Old version that can be bypassed 395 private boolean isShellFile(String executablePath) { 396 String upPath = executablePath.toUpperCase(Locale.ROOT); 397 return (upPath.endsWith(".CMD") || upPath.endsWith(".BAT")); 398 } 399 400 private String quoteString(String arg) { 401 StringBuilder argbuf = new StringBuilder(arg.length() + 2); 402 return argbuf.append('"').append(arg).append('"').toString(); 403 } 404 405 // Count backslashes before start index of string. 406 // .bat files don't include backslashes as part of the quote 407 private static int countLeadingBackslash(int verificationType, 408 CharSequence input, int start) { 409 if (verificationType == VERIFICATION_CMD_BAT) 410 return 0; 411 int j; 412 for (j = start - 1; j >= 0 && input.charAt(j) == BACKSLASH; j--) { 413 // just scanning backwards 414 } 415 return (start - 1) - j; // number of BACKSLASHES 416 } 417 418 private static final char DOUBLEQUOTE = '\"'; 419 private static final char BACKSLASH = '\\'; 420 421 private final long handle; 422 private final ProcessHandle processHandle; 423 private OutputStream stdin_stream; 424 private InputStream stdout_stream; 425 private InputStream stderr_stream; 426 427 @SuppressWarnings("removal") 428 private ProcessImpl(String cmd[], 429 final String envblock, 430 final String path, 431 final long[] stdHandles, 432 boolean forceNullOutputStream, 433 final boolean redirectErrorStream) 434 throws IOException 435 { 436 String cmdstr; 437 final SecurityManager security = System.getSecurityManager(); 438 final String value = GetPropertyAction. 439 privilegedGetProperty("jdk.lang.Process.allowAmbiguousCommands", 440 (security == null ? "true" : "false")); 441 final boolean allowAmbiguousCommands = !"false".equalsIgnoreCase(value); 442 443 if (allowAmbiguousCommands && security == null) { 444 // Legacy mode. 445 446 // Normalize path if possible. 447 String executablePath = new File(cmd[0]).getPath(); 448 449 // No worry about internal, unpaired ["], and redirection/piping. 450 if (needsEscaping(VERIFICATION_LEGACY, executablePath) ) 451 executablePath = quoteString(executablePath); 452 453 cmdstr = createCommandLine( 454 //legacy mode doesn't worry about extended verification 455 VERIFICATION_LEGACY, 456 executablePath, 457 cmd); 458 } else { 459 String executablePath; 460 try { 461 executablePath = getExecutablePath(cmd[0]); 462 } catch (IllegalArgumentException e) { 463 // Workaround for the calls like 464 // Runtime.getRuntime().exec("\"C:\\Program Files\\foo\" bar") 465 466 // No chance to avoid CMD/BAT injection, except to do the work 467 // right from the beginning. Otherwise we have too many corner 468 // cases from 469 // Runtime.getRuntime().exec(String[] cmd [, ...]) 470 // calls with internal ["] and escape sequences. 471 472 // Restore original command line. 473 StringBuilder join = new StringBuilder(); 474 // terminal space in command line is ok 475 for (String s : cmd) 476 join.append(s).append(' '); 477 478 // Parse the command line again. 479 cmd = getTokensFromCommand(join.toString()); 480 executablePath = getExecutablePath(cmd[0]); 481 482 // Check new executable name once more 483 if (security != null) 484 security.checkExec(executablePath); 485 } 486 487 // Quotation protects from interpretation of the [path] argument as 488 // start of longer path with spaces. Quotation has no influence to 489 // [.exe] extension heuristic. 490 boolean isShell = allowAmbiguousCommands ? isShellFile(executablePath) 491 : !isExe(executablePath); 492 cmdstr = createCommandLine( 493 // We need the extended verification procedures 494 isShell ? VERIFICATION_CMD_BAT 495 : (allowAmbiguousCommands ? VERIFICATION_WIN32 : VERIFICATION_WIN32_SAFE), 496 quoteString(executablePath), 497 cmd); 498 } 499 500 handle = create(cmdstr, envblock, path, 501 stdHandles, redirectErrorStream); 502 // Register a cleaning function to close the handle 503 final long local_handle = handle; // local to prevent capture of this 504 CleanerFactory.cleaner().register(this, () -> closeHandle(local_handle)); 505 506 processHandle = ProcessHandleImpl.getInternal(getProcessId0(handle)); 507 508 java.security.AccessController.doPrivileged( 509 new java.security.PrivilegedAction<Void>() { 510 public Void run() { 511 if (stdHandles[0] == -1L) 512 stdin_stream = ProcessBuilder.NullOutputStream.INSTANCE; 513 else { 514 FileDescriptor stdin_fd = new FileDescriptor(); 515 fdAccess.setHandle(stdin_fd, stdHandles[0]); 516 fdAccess.registerCleanup(stdin_fd); 517 stdin_stream = new BufferedOutputStream( 518 new FileOutputStream(stdin_fd)); 519 } 520 521 if (stdHandles[1] == -1L || forceNullOutputStream) 522 stdout_stream = ProcessBuilder.NullInputStream.INSTANCE; 523 else { 524 FileDescriptor stdout_fd = new FileDescriptor(); 525 fdAccess.setHandle(stdout_fd, stdHandles[1]); 526 fdAccess.registerCleanup(stdout_fd); 527 stdout_stream = new BufferedInputStream( 528 new PipeInputStream(stdout_fd)); 529 } 530 531 if (stdHandles[2] == -1L) 532 stderr_stream = ProcessBuilder.NullInputStream.INSTANCE; 533 else { 534 FileDescriptor stderr_fd = new FileDescriptor(); 535 fdAccess.setHandle(stderr_fd, stdHandles[2]); 536 fdAccess.registerCleanup(stderr_fd); 537 stderr_stream = new PipeInputStream(stderr_fd); 538 } 539 540 return null; }}); 541 } 542 543 public OutputStream getOutputStream() { 544 return stdin_stream; 545 } 546 547 public InputStream getInputStream() { 548 return stdout_stream; 549 } 550 551 public InputStream getErrorStream() { 552 return stderr_stream; 553 } 554 555 private static final int STILL_ACTIVE = getStillActive(); 556 private static native int getStillActive(); 557 558 public int exitValue() { 559 int exitCode = getExitCodeProcess(handle); 560 if (exitCode == STILL_ACTIVE) 561 throw new IllegalThreadStateException("process has not exited"); 562 return exitCode; 563 } 564 private static native int getExitCodeProcess(long handle); 565 566 public int waitFor() throws InterruptedException { 567 long comp = Blocker.begin(); 568 try { 569 waitForInterruptibly(handle); 570 } finally { 571 Blocker.end(comp); 572 } 573 if (Thread.interrupted()) 574 throw new InterruptedException(); 575 return getExitCodeProcess(handle); 576 } 577 578 private static native void waitForInterruptibly(long handle); 579 580 @Override 581 public boolean waitFor(long timeout, TimeUnit unit) 582 throws InterruptedException 583 { 584 long remainingNanos = unit.toNanos(timeout); // throw NPE before other conditions 585 if (getExitCodeProcess(handle) != STILL_ACTIVE) return true; 586 if (timeout <= 0) return false; 587 588 long deadline = System.nanoTime() + remainingNanos; 589 do { 590 // Round up to next millisecond 591 long msTimeout = TimeUnit.NANOSECONDS.toMillis(remainingNanos + 999_999L); 592 if (msTimeout < 0) { 593 // if wraps around then wait a long while 594 msTimeout = Integer.MAX_VALUE; 595 } 596 long comp = Blocker.begin(); 597 try { 598 waitForTimeoutInterruptibly(handle, msTimeout); 599 } finally { 600 Blocker.end(comp); 601 } 602 if (Thread.interrupted()) 603 throw new InterruptedException(); 604 if (getExitCodeProcess(handle) != STILL_ACTIVE) { 605 return true; 606 } 607 remainingNanos = deadline - System.nanoTime(); 608 } while (remainingNanos > 0); 609 610 return (getExitCodeProcess(handle) != STILL_ACTIVE); 611 } 612 613 private static native void waitForTimeoutInterruptibly( 614 long handle, long timeoutMillis); 615 616 @Override 617 public void destroy() { 618 terminateProcess(handle); 619 } 620 621 @Override 622 public CompletableFuture<Process> onExit() { 623 return ProcessHandleImpl.completion(pid(), false) 624 .handleAsync((exitStatus, unusedThrowable) -> this); 625 } 626 627 @Override 628 public ProcessHandle toHandle() { 629 @SuppressWarnings("removal") 630 SecurityManager sm = System.getSecurityManager(); 631 if (sm != null) { 632 sm.checkPermission(new RuntimePermission("manageProcess")); 633 } 634 return processHandle; 635 } 636 637 @Override 638 public boolean supportsNormalTermination() { 639 return ProcessImpl.SUPPORTS_NORMAL_TERMINATION; 640 } 641 642 @Override 643 public Process destroyForcibly() { 644 destroy(); 645 return this; 646 } 647 648 private static native void terminateProcess(long handle); 649 650 @Override 651 public long pid() { 652 return processHandle.pid(); 653 } 654 655 private static native int getProcessId0(long handle); 656 657 @Override 658 public boolean isAlive() { 659 return isProcessAlive(handle); 660 } 661 662 private static native boolean isProcessAlive(long handle); 663 664 /** 665 * The {@code toString} method returns a string consisting of 666 * the native process ID of the process and the exit value of the process. 667 * 668 * @return a string representation of the object. 669 */ 670 @Override 671 public String toString() { 672 int exitCode = getExitCodeProcess(handle); 673 return new StringBuilder("Process[pid=").append(pid()) 674 .append(", exitValue=").append(exitCode == STILL_ACTIVE ? "\"not exited\"" : exitCode) 675 .append("]").toString(); 676 } 677 678 /** 679 * Create a process using the win32 function CreateProcess. 680 * The method is synchronized due to MS kb315939 problem. 681 * All native handles should restore the inherit flag at the end of call. 682 * 683 * @param cmdstr the Windows command line 684 * @param envblock NUL-separated, double-NUL-terminated list of 685 * environment strings in VAR=VALUE form 686 * @param dir the working directory of the process, or null if 687 * inheriting the current directory from the parent process 688 * @param stdHandles array of windows HANDLEs. Indexes 0, 1, and 689 * 2 correspond to standard input, standard output and 690 * standard error, respectively. On input, a value of -1 691 * means to create a pipe to connect child and parent 692 * processes. On output, a value which is not -1 is the 693 * parent pipe handle corresponding to the pipe which has 694 * been created. An element of this array is -1 on input 695 * if and only if it is <em>not</em> -1 on output. 696 * @param redirectErrorStream redirectErrorStream attribute 697 * @return the native subprocess HANDLE returned by CreateProcess 698 */ 699 private static synchronized native long create(String cmdstr, 700 String envblock, 701 String dir, 702 long[] stdHandles, 703 boolean redirectErrorStream) 704 throws IOException; 705 706 /** 707 * Opens a file for atomic append. The file is created if it doesn't 708 * already exist. 709 * 710 * @param path the file to open or create 711 * @return the native HANDLE 712 */ 713 private static native long openForAtomicAppend(String path) 714 throws IOException; 715 716 private static native boolean closeHandle(long handle); 717 }