diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/same-file/src/win.rs | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/same-file/src/win.rs')
-rw-r--r-- | third_party/rust/same-file/src/win.rs | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/third_party/rust/same-file/src/win.rs b/third_party/rust/same-file/src/win.rs new file mode 100644 index 0000000000..30966d52a5 --- /dev/null +++ b/third_party/rust/same-file/src/win.rs @@ -0,0 +1,209 @@ +use std::fs::{File, OpenOptions}; +use std::hash::{Hash, Hasher}; +use std::io; +use std::mem; +use std::os::windows::fs::OpenOptionsExt; +use std::os::windows::io::{ + AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle, +}; +use std::path::Path; + +use winapi::shared::minwindef::DWORD; +use winapi::um::fileapi::{ + BY_HANDLE_FILE_INFORMATION, + GetFileInformationByHandle, +}; +use winapi::um::processenv::GetStdHandle; +use winapi::um::winbase::{ + FILE_FLAG_BACKUP_SEMANTICS, + STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE, +}; + +// For correctness, it is critical that both file handles remain open while +// their attributes are checked for equality. In particular, the file index +// numbers on a Windows stat object are not guaranteed to remain stable over +// time. +// +// See the docs and remarks on MSDN: +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363788(v=vs.85).aspx +// +// It gets worse. It appears that the index numbers are not always +// guaranteed to be unique. Namely, ReFS uses 128 bit numbers for unique +// identifiers. This requires a distinct syscall to get `FILE_ID_INFO` +// documented here: +// https://msdn.microsoft.com/en-us/library/windows/desktop/hh802691(v=vs.85).aspx +// +// It seems straight-forward enough to modify this code to use +// `FILE_ID_INFO` when available (minimum Windows Server 2012), but I don't +// have access to such Windows machines. +// +// Two notes. +// +// 1. Java's NIO uses the approach implemented here and appears to ignore +// `FILE_ID_INFO` altogether. So Java's NIO and this code are +// susceptible to bugs when running on a file system where +// `nFileIndex{Low,High}` are not unique. +// +// 2. LLVM has a bug where they fetch the id of a file and continue to use +// it even after the handle has been closed, so that uniqueness is no +// longer guaranteed (when `nFileIndex{Low,High}` are unique). +// bug report: http://lists.llvm.org/pipermail/llvm-bugs/2014-December/037218.html +// +// All said and done, checking whether two files are the same on Windows +// seems quite tricky. Moreover, even if the code is technically incorrect, +// it seems like the chances of actually observing incorrect behavior are +// extremely small. Nevertheless, we mitigate this by checking size too. +// +// In the case where this code is erroneous, two files will be reported +// as equivalent when they are in fact distinct. This will cause the loop +// detection code to report a false positive, which will prevent descending +// into the offending directory. As far as failure modes goes, this isn't +// that bad. + +#[derive(Debug)] +pub struct Handle { + file: Option<File>, + // If is_std is true, then we don't drop the corresponding File since it + // will close the handle. + is_std: bool, + key: Option<Key>, +} + +#[derive(Debug, Eq, PartialEq, Hash)] +struct Key { + volume: DWORD, + idx_high: DWORD, + idx_low: DWORD, +} + +impl Drop for Handle { + fn drop(&mut self) { + if self.is_std { + // unwrap() will not panic. Since we were able to open an + // std stream successfully, then `file` is guaranteed to be Some() + self.file.take().unwrap().into_raw_handle(); + } + } +} + +impl Eq for Handle {} + +impl PartialEq for Handle { + fn eq(&self, other: &Handle) -> bool { + // Need this branch to satisfy `Eq` since `Handle`s with `key.is_none()` + // wouldn't otherwise. + if self as *const Handle == other as *const Handle { + return true; + } else if self.key.is_none() || other.key.is_none() { + return false; + } + self.key == other.key + } +} + +impl AsRawHandle for ::Handle { + fn as_raw_handle(&self) -> RawHandle { + // unwrap() will not panic. Since we were able to open the + // file successfully, then `file` is guaranteed to be Some() + self.0.file.as_ref().take().unwrap().as_raw_handle() + } +} + +impl IntoRawHandle for ::Handle { + fn into_raw_handle(mut self) -> RawHandle { + // unwrap() will not panic. Since we were able to open the + // file successfully, then `file` is guaranteed to be Some() + self.0.file.take().unwrap().into_raw_handle() + } +} + +impl Hash for Handle { + fn hash<H: Hasher>(&self, state: &mut H) { + self.key.hash(state); + } +} + +impl Handle { + pub fn from_path<P: AsRef<Path>>(p: P) -> io::Result<Handle> { + let file = OpenOptions::new() + .read(true) + // Necessary in order to support opening directory paths. + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .open(p)?; + Handle::from_file(file) + } + + pub fn from_file(file: File) -> io::Result<Handle> { + file_info(&file).map(|info| Handle::from_file_info(file, false, info)) + } + + fn from_std_handle(file: File) -> io::Result<Handle> { + match file_info(&file) { + Ok(info) => Ok(Handle::from_file_info(file, true, info)), + // In a Windows console, if there is no pipe attached to a STD + // handle, then GetFileInformationByHandle will return an error. + // We don't really care. The only thing we care about is that + // this handle is never equivalent to any other handle, which is + // accomplished by setting key to None. + Err(_) => Ok(Handle { file: Some(file), is_std: true, key: None }), + } + } + + fn from_file_info( + file: File, + is_std: bool, + info: BY_HANDLE_FILE_INFORMATION, + ) -> Handle { + Handle { + file: Some(file), + is_std: is_std, + key: Some(Key { + volume: info.dwVolumeSerialNumber, + idx_high: info.nFileIndexHigh, + idx_low: info.nFileIndexLow, + }), + } + } + + pub fn stdin() -> io::Result<Handle> { + Handle::from_std_handle(unsafe { + File::from_raw_handle(GetStdHandle(STD_INPUT_HANDLE)) + }) + } + + pub fn stdout() -> io::Result<Handle> { + Handle::from_std_handle(unsafe { + File::from_raw_handle(GetStdHandle(STD_OUTPUT_HANDLE)) + }) + } + + pub fn stderr() -> io::Result<Handle> { + Handle::from_std_handle(unsafe { + File::from_raw_handle(GetStdHandle(STD_ERROR_HANDLE)) + }) + } + + pub fn as_file(&self) -> &File { + // unwrap() will not panic. Since we were able to open the + // file successfully, then `file` is guaranteed to be Some() + self.file.as_ref().take().unwrap() + } + + pub fn as_file_mut(&mut self) -> &mut File { + // unwrap() will not panic. Since we were able to open the + // file successfully, then `file` is guaranteed to be Some() + self.file.as_mut().take().unwrap() + } +} + +fn file_info(file: &File) -> io::Result<BY_HANDLE_FILE_INFORMATION> { + let (r, info) = unsafe { + let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed(); + (GetFileInformationByHandle(file.as_raw_handle(), &mut info), info) + }; + if r == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(info) + } +} |