Skip to content

Commit 8039567

Browse files
authored
Rollup merge of #98916 - ChrisDenton:hiberfil.sys, r=thomcc
Windows: Use `FindFirstFileW` for getting the metadata of locked system files Fixes #96980 Usually opening a file handle with access set to metadata only will always succeed, even if the file is locked. However some special system files, such as `C:\hiberfil.sys`, are locked by the system in a way that denies even that. So as a fallback we try reading the cached metadata from the directory. Note that the test is a bit iffy. I don't know if `hiberfil.sys` actually exists in the CI. r? rust-lang/libs
2 parents 97c9f16 + eee8ca9 commit 8039567

File tree

2 files changed

+99
-26
lines changed

2 files changed

+99
-26
lines changed

Diff for: library/std/src/fs/tests.rs

+17
Original file line numberDiff line numberDiff line change
@@ -1534,3 +1534,20 @@ fn read_large_dir() {
15341534
entry.unwrap();
15351535
}
15361536
}
1537+
1538+
/// Test the fallback for getting the metadata of files like hiberfil.sys that
1539+
/// Windows holds a special lock on, preventing normal means of querying
1540+
/// metadata. See #96980.
1541+
///
1542+
/// Note this fails in CI because `hiberfil.sys` does not actually exist there.
1543+
/// Therefore it's marked as ignored.
1544+
#[test]
1545+
#[ignore]
1546+
#[cfg(windows)]
1547+
fn hiberfil_sys() {
1548+
let hiberfil = Path::new(r"C:\hiberfil.sys");
1549+
assert_eq!(true, hiberfil.try_exists().unwrap());
1550+
fs::symlink_metadata(hiberfil).unwrap();
1551+
fs::metadata(hiberfil).unwrap();
1552+
assert_eq!(true, hiberfil.exists());
1553+
}

Diff for: library/std/src/sys/windows/fs.rs

+82-26
Original file line numberDiff line numberDiff line change
@@ -155,22 +155,7 @@ impl DirEntry {
155155
}
156156

157157
pub fn metadata(&self) -> io::Result<FileAttr> {
158-
Ok(FileAttr {
159-
attributes: self.data.dwFileAttributes,
160-
creation_time: self.data.ftCreationTime,
161-
last_access_time: self.data.ftLastAccessTime,
162-
last_write_time: self.data.ftLastWriteTime,
163-
file_size: ((self.data.nFileSizeHigh as u64) << 32) | (self.data.nFileSizeLow as u64),
164-
reparse_tag: if self.data.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
165-
// reserved unless this is a reparse point
166-
self.data.dwReserved0
167-
} else {
168-
0
169-
},
170-
volume_serial_number: None,
171-
number_of_links: None,
172-
file_index: None,
173-
})
158+
Ok(self.data.into())
174159
}
175160
}
176161

@@ -879,6 +864,26 @@ impl FileAttr {
879864
self.file_index
880865
}
881866
}
867+
impl From<c::WIN32_FIND_DATAW> for FileAttr {
868+
fn from(wfd: c::WIN32_FIND_DATAW) -> Self {
869+
FileAttr {
870+
attributes: wfd.dwFileAttributes,
871+
creation_time: wfd.ftCreationTime,
872+
last_access_time: wfd.ftLastAccessTime,
873+
last_write_time: wfd.ftLastWriteTime,
874+
file_size: ((wfd.nFileSizeHigh as u64) << 32) | (wfd.nFileSizeLow as u64),
875+
reparse_tag: if wfd.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
876+
// reserved unless this is a reparse point
877+
wfd.dwReserved0
878+
} else {
879+
0
880+
},
881+
volume_serial_number: None,
882+
number_of_links: None,
883+
file_index: None,
884+
}
885+
}
886+
}
882887

883888
fn to_u64(ft: &c::FILETIME) -> u64 {
884889
(ft.dwLowDateTime as u64) | ((ft.dwHighDateTime as u64) << 32)
@@ -1145,22 +1150,73 @@ pub fn link(_original: &Path, _link: &Path) -> io::Result<()> {
11451150
}
11461151

11471152
pub fn stat(path: &Path) -> io::Result<FileAttr> {
1148-
let mut opts = OpenOptions::new();
1149-
// No read or write permissions are necessary
1150-
opts.access_mode(0);
1151-
// This flag is so we can open directories too
1152-
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
1153-
let file = File::open(path, &opts)?;
1154-
file.file_attr()
1153+
metadata(path, ReparsePoint::Follow)
11551154
}
11561155

11571156
pub fn lstat(path: &Path) -> io::Result<FileAttr> {
1157+
metadata(path, ReparsePoint::Open)
1158+
}
1159+
1160+
#[repr(u32)]
1161+
#[derive(Clone, Copy, PartialEq, Eq)]
1162+
enum ReparsePoint {
1163+
Follow = 0,
1164+
Open = c::FILE_FLAG_OPEN_REPARSE_POINT,
1165+
}
1166+
impl ReparsePoint {
1167+
fn as_flag(self) -> u32 {
1168+
self as u32
1169+
}
1170+
}
1171+
1172+
fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result<FileAttr> {
11581173
let mut opts = OpenOptions::new();
11591174
// No read or write permissions are necessary
11601175
opts.access_mode(0);
1161-
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT);
1162-
let file = File::open(path, &opts)?;
1163-
file.file_attr()
1176+
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | reparse.as_flag());
1177+
1178+
// Attempt to open the file normally.
1179+
// If that fails with `ERROR_SHARING_VIOLATION` then retry using `FindFirstFileW`.
1180+
// If the fallback fails for any reason we return the original error.
1181+
match File::open(path, &opts) {
1182+
Ok(file) => file.file_attr(),
1183+
Err(e) if e.raw_os_error() == Some(c::ERROR_SHARING_VIOLATION as _) => {
1184+
// `ERROR_SHARING_VIOLATION` will almost never be returned.
1185+
// Usually if a file is locked you can still read some metadata.
1186+
// However, there are special system files, such as
1187+
// `C:\hiberfil.sys`, that are locked in a way that denies even that.
1188+
unsafe {
1189+
let path = maybe_verbatim(path)?;
1190+
1191+
// `FindFirstFileW` accepts wildcard file names.
1192+
// Fortunately wildcards are not valid file names and
1193+
// `ERROR_SHARING_VIOLATION` means the file exists (but is locked)
1194+
// therefore it's safe to assume the file name given does not
1195+
// include wildcards.
1196+
let mut wfd = mem::zeroed();
1197+
let handle = c::FindFirstFileW(path.as_ptr(), &mut wfd);
1198+
1199+
if handle == c::INVALID_HANDLE_VALUE {
1200+
// This can fail if the user does not have read access to the
1201+
// directory.
1202+
Err(e)
1203+
} else {
1204+
// We no longer need the find handle.
1205+
c::FindClose(handle);
1206+
1207+
// `FindFirstFileW` reads the cached file information from the
1208+
// directory. The downside is that this metadata may be outdated.
1209+
let attrs = FileAttr::from(wfd);
1210+
if reparse == ReparsePoint::Follow && attrs.file_type().is_symlink() {
1211+
Err(e)
1212+
} else {
1213+
Ok(attrs)
1214+
}
1215+
}
1216+
}
1217+
}
1218+
Err(e) => Err(e),
1219+
}
11641220
}
11651221

11661222
pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {

0 commit comments

Comments
 (0)