Skip to content

Commit

Permalink
Fix symlink creation on older Windows versions
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin Hogeland committed May 18, 2021
1 parent d228b09 commit 9816cf0
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 81 deletions.
3 changes: 3 additions & 0 deletions src/main/native/windows/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ cc_library(
"file.h",
"util.h",
],
linkopts = [
"-DEFAULTLIB:advapi32.lib", # RegGetValueW
],
visibility = [
"//src/main/cpp:__subpackages__",
"//src/main/tools:__pkg__",
Expand Down
29 changes: 22 additions & 7 deletions src/main/native/windows/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ namespace windows {
using std::unique_ptr;
using std::wstring;

bool IsDeveloperModeEnabled() {
DWORD val = 0;
DWORD valSize = sizeof(val);
if (RegGetValueW(
HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock",
L"AllowDevelopmentWithoutDevLicense", RRF_RT_DWORD, nullptr, &val,
&valSize) != ERROR_SUCCESS) {
return false;
}
return val != 0;
}


wstring AddUncPrefixMaybe(const wstring& path) {
return path.empty() || IsDevNull(path.c_str()) || HasUncPrefix(path.c_str())
? path
Expand Down Expand Up @@ -446,13 +460,14 @@ int CreateSymlink(const wstring& symlink_name, const wstring& symlink_target,
}

if (!CreateSymbolicLinkW(name.c_str(), target.c_str(),
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) {
// The flag SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE requires
// developer mode enabled, which we expect if using symbolic linking.
*error = MakeErrorMessage(
WSTR(__FILE__), __LINE__, L"CreateSymlink", symlink_target,
L"createSymbolicLinkW failed");
return CreateSymlinkResult::kError;
symlinkPrivilegeFlag)) {
*error = MakeErrorMessage(
WSTR(__FILE__), __LINE__, L"CreateSymlink", symlink_target,
GetLastError() == ERROR_PRIVILEGE_NOT_HELD
? L"createSymbolicLinkW failed (permission denied). Either "
"Windows developer mode or admin privileges are required."
: L"createSymbolicLinkW failed");
return CreateSymlinkResult::kError;
}
return CreateSymlinkResult::kSuccess;
}
Expand Down
14 changes: 12 additions & 2 deletions src/main/native/windows/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>

#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
#define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2
#endif

#include <windows.h>

#include <memory>
#include <string>

Expand All @@ -33,6 +33,16 @@ namespace windows {
using std::unique_ptr;
using std::wstring;

bool IsDeveloperModeEnabled();

// The flag SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE requires
// developer mode to be enabled. If it is not enabled, or the current
// version of Windows does not support it, do not use the flag.
// The process will need to be run with elevated privileges.
const DWORD symlinkPrivilegeFlag = IsDeveloperModeEnabled()
? SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
: 0;

template <typename char_type>
bool HasUncPrefix(const char_type* path) {
// Return true iff `path` starts with "\\?\", "\\.\", or "\??\".
Expand Down
6 changes: 0 additions & 6 deletions src/main/tools/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,6 @@ cc_binary(
"//src/conditions:windows": ["build-runfiles-windows.cc"],
"//conditions:default": ["build-runfiles.cc"],
}),
linkopts = select({
"//src/conditions:windows": [
"-DEFAULTLIB:advapi32.lib", # RegGetValueW
],
"//conditions:default": [],
}),
deps = ["//src/main/cpp/util:filesystem"] + select({
"//src/conditions:windows": ["//src/main/native/windows:lib-file"],
"//conditions:default": [],
Expand Down
82 changes: 16 additions & 66 deletions src/main/tools/build-runfiles-windows.cc
Original file line number Diff line number Diff line change
Expand Up @@ -129,19 +129,6 @@ bool ReadSymlink(const wstring& abs_path, wstring* target, wstring* error) {
return false;
}

bool IsDeveloperModeEnabled() {
DWORD val = 0;
DWORD valSize = sizeof(val);
if (RegGetValueW(
HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock",
L"AllowDevelopmentWithoutDevLicense", RRF_RT_DWORD, nullptr, &val,
&valSize) != ERROR_SUCCESS) {
return false;
}
return val != 0;
}

} // namespace

class RunfilesCreator {
Expand Down Expand Up @@ -211,10 +198,8 @@ class RunfilesCreator {
}

void CreateRunfiles() {
bool symlink_needs_privilege =
DoesCreatingSymlinkNeedAdminPrivilege(runfiles_output_base_);
ScanTreeAndPrune(runfiles_output_base_);
CreateFiles(symlink_needs_privilege);
CreateFiles();
CopyManifestFile();
}

Expand Down Expand Up @@ -247,48 +232,6 @@ class RunfilesCreator {
}
}

bool DoesCreatingSymlinkNeedAdminPrivilege(const wstring& runfiles_base_dir) {
// Creating symlinks without admin privilege is enabled by Developer Mode,
// available since Windows Version 1703.
if (IsDeveloperModeEnabled()) {
return false;
}
wstring dummy_link = runfiles_base_dir + L"\\dummy_link";
wstring dummy_target = runfiles_base_dir + L"\\dummy_target";

// Try creating symlink with admin privilege
bool created =
CreateSymbolicLinkW(dummy_link.c_str(), dummy_target.c_str(), 0);

// on a rare occasion the dummy_link may exist from a previous run
// retry after deleting the existing link
if (!created && GetLastError() == ERROR_ALREADY_EXISTS) {
DeleteFileOrDie(dummy_link);
created =
CreateSymbolicLinkW(dummy_link.c_str(), dummy_target.c_str(), 0);
}

// If we couldn't create symlink, print out an error message and exit.
if (!created) {
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());
}
}

DeleteFileOrDie(dummy_link);
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
Expand Down Expand Up @@ -360,11 +303,7 @@ class RunfilesCreator {
::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;

void CreateFiles() {
for (const auto& it : manifest_file_map) {
// Ensure the parent directory exists
wstring parent_dir = GetParentDirFromPath(it.first);
Expand Down Expand Up @@ -395,9 +334,20 @@ class RunfilesCreator {
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());
bazel::windows::symlinkPrivilegeFlag | create_dir)) {

if (GetLastError() == ERROR_PRIVILEGE_NOT_HELD) {
die(L"CreateSymbolicLinkW failed:\n%hs\n",
"Bazel needs to create symlinks to build the runfiles tree.\n"
"Creating symlinks on Windows requires one of the following:\n"
" 1. Bazel is run with administrator privileges.\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 (%s -> %s): %hs", it.first.c_str(),
it.second.c_str(), GetLastErrorString().c_str());
}
}
}
}
Expand Down

0 comments on commit 9816cf0

Please sign in to comment.