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