blob: 965a18672edf1303f4aa993b572ca7aba8f227d1 [file] [log] [blame]
// 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 java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
/**
* A subprocess factory that uses the Win32 API.
*/
public class WindowsSubprocessFactory implements Subprocess.Factory {
public static final WindowsSubprocessFactory INSTANCE = new WindowsSubprocessFactory();
private WindowsSubprocessFactory() {
// Singleton
}
@Override
public Subprocess create(SubprocessBuilder builder) throws IOException {
WindowsJniLoader.loadJni();
String commandLine = WindowsProcesses.quoteCommandLine(builder.getArgv());
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.nativeCreateProcess(
commandLine, env, builder.getWorkingDirectory().getPath(), stdoutPath, stderrPath);
String error = WindowsProcesses.nativeProcessGetLastError(nativeProcess);
if (!error.isEmpty()) {
WindowsProcesses.nativeDeleteProcess(nativeProcess);
throw new IOException(error);
}
return new WindowsSubprocess(nativeProcess, commandLine, stdoutPath != null,
stderrPath != null, builder.getTimeoutMillis());
}
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);
}
}