Use the Win32 API to create and query junctions in the C++ client.
This change makes it possible to build Bazel with itself in server mode.
Progress towards #930 . Does not completely fix it because there are still a bunch of issues that need to be taken care of, but it's usable.
--
MOS_MIGRATED_REVID=120994369
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc
index 1cdef6b..6a160a7 100644
--- a/src/main/cpp/blaze.cc
+++ b/src/main/cpp/blaze.cc
@@ -1322,12 +1322,10 @@
// running servers. Lastly, symlink to our installation so others know which
// installation is running.
string installation_path = globals->options.output_base + "/install";
- char prev_installation[PATH_MAX + 1] = ""; // NULs the whole array
- // TODO(dslomov): On Windows, readlink always fails,
- // so we do the linking every time.
- if (readlink(installation_path.c_str(),
- prev_installation, PATH_MAX) == -1 ||
- prev_installation != globals->options.install_base) {
+ string prev_installation;
+ bool ok = ReadDirectorySymlink(installation_path.c_str(), &prev_installation);
+ if (!ok || !CompareAbsolutePaths(
+ prev_installation, globals->options.install_base)) {
if (KillRunningServerIfAny(server)) {
globals->restart_reason = NEW_VERSION;
}
diff --git a/src/main/cpp/blaze_util_mingw.cc b/src/main/cpp/blaze_util_mingw.cc
index b846e6d..6e195cf 100644
--- a/src/main/cpp/blaze_util_mingw.cc
+++ b/src/main/cpp/blaze_util_mingw.cc
@@ -39,6 +39,29 @@
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) {
}
@@ -397,46 +420,197 @@
return result;
}
-bool SymlinkDirectories(const string &target, const string &link) {
- const string target_win = ConvertPath(target);
- const string link_win = ConvertPath(link);
- vector<string> args;
- args.push_back("cmd");
- args.push_back("/C");
- args.push_back("mklink");
- args.push_back("/J");
- args.push_back(link_win);
- args.push_back(target_win);
+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;
+}
- CmdLine cmdline;
- CreateCommandLine(&cmdline, "cmd", args);
+// Cribbed from ntifs.h, not present in windows.h
- STARTUPINFO startupInfo = {0};
- PROCESS_INFORMATION processInfo = {0};
+#define REPARSE_MOUNTPOINT_HEADER_SIZE 8
- 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,
- 0, // _In_ DWORD dwCreationFlags,
- NULL, // _In_opt_ LPVOID lpEnvironment,
- NULL, // _In_opt_ LPCTSTR lpCurrentDirectory,
- &startupInfo, // _In_ LPSTARTUPINFO lpStartupInfo,
- &processInfo); // _Out_ LPPROCESS_INFORMATION lpProcessInformation
+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;
- if (!success) {
- pdie(255, "Error %u executing: %s\n", GetLastError(), cmdline);
+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 + ")");
}
- WaitForSingleObject(processInfo.hProcess, INFINITE);
- DWORD exit_code;
- GetExitCodeProcess(processInfo.hProcess, &exit_code);
- CloseHandle(processInfo.hProcess);
- CloseHandle(processInfo.hThread);
- return exit_code == 0;
+ 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;
+ }
+
+ char print_name[MAX_PATH];
+ int count = ::WideCharToMultiByte(
+ CP_UTF8,
+ 0,
+ reparse_buffer->PathBuffer +
+ (reparse_buffer->PrintNameOffset / sizeof(WCHAR)),
+ reparse_buffer->PrintNameLength,
+ print_name,
+ MAX_PATH,
+ NULL,
+ NULL);
+ if (count == 0) {
+ PrintError("WideCharToMultiByte()");
+ *result = "";
+ return false;
+ } else {
+ *result = ConvertPathToPosix(print_name);
+ 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;
}
} // namespace blaze
diff --git a/src/main/cpp/blaze_util_platform.h b/src/main/cpp/blaze_util_platform.h
index 3d3463c..a2a865c 100644
--- a/src/main/cpp/blaze_util_platform.h
+++ b/src/main/cpp/blaze_util_platform.h
@@ -82,10 +82,20 @@
std::string ListSeparator();
// Create a symlink to directory ``target`` at location ``link``.
-// Returns true on success, false on failure.
+// Returns true on success, false on failure. The target must be absolute.
// Implemented via junctions on Windows.
bool SymlinkDirectories(const string &target, const string &link);
+// Reads which directory a symlink points to. Puts the target of the symlink
+// in ``result`` and returns if the operation was successful. Will not work on
+// symlinks that don't point to directories on Windows.
+bool ReadDirectorySymlink(const string &symlink, string *result);
+
+// Compares two absolute paths. Necessary because the same path can have
+// multiple different names under msys2: "C:\foo\bar" or "C:/foo/bar"
+// (Windows-style) and "/c/foo/bar" (msys2 style). Returns if the paths are
+// equal.
+bool CompareAbsolutePaths(const string& a, const string& b);
} // namespace blaze
#endif // BAZEL_SRC_MAIN_CPP_BLAZE_UTIL_PLATFORM_H_
diff --git a/src/main/cpp/blaze_util_posix.cc b/src/main/cpp/blaze_util_posix.cc
index e7f85b2..5ed8b59 100644
--- a/src/main/cpp/blaze_util_posix.cc
+++ b/src/main/cpp/blaze_util_posix.cc
@@ -136,4 +136,20 @@
pdie(0, "Cannot execute %s", exe.c_str());
}
+bool ReadDirectorySymlink(const string &name, string* result) {
+ char buf[PATH_MAX + 1];
+ int len = readlink(name.c_str(), buf, PATH_MAX);
+ if (len < 0) {
+ return false;
+ }
+
+ buf[len] = 0;
+ *result = buf;
+ return true;
+}
+
+bool CompareAbsolutePaths(const string& a, const string& b) {
+ return a == b;
+}
+
} // namespace blaze.