diff options
| author | Chris Denton <christophersdenton@gmail.com> | 2022-07-05 09:13:40 +0100 |
|---|---|---|
| committer | Chris Denton <christophersdenton@gmail.com> | 2022-07-05 09:15:35 +0100 |
| commit | 8d4adad953edef2ef94b02e2bb1ed9b22bb1d975 (patch) | |
| tree | d0f78ce23e8d3a4301aa284e0396f4c4dec09f8a /library/std/src/sys/windows | |
| parent | 13ab7962ac131733400356241c37e3ddd68d1aff (diff) | |
| download | rust-8d4adad953edef2ef94b02e2bb1ed9b22bb1d975.tar.gz rust-8d4adad953edef2ef94b02e2bb1ed9b22bb1d975.zip | |
Windows: Use `FindFirstFileW` if `metadata` fails
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.
Diffstat (limited to 'library/std/src/sys/windows')
| -rw-r--r-- | library/std/src/sys/windows/fs.rs | 71 |
1 files changed, 61 insertions, 10 deletions
diff --git a/library/std/src/sys/windows/fs.rs b/library/std/src/sys/windows/fs.rs index b5d2b4de704..4d3162f1254 100644 --- a/library/std/src/sys/windows/fs.rs +++ b/library/std/src/sys/windows/fs.rs @@ -1150,22 +1150,73 @@ pub fn link(_original: &Path, _link: &Path) -> io::Result<()> { } pub fn stat(path: &Path) -> io::Result<FileAttr> { - let mut opts = OpenOptions::new(); - // No read or write permissions are necessary - opts.access_mode(0); - // This flag is so we can open directories too - opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS); - let file = File::open(path, &opts)?; - file.file_attr() + metadata(path, ReparsePoint::Follow) } pub fn lstat(path: &Path) -> io::Result<FileAttr> { + metadata(path, ReparsePoint::Open) +} + +#[repr(u32)] +#[derive(Clone, Copy, PartialEq, Eq)] +enum ReparsePoint { + Follow = 0, + Open = c::FILE_FLAG_OPEN_REPARSE_POINT, +} +impl ReparsePoint { + fn as_flag(self) -> u32 { + self as u32 + } +} + +fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result<FileAttr> { let mut opts = OpenOptions::new(); // No read or write permissions are necessary opts.access_mode(0); - opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT); - let file = File::open(path, &opts)?; - file.file_attr() + opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | reparse.as_flag()); + + // Attempt to open the file normally. + // If that fails with `ERROR_SHARING_VIOLATION` then retry using `FindFirstFileW`. + // If the fallback fails for any reason we return the original error. + match File::open(path, &opts) { + Ok(file) => file.file_attr(), + Err(e) if e.raw_os_error() == Some(c::ERROR_SHARING_VIOLATION as _) => { + // `ERROR_SHARING_VIOLATION` will almost never be returned. + // Usually if a file is locked you can still read some metadata. + // However, there are special system files, such as + // `C:\hiberfil.sys`, that are locked in a way that denies even that. + unsafe { + let path = maybe_verbatim(path)?; + + // `FindFirstFileW` accepts wildcard file names. + // Fortunately wildcards are not valid file names and + // `ERROR_SHARING_VIOLATION` means the file exists (but is locked) + // therefore it's safe to assume the file name given does not + // include wildcards. + let mut wfd = mem::zeroed(); + let handle = c::FindFirstFileW(path.as_ptr(), &mut wfd); + + if handle == c::INVALID_HANDLE_VALUE { + // This can fail if the user does not have read access to the + // directory. + Err(e) + } else { + // We no longer need the find handle. + c::FindClose(handle); + + // `FindFirstFileW` reads the cached file information from the + // directory. The downside is that this metadata may be outdated. + let attrs = FileAttr::from(wfd); + if reparse == ReparsePoint::Follow && attrs.file_type().is_symlink() { + Err(e) + } else { + Ok(attrs) + } + } + } + } + Err(e) => Err(e), + } } pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { |
