/* * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.lang; import java.io.IOException; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileDescriptor; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.lang.ProcessBuilder.Redirect; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.StringTokenizer; /* This class is for the exclusive use of ProcessBuilder.start() to * create new processes. * * @author Martin Buchholz * @since 1.5 */ final class ProcessImpl extends Process { private static final sun.misc.JavaIOFileDescriptorAccess fdAccess = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); /** * Open a file for writing. If {@code append} is {@code true} then the file * is opened for atomic append directly and a FileOutputStream constructed * with the resulting handle. This is because a FileOutputStream created * to append to a file does not open the file in a manner that guarantees * that writes by the child process will be atomic. */ private static FileOutputStream newFileOutputStream(File f, boolean append) throws IOException { if (append) { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkWrite(f.getPath()); long handle = openForAtomicAppend(f.getPath()); final FileDescriptor fd = new FileDescriptor(); fdAccess.setHandle(fd, handle); return AccessController.doPrivileged( new PrivilegedAction() { public FileOutputStream run() { return new FileOutputStream(fd); } } ); } else { return new FileOutputStream(f); } } // System-dependent portion of ProcessBuilder.start() static Process start(String cmdarray[], java.util.Map environment, String dir, ProcessBuilder.Redirect[] redirects, boolean redirectErrorStream) throws IOException { String envblock = ProcessEnvironment.toEnvironmentBlock(environment); FileInputStream f0 = null; FileOutputStream f1 = null; FileOutputStream f2 = null; try { long[] stdHandles; if (redirects == null) { stdHandles = new long[] { -1L, -1L, -1L }; } else { stdHandles = new long[3]; if (redirects[0] == Redirect.PIPE) stdHandles[0] = -1L; else if (redirects[0] == Redirect.INHERIT) stdHandles[0] = fdAccess.getHandle(FileDescriptor.in); else { f0 = new FileInputStream(redirects[0].file()); stdHandles[0] = fdAccess.getHandle(f0.getFD()); } if (redirects[1] == Redirect.PIPE) stdHandles[1] = -1L; else if (redirects[1] == Redirect.INHERIT) stdHandles[1] = fdAccess.getHandle(FileDescriptor.out); else { f1 = newFileOutputStream(redirects[1].file(), redirects[1].append()); stdHandles[1] = fdAccess.getHandle(f1.getFD()); } if (redirects[2] == Redirect.PIPE) stdHandles[2] = -1L; else if (redirects[2] == Redirect.INHERIT) stdHandles[2] = fdAccess.getHandle(FileDescriptor.err); else { f2 = newFileOutputStream(redirects[2].file(), redirects[2].append()); stdHandles[2] = fdAccess.getHandle(f2.getFD()); } } return new ProcessImpl(cmdarray, envblock, dir, stdHandles, redirectErrorStream); } finally { // In theory, close() can throw IOException // (although it is rather unlikely to happen here) try { if (f0 != null) f0.close(); } finally { try { if (f1 != null) f1.close(); } finally { if (f2 != null) f2.close(); } } } } private long handle = 0; private OutputStream stdin_stream; private InputStream stdout_stream; private InputStream stderr_stream; private ProcessImpl(final String cmd[], final String envblock, final String path, final long[] stdHandles, final boolean redirectErrorStream) throws IOException { String prog = ""; try { // Win32 CreateProcess requires cmd[0] to be normalized cmd[0] = new File(cmd[0]).getPath(); StringBuilder cmdbuf = new StringBuilder(80); for (int i = 0; i < cmd.length; i++) { if (i > 0) { cmdbuf.append(' '); } String s = cmd[i]; if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) { if (s.charAt(0) != '"') { cmdbuf.append('"'); cmdbuf.append(s); if (s.endsWith("\\")) { cmdbuf.append("\\"); } cmdbuf.append('"'); } else if (s.endsWith("\"")) { /* The argument has already been quoted. */ cmdbuf.append(s); } else { /* Unmatched quote for the argument. */ throw new IllegalArgumentException(); } } else { cmdbuf.append(s); } } String cmdstr = cmdbuf.toString(); if (cmdstr.length() > 0 && cmdstr.charAt(0) == '"') { // quoted string must contain the prog name int end = cmdstr.indexOf('"', 1); if (end == -1 || cmdstr.indexOf('"', end+1) != -1) { // either unterminated string or has embedded string throw new IllegalArgumentException("Invalid string"); } prog = cmdstr.substring(1, end); } else { prog = getProgramPath(cmdstr); } SecurityManager security = System.getSecurityManager(); if (security != null) { try { security.checkExec(prog); } catch (SecurityException e) { // We support invocation of "foo" or "foo.exe" assuming "foo.exe" exists. // So, the security check should allow the same latitude. String lprog = prog.toLowerCase(); if (lprog.endsWith(".exe")) { // check without the .exe extension int len = lprog.length(); String mprog = prog.substring(0, len-4); security.checkExec(mprog); } else { throw e; } } } handle = create(cmdstr, envblock, path, stdHandles, redirectErrorStream); java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Void run() { if (stdHandles[0] == -1L) stdin_stream = ProcessBuilder.NullOutputStream.INSTANCE; else { FileDescriptor stdin_fd = new FileDescriptor(); fdAccess.setHandle(stdin_fd, stdHandles[0]); stdin_stream = new BufferedOutputStream( new FileOutputStream(stdin_fd)); } if (stdHandles[1] == -1L) stdout_stream = ProcessBuilder.NullInputStream.INSTANCE; else { FileDescriptor stdout_fd = new FileDescriptor(); fdAccess.setHandle(stdout_fd, stdHandles[1]); stdout_stream = new BufferedInputStream( new FileInputStream(stdout_fd)); } if (stdHandles[2] == -1L) stderr_stream = ProcessBuilder.NullInputStream.INSTANCE; else { FileDescriptor stderr_fd = new FileDescriptor(); fdAccess.setHandle(stderr_fd, stdHandles[2]); stderr_stream = new FileInputStream(stderr_fd); } return null; } }); } catch (IOException e) { throw new IOException ( "Cannot run program \"" + prog + "\"" + (path == null ? "" : " (in directory \"" + path + "\")") + ": " + e.getMessage(), e); } } /** * Use the same algorithm as Windows CreateProcess to determine the pathname to * the given command. Is complicated by the presence of whitespace in the pathname, * which makes it hard(er) to distinguish between command line parameters and * whitespace in the command path. We have to check for the existence of files * to disambiguate. Note, this means when the target does *not* exist, the path * remains ambiguous, and security checks in particular, will probably fail * because the whole command string (including args) will be passed to the security * check. * * C:\A B\C D\E F G * * We check in the following order (for files existing) * 1. C:\A.exe * 2. C:\A * 3. C:\A B\C.exe * 4. C:\A B\C * 5. C:\A B\C D\E.exe * 6. C:\A B\C D\E (and so on) * Any token ending in '\\' can't be a valid filename. So it isn't checked. * If no file is found, then the entire original string is returned. */ static String getProgramPath(final String command) { return java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public String run() { StringBuilder sb = new StringBuilder(); StringTokenizer tokenizer = new StringTokenizer(command, " \t", true); while (tokenizer.hasMoreTokens()) { String t = tokenizer.nextToken(); sb.append(t); if (t.equals(" ") || t.equals("\t") || t.endsWith("\\")) { continue; } t = sb.toString(); if (fileHasNoDot(t)) { // try appending .exe String t1 = t + ".exe"; if (exists(t1)) { return t1; } } if (exists(t)) { return t; } } return command; } }); } // final component of path has no '.' in it private static boolean fileHasNoDot(String name) { int n = name.lastIndexOf('\\') + 1; return name.length() > n ? name.indexOf('.', n) == -1 : false; } private static boolean exists(String name) { File f = new File (name); return f.exists(); } public OutputStream getOutputStream() { return stdin_stream; } public InputStream getInputStream() { return stdout_stream; } public InputStream getErrorStream() { return stderr_stream; } public void finalize() { closeHandle(handle); } private static final int STILL_ACTIVE = getStillActive(); private static native int getStillActive(); public int exitValue() { int exitCode = getExitCodeProcess(handle); if (exitCode == STILL_ACTIVE) throw new IllegalThreadStateException("process has not exited"); return exitCode; } private static native int getExitCodeProcess(long handle); public int waitFor() throws InterruptedException { waitForInterruptibly(handle); if (Thread.interrupted()) throw new InterruptedException(); return exitValue(); } private static native void waitForInterruptibly(long handle); public void destroy() { terminateProcess(handle); } private static native void terminateProcess(long handle); /** * Create a process using the win32 function CreateProcess. * * @param cmdstr the Windows commandline * @param envblock NUL-separated, double-NUL-terminated list of * environment strings in VAR=VALUE form * @param dir the working directory of the process, or null if * inheriting the current directory from the parent process * @param stdHandles array of windows HANDLEs. Indexes 0, 1, and * 2 correspond to standard input, standard output and * standard error, respectively. On input, a value of -1 * means to create a pipe to connect child and parent * processes. On output, a value which is not -1 is the * parent pipe handle corresponding to the pipe which has * been created. An element of this array is -1 on input * if and only if it is not -1 on output. * @param redirectErrorStream redirectErrorStream attribute * @return the native subprocess HANDLE returned by CreateProcess */ private static native long create(String cmdstr, String envblock, String dir, long[] stdHandles, boolean redirectErrorStream) throws IOException; /** * Opens a file for atomic append. The file is created if it doesn't * already exist. * * @param file the file to open or create * @return the native HANDLE */ private static native long openForAtomicAppend(String path) throws IOException; private static native boolean closeHandle(long handle); }