From 8896dad21f6993d4b9fd74d3a332143e75080d06 Mon Sep 17 00:00:00 2001 From: Laszlo Csomor Date: Wed, 28 Sep 2016 15:02:13 +0000 Subject: Windows, native: add windows_msvc config_setting Update `select` statements in BUILD files with the new config_setting. This is a first step on a long path that leads to us being able to compile bazel on Windows with --cpu=x64_windows_msvc. Needless to say, we're not there yet. Tested: on Linux, Darwin, Windows/MSYS -- MOS_MIGRATED_REVID=134534613 --- src/main/cpp/BUILD | 3 + src/main/cpp/blaze_util_msvc.cc | 783 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 786 insertions(+) create mode 100644 src/main/cpp/blaze_util_msvc.cc (limited to 'src/main/cpp') diff --git a/src/main/cpp/BUILD b/src/main/cpp/BUILD index 64de8eb688..b52b292d05 100644 --- a/src/main/cpp/BUILD +++ b/src/main/cpp/BUILD @@ -24,6 +24,9 @@ cc_library( "//src:windows": [ "blaze_util_mingw.cc", ], + "//src:windows_msvc": [ + "blaze_util_msvc.cc", + ], "//conditions:default": [ "blaze_util_linux.cc", "blaze_util_posix.cc", diff --git a/src/main/cpp/blaze_util_msvc.cc b/src/main/cpp/blaze_util_msvc.cc new file mode 100644 index 0000000000..9136c61a5b --- /dev/null +++ b/src/main/cpp/blaze_util_msvc.cc @@ -0,0 +1,783 @@ +// 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, ENAMETOOLONG +#include +#include // strerror +#include +#include +#include +#include + +#include + +#include +#include +#include // 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/util/errors.h" +#include "src/main/cpp/util/exit_code.h" +#include "src/main/cpp/util/file.h" +#include "src/main/cpp/util/strings.h" + +namespace blaze { + +using blaze_util::die; +using blaze_util::pdie; +using std::string; +using std::vector; + +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 GetSelfPath() { + char buffer[PATH_MAX] = {}; + if (!GetModuleFileName(0, buffer, sizeof(buffer))) { + pdie(255, "Error %u getting executable file name\n", GetLastError()); + } + return string(buffer); +} + +string GetOutputRoot() { + char* tmpdir = getenv("TMPDIR"); + if (tmpdir == 0 || strlen(tmpdir) == 0) { + return "/var/tmp"; + } else { + return string(tmpdir); + } +} + +uint64_t MonotonicClock() { + struct timespec ts = {}; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +uint64_t ProcessClock() { + struct timespec ts = {}; + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts); + return ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +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) { + char server_cwd[PATH_MAX] = {}; + if (readlink( + ("/proc/" + ToString(pid) + "/cwd").c_str(), + server_cwd, sizeof(server_cwd)) < 0) { + return ""; + } + + return string(server_cwd); +} + +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& 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& 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() { + 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; +} + +// 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& 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 (!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); +} + +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); +} + +// 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(&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& 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 + signal(SIGINT, MingwSignalHandler); + 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) { + // If the path looks like %USERPROFILE%/foo/bar, don't convert. + if (path.empty() || path[0] == '%') { + return path; + } + char* wpath = static_cast(cygwin_create_path( + CCP_POSIX_TO_WIN_A, static_cast(path.c_str()))); + string result(wpath); + free(wpath); + return result; +} + +// 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; +} + +string ConvertPathToPosix(const string& win_path) { + char* posix_path = static_cast(cygwin_create_path( + CCP_WIN_A_TO_POSIX, static_cast(win_path.c_str()))); + string result(posix_path); + free(posix_path); + return result; +} + +// 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_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_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 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) { +} + +} // namespace blaze -- cgit v1.2.3