Skip to content

Commit 2f0927a

Browse files
Kevin Hogelandcopybara-github
Kevin Hogeland
authored andcommitted
Fix symlink creation on older Windows versions
This patch ensures that the `SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE` flag is not passed to `CreateSymbolicLinkW` when the developer mode registry key is not present and enabled on a supported version of Windows. This allows symlinks to be created when Bazel is run with elevated privileges, regardless of Windows version or developer mode. I also removed the dummy file creation check while refactoring that code for reuse. It seemed overly complicated vs. simply checking the registry and failing during runfiles creation if we're not admin. Please let me know if there's some subtle reason it needed to be done that way. Fixes #13169 - tested on Windows Server 2016. Closes #13488. PiperOrigin-RevId: 375035407
1 parent 1544777 commit 2f0927a

File tree

8 files changed

+70
-85
lines changed

8 files changed

+70
-85
lines changed

site/docs/windows.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ fsutil 8dot3name set 0
3232

3333
### Enable symlink support
3434

35-
Some features require Bazel to create file symlink on Windows, you can allow Bazel to do that by enabling [Developer Mode](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) on Windows (Only works for Windows 10, version 1703 or newer).
36-
After enabling the Developer Mode, you should be able to use the following features:
35+
Some features require Bazel to be able to create file symlinks on Windows,
36+
either by enabling
37+
[Developer Mode](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development)
38+
(on Windows 10 version 1703 or newer), or by running Bazel as an administrator.
39+
This enables the following features:
3740

3841
* [\-\-windows_enable_symlinks](command-line-reference.html#flag--windows_enable_symlinks)
3942
* [\-\-enable_runfiles](command-line-reference.html#flag--enable_runfiles)

src/main/native/windows/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ cc_library(
2525
"file.h",
2626
"util.h",
2727
],
28+
linkopts = [
29+
"-DEFAULTLIB:advapi32.lib", # RegGetValueW
30+
],
2831
visibility = [
2932
"//src/main/cpp:__subpackages__",
3033
"//src/main/tools:__pkg__",

src/main/native/windows/build_windows_jni.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ cat > "${VSTEMP}/windows_jni.bat" <<EOF
120120
@$pwd_drive
121121
@cd "$abs_pwd"
122122
@set TMP=$(cygpath -a -w "${VSTEMP}")
123-
@CL /O2 /EHsc /LD /Fe:"$(cygpath -a -w ${DLL})" /I "%TMP%" /I . ${WINDOWS_SOURCES[*]}
123+
@CL /O2 /EHsc /LD /Fe:"$(cygpath -a -w ${DLL})" /I "%TMP%" /I . ${WINDOWS_SOURCES[*]} /link /DEFAULTLIB:advapi32.lib
124124
EOF
125125

126126
# Invoke the file and hopefully generate the .DLL .

src/main/native/windows/file.cc

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include <WinIoCtl.h>
2222
#include <stdint.h> // uint8_t
23+
#include <versionhelpers.h>
2324
#include <windows.h>
2425

2526
#include <memory>
@@ -39,6 +40,25 @@ namespace windows {
3940
using std::unique_ptr;
4041
using std::wstring;
4142

43+
DWORD DetermineSymlinkPrivilegeFlag() {
44+
DWORD val = 0;
45+
DWORD valSize = sizeof(val);
46+
if ( // The unprivileged create flag was introduced in Windows 10 build
47+
// 14972:
48+
// https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/
49+
!IsWindowsVersionOrGreater(10, 0, 14972)
50+
// Check if developer mode is disabled:
51+
|| RegGetValueW(
52+
HKEY_LOCAL_MACHINE,
53+
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock",
54+
L"AllowDevelopmentWithoutDevLicense", RRF_RT_DWORD, nullptr, &val,
55+
&valSize) != ERROR_SUCCESS ||
56+
val == 0) {
57+
return 0;
58+
}
59+
return SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
60+
}
61+
4262
wstring AddUncPrefixMaybe(const wstring& path) {
4363
return path.empty() || IsDevNull(path.c_str()) || HasUncPrefix(path.c_str())
4464
? path
@@ -446,13 +466,14 @@ int CreateSymlink(const wstring& symlink_name, const wstring& symlink_target,
446466
}
447467

448468
if (!CreateSymbolicLinkW(name.c_str(), target.c_str(),
449-
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) {
450-
// The flag SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE requires
451-
// developer mode enabled, which we expect if using symbolic linking.
452-
*error = MakeErrorMessage(
453-
WSTR(__FILE__), __LINE__, L"CreateSymlink", symlink_target,
454-
L"createSymbolicLinkW failed");
455-
return CreateSymlinkResult::kError;
469+
symlinkPrivilegeFlag)) {
470+
*error = MakeErrorMessage(
471+
WSTR(__FILE__), __LINE__, L"CreateSymlink", symlink_target,
472+
GetLastError() == ERROR_PRIVILEGE_NOT_HELD
473+
? L"createSymbolicLinkW failed (permission denied). Either "
474+
"Windows developer mode or admin privileges are required."
475+
: L"createSymbolicLinkW failed");
476+
return CreateSymlinkResult::kError;
456477
}
457478
return CreateSymlinkResult::kSuccess;
458479
}

src/main/native/windows/file.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
#define WIN32_LEAN_AND_MEAN
1919
#endif
2020

21+
#include <windows.h>
22+
2123
#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
2224
#define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2
2325
#endif
2426

25-
#include <windows.h>
26-
2727
#include <memory>
2828
#include <string>
2929

@@ -33,6 +33,16 @@ namespace windows {
3333
using std::unique_ptr;
3434
using std::wstring;
3535

36+
bool IsDeveloperModeEnabled();
37+
38+
DWORD DetermineSymlinkPrivilegeFlag();
39+
40+
// The flag SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE requires
41+
// developer mode to be enabled. If it is not enabled, or the current
42+
// version of Windows does not support it, do not use the flag.
43+
// The process will need to be run with elevated privileges.
44+
const DWORD symlinkPrivilegeFlag = DetermineSymlinkPrivilegeFlag();
45+
3646
template <typename char_type>
3747
bool HasUncPrefix(const char_type* path) {
3848
// Return true iff `path` starts with "\\?\", "\\.\", or "\??\".

src/main/tools/BUILD

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,6 @@ cc_binary(
6060
"//src/conditions:windows": ["build-runfiles-windows.cc"],
6161
"//conditions:default": ["build-runfiles.cc"],
6262
}),
63-
linkopts = select({
64-
"//src/conditions:windows": [
65-
"-DEFAULTLIB:advapi32.lib", # RegGetValueW
66-
],
67-
"//conditions:default": [],
68-
}),
6963
deps = ["//src/main/cpp/util:filesystem"] + select({
7064
"//src/conditions:windows": ["//src/main/native/windows:lib-file"],
7165
"//conditions:default": [],

src/main/tools/build-runfiles-windows.cc

Lines changed: 18 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -129,19 +129,6 @@ bool ReadSymlink(const wstring& abs_path, wstring* target, wstring* error) {
129129
return false;
130130
}
131131

132-
bool IsDeveloperModeEnabled() {
133-
DWORD val = 0;
134-
DWORD valSize = sizeof(val);
135-
if (RegGetValueW(
136-
HKEY_LOCAL_MACHINE,
137-
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock",
138-
L"AllowDevelopmentWithoutDevLicense", RRF_RT_DWORD, nullptr, &val,
139-
&valSize) != ERROR_SUCCESS) {
140-
return false;
141-
}
142-
return val != 0;
143-
}
144-
145132
} // namespace
146133

147134
class RunfilesCreator {
@@ -211,10 +198,8 @@ class RunfilesCreator {
211198
}
212199

213200
void CreateRunfiles() {
214-
bool symlink_needs_privilege =
215-
DoesCreatingSymlinkNeedAdminPrivilege(runfiles_output_base_);
216201
ScanTreeAndPrune(runfiles_output_base_);
217-
CreateFiles(symlink_needs_privilege);
202+
CreateFiles();
218203
CopyManifestFile();
219204
}
220205

@@ -247,48 +232,6 @@ class RunfilesCreator {
247232
}
248233
}
249234

250-
bool DoesCreatingSymlinkNeedAdminPrivilege(const wstring& runfiles_base_dir) {
251-
// Creating symlinks without admin privilege is enabled by Developer Mode,
252-
// available since Windows Version 1703.
253-
if (IsDeveloperModeEnabled()) {
254-
return false;
255-
}
256-
wstring dummy_link = runfiles_base_dir + L"\\dummy_link";
257-
wstring dummy_target = runfiles_base_dir + L"\\dummy_target";
258-
259-
// Try creating symlink with admin privilege
260-
bool created =
261-
CreateSymbolicLinkW(dummy_link.c_str(), dummy_target.c_str(), 0);
262-
263-
// on a rare occasion the dummy_link may exist from a previous run
264-
// retry after deleting the existing link
265-
if (!created && GetLastError() == ERROR_ALREADY_EXISTS) {
266-
DeleteFileOrDie(dummy_link);
267-
created =
268-
CreateSymbolicLinkW(dummy_link.c_str(), dummy_target.c_str(), 0);
269-
}
270-
271-
// If we couldn't create symlink, print out an error message and exit.
272-
if (!created) {
273-
if (GetLastError() == ERROR_PRIVILEGE_NOT_HELD) {
274-
die(L"CreateSymbolicLinkW failed:\n%hs\n",
275-
"Bazel needs to create symlink for building runfiles tree.\n"
276-
"Creating symlink on Windows requires either of the following:\n"
277-
" 1. Program is running with elevated privileges (Admin "
278-
"rights).\n"
279-
" 2. The system version is Windows 10 Creators Update (1703) or "
280-
"later and "
281-
"developer mode is enabled.",
282-
GetLastErrorString().c_str());
283-
} else {
284-
die(L"CreateSymbolicLinkW failed: %hs", GetLastErrorString().c_str());
285-
}
286-
}
287-
288-
DeleteFileOrDie(dummy_link);
289-
return true;
290-
}
291-
292235
// This function scan the current directory, remove all
293236
// files/symlinks/directories that are not presented in manifest file. If a
294237
// symlink already exists and points to the correct target, this function
@@ -360,11 +303,7 @@ class RunfilesCreator {
360303
::FindClose(handle);
361304
}
362305

363-
void CreateFiles(bool creating_symlink_needs_admin_privilege) {
364-
DWORD privilege_flag = creating_symlink_needs_admin_privilege
365-
? 0
366-
: SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
367-
306+
void CreateFiles() {
368307
for (const auto& it : manifest_file_map) {
369308
// Ensure the parent directory exists
370309
wstring parent_dir = GetParentDirFromPath(it.first);
@@ -394,10 +333,22 @@ class RunfilesCreator {
394333
if (blaze_util::IsDirectoryW(it.second.c_str())) {
395334
create_dir = SYMBOLIC_LINK_FLAG_DIRECTORY;
396335
}
397-
if (!CreateSymbolicLinkW(it.first.c_str(), it.second.c_str(),
398-
privilege_flag | create_dir)) {
399-
die(L"CreateSymbolicLinkW failed (%s -> %s): %hs", it.first.c_str(),
400-
it.second.c_str(), GetLastErrorString().c_str());
336+
if (!CreateSymbolicLinkW(
337+
it.first.c_str(), it.second.c_str(),
338+
bazel::windows::symlinkPrivilegeFlag | create_dir)) {
339+
if (GetLastError() == ERROR_PRIVILEGE_NOT_HELD) {
340+
die(L"CreateSymbolicLinkW failed:\n%hs\n",
341+
"Bazel needs to create symlinks to build the runfiles tree.\n"
342+
"Creating symlinks on Windows requires one of the following:\n"
343+
" 1. Bazel is run with administrator privileges.\n"
344+
" 2. The system version is Windows 10 Creators Update "
345+
"(1703) or "
346+
"later and developer mode is enabled.",
347+
GetLastErrorString().c_str());
348+
} else {
349+
die(L"CreateSymbolicLinkW failed (%s -> %s): %hs", it.first.c_str(),
350+
it.second.c_str(), GetLastErrorString().c_str());
351+
}
401352
}
402353
}
403354
}

tools/jdk/BUILD.java_tools

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ cc_library(
308308
"java_tools/src/main/native/windows/file.h",
309309
"java_tools/src/main/native/windows/util.h",
310310
],
311+
linkopts = [
312+
"-DEFAULTLIB:advapi32.lib",
313+
],
311314
strip_include_prefix = "java_tools",
312315
)
313316

0 commit comments

Comments
 (0)