use std::ffi::OsString;
use std::fs::File;
use std::io;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::os::windows::io::OwnedHandle;
use std::path::{Path, PathBuf};
use windows_namespace_request_sys::PathCaptureError;
use windows_namespace_request_sys::open::OpenFile;
use windows_sys::Win32::Foundation::MAX_PATH;
use windows_sys::Win32::Storage::FileSystem::{
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
};
use wtf_string::Wtf16String;
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct Parameters {
pub access: u32,
pub disposition: u32,
pub flags: u32,
pub truncate_after: bool,
}
const VERBATIM: [u16; 4] = [b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16];
pub(super) fn open(path: &Path, parameters: Parameters) -> io::Result<OwnedHandle> {
let path = verbatim_if_long(path)?;
if path.as_os_str().is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"the path was empty",
));
}
let request = OpenFile::new(Wtf16String::from_os_str(path.as_os_str()))
.map_err(path_error)?
.with_desired_access(parameters.access)
.with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.with_creation_disposition(parameters.disposition)
.with_flags_and_attributes(parameters.flags);
let handle = request
.perform()
.map_err(|error| io::Error::from_raw_os_error(raw_code(error.code())))?;
if !parameters.truncate_after {
return Ok(handle);
}
let file = File::from(handle);
file.set_len(0)?;
Ok(OwnedHandle::from(file))
}
fn verbatim_if_long(path: &Path) -> io::Result<PathBuf> {
let absolute = std::path::absolute(path)?;
let longest = MAX_PATH as usize - 1;
if path.as_os_str().encode_wide().count() <= longest
&& absolute.as_os_str().encode_wide().count() <= longest
{
return Ok(path.to_path_buf());
}
Ok(verbatim(&absolute))
}
fn verbatim(absolute: &Path) -> PathBuf {
let units: Vec<u16> = absolute.as_os_str().encode_wide().collect();
let backslash = u16::from(b'\\');
let question = u16::from(b'?');
let dot = u16::from(b'.');
let starts_with_two_backslashes = units.starts_with(&[backslash, backslash]);
let third = units.get(2).copied();
if starts_with_two_backslashes && matches!(third, Some(unit) if unit == question || unit == dot)
{
return absolute.to_path_buf();
}
let mut verbatim = VERBATIM.to_vec();
if starts_with_two_backslashes {
verbatim.extend("UNC\\".encode_utf16());
verbatim.extend_from_slice(&units[2..]);
} else {
verbatim.extend_from_slice(&units);
}
PathBuf::from(OsString::from_wide(&verbatim))
}
fn path_error(error: PathCaptureError) -> io::Error {
match error {
PathCaptureError::CurrentDirectory(code) => {
io::Error::from_raw_os_error(raw_code(code.code()))
}
other => io::Error::new(io::ErrorKind::InvalidInput, other),
}
}
fn raw_code(code: u32) -> i32 {
i32::from_ne_bytes(code.to_ne_bytes())
}