1 /*
   2  * Copyright (c) 1995, 2012, 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.IOException;
  29 import java.io.File;
  30 import java.io.InputStream;
  31 import java.io.OutputStream;
  32 import java.io.FileInputStream;
  33 import java.io.FileOutputStream;
  34 import java.io.FileDescriptor;
  35 import java.io.BufferedInputStream;
  36 import java.io.BufferedOutputStream;
  37 import java.lang.ProcessBuilder.Redirect;
  38 import java.security.AccessController;
  39 import java.security.PrivilegedAction;
  40 import java.util.concurrent.TimeUnit;
  41 
  42 /* This class is for the exclusive use of ProcessBuilder.start() to
  43  * create new processes.
  44  *
  45  * @author Martin Buchholz
  46  * @since   1.5
  47  */
  48 
  49 final class ProcessImpl extends Process {
  50     private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
  51         = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
  52 
  53     /**
  54      * Open a file for writing. If {@code append} is {@code true} then the file
  55      * is opened for atomic append directly and a FileOutputStream constructed
  56      * with the resulting handle. This is because a FileOutputStream created
  57      * to append to a file does not open the file in a manner that guarantees
  58      * that writes by the child process will be atomic.
  59      */
  60     private static FileOutputStream newFileOutputStream(File f, boolean append)
  61         throws IOException
  62     {
  63         if (append) {
  64             String path = f.getPath();
  65             SecurityManager sm = System.getSecurityManager();
  66             if (sm != null)
  67                 sm.checkWrite(path);
  68             long handle = openForAtomicAppend(path);
  69             final FileDescriptor fd = new FileDescriptor();
  70             fdAccess.setHandle(fd, handle);
  71             return AccessController.doPrivileged(
  72                 new PrivilegedAction<FileOutputStream>() {
  73                     public FileOutputStream run() {
  74                         return new FileOutputStream(fd);
  75                     }
  76                 }
  77             );
  78         } else {
  79             return new FileOutputStream(f);
  80         }
  81     }
  82 
  83     // System-dependent portion of ProcessBuilder.start()
  84     static Process start(String cmdarray[],
  85                          java.util.Map<String,String> environment,
  86                          String dir,
  87                          ProcessBuilder.Redirect[] redirects,
  88                          boolean redirectErrorStream)
  89         throws IOException
  90     {
  91         String envblock = ProcessEnvironment.toEnvironmentBlock(environment);
  92 
  93         FileInputStream  f0 = null;
  94         FileOutputStream f1 = null;
  95         FileOutputStream f2 = null;
  96 
  97         try {
  98             long[] stdHandles;
  99             if (redirects == null) {
 100                 stdHandles = new long[] { -1L, -1L, -1L };
 101             } else {
 102                 stdHandles = new long[3];
 103 
 104                 if (redirects[0] == Redirect.PIPE)
 105                     stdHandles[0] = -1L;
 106                 else if (redirects[0] == Redirect.INHERIT)
 107                     stdHandles[0] = fdAccess.getHandle(FileDescriptor.in);
 108                 else {
 109                     f0 = new FileInputStream(redirects[0].file());
 110                     stdHandles[0] = fdAccess.getHandle(f0.getFD());
 111                 }
 112 
 113                 if (redirects[1] == Redirect.PIPE)
 114                     stdHandles[1] = -1L;
 115                 else if (redirects[1] == Redirect.INHERIT)
 116                     stdHandles[1] = fdAccess.getHandle(FileDescriptor.out);
 117                 else {
 118                     f1 = newFileOutputStream(redirects[1].file(),
 119                                              redirects[1].append());
 120                     stdHandles[1] = fdAccess.getHandle(f1.getFD());
 121                 }
 122 
 123                 if (redirects[2] == Redirect.PIPE)
 124                     stdHandles[2] = -1L;
 125                 else if (redirects[2] == Redirect.INHERIT)
 126                     stdHandles[2] = fdAccess.getHandle(FileDescriptor.err);
 127                 else {
 128                     f2 = newFileOutputStream(redirects[2].file(),
 129                                              redirects[2].append());
 130                     stdHandles[2] = fdAccess.getHandle(f2.getFD());
 131                 }
 132             }
 133 
 134             return new ProcessImpl(cmdarray, envblock, dir,
 135                                    stdHandles, redirectErrorStream);
 136         } finally {
 137             // In theory, close() can throw IOException
 138             // (although it is rather unlikely to happen here)
 139             try { if (f0 != null) f0.close(); }
 140             finally {
 141                 try { if (f1 != null) f1.close(); }
 142                 finally { if (f2 != null) f2.close(); }
 143             }
 144         }
 145 
 146     }
 147 
 148     // We guarantee the only command file execution for implicit [cmd.exe] run.
 149     //    http://technet.microsoft.com/en-us/library/bb490954.aspx
 150     private static final char CMD_BAT_ESCAPE[] = {' ', '\t', '<', '>', '&', '|', '^'};
 151     private static final char WIN32_EXECUTABLE_ESCAPE[] = {' ', '\t', '<', '>'};
 152 
 153     private static boolean isQuoted(boolean noQuotesInside, String arg,
 154             String errorMessage) {
 155         int lastPos = arg.length() - 1;
 156         if (lastPos >=1 && arg.charAt(0) == '"' && arg.charAt(lastPos) == '"') {
 157             // The argument has already been quoted.
 158             if (noQuotesInside) {
 159                 if (arg.indexOf('"', 1) != lastPos) {
 160                     // There is ["] inside.
 161                     throw new IllegalArgumentException(errorMessage);
 162                 }
 163             }
 164             return true;
 165         }
 166         if (noQuotesInside) {
 167             if (arg.indexOf('"') >= 0) {
 168                 // There is ["] inside.
 169                 throw new IllegalArgumentException(errorMessage);
 170             }
 171         }
 172         return false;
 173     }
 174 
 175     private static boolean needsEscaping(boolean isCmdFile, String arg) {
 176         // Switch off MS heuristic for internal ["].
 177         // Please, use the explicit [cmd.exe] call
 178         // if you need the internal ["].
 179         //    Example: "cmd.exe", "/C", "Extended_MS_Syntax"
 180 
 181         // For [.exe] or [.com] file the unpaired/internal ["]
 182         // in the argument is not a problem.
 183         boolean argIsQuoted = isQuoted(isCmdFile, arg,
 184             "Argument has embedded quote, use the explicit CMD.EXE call.");
 185 
 186         if (!argIsQuoted) {
 187             char testEscape[] = isCmdFile
 188                     ? CMD_BAT_ESCAPE
 189                     : WIN32_EXECUTABLE_ESCAPE;
 190             for (int i = 0; i < testEscape.length; ++i) {
 191                 if (arg.indexOf(testEscape[i]) >= 0) {
 192                     return true;
 193                 }
 194             }
 195         }
 196         return false;
 197     }
 198 
 199     private static String getExecutablePath(String path)
 200         throws IOException
 201     {
 202         boolean pathIsQuoted = isQuoted(true, path,
 203                 "Executable name has embedded quote, split the arguments");
 204 
 205         // Win32 CreateProcess requires path to be normalized
 206         File fileToRun = new File(pathIsQuoted
 207             ? path.substring(1, path.length() - 1)
 208             : path);
 209 
 210         // From the [CreateProcess] function documentation:
 211         //
 212         // "If the file name does not contain an extension, .exe is appended.
 213         // Therefore, if the file name extension is .com, this parameter
 214         // must include the .com extension. If the file name ends in
 215         // a period (.) with no extension, or if the file name contains a path,
 216         // .exe is not appended."
 217         //
 218         // "If the file name !does not contain a directory path!,
 219         // the system searches for the executable file in the following
 220         // sequence:..."
 221         //
 222         // In practice ANY non-existent path is extended by [.exe] extension
 223         // in the [CreateProcess] funcion with the only exception:
 224         // the path ends by (.)
 225 
 226         return fileToRun.getPath();
 227     }
 228 
 229 
 230     private long handle = 0;
 231     private OutputStream stdin_stream;
 232     private InputStream stdout_stream;
 233     private InputStream stderr_stream;
 234 
 235     private ProcessImpl(final String cmd[],
 236                         final String envblock,
 237                         final String path,
 238                         final long[] stdHandles,
 239                         final boolean redirectErrorStream)
 240         throws IOException
 241     {
 242         // The [executablePath] is not quoted for any case.
 243         String executablePath = getExecutablePath(cmd[0]);
 244 
 245         // We need to extend the argument verification procedure
 246         // to guarantee the only command file execution for implicit [cmd.exe]
 247         // run.
 248         String upPath = executablePath.toUpperCase();
 249         boolean isCmdFile = (upPath.endsWith(".CMD") || upPath.endsWith(".BAT"));
 250 
 251         StringBuilder cmdbuf = new StringBuilder(80);
 252 
 253         // Quotation protects from interpretation of the [path] argument as
 254         // start of longer path with spaces. Quotation has no influence to
 255         // [.exe] extension heuristic.
 256         cmdbuf.append('"');
 257         cmdbuf.append(executablePath);
 258         cmdbuf.append('"');
 259 
 260         for (int i = 1; i < cmd.length; i++) {
 261             cmdbuf.append(' ');
 262             String s = cmd[i];
 263             if (needsEscaping(isCmdFile, s)) {
 264                 cmdbuf.append('"');
 265                 cmdbuf.append(s);
 266 
 267                 // The code protects the [java.exe] and console command line
 268                 // parser, that interprets the [\"] combination as an escape
 269                 // sequence for the ["] char.
 270                 //     http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
 271                 //
 272                 // If the argument is an FS path, doubling of the tail [\]
 273                 // char is not a problem for non-console applications.
 274                 //
 275                 // The [\"] sequence is not an escape sequence for the [cmd.exe]
 276                 // command line parser. The case of the [""] tail escape
 277                 // sequence could not be realized due to the argument validation
 278                 // procedure.
 279                 if (!isCmdFile && s.endsWith("\\")) {
 280                     cmdbuf.append('\\');
 281                 }
 282                 cmdbuf.append('"');
 283             } else {
 284                 cmdbuf.append(s);
 285             }
 286         }
 287         String cmdstr = cmdbuf.toString();
 288 
 289         handle = create(cmdstr, envblock, path,
 290                         stdHandles, redirectErrorStream);
 291 
 292         java.security.AccessController.doPrivileged(
 293         new java.security.PrivilegedAction<Void>() {
 294         public Void run() {
 295             if (stdHandles[0] == -1L)
 296                 stdin_stream = ProcessBuilder.NullOutputStream.INSTANCE;
 297             else {
 298                 FileDescriptor stdin_fd = new FileDescriptor();
 299                 fdAccess.setHandle(stdin_fd, stdHandles[0]);
 300                 stdin_stream = new BufferedOutputStream(
 301                     new FileOutputStream(stdin_fd));
 302             }
 303 
 304             if (stdHandles[1] == -1L)
 305                 stdout_stream = ProcessBuilder.NullInputStream.INSTANCE;
 306             else {
 307                 FileDescriptor stdout_fd = new FileDescriptor();
 308                 fdAccess.setHandle(stdout_fd, stdHandles[1]);
 309                 stdout_stream = new BufferedInputStream(
 310                     new FileInputStream(stdout_fd));
 311             }
 312 
 313             if (stdHandles[2] == -1L)
 314                 stderr_stream = ProcessBuilder.NullInputStream.INSTANCE;
 315             else {
 316                 FileDescriptor stderr_fd = new FileDescriptor();
 317                 fdAccess.setHandle(stderr_fd, stdHandles[2]);
 318                 stderr_stream = new FileInputStream(stderr_fd);
 319             }
 320 
 321             return null; }});
 322     }
 323 
 324     public OutputStream getOutputStream() {
 325         return stdin_stream;
 326     }
 327 
 328     public InputStream getInputStream() {
 329         return stdout_stream;
 330     }
 331 
 332     public InputStream getErrorStream() {
 333         return stderr_stream;
 334     }
 335 
 336     protected void finalize() {
 337         closeHandle(handle);
 338     }
 339 
 340     private static final int STILL_ACTIVE = getStillActive();
 341     private static native int getStillActive();
 342 
 343     public int exitValue() {
 344         int exitCode = getExitCodeProcess(handle);
 345         if (exitCode == STILL_ACTIVE)
 346             throw new IllegalThreadStateException("process has not exited");
 347         return exitCode;
 348     }
 349     private static native int getExitCodeProcess(long handle);
 350 
 351     public int waitFor() throws InterruptedException {
 352         waitForInterruptibly(handle);
 353         if (Thread.interrupted())
 354             throw new InterruptedException();
 355         return exitValue();
 356     }
 357 
 358     private static native void waitForInterruptibly(long handle);
 359 
 360     @Override
 361     public boolean waitFor(long timeout, TimeUnit unit)
 362         throws InterruptedException
 363     {
 364         if (getExitCodeProcess(handle) != STILL_ACTIVE) return true;
 365         if (timeout <= 0) return false;
 366 
 367         long msTimeout = unit.toMillis(timeout);
 368 
 369         waitForTimeoutInterruptibly(handle, msTimeout);
 370         if (Thread.interrupted())
 371             throw new InterruptedException();
 372         return (getExitCodeProcess(handle) != STILL_ACTIVE);
 373     }
 374 
 375     private static native void waitForTimeoutInterruptibly(
 376         long handle, long timeout);
 377 
 378     public void destroy() { terminateProcess(handle); }
 379 
 380     @Override
 381     public Process destroyForcibly() {
 382         destroy();
 383         return this;
 384     }
 385 
 386     private static native void terminateProcess(long handle);
 387 
 388     @Override
 389     public boolean isAlive() {
 390         return isProcessAlive(handle);
 391     }
 392 
 393     private static native boolean isProcessAlive(long handle);
 394 
 395     /**
 396      * Create a process using the win32 function CreateProcess.
 397      *
 398      * @param cmdstr the Windows commandline
 399      * @param envblock NUL-separated, double-NUL-terminated list of
 400      *        environment strings in VAR=VALUE form
 401      * @param dir the working directory of the process, or null if
 402      *        inheriting the current directory from the parent process
 403      * @param stdHandles array of windows HANDLEs.  Indexes 0, 1, and
 404      *        2 correspond to standard input, standard output and
 405      *        standard error, respectively.  On input, a value of -1
 406      *        means to create a pipe to connect child and parent
 407      *        processes.  On output, a value which is not -1 is the
 408      *        parent pipe handle corresponding to the pipe which has
 409      *        been created.  An element of this array is -1 on input
 410      *        if and only if it is <em>not</em> -1 on output.
 411      * @param redirectErrorStream redirectErrorStream attribute
 412      * @return the native subprocess HANDLE returned by CreateProcess
 413      */
 414     private static native long create(String cmdstr,
 415                                       String envblock,
 416                                       String dir,
 417                                       long[] stdHandles,
 418                                       boolean redirectErrorStream)
 419         throws IOException;
 420 
 421     /**
 422      * Opens a file for atomic append. The file is created if it doesn't
 423      * already exist.
 424      *
 425      * @param file the file to open or create
 426      * @return the native HANDLE
 427      */
 428     private static native long openForAtomicAppend(String path)
 429         throws IOException;
 430 
 431     private static native boolean closeHandle(long handle);
 432 }