turbo_tasks_fs/windows.rs
1use std::path::{Path, PathBuf};
2
3/// Converts `path` into the verbatim, drive-letter-case-folded representation used internally by
4/// [`crate::DiskFileSystem`] on Windows.
5///
6/// This is the purely lexical counterpart to [`std::fs::canonicalize`]: `to_verbatim`
7/// (`GetFullPathNameW`) makes the path absolute and verbatim (`\\?\`-prefixed) but, unlike
8/// `canonicalize`, does not touch the disk — it resolves neither symlinks nor 8.3 short names. The
9/// drive letter is then upper-cased to match the form `GetFinalPathNameByHandle` (and thus
10/// `canonicalize`) produces.
11///
12/// No-op on non-Windows platforms (returns `path` unchanged).
13pub fn to_verbatim_with_case_folded_disk(path: &Path) -> std::io::Result<PathBuf> {
14 #[cfg(windows)]
15 {
16 use std::{
17 ffi::OsString,
18 os::windows::ffi::{OsStrExt, OsStringExt},
19 path::{Component, Prefix},
20 };
21
22 use omnipath::WinPathExt;
23
24 // `to_verbatim` guarantees an absolute, verbatim path from here on, so there's no
25 // non-verbatim case to guard against below.
26 let path = path.to_verbatim()?;
27
28 // Only `\\?\C:\...` (`VerbatimDisk`) paths carry a drive letter; verbatim UNC
29 // (`\\?\UNC\...`) and other verbatim device paths (`\\?\prefix`) don't, so there's nothing
30 // to case-fold for those.
31 //
32 // We can't read the letter from `VerbatimDisk(disk)` because that value is already
33 // normalized to uppercase, so it wouldn't tell us whether the underlying path needs
34 // rewriting.
35 let is_verbatim_disk = matches!(
36 path.components().next(),
37 Some(Component::Prefix(prefix)) if matches!(prefix.kind(), Prefix::VerbatimDisk(_))
38 );
39 if !is_verbatim_disk {
40 return Ok(path);
41 }
42
43 // The layout is `\\?\C:\...`, so the drive letter is the 5th UTF-16 code unit (index 4)
44 // and is guaranteed to be a-z or A-Z.
45 if let Some(disk) = path.as_os_str().encode_wide().nth(4).map(|disk| disk as u8)
46 && disk.is_ascii_lowercase()
47 {
48 // we must encode/decode because OsString's internal encoding is opaque/unstable
49 let mut wide: Vec<u16> = path.as_os_str().encode_wide().collect();
50 wide[4] = u16::from(disk.to_ascii_uppercase());
51 return Ok(PathBuf::from(OsString::from_wide(&wide)));
52 }
53
54 Ok(path)
55 }
56
57 #[cfg(not(windows))]
58 Ok(path.to_path_buf())
59}
60
61#[cfg(all(test, windows))]
62mod tests {
63 use std::path::Path;
64
65 use super::*;
66
67 fn fold(path: &str) -> String {
68 to_verbatim_with_case_folded_disk(Path::new(path))
69 .unwrap()
70 .to_str()
71 .unwrap()
72 .to_owned()
73 }
74
75 #[test]
76 fn upper_cases_lower_drive_letter() {
77 // `to_verbatim` passes an already-verbatim path through unchanged, so these exercise the
78 // drive-letter case-folding in isolation.
79 assert_eq!(fold(r"\\?\c:\foo\bar"), r"\\?\C:\foo\bar");
80 assert_eq!(fold(r"\\?\z:\"), r"\\?\Z:\");
81 }
82
83 #[test]
84 fn ignores_verbatim_paths_without_a_drive() {
85 assert_eq!(
86 fold(r"\\?\UNC\server\share\foo"),
87 r"\\?\UNC\server\share\foo"
88 );
89 }
90}