From 301ea247847d3819538744e87ac6a04a5e9a3071 Mon Sep 17 00:00:00 2001 From: Rgis Desgroppes Date: Mon, 17 Nov 2025 08:55:13 -0800 Subject: [PATCH] Compensate for Windows filesystems lacking junction support **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 --- .../build/lib/windows/WindowsFileOperations.java | 4 ++++ src/main/native/windows/file.cc | 10 ++++++++++ src/main/native/windows/file.h | 1 + 3 files changed, 15 insertions(+) diff --git a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileOperations.java b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileOperations.java index 89c7e1484f3a4e..d5e2058e57e015 100644 --- a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileOperations.java +++ b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileOperations.java @@ -70,6 +70,7 @@ private WindowsFileOperations() { private static final int CREATE_JUNCTION_ALREADY_EXISTS_BUT_NOT_A_JUNCTION = 4; private static final int CREATE_JUNCTION_ACCESS_DENIED = 5; private static final int CREATE_JUNCTION_DISAPPEARED = 6; + private static final int CREATE_JUNCTION_NOT_SUPPORTED = 7; // Keep CREATE_SYMLINK_* values in sync with src/main/native/windows/file.h. private static final int CREATE_SYMLINK_SUCCESS = 0; @@ -172,6 +173,9 @@ public static void createJunction(String name, String target) throws IOException case CREATE_JUNCTION_DISAPPEARED: error[0] = "the junction's path got modified unexpectedly"; break; + case CREATE_JUNCTION_NOT_SUPPORTED: + error[0] = "filesystem does not support junctions"; + break; default: // This is CREATE_JUNCTION_ERROR (1). The JNI code puts a custom message in 'error[0]'. break; diff --git a/src/main/native/windows/file.cc b/src/main/native/windows/file.cc index ed6653e3e60d75..7a24589285e28a 100644 --- a/src/main/native/windows/file.cc +++ b/src/main/native/windows/file.cc @@ -456,6 +456,11 @@ int CreateJunction(const wstring& junction_name, const wstring& junction_target, if (err == ERROR_DIR_NOT_EMPTY) { return CreateJunctionResult::kAlreadyExistsButNotJunction; } + // ERROR_INVALID_FUNCTION indicates the filesystem doesn't support + // junction/reparse point operations (e.g., virtiofs). + if (err == ERROR_INVALID_FUNCTION) { + return CreateJunctionResult::kNotSupported; + } // Some unknown error occurred. if (error) { *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeviceIoControl", @@ -587,6 +592,11 @@ int ReadSymlinkOrJunction(const wstring& path, wstring* result, if (err == ERROR_NOT_A_REPARSE_POINT) { return ReadSymlinkOrJunctionResult::kNotALink; } + // ERROR_INVALID_FUNCTION indicates the filesystem doesn't support + // reparse point operations (e.g., virtiofs). Treat as not a link. + if (err == ERROR_INVALID_FUNCTION) { + return ReadSymlinkOrJunctionResult::kNotALink; + } // Some unknown error occurred. if (error) { diff --git a/src/main/native/windows/file.h b/src/main/native/windows/file.h index 4e7c6557b655e5..973899fdb55b37 100644 --- a/src/main/native/windows/file.h +++ b/src/main/native/windows/file.h @@ -113,6 +113,7 @@ struct CreateJunctionResult { kAlreadyExistsButNotJunction = 4, kAccessDenied = 5, kDisappeared = 6, + kNotSupported = 7, }; };