| // 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> | |
| #include <string.h> // strerror | |
| #include <sys/cygwin.h> | |
| #include <sys/socket.h> | |
| #include <sys/statfs.h> | |
| #include <unistd.h> | |
| #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/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<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() { | |
| 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<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 (!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<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 | |
| 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<char*>(cygwin_create_path( | |
| CCP_POSIX_TO_WIN_A, static_cast<const void*>(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<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; | |
| } | |
| // 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) { | |
| } | |
| } // namespace blaze |