Skip to content

Commit eb2d6eb

Browse files
[9.0.0] Compensate for Windows filesystems lacking junction support (#27695)
**Problem**: Bazel fails completely on Windows when using filesystems that don't support junction/reparse point operations (e.g., virtiofs, VirtualBox shared folders, network drives, RAM disks). The fatal error occurs when `ReadSymlinkOrJunction` fails during path resolution (e.g., when Starlark code calls `.realpath`): "Cannot read link: DeviceIoControl: Incorrect function". This causes build analysis to abort completely. Additionally, `CreateJunction` failures when creating convenience symlinks produce cryptic error messages, though these were already non-fatal warnings. Both fail because `DeviceIoControl` returns `ERROR_INVALID_FUNCTION` when the filesystem doesn't implement `FSCTL_GET_REPARSE_POINT` or `FSCTL_SET_REPARSE_POINT` operations. **Proposed solution:** Handle `ERROR_INVALID_FUNCTION` gracefully by treating it as a "not supported" condition rather than a fatal error: 1. in `ReadSymlinkOrJunction` (`file.cc`:592): return `kNotALink` instead of `kError` when `ERROR_INVALID_FUNCTION` occurs. This allows path resolution to continue for non-symlink paths on unsupported filesystems. 2. in `CreateJunction` (`file.cc`:461): return new `kNotSupported` result code when `ERROR_INVALID_FUNCTION` occurs. This produces clear "filesystem does not support junctions" warnings instead of cryptic "Incorrect function" messages. This improves UX but doesn't change behavior (these failures were already non-fatal). This follows the try-first, fallback-on-error pattern (EAFP) used by other major projects when handling unsupported filesystem operations. **Prior art:** - Rust (rust-lang/rust#138133): checks `ERROR_INVALID_FUNCTION`, `ERROR_NOT_SUPPORTED`, and `ERROR_INVALID_PARAMETER` for filesystem operation fallbacks in `std::fs::rename`. - Microsoft STL (microsoft/STL#2077): handles junctions and reparse point errors including `ERROR_INVALID_PARAMETER` with robust fallback logic in `filesystem.cpp`. - Go (golang/go#20506): uses fallback strategies when symlink APIs are unavailable on different Windows versions. - WinFsp (winfsp/winfsp#88): documents that `ERROR_INVALID_FUNCTION` indicates `STATUS_NOT_IMPLEMENTED` for unsupported operations. - Microsoft Learn: recommends checking `FILE_SUPPORTS_REPARSE_POINTS` flag via `GetVolumeInformation`, but try-catch approach is simpler and handles edge cases where detection succeeds but operations fail. **Impact**: - enables Bazel to work on virtiofs, VirtualBox shared folders, RAM disks, and other filesystems that don't support Windows junction operations. - convenience symlinks (bazel-bin, bazel-out, etc.) still won't be created, but now with clearer error messages. **Limitations**: Full junction support would require filesystem-level changes (e.g., virtiofs driver improvements). **Testing:** Tested on Windows 11 VM with host directory mounted via virtiofs, with [rules_pkg](https://github.com/bazelbuild/rules_pkg/blob/6cdaba69ee76463b2b8e97e8d243dbb6115c3aee/toolchains/git/git_configure.bzl#L40). Before change: build analysis aborted with "Cannot read link" fatal error. After change: builds complete successfully with clearer warnings about unsupported junctions for convenience symlinks. Closes #27598. PiperOrigin-RevId: 833360316 Change-Id: I3751602b2bd793c1cee75b7b66fa73c955a72517 Commit dab96fc Co-authored-by: Rgis Desgroppes <[email protected]>
1 parent 634c7bd commit eb2d6eb

File tree

3 files changed

+15
-0
lines changed

3 files changed

+15
-0
lines changed

src/main/java/com/google/devtools/build/lib/windows/WindowsFileOperations.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ private WindowsFileOperations() {
7070
private static final int CREATE_JUNCTION_ALREADY_EXISTS_BUT_NOT_A_JUNCTION = 4;
7171
private static final int CREATE_JUNCTION_ACCESS_DENIED = 5;
7272
private static final int CREATE_JUNCTION_DISAPPEARED = 6;
73+
private static final int CREATE_JUNCTION_NOT_SUPPORTED = 7;
7374

7475
// Keep CREATE_SYMLINK_* values in sync with src/main/native/windows/file.h.
7576
private static final int CREATE_SYMLINK_SUCCESS = 0;
@@ -172,6 +173,9 @@ public static void createJunction(String name, String target) throws IOException
172173
case CREATE_JUNCTION_DISAPPEARED:
173174
error[0] = "the junction's path got modified unexpectedly";
174175
break;
176+
case CREATE_JUNCTION_NOT_SUPPORTED:
177+
error[0] = "filesystem does not support junctions";
178+
break;
175179
default:
176180
// This is CREATE_JUNCTION_ERROR (1). The JNI code puts a custom message in 'error[0]'.
177181
break;

src/main/native/windows/file.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,11 @@ int CreateJunction(const wstring& junction_name, const wstring& junction_target,
456456
if (err == ERROR_DIR_NOT_EMPTY) {
457457
return CreateJunctionResult::kAlreadyExistsButNotJunction;
458458
}
459+
// ERROR_INVALID_FUNCTION indicates the filesystem doesn't support
460+
// junction/reparse point operations (e.g., virtiofs).
461+
if (err == ERROR_INVALID_FUNCTION) {
462+
return CreateJunctionResult::kNotSupported;
463+
}
459464
// Some unknown error occurred.
460465
if (error) {
461466
*error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeviceIoControl",
@@ -587,6 +592,11 @@ int ReadSymlinkOrJunction(const wstring& path, wstring* result,
587592
if (err == ERROR_NOT_A_REPARSE_POINT) {
588593
return ReadSymlinkOrJunctionResult::kNotALink;
589594
}
595+
// ERROR_INVALID_FUNCTION indicates the filesystem doesn't support
596+
// reparse point operations (e.g., virtiofs). Treat as not a link.
597+
if (err == ERROR_INVALID_FUNCTION) {
598+
return ReadSymlinkOrJunctionResult::kNotALink;
599+
}
590600

591601
// Some unknown error occurred.
592602
if (error) {

src/main/native/windows/file.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ struct CreateJunctionResult {
113113
kAlreadyExistsButNotJunction = 4,
114114
kAccessDenied = 5,
115115
kDisappeared = 6,
116+
kNotSupported = 7,
116117
};
117118
};
118119

0 commit comments

Comments
 (0)