aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java
blob: c9a39943fa9360bcc0ffd18c41f608a4c2de32fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// 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<>(String.CASE_INSENSITIVE_ORDER);
    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);
  }
}