| // Copyright 2014 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. | |
| #ifndef WIN32_LEAN_AND_MEAN | |
| #define WIN32_LEAN_AND_MEAN | |
| #endif | |
| #include <windows.h> | |
| #include <fcntl.h> | |
| #include <io.h> // _open | |
| #include <knownfolders.h> // FOLDERID_Profile | |
| #include <lmcons.h> // UNLEN | |
| #include <objbase.h> // CoTaskMemFree | |
| #include <shlobj.h> // SHGetKnownFolderPath | |
| #include <stdarg.h> // va_start, va_end, va_list | |
| #include <algorithm> | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include <memory> | |
| #include <mutex> // NOLINT | |
| #include <set> | |
| #include <sstream> | |
| #include <thread> // NOLINT (to silence Google-internal linter) | |
| #include <type_traits> // static_assert | |
| #include <vector> | |
| #include "src/main/cpp/blaze_util.h" | |
| #include "src/main/cpp/blaze_util_platform.h" | |
| #include "src/main/cpp/startup_options.h" | |
| #include "src/main/cpp/util/errors.h" | |
| #include "src/main/cpp/util/exit_code.h" | |
| #include "src/main/cpp/util/file.h" | |
| #include "src/main/cpp/util/file_platform.h" | |
| #include "src/main/cpp/util/logging.h" | |
| #include "src/main/cpp/util/md5.h" | |
| #include "src/main/cpp/util/numbers.h" | |
| #include "src/main/cpp/util/path.h" | |
| #include "src/main/cpp/util/path_platform.h" | |
| #include "src/main/cpp/util/strings.h" | |
| #include "src/main/native/windows/file.h" | |
| #include "src/main/native/windows/process.h" | |
| #include "src/main/native/windows/util.h" | |
| namespace blaze { | |
| // Ensure we can safely cast (const) wchar_t* to LP(C)WSTR. | |
| // This is true with MSVC but usually not with GCC. | |
| static_assert(sizeof(wchar_t) == sizeof(WCHAR), | |
| "wchar_t and WCHAR should be the same size"); | |
| // When using widechar Win32 API functions the maximum path length is 32K. | |
| // Add 4 characters for potential UNC prefix and a couple more for safety. | |
| static const size_t kWindowsPathBufferSize = 0x8010; | |
| using bazel::windows::AutoAttributeList; | |
| using bazel::windows::AutoHandle; | |
| using bazel::windows::CreateJunction; | |
| using bazel::windows::CreateJunctionResult; | |
| // TODO(bazel-team): stop using BAZEL_DIE, handle errors on the caller side. | |
| // BAZEL_DIE calls exit(exitcode), which makes it difficult to follow the | |
| // control flow and does not call destructors on local variables on the call | |
| // stack. | |
| using blaze_util::GetLastErrorString; | |
| using std::string; | |
| using std::unique_ptr; | |
| using std::wstring; | |
| namespace embedded_binaries { | |
| class WindowsDumper : public Dumper { | |
| public: | |
| static WindowsDumper* Create(string* error); | |
| ~WindowsDumper() { Finish(nullptr); } | |
| void Dump(const void* data, const size_t size, const string& path) override; | |
| bool Finish(string* error) override; | |
| private: | |
| WindowsDumper() : threadpool_(nullptr), cleanup_group_(nullptr) {} | |
| PTP_POOL threadpool_; | |
| PTP_CLEANUP_GROUP cleanup_group_; | |
| TP_CALLBACK_ENVIRON threadpool_env_; | |
| std::mutex dir_cache_lock_; | |
| std::set<string> dir_cache_; | |
| std::mutex error_lock_; | |
| string error_msg_; | |
| }; | |
| namespace { | |
| class DumpContext { | |
| public: | |
| DumpContext(unique_ptr<uint8_t[]> data, const size_t size, const string path, | |
| std::mutex* dir_cache_lock, std::set<string>* dir_cache, | |
| std::mutex* error_lock_, string* error_msg); | |
| void Run(); | |
| private: | |
| void MaybeSignalError(const string& msg); | |
| unique_ptr<uint8_t[]> data_; | |
| const size_t size_; | |
| const string path_; | |
| std::mutex* dir_cache_lock_; | |
| std::set<string>* dir_cache_; | |
| std::mutex* error_lock_; | |
| string* error_msg_; | |
| }; | |
| VOID CALLBACK WorkCallback(_Inout_ PTP_CALLBACK_INSTANCE Instance, | |
| _Inout_opt_ PVOID Context, _Inout_ PTP_WORK Work); | |
| } // namespace | |
| Dumper* Create(string* error) { return WindowsDumper::Create(error); } | |
| WindowsDumper* WindowsDumper::Create(string* error) { | |
| unique_ptr<WindowsDumper> result(new WindowsDumper()); | |
| result->threadpool_ = CreateThreadpool(nullptr); | |
| if (result->threadpool_ == nullptr) { | |
| if (error) { | |
| string msg = GetLastErrorString(); | |
| *error = "CreateThreadpool failed: " + msg; | |
| } | |
| return nullptr; | |
| } | |
| result->cleanup_group_ = CreateThreadpoolCleanupGroup(); | |
| if (result->cleanup_group_ == nullptr) { | |
| string msg = GetLastErrorString(); | |
| CloseThreadpool(result->threadpool_); | |
| if (error) { | |
| string msg = GetLastErrorString(); | |
| *error = "CreateThreadpoolCleanupGroup failed: " + msg; | |
| } | |
| return nullptr; | |
| } | |
| // I (@laszlocsomor) experimented with different thread counts and found that | |
| // 8 threads provide a significant advantage over 1 thread, but adding more | |
| // threads provides only marginal speedup. | |
| SetThreadpoolThreadMaximum(result->threadpool_, 16); | |
| SetThreadpoolThreadMinimum(result->threadpool_, 8); | |
| InitializeThreadpoolEnvironment(&result->threadpool_env_); | |
| SetThreadpoolCallbackPool(&result->threadpool_env_, result->threadpool_); | |
| SetThreadpoolCallbackCleanupGroup(&result->threadpool_env_, | |
| result->cleanup_group_, nullptr); | |
| return result.release(); // release pointer ownership | |
| } | |
| void WindowsDumper::Dump(const void* data, const size_t size, | |
| const string& path) { | |
| { | |
| std::lock_guard<std::mutex> g(error_lock_); | |
| if (!error_msg_.empty()) { | |
| return; | |
| } | |
| } | |
| unique_ptr<uint8_t[]> data_copy(new uint8_t[size]); | |
| memcpy(data_copy.get(), data, size); | |
| unique_ptr<DumpContext> ctx(new DumpContext(std::move(data_copy), size, path, | |
| &dir_cache_lock_, &dir_cache_, | |
| &error_lock_, &error_msg_)); | |
| PTP_WORK w = CreateThreadpoolWork(WorkCallback, ctx.get(), &threadpool_env_); | |
| if (w == nullptr) { | |
| string err = GetLastErrorString(); | |
| err = string("WindowsDumper::Dump() couldn't submit work: ") + err; | |
| std::lock_guard<std::mutex> g(error_lock_); | |
| error_msg_ = err; | |
| } else { | |
| ctx.release(); // release pointer ownership | |
| SubmitThreadpoolWork(w); | |
| } | |
| } | |
| bool WindowsDumper::Finish(string* error) { | |
| if (threadpool_ == nullptr) { | |
| return true; | |
| } | |
| CloseThreadpoolCleanupGroupMembers(cleanup_group_, FALSE, nullptr); | |
| CloseThreadpoolCleanupGroup(cleanup_group_); | |
| CloseThreadpool(threadpool_); | |
| threadpool_ = nullptr; | |
| cleanup_group_ = nullptr; | |
| std::lock_guard<std::mutex> g(error_lock_); | |
| if (!error_msg_.empty() && error) { | |
| *error = error_msg_; | |
| } | |
| return error_msg_.empty(); | |
| } | |
| namespace { | |
| DumpContext::DumpContext(unique_ptr<uint8_t[]> data, const size_t size, | |
| const string path, std::mutex* dir_cache_lock, | |
| std::set<string>* dir_cache, std::mutex* error_lock_, | |
| string* error_msg) | |
| : data_(std::move(data)), | |
| size_(size), | |
| path_(path), | |
| dir_cache_lock_(dir_cache_lock), | |
| dir_cache_(dir_cache), | |
| error_msg_(error_msg) {} | |
| void DumpContext::Run() { | |
| string dirname = blaze_util::Dirname(path_); | |
| bool success = true; | |
| // Performance optimization: memoize the paths we already created a | |
| // directory for, to spare a stat in attempting to recreate an already | |
| // existing directory. This optimization alone shaves off seconds from the | |
| // extraction time on Windows. | |
| { | |
| std::lock_guard<std::mutex> guard(*dir_cache_lock_); | |
| if (dir_cache_->insert(dirname).second) { | |
| success = blaze_util::MakeDirectories(dirname, 0777); | |
| } | |
| } | |
| if (!success) { | |
| MaybeSignalError(string("Couldn't create directory '") + dirname + "'"); | |
| return; | |
| } | |
| if (!blaze_util::WriteFile(data_.get(), size_, path_, 0755)) { | |
| MaybeSignalError(string("Failed to write zipped file '") + path_ + "'"); | |
| } | |
| } | |
| void DumpContext::MaybeSignalError(const string& msg) { | |
| std::lock_guard<std::mutex> g(*error_lock_); | |
| *error_msg_ = msg; | |
| } | |
| VOID CALLBACK WorkCallback(_Inout_ PTP_CALLBACK_INSTANCE Instance, | |
| _Inout_opt_ PVOID Context, _Inout_ PTP_WORK Work) { | |
| unique_ptr<DumpContext> ctx(reinterpret_cast<DumpContext*>(Context)); | |
| ctx->Run(); | |
| } | |
| } // namespace | |
| } // namespace embedded_binaries | |
| SignalHandler SignalHandler::INSTANCE; | |
| class WindowsClock { | |
| public: | |
| uint64_t GetMilliseconds() const; | |
| static const WindowsClock INSTANCE; | |
| private: | |
| // Clock frequency per seconds. | |
| // It's safe to cache this because (from QueryPerformanceFrequency on MSDN): | |
| // "The frequency of the performance counter is fixed at system boot and is | |
| // consistent across all processors. Therefore, the frequency need only be | |
| // queried upon application initialization, and the result can be cached." | |
| const LARGE_INTEGER kFrequency; | |
| WindowsClock(); | |
| static LARGE_INTEGER GetFrequency(); | |
| static LARGE_INTEGER GetMillisecondsAsLargeInt(const LARGE_INTEGER& freq); | |
| }; | |
| BOOL WINAPI ConsoleCtrlHandler(_In_ DWORD ctrlType) { | |
| static volatile int sigint_count = 0; | |
| switch (ctrlType) { | |
| case CTRL_C_EVENT: | |
| case CTRL_BREAK_EVENT: | |
| if (++sigint_count >= 3) { | |
| SigPrintf( | |
| "\n%s caught third Ctrl+C handler signal; killed.\n\n", | |
| SignalHandler::Get().GetProductName().c_str()); | |
| if (SignalHandler::Get().GetServerProcessInfo()->server_pid_ != -1) { | |
| KillServerProcess( | |
| SignalHandler::Get().GetServerProcessInfo()->server_pid_, | |
| SignalHandler::Get().GetOutputBase()); | |
| } | |
| _exit(1); | |
| } | |
| SigPrintf( | |
| "\n%s Ctrl+C handler; shutting down.\n\n", | |
| SignalHandler::Get().GetProductName().c_str()); | |
| SignalHandler::Get().CancelServer(); | |
| return TRUE; | |
| case CTRL_CLOSE_EVENT: | |
| SignalHandler::Get().CancelServer(); | |
| return TRUE; | |
| } | |
| return false; | |
| } | |
| void SignalHandler::Install(const string& product_name, | |
| const blaze_util::Path& output_base, | |
| const ServerProcessInfo* server_process_info_, | |
| SignalHandler::Callback cancel_server) { | |
| product_name_ = product_name; | |
| output_base_ = output_base; | |
| server_process_info_ = server_process_info_; | |
| cancel_server_ = cancel_server; | |
| ::SetConsoleCtrlHandler(&ConsoleCtrlHandler, TRUE); | |
| } | |
| ATTRIBUTE_NORETURN void SignalHandler::PropagateSignalOrExit(int exit_code) { | |
| // We do not handle signals on Windows; always exit with exit_code. | |
| exit(exit_code); | |
| } | |
| // A signal-safe version of fprintf(stderr, ...). | |
| // | |
| // WARNING: any output from the blaze client may be interleaved | |
| // with output from the blaze server. In --curses mode, | |
| // the Blaze server often erases the previous line of output. | |
| // So, be sure to end each such message with TWO newlines, | |
| // otherwise it may be erased by the next message from the | |
| // Blaze server. | |
| // Also, it's a good idea to start each message with a newline, | |
| // in case the Blaze server has written a partial line. | |
| void SigPrintf(const char *format, ...) { | |
| int stderr_fileno = _fileno(stderr); | |
| char buf[1024]; | |
| va_list ap; | |
| va_start(ap, format); | |
| int r = vsnprintf(buf, sizeof buf, format, ap); | |
| va_end(ap); | |
| if (write(stderr_fileno, buf, r) <= 0) { | |
| // We don't care, just placate the compiler. | |
| } | |
| } | |
| static void PrintErrorW(const wstring& op) { | |
| DWORD last_error = ::GetLastError(); | |
| if (last_error == 0) { | |
| return; | |
| } | |
| WCHAR* message_buffer; | |
| FormatMessageW( | |
| /* dwFlags */ FORMAT_MESSAGE_ALLOCATE_BUFFER | | |
| FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | |
| /* lpSource */ nullptr, | |
| /* dwMessageId */ last_error, | |
| /* dwLanguageId */ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | |
| /* lpBuffer */ message_buffer, | |
| /* nSize */ 0, | |
| /* Arguments */ nullptr); | |
| fwprintf(stderr, L"ERROR: %s: %s (%d)\n", op.c_str(), message_buffer, | |
| last_error); | |
| LocalFree(message_buffer); | |
| } | |
| void WarnFilesystemType(const blaze_util::Path& output_base) {} | |
| string GetProcessIdAsString() { | |
| return blaze_util::ToString(GetCurrentProcessId()); | |
| } | |
| string GetSelfPath(const char* argv0) { | |
| WCHAR buffer[kWindowsPathBufferSize] = {0}; | |
| if (!GetModuleFileNameW(0, buffer, kWindowsPathBufferSize)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "GetSelfPath: GetModuleFileNameW: " << GetLastErrorString(); | |
| } | |
| return blaze_util::WstringToCstring(buffer); | |
| } | |
| string GetOutputRoot() { | |
| string home = GetHomeDir(); | |
| if (home.empty()) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "Cannot find a good output root.\n" | |
| "Set the USERPROFILE or the HOME environment variable.\n" | |
| "Example (in cmd.exe):\n" | |
| " set USERPROFILE=c:\\_bazel\\<YOUR-USERNAME>\n" | |
| "or:\n" | |
| " set HOME=c:\\_bazel\\<YOUR-USERNAME>"; | |
| } | |
| return home; | |
| } | |
| string GetHomeDir() { | |
| // Check HOME, for sake of consistency with Linux / macOS. This is only set | |
| // under MSYS2, or potentially in tests. | |
| string home = GetPathEnv("HOME"); | |
| if (IsRunningWithinTest() || !home.empty()) { | |
| // Bazel is running inside of a test. Respect $HOME that the test setup has | |
| // set, even if it's empty. | |
| return home; | |
| } | |
| // Check USERPROFILE before calling SHGetKnownFolderPath. Doing so allows the | |
| // user to customize (or override) the home directory. | |
| // See https://github.com/bazelbuild/bazel/issues/7819#issuecomment-533050947 | |
| string userprofile = GetPathEnv("USERPROFILE"); | |
| if (!userprofile.empty()) { | |
| return userprofile; | |
| } | |
| PWSTR wpath; | |
| // Look up the user's home directory. The default value of "FOLDERID_Profile" | |
| // is the same as %USERPROFILE%, but it does not require the envvar to be set. | |
| // On Windows 2016 Server, Nano server: FOLDERID_Profile is unknown but | |
| // %USERPROFILE% is set. See https://github.com/bazelbuild/bazel/issues/6701 | |
| if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_Profile, KF_FLAG_DEFAULT, | |
| nullptr, &wpath))) { | |
| string result = blaze_util::WstringToCstring(wpath); | |
| ::CoTaskMemFree(wpath); | |
| return result; | |
| } | |
| return ""; | |
| } | |
| string FindSystemWideBlazerc() { | |
| // TODO(bazel-team): figure out a good path to return here. | |
| return ""; | |
| } | |
| string GetJavaBinaryUnderJavabase() { return "bin/java.exe"; } | |
| uint64_t GetMillisecondsMonotonic() { | |
| return WindowsClock::INSTANCE.GetMilliseconds(); | |
| } | |
| void SetScheduling(bool batch_cpu_scheduling, int io_nice_level) { | |
| // TODO(bazel-team): There should be a similar function on Windows. | |
| } | |
| std::unique_ptr<blaze_util::Path> GetProcessCWD(int pid) { | |
| // TODO(bazel-team) 2016-11-18: decide whether we need this on Windows and | |
| // implement or delete. | |
| return nullptr; | |
| } | |
| bool IsSharedLibrary(const string &filename) { | |
| return blaze_util::ends_with(filename, ".dll"); | |
| } | |
| string GetSystemJavabase() { | |
| string javahome(GetPathEnv("JAVA_HOME")); | |
| if (!javahome.empty()) { | |
| string javac = blaze_util::JoinPath(javahome, "bin/javac.exe"); | |
| if (blaze_util::PathExists(javac.c_str())) { | |
| return javahome; | |
| } | |
| BAZEL_LOG(WARNING) | |
| << "Ignoring JAVA_HOME, because it must point to a JDK, not a JRE."; | |
| } | |
| return ""; | |
| } | |
| namespace { | |
| // Max command line length is per CreateProcess documentation | |
| // (https://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx) | |
| static const int MAX_CMDLINE_LENGTH = 32768; | |
| struct CmdLine { | |
| WCHAR cmdline[MAX_CMDLINE_LENGTH]; | |
| }; | |
| static void CreateCommandLine(CmdLine* result, const blaze_util::Path& exe, | |
| const std::vector<std::wstring>& wargs_vector) { | |
| std::wstringstream cmdline; | |
| string short_exe; | |
| if (!exe.IsEmpty()) { | |
| string error; | |
| wstring wshort_exe; | |
| if (!blaze_util::AsShortWindowsPath(exe.AsNativePath(), &wshort_exe, | |
| &error)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "CreateCommandLine: AsShortWindowsPath(" << exe.AsPrintablePath() | |
| << "): " << error; | |
| } | |
| cmdline << L'\"' << wshort_exe << L'\"'; | |
| } | |
| bool first = true; | |
| for (const std::wstring& wa : wargs_vector) { | |
| if (first) { | |
| // Skip first argument, it is equal to 'exe'. | |
| first = false; | |
| continue; | |
| } else { | |
| cmdline << L' '; | |
| } | |
| cmdline << wa; | |
| } | |
| wstring cmdline_str = cmdline.str(); | |
| if (cmdline_str.size() >= MAX_CMDLINE_LENGTH) { | |
| BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) | |
| << "Command line too long (" << cmdline_str.size() << " > " | |
| << MAX_CMDLINE_LENGTH | |
| << "): " << blaze_util::WstringToCstring(cmdline_str); | |
| } | |
| // Copy command line into a mutable buffer. | |
| // CreateProcess is allowed to mutate its command line argument. | |
| wcsncpy(result->cmdline, cmdline_str.c_str(), MAX_CMDLINE_LENGTH - 1); | |
| result->cmdline[MAX_CMDLINE_LENGTH - 1] = 0; | |
| } | |
| } // namespace | |
| static bool GetProcessStartupTime(HANDLE process, uint64_t* result) { | |
| FILETIME creation_time, dummy1, dummy2, dummy3; | |
| // GetProcessTimes cannot handle NULL arguments. | |
| if (process == INVALID_HANDLE_VALUE || | |
| !::GetProcessTimes(process, &creation_time, &dummy1, &dummy2, &dummy3)) { | |
| return false; | |
| } | |
| *result = static_cast<uint64_t>(creation_time.dwHighDateTime) << 32 | | |
| creation_time.dwLowDateTime; | |
| return true; | |
| } | |
| static void WriteProcessStartupTime(const blaze_util::Path& server_dir, | |
| HANDLE process) { | |
| uint64_t start_time = 0; | |
| if (!GetProcessStartupTime(process, &start_time)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "WriteProcessStartupTime(" << server_dir.AsPrintablePath() | |
| << "): GetProcessStartupTime failed: " << GetLastErrorString(); | |
| } | |
| blaze_util::Path start_time_file = server_dir.GetRelative("server.starttime"); | |
| if (!blaze_util::WriteFile(blaze_util::ToString(start_time), | |
| start_time_file)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "WriteProcessStartupTime(" << server_dir.AsPrintablePath() | |
| << "): WriteFile(" << start_time_file.AsPrintablePath() | |
| << ") failed: " << GetLastErrorString(); | |
| } | |
| } | |
| static HANDLE CreateJvmOutputFile(const blaze_util::Path& path, | |
| LPSECURITY_ATTRIBUTES sa, | |
| bool daemon_out_append) { | |
| // If the previous server process was asked to be shut down (but not killed), | |
| // it takes a while for it to comply, so wait until the JVM output file that | |
| // it held open is closed. There seems to be no better way to wait for a file | |
| // to be closed on Windows. | |
| static const unsigned int timeout_sec = 60; | |
| for (unsigned int waited = 0; waited < timeout_sec; ++waited) { | |
| HANDLE handle = ::CreateFileW( | |
| /* lpFileName */ path.AsNativePath().c_str(), | |
| /* dwDesiredAccess */ GENERIC_READ | GENERIC_WRITE, | |
| /* dwShareMode */ FILE_SHARE_READ, | |
| /* lpSecurityAttributes */ sa, | |
| /* dwCreationDisposition */ | |
| daemon_out_append ? OPEN_ALWAYS : CREATE_ALWAYS, | |
| /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, | |
| /* hTemplateFile */ nullptr); | |
| if (handle != INVALID_HANDLE_VALUE) { | |
| if (daemon_out_append && | |
| !SetFilePointerEx(handle, {0}, nullptr, FILE_END)) { | |
| fprintf(stderr, "Could not seek to end of file (%s)\n", | |
| path.AsPrintablePath().c_str()); | |
| return INVALID_HANDLE_VALUE; | |
| } | |
| return handle; | |
| } | |
| if (GetLastError() != ERROR_SHARING_VIOLATION && | |
| GetLastError() != ERROR_LOCK_VIOLATION) { | |
| // Some other error occurred than the file being open; bail out. | |
| break; | |
| } | |
| // The file is still held open, the server is shutting down. There's a | |
| // chance that another process holds it open, we don't know; in that case | |
| // we just exit after the timeout expires. | |
| if (waited == 5 || waited == 10 || waited == 30) { | |
| fprintf(stderr, | |
| "Waiting for previous Bazel server's log file to close " | |
| "(waited %d seconds, waiting at most %d)\n", | |
| waited, timeout_sec); | |
| } | |
| Sleep(1000); | |
| } | |
| return INVALID_HANDLE_VALUE; | |
| } | |
| class ProcessHandleBlazeServerStartup : public BlazeServerStartup { | |
| public: | |
| ProcessHandleBlazeServerStartup(HANDLE _proc) : proc(_proc) {} | |
| bool IsStillAlive() override { | |
| FILETIME dummy1, exit_time, dummy2, dummy3; | |
| return GetProcessTimes(proc, &dummy1, &exit_time, &dummy2, &dummy3) && | |
| exit_time.dwHighDateTime == 0 && exit_time.dwLowDateTime == 0; | |
| } | |
| private: | |
| AutoHandle proc; | |
| }; | |
| int ExecuteDaemon(const blaze_util::Path& exe, | |
| const std::vector<string>& args_vector, | |
| const std::map<string, EnvVarValue>& env, | |
| const blaze_util::Path& daemon_output, | |
| const bool daemon_out_append, const string& binaries_dir, | |
| const blaze_util::Path& server_dir, | |
| const StartupOptions& options, | |
| BlazeServerStartup** server_startup) { | |
| SECURITY_ATTRIBUTES inheritable_handle_sa = {sizeof(SECURITY_ATTRIBUTES), | |
| nullptr, TRUE}; | |
| AutoHandle devnull(::CreateFileW( | |
| L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, | |
| &inheritable_handle_sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); | |
| if (!devnull.IsValid()) { | |
| std::string error = GetLastErrorString(); | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "ExecuteDaemon(" << exe.AsPrintablePath() | |
| << "): CreateFileA(NUL) failed: " << error; | |
| } | |
| AutoHandle stdout_file(CreateJvmOutputFile( | |
| daemon_output, &inheritable_handle_sa, daemon_out_append)); | |
| if (!stdout_file.IsValid()) { | |
| std::string error = GetLastErrorString(); | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "ExecuteDaemon(" << exe.AsPrintablePath() | |
| << "): CreateJvmOutputFile(" << daemon_output.AsPrintablePath() | |
| << ") failed: " << error; | |
| } | |
| HANDLE stderr_handle; | |
| // We must duplicate the handle to stdout, otherwise "bazel clean --expunge" | |
| // won't work, because when it tries to close stdout then stderr, the former | |
| // will succeed but the latter will appear to be valid yet still fail to | |
| // close. | |
| if (!DuplicateHandle( | |
| /* hSourceProcessHandle */ GetCurrentProcess(), | |
| /* hSourceHandle */ stdout_file, | |
| /* hTargetProcessHandle */ GetCurrentProcess(), | |
| /* lpTargetHandle */ &stderr_handle, | |
| /* dwDesiredAccess */ 0, | |
| /* bInheritHandle */ TRUE, | |
| /* dwOptions */ DUPLICATE_SAME_ACCESS)) { | |
| std::string error = GetLastErrorString(); | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "ExecuteDaemon(" << exe.AsPrintablePath() << "): DuplicateHandle(" | |
| << daemon_output.AsPrintablePath() << ") failed: " << error; | |
| } | |
| AutoHandle stderr_file(stderr_handle); | |
| // Create an attribute list. | |
| wstring werror; | |
| std::unique_ptr<AutoAttributeList> lpAttributeList; | |
| if (!AutoAttributeList::Create(devnull, stdout_file, stderr_handle, | |
| &lpAttributeList, &werror)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "ExecuteDaemon(" << exe.AsPrintablePath() | |
| << "): attribute list creation failed: " | |
| << blaze_util::WstringToCstring(werror); | |
| } | |
| PROCESS_INFORMATION processInfo = {0}; | |
| STARTUPINFOEXW startupInfoEx = {0}; | |
| lpAttributeList->InitStartupInfoExW(&startupInfoEx); | |
| std::vector<std::wstring> wesc_args_vector; | |
| wesc_args_vector.reserve(args_vector.size()); | |
| for (const string& a : args_vector) { | |
| std::wstring wa = blaze_util::CstringToWstring(a); | |
| std::wstring wesc = bazel::windows::WindowsEscapeArg(wa); | |
| wesc_args_vector.push_back(wesc); | |
| } | |
| CmdLine cmdline; | |
| CreateCommandLine(&cmdline, exe, wesc_args_vector); | |
| BOOL ok; | |
| { | |
| WithEnvVars env_obj(env); | |
| ok = CreateProcessW( | |
| /* lpApplicationName */ nullptr, | |
| /* lpCommandLine */ cmdline.cmdline, | |
| /* lpProcessAttributes */ nullptr, | |
| /* lpThreadAttributes */ nullptr, | |
| /* bInheritHandles */ TRUE, | |
| /* dwCreationFlags */ DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | | |
| EXTENDED_STARTUPINFO_PRESENT, | |
| /* lpEnvironment */ nullptr, | |
| /* lpCurrentDirectory */ nullptr, | |
| /* lpStartupInfo */ &startupInfoEx.StartupInfo, | |
| /* lpProcessInformation */ &processInfo); | |
| } | |
| if (!ok) { | |
| string err = GetLastErrorString(); | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "ExecuteDaemon(" << exe.AsPrintablePath() << "): CreateProcess(" | |
| << cmdline.cmdline << ") failed: " << err; | |
| } | |
| WriteProcessStartupTime(server_dir, processInfo.hProcess); | |
| // Pass ownership of processInfo.hProcess | |
| *server_startup = new ProcessHandleBlazeServerStartup(processInfo.hProcess); | |
| string pid_string = blaze_util::ToString(processInfo.dwProcessId); | |
| blaze_util::Path pid_file = server_dir.GetRelative(kServerPidFile); | |
| if (!blaze_util::WriteFile(pid_string, pid_file)) { | |
| // Not a lot we can do if this fails | |
| fprintf(stderr, "Cannot write PID file %s\n", | |
| pid_file.AsPrintablePath().c_str()); | |
| } | |
| // Don't close processInfo.hProcess here, it's now owned by the | |
| // ProcessHandleBlazeServerStartup instance. | |
| CloseHandle(processInfo.hThread); | |
| return processInfo.dwProcessId; | |
| } | |
| // Run the given program in the current working directory, using the given | |
| // argument vector, wait for it to finish, then exit ourselves with the exitcode | |
| // of that program. | |
| ATTRIBUTE_NORETURN static void ExecuteProgram( | |
| const blaze_util::Path& exe, | |
| const std::vector<std::wstring>& wargs_vector) { | |
| CmdLine cmdline; | |
| CreateCommandLine(&cmdline, blaze_util::Path(), wargs_vector); | |
| bazel::windows::WaitableProcess proc; | |
| std::wstring werror; | |
| // TODO(laszlocsomor): Fix proc.Create to accept paths with UNC prefix. | |
| if (!proc.Create(blaze_util::RemoveUncPrefixMaybe(exe.AsNativePath().c_str()), | |
| cmdline.cmdline, nullptr, L"", &werror) || | |
| proc.WaitFor(-1, nullptr, &werror) != | |
| bazel::windows::WaitableProcess::kWaitSuccess) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "ExecuteProgram(" << exe.AsPrintablePath() | |
| << ") failed: " << blaze_util::WstringToCstring(werror); | |
| } | |
| werror.clear(); | |
| int x = proc.GetExitCode(&werror); | |
| if (!werror.empty()) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "ExecuteProgram(" << exe.AsPrintablePath() | |
| << ") failed: " << blaze_util::WstringToCstring(werror); | |
| } | |
| exit(x); | |
| } | |
| void ExecuteServerJvm(const blaze_util::Path& exe, | |
| const std::vector<string>& server_jvm_args) { | |
| std::vector<std::wstring> wargs; | |
| wargs.reserve(server_jvm_args.size()); | |
| for (const string& a : server_jvm_args) { | |
| std::wstring wa = blaze_util::CstringToWstring(a); | |
| std::wstring wesc = bazel::windows::WindowsEscapeArg(wa); | |
| wargs.push_back(wesc); | |
| } | |
| ExecuteProgram(exe, wargs); | |
| } | |
| void ExecuteRunRequest(const blaze_util::Path& exe, | |
| const std::vector<string>& run_request_args) { | |
| std::vector<std::wstring> wargs; | |
| wargs.reserve(run_request_args.size()); | |
| for (const string& a : run_request_args) { | |
| // The arguments are already escaped, don't escape further. | |
| wargs.push_back(blaze_util::CstringToWstring(a)); | |
| } | |
| ExecuteProgram(exe, wargs); | |
| } | |
| const char kListSeparator = ';'; | |
| bool SymlinkDirectories(const string& posix_target, | |
| const blaze_util::Path& name) { | |
| wstring target; | |
| string error; | |
| if (!blaze_util::AsAbsoluteWindowsPath(posix_target, &target, &error)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "SymlinkDirectories(" << posix_target << ", " | |
| << name.AsPrintablePath() << "): AsAbsoluteWindowsPath(" << posix_target | |
| << ") failed: " << error; | |
| return false; | |
| } | |
| wstring werror; | |
| if (CreateJunction(name.AsNativePath(), target, &werror) != | |
| CreateJunctionResult::kSuccess) { | |
| string error(blaze_util::WstringToCstring(werror)); | |
| BAZEL_LOG(ERROR) << "SymlinkDirectories(" << posix_target << ", " | |
| << name.AsPrintablePath() | |
| << "): CreateJunction: " << error; | |
| return false; | |
| } | |
| return true; | |
| } | |
| #ifndef STILL_ACTIVE | |
| #define STILL_ACTIVE (259) // From MSDN about GetExitCodeProcess. | |
| #endif | |
| // On Windows (and Linux) we use a combination of PID and start time to identify | |
| // the server process. That is supposed to be unique unless one can start more | |
| // processes than there are PIDs available within a single jiffy. | |
| bool VerifyServerProcess(int pid, const blaze_util::Path& output_base) { | |
| AutoHandle process( | |
| ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); | |
| if (!process.IsValid()) { | |
| // Cannot find the server process. Can happen if the PID file is stale. | |
| return false; | |
| } | |
| DWORD exit_code = 0; | |
| uint64_t start_time = 0; | |
| if (!::GetExitCodeProcess(process, &exit_code) || exit_code != STILL_ACTIVE || | |
| !GetProcessStartupTime(process, &start_time)) { | |
| // Process doesn't exist or died meantime, all is good. No stale server is | |
| // present. | |
| return false; | |
| } | |
| string recorded_start_time; | |
| bool file_present = blaze_util::ReadFile( | |
| output_base.GetRelative("server/server.starttime"), &recorded_start_time); | |
| // If start time file got deleted, but PID file didn't, assume that this is an | |
| // old Bazel process that doesn't know how to write start time files yet. | |
| return !file_present || | |
| recorded_start_time == blaze_util::ToString(start_time); | |
| } | |
| bool KillServerProcess(int pid, const blaze_util::Path& output_base) { | |
| AutoHandle process(::OpenProcess( | |
| PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); | |
| DWORD exitcode = 0; | |
| if (!process.IsValid() || !::GetExitCodeProcess(process, &exitcode) || | |
| exitcode != STILL_ACTIVE) { | |
| // Cannot find the server process (can happen if the PID file is stale) or | |
| // it already exited. | |
| return false; | |
| } | |
| BOOL result = TerminateProcess(process, /*uExitCode*/ 0); | |
| if (!result || !AwaitServerProcessTermination(pid, output_base, | |
| kPostKillGracePeriodSeconds)) { | |
| string err = GetLastErrorString(); | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "Cannot terminate server process with PID " << pid | |
| << ", output_base=(" << output_base.AsPrintablePath() << "): " << err; | |
| } | |
| return result; | |
| } | |
| void TrySleep(unsigned int milliseconds) { | |
| Sleep(milliseconds); | |
| } | |
| // Not supported. | |
| void ExcludePathFromBackup(const blaze_util::Path& path) {} | |
| string GetHashedBaseDir(const string& root, const string& hashable) { | |
| // Builds a shorter output base dir name for Windows. | |
| // We create a path name representing the 128 bits of MD5 digest. To avoid | |
| // platform incompatibilities we restrict the alphabet to ASCII letters and | |
| // numbers. Windows paths are case-insensitive, so use only lower-case | |
| // letters. These constraints yield a 5-bit alphabet. | |
| // Since we only need 6 digits, ignore 0 and 1 because they look like | |
| // upper-case "O" and lower-case "l". | |
| static const char* alphabet = "abcdefghijklmnopqrstuvwxyz234567"; | |
| // 128 bits of data in base-32 require 128/5 = 25 digits with 3 bits lost. | |
| // Maximum path length on Windows is only 259 characters, so we'll only use | |
| // a few characters characters (base-32 digits) to represent the digest. | |
| // Using only 8 characters we represent 40 bits of the original 128. | |
| // Since the mapping is lossy and collisions are unlikely in practice, we'll | |
| // keep the mapping simple and just use the lower 5 bits of the first 8 bytes. | |
| static const unsigned char kLower5BitsMask = 0x1F; | |
| static const int filename_length = 8; | |
| unsigned char md5[blaze_util::Md5Digest::kDigestLength]; | |
| char coded_name[filename_length + 1]; | |
| blaze_util::Md5Digest digest; | |
| digest.Update(hashable.data(), hashable.size()); | |
| digest.Finish(md5); | |
| for (int i = 0; i < filename_length; ++i) { | |
| coded_name[i] = alphabet[md5[i] & kLower5BitsMask]; | |
| } | |
| coded_name[filename_length] = '\0'; | |
| return blaze_util::JoinPath(root, string(coded_name)); | |
| } | |
| void CreateSecureOutputRoot(const blaze_util::Path& path) { | |
| // TODO(bazel-team): implement this properly, by mimicing whatever the POSIX | |
| // implementation does. | |
| if (!blaze_util::MakeDirectories(path, 0755)) { | |
| string err = GetLastErrorString(); | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "MakeDirectories(" << path.AsPrintablePath() << ") failed: " << err; | |
| } | |
| if (!blaze_util::IsDirectory(path)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "'" << path.AsPrintablePath() << "' is not a directory"; | |
| } | |
| ExcludePathFromBackup(path); | |
| } | |
| string GetEnv(const string& name) { | |
| DWORD size = ::GetEnvironmentVariableA(name.c_str(), nullptr, 0); | |
| if (size == 0) { | |
| return string(); // unset or empty envvar | |
| } | |
| unique_ptr<char[]> value(new char[size]); | |
| ::GetEnvironmentVariableA(name.c_str(), value.get(), size); | |
| return string(value.get()); | |
| } | |
| string GetPathEnv(const string& name) { | |
| string value = GetEnv(name); | |
| if (value.empty()) { | |
| return value; | |
| } | |
| if (bazel::windows::HasUncPrefix(value.c_str())) { | |
| value = value.substr(4); | |
| } | |
| string wpath, error; | |
| if (!blaze_util::AsWindowsPath(value, &wpath, &error)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "Invalid path in envvar \"" << name << "\": " << error; | |
| } | |
| // Callers of GetPathEnv expect a path with forward slashes. | |
| std::replace(wpath.begin(), wpath.end(), '\\', '/'); | |
| return wpath; | |
| } | |
| bool ExistsEnv(const string& name) { | |
| return ::GetEnvironmentVariableA(name.c_str(), nullptr, 0) != 0; | |
| } | |
| void SetEnv(const string& name, const string& value) { | |
| // _putenv_s both calls ::SetEnvionmentVariableA and updates environ(5). | |
| _putenv_s(name.c_str(), value.c_str()); | |
| } | |
| void UnsetEnv(const string& name) { SetEnv(name, ""); } | |
| bool WarnIfStartedFromDesktop() { | |
| // GetConsoleProcessList returns: | |
| // 0, if no console attached (Bazel runs as a subprocess) | |
| // 1, if Bazel was started by clicking on its icon | |
| // 2, if Bazel was started from the command line (even if its output is | |
| // redirected) | |
| DWORD dummy[2] = {0}; | |
| if (GetConsoleProcessList(dummy, 2) != 1) { | |
| return false; | |
| } | |
| printf( | |
| "Bazel is a command line tool.\n\n" | |
| "Try opening a console, such as the Windows Command Prompt (cmd.exe) " | |
| "or PowerShell, and running \"bazel help\".\n\n" | |
| "Press Enter to close this window..."); | |
| ReadFile(GetStdHandle(STD_INPUT_HANDLE), dummy, 1, dummy, nullptr); | |
| return true; | |
| } | |
| #ifndef ENABLE_PROCESSED_OUTPUT | |
| // From MSDN about BOOL SetConsoleMode(HANDLE, DWORD). | |
| #define ENABLE_PROCESSED_OUTPUT 0x0001 | |
| #endif // not ENABLE_PROCESSED_OUTPUT | |
| #ifndef ENABLE_WRAP_AT_EOL_OUTPUT | |
| // From MSDN about BOOL SetConsoleMode(HANDLE, DWORD). | |
| #define ENABLE_WRAP_AT_EOL_OUTPUT 0x0002 | |
| #endif // not ENABLE_WRAP_AT_EOL_OUTPUT | |
| #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING | |
| // From MSDN about BOOL SetConsoleMode(HANDLE, DWORD). | |
| #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 | |
| #endif // not ENABLE_VIRTUAL_TERMINAL_PROCESSING | |
| void SetupStdStreams() { | |
| static const DWORD stdhandles[] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, | |
| STD_ERROR_HANDLE}; | |
| for (int i = 0; i <= 2; ++i) { | |
| HANDLE handle = ::GetStdHandle(stdhandles[i]); | |
| if (handle == INVALID_HANDLE_VALUE || handle == nullptr) { | |
| // Ensure we have open fds to each std* stream. Otherwise we can end up | |
| // with bizarre things like stdout going to the lock file, etc. | |
| _open("NUL", (i == 0) ? _O_RDONLY : _O_WRONLY); | |
| } | |
| DWORD mode = 0; | |
| if (i > 0 && handle != INVALID_HANDLE_VALUE && handle != nullptr && | |
| ::GetConsoleMode(handle, &mode)) { | |
| DWORD newmode = mode | ENABLE_PROCESSED_OUTPUT | | |
| ENABLE_WRAP_AT_EOL_OUTPUT | | |
| ENABLE_VIRTUAL_TERMINAL_PROCESSING; | |
| if (mode != newmode) { | |
| // We don't care about the success of this. Worst that can happen if | |
| // this method fails is that the console won't understand control | |
| // characters like color change or carriage return. | |
| ::SetConsoleMode(handle, newmode); | |
| } | |
| } | |
| } | |
| } | |
| LARGE_INTEGER WindowsClock::GetFrequency() { | |
| LARGE_INTEGER result; | |
| if (!QueryPerformanceFrequency(&result)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "WindowsClock::GetFrequency: QueryPerformanceFrequency failed: " | |
| << GetLastErrorString(); | |
| } | |
| // On ancient Windows versions (pre-XP) and specific hardware the result may | |
| // be 0. Since this is pre-XP, we don't handle that, just error out. | |
| if (result.QuadPart <= 0) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "WindowsClock::GetFrequency: QueryPerformanceFrequency returned " | |
| "invalid result (" | |
| << result.QuadPart << "): " << GetLastErrorString(); | |
| } | |
| return result; | |
| } | |
| LARGE_INTEGER WindowsClock::GetMillisecondsAsLargeInt( | |
| const LARGE_INTEGER& freq) { | |
| LARGE_INTEGER counter; | |
| if (!QueryPerformanceCounter(&counter)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "WindowsClock::GetMillisecondsAsLargeInt: QueryPerformanceCounter " | |
| "failed: " | |
| << GetLastErrorString(); | |
| } | |
| LARGE_INTEGER result; | |
| result.QuadPart = | |
| // seconds | |
| (counter.QuadPart / freq.QuadPart) * 1000LL + | |
| // milliseconds | |
| (((counter.QuadPart % freq.QuadPart) * 1000LL) / freq.QuadPart); | |
| return result; | |
| } | |
| const WindowsClock WindowsClock::INSTANCE; | |
| WindowsClock::WindowsClock() | |
| : kFrequency(GetFrequency()) {} | |
| uint64_t WindowsClock::GetMilliseconds() const { | |
| return GetMillisecondsAsLargeInt(kFrequency).QuadPart; | |
| } | |
| uint64_t AcquireLock(const blaze_util::Path& output_base, bool batch_mode, | |
| bool block, BlazeLock* blaze_lock) { | |
| blaze_util::Path lockfile = output_base.GetRelative("lock"); | |
| blaze_lock->handle = INVALID_HANDLE_VALUE; | |
| bool first_lock_attempt = true; | |
| uint64_t st = GetMillisecondsMonotonic(); | |
| while (true) { | |
| blaze_lock->handle = ::CreateFileW( | |
| /* lpFileName */ lockfile.AsNativePath().c_str(), | |
| /* dwDesiredAccess */ GENERIC_READ | GENERIC_WRITE, | |
| /* dwShareMode */ FILE_SHARE_READ, | |
| /* lpSecurityAttributes */ nullptr, | |
| /* dwCreationDisposition */ CREATE_ALWAYS, | |
| /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, | |
| /* hTemplateFile */ nullptr); | |
| if (blaze_lock->handle != INVALID_HANDLE_VALUE) { | |
| // We could open the file, so noone else holds a lock on it. | |
| break; | |
| } | |
| if (GetLastError() == ERROR_SHARING_VIOLATION) { | |
| // Someone else has the lock. | |
| BAZEL_LOG(USER) << "Another command holds the client lock"; | |
| if (!block) { | |
| BAZEL_DIE(blaze_exit_code::LOCK_HELD_NOBLOCK_FOR_LOCK) | |
| << "Exiting because the lock is held and --noblock_for_lock was " | |
| "given."; | |
| } | |
| if (first_lock_attempt) { | |
| first_lock_attempt = false; | |
| BAZEL_LOG(USER) << "Waiting for it to complete..."; | |
| fflush(stderr); | |
| } | |
| Sleep(/* dwMilliseconds */ 200); | |
| } else { | |
| string err = GetLastErrorString(); | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "AcquireLock(" << lockfile.AsPrintablePath() | |
| << "): CreateFile failed: " << err; | |
| } | |
| } | |
| uint64_t wait_time = GetMillisecondsMonotonic() - st; | |
| // We have the lock. | |
| OVERLAPPED overlapped = {0}; | |
| if (!LockFileEx( | |
| /* hFile */ blaze_lock->handle, | |
| /* dwFlags */ LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, | |
| /* dwReserved */ 0, | |
| /* nNumberOfBytesToLockLow */ 1, | |
| /* nNumberOfBytesToLockHigh */ 0, | |
| /* lpOverlapped */ &overlapped)) { | |
| string err = GetLastErrorString(); | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "AcquireLock(" << lockfile.AsPrintablePath() | |
| << "): LockFileEx failed: " << err; | |
| } | |
| // On other platforms we write some info about this process into the lock file | |
| // such as the server PID. On Windows we don't do that because the file is | |
| // locked exclusively, meaning other processes may not open the file even for | |
| // reading. | |
| return wait_time; | |
| } | |
| void ReleaseLock(BlazeLock* blaze_lock) { | |
| OVERLAPPED overlapped = {0}; | |
| UnlockFileEx(blaze_lock->handle, 0, 1, 0, &overlapped); | |
| CloseHandle(blaze_lock->handle); | |
| } | |
| #ifdef GetUserName | |
| // By including <windows.h>, we have GetUserName defined either as | |
| // GetUserNameA or GetUserNameW. | |
| #undef GetUserName | |
| #endif | |
| string GetUserName() { | |
| // Check USER, for sake of consistency with Linux / macOS. This is only set | |
| // under MSYS2, or potentially in tests. | |
| string user = GetEnv("USER"); | |
| if (!user.empty()) { | |
| return user; | |
| } | |
| // Check USERNAME before calling GetUserNameW. Doing so allows the user to | |
| // customize (or override) the user name. | |
| // See https://github.com/bazelbuild/bazel/issues/7819#issuecomment-533050947 | |
| user = GetEnv("USERNAME"); | |
| if (!user.empty()) { | |
| return user; | |
| } | |
| WCHAR buffer[UNLEN + 1]; | |
| DWORD len = UNLEN + 1; | |
| if (!::GetUserNameW(buffer, &len)) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "GetUserNameW failed: " << GetLastErrorString(); | |
| } | |
| return blaze_util::WstringToCstring(buffer); | |
| } | |
| bool IsEmacsTerminal() { | |
| string emacs = GetEnv("EMACS"); | |
| // GNU Emacs <25.1 (and ~all non-GNU emacsen) set EMACS=t, but >=25.1 doesn't | |
| // do that and instead sets INSIDE_EMACS=<stuff> (where <stuff> can look like | |
| // e.g. "25.1.1,comint"). So we check both variables for maximum | |
| // compatibility. | |
| return emacs == "t" || ExistsEnv("INSIDE_EMACS"); | |
| } | |
| bool IsStandardTerminal() { | |
| for (DWORD i : {STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}) { | |
| DWORD mode = 0; | |
| HANDLE handle = ::GetStdHandle(STD_ERROR_HANDLE); | |
| // handle may be invalid when std{out,err} is redirected | |
| if (handle == INVALID_HANDLE_VALUE || !::GetConsoleMode(handle, &mode) || | |
| !(mode & ENABLE_PROCESSED_OUTPUT) || | |
| !(mode & ENABLE_WRAP_AT_EOL_OUTPUT) || | |
| !(mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| int GetTerminalColumns() { | |
| string columns_env = GetEnv("COLUMNS"); | |
| if (!columns_env.empty()) { | |
| char* endptr; | |
| int columns = blaze_util::strto32(columns_env.c_str(), &endptr, 10); | |
| if (*endptr == '\0') { // $COLUMNS is a valid number | |
| return columns; | |
| } | |
| } | |
| HANDLE stdout_handle = ::GetStdHandle(STD_OUTPUT_HANDLE); | |
| if (stdout_handle != INVALID_HANDLE_VALUE) { | |
| // stdout_handle may be invalid when stdout is redirected. | |
| CONSOLE_SCREEN_BUFFER_INFO screen_info; | |
| if (GetConsoleScreenBufferInfo(stdout_handle, &screen_info)) { | |
| int width = 1 + screen_info.srWindow.Right - screen_info.srWindow.Left; | |
| if (width > 1) { | |
| return width; | |
| } | |
| } | |
| } | |
| return 80; // default if not a terminal. | |
| } | |
| bool UnlimitResources() { | |
| return true; // Nothing to do so assume success. | |
| } | |
| bool UnlimitCoredumps() { | |
| return true; // Nothing to do so assume success. | |
| } | |
| static const int MAX_KEY_LENGTH = 255; | |
| // We do not care about registry values longer than MAX_PATH | |
| static const int REG_VALUE_BUFFER_SIZE = MAX_PATH; | |
| // Implements heuristics to discover msys2 installation. | |
| static string GetMsysBash() { | |
| HKEY h_uninstall; | |
| // MSYS2 installer writes its registry into HKCU, although documentation | |
| // (https://msdn.microsoft.com/en-us/library/ms954376.aspx) | |
| // clearly states that it should go to HKLM. | |
| static constexpr const char key[] = | |
| "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; | |
| if (RegOpenKeyExA(HKEY_CURRENT_USER, // _In_ HKEY hKey, | |
| key, // _In_opt_ LPCTSTR lpSubKey, | |
| 0, // _In_ DWORD ulOptions, | |
| KEY_ENUMERATE_SUB_KEYS | | |
| KEY_QUERY_VALUE, // _In_ REGSAM samDesired, | |
| &h_uninstall // _Out_ PHKEY phkResult | |
| )) { | |
| BAZEL_LOG(INFO) << "Cannot open HKCU\\" << key; | |
| return string(); | |
| } | |
| AutoHandle auto_uninstall(h_uninstall); | |
| // Since MSYS2 decided to generate a new product key for each installation, | |
| // we enumerate all keys under | |
| // HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall and find the first | |
| // with MSYS2 64bit display name. | |
| static constexpr const char msys_display_name[] = "MSYS2 64bit"; | |
| DWORD n_subkeys; | |
| if (RegQueryInfoKey(h_uninstall, // _In_ HKEY hKey, | |
| 0, // _Out_opt_ LPTSTR lpClass, | |
| 0, // _Inout_opt_ LPDWORD lpcClass, | |
| 0, // _Reserved_ LPDWORD lpReserved, | |
| &n_subkeys, // _Out_opt_ LPDWORD lpcSubKeys, | |
| 0, // _Out_opt_ LPDWORD lpcMaxSubKeyLen, | |
| 0, // _Out_opt_ LPDWORD lpcMaxClassLen, | |
| 0, // _Out_opt_ LPDWORD lpcValues, | |
| 0, // _Out_opt_ LPDWORD lpcMaxValueNameLen, | |
| 0, // _Out_opt_ LPDWORD lpcMaxValueLen, | |
| 0, // _Out_opt_ LPDWORD lpcbSecurityDescriptor, | |
| 0 // _Out_opt_ PFILETIME lpftLastWriteTime | |
| )) { | |
| BAZEL_LOG(INFO) << "Cannot query HKCU\\" << key; | |
| return string(); | |
| } | |
| for (DWORD key_index = 0; key_index < n_subkeys; key_index++) { | |
| char subkey_name[MAX_KEY_LENGTH]; | |
| if (RegEnumKeyA(h_uninstall, // _In_ HKEY hKey, | |
| key_index, // _In_ DWORD dwIndex, | |
| subkey_name, // _Out_ LPTSTR lpName, | |
| sizeof(subkey_name) // _In_ DWORD cchName | |
| )) { | |
| BAZEL_LOG(INFO) << "Cannot get " << key_index << " subkey of HKCU\\" | |
| << key; | |
| continue; // try next subkey | |
| } | |
| HKEY h_subkey; | |
| if (RegOpenKeyEx(h_uninstall, // _In_ HKEY hKey, | |
| subkey_name, // _In_opt_ LPCTSTR lpSubKey, | |
| 0, // _In_ DWORD ulOptions, | |
| KEY_QUERY_VALUE, // _In_ REGSAM samDesired, | |
| &h_subkey // _Out_ PHKEY phkResult | |
| )) { | |
| BAZEL_LOG(ERROR) << "Failed to open subkey HKCU\\" << key << "\\" | |
| << subkey_name; | |
| continue; // try next subkey | |
| } | |
| AutoHandle auto_subkey(h_subkey); | |
| BYTE value[REG_VALUE_BUFFER_SIZE]; | |
| DWORD value_length = sizeof(value); | |
| DWORD value_type; | |
| if (RegQueryValueEx(h_subkey, // _In_ HKEY hKey, | |
| "DisplayName", // _In_opt_ LPCTSTR lpValueName, | |
| 0, // _Reserved_ LPDWORD lpReserved, | |
| &value_type, // _Out_opt_ LPDWORD lpType, | |
| value, // _Out_opt_ LPBYTE lpData, | |
| &value_length // _Inout_opt_ LPDWORD lpcbData | |
| )) { | |
| // This registry key has no DisplayName subkey, so it cannot be MSYS2, or | |
| // it cannot be a version of MSYS2 that we are looking for. | |
| continue; | |
| } | |
| if (value_type == REG_SZ && | |
| 0 == memcmp(msys_display_name, value, sizeof(msys_display_name))) { | |
| BAZEL_LOG(INFO) << "Getting install location of HKCU\\" << key << "\\" | |
| << subkey_name; | |
| BYTE path[REG_VALUE_BUFFER_SIZE]; | |
| DWORD path_length = sizeof(path); | |
| DWORD path_type; | |
| if (RegQueryValueEx( | |
| h_subkey, // _In_ HKEY hKey, | |
| "InstallLocation", // _In_opt_ LPCTSTR lpValueName, | |
| 0, // _Reserved_ LPDWORD lpReserved, | |
| &path_type, // _Out_opt_ LPDWORD lpType, | |
| path, // _Out_opt_ LPBYTE lpData, | |
| &path_length // _Inout_opt_ LPDWORD lpcbData | |
| )) { | |
| // This version of MSYS2 does not seem to create a "InstallLocation" | |
| // subkey. Let's ignore this registry key to avoid picking up an MSYS2 | |
| // version that may be different from what Bazel expects. | |
| continue; // try next subkey | |
| } | |
| if (path_length == 0 || path_type != REG_SZ) { | |
| // This version of MSYS2 seem to have recorded an empty installation | |
| // location, or the registry key was modified. Either way, let's ignore | |
| // this registry key and keep looking at the next subkey. | |
| continue; | |
| } | |
| BAZEL_LOG(INFO) << "Install location of HKCU\\" << key << "\\" | |
| << subkey_name << " is " << path; | |
| string path_as_string(path, path + path_length - 1); | |
| string bash_exe = path_as_string + "\\usr\\bin\\bash.exe"; | |
| if (!blaze_util::PathExists(bash_exe)) { | |
| // The supposed bash.exe does not exist. Maybe MSYS2 was deleted but not | |
| // uninstalled? We can't tell, but for all we care, this MSYS2 path is | |
| // not what we need, so ignore this registry key. | |
| continue; | |
| } | |
| BAZEL_LOG(INFO) << "Detected MSYS2 Bash at " << bash_exe.c_str(); | |
| return bash_exe; | |
| } | |
| } | |
| return string(); | |
| } | |
| static string GetBinaryFromPath(const string& binary_name) { | |
| char found[MAX_PATH]; | |
| string path_list = blaze::GetPathEnv("PATH"); | |
| // We do not fully replicate all the quirks of search in PATH. | |
| // There is no system function to do so, and that way lies madness. | |
| size_t start = 0; | |
| do { | |
| // This ignores possibly quoted semicolons in PATH etc. | |
| size_t end = path_list.find_first_of(";", start); | |
| string path = path_list.substr( | |
| start, end != string::npos ? end - start : string::npos); | |
| // Handle one typical way of quoting (where.exe does not handle this, but | |
| // CreateProcess does). | |
| if (path.size() > 1 && path[0] == '"' && path[path.size() - 1] == '"') { | |
| path = path.substr(1, path.size() - 2); | |
| } | |
| if (SearchPathA(path.c_str(), // _In_opt_ LPCTSTR lpPath, | |
| binary_name.c_str(), // _In_ LPCTSTR lpFileName, | |
| 0, // LPCTSTR lpExtension, | |
| sizeof(found), // DWORD nBufferLength, | |
| found, // _Out_ LPTSTR lpBuffer, | |
| 0 // _Out_opt_ LPTSTR *lpFilePart | |
| )) { | |
| BAZEL_LOG(INFO) << binary_name.c_str() << " found on PATH: " << found; | |
| return string(found); | |
| } | |
| if (end == string::npos) { | |
| break; | |
| } | |
| start = end + 1; | |
| } while (true); | |
| return string(); | |
| } | |
| static string LocateBashMaybe() { | |
| string msys_bash = GetMsysBash(); | |
| return msys_bash.empty() ? GetBinaryFromPath("bash.exe") : msys_bash; | |
| } | |
| string DetectBashAndExportBazelSh() { | |
| string bash = blaze::GetPathEnv("BAZEL_SH"); | |
| if (!bash.empty()) { | |
| return bash; | |
| } | |
| uint64_t start = blaze::GetMillisecondsMonotonic(); | |
| bash = LocateBashMaybe(); | |
| uint64_t end = blaze::GetMillisecondsMonotonic(); | |
| if (bash.empty()) { | |
| BAZEL_LOG(INFO) << "BAZEL_SH detection took " << end - start | |
| << " msec, not found"; | |
| } else { | |
| BAZEL_LOG(INFO) << "BAZEL_SH detection took " << end - start | |
| << " msec, found " << bash.c_str(); | |
| // Set process environment variable. | |
| blaze::SetEnv("BAZEL_SH", bash); | |
| } | |
| return bash; | |
| } | |
| void EnsurePythonPathOption(std::vector<string>* options) { | |
| string python_path = GetBinaryFromPath("python.exe"); | |
| if (!python_path.empty()) { | |
| // Provide python path as coming from the least important rc file. | |
| std::replace(python_path.begin(), python_path.end(), '\\', '/'); | |
| options->push_back(string("--default_override=0:build=--python_path=") + | |
| python_path); | |
| } | |
| } | |
| } // namespace blaze |