|  | // Copyright 2017 The Bazel Authors. All rights reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | #ifndef WIN32_LEAN_AND_MEAN | 
|  | #define WIN32_LEAN_AND_MEAN | 
|  | #endif | 
|  | #include <windows.h> | 
|  |  | 
|  | #include <string.h> | 
|  |  | 
|  | #include <fstream> | 
|  | #include <iostream> | 
|  | #include <sstream> | 
|  | #include <string> | 
|  | #include <unordered_map> | 
|  |  | 
|  | #include "src/main/cpp/util/file_platform.h" | 
|  | #include "src/main/cpp/util/path_platform.h" | 
|  | #include "src/main/cpp/util/strings.h" | 
|  | #include "src/main/native/windows/file.h" | 
|  |  | 
|  | using std::ifstream; | 
|  | using std::string; | 
|  | using std::stringstream; | 
|  | using std::unordered_map; | 
|  | using std::wstring; | 
|  |  | 
|  | #ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE | 
|  | #define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2 | 
|  | #endif | 
|  |  | 
|  | #ifndef SYMBOLIC_LINK_FLAG_DIRECTORY | 
|  | #define SYMBOLIC_LINK_FLAG_DIRECTORY 0x1 | 
|  | #endif | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const wchar_t* manifest_filename; | 
|  | const wchar_t* runfiles_base_dir; | 
|  |  | 
|  | string GetLastErrorString() { | 
|  | DWORD last_error = GetLastError(); | 
|  | if (last_error == 0) { | 
|  | return string(); | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | stringstream result; | 
|  | result << "(error: " << last_error << "): " << message_buffer; | 
|  | LocalFree(message_buffer); | 
|  | return result.str(); | 
|  | } | 
|  |  | 
|  | void die(const wchar_t* format, ...) { | 
|  | va_list ap; | 
|  | va_start(ap, format); | 
|  | fputws(L"build-runfiles error: ", stderr); | 
|  | vfwprintf(stderr, format, ap); | 
|  | va_end(ap); | 
|  | fputwc(L'\n', stderr); | 
|  | fputws(L"manifest file name: ", stderr); | 
|  | fputws(manifest_filename, stderr); | 
|  | fputwc(L'\n', stderr); | 
|  | fputws(L"runfiles base directory: ", stderr); | 
|  | fputws(runfiles_base_dir, stderr); | 
|  | fputwc(L'\n', stderr); | 
|  | exit(1); | 
|  | } | 
|  |  | 
|  | wstring AsAbsoluteWindowsPath(const wchar_t* path) { | 
|  | wstring wpath; | 
|  | string error; | 
|  | if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath, &error)) { | 
|  | die(L"Couldn't convert %s to absolute Windows path: %hs", path, | 
|  | error.c_str()); | 
|  | } | 
|  | return wpath; | 
|  | } | 
|  |  | 
|  | bool DoesDirectoryPathExist(const wchar_t* path) { | 
|  | DWORD dwAttrib = GetFileAttributesW(AsAbsoluteWindowsPath(path).c_str()); | 
|  |  | 
|  | return (dwAttrib != INVALID_FILE_ATTRIBUTES && | 
|  | (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); | 
|  | } | 
|  |  | 
|  | wstring GetParentDirFromPath(const wstring& path) { | 
|  | return path.substr(0, path.find_last_of(L"\\/")); | 
|  | } | 
|  |  | 
|  | inline void Trim(wstring& str) { | 
|  | str.erase(0, str.find_first_not_of(' ')); | 
|  | str.erase(str.find_last_not_of(' ') + 1); | 
|  | } | 
|  |  | 
|  | bool ReadSymlink(const wstring& abs_path, wstring* target, wstring* error) { | 
|  | switch (bazel::windows::ReadSymlinkOrJunction(abs_path, target, error)) { | 
|  | case bazel::windows::ReadSymlinkOrJunctionResult::kSuccess: | 
|  | return true; | 
|  | case bazel::windows::ReadSymlinkOrJunctionResult::kAccessDenied: | 
|  | *error = L"access is denied"; | 
|  | break; | 
|  | case bazel::windows::ReadSymlinkOrJunctionResult::kDoesNotExist: | 
|  | *error = L"path does not exist"; | 
|  | break; | 
|  | case bazel::windows::ReadSymlinkOrJunctionResult::kNotALink: | 
|  | *error = L"path is not a link"; | 
|  | break; | 
|  | case bazel::windows::ReadSymlinkOrJunctionResult::kUnknownLinkType: | 
|  | *error = L"unknown link type"; | 
|  | break; | 
|  | default: | 
|  | // This is bazel::windows::ReadSymlinkOrJunctionResult::kError (1). | 
|  | // The JNI code puts a custom message in 'error'. | 
|  | break; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class RunfilesCreator { | 
|  | typedef std::unordered_map<std::wstring, std::wstring> ManifestFileMap; | 
|  |  | 
|  | public: | 
|  | RunfilesCreator(const wstring& manifest_path, | 
|  | const wstring& runfiles_output_base) | 
|  | : manifest_path_(manifest_path), | 
|  | runfiles_output_base_(runfiles_output_base) { | 
|  | SetupOutputBase(); | 
|  | if (!SetCurrentDirectoryW(runfiles_output_base_.c_str())) { | 
|  | die(L"SetCurrentDirectoryW failed (%s): %hs", | 
|  | runfiles_output_base_.c_str(), GetLastErrorString().c_str()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ReadManifest(bool allow_relative, bool ignore_metadata) { | 
|  | ifstream manifest_file( | 
|  | AsAbsoluteWindowsPath(manifest_path_.c_str()).c_str()); | 
|  |  | 
|  | if (!manifest_file) { | 
|  | die(L"Couldn't open MANIFEST file: %s", manifest_path_.c_str()); | 
|  | } | 
|  |  | 
|  | string line; | 
|  | int lineno = 0; | 
|  | while (getline(manifest_file, line)) { | 
|  | lineno++; | 
|  | // Skip metadata lines. They are used solely for | 
|  | // dependency checking. | 
|  | if (ignore_metadata && lineno % 2 == 0) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | size_t space_pos = line.find_first_of(' '); | 
|  | wstring wline = blaze_util::CstringToWstring(line); | 
|  | wstring link, target; | 
|  | if (space_pos == string::npos) { | 
|  | link = wline; | 
|  | target = wstring(); | 
|  | } else { | 
|  | link = wline.substr(0, space_pos); | 
|  | target = wline.substr(space_pos + 1); | 
|  | } | 
|  |  | 
|  | // Removing leading and trailing spaces | 
|  | Trim(link); | 
|  | Trim(target); | 
|  |  | 
|  | // We sometimes need to create empty files under the runfiles tree. | 
|  | // For example, for python binary, __init__.py is needed under every | 
|  | // directory. Storing an entry with an empty target indicates we need to | 
|  | // create such a file when creating the runfiles tree. | 
|  | if (!allow_relative && !target.empty() && | 
|  | !blaze_util::IsAbsolute(target)) { | 
|  | die(L"Target cannot be relative path: %hs", line.c_str()); | 
|  | } | 
|  |  | 
|  | link = AsAbsoluteWindowsPath(link.c_str()); | 
|  | if (!target.empty()) { | 
|  | target = AsAbsoluteWindowsPath(target.c_str()); | 
|  | } | 
|  |  | 
|  | manifest_file_map.insert(make_pair(link, target)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void CreateRunfiles() { | 
|  | bool symlink_needs_privilege = | 
|  | DoesCreatingSymlinkNeedAdminPrivilege(runfiles_output_base_); | 
|  | ScanTreeAndPrune(runfiles_output_base_); | 
|  | CreateFiles(symlink_needs_privilege); | 
|  | CopyManifestFile(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | void SetupOutputBase() { | 
|  | if (!DoesDirectoryPathExist(runfiles_output_base_.c_str())) { | 
|  | MakeDirectoriesOrDie(runfiles_output_base_); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MakeDirectoriesOrDie(const wstring& path) { | 
|  | if (!blaze_util::MakeDirectoriesW(path, 0755)) { | 
|  | die(L"MakeDirectoriesW failed (%s): %hs", path.c_str(), | 
|  | GetLastErrorString().c_str()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void RemoveDirectoryOrDie(const wstring& path) { | 
|  | if (!RemoveDirectoryW(path.c_str())) { | 
|  | die(L"RemoveDirectoryW failed (%s): %hs", GetLastErrorString().c_str()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DeleteFileOrDie(const wstring& path) { | 
|  | SetFileAttributesW(path.c_str(), GetFileAttributesW(path.c_str()) & | 
|  | ~FILE_ATTRIBUTE_READONLY); | 
|  | if (!DeleteFileW(path.c_str())) { | 
|  | die(L"DeleteFileW failed (%s): %hs", path.c_str(), | 
|  | GetLastErrorString().c_str()); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool DoesCreatingSymlinkNeedAdminPrivilege(const wstring& runfiles_base_dir) { | 
|  | wstring dummy_link = runfiles_base_dir + L"\\dummy_link"; | 
|  | wstring dummy_target = runfiles_base_dir + L"\\dummy_target"; | 
|  |  | 
|  | // Try creating symlink without admin privilege. | 
|  | if (CreateSymbolicLinkW(dummy_link.c_str(), dummy_target.c_str(), | 
|  | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) { | 
|  | DeleteFileOrDie(dummy_link); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Try creating symlink with admin privilege | 
|  | if (CreateSymbolicLinkW(dummy_link.c_str(), dummy_target.c_str(), 0)) { | 
|  | DeleteFileOrDie(dummy_link); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // If we couldn't create symlink, print out an error message and exit. | 
|  | if (GetLastError() == ERROR_PRIVILEGE_NOT_HELD) { | 
|  | die(L"CreateSymbolicLinkW failed:\n%hs\n", | 
|  | "Bazel needs to create symlink for building runfiles tree.\n" | 
|  | "Creating symlink on Windows requires either of the following:\n" | 
|  | "    1. Program is running with elevated privileges (Admin rights).\n" | 
|  | "    2. The system version is Windows 10 Creators Update (1703) or " | 
|  | "later and " | 
|  | "developer mode is enabled.", | 
|  | GetLastErrorString().c_str()); | 
|  | } else { | 
|  | die(L"CreateSymbolicLinkW failed: %hs", GetLastErrorString().c_str()); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // This function scan the current directory, remove all | 
|  | // files/symlinks/directories that are not presented in manifest file. If a | 
|  | // symlink already exists and points to the correct target, this function | 
|  | // erases its entry from manifest_file_map, so that we won't recreate it. | 
|  | void ScanTreeAndPrune(const wstring& path) { | 
|  | static const wstring kDot(L"."); | 
|  | static const wstring kDotDot(L".."); | 
|  |  | 
|  | WIN32_FIND_DATAW metadata; | 
|  | HANDLE handle = ::FindFirstFileW((path + L"\\*").c_str(), &metadata); | 
|  | if (handle == INVALID_HANDLE_VALUE) { | 
|  | return;  // directory does not exist or is empty | 
|  | } | 
|  |  | 
|  | do { | 
|  | if (kDot != metadata.cFileName && kDotDot != metadata.cFileName) { | 
|  | wstring subpath = path + L"\\" + metadata.cFileName; | 
|  | subpath = AsAbsoluteWindowsPath(subpath.c_str()); | 
|  | bool is_dir = | 
|  | (metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; | 
|  | bool is_symlink = | 
|  | (metadata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; | 
|  | if (is_symlink) { | 
|  | wstring target, werror; | 
|  | if (!ReadSymlink(subpath, &target, &werror)) { | 
|  | die(L"ReadSymlinkW failed (%s): %hs", subpath.c_str(), | 
|  | werror.c_str()); | 
|  | } | 
|  |  | 
|  | target = AsAbsoluteWindowsPath(target.c_str()); | 
|  | ManifestFileMap::iterator expected_target = | 
|  | manifest_file_map.find(subpath); | 
|  |  | 
|  | if (expected_target == manifest_file_map.end() || | 
|  | expected_target->second.empty() | 
|  | // Both paths are normalized paths in lower case, we can compare | 
|  | // them directly. | 
|  | || target != expected_target->second.c_str() || | 
|  | blaze_util::IsDirectoryW(target) != is_dir) { | 
|  | if (is_dir) { | 
|  | RemoveDirectoryOrDie(subpath); | 
|  | } else { | 
|  | DeleteFileOrDie(subpath); | 
|  | } | 
|  | } else { | 
|  | manifest_file_map.erase(expected_target); | 
|  | } | 
|  | } else { | 
|  | if (is_dir) { | 
|  | ScanTreeAndPrune(subpath); | 
|  | // If the directory is empty, then we remove the directory. | 
|  | // Otherwise RemoveDirectory will fail with ERROR_DIR_NOT_EMPTY, | 
|  | // which we can just ignore. | 
|  | // Because if the directory is not empty, it means it contains some | 
|  | // symlinks already pointing to the correct targets (we just called | 
|  | // ScanTreeAndPrune). Then this directory shouldn't be removed in | 
|  | // the first place. | 
|  | if (!RemoveDirectoryW(subpath.c_str()) && | 
|  | GetLastError() != ERROR_DIR_NOT_EMPTY) { | 
|  | die(L"RemoveDirectoryW failed (%s): %hs", subpath.c_str(), | 
|  | GetLastErrorString().c_str()); | 
|  | } | 
|  | } else { | 
|  | DeleteFileOrDie(subpath); | 
|  | } | 
|  | } | 
|  | } | 
|  | } while (::FindNextFileW(handle, &metadata)); | 
|  | ::FindClose(handle); | 
|  | } | 
|  |  | 
|  | void CreateFiles(bool creating_symlink_needs_admin_privilege) { | 
|  | DWORD privilege_flag = creating_symlink_needs_admin_privilege | 
|  | ? 0 | 
|  | : SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; | 
|  |  | 
|  | for (const auto& it : manifest_file_map) { | 
|  | // Ensure the parent directory exists | 
|  | wstring parent_dir = GetParentDirFromPath(it.first); | 
|  | if (!DoesDirectoryPathExist(parent_dir.c_str())) { | 
|  | MakeDirectoriesOrDie(parent_dir); | 
|  | } | 
|  |  | 
|  | if (it.second.empty()) { | 
|  | // Create an empty file | 
|  | HANDLE h = CreateFileW(it.first.c_str(),  // name of the file | 
|  | GENERIC_WRITE,     // open for writing | 
|  | // Must share for reading, otherwise | 
|  | // symlink-following file existence checks (e.g. | 
|  | // java.nio.file.Files.exists()) fail. | 
|  | FILE_SHARE_READ, | 
|  | 0,  // use default security descriptor | 
|  | CREATE_ALWAYS,  // overwrite if exists | 
|  | FILE_ATTRIBUTE_NORMAL, 0); | 
|  | if (h != INVALID_HANDLE_VALUE) { | 
|  | CloseHandle(h); | 
|  | } else { | 
|  | die(L"CreateFileW failed (%s): %hs", it.first.c_str(), | 
|  | GetLastErrorString().c_str()); | 
|  | } | 
|  | } else { | 
|  | DWORD create_dir = 0; | 
|  | if (blaze_util::IsDirectoryW(it.second.c_str())) { | 
|  | create_dir = SYMBOLIC_LINK_FLAG_DIRECTORY; | 
|  | } | 
|  | if (!CreateSymbolicLinkW(it.first.c_str(), it.second.c_str(), | 
|  | privilege_flag | create_dir)) { | 
|  | die(L"CreateSymbolicLinkW failed (%s -> %s): %hs", it.first.c_str(), | 
|  | it.second.c_str(), GetLastErrorString().c_str()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void CopyManifestFile() { | 
|  | wstring new_manifest_file = runfiles_output_base_ + L"\\MANIFEST"; | 
|  | if (!CopyFileW(manifest_path_.c_str(), new_manifest_file.c_str(), | 
|  | /*bFailIfExists=*/FALSE)) { | 
|  | die(L"CopyFileW failed (%s -> %s): %hs", manifest_path_.c_str(), | 
|  | new_manifest_file.c_str(), GetLastErrorString().c_str()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  | wstring manifest_path_; | 
|  | wstring runfiles_output_base_; | 
|  | ManifestFileMap manifest_file_map; | 
|  | }; | 
|  |  | 
|  | int wmain(int argc, wchar_t** argv) { | 
|  | argc--; | 
|  | argv++; | 
|  | bool allow_relative = false; | 
|  | bool ignore_metadata = false; | 
|  |  | 
|  | while (argc >= 1) { | 
|  | if (wcscmp(argv[0], L"--allow_relative") == 0) { | 
|  | allow_relative = true; | 
|  | argc--; | 
|  | argv++; | 
|  | } else if (wcscmp(argv[0], L"--use_metadata") == 0) { | 
|  | // If --use_metadata is passed, it means manifest file contains metadata | 
|  | // lines, which we should ignore when reading manifest file. | 
|  | ignore_metadata = true; | 
|  | argc--; | 
|  | argv++; | 
|  | } else { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (argc != 2) { | 
|  | fprintf(stderr, | 
|  | "usage: [--allow_relative] [--use_metadata] " | 
|  | "<manifest_file> <runfiles_base_dir>\n"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | manifest_filename = argv[0]; | 
|  | runfiles_base_dir = argv[1]; | 
|  |  | 
|  | wstring manifest_absolute_path = AsAbsoluteWindowsPath(manifest_filename); | 
|  | wstring output_base_absolute_path = AsAbsoluteWindowsPath(runfiles_base_dir); | 
|  |  | 
|  | RunfilesCreator runfiles_creator(manifest_absolute_path, | 
|  | output_base_absolute_path); | 
|  | runfiles_creator.ReadManifest(allow_relative, ignore_metadata); | 
|  | runfiles_creator.CreateRunfiles(); | 
|  |  | 
|  | return 0; | 
|  | } |