| // 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/global_variables.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_(NULL), cleanup_group_(NULL) {} |
| |
| 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(NULL); |
| if (result->threadpool_ == NULL) { |
| if (error) { |
| string msg = GetLastErrorString(); |
| *error = "CreateThreadpool failed: " + msg; |
| } |
| return nullptr; |
| } |
| |
| result->cleanup_group_ = CreateThreadpoolCleanupGroup(); |
| if (result->cleanup_group_ == NULL) { |
| 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_, NULL); |
| |
| 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 == NULL) { |
| 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_ == NULL) { |
| return true; |
| } |
| CloseThreadpoolCleanupGroupMembers(cleanup_group_, FALSE, NULL); |
| CloseThreadpoolCleanupGroup(cleanup_group_); |
| CloseThreadpool(threadpool_); |
| threadpool_ = NULL; |
| cleanup_group_ = NULL; |
| |
| 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().GetGlobals()->server_pid != -1) { |
| KillServerProcess( |
| SignalHandler::Get().GetGlobals()->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 string &output_base, |
| GlobalVariables* globals, |
| SignalHandler::Callback cancel_server) { |
| product_name_ = product_name; |
| output_base_ = output_base; |
| globals_ = globals; |
| 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 string& output_base) { |
| } |
| |
| string GetProcessIdAsString() { |
| return ToString(GetCurrentProcessId()); |
| } |
| |
| string GetSelfPath() { |
| WCHAR buffer[kWindowsPathBufferSize] = {0}; |
| if (!GetModuleFileNameW(0, buffer, kWindowsPathBufferSize)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "GetSelfPath: GetModuleFileNameW: " << GetLastErrorString(); |
| } |
| return string(blaze_util::WstringToCstring(buffer).get()); |
| } |
| |
| 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() { |
| if (IsRunningWithinTest()) { |
| // Bazel is running inside of a test. Respect $HOME that the test setup has |
| // set instead of using the actual home directory of the current user. |
| return GetPathEnv("HOME"); |
| } |
| |
| 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. |
| if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_Profile, KF_FLAG_DEFAULT, NULL, |
| &wpath))) { |
| string result = string(blaze_util::WstringToCstring(wpath).get()); |
| ::CoTaskMemFree(wpath); |
| return result; |
| } |
| |
| // On Windows 2016 Server, Nano server: FOLDERID_Profile is unknown but |
| // %USERPROFILE% is set. See https://github.com/bazelbuild/bazel/issues/6701 |
| string userprofile = GetPathEnv("USERPROFILE"); |
| if (!userprofile.empty()) { |
| return userprofile; |
| } |
| |
| return GetPathEnv("HOME"); // only defined in MSYS/Cygwin |
| } |
| |
| 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. |
| } |
| |
| string GetProcessCWD(int pid) { |
| // TODO(bazel-team) 2016-11-18: decide whether we need this on Windows and |
| // implement or delete. |
| return ""; |
| } |
| |
| 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) |
| // |
| // Quoting rules are described here: |
| // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ |
| |
| static const int MAX_CMDLINE_LENGTH = 32768; |
| |
| struct CmdLine { |
| WCHAR cmdline[MAX_CMDLINE_LENGTH]; |
| }; |
| static void CreateCommandLine(CmdLine* result, const string& exe, |
| const std::vector<string>& args_vector) { |
| std::wstringstream cmdline; |
| string short_exe; |
| if (!exe.empty()) { |
| string error; |
| if (!blaze_util::AsShortWindowsPath(exe, &short_exe, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "CreateCommandLine: AsShortWindowsPath(" << exe << "): " << error; |
| } |
| wstring wshort_exe = blaze_util::CstringToWstring(short_exe.c_str()).get(); |
| cmdline << L'\"' << wshort_exe << L'\"'; |
| } |
| |
| bool first = true; |
| for (const auto& s : args_vector) { |
| if (first) { |
| // Skip first argument, it is equal to 'exe'. |
| first = false; |
| continue; |
| } else { |
| cmdline << L' '; |
| } |
| |
| bool has_space = s.find(" ") != string::npos; |
| |
| if (has_space) { |
| cmdline << L'\"'; |
| } |
| |
| wstring ws = blaze_util::CstringToWstring(s.c_str()).get(); |
| std::wstring::const_iterator it = ws.begin(); |
| while (it != ws.end()) { |
| wchar_t ch = *it++; |
| switch (ch) { |
| case L'"': |
| // Escape double quotes |
| cmdline << L"\\\""; |
| break; |
| |
| case L'\\': |
| if (it == ws.end()) { |
| // Backslashes at the end of the string are quoted if we add quotes |
| cmdline << (has_space ? L"\\\\" : L"\\"); |
| } else { |
| // Backslashes everywhere else are quoted if they are followed by a |
| // quote or a backslash |
| cmdline << (*it == L'"' || *it == L'\\' ? L"\\\\" : L"\\"); |
| } |
| break; |
| |
| default: |
| cmdline << ch; |
| } |
| } |
| |
| if (has_space) { |
| cmdline << L'\"'; |
| } |
| } |
| |
| 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.c_str()).get(); |
| } |
| |
| // 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 string& 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 |
| << "): GetProcessStartupTime failed: " << GetLastErrorString(); |
| } |
| |
| string start_time_file = blaze_util::JoinPath(server_dir, "server.starttime"); |
| if (!blaze_util::WriteFile(ToString(start_time), start_time_file)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "WriteProcessStartupTime(" << server_dir << "): WriteFile(" |
| << start_time_file << ") failed: " << GetLastErrorString(); |
| } |
| } |
| |
| static HANDLE CreateJvmOutputFile(const wstring& 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.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 */ NULL); |
| if (handle != INVALID_HANDLE_VALUE) { |
| if (daemon_out_append |
| && !SetFilePointerEx(handle, {0}, NULL, FILE_END)) { |
| fprintf(stderr, "Could not seek to end of file (%ls)\n", path.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 string& exe, |
| const std::vector<string>& args_vector, |
| const std::map<string, EnvVarValue>& env, |
| const string& daemon_output, |
| const bool daemon_out_append, |
| const string& binaries_dir, |
| const string& server_dir, |
| const StartupOptions &options, |
| BlazeServerStartup** server_startup) { |
| wstring wdaemon_output; |
| string error; |
| if (!blaze_util::AsAbsoluteWindowsPath(daemon_output, &wdaemon_output, |
| &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "ExecuteDaemon(" << exe << "): AsAbsoluteWindowsPath(" |
| << daemon_output << ") failed: " << error; |
| } |
| |
| SECURITY_ATTRIBUTES inheritable_handle_sa = {sizeof(SECURITY_ATTRIBUTES), |
| NULL, TRUE}; |
| |
| AutoHandle devnull(::CreateFileW( |
| L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, |
| &inheritable_handle_sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); |
| if (!devnull.IsValid()) { |
| error = GetLastErrorString(); |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "ExecuteDaemon(" << exe << "): CreateFileA(NUL) failed: " << error; |
| } |
| |
| AutoHandle stdout_file(CreateJvmOutputFile( |
| wdaemon_output.c_str(), &inheritable_handle_sa, daemon_out_append)); |
| if (!stdout_file.IsValid()) { |
| error = GetLastErrorString(); |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "ExecuteDaemon(" << exe << "): CreateJvmOutputFile(" |
| << blaze_util::WstringToString(wdaemon_output) << ") 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)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "ExecuteDaemon(" << exe << "): DuplicateHandle(" |
| << blaze_util::WstringToString(wdaemon_output) |
| << ") failed: " << GetLastErrorString(); |
| } |
| 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 << "): attribute list creation failed: " |
| << blaze_util::WstringToString(werror); |
| } |
| |
| PROCESS_INFORMATION processInfo = {0}; |
| STARTUPINFOEXW startupInfoEx = {0}; |
| lpAttributeList->InitStartupInfoExW(&startupInfoEx); |
| |
| CmdLine cmdline; |
| CreateCommandLine(&cmdline, exe, args_vector); |
| |
| BOOL ok; |
| { |
| WithEnvVars env_obj(env); |
| |
| ok = CreateProcessW( |
| /* lpApplicationName */ NULL, |
| /* lpCommandLine */ cmdline.cmdline, |
| /* lpProcessAttributes */ NULL, |
| /* lpThreadAttributes */ NULL, |
| /* bInheritHandles */ TRUE, |
| /* dwCreationFlags */ DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | |
| EXTENDED_STARTUPINFO_PRESENT, |
| /* lpEnvironment */ NULL, |
| /* lpCurrentDirectory */ NULL, |
| /* lpStartupInfo */ &startupInfoEx.StartupInfo, |
| /* lpProcessInformation */ &processInfo); |
| } |
| |
| if (!ok) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "ExecuteDaemon(" << exe << "): CreateProcess(" << cmdline.cmdline |
| << ") failed: " << GetLastErrorString(); |
| } |
| |
| WriteProcessStartupTime(server_dir, processInfo.hProcess); |
| |
| // Pass ownership of processInfo.hProcess |
| *server_startup = new ProcessHandleBlazeServerStartup(processInfo.hProcess); |
| |
| string pid_string = ToString(processInfo.dwProcessId); |
| string pid_file = blaze_util::JoinPath(server_dir, 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.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. |
| void ExecuteProgram(const string& exe, const std::vector<string>& args_vector) { |
| std::wstring wexe = blaze_util::CstringToWstring(exe.c_str()).get(); |
| |
| CmdLine cmdline; |
| CreateCommandLine(&cmdline, "", args_vector); |
| |
| bazel::windows::WaitableProcess proc; |
| std::wstring werror; |
| if (!proc.Create(wexe, cmdline.cmdline, nullptr, L"", &werror) || |
| proc.WaitFor(-1, nullptr, &werror) != |
| bazel::windows::WaitableProcess::kWaitSuccess) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "ExecuteProgram(" << exe |
| << ") failed: " << blaze_util::WstringToCstring(werror.c_str()).get(); |
| } |
| werror.clear(); |
| int x = proc.GetExitCode(&werror); |
| if (!werror.empty()) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "ExecuteProgram(" << exe |
| << ") failed: " << blaze_util::WstringToCstring(werror.c_str()).get(); |
| } |
| exit(x); |
| } |
| |
| const char kListSeparator = ';'; |
| |
| bool SymlinkDirectories(const string &posix_target, const string &posix_name) { |
| wstring name; |
| wstring target; |
| string error; |
| if (!blaze_util::AsAbsoluteWindowsPath(posix_name, &name, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "SymlinkDirectories(" << posix_target << ", " << posix_name |
| << "): AsAbsoluteWindowsPath(" << posix_target << ") failed: " << error; |
| return false; |
| } |
| if (!blaze_util::AsAbsoluteWindowsPath(posix_target, &target, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "SymlinkDirectories(" << posix_target << ", " << posix_name |
| << "): AsAbsoluteWindowsPath(" << posix_name << ") failed: " << error; |
| return false; |
| } |
| wstring werror; |
| if (CreateJunction(name, target, &werror) != CreateJunctionResult::kSuccess) { |
| string error(blaze_util::WstringToCstring(werror.c_str()).get()); |
| BAZEL_LOG(ERROR) << "SymlinkDirectories(" << posix_target << ", " |
| << posix_name << "): 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 string& 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( |
| blaze_util::JoinPath(output_base, "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 == ToString(start_time); |
| } |
| |
| bool KillServerProcess(int pid, const string& 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)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "Cannot terminate server process with PID " << pid |
| << ", output_base=(" << output_base << "): " << GetLastErrorString(); |
| } |
| return result; |
| } |
| |
| void TrySleep(unsigned int milliseconds) { |
| Sleep(milliseconds); |
| } |
| |
| // Not supported. |
| void ExcludePathFromBackup(const string &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 string& path) { |
| // TODO(bazel-team): implement this properly, by mimicing whatever the POSIX |
| // implementation does. |
| const char* root = path.c_str(); |
| if (!blaze_util::MakeDirectories(path, 0755)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "MakeDirectories(" << root << ") failed: " << GetLastErrorString(); |
| } |
| |
| if (!blaze_util::IsDirectory(path)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "'" << root << "' is not a directory"; |
| } |
| |
| ExcludePathFromBackup(root); |
| } |
| |
| string GetEnv(const string& name) { |
| DWORD size = ::GetEnvironmentVariableA(name.c_str(), NULL, 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(), NULL, 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, NULL); |
| 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 == NULL) { |
| // 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 != NULL && |
| ::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 string& output_base, bool batch_mode, bool block, |
| BlazeLock* blaze_lock) { |
| string lockfile = blaze_util::JoinPath(output_base, "lock"); |
| wstring wlockfile; |
| string error; |
| if (!blaze_util::AsAbsoluteWindowsPath(lockfile, &wlockfile, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "AcquireLock(" << output_base << "): AsAbsoluteWindowsPath(" |
| << lockfile << ") failed: " << error; |
| } |
| |
| blaze_lock->handle = INVALID_HANDLE_VALUE; |
| bool first_lock_attempt = true; |
| uint64_t st = GetMillisecondsMonotonic(); |
| while (true) { |
| blaze_lock->handle = ::CreateFileW( |
| /* lpFileName */ wlockfile.c_str(), |
| /* dwDesiredAccess */ GENERIC_READ | GENERIC_WRITE, |
| /* dwShareMode */ FILE_SHARE_READ, |
| /* lpSecurityAttributes */ NULL, |
| /* dwCreationDisposition */ CREATE_ALWAYS, |
| /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, |
| /* hTemplateFile */ NULL); |
| 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 { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "AcquireLock(" << lockfile << "): CreateFileW(" |
| << blaze_util::WstringToString(wlockfile) |
| << ") failed: " << GetLastErrorString(); |
| } |
| } |
| 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)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "AcquireLock(" << lockfile << "): LockFileEx(" |
| << blaze_util::WstringToString(wlockfile) |
| << ") failed: " << GetLastErrorString(); |
| } |
| // 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() { |
| WCHAR buffer[UNLEN + 1]; |
| DWORD len = UNLEN + 1; |
| if (!::GetUserNameW(buffer, &len)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "GetUserNameW failed: " << GetLastErrorString(); |
| } |
| return string(blaze_util::WstringToCstring(buffer).get()); |
| } |
| |
| 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"); |
| } |
| |
| // Returns true if stderr is connected to a terminal, and it can support color |
| // and cursor movement (this is computed heuristically based on the values of |
| // environment variables). Blaze only outputs control characters to stderr, |
| // so we only care for the stderr descriptor type. |
| bool IsStderrStandardTerminal() { |
| DWORD mode = 0; |
| HANDLE handle = ::GetStdHandle(STD_ERROR_HANDLE); |
| // handle may be invalid when stderr 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; |
| } |
| |
| // Returns the number of columns of the terminal to which stderr is connected, |
| // or $COLUMNS (default 80) if there is no such terminal. Blaze only outputs |
| // formatted messages to stderr, so we only care for width of a terminal |
| // connected to the stderr descriptor. |
| int GetStderrTerminalColumns() { |
| 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_ERROR_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 |