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