blob: 297116d3106027bd1e5c6b0fb53211a73220433e [file] [log] [blame]
// 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;
}