aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsProcesses.java
blob: 65b262088a1a9ee44456b67bdc75187baa48dcb8 (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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// 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.jni;

import java.util.List;

/** Process management on Windows. */
public class WindowsProcesses {
  public static final long INVALID = -1;

  private WindowsProcesses() {
    // Prevent construction
  }

  /**
   * Creates a process with the specified Windows command line.
   *
   * <p>Appropriately quoting arguments is the responsibility of the caller.
   *
   * @param argv0 the binary to run; must be unquoted; must be either an absolute, normalized
   *     Windows path with a drive letter (e.g. "c:\foo\bar app.exe") or a single file name (e.g.
   *     "foo app.exe")
   * @param argvRest the rest of the command line, i.e. argv[1:] (needs to be quoted Windows style)
   * @param env the environment of the new process. null means inherit that of the Bazel server
   * @param cwd the working directory of the new process. if null, the same as that of the current
   *     process
   * @param stdoutFile the file the stdout should be redirected to. if null, nativeReadStdout will
   *     work.
   * @param stderrFile the file the stdout should be redirected to. if null, nativeReadStderr will
   *     work.
   * @param redirectErrorStream whether we merge the process's standard error and standard output.
   * @return the opaque identifier of the created process
   */
  public static long createProcess(
      String argv0,
      String argvRest,
      byte[] env,
      String cwd,
      String stdoutFile,
      String stderrFile,
      boolean redirectErrorStream) {
    WindowsJniLoader.loadJni();
    return nativeCreateProcess(
        argv0, argvRest, env, cwd, stdoutFile, stderrFile, redirectErrorStream);
  }

  public static long createProcess(
      String argv0, String argvRest, byte[] env, String cwd, String stdoutFile, String stderrFile) {
    WindowsJniLoader.loadJni();
    return nativeCreateProcess(argv0, argvRest, env, cwd, stdoutFile, stderrFile, false);
  }

  private static native long nativeCreateProcess(
      String argv0,
      String argvRest,
      byte[] env,
      String cwd,
      String stdoutFile,
      String stderrFile,
      boolean redirectErrorStream);

  /**
   * Writes data from the given array to the stdin of the specified process.
   *
   * <p>Blocks until either some data was written or the process is terminated.
   *
   * @return the number of bytes written
   */
  public static int writeStdin(long process, byte[] bytes, int offset, int length) {
    WindowsJniLoader.loadJni();
    return nativeWriteStdin(process, bytes, offset, length);
  }

  private static native int nativeWriteStdin(long process, byte[] bytes, int offset, int length);

  /** Returns an opaque identifier of stdout stream for the process. */
  public static long getStdout(long process) {
    WindowsJniLoader.loadJni();
    return nativeGetStdout(process);
  }

  private static native long nativeGetStdout(long process);

  /** Returns an opaque identifier of stderr stream for the process. */
  public static long getStderr(long process) {
    WindowsJniLoader.loadJni();
    return nativeGetStderr(process);
  }

  private static native long nativeGetStderr(long process);

  /**
   * Reads data from the stream into the given array. {@code stream} should come from {@link
   * #nativeGetStdout(long)} or {@link #nativeGetStderr(long)}.
   *
   * <p>Blocks until either some data was read or the process is terminated.
   *
   * @return the number of bytes read, 0 on EOF, or -1 if there was an error.
   */
  public static int readStream(long stream, byte[] bytes, int offset, int length) {
    WindowsJniLoader.loadJni();
    return nativeReadStream(stream, bytes, offset, length);
  }

  private static native int nativeReadStream(long stream, byte[] bytes, int offset, int length);

  /**
   * Waits until the given process terminates. If timeout is non-negative, it indicates the number
   * of milliseconds before the call times out.
   *
   * <p>Return values:
   * <li>0: Process finished
   * <li>1: Timeout
   * <li>2: Something went wrong
   */
  public static int waitFor(long process, long timeout) {
    WindowsJniLoader.loadJni();
    return nativeWaitFor(process, timeout);
  }

  private static native int nativeWaitFor(long process, long timeout);

  /**
   * Returns the exit code of the process. Throws {@code IllegalStateException} if something goes
   * wrong.
   */
  public static int getExitCode(long process) {
    WindowsJniLoader.loadJni();
    return nativeGetExitCode(process);
  }

  private static native int nativeGetExitCode(long process);

  /** Returns the process ID of the given process or -1 if there was an error. */
  public static int getProcessPid(long process) {
    WindowsJniLoader.loadJni();
    return nativeGetProcessPid(process);
  }

  private static native int nativeGetProcessPid(long process);

  /** Terminates the given process. Returns true if the termination was successful. */
  public static boolean terminate(long process) {
    WindowsJniLoader.loadJni();
    return nativeTerminate(process);
  }

  private static native boolean nativeTerminate(long process);

  /**
   * Releases the native data structures associated with the process.
   *
   * <p>Calling any other method on the same process after this call will result in the JVM crashing
   * or worse.
   */
  public static void deleteProcess(long process) {
    WindowsJniLoader.loadJni();
    nativeDeleteProcess(process);
  }

  private static native void nativeDeleteProcess(long process);

  /**
   * Closes the stream
   *
   * @param stream should come from {@link #nativeGetStdout(long)} or {@link
   *     #nativeGetStderr(long)}.
   */
  public static void closeStream(long stream) {
    WindowsJniLoader.loadJni();
    nativeCloseStream(stream);
  }

  private static native void nativeCloseStream(long stream);

  /**
   * Returns a string representation of the last error caused by any call on the given process or
   * the empty string if the last operation was successful.
   *
   * <p>Does <b>NOT</b> terminate the process if it is still running.
   *
   * <p>After this call returns, subsequent calls will return the empty string if there was no
   * failed operation in between.
   */
  public static String processGetLastError(long process) {
    WindowsJniLoader.loadJni();
    return nativeProcessGetLastError(process);
  }

  private static native String nativeProcessGetLastError(long process);

  public static String streamGetLastError(long process) {
    WindowsJniLoader.loadJni();
    return nativeStreamGetLastError(process);
  }

  private static native String nativeStreamGetLastError(long process);

  /** returns the PID of the current process. */
  public static int getpid() {
    WindowsJniLoader.loadJni();
    return nativeGetpid();
  }

  private static native int nativeGetpid();

  // TODO(laszlocsomor): Audit this method and fix bugs. This method implements Bash quoting
  // semantics but Windows quote semantics are different.
  // More info: http://daviddeley.com/autohotkey/parameters/parameters.htm
  public static String quoteCommandLine(List<String> argv) {
    StringBuilder result = new StringBuilder();
    for (int iArg = 0; iArg < argv.size(); iArg++) {
      if (iArg != 0) {
        result.append(" ");
      }
      String arg = argv.get(iArg);
      if (arg.isEmpty()) {
        result.append("\"\"");
        continue;
      }
      boolean hasSpace = arg.contains(" ");
      if (!arg.contains("\"") && !arg.contains("\\") && !hasSpace) {
        // fast path. Just append the input string.
        result.append(arg);
      } else {
        // We need to quote things if the argument contains a space.
        if (hasSpace) {
          result.append("\"");
        }

        for (int iChar = 0; iChar < arg.length(); iChar++) {
          boolean lastChar = iChar == arg.length() - 1;
          switch (arg.charAt(iChar)) {
            case '"':
              // Escape double quotes
              result.append("\\\"");
              break;
            case '\\':
              // Backslashes at the end of the string are quoted if we add quotes
              if (lastChar) {
                result.append(hasSpace ? "\\\\" : "\\");
              } else {
                // Backslashes everywhere else are quoted if they are followed by a
                // quote or a backslash
                result.append(
                    arg.charAt(iChar + 1) == '"' || arg.charAt(iChar + 1) == '\\' ? "\\\\" : "\\");
              }
              break;
            default:
              result.append(arg.charAt(iChar));
          }
        }
        // Add ending quotes if we added a quote at the beginning.
        if (hasSpace) {
          result.append("\"");
        }
      }
    }

    return result.toString();
  }
}