Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix symlink creation on older Windows versions #13488

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
16 changes: 14 additions & 2 deletions src/main/native/windows/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,33 @@
#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>

#pragma comment(lib, "advapi32.lib") //RegGetValueW

namespace bazel {
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({
khogeland marked this conversation as resolved.
Show resolved Hide resolved
"//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