Skip to content

Windows: add fallback if canonicalize fails - #161951

Merged
rust-bors[bot] merged 2 commits into
rust-lang:mainfrom
ChrisDenton:canonicalize-boogaloo
Sep 4, 2026
Merged

Windows: add fallback if canonicalize fails#161951
rust-bors[bot] merged 2 commits into
rust-lang:mainfrom
ChrisDenton:canonicalize-boogaloo

Conversation

@ChrisDenton

@ChrisDenton ChrisDenton commented Aug 28, 2026

Copy link
Copy Markdown
Member

View all comments

This attempts a partial workaround for issues such as: #59392, #79449, #59107, #54875, #52440, #52377, #48249, #74327, #55812

This may require a bit of explanation depending on how familiar you are with Windows paths, I'll try to keep it brief. The short version is that the above issues are cases where third party devices don't integrate with the system sufficiently so Windows isn't aware of the canonical drive for a particular path, causing our canonicalize function to fail. This PR works around it by manually search for the drive letter that corresponds to the root of the path. This only works in cases where there is a drive letter assigned but that is the majority of cases. It won't work when the device is only mounted to a directory in another filesystem or isn't mounted at all.

To explain the implementation of this PR you should be aware that Windows on Windows NT is more like WINE on Linux then many people realise. You have a kernel (NT) and then you have an implementation of the Win32 APIs on top (this is why kernel32.dll is nothing to do with the real kernel, it's like an implementation of Win95's kernel API on top of another OS). Admittedly the boundaries have become fuzzier over the years but there still remains a clear distinction between the Win32 API and the NT kernel API in many places.

Paths are one place where this distinction is made clear. You have the familiar Win32 paths like C:\path\to\file that date back to the time of DOS. And then you have the low-level NT kernel paths that look like \Device\HarddiskVolume6\path\to\file (which aren't really meant to be user-visible). To bridge the gap, the NT namespace has a special ?? directory containing mappings (i.e. symlinks) from drives like C: to paths like \Device\HarddiskVolume6. In that way translating between Win32 and NT paths is made simpler as you can replace C: with \??\C: and it'll get resolved to the right path and vice versa (the actual translation from win32 to NT is more complicated but I've already spent too many words on this).

So back to canonicalisation. Resolving the canonical NT path should always succeed. The problem comes when mapping that to a Win32 drive path. When the drive is managed by the system then when resolving paths it knows which drive to pick. However, if the drive mapping is added manually then it doesn't.

So the way to workaround this is to manually look at the drive mappings and see which one is the root of the NT path we have.

@rustbot rustbot added O-windows Operating system: Windows S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Aug 28, 2026
@rustbot

rustbot commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

r? @clarfonthey

rustbot has assigned @clarfonthey.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: @ChrisDenton, libs
  • @ChrisDenton, libs expanded to 13 candidates
  • Random selection from JohnTitor, Mark-Simulacrum, clarfonthey, nia-e

@ChrisDenton

Copy link
Copy Markdown
Member Author

This will probably need a libs discussion. But it isn't urgent.

Comment thread library/std/src/sys/fs/windows.rs
Comment thread library/std/src/sys/fs/windows.rs Outdated
Comment thread library/std/src/sys/fs/windows.rs Outdated
Comment thread library/std/src/sys/fs/windows.rs Outdated
Comment thread library/std/src/sys/fs/windows.rs Outdated
Comment thread library/std/src/sys/fs/windows.rs Outdated
Comment thread library/std/src/sys/pal/windows/api.rs
Comment thread library/std/src/sys/fs/windows.rs Outdated
let mut path = Vec::with_capacity(r"\\?\C:".len() + nt_path.len());
// Create a verbatim drive root (e.g. \\?\C:)
path.extend_from_slice(&[b'\\', b'\\', b'?', b'\\', letter, b':']);
path.extend(OsString::from_wide(nt_path).into_encoded_bytes());

@clarfonthey clarfonthey Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps OsStr::new(nt_path).encode_wide()?

View changes since the review

@clarfonthey

Copy link
Copy Markdown
Contributor

In terms of the current version: I have no blocking concerns and the rest looks good, so, I'll say r=me and you can merge whether you decide to make the changes I mention or not.

@ChrisDenton
ChrisDenton force-pushed the canonicalize-boogaloo branch from 9dcab79 to 7be5641 Compare September 3, 2026 00:48
@ChrisDenton

Copy link
Copy Markdown
Member Author

@bors r=clarfonthey rollup

@rust-bors

rust-bors Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 7be5641 has been approved by clarfonthey

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 3, 2026
Zalathar added a commit to Zalathar/rust that referenced this pull request Sep 3, 2026
… r=clarfonthey

Windows: add fallback if `canonicalize` fails

This attempts a partial workaround for issues such as: rust-lang#59392, rust-lang#79449, rust-lang#59107, rust-lang#54875, rust-lang#52440, rust-lang#52377, rust-lang#48249, rust-lang#74327, rust-lang#55812

This may require a bit of explanation depending on how familiar you are with Windows paths, I'll try to keep it brief. The short version is that the above issues are cases where third party devices don't integrate with the system sufficiently so Windows isn't aware of the canonical drive for a particular path, causing our `canonicalize` function to fail. This PR works around it by manually search for the drive letter that corresponds to the root of the path. This only works in cases where there is a drive letter assigned but that is the majority of cases. It won't work when the device is only mounted to a directory in another filesystem or isn't mounted at all.

To explain the implementation of this PR you should be aware that Windows on Windows NT is more like WINE on Linux then many people realise. You have a kernel (NT) and then you have an implementation of the Win32 APIs on top (this is why `kernel32.dll` is nothing to do with the real kernel, it's like an implementation of Win95's kernel API on top of another OS).  Admittedly the boundaries have become fuzzier over the years but there still remains a clear distinction between the Win32 API and the NT kernel API in many places.

Paths are one place where this distinction is made clear. You have the familiar Win32 paths like `C:\path\to\file` that date back to the time of DOS. And then you have the low-level NT kernel paths that look like `\Device\HarddiskVolume6\path\to\file` (which aren't really meant to be user-visible). To bridge the gap, the NT namespace has a special `??` directory containing mappings (i.e. symlinks) from drives like `C:` to paths like `\Device\HarddiskVolume6`. In that way translating between Win32 and NT paths is made simpler as you can replace `C:` with `\??\C:` and it'll get resolved to the right path and vice versa (the actual translation from win32 to NT is more complicated but I've already spent too many words on this).

So back to canonicalisation. Resolving the canonical NT path should always succeed. The problem comes when mapping that to a Win32 drive path. When the drive is managed by the system then when resolving paths it knows which drive to pick. However, if the drive mapping is added manually then it doesn't.

So the way to workaround this is to manually look at the drive mappings and see which one is the root of the NT path we have.
rust-bors Bot pushed a commit that referenced this pull request Sep 3, 2026
Rollup of 29 pull requests

Successful merges:

 - #161694 (add `Complex` ABI run-make test)
 - #162014 (Move more `rustdoc-html` tests using `--test` into the right folder)
 - #162164 (Revert "Implement Debug for C-like enums with a concatenated string")
 - #160564 (volatile: allow accesses to non-AM memory to trap)
 - #161579 (suggest calling a fn item used as the iterator of a `for` loop)
 - #162044 (coverage: Resolve spans to file-coordinates in a separate step)
 - #162120 (Introduce `PerOwnerLoweringState`)
 - #162132 (std: improve safety documentation in UNIX stack overflow code)
 - #162151 (Test itanium mangling of `f16` and `f128`)
 - #162162 (Don't special-case `!` in stability checks anymore)
 - #162181 (Remove wrong UnusedBraces lint for iterator loop in edition 2024 )
 - #162187 (Rename `thir::ExprKind::Use` to `ValueExpr`)
 - #158401 (mgca: Don't ICE when evaluating ValTrees that contain error constants)
 - #159873 (fuchsia: Add safestack as a supported sanitizer for x86_64 fuchsia)
 - #161847 (Preserve visibility in nested macro import suggestions)
 - #161951 (Windows: add fallback if `canonicalize` fails)
 - #161972 (Improve tests for `#[track_caller]` in async)
 - #162008 (Render the `box` pattern removal diagnostic more actionable & remove `box` expression recovery)
 - #162065 (std: don't reference `libc::O_NOFOLLOW` on VxWorks in `set_perm_nofollow`)
 - #162076 (docs(num): clarify conditions under which error occurs in `impl TryFrom<int> for int`)
 - #162152 (Revert "retrieve supported GCC targets from the sysroot")
 - #162153 (Prefer `LLVMGetVersion` for runtime info)
 - #162168 (fix ICE in project_goals/inherent)
 - #162171 (Explain LoongArch f16 NaN-boxing in inline asm)
 - #162173 (fix supposedly unreachable `bug!` being reachable)
 - #162180 (remove outdated next-solver FIXMEs)
 - #162191 (core: mark float `ClampBounds` methods as `#[inline]`)
 - #162195 (docs(time): clarify exact seconds for week and day)
 - #162199 (docs(time): clarify exact seconds for hour and minute)
@joboet

joboet commented Sep 3, 2026

Copy link
Copy Markdown
Member

Failed in a rollup: #162221 (comment)

@bors r-

@rust-bors rust-bors Bot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Sep 3, 2026
@rust-bors

rust-bors Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

This pull request was unapproved.

This PR was contained in a rollup (#162221), which was unapproved.

View changes since this unapproval

Get the NT path then search for a drive that links to a prefix of it.
@ChrisDenton
ChrisDenton force-pushed the canonicalize-boogaloo branch from 7be5641 to 29776c0 Compare September 3, 2026 13:14
@ChrisDenton

Copy link
Copy Markdown
Member Author

Sorry, fixed docs and tested them locally.

@bors r=clarfonthey

@rust-bors

rust-bors Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 29776c0 has been approved by clarfonthey

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 3, 2026
@ChrisDenton

Copy link
Copy Markdown
Member Author

Though in case it puts rollupers mind at ease:

@bors try jobs=dist-aarch64-msvc

@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Sep 3, 2026
Windows: add fallback if `canonicalize` fails


try-job: dist-aarch64-msvc
@rust-bors

rust-bors Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: a1f9be2 (a1f9be228e7801935252c911914286a7706b7b71)
Base parent: 4fcf397 (4fcf39725a9c99bd495d8c73af83628a256ff9a9)

JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Sep 3, 2026
… r=clarfonthey

Windows: add fallback if `canonicalize` fails

This attempts a partial workaround for issues such as: rust-lang#59392, rust-lang#79449, rust-lang#59107, rust-lang#54875, rust-lang#52440, rust-lang#52377, rust-lang#48249, rust-lang#74327, rust-lang#55812

This may require a bit of explanation depending on how familiar you are with Windows paths, I'll try to keep it brief. The short version is that the above issues are cases where third party devices don't integrate with the system sufficiently so Windows isn't aware of the canonical drive for a particular path, causing our `canonicalize` function to fail. This PR works around it by manually search for the drive letter that corresponds to the root of the path. This only works in cases where there is a drive letter assigned but that is the majority of cases. It won't work when the device is only mounted to a directory in another filesystem or isn't mounted at all.

To explain the implementation of this PR you should be aware that Windows on Windows NT is more like WINE on Linux then many people realise. You have a kernel (NT) and then you have an implementation of the Win32 APIs on top (this is why `kernel32.dll` is nothing to do with the real kernel, it's like an implementation of Win95's kernel API on top of another OS).  Admittedly the boundaries have become fuzzier over the years but there still remains a clear distinction between the Win32 API and the NT kernel API in many places.

Paths are one place where this distinction is made clear. You have the familiar Win32 paths like `C:\path\to\file` that date back to the time of DOS. And then you have the low-level NT kernel paths that look like `\Device\HarddiskVolume6\path\to\file` (which aren't really meant to be user-visible). To bridge the gap, the NT namespace has a special `??` directory containing mappings (i.e. symlinks) from drives like `C:` to paths like `\Device\HarddiskVolume6`. In that way translating between Win32 and NT paths is made simpler as you can replace `C:` with `\??\C:` and it'll get resolved to the right path and vice versa (the actual translation from win32 to NT is more complicated but I've already spent too many words on this).

So back to canonicalisation. Resolving the canonical NT path should always succeed. The problem comes when mapping that to a Win32 drive path. When the drive is managed by the system then when resolving paths it knows which drive to pick. However, if the drive mapping is added manually then it doesn't.

So the way to workaround this is to manually look at the drive mappings and see which one is the root of the NT path we have.
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Sep 3, 2026
… r=clarfonthey

Windows: add fallback if `canonicalize` fails

This attempts a partial workaround for issues such as: rust-lang#59392, rust-lang#79449, rust-lang#59107, rust-lang#54875, rust-lang#52440, rust-lang#52377, rust-lang#48249, rust-lang#74327, rust-lang#55812

This may require a bit of explanation depending on how familiar you are with Windows paths, I'll try to keep it brief. The short version is that the above issues are cases where third party devices don't integrate with the system sufficiently so Windows isn't aware of the canonical drive for a particular path, causing our `canonicalize` function to fail. This PR works around it by manually search for the drive letter that corresponds to the root of the path. This only works in cases where there is a drive letter assigned but that is the majority of cases. It won't work when the device is only mounted to a directory in another filesystem or isn't mounted at all.

To explain the implementation of this PR you should be aware that Windows on Windows NT is more like WINE on Linux then many people realise. You have a kernel (NT) and then you have an implementation of the Win32 APIs on top (this is why `kernel32.dll` is nothing to do with the real kernel, it's like an implementation of Win95's kernel API on top of another OS).  Admittedly the boundaries have become fuzzier over the years but there still remains a clear distinction between the Win32 API and the NT kernel API in many places.

Paths are one place where this distinction is made clear. You have the familiar Win32 paths like `C:\path\to\file` that date back to the time of DOS. And then you have the low-level NT kernel paths that look like `\Device\HarddiskVolume6\path\to\file` (which aren't really meant to be user-visible). To bridge the gap, the NT namespace has a special `??` directory containing mappings (i.e. symlinks) from drives like `C:` to paths like `\Device\HarddiskVolume6`. In that way translating between Win32 and NT paths is made simpler as you can replace `C:` with `\??\C:` and it'll get resolved to the right path and vice versa (the actual translation from win32 to NT is more complicated but I've already spent too many words on this).

So back to canonicalisation. Resolving the canonical NT path should always succeed. The problem comes when mapping that to a Win32 drive path. When the drive is managed by the system then when resolving paths it knows which drive to pick. However, if the drive mapping is added manually then it doesn't.

So the way to workaround this is to manually look at the drive mappings and see which one is the root of the NT path we have.
rust-bors Bot pushed a commit that referenced this pull request Sep 3, 2026
…uwer

Rollup of 12 pull requests

Successful merges:

 - #161227 (implement `Add` and `Sub` for `Complex`)
 - #161280 (make target feature ABI check a hard error on ARM)
 - #161893 (Add custom allocator support to `(try_)map` on `UniqueArc` and `UniqueRc`)
 - #162154 (fix[154166]: closure debug capture print)
 - #161951 (Windows: add fallback if `canonicalize` fails)
 - #162173 (fix supposedly unreachable `bug!` being reachable)
 - #162180 (remove outdated next-solver handling)
 - #162191 (core: mark float `ClampBounds` methods as `#[inline]`)
 - #162195 (docs(time): clarify exact seconds for week and day)
 - #162199 (docs(time): clarify exact seconds for hour and minute)
 - #162222 (coverage: Small cleanups in `extract_hir_info`)
 - #162230 (Pass -Z merge-functions=disabled in tests/codegen-llvm/intrinsics/unchecked_math.rs)
rust-bors Bot pushed a commit that referenced this pull request Sep 4, 2026
…uwer

Rollup of 12 pull requests

Successful merges:

 - #161227 (implement `Add` and `Sub` for `Complex`)
 - #161280 (make target feature ABI check a hard error on ARM)
 - #161893 (Add custom allocator support to `(try_)map` on `UniqueArc` and `UniqueRc`)
 - #162154 (fix[154166]: closure debug capture print)
 - #161951 (Windows: add fallback if `canonicalize` fails)
 - #162173 (fix supposedly unreachable `bug!` being reachable)
 - #162180 (remove outdated next-solver handling)
 - #162191 (core: mark float `ClampBounds` methods as `#[inline]`)
 - #162195 (docs(time): clarify exact seconds for week and day)
 - #162199 (docs(time): clarify exact seconds for hour and minute)
 - #162222 (coverage: Small cleanups in `extract_hir_info`)
 - #162230 (Pass -Z merge-functions=disabled in tests/codegen-llvm/intrinsics/unchecked_math.rs)
@rust-bors
rust-bors Bot merged commit d858574 into rust-lang:main Sep 4, 2026
14 checks passed
@rustbot rustbot added this to the 1.100.0 milestone Sep 4, 2026
rust-bors Bot pushed a commit that referenced this pull request Sep 4, 2026
Rollup merge of #161951 - ChrisDenton:canonicalize-boogaloo, r=clarfonthey

Windows: add fallback if `canonicalize` fails

This attempts a partial workaround for issues such as: #59392, #79449, #59107, #54875, #52440, #52377, #48249, #74327, #55812

This may require a bit of explanation depending on how familiar you are with Windows paths, I'll try to keep it brief. The short version is that the above issues are cases where third party devices don't integrate with the system sufficiently so Windows isn't aware of the canonical drive for a particular path, causing our `canonicalize` function to fail. This PR works around it by manually search for the drive letter that corresponds to the root of the path. This only works in cases where there is a drive letter assigned but that is the majority of cases. It won't work when the device is only mounted to a directory in another filesystem or isn't mounted at all.

To explain the implementation of this PR you should be aware that Windows on Windows NT is more like WINE on Linux then many people realise. You have a kernel (NT) and then you have an implementation of the Win32 APIs on top (this is why `kernel32.dll` is nothing to do with the real kernel, it's like an implementation of Win95's kernel API on top of another OS).  Admittedly the boundaries have become fuzzier over the years but there still remains a clear distinction between the Win32 API and the NT kernel API in many places.

Paths are one place where this distinction is made clear. You have the familiar Win32 paths like `C:\path\to\file` that date back to the time of DOS. And then you have the low-level NT kernel paths that look like `\Device\HarddiskVolume6\path\to\file` (which aren't really meant to be user-visible). To bridge the gap, the NT namespace has a special `??` directory containing mappings (i.e. symlinks) from drives like `C:` to paths like `\Device\HarddiskVolume6`. In that way translating between Win32 and NT paths is made simpler as you can replace `C:` with `\??\C:` and it'll get resolved to the right path and vice versa (the actual translation from win32 to NT is more complicated but I've already spent too many words on this).

So back to canonicalisation. Resolving the canonical NT path should always succeed. The problem comes when mapping that to a Win32 drive path. When the drive is managed by the system then when resolving paths it knows which drive to pick. However, if the drive mapping is added manually then it doesn't.

So the way to workaround this is to manually look at the drive mappings and see which one is the root of the NT path we have.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

O-windows Operating system: Windows S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants