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 }