1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use failure::Error;
use fs2::FileExt;
use log::warn;
use std::fs::OpenOptions;
use std::path::{Component, Path, PathBuf, Prefix, PrefixComponent};
pub(crate) fn file_lock<T>(
path: &Path,
msg: &str,
f: impl FnOnce() -> Result<T, Error> + std::panic::UnwindSafe,
) -> Result<T, Error> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)?;
let mut message_displayed = false;
while let Err(err) = file.try_lock_exclusive() {
if !message_displayed && err.kind() == fs2::lock_contended_error().kind() {
warn!("blocking on other processes finishing to {}", msg);
message_displayed = true;
}
file.lock_exclusive()?;
}
let res = std::panic::catch_unwind(f);
let _ = file.unlock();
match res {
Ok(res) => res,
Err(panic) => std::panic::resume_unwind(panic),
}
}
fn strip_verbatim_from_prefix(prefix: &PrefixComponent<'_>) -> Option<PathBuf> {
let ret = match prefix.kind() {
Prefix::Verbatim(s) => Path::new(s).to_owned(),
Prefix::VerbatimDisk(drive) => [format!(r"{}:\", drive as char)].iter().collect(),
Prefix::VerbatimUNC(_, _) => unimplemented!(),
_ => return None,
};
Some(ret)
}
pub(crate) fn normalize_path(path: &Path) -> PathBuf {
let mut p = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
if cfg!(windows) {
const MAX_PATH_LEN: usize = 260 - 12;
let mut components = p.components();
let first_component = components.next().unwrap();
if let Component::Prefix(prefix) = first_component {
if let Some(mut modified_path) = strip_verbatim_from_prefix(&prefix) {
modified_path.push(components.as_path());
p = modified_path;
}
}
if p.as_os_str().len() >= MAX_PATH_LEN {
warn!(
"Canonicalized path is too long for Windows: {:?}",
p.as_os_str(),
);
}
}
p
}
#[cfg(test)]
#[cfg(windows)]
mod windows_tests {
use super::*;
use std::path::Path;
#[test]
fn strip_verbatim() {
let suite = vec![
(r"C:\Users\carl", None),
(r"\Users\carl", None),
(r"\\?\C:\Users\carl", Some(r"C:\")),
(r"\\?\Users\carl", Some(r"Users")),
];
for (input, output) in suite {
let p = Path::new(input);
let first_component = p.components().next().unwrap();
if let Component::Prefix(prefix) = &first_component {
let stripped = strip_verbatim_from_prefix(&prefix);
assert_eq!(stripped.as_ref().map(|p| p.to_str().unwrap()), output);
} else {
assert!(output.is_none());
}
}
}
}