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