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