blob: 42095e19c2227a7e879c2dae522155ebf506f23d [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Laszlo Csomor26f858c2017-02-08 17:50:10 +000015#include <fcntl.h>
Laszlo Csomord86ae8c2016-12-05 13:54:59 +000016#include <stdarg.h> // va_start, va_end, va_list
Laszlo Csomorf926f3e2016-11-09 09:05:48 +000017
18#ifndef COMPILER_MSVC
Laszlo Csomor6e2ccb72017-03-01 16:53:35 +000019#include <errno.h>
20#include <limits.h>
Mostyn Bramley-Moore44e8d102015-11-10 11:27:39 +000021#include <sys/cygwin.h>
Laszlo Csomord0a12692016-11-28 13:35:23 +000022#include <sys/ioctl.h>
Googler11565c12015-07-23 12:03:53 +000023#include <sys/socket.h>
Laszlo Csomor8a48f612016-11-17 10:18:34 +000024#include <sys/stat.h>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025#include <sys/statfs.h>
26#include <unistd.h>
László Csomorfa27b502017-03-23 15:09:34 +000027#endif // COMPILER_MSVC
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028
László Csomorfa27b502017-03-23 15:09:34 +000029#include <windows.h>
laszlocsomor704fb662017-03-29 09:36:30 +000030#include <lmcons.h> // UNLEN
31#include <versionhelpers.h> // IsWindows8OrGreater
Dmitry Lomov78c0cc72015-08-11 16:44:21 +000032
Laszlo Csomor26f858c2017-02-08 17:50:10 +000033#ifdef COMPILER_MSVC
László Csomorfa27b502017-03-23 15:09:34 +000034#include <io.h> // _open
35#include <knownfolders.h> // FOLDERID_Profile
36#include <objbase.h> // CoTaskMemFree
37#include <shlobj.h> // SHGetKnownFolderPath
38#endif
Laszlo Csomor26f858c2017-02-08 17:50:10 +000039
László Csomore64ed192017-02-23 15:43:54 +000040#include <algorithm>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041#include <cstdio>
Laszlo Csomor31500b82016-12-15 10:15:36 +000042#include <cstdlib>
Laszlo Csomorf9f41c72017-01-09 12:06:45 +000043#include <sstream>
Lukacs Berki68cb41a2016-07-06 11:43:37 +000044#include <thread> // NOLINT (to slience Google-internal linter)
Laszlo Csomor31500b82016-12-15 10:15:36 +000045#include <type_traits> // static_assert
Laszlo Csomore152f722017-02-03 10:05:36 +000046#include <vector>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000048#include "src/main/cpp/blaze_util.h"
Thiago Farina7f9357f2015-04-23 13:57:43 +000049#include "src/main/cpp/blaze_util_platform.h"
Laszlo Csomor32086b22016-11-24 15:23:55 +000050#include "src/main/cpp/global_variables.h"
51#include "src/main/cpp/startup_options.h"
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000052#include "src/main/cpp/util/errors.h"
Thiago Farina7f9357f2015-04-23 13:57:43 +000053#include "src/main/cpp/util/exit_code.h"
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000054#include "src/main/cpp/util/file.h"
Laszlo Csomor49970e02016-11-28 08:55:47 +000055#include "src/main/cpp/util/file_platform.h"
Laszlo Csomor6bf95762016-11-16 13:29:22 +000056#include "src/main/cpp/util/md5.h"
Laszlo Csomord0a12692016-11-28 13:35:23 +000057#include "src/main/cpp/util/numbers.h"
László Csomor72879212017-02-02 15:55:18 +000058#include "src/main/cpp/util/strings.h"
Laszlo Csomorf0702342017-06-28 16:05:23 +020059#include "src/main/native/windows/file.h"
60#include "src/main/native/windows/util.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010061
62namespace blaze {
63
Laszlo Csomor31500b82016-12-15 10:15:36 +000064// Ensure we can safely cast (const) wchar_t* to LP(C)WSTR.
65// This is true with MSVC but usually not with GCC.
66static_assert(sizeof(wchar_t) == sizeof(WCHAR),
67 "wchar_t and WCHAR should be the same size");
68
69// When using widechar Win32 API functions the maximum path length is 32K.
70// Add 4 characters for potential UNC prefix and a couple more for safety.
71static const size_t kWindowsPathBufferSize = 0x8010;
72
Laszlo Csomorf0702342017-06-28 16:05:23 +020073using bazel::windows::AutoHandle;
74using bazel::windows::CreateJunction;
75
Laszlo Csomorf00cee82016-12-15 10:58:03 +000076// TODO(bazel-team): get rid of die/pdie, handle errors on the caller side.
77// die/pdie are exit points in the code and they make it difficult to follow the
78// control flow, plus it's not clear whether they call destructors on local
79// variables in the call stack.
Thiago Farina241f46c2015-04-13 14:33:30 +000080using blaze_util::die;
81using blaze_util::pdie;
Laszlo Csomorf00cee82016-12-15 10:58:03 +000082
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010083using std::string;
Laszlo Csomorf00cee82016-12-15 10:58:03 +000084using std::unique_ptr;
Laszlo Csomor31500b82016-12-15 10:15:36 +000085using std::wstring;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010086
Laszlo Csomor32086b22016-11-24 15:23:55 +000087SignalHandler SignalHandler::INSTANCE;
88
Laszlo Csomora85f52d2016-11-08 13:43:23 +000089class WindowsClock {
90 public:
91 uint64_t GetMilliseconds() const;
92 uint64_t GetProcessMilliseconds() const;
93
94 static const WindowsClock INSTANCE;
95
96 private:
97 // Clock frequency per seconds.
98 // It's safe to cache this because (from QueryPerformanceFrequency on MSDN):
99 // "The frequency of the performance counter is fixed at system boot and is
100 // consistent across all processors. Therefore, the frequency need only be
101 // queried upon application initialization, and the result can be cached."
102 const LARGE_INTEGER kFrequency;
103
104 // Time (in milliseconds) at process start.
105 const LARGE_INTEGER kStart;
106
107 WindowsClock();
108
109 static LARGE_INTEGER GetFrequency();
110 static LARGE_INTEGER GetMillisecondsAsLargeInt(const LARGE_INTEGER& freq);
111};
112
Laszlo Csomor32086b22016-11-24 15:23:55 +0000113#ifdef COMPILER_MSVC
114
Dmitry Lomov69ad6952017-03-21 16:20:37 +0000115BOOL WINAPI ConsoleCtrlHandler(_In_ DWORD ctrlType) {
116 static volatile int sigint_count = 0;
117 switch (ctrlType) {
118 case CTRL_C_EVENT:
119 case CTRL_BREAK_EVENT:
120 if (++sigint_count >= 3) {
121 SigPrintf(
122 "\n%s caught third Ctrl+C handler signal; killed.\n\n",
123 SignalHandler::Get().GetGlobals()->options->product_name.c_str());
124 if (SignalHandler::Get().GetGlobals()->server_pid != -1) {
mschallerfd37b512017-07-11 18:21:36 +0200125 KillServerProcess(
126 SignalHandler::Get().GetGlobals()->server_pid,
127 SignalHandler::Get().GetGlobals()->options->output_base);
Dmitry Lomov69ad6952017-03-21 16:20:37 +0000128 }
129 _exit(1);
130 }
131 SigPrintf(
132 "\n%s Ctrl+C handler; shutting down.\n\n",
133 SignalHandler::Get().GetGlobals()->options->product_name.c_str());
134 SignalHandler::Get().CancelServer();
135 return TRUE;
136
137 case CTRL_CLOSE_EVENT:
138 SignalHandler::Get().CancelServer();
139 return TRUE;
140 }
141 return false;
142}
143
Laszlo Csomor32086b22016-11-24 15:23:55 +0000144void SignalHandler::Install(GlobalVariables* globals,
145 SignalHandler::Callback cancel_server) {
Dmitry Lomov69ad6952017-03-21 16:20:37 +0000146 _globals = globals;
147 _cancel_server = cancel_server;
148 ::SetConsoleCtrlHandler(&ConsoleCtrlHandler, TRUE);
Laszlo Csomor32086b22016-11-24 15:23:55 +0000149}
150
151ATTRIBUTE_NORETURN void SignalHandler::PropagateSignalOrExit(int exit_code) {
Dmitry Lomov69ad6952017-03-21 16:20:37 +0000152 // We do not handle signals on Windows; always exit with exit_code.
153 exit(exit_code);
Laszlo Csomor32086b22016-11-24 15:23:55 +0000154}
155
156#else // not COMPILER_MSVC
157
158// The number of the last received signal that should cause the client
159// to shutdown. This is saved so that the client's WTERMSIG can be set
160// correctly. (Currently only SIGPIPE uses this mechanism.)
161static volatile sig_atomic_t signal_handler_received_signal = 0;
162
Laszlo Csomor32086b22016-11-24 15:23:55 +0000163// Signal handler.
164static void handler(int signum) {
165 int saved_errno = errno;
166
167 static volatile sig_atomic_t sigint_count = 0;
168
169 switch (signum) {
170 case SIGINT:
171 if (++sigint_count >= 3) {
Laszlo Csomor3b89d2d2016-11-28 14:04:27 +0000172 SigPrintf(
Laszlo Csomor32086b22016-11-24 15:23:55 +0000173 "\n%s caught third interrupt signal; killed.\n\n",
174 SignalHandler::Get().GetGlobals()->options->product_name.c_str());
175 if (SignalHandler::Get().GetGlobals()->server_pid != -1) {
mschallerfd37b512017-07-11 18:21:36 +0200176 KillServerProcess(
177 SignalHandler::Get().GetGlobals()->server_pid,
178 SignalHandler::Get().GetGlobals()->options->output_base);
Laszlo Csomor32086b22016-11-24 15:23:55 +0000179 }
Laszlo Csomore86b04c2016-12-21 17:36:00 +0000180 _exit(1);
Laszlo Csomor32086b22016-11-24 15:23:55 +0000181 }
Laszlo Csomor3b89d2d2016-11-28 14:04:27 +0000182 SigPrintf(
Laszlo Csomor32086b22016-11-24 15:23:55 +0000183 "\n%s caught interrupt signal; shutting down.\n\n",
184 SignalHandler::Get().GetGlobals()->options->product_name.c_str());
185 SignalHandler::Get().CancelServer();
186 break;
187 case SIGTERM:
Laszlo Csomor3b89d2d2016-11-28 14:04:27 +0000188 SigPrintf(
Laszlo Csomor32086b22016-11-24 15:23:55 +0000189 "\n%s caught terminate signal; shutting down.\n\n",
190 SignalHandler::Get().GetGlobals()->options->product_name.c_str());
191 SignalHandler::Get().CancelServer();
192 break;
193 case SIGPIPE:
194 signal_handler_received_signal = SIGPIPE;
195 break;
196 case SIGQUIT:
Laszlo Csomor3b89d2d2016-11-28 14:04:27 +0000197 SigPrintf("\nSending SIGQUIT to JVM process %d (see %s).\n\n",
Laszlo Csomor32086b22016-11-24 15:23:55 +0000198 SignalHandler::Get().GetGlobals()->server_pid,
199 SignalHandler::Get().GetGlobals()->jvm_log_file.c_str());
200 kill(SignalHandler::Get().GetGlobals()->server_pid, SIGQUIT);
201 break;
202 }
203
204 errno = saved_errno;
205}
206
207void SignalHandler::Install(GlobalVariables* globals,
208 SignalHandler::Callback cancel_server) {
209 _globals = globals;
210 _cancel_server = cancel_server;
211
212 // Unblock all signals.
213 sigset_t sigset;
214 sigemptyset(&sigset);
215 sigprocmask(SIG_SETMASK, &sigset, NULL);
216
217 signal(SIGINT, handler);
218 signal(SIGTERM, handler);
219 signal(SIGPIPE, handler);
220 signal(SIGQUIT, handler);
221}
222
223ATTRIBUTE_NORETURN void SignalHandler::PropagateSignalOrExit(int exit_code) {
224 if (signal_handler_received_signal) {
225 // Kill ourselves with the same signal, so that callers see the
226 // right WTERMSIG value.
227 signal(signal_handler_received_signal, SIG_DFL);
228 raise(signal_handler_received_signal);
229 exit(1); // (in case raise didn't kill us for some reason)
230 } else {
231 exit(exit_code);
232 }
233}
234
Laszlo Csomor3b89d2d2016-11-28 14:04:27 +0000235#endif // COMPILER_MSVC
236
Laszlo Csomor32086b22016-11-24 15:23:55 +0000237// A signal-safe version of fprintf(stderr, ...).
238//
239// WARNING: any output from the blaze client may be interleaved
240// with output from the blaze server. In --curses mode,
241// the Blaze server often erases the previous line of output.
242// So, be sure to end each such message with TWO newlines,
243// otherwise it may be erased by the next message from the
244// Blaze server.
245// Also, it's a good idea to start each message with a newline,
246// in case the Blaze server has written a partial line.
Laszlo Csomor3b89d2d2016-11-28 14:04:27 +0000247void SigPrintf(const char *format, ...) {
248#ifdef COMPILER_MSVC
Dmitry Lomov5387c732017-03-22 09:33:28 +0000249 int stderr_fileno = _fileno(stderr);
Laszlo Csomor3b89d2d2016-11-28 14:04:27 +0000250#else // not COMPILER_MSVC
Dmitry Lomov5387c732017-03-22 09:33:28 +0000251 int stderr_fileno = STDERR_FILENO;
252#endif
Laszlo Csomor32086b22016-11-24 15:23:55 +0000253 char buf[1024];
254 va_list ap;
255 va_start(ap, format);
256 int r = vsnprintf(buf, sizeof buf, format, ap);
257 va_end(ap);
Dmitry Lomov5387c732017-03-22 09:33:28 +0000258 if (write(stderr_fileno, buf, r) <= 0) {
Laszlo Csomor32086b22016-11-24 15:23:55 +0000259 // We don't care, just placate the compiler.
260 }
Laszlo Csomor3b89d2d2016-11-28 14:04:27 +0000261}
Laszlo Csomor32086b22016-11-24 15:23:55 +0000262
Laszlo Csomor31500b82016-12-15 10:15:36 +0000263static void PrintErrorW(const wstring& op) {
264 DWORD last_error = ::GetLastError();
265 if (last_error == 0) {
266 return;
267 }
268
269 WCHAR* message_buffer;
270 FormatMessageW(
271 /* dwFlags */ FORMAT_MESSAGE_ALLOCATE_BUFFER |
272 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
273 /* lpSource */ nullptr,
274 /* dwMessageId */ last_error,
275 /* dwLanguageId */ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
276 /* lpBuffer */ message_buffer,
277 /* nSize */ 0,
278 /* Arguments */ nullptr);
279
280 fwprintf(stderr, L"ERROR: %s: %s (%d)\n", op.c_str(), message_buffer,
281 last_error);
282 LocalFree(message_buffer);
283}
284
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100285void WarnFilesystemType(const string& output_base) {
286}
287
Laszlo Csomorae16e762016-11-18 10:16:08 +0000288string GetProcessIdAsString() {
289 return ToString(GetCurrentProcessId());
290}
291
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100292string GetSelfPath() {
Laszlo Csomor31500b82016-12-15 10:15:36 +0000293 WCHAR buffer[kWindowsPathBufferSize] = {0};
294 if (!GetModuleFileNameW(0, buffer, kWindowsPathBufferSize)) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200295 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
296 "GetSelfPath: GetModuleFileNameW");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100297 }
Laszlo Csomor31500b82016-12-15 10:15:36 +0000298 return string(blaze_util::WstringToCstring(buffer).get());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100299}
300
Kristina Chodorow93963352015-03-20 19:11:19 +0000301string GetOutputRoot() {
Laszlo Csomor5eb3ef92017-02-07 16:52:38 +0000302 for (const char* i : {"TMPDIR", "TEMPDIR", "TMP", "TEMP"}) {
303 string tmpdir(GetEnv(i));
304 if (!tmpdir.empty()) {
305 return tmpdir;
306 }
307 }
Laszlo Csomorf926f3e2016-11-09 09:05:48 +0000308#ifdef COMPILER_MSVC
Laszlo Csomor31500b82016-12-15 10:15:36 +0000309 // GetTempPathW and GetEnvironmentVariableW only work properly when Bazel
Laszlo Csomorf926f3e2016-11-09 09:05:48 +0000310 // runs under cmd.exe, not when it's run from msys.
Laszlo Csomor31500b82016-12-15 10:15:36 +0000311 // The reason is that MSYS consumes all environment variables and sets its own
312 // ones. The symptom of this is that GetEnvironmentVariableW returns nothing
313 // for TEMP under MSYS, though it can retrieve WINDIR.
Laszlo Csomorf926f3e2016-11-09 09:05:48 +0000314
Laszlo Csomor31500b82016-12-15 10:15:36 +0000315 WCHAR buffer[kWindowsPathBufferSize] = {0};
Laszlo Csomorf2b5f272017-01-18 09:51:57 +0000316 if (!::GetTempPathW(kWindowsPathBufferSize, buffer)) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200317 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
318 "GetOutputRoot: GetTempPathW");
Dmitry Lomovbc84cc82016-04-15 14:05:24 +0000319 }
Laszlo Csomor31500b82016-12-15 10:15:36 +0000320 return string(blaze_util::WstringToCstring(buffer).get());
Laszlo Csomorf926f3e2016-11-09 09:05:48 +0000321#else // not COMPILER_MSVC
Laszlo Csomorf926f3e2016-11-09 09:05:48 +0000322 return "/var/tmp";
323#endif // COMPILER_MSVC
Kristina Chodorow93963352015-03-20 19:11:19 +0000324}
325
László Csomorfa27b502017-03-23 15:09:34 +0000326string GetHomeDir() {
327#ifdef COMPILER_MSVC
328 PWSTR wpath;
329 if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_Profile, KF_FLAG_DEFAULT, NULL,
330 &wpath))) {
331 string result = string(blaze_util::WstringToCstring(wpath).get());
332 ::CoTaskMemFree(wpath);
333 return result;
334 }
335#endif
336 return GetEnv("HOME"); // only defined in MSYS/Cygwin
337}
338
Laszlo Csomor760f7862016-12-19 15:46:47 +0000339string FindSystemWideBlazerc() {
340#ifdef COMPILER_MSVC
László Csomorfa27b502017-03-23 15:09:34 +0000341 // TODO(bazel-team): figure out a good path to return here.
Laszlo Csomor760f7862016-12-19 15:46:47 +0000342 return "";
343#else // not COMPILER_MSVC
344 string path = "/etc/bazel.bazelrc";
Laszlo Csomor00549b42017-01-11 09:12:10 +0000345 if (blaze_util::CanReadFile(path)) {
Laszlo Csomor760f7862016-12-19 15:46:47 +0000346 return path;
347 }
348 return "";
349#endif // COMPILER_MSVC
350}
351
Laszlo Csomor00549b42017-01-11 09:12:10 +0000352string GetJavaBinaryUnderJavabase() { return "bin/java.exe"; }
353
Laszlo Csomor943d3cf2016-11-07 14:27:21 +0000354uint64_t GetMillisecondsMonotonic() {
Laszlo Csomora85f52d2016-11-08 13:43:23 +0000355 return WindowsClock::INSTANCE.GetMilliseconds();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100356}
357
Laszlo Csomor943d3cf2016-11-07 14:27:21 +0000358uint64_t GetMillisecondsSinceProcessStart() {
Laszlo Csomora85f52d2016-11-08 13:43:23 +0000359 return WindowsClock::INSTANCE.GetProcessMilliseconds();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100360}
361
362void SetScheduling(bool batch_cpu_scheduling, int io_nice_level) {
363 // TODO(bazel-team): There should be a similar function on Windows.
364}
365
366string GetProcessCWD(int pid) {
Laszlo Csomorcefa9a22016-11-22 10:50:07 +0000367#ifdef COMPILER_MSVC
368 // TODO(bazel-team) 2016-11-18: decide whether we need this on Windows and
369 // implement or delete.
370 return "";
371#else // not COMPILER_MSVC
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100372 char server_cwd[PATH_MAX] = {};
373 if (readlink(
Googler9588b812015-07-23 11:49:37 +0000374 ("/proc/" + ToString(pid) + "/cwd").c_str(),
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100375 server_cwd, sizeof(server_cwd)) < 0) {
376 return "";
377 }
378
379 return string(server_cwd);
Laszlo Csomorcefa9a22016-11-22 10:50:07 +0000380#endif // COMPILER_MSVC
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100381}
382
Thiago Farina01f36002015-04-08 15:59:08 +0000383bool IsSharedLibrary(const string &filename) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100384 return blaze_util::ends_with(filename, ".dll");
385}
386
387string GetDefaultHostJavabase() {
Laszlo Csomorf2b5f272017-01-18 09:51:57 +0000388 string javahome(GetEnv("JAVA_HOME"));
389 if (javahome.empty()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100390 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
391 "Error: JAVA_HOME not set.");
392 }
393 return javahome;
394}
395
Dmitry Lomovb5ff50b2016-02-03 19:35:42 +0000396namespace {
Dmitry Lomovb5ff50b2016-02-03 19:35:42 +0000397
Lukacs Berki7494c922016-04-27 11:17:51 +0000398// Max command line length is per CreateProcess documentation
399// (https://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx)
Lukacs Berki83c78b12016-06-24 12:35:08 +0000400//
401// Quoting rules are described here:
402// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
403
Lukacs Berki7494c922016-04-27 11:17:51 +0000404static const int MAX_CMDLINE_LENGTH = 32768;
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000405
Lukacs Berki7494c922016-04-27 11:17:51 +0000406struct CmdLine {
407 char cmdline[MAX_CMDLINE_LENGTH];
408};
409static void CreateCommandLine(CmdLine* result, const string& exe,
Laszlo Csomore152f722017-02-03 10:05:36 +0000410 const std::vector<string>& args_vector) {
Laszlo Csomorf9f41c72017-01-09 12:06:45 +0000411 std::ostringstream cmdline;
Laszlo Csomor44ecf9a2017-01-10 10:41:26 +0000412 string short_exe;
Laszlo Csomor00549b42017-01-11 09:12:10 +0000413 if (!blaze_util::AsShortWindowsPath(exe, &short_exe)) {
Laszlo Csomor44ecf9a2017-01-10 10:41:26 +0000414 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +0200415 "CreateCommandLine: AsShortWindowsPath(%s)", exe.c_str());
Laszlo Csomor44ecf9a2017-01-10 10:41:26 +0000416 }
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000417 bool first = true;
418 for (const auto& s : args_vector) {
419 if (first) {
420 first = false;
Laszlo Csomor00549b42017-01-11 09:12:10 +0000421 // Skip first argument, instead use quoted executable name.
Philipp Wollermannb78d8c82017-03-07 10:29:42 +0000422 cmdline << '\"' << short_exe << '\"';
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000423 continue;
424 } else {
Laszlo Csomorf9f41c72017-01-09 12:06:45 +0000425 cmdline << ' ';
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000426 }
Dmitry Lomovb5ff50b2016-02-03 19:35:42 +0000427
Lukacs Berki83c78b12016-06-24 12:35:08 +0000428 bool has_space = s.find(" ") != string::npos;
429
430 if (has_space) {
Laszlo Csomorf9f41c72017-01-09 12:06:45 +0000431 cmdline << '\"';
Dmitry Lomovb5ff50b2016-02-03 19:35:42 +0000432 }
433
Lukacs Berki83c78b12016-06-24 12:35:08 +0000434 std::string::const_iterator it = s.begin();
435 while (it != s.end()) {
436 char ch = *it++;
437 switch (ch) {
438 case '"':
439 // Escape double quotes
Laszlo Csomorf9f41c72017-01-09 12:06:45 +0000440 cmdline << "\\\"";
Lukacs Berki83c78b12016-06-24 12:35:08 +0000441 break;
442
443 case '\\':
444 if (it == s.end()) {
445 // Backslashes at the end of the string are quoted if we add quotes
Laszlo Csomorf9f41c72017-01-09 12:06:45 +0000446 cmdline << (has_space ? "\\\\" : "\\");
Lukacs Berki83c78b12016-06-24 12:35:08 +0000447 } else {
448 // Backslashes everywhere else are quoted if they are followed by a
449 // quote or a backslash
Laszlo Csomorf9f41c72017-01-09 12:06:45 +0000450 cmdline << (*it == '"' || *it == '\\' ? "\\\\" : "\\");
Lukacs Berki83c78b12016-06-24 12:35:08 +0000451 }
452 break;
453
Laszlo Csomorf00cee82016-12-15 10:58:03 +0000454 default:
Laszlo Csomorf9f41c72017-01-09 12:06:45 +0000455 cmdline << ch;
Lukacs Berki83c78b12016-06-24 12:35:08 +0000456 }
457 }
458
459 if (has_space) {
Laszlo Csomorf9f41c72017-01-09 12:06:45 +0000460 cmdline << '\"';
Dmitry Lomovb5ff50b2016-02-03 19:35:42 +0000461 }
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000462 }
463
Laszlo Csomorf9f41c72017-01-09 12:06:45 +0000464 string cmdline_str = cmdline.str();
465 if (cmdline_str.size() >= MAX_CMDLINE_LENGTH) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200466 pdie(blaze_exit_code::INTERNAL_ERROR, "Command line too long (%d > %d): %s",
467 cmdline_str.size(), MAX_CMDLINE_LENGTH, cmdline_str.c_str());
Lukacs Berki7494c922016-04-27 11:17:51 +0000468 }
469
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000470 // Copy command line into a mutable buffer.
471 // CreateProcess is allowed to mutate its command line argument.
Laszlo Csomorf9f41c72017-01-09 12:06:45 +0000472 strncpy(result->cmdline, cmdline_str.c_str(), MAX_CMDLINE_LENGTH - 1);
Lukacs Berki2236f7d2016-04-28 08:49:43 +0000473 result->cmdline[MAX_CMDLINE_LENGTH - 1] = 0;
Lukacs Berki7494c922016-04-27 11:17:51 +0000474}
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000475
Lukacs Berki7494c922016-04-27 11:17:51 +0000476} // namespace
477
Laszlo Csomorf00cee82016-12-15 10:58:03 +0000478string GetJvmVersion(const string& java_exe) {
Lukacs Berki7494c922016-04-27 11:17:51 +0000479 HANDLE pipe_read, pipe_write;
Laszlo Csomorf00cee82016-12-15 10:58:03 +0000480
481 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200482 if (!::CreatePipe(&pipe_read, &pipe_write, &sa, 0)) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200483 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
484 "GetJvmVersion: CreatePipe");
Lukacs Berki7494c922016-04-27 11:17:51 +0000485 }
486
487 if (!SetHandleInformation(pipe_read, HANDLE_FLAG_INHERIT, 0)) {
Laszlo Csomorf00cee82016-12-15 10:58:03 +0000488 CloseHandle(pipe_read);
489 CloseHandle(pipe_write);
Laszlo Csomor6d477072017-07-20 10:09:44 +0200490 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
491 "GetJvmVersion: SetHandleInformation");
Lukacs Berki7494c922016-04-27 11:17:51 +0000492 }
493
494 PROCESS_INFORMATION processInfo = {0};
Laszlo Csomor44ecf9a2017-01-10 10:41:26 +0000495 STARTUPINFOA startupInfo = {0};
Lukacs Berki7494c922016-04-27 11:17:51 +0000496 startupInfo.hStdError = pipe_write;
497 startupInfo.hStdOutput = pipe_write;
498 startupInfo.dwFlags |= STARTF_USESTDHANDLES;
Lukacs Berki7494c922016-04-27 11:17:51 +0000499
Laszlo Csomor44ecf9a2017-01-10 10:41:26 +0000500 string win_java_exe;
Laszlo Csomor00549b42017-01-11 09:12:10 +0000501 if (!blaze_util::AsShortWindowsPath(java_exe, &win_java_exe)) {
Laszlo Csomor3cb63682017-01-05 12:13:48 +0000502 CloseHandle(pipe_read);
503 CloseHandle(pipe_write);
Laszlo Csomor44ecf9a2017-01-10 10:41:26 +0000504 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomora6695572017-01-18 10:58:17 +0000505 "GetJvmVersion: AsShortWindowsPath(%s)", java_exe.c_str());
Laszlo Csomor3cb63682017-01-05 12:13:48 +0000506 }
Laszlo Csomor44ecf9a2017-01-10 10:41:26 +0000507 win_java_exe = string("\"") + win_java_exe + "\" -version";
Laszlo Csomor3cb63682017-01-05 12:13:48 +0000508
Laszlo Csomor44ecf9a2017-01-10 10:41:26 +0000509 char cmdline[MAX_CMDLINE_LENGTH];
510 strncpy(cmdline, win_java_exe.c_str(), win_java_exe.size() + 1);
511 BOOL ok = CreateProcessA(
Laszlo Csomor31500b82016-12-15 10:15:36 +0000512 /* lpApplicationName */ NULL,
Laszlo Csomorf00cee82016-12-15 10:58:03 +0000513 /* lpCommandLine */ cmdline,
Laszlo Csomor31500b82016-12-15 10:15:36 +0000514 /* lpProcessAttributes */ NULL,
515 /* lpThreadAttributes */ NULL,
516 /* bInheritHandles */ TRUE,
517 /* dwCreationFlags */ 0,
518 /* lpEnvironment */ NULL,
519 /* lpCurrentDirectory */ NULL,
520 /* lpStartupInfo */ &startupInfo,
521 /* lpProcessInformation */ &processInfo);
Lukacs Berki7494c922016-04-27 11:17:51 +0000522
523 if (!ok) {
Laszlo Csomorf00cee82016-12-15 10:58:03 +0000524 CloseHandle(pipe_read);
525 CloseHandle(pipe_write);
Dmitry Lomov42d82902016-07-15 13:26:57 +0000526 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +0200527 "RunProgram: CreateProcess(%s)", cmdline);
Lukacs Berki7494c922016-04-27 11:17:51 +0000528 }
529
530 CloseHandle(pipe_write);
531 std::string result = "";
532 DWORD bytes_read;
533 CHAR buf[1024];
534
535 for (;;) {
536 ok = ::ReadFile(pipe_read, buf, 1023, &bytes_read, NULL);
537 if (!ok || bytes_read == 0) {
538 break;
539 }
540 buf[bytes_read] = 0;
541 result = result + buf;
542 }
543
544 CloseHandle(pipe_read);
545 CloseHandle(processInfo.hProcess);
546 CloseHandle(processInfo.hThread);
Laszlo Csomorf00cee82016-12-15 10:58:03 +0000547 return ReadJvmVersion(result);
Lukacs Berki7494c922016-04-27 11:17:51 +0000548}
549
Laszlo Csomor5fa18d12017-03-14 15:25:13 +0000550#ifndef COMPILER_MSVC
Lukacs Berki7494c922016-04-27 11:17:51 +0000551// If we pass DETACHED_PROCESS to CreateProcess(), cmd.exe appropriately
552// returns the command prompt when the client terminates. msys2, however, in
553// its infinite wisdom, waits until the *server* terminates and cannot be
554// convinced otherwise.
555//
556// So, we first pretend to be a POSIX daemon so that msys2 knows about our
557// intentions and *then* we call CreateProcess(). Life ain't easy.
558static bool DaemonizeOnWindows() {
559 if (fork() > 0) {
560 // We are the original client process.
561 return true;
562 }
563
564 if (fork() > 0) {
565 // We are the child of the original client process. Terminate so that the
566 // actual server is not a child process of the client.
567 exit(0);
568 }
569
570 setsid();
571 // Contrary to the POSIX version, we are not closing the three standard file
572 // descriptors here. CreateProcess() will take care of that and it's useful
573 // to see the error messages in ExecuteDaemon() on the console of the client.
574 return false;
575}
Laszlo Csomor5fa18d12017-03-14 15:25:13 +0000576#endif // not COMPILER_MSVC
Lukacs Berki7494c922016-04-27 11:17:51 +0000577
Laszlo Csomord0512c42017-03-20 16:10:39 +0000578static bool GetProcessStartupTime(HANDLE process, uint64_t* result) {
579 FILETIME creation_time, dummy1, dummy2, dummy3;
580 // GetProcessTimes cannot handle NULL arguments.
581 if (process == INVALID_HANDLE_VALUE ||
582 !::GetProcessTimes(process, &creation_time, &dummy1, &dummy2, &dummy3)) {
583 return false;
584 }
585 *result = static_cast<uint64_t>(creation_time.dwHighDateTime) << 32 |
586 creation_time.dwLowDateTime;
587 return true;
588}
589
590static void WriteProcessStartupTime(const string& server_dir, HANDLE process) {
591 uint64_t start_time = 0;
592 if (!GetProcessStartupTime(process, &start_time)) {
593 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +0200594 "WriteProcessStartupTime(%s): GetProcessStartupTime",
Laszlo Csomord0512c42017-03-20 16:10:39 +0000595 server_dir.c_str());
596 }
597
598 string start_time_file = blaze_util::JoinPath(server_dir, "server.starttime");
599 if (!blaze_util::WriteFile(ToString(start_time), start_time_file)) {
600 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +0200601 "WriteProcessStartupTime(%s): WriteFile(%s)", server_dir.c_str(),
602 start_time_file.c_str());
Laszlo Csomord0512c42017-03-20 16:10:39 +0000603 }
604}
605
606static HANDLE CreateJvmOutputFile(const wstring& path,
607 SECURITY_ATTRIBUTES* sa) {
608 // If the previous server process was asked to be shut down (but not killed),
609 // it takes a while for it to comply, so wait until the JVM output file that
610 // it held open is closed. There seems to be no better way to wait for a file
611 // to be closed on Windows.
612 static const unsigned int timeout_sec = 60;
613 for (unsigned int waited = 0; waited < timeout_sec; ++waited) {
614 HANDLE handle = ::CreateFileW(
615 /* lpFileName */ path.c_str(),
616 /* dwDesiredAccess */ GENERIC_READ | GENERIC_WRITE,
Laszlo Csomor70ff798a2017-06-12 09:39:10 +0200617 /* dwShareMode */ FILE_SHARE_READ,
Laszlo Csomord0512c42017-03-20 16:10:39 +0000618 /* lpSecurityAttributes */ sa,
619 /* dwCreationDisposition */ CREATE_ALWAYS,
620 /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL,
621 /* hTemplateFile */ NULL);
622 if (handle != INVALID_HANDLE_VALUE) {
623 return handle;
624 }
625 if (GetLastError() != ERROR_SHARING_VIOLATION &&
626 GetLastError() != ERROR_LOCK_VIOLATION) {
627 // Some other error occurred than the file being open; bail out.
628 break;
629 }
630
631 // The file is still held open, the server is shutting down. There's a
632 // chance that another process holds it open, we don't know; in that case
633 // we just exit after the timeout expires.
634 if (waited == 5 || waited == 10 || waited == 30) {
635 fprintf(stderr,
636 "Waiting for previous Bazel server's log file to close "
637 "(waited %d seconds, waiting at most %d)\n",
638 waited, timeout_sec);
639 }
640 Sleep(1000);
641 }
642 return INVALID_HANDLE_VALUE;
643}
644
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200645#ifdef COMPILER_MSVC
646
647class ProcessHandleBlazeServerStartup : public BlazeServerStartup {
648 public:
649 ProcessHandleBlazeServerStartup(HANDLE _proc) : proc(_proc) {}
650
651 bool IsStillAlive() override {
652 FILETIME dummy1, exit_time, dummy2, dummy3;
653 return GetProcessTimes(proc, &dummy1, &exit_time, &dummy2, &dummy3) &&
654 exit_time.dwHighDateTime == 0 && exit_time.dwLowDateTime == 0;
655 }
656
657 private:
Laszlo Csomorf0702342017-06-28 16:05:23 +0200658 AutoHandle proc;
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200659};
660
661#else // COMPILER_MSVC
662
Lukacs Berki1977d922016-05-02 09:31:37 +0000663// Keeping an eye on the server process on Windows is not implemented yet.
664// TODO(lberki): Implement this, because otherwise if we can't start up a server
665// process, the client will hang until it times out.
666class DummyBlazeServerStartup : public BlazeServerStartup {
667 public:
668 DummyBlazeServerStartup() {}
669 virtual ~DummyBlazeServerStartup() {}
Lukacs Berki9d52bc52016-06-07 11:11:04 +0000670 virtual bool IsStillAlive() { return true; }
Lukacs Berki1977d922016-05-02 09:31:37 +0000671};
672
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200673#endif // COMPILER_MSVC
674
Lukacs Berki1977d922016-05-02 09:31:37 +0000675void ExecuteDaemon(const string& exe, const std::vector<string>& args_vector,
676 const string& daemon_output, const string& server_dir,
677 BlazeServerStartup** server_startup) {
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200678#ifndef COMPILER_MSVC
Lukacs Berki7494c922016-04-27 11:17:51 +0000679 if (DaemonizeOnWindows()) {
680 // We are the client process
Lukacs Berki1977d922016-05-02 09:31:37 +0000681 *server_startup = new DummyBlazeServerStartup();
682 return;
Lukacs Berki7494c922016-04-27 11:17:51 +0000683 }
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200684#endif // not COMPILER_MSVC
Lukacs Berki7494c922016-04-27 11:17:51 +0000685
Laszlo Csomora6695572017-01-18 10:58:17 +0000686 wstring wdaemon_output;
laszlocsomorb69acfa2017-07-21 13:18:13 +0200687 if (!blaze_util::AsAbsoluteWindowsPath(daemon_output, &wdaemon_output)) {
Laszlo Csomora6695572017-01-18 10:58:17 +0000688 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
laszlocsomorb69acfa2017-07-21 13:18:13 +0200689 "ExecuteDaemon(%s): AsAbsoluteWindowsPath(%s)", exe.c_str(),
Laszlo Csomor6d477072017-07-20 10:09:44 +0200690 daemon_output.c_str());
Laszlo Csomora6695572017-01-18 10:58:17 +0000691 }
692
Lukacs Berki7494c922016-04-27 11:17:51 +0000693 SECURITY_ATTRIBUTES sa;
Lukacs Berki7494c922016-04-27 11:17:51 +0000694 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200695 // We redirect stdin to the NUL device, and redirect stdout and stderr to
Laszlo Csomor70ff798a2017-06-12 09:39:10 +0200696 // `stdout_file` and `stderr_file` (opened below) by telling CreateProcess to
697 // use these file handles, so they must be inheritable.
Lukacs Berki7494c922016-04-27 11:17:51 +0000698 sa.bInheritHandle = TRUE;
699 sa.lpSecurityDescriptor = NULL;
700
Laszlo Csomorf0702342017-06-28 16:05:23 +0200701 AutoHandle devnull(::CreateFileA("NUL", GENERIC_READ, FILE_SHARE_READ, NULL,
702 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200703 if (!devnull.IsValid()) {
704 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +0200705 "ExecuteDaemon(%s): CreateFileA(NUL)", exe.c_str());
Lukacs Berki7494c922016-04-27 11:17:51 +0000706 }
707
Laszlo Csomorf0702342017-06-28 16:05:23 +0200708 AutoHandle stdout_file(CreateJvmOutputFile(wdaemon_output.c_str(), &sa));
Laszlo Csomor70ff798a2017-06-12 09:39:10 +0200709 if (!stdout_file.IsValid()) {
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200710 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +0200711 "ExecuteDaemon(%s): CreateJvmOutputFile(%ls)", exe.c_str(),
712 wdaemon_output.c_str());
Lukacs Berki7494c922016-04-27 11:17:51 +0000713 }
Laszlo Csomor70ff798a2017-06-12 09:39:10 +0200714 HANDLE stderr_handle;
715 // We must duplicate the handle to stdout, otherwise "bazel clean --expunge"
716 // won't work, because when it tries to close stdout then stderr, the former
717 // will succeed but the latter will appear to be valid yet still fail to
718 // close.
719 if (!DuplicateHandle(
720 /* hSourceProcessHandle */ GetCurrentProcess(),
721 /* hSourceHandle */ stdout_file,
722 /* hTargetProcessHandle */ GetCurrentProcess(),
723 /* lpTargetHandle */ &stderr_handle,
724 /* dwDesiredAccess */ 0,
725 /* bInheritHandle */ TRUE,
726 /* dwOptions */ DUPLICATE_SAME_ACCESS)) {
727 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +0200728 "ExecuteDaemon(%s): DuplicateHandle(%ls)", exe.c_str(),
729 wdaemon_output.c_str());
Laszlo Csomor70ff798a2017-06-12 09:39:10 +0200730 }
Laszlo Csomorf0702342017-06-28 16:05:23 +0200731 AutoHandle stderr_file(stderr_handle);
Lukacs Berki7494c922016-04-27 11:17:51 +0000732
733 PROCESS_INFORMATION processInfo = {0};
Laszlo Csomord86ae8c2016-12-05 13:54:59 +0000734 STARTUPINFOA startupInfo = {0};
Lukacs Berki7494c922016-04-27 11:17:51 +0000735
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200736 startupInfo.hStdInput = devnull;
Laszlo Csomor70ff798a2017-06-12 09:39:10 +0200737 startupInfo.hStdError = stdout_file;
738 startupInfo.hStdOutput = stderr_handle;
Lukacs Berki7494c922016-04-27 11:17:51 +0000739 startupInfo.dwFlags |= STARTF_USESTDHANDLES;
740 CmdLine cmdline;
741 CreateCommandLine(&cmdline, exe, args_vector);
Lukacs Berki7494c922016-04-27 11:17:51 +0000742
Laszlo Csomor31500b82016-12-15 10:15:36 +0000743 BOOL ok = CreateProcessA(
744 /* lpApplicationName */ NULL,
745 /* lpCommandLine */ cmdline.cmdline,
746 /* lpProcessAttributes */ NULL,
747 /* lpThreadAttributes */ NULL,
748 /* bInheritHandles */ TRUE,
749 /* dwCreationFlags */ DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP,
750 /* lpEnvironment */ NULL,
751 /* lpCurrentDirectory */ NULL,
752 /* lpStartupInfo */ &startupInfo,
753 /* lpProcessInformation */ &processInfo);
Lukacs Berki7494c922016-04-27 11:17:51 +0000754
755 if (!ok) {
Dmitry Lomov42d82902016-07-15 13:26:57 +0000756 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +0200757 "ExecuteDaemon(%s): CreateProcess(%s)", exe.c_str(), cmdline.cmdline);
Lukacs Berki7494c922016-04-27 11:17:51 +0000758 }
759
Laszlo Csomord0512c42017-03-20 16:10:39 +0000760 WriteProcessStartupTime(server_dir, processInfo.hProcess);
761
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200762#ifdef COMPILER_MSVC
763 // Pass ownership of processInfo.hProcess
764 *server_startup = new ProcessHandleBlazeServerStartup(processInfo.hProcess);
765#endif
Lukacs Berki7494c922016-04-27 11:17:51 +0000766
Lukacs Berkid825a3d2016-06-23 11:10:02 +0000767 string pid_string = ToString(processInfo.dwProcessId);
Thiago Farina048bbfc2016-09-21 08:20:41 +0000768 string pid_file = blaze_util::JoinPath(server_dir, kServerPidFile);
Laszlo Csomor49970e02016-11-28 08:55:47 +0000769 if (!blaze_util::WriteFile(pid_string, pid_file)) {
Lukacs Berkie33cf0f2016-04-28 11:04:59 +0000770 // Not a lot we can do if this fails
771 fprintf(stderr, "Cannot write PID file %s\n", pid_file.c_str());
772 }
773
Laszlo Csomord12fb7a2017-04-26 13:41:56 +0200774 // Don't close processInfo.hProcess here, it's now owned by the
775 // ProcessHandleBlazeServerStartup instance.
Lukacs Berki7494c922016-04-27 11:17:51 +0000776 CloseHandle(processInfo.hThread);
777
Laszlo Csomor5fa18d12017-03-14 15:25:13 +0000778#ifndef COMPILER_MSVC
Lukacs Berki7494c922016-04-27 11:17:51 +0000779 exit(0);
Laszlo Csomor5fa18d12017-03-14 15:25:13 +0000780#endif // COMPILER_MSVC
Lukacs Berki7494c922016-04-27 11:17:51 +0000781}
782
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000783void BatchWaiterThread(HANDLE java_handle) {
784 WaitForSingleObject(java_handle, INFINITE);
785}
786
Laszlo Csomorcefa9a22016-11-22 10:50:07 +0000787#ifdef COMPILER_MSVC
788 // TODO(bazel-team): implement signal handling.
789#else // not COMPILER_MSVC
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000790static void MingwSignalHandler(int signum) {
791 // Java process will be terminated because we set the job to terminate if its
792 // handle is closed.
793 //
794 // Note that this is different how interruption is handled on Unix, where the
795 // Java process sets up a signal handler for SIGINT itself. That cannot be
796 // done on Windows without using native code, and it's better to have as
797 // little JNI as possible. The most important part of the cleanup after
798 // termination (killing all child processes) happens automatically on Windows
799 // anyway, since we put the batch Java process in its own job which does not
800 // allow breakaway processes.
801 exit(blaze_exit_code::ExitCode::INTERRUPTED);
802}
Laszlo Csomorcefa9a22016-11-22 10:50:07 +0000803#endif // COMPILER_MSVC
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000804
Lukacs Berki23cf3962016-07-19 09:28:23 +0000805// Returns whether assigning the given process to a job failed because nested
806// jobs are not available on the current system.
807static bool IsFailureDueToNestedJobsNotSupported(HANDLE process) {
808 BOOL is_in_job;
809 if (!IsProcessInJob(process, NULL, &is_in_job)) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200810 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
811 "IsFailureDueToNestedJobsNotSupported: IsProcessInJob");
Lukacs Berki23cf3962016-07-19 09:28:23 +0000812 return false;
813 }
814
815 if (!is_in_job) {
816 // Not in a job.
817 return false;
818 }
laszlocsomor704fb662017-03-29 09:36:30 +0000819 return !IsWindows8OrGreater();
Lukacs Berki23cf3962016-07-19 09:28:23 +0000820}
821
Philipp Wollermanne4d977f2017-03-15 15:34:11 +0000822// Run the given program in the current working directory, using the given
823// argument vector, wait for it to finish, then exit ourselves with the exitcode
824// of that program.
Laszlo Csomore152f722017-02-03 10:05:36 +0000825void ExecuteProgram(const string& exe, const std::vector<string>& args_vector) {
Lukacs Berki7494c922016-04-27 11:17:51 +0000826 CmdLine cmdline;
827 CreateCommandLine(&cmdline, exe, args_vector);
828
Laszlo Csomord86ae8c2016-12-05 13:54:59 +0000829 STARTUPINFOA startupInfo = {0};
Philipp Wollermanne4d977f2017-03-15 15:34:11 +0000830 startupInfo.cb = sizeof(STARTUPINFOA);
831
Lukacs Berki7494c922016-04-27 11:17:51 +0000832 PROCESS_INFORMATION processInfo = {0};
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000833
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000834 HANDLE job = CreateJobObject(NULL, NULL);
835 if (job == NULL) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200836 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
837 "ExecuteProgram(%s): CreateJobObject", exe.c_str());
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000838 }
839
Philipp Wollermanne4d977f2017-03-15 15:34:11 +0000840 JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {0};
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000841 job_info.BasicLimitInformation.LimitFlags =
842 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
Philipp Wollermanne4d977f2017-03-15 15:34:11 +0000843
844 if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation,
845 &job_info, sizeof(job_info))) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200846 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
847 "ExecuteProgram(%s): SetInformationJobObject", exe.c_str());
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000848 }
849
Laszlo Csomor31500b82016-12-15 10:15:36 +0000850 BOOL success = CreateProcessA(
851 /* lpApplicationName */ NULL,
852 /* lpCommandLine */ cmdline.cmdline,
853 /* lpProcessAttributes */ NULL,
854 /* lpThreadAttributes */ NULL,
855 /* bInheritHandles */ TRUE,
Philipp Wollermanne4d977f2017-03-15 15:34:11 +0000856 /* dwCreationFlags */ CREATE_SUSPENDED,
Laszlo Csomor31500b82016-12-15 10:15:36 +0000857 /* lpEnvironment */ NULL,
858 /* lpCurrentDirectory */ NULL,
859 /* lpStartupInfo */ &startupInfo,
860 /* lpProcessInformation */ &processInfo);
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000861
862 if (!success) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200863 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
864 "ExecuteProgram(%s): CreateProcess(%s)", exe.c_str(), cmdline.cmdline);
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000865 }
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000866
Philipp Wollermanne4d977f2017-03-15 15:34:11 +0000867 // We will try to put the launched process into a Job object. This will make
868 // Windows reliably kill all child processes that the process itself may
869 // launch once the process exits. On Windows systems that don't support nested
870 // jobs, this may fail if we are already running inside a job ourselves. In
871 // this case, we'll continue anyway, because we assume that our parent is
872 // handling process management for us.
873 if (!AssignProcessToJobObject(job, processInfo.hProcess) &&
874 !IsFailureDueToNestedJobsNotSupported(processInfo.hProcess)) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200875 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
876 "ExecuteProgram(%s): AssignProcessToJobObject", exe.c_str());
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000877 }
878
Philipp Wollermanne4d977f2017-03-15 15:34:11 +0000879 // Now that we potentially put the process into a new job object, we can start
880 // running it.
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000881 if (ResumeThread(processInfo.hThread) == -1) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200882 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
883 "ExecuteProgram(%s): ResumeThread", exe.c_str());
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000884 }
885
886 // msys doesn't deliver signals while a Win32 call is pending so we need to
887 // do the blocking call in another thread
Laszlo Csomorcefa9a22016-11-22 10:50:07 +0000888
889#ifdef COMPILER_MSVC
890 // TODO(bazel-team): implement signal handling.
891#else // not COMPILER_MSVC
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000892 signal(SIGINT, MingwSignalHandler);
Laszlo Csomorcefa9a22016-11-22 10:50:07 +0000893#endif // COMPILER_MSVC
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000894 std::thread batch_waiter_thread([=]() {
895 BatchWaiterThread(processInfo.hProcess);
896 });
897
Lukacs Berki1977d922016-05-02 09:31:37 +0000898 // The output base lock is held while waiting
Lukacs Berki68cb41a2016-07-06 11:43:37 +0000899 batch_waiter_thread.join();
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000900 DWORD exit_code;
Lukacs Berki7494c922016-04-27 11:17:51 +0000901 GetExitCodeProcess(processInfo.hProcess, &exit_code);
902 CloseHandle(processInfo.hProcess);
903 CloseHandle(processInfo.hThread);
904 exit(exit_code);
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000905}
906
Thiago Farinac3aee5a2017-04-24 18:02:52 +0200907const char kListSeparator = ';';
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000908
László Csomore64ed192017-02-23 15:43:54 +0000909string PathAsJvmFlag(const string& path) {
Laszlo Csomor9d3f9892017-03-07 10:03:44 +0000910 string spath;
911 if (!blaze_util::AsShortWindowsPath(path, &spath)) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200912 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
913 "PathAsJvmFlag(%s): AsShortWindowsPath", path.c_str());
Laszlo Csomor9d3f9892017-03-07 10:03:44 +0000914 }
László Csomore64ed192017-02-23 15:43:54 +0000915 // Convert backslashes to forward slashes, in order to avoid the JVM parsing
916 // Windows paths as if they contained escaped characters.
917 // See https://github.com/bazelbuild/bazel/issues/2576
Laszlo Csomor9d3f9892017-03-07 10:03:44 +0000918 std::replace(spath.begin(), spath.end(), '\\', '/');
919 return spath;
László Csomore64ed192017-02-23 15:43:54 +0000920}
921
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000922string ConvertPath(const string& path) {
Laszlo Csomorcefa9a22016-11-22 10:50:07 +0000923#ifdef COMPILER_MSVC
László Csomor78b8be62017-03-28 09:04:06 +0000924 // The path may not be Windows-style and may not be normalized, so convert it.
925 wstring wpath;
laszlocsomorb69acfa2017-07-21 13:18:13 +0200926 if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath)) {
927 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
928 "ConvertPath(%s): AsAbsoluteWindowsPath", path.c_str());
László Csomor78b8be62017-03-28 09:04:06 +0000929 }
930 std::transform(wpath.begin(), wpath.end(), wpath.begin(), ::towlower);
laszlocsomorb69acfa2017-07-21 13:18:13 +0200931 return string(blaze_util::WstringToCstring(
932 blaze_util::RemoveUncPrefixMaybe(wpath.c_str()))
933 .get());
Laszlo Csomorcefa9a22016-11-22 10:50:07 +0000934#else // not COMPILER_MSVC
Yun Peng44fa4c72016-07-15 08:38:38 +0000935 // If the path looks like %USERPROFILE%/foo/bar, don't convert.
936 if (path.empty() || path[0] == '%') {
László Csomor78b8be62017-03-28 09:04:06 +0000937 // It's fine to convert to lower-case even if the path contains environment
938 // variable names, since Windows can look them up case-insensitively.
939 return blaze_util::AsLower(path);
Yun Peng44fa4c72016-07-15 08:38:38 +0000940 }
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000941 char* wpath = static_cast<char*>(cygwin_create_path(
942 CCP_POSIX_TO_WIN_A, static_cast<const void*>(path.c_str())));
943 string result(wpath);
944 free(wpath);
László Csomor78b8be62017-03-28 09:04:06 +0000945 return blaze_util::AsLower(result);
Laszlo Csomorcefa9a22016-11-22 10:50:07 +0000946#endif // COMPILER_MSVC
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000947}
948
Yun Peng44fa4c72016-07-15 08:38:38 +0000949// Convert a Unix path list to Windows path list
950string ConvertPathList(const string& path_list) {
Laszlo Csomor9d3f9892017-03-07 10:03:44 +0000951#ifdef COMPILER_MSVC
952 // In the MSVC version we use the actual %PATH% value which is separated by
953 // ";" and contains Windows paths.
954 return path_list;
955#else // not COMPILER_MSVC
Yun Peng44fa4c72016-07-15 08:38:38 +0000956 string w_list = "";
957 int start = 0;
958 int pos;
959 while ((pos = path_list.find(":", start)) != string::npos) {
960 w_list += ConvertPath(path_list.substr(start, pos - start)) + ";";
961 start = pos + 1;
962 }
963 if (start < path_list.size()) {
964 w_list += ConvertPath(path_list.substr(start));
965 }
966 return w_list;
Laszlo Csomor9d3f9892017-03-07 10:03:44 +0000967#endif // COMPILER_MSVC
Yun Peng44fa4c72016-07-15 08:38:38 +0000968}
969
Lukacs Berki497d8242016-04-28 07:21:26 +0000970bool SymlinkDirectories(const string &posix_target, const string &posix_name) {
Laszlo Csomor38db3162017-02-15 13:54:32 +0000971 wstring name;
972 wstring target;
laszlocsomorb69acfa2017-07-21 13:18:13 +0200973 if (!blaze_util::AsAbsoluteWindowsPath(posix_name, &name)) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200974 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
laszlocsomorb69acfa2017-07-21 13:18:13 +0200975 "SymlinkDirectories(%s, %s): AsAbsoluteWindowsPath(%s)",
Laszlo Csomor6d477072017-07-20 10:09:44 +0200976 posix_target.c_str(), posix_name.c_str(), posix_target.c_str());
Laszlo Csomora6695572017-01-18 10:58:17 +0000977 return false;
978 }
laszlocsomorb69acfa2017-07-21 13:18:13 +0200979 if (!blaze_util::AsAbsoluteWindowsPath(posix_target, &target)) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200980 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
laszlocsomorb69acfa2017-07-21 13:18:13 +0200981 "SymlinkDirectories(%s, %s): AsAbsoluteWindowsPath(%s)",
Laszlo Csomor6d477072017-07-20 10:09:44 +0200982 posix_target.c_str(), posix_name.c_str(), posix_name.c_str());
Lukacs Berki497d8242016-04-28 07:21:26 +0000983 return false;
984 }
Laszlo Csomorf0702342017-06-28 16:05:23 +0200985 string error(CreateJunction(name, target));
Laszlo Csomor38db3162017-02-15 13:54:32 +0000986 if (!error.empty()) {
Laszlo Csomor6d477072017-07-20 10:09:44 +0200987 blaze_util::PrintError("SymlinkDirectories(%s, %s): CreateJunction: %s",
988 posix_target.c_str(), posix_name.c_str(),
989 error.c_str());
Lukacs Berki497d8242016-04-28 07:21:26 +0000990 return false;
991 }
Laszlo Csomor38db3162017-02-15 13:54:32 +0000992 return true;
Lukacs Berki497d8242016-04-28 07:21:26 +0000993}
994
Lukacs Berki497d8242016-04-28 07:21:26 +0000995bool CompareAbsolutePaths(const string& a, const string& b) {
László Csomor78b8be62017-03-28 09:04:06 +0000996 return ConvertPath(a) == ConvertPath(b);
Dmitry Lomov47afaab2016-02-19 08:21:13 +0000997}
998
Laszlo Csomor032ddee2017-03-21 10:36:03 +0000999#ifndef STILL_ACTIVE
1000#define STILL_ACTIVE (259) // From MSDN about GetExitCodeProcess.
1001#endif
1002
Laszlo Csomord0512c42017-03-20 16:10:39 +00001003// On Windows (and Linux) we use a combination of PID and start time to identify
1004// the server process. That is supposed to be unique unless one can start more
1005// processes than there are PIDs available within a single jiffy.
mschallerfd37b512017-07-11 18:21:36 +02001006bool VerifyServerProcess(int pid, const string& output_base) {
Laszlo Csomorf0702342017-06-28 16:05:23 +02001007 AutoHandle process(
Laszlo Csomord0512c42017-03-20 16:10:39 +00001008 ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
1009 if (!process.IsValid()) {
1010 // Cannot find the server process. Can happen if the PID file is stale.
1011 return false;
1012 }
1013
Laszlo Csomor032ddee2017-03-21 10:36:03 +00001014 DWORD exit_code = 0;
Laszlo Csomord0512c42017-03-20 16:10:39 +00001015 uint64_t start_time = 0;
Laszlo Csomor032ddee2017-03-21 10:36:03 +00001016 if (!::GetExitCodeProcess(process, &exit_code) || exit_code != STILL_ACTIVE ||
1017 !GetProcessStartupTime(process, &start_time)) {
1018 // Process doesn't exist or died meantime, all is good. No stale server is
1019 // present.
Laszlo Csomord0512c42017-03-20 16:10:39 +00001020 return false;
1021 }
1022
1023 string recorded_start_time;
1024 bool file_present = blaze_util::ReadFile(
1025 blaze_util::JoinPath(output_base, "server/server.starttime"),
1026 &recorded_start_time);
1027
1028 // If start time file got deleted, but PID file didn't, assume that this is an
1029 // old Bazel process that doesn't know how to write start time files yet.
1030 return !file_present || recorded_start_time == ToString(start_time);
Lukacs Berkiee44c382016-09-14 10:53:37 +00001031}
1032
mschallerfd37b512017-07-11 18:21:36 +02001033bool KillServerProcess(int pid, const string& output_base) {
Laszlo Csomorf0702342017-06-28 16:05:23 +02001034 AutoHandle process(::OpenProcess(
Laszlo Csomor032ddee2017-03-21 10:36:03 +00001035 PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
1036 DWORD exitcode = 0;
1037 if (!process.IsValid() || !::GetExitCodeProcess(process, &exitcode) ||
1038 exitcode != STILL_ACTIVE) {
1039 // Cannot find the server process (can happen if the PID file is stale) or
1040 // it already exited.
Lukacs Berki119dd4b2016-07-13 15:28:42 +00001041 return false;
Lukacs Berkid825a3d2016-06-23 11:10:02 +00001042 }
1043
Laszlo Csomorc8cd6bd2017-03-14 11:10:04 +00001044 BOOL result = TerminateProcess(process, /*uExitCode*/ 0);
mschallerfd37b512017-07-11 18:21:36 +02001045 if (!result || !AwaitServerProcessTermination(pid, output_base,
1046 kPostKillGracePeriodSeconds)) {
1047 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +02001048 "Cannot terminate server process with PID %d, output_base=(%s)", pid,
1049 output_base.c_str());
Lukacs Berkid825a3d2016-06-23 11:10:02 +00001050 }
Lukacs Berki119dd4b2016-07-13 15:28:42 +00001051 return result;
Lukacs Berki1977d922016-05-02 09:31:37 +00001052}
1053
mschallerfd37b512017-07-11 18:21:36 +02001054void TrySleep(unsigned int milliseconds) {
1055 Sleep(milliseconds);
1056}
1057
Dave MacLachlan6b747ee2016-07-20 10:00:44 +00001058// Not supported.
1059void ExcludePathFromBackup(const string &path) {
1060}
1061
Laszlo Csomor6bf95762016-11-16 13:29:22 +00001062string GetHashedBaseDir(const string& root, const string& hashable) {
1063 // Builds a shorter output base dir name for Windows.
1064 // This algorithm only uses 1/3 of the bits to get 8-char alphanumeric
1065 // file name.
1066
1067 static const char* alphabet
1068 // Exactly 64 characters.
John Caterb64349e2017-01-05 20:26:48 +00001069 = "abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789_-";
Laszlo Csomor6bf95762016-11-16 13:29:22 +00001070
1071 // The length of the resulting filename (8 characters).
1072 static const int filename_length = blaze_util::Md5Digest::kDigestLength / 2;
1073 unsigned char buf[blaze_util::Md5Digest::kDigestLength];
1074 char coded_name[filename_length + 1];
1075 blaze_util::Md5Digest digest;
1076 digest.Update(hashable.data(), hashable.size());
1077 digest.Finish(buf);
1078 for (int i = 0; i < filename_length; i++) {
1079 coded_name[i] = alphabet[buf[i] & 0x3F];
1080 }
1081 coded_name[filename_length] = '\0';
Laszlo Csomor760f7862016-12-19 15:46:47 +00001082 return blaze_util::JoinPath(root, string(coded_name));
Laszlo Csomor6bf95762016-11-16 13:29:22 +00001083}
1084
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001085void CreateSecureOutputRoot(const string& path) {
Laszlo Csomor5eb3ef92017-02-07 16:52:38 +00001086 // TODO(bazel-team): implement this properly, by mimicing whatever the POSIX
1087 // implementation does.
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001088 const char* root = path.c_str();
Laszlo Csomor5eb3ef92017-02-07 16:52:38 +00001089 if (!blaze_util::MakeDirectories(path, 0755)) {
1090 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +02001091 "MakeDirectories(%s) failed", root);
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001092 }
1093
Laszlo Csomor5eb3ef92017-02-07 16:52:38 +00001094#ifndef COMPILER_MSVC
1095 struct stat fileinfo = {};
1096
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001097 // The path already exists.
1098 // Check ownership and mode, and verify that it is a directory.
1099
1100 if (lstat(root, &fileinfo) < 0) {
1101 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "lstat('%s')", root);
1102 }
1103
1104 if (fileinfo.st_uid != geteuid()) {
1105 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' is not owned by me",
1106 root);
1107 }
1108
Laszlo Csomor5eb3ef92017-02-07 16:52:38 +00001109 // Ensure the permission mask is indeed 0755 (rwxr-xr-x).
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001110 if ((fileinfo.st_mode & 022) != 0) {
1111 int new_mode = fileinfo.st_mode & (~022);
1112 if (chmod(root, new_mode) < 0) {
1113 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1114 "'%s' has mode %o, chmod to %o failed", root,
1115 fileinfo.st_mode & 07777, new_mode);
1116 }
1117 }
Laszlo Csomor5eb3ef92017-02-07 16:52:38 +00001118#endif // not COMPILER_MSVC
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001119
Laszlo Csomor5eb3ef92017-02-07 16:52:38 +00001120 if (!blaze_util::IsDirectory(path)) {
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001121 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' is not a directory",
1122 root);
1123 }
1124
1125 ExcludePathFromBackup(root);
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001126}
1127
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001128string GetEnv(const string& name) {
Laszlo Csomorf2b5f272017-01-18 09:51:57 +00001129 DWORD size = ::GetEnvironmentVariableA(name.c_str(), NULL, 0);
1130 if (size == 0) {
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001131#ifdef COMPILER_MSVC
Laszlo Csomorf2b5f272017-01-18 09:51:57 +00001132 return string(); // unset or empty envvar
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001133#else // not COMPILER_MSVC
Laszlo Csomorf2b5f272017-01-18 09:51:57 +00001134 char* result = getenv(name.c_str());
1135 return result != NULL ? string(result) : string();
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001136#endif // COMPILER_MSVC
Laszlo Csomorf2b5f272017-01-18 09:51:57 +00001137 }
1138
1139 unique_ptr<char[]> value(new char[size]);
1140 ::GetEnvironmentVariableA(name.c_str(), value.get(), size);
1141 return string(value.get());
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001142}
1143
1144void SetEnv(const string& name, const string& value) {
Dmitry Lomov5a0d6ff2017-08-22 14:51:49 +02001145 // _putenv_s both calls ::SetEnvionmentVariableA and updates environ(5).
1146 _putenv_s(name.c_str(), value.c_str());
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001147}
1148
Laszlo Csomorf2b5f272017-01-18 09:51:57 +00001149void UnsetEnv(const string& name) { SetEnv(name, ""); }
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001150
Laszlo Csomorc4fb2182017-07-27 16:37:08 +02001151bool WarnIfStartedFromDesktop() {
1152 // GetConsoleProcessList returns:
1153 // 0, if no console attached (Bazel runs as a subprocess)
1154 // 1, if Bazel was started by clicking on its icon
1155 // 2, if Bazel was started from the command line (even if its output is
1156 // redirected)
1157 DWORD dummy[2] = {0};
1158 if (GetConsoleProcessList(dummy, 2) != 1) {
1159 return false;
1160 }
1161 printf(
1162 "Bazel is a command line tool.\n\n"
1163 "Try opening a console, such as the Windows Command Prompt (cmd.exe) "
1164 "or PowerShell, and running \"bazel help\".\n\n"
1165 "Press Enter to close this window...");
1166 ReadFile(GetStdHandle(STD_INPUT_HANDLE), dummy, 1, dummy, NULL);
1167 return true;
1168}
1169
Laszlo Csomor41ac1e02017-03-20 15:25:44 +00001170#ifndef ENABLE_PROCESSED_OUTPUT
1171// From MSDN about BOOL SetConsoleMode(HANDLE, DWORD).
1172#define ENABLE_PROCESSED_OUTPUT 0x0001
1173#endif // not ENABLE_PROCESSED_OUTPUT
1174
1175#ifndef ENABLE_WRAP_AT_EOL_OUTPUT
1176// From MSDN about BOOL SetConsoleMode(HANDLE, DWORD).
1177#define ENABLE_WRAP_AT_EOL_OUTPUT 0x0002
1178#endif // not ENABLE_WRAP_AT_EOL_OUTPUT
1179
1180#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
1181// From MSDN about BOOL SetConsoleMode(HANDLE, DWORD).
1182#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
1183#endif // not ENABLE_VIRTUAL_TERMINAL_PROCESSING
1184
Laszlo Csomor74ffaf72016-11-24 12:17:20 +00001185void SetupStdStreams() {
1186#ifdef COMPILER_MSVC
Laszlo Csomor26f858c2017-02-08 17:50:10 +00001187 static const DWORD stdhandles[] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
1188 STD_ERROR_HANDLE};
Laszlo Csomor3d97f492017-03-16 12:58:23 +00001189 for (int i = 0; i <= 2; ++i) {
Laszlo Csomor26f858c2017-02-08 17:50:10 +00001190 HANDLE handle = ::GetStdHandle(stdhandles[i]);
1191 if (handle == INVALID_HANDLE_VALUE || handle == NULL) {
1192 // Ensure we have open fds to each std* stream. Otherwise we can end up
1193 // with bizarre things like stdout going to the lock file, etc.
1194 _open("NUL", (i == 0) ? _O_RDONLY : _O_WRONLY);
1195 }
Laszlo Csomor41ac1e02017-03-20 15:25:44 +00001196 DWORD mode = 0;
1197 if (i > 0 && handle != INVALID_HANDLE_VALUE && handle != NULL &&
1198 ::GetConsoleMode(handle, &mode)) {
1199 DWORD newmode = mode | ENABLE_PROCESSED_OUTPUT |
1200 ENABLE_WRAP_AT_EOL_OUTPUT |
1201 ENABLE_VIRTUAL_TERMINAL_PROCESSING;
1202 if (mode != newmode) {
1203 // We don't care about the success of this. Worst that can happen if
1204 // this method fails is that the console won't understand control
1205 // characters like color change or carriage return.
1206 ::SetConsoleMode(handle, newmode);
1207 }
1208 }
Laszlo Csomor26f858c2017-02-08 17:50:10 +00001209 }
Laszlo Csomor74ffaf72016-11-24 12:17:20 +00001210#else // not COMPILER_MSVC
1211 // Set non-buffered output mode for stderr/stdout. The server already
1212 // line-buffers messages where it makes sense, so there's no need to do set
1213 // line-buffering here. On the other hand the server sometimes sends binary
1214 // output (when for example a query returns results as proto), in which case
1215 // we must not perform line buffering on the client side. So turn off
1216 // buffering here completely.
1217 setvbuf(stdout, NULL, _IONBF, 0);
1218 setvbuf(stderr, NULL, _IONBF, 0);
1219
1220 // Ensure we have three open fds. Otherwise we can end up with
1221 // bizarre things like stdout going to the lock file, etc.
1222 if (fcntl(STDIN_FILENO, F_GETFL) == -1) open("/dev/null", O_RDONLY);
1223 if (fcntl(STDOUT_FILENO, F_GETFL) == -1) open("/dev/null", O_WRONLY);
1224 if (fcntl(STDERR_FILENO, F_GETFL) == -1) open("/dev/null", O_WRONLY);
1225#endif // COMPILER_MSVC
1226}
1227
Laszlo Csomora85f52d2016-11-08 13:43:23 +00001228LARGE_INTEGER WindowsClock::GetFrequency() {
1229 LARGE_INTEGER result;
1230 if (!QueryPerformanceFrequency(&result)) {
Laszlo Csomor6d477072017-07-20 10:09:44 +02001231 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1232 "WindowsClock::GetFrequency: QueryPerformanceFrequency");
Laszlo Csomora85f52d2016-11-08 13:43:23 +00001233 }
1234
1235 // On ancient Windows versions (pre-XP) and specific hardware the result may
1236 // be 0. Since this is pre-XP, we don't handle that, just error out.
1237 if (result.QuadPart <= 0) {
Laszlo Csomor6d477072017-07-20 10:09:44 +02001238 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1239 "WindowsClock::GetFrequency: QueryPerformanceFrequency returned "
1240 "invalid result (%llu)\n",
Laszlo Csomora85f52d2016-11-08 13:43:23 +00001241 result.QuadPart);
1242 }
1243
1244 return result;
1245}
1246
1247LARGE_INTEGER WindowsClock::GetMillisecondsAsLargeInt(
1248 const LARGE_INTEGER& freq) {
1249 LARGE_INTEGER counter;
1250 if (!QueryPerformanceCounter(&counter)) {
Laszlo Csomor6d477072017-07-20 10:09:44 +02001251 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1252 "WindowsClock::GetMillisecondsAsLargeInt: QueryPerformanceCounter");
Laszlo Csomora85f52d2016-11-08 13:43:23 +00001253 }
1254
1255 LARGE_INTEGER result;
1256 result.QuadPart =
1257 // seconds
1258 (counter.QuadPart / freq.QuadPart) * 1000LL +
1259 // milliseconds
1260 (((counter.QuadPart % freq.QuadPart) * 1000LL) / freq.QuadPart);
1261
1262 return result;
1263}
1264
1265const WindowsClock WindowsClock::INSTANCE;
1266
1267WindowsClock::WindowsClock()
1268 : kFrequency(GetFrequency()),
1269 kStart(GetMillisecondsAsLargeInt(kFrequency)) {}
1270
1271uint64_t WindowsClock::GetMilliseconds() const {
1272 return GetMillisecondsAsLargeInt(kFrequency).QuadPart;
1273}
1274
1275uint64_t WindowsClock::GetProcessMilliseconds() const {
1276 return GetMilliseconds() - kStart.QuadPart;
1277}
1278
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001279uint64_t AcquireLock(const string& output_base, bool batch_mode, bool block,
1280 BlazeLock* blaze_lock) {
Laszlo Csomor760f7862016-12-19 15:46:47 +00001281 string lockfile = blaze_util::JoinPath(output_base, "lock");
laszlocsomorade79a72017-04-12 10:08:16 +00001282 wstring wlockfile;
laszlocsomorb69acfa2017-07-21 13:18:13 +02001283 if (!blaze_util::AsAbsoluteWindowsPath(lockfile, &wlockfile)) {
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001284 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
laszlocsomorb69acfa2017-07-21 13:18:13 +02001285 "AcquireLock(%s): AsAbsoluteWindowsPath(%s)", output_base.c_str(),
Laszlo Csomor6d477072017-07-20 10:09:44 +02001286 lockfile.c_str());
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001287 }
1288
laszlocsomorade79a72017-04-12 10:08:16 +00001289 blaze_lock->handle = INVALID_HANDLE_VALUE;
1290 bool first_lock_attempt = true;
1291 uint64_t st = GetMillisecondsMonotonic();
1292 while (true) {
1293 blaze_lock->handle = ::CreateFileW(
1294 /* lpFileName */ wlockfile.c_str(),
1295 /* dwDesiredAccess */ GENERIC_READ | GENERIC_WRITE,
1296 /* dwShareMode */ FILE_SHARE_READ,
1297 /* lpSecurityAttributes */ NULL,
1298 /* dwCreationDisposition */ CREATE_ALWAYS,
1299 /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL,
1300 /* hTemplateFile */ NULL);
1301 if (blaze_lock->handle != INVALID_HANDLE_VALUE) {
1302 // We could open the file, so noone else holds a lock on it.
1303 break;
1304 }
1305 if (GetLastError() == ERROR_SHARING_VIOLATION) {
1306 // Someone else has the lock.
1307 if (!block) {
1308 die(blaze_exit_code::BAD_ARGV,
1309 "Another command is running. Exiting immediately.");
1310 }
1311 if (first_lock_attempt) {
1312 first_lock_attempt = false;
1313 fprintf(stderr,
1314 "Another command is running. Waiting for it to complete...");
1315 fflush(stderr);
1316 }
1317 Sleep(/* dwMilliseconds */ 200);
1318 } else {
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001319 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +02001320 "AcquireLock(%s): CreateFileW(%ls)", lockfile.c_str(),
1321 wlockfile.c_str());
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001322 }
1323 }
laszlocsomorade79a72017-04-12 10:08:16 +00001324 uint64_t wait_time = GetMillisecondsMonotonic() - st;
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001325
laszlocsomorade79a72017-04-12 10:08:16 +00001326 // We have the lock.
1327 OVERLAPPED overlapped = {0};
1328 if (!LockFileEx(
1329 /* hFile */ blaze_lock->handle,
1330 /* dwFlags */ LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
1331 /* dwReserved */ 0,
1332 /* nNumberOfBytesToLockLow */ 1,
1333 /* nNumberOfBytesToLockHigh */ 0,
1334 /* lpOverlapped */ &overlapped)) {
1335 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomor6d477072017-07-20 10:09:44 +02001336 "AcquireLock(%s): LockFileEx(%ls)", lockfile.c_str(),
1337 wlockfile.c_str());
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001338 }
laszlocsomorade79a72017-04-12 10:08:16 +00001339 // On other platforms we write some info about this process into the lock file
1340 // such as the server PID. On Windows we don't do that because the file is
1341 // locked exclusively, meaning other processes may not open the file even for
1342 // reading.
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001343
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001344 return wait_time;
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001345}
1346
1347void ReleaseLock(BlazeLock* blaze_lock) {
laszlocsomorade79a72017-04-12 10:08:16 +00001348 OVERLAPPED overlapped = {0};
1349 UnlockFileEx(blaze_lock->handle, 0, 1, 0, &overlapped);
1350 CloseHandle(blaze_lock->handle);
Laszlo Csomor7e5fb402016-11-25 15:11:08 +00001351}
1352
Laszlo Csomor99b01542017-03-02 11:43:21 +00001353#ifdef GetUserName
1354// By including <windows.h>, we have GetUserName defined either as
1355// GetUserNameA or GetUserNameW.
1356#undef GetUserName
1357#endif
1358
Laszlo Csomord0a12692016-11-28 13:35:23 +00001359string GetUserName() {
Laszlo Csomor41ee5912016-12-15 12:26:06 +00001360 WCHAR buffer[UNLEN + 1];
1361 DWORD len = UNLEN + 1;
Laszlo Csomor6d477072017-07-20 10:09:44 +02001362 if (!::GetUserNameW(buffer, &len)) {
1363 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "GetUserNameW");
Laszlo Csomord0a12692016-11-28 13:35:23 +00001364 }
Laszlo Csomor41ee5912016-12-15 12:26:06 +00001365 return string(blaze_util::WstringToCstring(buffer).get());
Laszlo Csomord0a12692016-11-28 13:35:23 +00001366}
1367
1368bool IsEmacsTerminal() {
Laszlo Csomord0a12692016-11-28 13:35:23 +00001369 string emacs = GetEnv("EMACS");
1370 string inside_emacs = GetEnv("INSIDE_EMACS");
1371 // GNU Emacs <25.1 (and ~all non-GNU emacsen) set EMACS=t, but >=25.1 doesn't
1372 // do that and instead sets INSIDE_EMACS=<stuff> (where <stuff> can look like
1373 // e.g. "25.1.1,comint"). So we check both variables for maximum
1374 // compatibility.
1375 return emacs == "t" || !inside_emacs.empty();
Laszlo Csomord0a12692016-11-28 13:35:23 +00001376}
1377
Googlerd290ece2017-07-11 01:23:01 +02001378// Returns true iff both stdout and stderr are connected to a
1379// terminal, and it can support color and cursor movement
1380// (this is computed heuristically based on the values of
1381// environment variables).
1382bool IsStandardTerminal() {
Laszlo Csomord0a12692016-11-28 13:35:23 +00001383#ifdef COMPILER_MSVC
Googlerd290ece2017-07-11 01:23:01 +02001384 for (DWORD i : {STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}) {
1385 DWORD mode = 0;
1386 HANDLE handle = ::GetStdHandle(i);
1387 // handle may be invalid when std{out,err} is redirected
1388 if (handle == INVALID_HANDLE_VALUE || !::GetConsoleMode(handle, &mode) ||
1389 !(mode & ENABLE_PROCESSED_OUTPUT) ||
1390 !(mode & ENABLE_WRAP_AT_EOL_OUTPUT) ||
1391 !(mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
1392 return false;
1393 }
Laszlo Csomor41ac1e02017-03-20 15:25:44 +00001394 }
1395 return true;
Laszlo Csomord0a12692016-11-28 13:35:23 +00001396#else // not COMPILER_MSVC
1397 string term = GetEnv("TERM");
1398 if (term.empty() || term == "dumb" || term == "emacs" ||
1399 term == "xterm-mono" || term == "symbolics" || term == "9term" ||
1400 IsEmacsTerminal()) {
1401 return false;
1402 }
Googlerd290ece2017-07-11 01:23:01 +02001403 return isatty(STDOUT_FILENO) && isatty(STDERR_FILENO);
Laszlo Csomord0a12692016-11-28 13:35:23 +00001404#endif // COMPILER_MSVC
1405}
1406
Googlerd290ece2017-07-11 01:23:01 +02001407// Returns the number of columns of the terminal to which stdout is
1408// connected, or $COLUMNS (default 80) if there is no such terminal.
1409int GetTerminalColumns() {
László Csomor72879212017-02-02 15:55:18 +00001410#ifndef COMPILER_MSVC
Laszlo Csomord0a12692016-11-28 13:35:23 +00001411 struct winsize ws;
Googlerd290ece2017-07-11 01:23:01 +02001412 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) {
Laszlo Csomord0a12692016-11-28 13:35:23 +00001413 return ws.ws_col;
1414 }
László Csomor72879212017-02-02 15:55:18 +00001415#endif // not COMPILER_MSVC
1416
Laszlo Csomord0a12692016-11-28 13:35:23 +00001417 string columns_env = GetEnv("COLUMNS");
1418 if (!columns_env.empty()) {
1419 char* endptr;
1420 int columns = blaze_util::strto32(columns_env.c_str(), &endptr, 10);
1421 if (*endptr == '\0') { // $COLUMNS is a valid number
1422 return columns;
1423 }
1424 }
László Csomor72879212017-02-02 15:55:18 +00001425
Googlerd290ece2017-07-11 01:23:01 +02001426 HANDLE stdout_handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
Laszlo Csomor3d97f492017-03-16 12:58:23 +00001427 if (stdout_handle != INVALID_HANDLE_VALUE) {
1428 // stdout_handle may be invalid when stdout is redirected.
1429 CONSOLE_SCREEN_BUFFER_INFO screen_info;
1430 if (GetConsoleScreenBufferInfo(stdout_handle, &screen_info)) {
1431 int width = 1 + screen_info.srWindow.Right - screen_info.srWindow.Left;
1432 if (width > 1) {
1433 return width;
1434 }
László Csomor72879212017-02-02 15:55:18 +00001435 }
1436 }
László Csomor72879212017-02-02 15:55:18 +00001437
1438 return 80; // default if not a terminal.
Laszlo Csomord0a12692016-11-28 13:35:23 +00001439}
1440
jmmva96369c2017-07-10 18:14:36 +02001441bool UnlimitResources() {
1442 return true; // Nothing to do so assume success.
1443}
1444
Dmitry Lomov891f5402017-07-19 12:26:39 +02001445static const int MAX_KEY_LENGTH = 255;
1446// We do not care about registry values longer than MAX_PATH
1447static const int REG_VALUE_BUFFER_SIZE = MAX_PATH;
1448
1449// Implements heuristics to discover msys2 installation.
1450static string GetMsysBash() {
1451 HKEY h_uninstall;
1452
1453 // MSYS2 installer writes its registry into HKCU, although documentation
1454 // (https://msdn.microsoft.com/en-us/library/ms954376.aspx)
1455 // clearly states that it should go to HKLM.
1456 static const char* const key =
1457 "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
1458 if (RegOpenKeyExA(HKEY_CURRENT_USER, // _In_ HKEY hKey,
1459 key, // _In_opt_ LPCTSTR lpSubKey,
1460 0, // _In_ DWORD ulOptions,
1461 KEY_ENUMERATE_SUB_KEYS |
1462 KEY_QUERY_VALUE, // _In_ REGSAM samDesired,
1463 &h_uninstall // _Out_ PHKEY phkResult
1464 )) {
1465 debug_log("Cannot open HKCU\\%s", key);
1466 return string();
1467 }
1468 AutoHandle auto_uninstall(h_uninstall);
1469
1470 // Since MSYS2 decided to generate a new product key for each installation,
1471 // we enumerate all keys under
1472 // HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall and find the first
1473 // with MSYS2 64bit display name.
1474 static const char* const msys_display_name = "MSYS2 64bit";
1475 DWORD n_subkeys;
1476
1477 if (RegQueryInfoKey(h_uninstall, // _In_ HKEY hKey,
1478 0, // _Out_opt_ LPTSTR lpClass,
1479 0, // _Inout_opt_ LPDWORD lpcClass,
1480 0, // _Reserved_ LPDWORD lpReserved,
1481 &n_subkeys, // _Out_opt_ LPDWORD lpcSubKeys,
1482 0, // _Out_opt_ LPDWORD lpcMaxSubKeyLen,
1483 0, // _Out_opt_ LPDWORD lpcMaxClassLen,
1484 0, // _Out_opt_ LPDWORD lpcValues,
1485 0, // _Out_opt_ LPDWORD lpcMaxValueNameLen,
1486 0, // _Out_opt_ LPDWORD lpcMaxValueLen,
1487 0, // _Out_opt_ LPDWORD lpcbSecurityDescriptor,
1488 0 // _Out_opt_ PFILETIME lpftLastWriteTime
1489 )) {
1490 debug_log("Cannot query HKCU\\%s", key);
1491 return string();
1492 }
1493
1494 for (DWORD key_index = 0; key_index < n_subkeys; key_index++) {
1495 char subkey_name[MAX_KEY_LENGTH];
1496 if (RegEnumKeyA(h_uninstall, // _In_ HKEY hKey,
1497 key_index, // _In_ DWORD dwIndex,
1498 subkey_name, // _Out_ LPTSTR lpName,
1499 sizeof(subkey_name) // _In_ DWORD cchName
1500 )) {
1501 debug_log("Cannot get %d subkey of HKCU\\%s", key_index, key);
1502 continue; // try next subkey
1503 }
1504
1505 HKEY h_subkey;
1506 if (RegOpenKeyEx(h_uninstall, // _In_ HKEY hKey,
1507 subkey_name, // _In_opt_ LPCTSTR lpSubKey,
1508 0, // _In_ DWORD ulOptions,
1509 KEY_QUERY_VALUE, // _In_ REGSAM samDesired,
1510 &h_subkey // _Out_ PHKEY phkResult
1511 )) {
1512 debug_log("Failed to open subkey HKCU\\%s\\%s", key, subkey_name);
1513 continue; // try next subkey
1514 }
1515 AutoHandle auto_subkey(h_subkey);
1516
1517 BYTE value[REG_VALUE_BUFFER_SIZE];
1518 DWORD value_length = sizeof(value);
1519 DWORD value_type;
1520
1521 if (RegQueryValueEx(h_subkey, // _In_ HKEY hKey,
1522 "DisplayName", // _In_opt_ LPCTSTR lpValueName,
1523 0, // _Reserved_ LPDWORD lpReserved,
1524 &value_type, // _Out_opt_ LPDWORD lpType,
1525 value, // _Out_opt_ LPBYTE lpData,
1526 &value_length // _Inout_opt_ LPDWORD lpcbData
1527 )) {
1528 debug_log("Failed to query DisplayName of HKCU\\%s\\%s", key,
1529 subkey_name);
1530 continue; // try next subkey
1531 }
1532
1533 if (value_type == REG_SZ &&
1534 0 == memcmp(msys_display_name, value, sizeof(msys_display_name))) {
1535 debug_log("Getting install location of HKCU\\%s\\%s", key, subkey_name);
1536 BYTE path[REG_VALUE_BUFFER_SIZE];
1537 DWORD path_length = sizeof(path);
1538 DWORD path_type;
1539 if (RegQueryValueEx(
1540 h_subkey, // _In_ HKEY hKey,
1541 "InstallLocation", // _In_opt_ LPCTSTR lpValueName,
1542 0, // _Reserved_ LPDWORD lpReserved,
1543 &path_type, // _Out_opt_ LPDWORD lpType,
1544 path, // _Out_opt_ LPBYTE lpData,
1545 &path_length // _Inout_opt_ LPDWORD lpcbData
1546 )) {
1547 debug_log("Failed to query InstallLocation of HKCU\\%s\\%s", key,
1548 subkey_name);
1549 continue; // try next subkey
1550 }
1551
1552 if (path_length == 0 || path_type != REG_SZ) {
1553 debug_log("Zero-length (%d) install location or wrong type (%d)",
1554 path_length, path_type);
1555 continue; // try next subkey
1556 }
1557
1558 debug_log("Install location of HKCU\\%s\\%s is %s", key, subkey_name,
1559 path);
1560 string path_as_string(path, path + path_length - 1);
1561 string bash_exe = path_as_string + "\\usr\\bin\\bash.exe";
1562 if (!blaze_util::PathExists(bash_exe)) {
1563 debug_log("%s does not exist", bash_exe.c_str());
1564 continue; // try next subkey
1565 }
1566
1567 debug_log("Detected msys bash at %s", bash_exe.c_str());
1568 return bash_exe;
1569 }
1570 }
1571 return string();
1572}
1573
1574// Implements heuristics to discover Git-on-Win installation.
1575static string GetBashFromGitOnWin() {
1576 HKEY h_GitOnWin_uninstall;
1577
1578 // Well-known registry key for Git-on-Windows.
1579 static const char* const key =
1580 "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1";
1581 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, // _In_ HKEY hKey,
1582 key, // _In_opt_ LPCTSTR lpSubKey,
1583 0, // _In_ DWORD ulOptions,
1584 KEY_QUERY_VALUE, // _In_ REGSAM samDesired,
1585 &h_GitOnWin_uninstall // _Out_ PHKEY phkResult
1586 )) {
1587 debug_log("Cannot open HKCU\\%s", key);
1588 return string();
1589 }
1590 AutoHandle auto_h_GitOnWin_uninstall(h_GitOnWin_uninstall);
1591
1592 debug_log("Getting install location of HKLM\\%s", key);
1593 BYTE path[REG_VALUE_BUFFER_SIZE];
1594 DWORD path_length = sizeof(path);
1595 DWORD path_type;
1596 if (RegQueryValueEx(h_GitOnWin_uninstall, // _In_ HKEY hKey,
1597 "InstallLocation", // _In_opt_ LPCTSTR lpValueName,
1598 0, // _Reserved_ LPDWORD lpReserved,
1599 &path_type, // _Out_opt_ LPDWORD lpType,
1600 path, // _Out_opt_ LPBYTE lpData,
1601 &path_length // _Inout_opt_ LPDWORD lpcbData
1602 )) {
1603 debug_log("Failed to query InstallLocation of HKLM\\%s", key);
1604 return string();
1605 }
1606
1607 if (path_length == 0 || path_type != REG_SZ) {
1608 debug_log("Zero-length (%d) install location or wrong type (%d)",
1609 path_length, path_type);
1610 return string();
1611 }
1612
1613 debug_log("Install location of HKLM\\%s is %s", key, path);
1614 string path_as_string(path, path + path_length - 1);
1615 string bash_exe = path_as_string + "\\usr\\bin\\bash.exe";
1616 if (!blaze_util::PathExists(bash_exe)) {
1617 debug_log("%s does not exist", bash_exe.c_str());
1618 return string();
1619 }
1620
dslomov89701822017-07-20 10:02:27 +02001621 debug_log("Detected git-on-Windows bash at %s", bash_exe.c_str());
Dmitry Lomov891f5402017-07-19 12:26:39 +02001622 return bash_exe;
1623}
1624
1625static string GetBashFromPath() {
1626 char found[MAX_PATH];
1627 string path_list = blaze::GetEnv("PATH");
1628
1629 // We do not fully replicate all the quirks of search in PATH.
1630 // There is no system function to do so, and that way lies madness.
1631 size_t start = 0;
1632 do {
1633 // This ignores possibly quoted semicolons in PATH etc.
1634 size_t end = path_list.find_first_of(";", start);
1635 string path = path_list.substr(
1636 start, end != string::npos ? end - start : string::npos);
1637 // Handle one typical way of quoting (where.exe does not handle this, but
1638 // CreateProcess does).
1639 if (path.size() > 1 && path[0] == '"' && path[path.size() - 1] == '"') {
1640 path = path.substr(1, path.size() - 2);
1641 }
1642 if (SearchPathA(path.c_str(), // _In_opt_ LPCTSTR lpPath,
1643 "bash.exe", // _In_ LPCTSTR lpFileName,
1644 0, // LPCTSTR lpExtension,
1645 sizeof(found), // DWORD nBufferLength,
1646 found, // _Out_ LPTSTR lpBuffer,
1647 0 // _Out_opt_ LPTSTR *lpFilePart
1648 )) {
1649 debug_log("bash.exe found on PATH: %s", found);
1650 return string(found);
1651 }
1652 if (end == string::npos) {
1653 break;
1654 }
1655 start = end + 1;
1656 } while (true);
1657
1658 debug_log("bash.exe not found on PATH");
1659 return string();
1660}
1661
1662static string LocateBash() {
1663 string msys_bash = GetMsysBash();
1664 if (!msys_bash.empty()) {
1665 return msys_bash;
1666 }
1667
1668 string git_on_win_bash = GetBashFromGitOnWin();
1669 if (!git_on_win_bash.empty()) {
1670 return git_on_win_bash;
1671 }
1672
1673 return GetBashFromPath();
1674}
1675
1676void DetectBashOrDie() {
1677 if (!blaze::GetEnv("BAZEL_SH").empty()) return;
1678
1679 uint64_t start = blaze::GetMillisecondsMonotonic();
1680
1681 string bash = LocateBash();
1682 uint64_t end = blaze::GetMillisecondsMonotonic();
Dmitry Lomov5a0d6ff2017-08-22 14:51:49 +02001683 debug_log("BAZEL_SH detection took %lu msec, found %s", end - start,
1684 bash.c_str());
Dmitry Lomov891f5402017-07-19 12:26:39 +02001685
1686 if (!bash.empty()) {
Dmitry Lomov5a0d6ff2017-08-22 14:51:49 +02001687 // Set process environment variable.
Dmitry Lomov891f5402017-07-19 12:26:39 +02001688 blaze::SetEnv("BAZEL_SH", bash);
1689 } else {
1690 printf(
1691 "Bazel on Windows requires bash.exe and other Unix tools, but we could "
1692 "not find them.\n"
1693 "If you do not have them installed, the easiest is to install MSYS2 "
1694 "from\n"
1695 " http://repo.msys2.org/distrib/msys2-x86_64-latest.exe\n"
1696 "or git-on-Windows from\n"
1697 " https://git-scm.com/download/win\n"
1698 "\n"
1699 "If you already have bash.exe installed but Bazel cannot find it,\n"
dslomov89701822017-07-20 10:02:27 +02001700 "set BAZEL_SH environment variable to its location:\n"
Dmitry Lomov891f5402017-07-19 12:26:39 +02001701 " set BAZEL_SH=c:\\path\\to\\bash.exe\n");
1702 exit(1);
1703 }
1704}
1705
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001706} // namespace blaze