| // Copyright 2016 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.devtools.build.lib.windows; |
| |
| import com.google.common.base.Charsets; |
| import com.google.devtools.build.lib.shell.Subprocess; |
| import com.google.devtools.build.lib.shell.SubprocessBuilder; |
| import com.google.devtools.build.lib.shell.SubprocessBuilder.StreamAction; |
| import com.google.devtools.build.lib.shell.SubprocessFactory; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.windows.jni.WindowsProcesses; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| /** |
| * A subprocess factory that uses the Win32 API. |
| */ |
| public class WindowsSubprocessFactory implements SubprocessFactory { |
| public static final WindowsSubprocessFactory INSTANCE = new WindowsSubprocessFactory(); |
| |
| private WindowsSubprocessFactory() { |
| // Singleton |
| } |
| |
| @Override |
| public Subprocess create(SubprocessBuilder builder) throws IOException { |
| List<String> argv = builder.getArgv(); |
| |
| // DO NOT quote argv0, createProcess will do it for us. |
| String argv0 = processArgv0(argv.get(0)); |
| String argvRest = |
| argv.size() > 1 ? WindowsProcesses.quoteCommandLine(argv.subList(1, argv.size())) : ""; |
| byte[] env = builder.getEnv() == null ? null : convertEnvToNative(builder.getEnv()); |
| |
| String stdoutPath = getRedirectPath(builder.getStdout(), builder.getStdoutFile()); |
| String stderrPath = getRedirectPath(builder.getStderr(), builder.getStderrFile()); |
| |
| long nativeProcess = |
| WindowsProcesses.createProcess( |
| argv0, |
| argvRest, |
| env, |
| builder.getWorkingDirectory().getPath(), |
| stdoutPath, |
| stderrPath, |
| builder.redirectErrorStream()); |
| String error = WindowsProcesses.processGetLastError(nativeProcess); |
| if (!error.isEmpty()) { |
| WindowsProcesses.deleteProcess(nativeProcess); |
| throw new IOException(error); |
| } |
| |
| return new WindowsSubprocess( |
| nativeProcess, |
| argv0 + " " + argvRest, |
| stdoutPath != null, |
| stderrPath != null, |
| builder.getTimeoutMillis()); |
| } |
| |
| public String processArgv0(String argv0) { |
| // Normalize the path and make it Windows-style. |
| // If argv0 is at least MAX_PATH (260 chars) long, createNativeProcess calls GetShortPathNameW |
| // to obtain a 8dot3 name for it (thereby support long paths in CreateProcessA), but then argv0 |
| // must be prefixed with "\\?\" for GetShortPathNameW to work, so it also must be an absolute, |
| // normalized, Windows-style path. |
| // Therefore if it's absolute, then normalize it also. |
| // If it's not absolute, then it cannot be longer than MAX_PATH, since MAX_PATH also limits the |
| // length of file names. |
| PathFragment argv0fragment = PathFragment.create(argv0); |
| return (argv0fragment.isAbsolute()) ? argv0fragment.getPathString().replace('/', '\\') : argv0; |
| } |
| |
| private String getRedirectPath(StreamAction action, File file) { |
| switch (action) { |
| case DISCARD: |
| return "NUL"; // That's /dev/null on Windows |
| |
| case REDIRECT: |
| return file.getPath(); |
| |
| case STREAM: |
| return null; |
| |
| default: |
| throw new IllegalStateException(); |
| } |
| } |
| |
| private String getSystemRoot(Map<String, String> env) { |
| // Windows environment variables are case-insensitive, so we can't just say |
| // System.getenv().get("SystemRoot") |
| for (String key : env.keySet()) { |
| if (key.toUpperCase().equals("SYSTEMROOT")) { |
| return env.get(key); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Converts an environment map to the format expected in lpEnvironment by CreateProcess(). |
| */ |
| private byte[] convertEnvToNative(Map<String, String> env) throws IOException { |
| Map<String, String> realEnv = new TreeMap<>(); |
| realEnv.putAll(env == null ? System.getenv() : env); |
| if (getSystemRoot(realEnv) == null) { |
| // Some versions of MSVCRT.DLL require SystemRoot to be set. It's quite a common library to |
| // link in, so we add this environment variable regardless of whether the caller requested |
| // it or not. |
| String systemRoot = getSystemRoot(System.getenv()); |
| if (systemRoot != null) { |
| realEnv.put("SystemRoot", systemRoot); |
| } |
| } |
| |
| if (realEnv.isEmpty()) { |
| // Special case: CreateProcess() always expects the environment block to be terminated |
| // with two zeros. |
| return new byte[] { 0, 0, }; |
| } |
| |
| StringBuilder result = new StringBuilder(); |
| for (Map.Entry<String, String> entry : realEnv.entrySet()) { |
| if (entry.getKey().contains("=")) { |
| // lpEnvironment requires no '=' in environment variable name, but on Windows, |
| // System.getenv() returns environment variables like '=C:' or '=ExitCode', so it can't |
| // be an error, we have ignore them here. |
| continue; |
| } |
| result.append(entry.getKey() + "=" + entry.getValue() + "\0"); |
| } |
| |
| result.append("\0"); |
| return result.toString().getBytes(Charsets.UTF_8); |
| } |
| } |