| // 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; |
| } |