| // 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. |
| |
| #include <errno.h> // errno, ENAMETOOLONG |
| #include <limits.h> |
| |
| #ifndef COMPILER_MSVC |
| #include <fcntl.h> |
| #include <sys/cygwin.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/statfs.h> |
| #include <unistd.h> |
| #endif // COMPILER_MSVC |
| |
| #include <windows.h> |
| |
| #include <cstdlib> |
| #include <cstdio> |
| #include <thread> // NOLINT (to slience Google-internal linter) |
| |
| #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/md5.h" |
| #include "src/main/cpp/util/strings.h" |
| |
| namespace blaze { |
| |
| using blaze_util::die; |
| using blaze_util::pdie; |
| using std::string; |
| using std::vector; |
| |
| SignalHandler SignalHandler::INSTANCE; |
| |
| class WindowsClock { |
| public: |
| uint64_t GetMilliseconds() const; |
| uint64_t GetProcessMilliseconds() 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; |
| |
| // Time (in milliseconds) at process start. |
| const LARGE_INTEGER kStart; |
| |
| WindowsClock(); |
| |
| static LARGE_INTEGER GetFrequency(); |
| static LARGE_INTEGER GetMillisecondsAsLargeInt(const LARGE_INTEGER& freq); |
| }; |
| |
| #ifdef COMPILER_MSVC |
| |
| void SignalHandler::Install(GlobalVariables* globals, |
| SignalHandler::Callback cancel_server) { |
| // TODO(bazel-team): implement this. |
| pdie(255, "blaze::SignalHandler::Install is not implemented on Windows"); |
| } |
| |
| ATTRIBUTE_NORETURN void SignalHandler::PropagateSignalOrExit(int exit_code) { |
| // TODO(bazel-team): implement this. |
| pdie(255, |
| "blaze::SignalHandler::PropagateSignalOrExit is not implemented on " |
| "Windows"); |
| } |
| |
| #else // not COMPILER_MSVC |
| |
| // The number of the last received signal that should cause the client |
| // to shutdown. This is saved so that the client's WTERMSIG can be set |
| // correctly. (Currently only SIGPIPE uses this mechanism.) |
| static volatile sig_atomic_t signal_handler_received_signal = 0; |
| |
| // A signal-safe version of fprintf(stderr, ...). |
| static void sigprintf(const char* format, ...); |
| |
| // Signal handler. |
| static void handler(int signum) { |
| int saved_errno = errno; |
| |
| static volatile sig_atomic_t sigint_count = 0; |
| |
| switch (signum) { |
| case SIGINT: |
| if (++sigint_count >= 3) { |
| sigprintf( |
| "\n%s caught third interrupt signal; killed.\n\n", |
| SignalHandler::Get().GetGlobals()->options->product_name.c_str()); |
| if (SignalHandler::Get().GetGlobals()->server_pid != -1) { |
| KillServerProcess(SignalHandler::Get().GetGlobals()->server_pid); |
| } |
| ExitImmediately(1); |
| } |
| sigprintf( |
| "\n%s caught interrupt signal; shutting down.\n\n", |
| SignalHandler::Get().GetGlobals()->options->product_name.c_str()); |
| SignalHandler::Get().CancelServer(); |
| break; |
| case SIGTERM: |
| sigprintf( |
| "\n%s caught terminate signal; shutting down.\n\n", |
| SignalHandler::Get().GetGlobals()->options->product_name.c_str()); |
| SignalHandler::Get().CancelServer(); |
| break; |
| case SIGPIPE: |
| signal_handler_received_signal = SIGPIPE; |
| break; |
| case SIGQUIT: |
| sigprintf("\nSending SIGQUIT to JVM process %d (see %s).\n\n", |
| SignalHandler::Get().GetGlobals()->server_pid, |
| SignalHandler::Get().GetGlobals()->jvm_log_file.c_str()); |
| kill(SignalHandler::Get().GetGlobals()->server_pid, SIGQUIT); |
| break; |
| } |
| |
| errno = saved_errno; |
| } |
| |
| void SignalHandler::Install(GlobalVariables* globals, |
| SignalHandler::Callback cancel_server) { |
| _globals = globals; |
| _cancel_server = cancel_server; |
| |
| // Unblock all signals. |
| sigset_t sigset; |
| sigemptyset(&sigset); |
| sigprocmask(SIG_SETMASK, &sigset, NULL); |
| |
| signal(SIGINT, handler); |
| signal(SIGTERM, handler); |
| signal(SIGPIPE, handler); |
| signal(SIGQUIT, handler); |
| } |
| |
| ATTRIBUTE_NORETURN void SignalHandler::PropagateSignalOrExit(int exit_code) { |
| if (signal_handler_received_signal) { |
| // Kill ourselves with the same signal, so that callers see the |
| // right WTERMSIG value. |
| signal(signal_handler_received_signal, SIG_DFL); |
| raise(signal_handler_received_signal); |
| exit(1); // (in case raise didn't kill us for some reason) |
| } else { |
| 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. |
| static void sigprintf(const char *format, ...) { |
| 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. |
| } |
| } |
| |
| #endif // COMPILER_MSVC |
| |
| static void PrintError(const string& op) { |
| DWORD last_error = ::GetLastError(); |
| if (last_error == 0) { |
| return; |
| } |
| |
| char* message_buffer; |
| size_t size = FormatMessageA( |
| FORMAT_MESSAGE_ALLOCATE_BUFFER |
| | FORMAT_MESSAGE_FROM_SYSTEM |
| | FORMAT_MESSAGE_IGNORE_INSERTS, |
| NULL, |
| last_error, |
| MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| (LPSTR) &message_buffer, |
| 0, |
| NULL); |
| |
| fprintf(stderr, "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() { |
| #ifdef COMPILER_MSVC |
| const size_t PATH_MAX = 4096; |
| #endif // COMPILER_MSVC |
| char buffer[PATH_MAX] = {}; |
| if (!GetModuleFileName(0, buffer, sizeof(buffer))) { |
| pdie(255, "Error %u getting executable file name\n", GetLastError()); |
| } |
| |
| // TODO(bazel-team): Implement proper handling for UNC paths |
| // (e.g. "\\?\C:\foo\bar") instead of erroring out when we see them. |
| if (strlen(buffer) == 0 || buffer[0] == '\\') { |
| PrintError("GetModuleFileName"); |
| buffer[PATH_MAX - 1] = '\0'; |
| pdie(255, "Error in GetSelfPath, buffer=(%s)", buffer); |
| } |
| return string(buffer); |
| } |
| |
| string GetOutputRoot() { |
| #ifdef COMPILER_MSVC |
| // GetTempPathA and GetEnvironmentVariableA only work properly when Bazel |
| // runs under cmd.exe, not when it's run from msys. |
| // We don't know the reason for this; what's sure is GetEnvironmentVariableA |
| // returns nothing for TEMP under msys, though it can retrieve WINDIR. |
| |
| char buf[MAX_PATH + 1]; |
| if (!GetTempPathA(sizeof(buf), buf)) { |
| PrintError("GetTempPath"); |
| pdie(255, "Could not retrieve the temp directory path"); |
| } |
| return buf; |
| #else // not COMPILER_MSVC |
| for (const char* i : {"TMPDIR", "TEMPDIR", "TMP", "TEMP"}) { |
| char* tmpdir = getenv(i); |
| if (tmpdir != NULL && strlen(tmpdir) > 0) { |
| return tmpdir; |
| } |
| } |
| |
| return "/var/tmp"; |
| #endif // COMPILER_MSVC |
| } |
| |
| uint64_t GetMillisecondsMonotonic() { |
| return WindowsClock::INSTANCE.GetMilliseconds(); |
| } |
| |
| uint64_t GetMillisecondsSinceProcessStart() { |
| return WindowsClock::INSTANCE.GetProcessMilliseconds(); |
| } |
| |
| 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) { |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team) 2016-11-18: decide whether we need this on Windows and |
| // implement or delete. |
| return ""; |
| #else // not COMPILER_MSVC |
| char server_cwd[PATH_MAX] = {}; |
| if (readlink( |
| ("/proc/" + ToString(pid) + "/cwd").c_str(), |
| server_cwd, sizeof(server_cwd)) < 0) { |
| return ""; |
| } |
| |
| return string(server_cwd); |
| #endif // COMPILER_MSVC |
| } |
| |
| bool IsSharedLibrary(const string &filename) { |
| return blaze_util::ends_with(filename, ".dll"); |
| } |
| |
| string GetDefaultHostJavabase() { |
| const char *javahome = getenv("JAVA_HOME"); |
| if (javahome == NULL) { |
| die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| "Error: JAVA_HOME not set."); |
| } |
| return javahome; |
| } |
| |
| namespace { |
| void ReplaceAll( |
| std::string* s, const std::string& pattern, const std::string with) { |
| size_t pos = 0; |
| while (true) { |
| size_t pos = s->find(pattern, pos); |
| if (pos == std::string::npos) return; |
| *s = s->replace(pos, pattern.length(), with); |
| pos += with.length(); |
| } |
| } |
| |
| // 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 { |
| char cmdline[MAX_CMDLINE_LENGTH]; |
| }; |
| static void CreateCommandLine(CmdLine* result, const string& exe, |
| const vector<string>& args_vector) { |
| string cmdline; |
| bool first = true; |
| for (const auto& s : args_vector) { |
| if (first) { |
| first = false; |
| // Skip first argument, instead use quoted executable name with ".exe" |
| // suffix. |
| cmdline.append("\""); |
| cmdline.append(exe); |
| cmdline.append(".exe"); |
| cmdline.append("\""); |
| continue; |
| } else { |
| cmdline.append(" "); |
| } |
| |
| bool has_space = s.find(" ") != string::npos; |
| |
| if (has_space) { |
| cmdline.append("\""); |
| } |
| |
| std::string::const_iterator it = s.begin(); |
| while (it != s.end()) { |
| char ch = *it++; |
| switch (ch) { |
| case '"': |
| // Escape double quotes |
| cmdline.append("\\\""); |
| break; |
| |
| case '\\': |
| if (it == s.end()) { |
| // Backslashes at the end of the string are quoted if we add quotes |
| cmdline.append(has_space ? "\\\\" : "\\"); |
| } else { |
| // Backslashes everywhere else are quoted if they are followed by a |
| // quote or a backslash |
| cmdline.append(*it == '"' || *it == '\\' ? "\\\\" : "\\"); |
| } |
| break; |
| |
| default: |
| cmdline.append(1, ch); |
| } |
| } |
| |
| if (has_space) { |
| cmdline.append("\""); |
| } |
| } |
| |
| if (cmdline.size() >= MAX_CMDLINE_LENGTH) { |
| pdie(blaze_exit_code::INTERNAL_ERROR, |
| "Command line too long: %s", cmdline.c_str()); |
| } |
| |
| // Copy command line into a mutable buffer. |
| // CreateProcess is allowed to mutate its command line argument. |
| strncpy(result->cmdline, cmdline.c_str(), MAX_CMDLINE_LENGTH - 1); |
| result->cmdline[MAX_CMDLINE_LENGTH - 1] = 0; |
| } |
| |
| } // namespace |
| |
| string RunProgram( |
| const string& exe, const vector<string>& args_vector) { |
| SECURITY_ATTRIBUTES sa = {0}; |
| |
| sa.nLength = sizeof(SECURITY_ATTRIBUTES); |
| sa.bInheritHandle = TRUE; |
| sa.lpSecurityDescriptor = NULL; |
| |
| HANDLE pipe_read, pipe_write; |
| if (!CreatePipe(&pipe_read, &pipe_write, &sa, 0)) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "CreatePipe"); |
| } |
| |
| if (!SetHandleInformation(pipe_read, HANDLE_FLAG_INHERIT, 0)) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "SetHandleInformation"); |
| } |
| |
| PROCESS_INFORMATION processInfo = {0}; |
| STARTUPINFO startupInfo = {0}; |
| |
| startupInfo.hStdError = pipe_write; |
| startupInfo.hStdOutput = pipe_write; |
| startupInfo.dwFlags |= STARTF_USESTDHANDLES; |
| CmdLine cmdline; |
| CreateCommandLine(&cmdline, exe, args_vector); |
| |
| bool ok = CreateProcess( |
| NULL, // _In_opt_ LPCTSTR lpApplicationName, |
| // _Inout_opt_ LPTSTR lpCommandLine, |
| cmdline.cmdline, |
| NULL, // _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, |
| NULL, // _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, |
| true, // _In_ BOOL bInheritHandles, |
| 0, // _In_ DWORD dwCreationFlags, |
| NULL, // _In_opt_ LPVOID lpEnvironment, |
| NULL, // _In_opt_ LPCTSTR lpCurrentDirectory, |
| &startupInfo, // _In_ LPSTARTUPINFO lpStartupInfo, |
| &processInfo); // _Out_ LPPROCESS_INFORMATION lpProcessInformation |
| |
| if (!ok) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| "RunProgram/CreateProcess: Error %d while executing %s", |
| GetLastError(), cmdline.cmdline); |
| } |
| |
| CloseHandle(pipe_write); |
| std::string result = ""; |
| DWORD bytes_read; |
| CHAR buf[1024]; |
| |
| for (;;) { |
| ok = ::ReadFile(pipe_read, buf, 1023, &bytes_read, NULL); |
| if (!ok || bytes_read == 0) { |
| break; |
| } |
| buf[bytes_read] = 0; |
| result = result + buf; |
| } |
| |
| CloseHandle(pipe_read); |
| CloseHandle(processInfo.hProcess); |
| CloseHandle(processInfo.hThread); |
| |
| return result; |
| } |
| |
| // If we pass DETACHED_PROCESS to CreateProcess(), cmd.exe appropriately |
| // returns the command prompt when the client terminates. msys2, however, in |
| // its infinite wisdom, waits until the *server* terminates and cannot be |
| // convinced otherwise. |
| // |
| // So, we first pretend to be a POSIX daemon so that msys2 knows about our |
| // intentions and *then* we call CreateProcess(). Life ain't easy. |
| static bool DaemonizeOnWindows() { |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team) 2016-11-18: implement this. |
| return false; |
| #else // not COMPILER_MSVC |
| if (fork() > 0) { |
| // We are the original client process. |
| return true; |
| } |
| |
| if (fork() > 0) { |
| // We are the child of the original client process. Terminate so that the |
| // actual server is not a child process of the client. |
| exit(0); |
| } |
| |
| setsid(); |
| // Contrary to the POSIX version, we are not closing the three standard file |
| // descriptors here. CreateProcess() will take care of that and it's useful |
| // to see the error messages in ExecuteDaemon() on the console of the client. |
| return false; |
| #endif // COMPILER_MSVC |
| } |
| |
| // Keeping an eye on the server process on Windows is not implemented yet. |
| // TODO(lberki): Implement this, because otherwise if we can't start up a server |
| // process, the client will hang until it times out. |
| class DummyBlazeServerStartup : public BlazeServerStartup { |
| public: |
| DummyBlazeServerStartup() {} |
| virtual ~DummyBlazeServerStartup() {} |
| virtual bool IsStillAlive() { return true; } |
| }; |
| |
| void ExecuteDaemon(const string& exe, const std::vector<string>& args_vector, |
| const string& daemon_output, const string& server_dir, |
| BlazeServerStartup** server_startup) { |
| if (DaemonizeOnWindows()) { |
| // We are the client process |
| *server_startup = new DummyBlazeServerStartup(); |
| return; |
| } |
| |
| SECURITY_ATTRIBUTES sa; |
| sa.nLength = sizeof(SECURITY_ATTRIBUTES); |
| // We redirect stdout and stderr by telling CreateProcess to use a file handle |
| // we open below and these handles must be inheriatable |
| sa.bInheritHandle = TRUE; |
| sa.lpSecurityDescriptor = NULL; |
| |
| HANDLE output_file = CreateFile( |
| ConvertPath(daemon_output).c_str(), // lpFileName |
| GENERIC_READ | GENERIC_WRITE, // dwDesiredAccess |
| // So that the file can be read while the server is running |
| FILE_SHARE_READ, // dwShareMode |
| &sa, // lpSecurityAttributes |
| CREATE_ALWAYS, // dwCreationDisposition |
| FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes |
| NULL); // hTemplateFile |
| |
| if (output_file == INVALID_HANDLE_VALUE) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "CreateFile"); |
| } |
| |
| HANDLE pipe_read, pipe_write; |
| if (!CreatePipe(&pipe_read, &pipe_write, &sa, 0)) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "CreatePipe"); |
| } |
| |
| if (!SetHandleInformation(pipe_write, HANDLE_FLAG_INHERIT, 0)) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "SetHandleInformation"); |
| } |
| |
| PROCESS_INFORMATION processInfo = {0}; |
| STARTUPINFO startupInfo = {0}; |
| |
| startupInfo.hStdInput = pipe_read; |
| startupInfo.hStdError = output_file; |
| startupInfo.hStdOutput = output_file; |
| startupInfo.dwFlags |= STARTF_USESTDHANDLES; |
| CmdLine cmdline; |
| CreateCommandLine(&cmdline, exe, args_vector); |
| |
| // Propagate BAZEL_SH environment variable to a sub-process. |
| // todo(dslomov): More principled approach to propagating |
| // environment variables. |
| SetEnvironmentVariable("BAZEL_SH", getenv("BAZEL_SH")); |
| |
| bool ok = CreateProcess( |
| NULL, // _In_opt_ LPCTSTR lpApplicationName, |
| // _Inout_opt_ LPTSTR lpCommandLine, |
| cmdline.cmdline, |
| NULL, // _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, |
| NULL, // _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, |
| TRUE, // _In_ BOOL bInheritHandles, |
| // _In_ DWORD dwCreationFlags, |
| DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP, |
| NULL, // _In_opt_ LPVOID lpEnvironment, |
| NULL, // _In_opt_ LPCTSTR lpCurrentDirectory, |
| &startupInfo, // _In_ LPSTARTUPINFO lpStartupInfo, |
| &processInfo); // _Out_ LPPROCESS_INFORMATION lpProcessInformation |
| |
| if (!ok) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| "ExecuteDaemon/CreateProcess: error %u executing: %s\n", |
| GetLastError(), cmdline.cmdline); |
| } |
| |
| CloseHandle(output_file); |
| CloseHandle(pipe_write); |
| CloseHandle(pipe_read); |
| |
| 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()); |
| } |
| |
| CloseHandle(processInfo.hProcess); |
| CloseHandle(processInfo.hThread); |
| |
| exit(0); |
| } |
| |
| void BatchWaiterThread(HANDLE java_handle) { |
| WaitForSingleObject(java_handle, INFINITE); |
| } |
| |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team): implement signal handling. |
| #else // not COMPILER_MSVC |
| static void MingwSignalHandler(int signum) { |
| // Java process will be terminated because we set the job to terminate if its |
| // handle is closed. |
| // |
| // Note that this is different how interruption is handled on Unix, where the |
| // Java process sets up a signal handler for SIGINT itself. That cannot be |
| // done on Windows without using native code, and it's better to have as |
| // little JNI as possible. The most important part of the cleanup after |
| // termination (killing all child processes) happens automatically on Windows |
| // anyway, since we put the batch Java process in its own job which does not |
| // allow breakaway processes. |
| exit(blaze_exit_code::ExitCode::INTERRUPTED); |
| } |
| #endif // COMPILER_MSVC |
| |
| // Returns whether assigning the given process to a job failed because nested |
| // jobs are not available on the current system. |
| static bool IsFailureDueToNestedJobsNotSupported(HANDLE process) { |
| BOOL is_in_job; |
| if (!IsProcessInJob(process, NULL, &is_in_job)) { |
| PrintError("IsProcessInJob()"); |
| return false; |
| } |
| |
| if (!is_in_job) { |
| // Not in a job. |
| return false; |
| } |
| |
| OSVERSIONINFOEX version_info; |
| version_info.dwOSVersionInfoSize = sizeof(version_info); |
| if (!GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info))) { |
| PrintError("GetVersionEx()"); |
| return false; |
| } |
| |
| return version_info.dwMajorVersion < 6 |
| || version_info.dwMajorVersion == 6 && version_info.dwMinorVersion <= 1; |
| } |
| |
| // Run the given program in the current working directory, |
| // using the given argument vector. |
| void ExecuteProgram( |
| const string& exe, const vector<string>& args_vector) { |
| CmdLine cmdline; |
| CreateCommandLine(&cmdline, exe, args_vector); |
| |
| STARTUPINFO startupInfo = {0}; |
| PROCESS_INFORMATION processInfo = {0}; |
| |
| // Propagate BAZEL_SH environment variable to a sub-process. |
| // todo(dslomov): More principled approach to propagating |
| // environment variables. |
| SetEnvironmentVariable("BAZEL_SH", getenv("BAZEL_SH")); |
| |
| HANDLE job = CreateJobObject(NULL, NULL); |
| if (job == NULL) { |
| pdie(255, "Error %u while creating job\n", GetLastError()); |
| } |
| |
| JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = { 0 }; |
| job_info.BasicLimitInformation.LimitFlags = |
| JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; |
| if (!SetInformationJobObject( |
| job, |
| JobObjectExtendedLimitInformation, |
| &job_info, |
| sizeof(job_info))) { |
| pdie(255, "Error %u while setting up job\n", GetLastError()); |
| } |
| |
| bool success = CreateProcess( |
| NULL, // _In_opt_ LPCTSTR lpApplicationName, |
| // _Inout_opt_ LPTSTR lpCommandLine, |
| cmdline.cmdline, |
| NULL, // _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, |
| NULL, // _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, |
| true, // _In_ BOOL bInheritHandles, |
| // _In_ DWORD dwCreationFlags, |
| CREATE_NEW_PROCESS_GROUP // So that Ctrl-Break does not affect it |
| | CREATE_BREAKAWAY_FROM_JOB // We'll put it in a new job |
| | CREATE_SUSPENDED, // So that it doesn't start a new job itself |
| NULL, // _In_opt_ LPVOID lpEnvironment, |
| NULL, // _In_opt_ LPCTSTR lpCurrentDirectory, |
| &startupInfo, // _In_ LPSTARTUPINFO lpStartupInfo, |
| &processInfo); // _Out_ LPPROCESS_INFORMATION lpProcessInformation |
| |
| if (!success) { |
| pdie(255, "ExecuteProgram/CreateProcess: error %u executing: %s\n", |
| GetLastError(), cmdline.cmdline); |
| } |
| |
| if (!AssignProcessToJobObject(job, processInfo.hProcess)) { |
| if (!IsFailureDueToNestedJobsNotSupported(processInfo.hProcess)) { |
| pdie(255, "Error %u while assigning process to job\n", GetLastError()); |
| } |
| |
| // Otherwise, the OS doesn't support nested jobs so we'll just have to |
| // make do without. |
| } |
| |
| // Now that we put the process in a new job object, we can start executing it |
| if (ResumeThread(processInfo.hThread) == -1) { |
| pdie(255, "Error %u while starting Java process\n", GetLastError()); |
| } |
| |
| // msys doesn't deliver signals while a Win32 call is pending so we need to |
| // do the blocking call in another thread |
| |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team): implement signal handling. |
| #else // not COMPILER_MSVC |
| signal(SIGINT, MingwSignalHandler); |
| #endif // COMPILER_MSVC |
| std::thread batch_waiter_thread([=]() { |
| BatchWaiterThread(processInfo.hProcess); |
| }); |
| |
| // The output base lock is held while waiting |
| batch_waiter_thread.join(); |
| DWORD exit_code; |
| GetExitCodeProcess(processInfo.hProcess, &exit_code); |
| CloseHandle(processInfo.hProcess); |
| CloseHandle(processInfo.hThread); |
| exit(exit_code); |
| } |
| |
| string ListSeparator() { return ";"; } |
| |
| string ConvertPath(const string& path) { |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team): implement this. |
| pdie(255, "blaze::ConvertPath is not implemented on Windows"); |
| return ""; |
| #else // not COMPILER_MSVC |
| // If the path looks like %USERPROFILE%/foo/bar, don't convert. |
| if (path.empty() || path[0] == '%') { |
| return path; |
| } |
| char* wpath = static_cast<char*>(cygwin_create_path( |
| CCP_POSIX_TO_WIN_A, static_cast<const void*>(path.c_str()))); |
| string result(wpath); |
| free(wpath); |
| return result; |
| #endif // COMPILER_MSVC |
| } |
| |
| // Convert a Unix path list to Windows path list |
| string ConvertPathList(const string& path_list) { |
| string w_list = ""; |
| int start = 0; |
| int pos; |
| while ((pos = path_list.find(":", start)) != string::npos) { |
| w_list += ConvertPath(path_list.substr(start, pos - start)) + ";"; |
| start = pos + 1; |
| } |
| if (start < path_list.size()) { |
| w_list += ConvertPath(path_list.substr(start)); |
| } |
| return w_list; |
| } |
| |
| static string ConvertPathToPosix(const string& win_path) { |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team) 2016-11-18: verify that this function is not needed on |
| // Windows. |
| return win_path; |
| #else // not COMPILER_MSVC |
| char* posix_path = static_cast<char*>(cygwin_create_path( |
| CCP_WIN_A_TO_POSIX, static_cast<const void*>(win_path.c_str()))); |
| string result(posix_path); |
| free(posix_path); |
| return result; |
| #endif // COMPILER_MSVC |
| } |
| |
| // Cribbed from ntifs.h, not present in windows.h |
| |
| #define REPARSE_MOUNTPOINT_HEADER_SIZE 8 |
| |
| typedef struct { |
| DWORD ReparseTag; |
| WORD ReparseDataLength; |
| WORD Reserved; |
| WORD SubstituteNameOffset; |
| WORD SubstituteNameLength; |
| WORD PrintNameOffset; |
| WORD PrintNameLength; |
| WCHAR PathBuffer[ANYSIZE_ARRAY]; |
| } REPARSE_MOUNTPOINT_DATA_BUFFER, *PREPARSE_MOUNTPOINT_DATA_BUFFER; |
| |
| HANDLE OpenDirectory(const string& path, bool readWrite) { |
| HANDLE result = ::CreateFile( |
| path.c_str(), |
| readWrite ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ, |
| 0, |
| NULL, |
| OPEN_EXISTING, |
| FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, |
| NULL); |
| if (result == INVALID_HANDLE_VALUE) { |
| PrintError("CreateFile(" + path + ")"); |
| } |
| |
| return result; |
| } |
| |
| bool SymlinkDirectories(const string &posix_target, const string &posix_name) { |
| string target = ConvertPath(posix_target); |
| string name = ConvertPath(posix_name); |
| |
| // Junctions are directories, so create one |
| if (!::CreateDirectory(name.c_str(), NULL)) { |
| PrintError("CreateDirectory(" + name + ")"); |
| return false; |
| } |
| |
| HANDLE directory = OpenDirectory(name, true); |
| if (directory == INVALID_HANDLE_VALUE) { |
| return false; |
| } |
| |
| char reparse_buffer_bytes[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; |
| REPARSE_MOUNTPOINT_DATA_BUFFER* reparse_buffer = |
| reinterpret_cast<REPARSE_MOUNTPOINT_DATA_BUFFER *>(reparse_buffer_bytes); |
| memset(reparse_buffer_bytes, 0, MAXIMUM_REPARSE_DATA_BUFFER_SIZE); |
| |
| // non-parsed path prefix. Required for junction targets. |
| string prefixed_target = "\\??\\" + target; |
| int prefixed_target_length = ::MultiByteToWideChar( |
| CP_ACP, |
| 0, |
| prefixed_target.c_str(), |
| -1, |
| reparse_buffer->PathBuffer, |
| MAX_PATH); |
| if (prefixed_target_length == 0) { |
| PrintError("MultiByteToWideChar(" + prefixed_target + ")"); |
| CloseHandle(directory); |
| return false; |
| } |
| |
| // In addition to their target, junctions also have another string which |
| // tells which target to show to the user. mklink cuts of the \??\ part, so |
| // that's what we do, too. |
| int target_length = ::MultiByteToWideChar( |
| CP_UTF8, |
| 0, |
| target.c_str(), |
| -1, |
| reparse_buffer->PathBuffer + prefixed_target_length, |
| MAX_PATH); |
| if (target_length == 0) { |
| PrintError("MultiByteToWideChar(" + target + ")"); |
| CloseHandle(directory); |
| return false; |
| } |
| |
| reparse_buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; |
| reparse_buffer->PrintNameOffset = prefixed_target_length * sizeof(WCHAR); |
| reparse_buffer->PrintNameLength = (target_length - 1) * sizeof(WCHAR); |
| reparse_buffer->SubstituteNameLength = |
| (prefixed_target_length - 1) * sizeof(WCHAR); |
| reparse_buffer->SubstituteNameOffset = 0; |
| reparse_buffer->Reserved = 0; |
| reparse_buffer->ReparseDataLength = |
| reparse_buffer->SubstituteNameLength + |
| reparse_buffer->PrintNameLength + 12; |
| |
| DWORD bytes_returned; |
| bool result = ::DeviceIoControl( |
| directory, |
| FSCTL_SET_REPARSE_POINT, |
| reparse_buffer, |
| reparse_buffer->ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE, |
| NULL, |
| 0, |
| &bytes_returned, |
| NULL); |
| if (!result) { |
| PrintError("DeviceIoControl(FSCTL_SET_REPARSE_POINT, " + name + ")"); |
| } |
| CloseHandle(directory); |
| return result; |
| } |
| |
| bool ReadDirectorySymlink(const string &posix_name, string* result) { |
| string name = ConvertPath(posix_name); |
| HANDLE directory = OpenDirectory(name, false); |
| if (directory == INVALID_HANDLE_VALUE) { |
| return false; |
| } |
| |
| char reparse_buffer_bytes[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; |
| REPARSE_MOUNTPOINT_DATA_BUFFER* reparse_buffer = |
| reinterpret_cast<REPARSE_MOUNTPOINT_DATA_BUFFER *>(reparse_buffer_bytes); |
| memset(reparse_buffer_bytes, 0, MAXIMUM_REPARSE_DATA_BUFFER_SIZE); |
| |
| reparse_buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; |
| DWORD bytes_returned; |
| bool ok = ::DeviceIoControl( |
| directory, |
| FSCTL_GET_REPARSE_POINT, |
| NULL, |
| 0, |
| reparse_buffer, |
| MAXIMUM_REPARSE_DATA_BUFFER_SIZE, |
| &bytes_returned, |
| NULL); |
| if (!ok) { |
| PrintError("DeviceIoControl(FSCTL_GET_REPARSE_POINT, " + name + ")"); |
| } |
| |
| CloseHandle(directory); |
| if (!ok) { |
| return false; |
| } |
| |
| vector<char> print_name(reparse_buffer->PrintNameLength * sizeof(WCHAR) + 1); |
| int count = ::WideCharToMultiByte( |
| CP_UTF8, |
| 0, |
| reparse_buffer->PathBuffer + |
| (reparse_buffer->PrintNameOffset / sizeof(WCHAR)), |
| reparse_buffer->PrintNameLength, |
| &print_name[0], |
| print_name.size(), |
| NULL, |
| NULL); |
| if (count == 0) { |
| PrintError("WideCharToMultiByte()"); |
| *result = ""; |
| return false; |
| } else { |
| *result = ConvertPathToPosix(&print_name[0]); |
| return true; |
| } |
| } |
| |
| static bool IsAbsoluteWindowsPath(const string& p) { |
| if (p.size() < 3) { |
| return false; |
| } |
| |
| if (p.substr(1, 2) == ":/") { |
| return true; |
| } |
| |
| if (p.substr(1, 2) == ":\\") { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool CompareAbsolutePaths(const string& a, const string& b) { |
| string a_real = IsAbsoluteWindowsPath(a) ? ConvertPathToPosix(a) : a; |
| string b_real = IsAbsoluteWindowsPath(b) ? ConvertPathToPosix(b) : b; |
| return a_real == b_real; |
| } |
| |
| bool VerifyServerProcess( |
| int pid, const string& output_base, const string& install_base) { |
| // TODO(lberki): This might accidentally kill an unrelated process if the |
| // server died and the PID got reused. |
| return true; |
| } |
| |
| bool KillServerProcess(int pid) { |
| HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, pid); |
| if (process == NULL) { |
| // Cannot find the server process. Can happen if the PID file is stale. |
| return false; |
| } |
| |
| bool result = TerminateProcess(process, /*uExitCode*/0); |
| if (!result) { |
| fprintf(stderr, "Cannot terminate server process with PID %d\n", pid); |
| } |
| |
| CloseHandle(process); |
| return result; |
| } |
| |
| // Not supported. |
| void ExcludePathFromBackup(const string &path) { |
| } |
| |
| string GetHashedBaseDir(const string& root, const string& hashable) { |
| // Builds a shorter output base dir name for Windows. |
| // This algorithm only uses 1/3 of the bits to get 8-char alphanumeric |
| // file name. |
| |
| static const char* alphabet |
| // Exactly 64 characters. |
| = "abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789$-"; |
| |
| // The length of the resulting filename (8 characters). |
| static const int filename_length = blaze_util::Md5Digest::kDigestLength / 2; |
| unsigned char buf[blaze_util::Md5Digest::kDigestLength]; |
| char coded_name[filename_length + 1]; |
| blaze_util::Md5Digest digest; |
| digest.Update(hashable.data(), hashable.size()); |
| digest.Finish(buf); |
| for (int i = 0; i < filename_length; i++) { |
| coded_name[i] = alphabet[buf[i] & 0x3F]; |
| } |
| coded_name[filename_length] = '\0'; |
| return root + "/" + string(coded_name); |
| } |
| |
| void CreateSecureOutputRoot(const string& path) { |
| // TODO(bazel-team) 2016-11-26: implement this function without using the |
| // POSIX API, then get rid of the POSIX version, which is a copy of the |
| // blaze_util_posix version of the same method. |
| |
| #ifdef COMPILER_MSVC |
| pdie(255, "blaze::CreateSecureOutputRoot is not implemented on Windows"); |
| #else // not COMPILER_MSVC |
| const char* root = path.c_str(); |
| struct stat fileinfo = {}; |
| |
| if (!MakeDirectories(root, 0755)) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "mkdir('%s')", root); |
| } |
| |
| // The path already exists. |
| // Check ownership and mode, and verify that it is a directory. |
| |
| if (lstat(root, &fileinfo) < 0) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "lstat('%s')", root); |
| } |
| |
| if (fileinfo.st_uid != geteuid()) { |
| die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' is not owned by me", |
| root); |
| } |
| |
| if ((fileinfo.st_mode & 022) != 0) { |
| int new_mode = fileinfo.st_mode & (~022); |
| if (chmod(root, new_mode) < 0) { |
| die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| "'%s' has mode %o, chmod to %o failed", root, |
| fileinfo.st_mode & 07777, new_mode); |
| } |
| } |
| |
| if (stat(root, &fileinfo) < 0) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "stat('%s')", root); |
| } |
| |
| if (!S_ISDIR(fileinfo.st_mode)) { |
| die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' is not a directory", |
| root); |
| } |
| |
| ExcludePathFromBackup(root); |
| #endif // COMPILER_MSVC |
| } |
| |
| #ifdef COMPILER_MSVC |
| bool MakeDirectories(const string& path, unsigned int mode) { |
| // TODO(bazel-team): implement this. |
| pdie(255, "blaze::MakeDirectories is not implemented on Windows"); |
| return false; |
| } |
| #else // not COMPILER_MSVC |
| // Runs "stat" on `path`. Returns -1 and sets errno if stat fails or |
| // `path` isn't a directory. If check_perms is true, this will also |
| // make sure that `path` is owned by the current user and has `mode` |
| // permissions (observing the umask). It attempts to run chmod to |
| // correct the mode if necessary. If `path` is a symlink, this will |
| // check ownership of the link, not the underlying directory. |
| static bool GetDirectoryStat(const string& path, mode_t mode, |
| bool check_perms) { |
| struct stat filestat = {}; |
| if (stat(path.c_str(), &filestat) == -1) { |
| return false; |
| } |
| |
| if (!S_ISDIR(filestat.st_mode)) { |
| errno = ENOTDIR; |
| return false; |
| } |
| |
| if (check_perms) { |
| // If this is a symlink, run checks on the link. (If we did lstat above |
| // then it would return false for ISDIR). |
| struct stat linkstat = {}; |
| if (lstat(path.c_str(), &linkstat) != 0) { |
| return false; |
| } |
| if (linkstat.st_uid != geteuid()) { |
| // The directory isn't owned by me. |
| errno = EACCES; |
| return false; |
| } |
| |
| mode_t mask = umask(022); |
| umask(mask); |
| mode = (mode & ~mask); |
| if ((filestat.st_mode & 0777) != mode && chmod(path.c_str(), mode) == -1) { |
| // errno set by chmod. |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static bool MakeDirectories(const string& path, mode_t mode, bool childmost) { |
| if (path.empty() || path == "/") { |
| errno = EACCES; |
| return false; |
| } |
| |
| bool stat_succeeded = GetDirectoryStat(path, mode, childmost); |
| if (stat_succeeded) { |
| return true; |
| } |
| |
| if (errno == ENOENT) { |
| // Path does not exist, attempt to create its parents, then it. |
| string parent = blaze_util::Dirname(path); |
| if (!MakeDirectories(parent, mode, false)) { |
| // errno set by stat. |
| return false; |
| } |
| |
| if (mkdir(path.c_str(), mode) == -1) { |
| if (errno == EEXIST) { |
| if (childmost) { |
| // If there are multiple bazel calls at the same time then the |
| // directory could be created between the MakeDirectories and mkdir |
| // calls. This is okay, but we still have to check the permissions. |
| return GetDirectoryStat(path, mode, childmost); |
| } else { |
| // If this isn't the childmost directory, we don't care what the |
| // permissions were. If it's not even a directory then that error will |
| // get caught when we attempt to create the next directory down the |
| // chain. |
| return true; |
| } |
| } |
| // errno set by mkdir. |
| return false; |
| } |
| return true; |
| } |
| |
| return stat_succeeded; |
| } |
| |
| // mkdir -p path. Returns 0 if the path was created or already exists and could |
| // be chmod-ed to exactly the given permissions. If final part of the path is a |
| // symlink, this ensures that the destination of the symlink has the desired |
| // permissions. It also checks that the directory or symlink is owned by us. |
| // On failure, this returns -1 and sets errno. |
| bool MakeDirectories(const string& path, mode_t mode) { |
| return MakeDirectories(path, mode, true); |
| } |
| #endif // COMPILER_MSVC |
| |
| string GetEnv(const string& name) { |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team): implement this. |
| pdie(255, "blaze::GetEnv is not implemented on Windows"); |
| return ""; |
| #else // not COMPILER_MSVC |
| char* result = getenv(name.c_str()); |
| return result != NULL ? string(result) : ""; |
| #endif // COMPILER_MSVC |
| } |
| |
| void SetEnv(const string& name, const string& value) { |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team): implement this. |
| pdie(255, "blaze::SetEnv is not implemented on Windows"); |
| #else // not COMPILER_MSVC |
| setenv(name.c_str(), value.c_str(), 1); |
| #endif // COMPILER_MSVC |
| } |
| |
| void UnsetEnv(const string& name) { |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team): implement this. |
| pdie(255, "blaze::UnsetEnv is not implemented on Windows"); |
| #else // not COMPILER_MSVC |
| unsetenv(name.c_str()); |
| #endif // COMPILER_MSVC |
| } |
| |
| ATTRIBUTE_NORETURN void ExitImmediately(int exit_code) { |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team): implement this. |
| pdie(255, "blaze::ExitImmediately is not implemented on Windows"); |
| #else // not COMPILER_MSVC |
| _exit(exit_code); |
| #endif // COMPILER_MSVC |
| } |
| |
| void SetupStdStreams() { |
| #ifdef COMPILER_MSVC |
| // TODO(bazel-team): implement this. |
| pdie(255, "blaze::SetupStdStreams is not implemented on Windows"); |
| #else // not COMPILER_MSVC |
| // Set non-buffered output mode for stderr/stdout. The server already |
| // line-buffers messages where it makes sense, so there's no need to do set |
| // line-buffering here. On the other hand the server sometimes sends binary |
| // output (when for example a query returns results as proto), in which case |
| // we must not perform line buffering on the client side. So turn off |
| // buffering here completely. |
| setvbuf(stdout, NULL, _IONBF, 0); |
| setvbuf(stderr, NULL, _IONBF, 0); |
| |
| // Ensure we have three open fds. Otherwise we can end up with |
| // bizarre things like stdout going to the lock file, etc. |
| if (fcntl(STDIN_FILENO, F_GETFL) == -1) open("/dev/null", O_RDONLY); |
| if (fcntl(STDOUT_FILENO, F_GETFL) == -1) open("/dev/null", O_WRONLY); |
| if (fcntl(STDERR_FILENO, F_GETFL) == -1) open("/dev/null", O_WRONLY); |
| #endif // COMPILER_MSVC |
| } |
| |
| LARGE_INTEGER WindowsClock::GetFrequency() { |
| LARGE_INTEGER result; |
| if (!QueryPerformanceFrequency(&result)) { |
| PrintError("QueryPerformanceFrequency"); |
| pdie(255, "Error getting time resolution\n"); |
| } |
| |
| // 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) { |
| pdie(255, "QueryPerformanceFrequency returned invalid result (%llu)\n", |
| result.QuadPart); |
| } |
| |
| return result; |
| } |
| |
| LARGE_INTEGER WindowsClock::GetMillisecondsAsLargeInt( |
| const LARGE_INTEGER& freq) { |
| LARGE_INTEGER counter; |
| if (!QueryPerformanceCounter(&counter)) { |
| PrintError("QueryPerformanceCounter"); |
| pdie(255, "Error getting performance counter\n"); |
| } |
| |
| 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()), |
| kStart(GetMillisecondsAsLargeInt(kFrequency)) {} |
| |
| uint64_t WindowsClock::GetMilliseconds() const { |
| return GetMillisecondsAsLargeInt(kFrequency).QuadPart; |
| } |
| |
| uint64_t WindowsClock::GetProcessMilliseconds() const { |
| return GetMilliseconds() - kStart.QuadPart; |
| } |
| |
| uint64_t AcquireLock(const string& output_base, bool batch_mode, bool block, |
| BlazeLock* blaze_lock) { |
| #ifdef COMPILER_MSVC |
| pdie(255, "blaze::AcquireLock is not implemented on Windows"); |
| return 0; |
| #else // not COMPILER_MSVC |
| string lockfile = output_base + "/lock"; |
| int lockfd = open(lockfile.c_str(), O_CREAT|O_RDWR, 0644); |
| |
| if (lockfd < 0) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| "cannot open lockfile '%s' for writing", lockfile.c_str()); |
| } |
| |
| // Keep server from inheriting a useless fd if we are not in batch mode |
| if (!batch_mode) { |
| if (fcntl(lockfd, F_SETFD, FD_CLOEXEC) == -1) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| "fcntl(F_SETFD) failed for lockfile"); |
| } |
| } |
| |
| struct flock lock; |
| lock.l_type = F_WRLCK; |
| lock.l_whence = SEEK_SET; |
| lock.l_start = 0; |
| // This doesn't really matter now, but allows us to subdivide the lock |
| // later if that becomes meaningful. (Ranges beyond EOF can be locked.) |
| lock.l_len = 4096; |
| |
| uint64_t wait_time = 0; |
| // Try to take the lock, without blocking. |
| if (fcntl(lockfd, F_SETLK, &lock) == -1) { |
| if (errno != EACCES && errno != EAGAIN) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| "unexpected result from F_SETLK"); |
| } |
| |
| // We didn't get the lock. Find out who has it. |
| struct flock probe = lock; |
| probe.l_pid = 0; |
| if (fcntl(lockfd, F_GETLK, &probe) == -1) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| "unexpected result from F_GETLK"); |
| } |
| if (!block) { |
| die(blaze_exit_code::BAD_ARGV, |
| "Another command is running (pid=%d). Exiting immediately.", |
| probe.l_pid); |
| } |
| fprintf(stderr, "Another command is running (pid = %d). " |
| "Waiting for it to complete...", probe.l_pid); |
| fflush(stderr); |
| |
| // Take a clock sample for that start of the waiting time |
| uint64_t st = GetMillisecondsMonotonic(); |
| // Try to take the lock again (blocking). |
| int r; |
| do { |
| r = fcntl(lockfd, F_SETLKW, &lock); |
| } while (r == -1 && errno == EINTR); |
| fprintf(stderr, "\n"); |
| if (r == -1) { |
| pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| "couldn't acquire file lock"); |
| } |
| // Take another clock sample, calculate elapsed |
| uint64_t et = GetMillisecondsMonotonic(); |
| wait_time = et - st; |
| } |
| |
| // Identify ourselves in the lockfile. |
| (void) ftruncate(lockfd, 0); |
| const char *tty = ttyname(STDIN_FILENO); // NOLINT (single-threaded) |
| string msg = "owner=launcher\npid=" |
| + ToString(getpid()) + "\ntty=" + (tty ? tty : "") + "\n"; |
| // The contents are currently meant only for debugging. |
| (void) write(lockfd, msg.data(), msg.size()); |
| blaze_lock->lockfd = lockfd; |
| return wait_time; |
| #endif // COMPILER_MSVC |
| } |
| |
| void ReleaseLock(BlazeLock* blaze_lock) { |
| #ifdef COMPILER_MSVC |
| pdie(255, "blaze::AcquireLock is not implemented on Windows"); |
| #else // not COMPILER_MSVC |
| close(blaze_lock->lockfd); |
| #endif // COMPILER_MSVC |
| } |
| |
| } // namespace blaze |