use super::{DirHandle, Excl, GuardIo, NodeId, NodeKind, OpenMode};
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use std::path::{Path, PathBuf};
use std::ptr;
use windows_sys::Wdk::Foundation::OBJECT_ATTRIBUTES;
use windows_sys::Wdk::Storage::FileSystem::{
FileRenameInformation, NtCreateFile, NtSetInformationFile, FILE_CREATE, FILE_DIRECTORY_FILE,
FILE_NON_DIRECTORY_FILE, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OVERWRITE_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
};
use windows_sys::Win32::Foundation::{
RtlNtStatusToDosError, GENERIC_READ, GENERIC_WRITE, HANDLE, OBJ_CASE_INSENSITIVE,
OBJ_DONT_REPARSE, STATUS_SUCCESS, UNICODE_STRING,
};
use windows_sys::Win32::Storage::FileSystem::{
FileBasicInfo, FileDispositionInfo, FileIdInfo, GetFileInformationByHandle,
GetFileInformationByHandleEx, GetFinalPathNameByHandleW, SetFileInformationByHandle,
BY_HANDLE_FILE_INFORMATION, DELETE, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_REPARSE_POINT,
FILE_BASIC_INFO, FILE_DISPOSITION_INFO, FILE_FLAG_BACKUP_SEMANTICS,
FILE_FLAG_OPEN_REPARSE_POINT, FILE_ID_INFO, FILE_NAME_NORMALIZED, FILE_READ_ATTRIBUTES,
FILE_RENAME_INFO, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES,
SYNCHRONIZE, VOLUME_NAME_DOS,
};
const STATUS_REPARSE_POINT_ENCOUNTERED: i32 = 0xC000_050Bu32 as i32;
const STATUS_NOT_A_DIRECTORY: i32 = 0xC000_0103u32 as i32;
pub struct WindowsDirHandle(OwnedHandle);
impl crate::sealed::Sealed for WindowsDirHandle {}
const SHARE_ALL: u32 = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
fn wide(s: &OsStr) -> Vec<u16> {
s.encode_wide().collect()
}
fn nt_error(status: i32) -> GuardIo {
let code = unsafe { RtlNtStatusToDosError(status) };
let err = io::Error::from_raw_os_error(code as i32);
if matches!(
status,
STATUS_REPARSE_POINT_ENCOUNTERED | STATUS_NOT_A_DIRECTORY
) {
GuardIo::link(err)
} else {
GuardIo::io(err)
}
}
fn nt_open(
root: Option<HANDLE>,
name: &mut [u16],
access: u32,
disposition: u32,
options: u32,
extra_obj_flags: u32,
) -> Result<OwnedHandle, GuardIo> {
let byte_len = (name.len() * 2) as u16;
let mut unicode = UNICODE_STRING {
Length: byte_len,
MaximumLength: byte_len,
Buffer: name.as_mut_ptr(),
};
let attrs = OBJECT_ATTRIBUTES {
Length: size_of::<OBJECT_ATTRIBUTES>() as u32,
RootDirectory: root.unwrap_or(ptr::null_mut()),
ObjectName: &mut unicode,
Attributes: OBJ_CASE_INSENSITIVE | extra_obj_flags,
SecurityDescriptor: ptr::null_mut(),
SecurityQualityOfService: ptr::null_mut(),
};
let mut handle: HANDLE = ptr::null_mut();
let mut iosb = unsafe { std::mem::zeroed() };
let status = unsafe {
NtCreateFile(
&mut handle,
access | SYNCHRONIZE,
&attrs,
&mut iosb,
ptr::null(),
0,
SHARE_ALL,
disposition,
options | FILE_SYNCHRONOUS_IO_NONALERT,
ptr::null(),
0,
)
};
if status != STATUS_SUCCESS {
return Err(nt_error(status));
}
Ok(unsafe { OwnedHandle::from_raw_handle(handle as _) })
}
fn info_by_handle(h: HANDLE) -> Option<BY_HANDLE_FILE_INFORMATION> {
let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
if unsafe { GetFileInformationByHandle(h, &mut info) } == 0 {
return None;
}
Some(info)
}
fn refuse_if_reparse(h: &OwnedHandle) -> Result<(), GuardIo> {
let Some(info) = info_by_handle(h.as_raw_handle() as HANDLE) else {
return Err(GuardIo::last_os_error());
};
if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
return Err(GuardIo::link(io::Error::from(io::ErrorKind::InvalidInput)));
}
Ok(())
}
fn to_file(h: OwnedHandle) -> File {
File::from(h)
}
impl DirHandle for WindowsDirHandle {
type Attrs = u32;
fn open_root(path: &Path) -> Result<Self, GuardIo> {
let native = path.display().to_string().replace('/', "\\");
let mut name = wide(OsStr::new(&format!(r"\??\{native}")));
let h = nt_open(
None,
&mut name,
FILE_READ_ATTRIBUTES | GENERIC_READ,
FILE_OPEN,
FILE_DIRECTORY_FILE,
0,
)?;
Ok(Self(h))
}
fn open_child_dir(&self, name: &OsStr) -> Result<Self, GuardIo> {
let mut n = wide(name);
let h = nt_open(
Some(self.0.as_raw_handle() as HANDLE),
&mut n,
FILE_READ_ATTRIBUTES | GENERIC_READ,
FILE_OPEN,
FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT,
0,
)?;
refuse_if_reparse(&h)?;
Ok(Self(h))
}
fn make_child_dir(&self, name: &OsStr) -> Result<(), GuardIo> {
let mut n = wide(name);
nt_open(
Some(self.0.as_raw_handle() as HANDLE),
&mut n,
FILE_READ_ATTRIBUTES,
FILE_CREATE,
FILE_DIRECTORY_FILE,
0,
)?;
Ok(())
}
fn open_child_file(&self, name: &OsStr, mode: OpenMode) -> Result<File, GuardIo> {
let mut n = wide(name);
let options = match mode {
OpenMode::File => FILE_NON_DIRECTORY_FILE,
OpenMode::Dir => FILE_DIRECTORY_FILE,
} | FILE_OPEN_REPARSE_POINT;
let h = nt_open(
Some(self.0.as_raw_handle() as HANDLE),
&mut n,
GENERIC_READ,
FILE_OPEN,
options,
0,
)?;
refuse_if_reparse(&h)?;
Ok(to_file(h))
}
fn create_child_file(&self, name: &OsStr, excl: Excl) -> Result<File, GuardIo> {
let mut n = wide(name);
let disposition = match excl {
Excl::Truncate => FILE_OVERWRITE_IF,
Excl::MustNotExist => FILE_CREATE,
};
let h = nt_open(
Some(self.0.as_raw_handle() as HANDLE),
&mut n,
GENERIC_WRITE | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES,
disposition,
FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT,
0,
)?;
refuse_if_reparse(&h)?;
Ok(to_file(h))
}
fn rename_child(&self, from: &OsStr, to: &OsStr) -> Result<(), GuardIo> {
let mut n = wide(from);
let src = nt_open(
Some(self.0.as_raw_handle() as HANDLE),
&mut n,
DELETE,
FILE_OPEN,
FILE_OPEN_REPARSE_POINT,
0,
)?;
let target = wide(to);
let bytes = size_of::<FILE_RENAME_INFO>() + (target.len() + 1) * 2;
let mut buf = vec![0u8; bytes];
let info = unsafe { &mut *(buf.as_mut_ptr() as *mut FILE_RENAME_INFO) };
info.Anonymous.ReplaceIfExists = true;
info.RootDirectory = self.0.as_raw_handle() as HANDLE;
info.FileNameLength = (target.len() * 2) as u32;
unsafe {
ptr::copy_nonoverlapping(target.as_ptr(), info.FileName.as_mut_ptr(), target.len());
}
let mut iosb = unsafe { std::mem::zeroed() };
let status = unsafe {
NtSetInformationFile(
src.as_raw_handle() as HANDLE,
&mut iosb,
buf.as_ptr().cast(),
bytes as u32,
FileRenameInformation,
)
};
if status != STATUS_SUCCESS {
return Err(nt_error(status));
}
Ok(())
}
fn unlink_child(&self, name: &OsStr) {
let mut n = wide(name);
let Ok(h) = nt_open(
Some(self.0.as_raw_handle() as HANDLE),
&mut n,
DELETE,
FILE_OPEN,
FILE_OPEN_REPARSE_POINT,
0,
) else {
return;
};
let info = FILE_DISPOSITION_INFO { DeleteFile: true };
unsafe {
SetFileInformationByHandle(
h.as_raw_handle() as HANDLE,
FileDispositionInfo,
(&raw const info).cast(),
size_of::<FILE_DISPOSITION_INFO>() as u32,
);
}
}
fn child_kind(&self, name: &OsStr) -> Option<NodeKind> {
let mut n = wide(name);
let h = nt_open(
Some(self.0.as_raw_handle() as HANDLE),
&mut n,
FILE_READ_ATTRIBUTES,
FILE_OPEN,
FILE_OPEN_REPARSE_POINT,
0,
)
.ok()?;
let info = info_by_handle(h.as_raw_handle() as HANDLE)?;
if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
return Some(NodeKind::NotFollowable { tag: None });
}
if info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0 {
return Some(NodeKind::Dir);
}
Some(NodeKind::RegularFile)
}
fn child_attrs(&self, name: &OsStr) -> Option<Self::Attrs> {
let mut n = wide(name);
let h = nt_open(
Some(self.0.as_raw_handle() as HANDLE),
&mut n,
FILE_READ_ATTRIBUTES,
FILE_OPEN,
FILE_OPEN_REPARSE_POINT,
0,
)
.ok()?;
Some(info_by_handle(h.as_raw_handle() as HANDLE)?.dwFileAttributes)
}
fn apply_attrs(&self, file: &File, attrs: Self::Attrs) -> io::Result<()> {
let mut info: FILE_BASIC_INFO = unsafe { std::mem::zeroed() };
info.FileAttributes = attrs;
let ok = unsafe {
SetFileInformationByHandle(
file.as_raw_handle() as HANDLE,
FileBasicInfo,
(&raw const info).cast(),
size_of::<FILE_BASIC_INFO>() as u32,
)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn identity(&self) -> Result<NodeId, GuardIo> {
id_from(self.0.as_raw_handle() as HANDLE).map_err(GuardIo::io)
}
fn resolve_beneath(&self, rel: &Path, mode: OpenMode) -> Result<Option<File>, GuardIo> {
let native: PathBuf = rel
.components()
.filter_map(|c| match c {
std::path::Component::Normal(s) => Some(s),
_ => None,
})
.collect();
if native.as_os_str().is_empty() {
return Ok(None);
}
let mut n = wide(native.as_os_str());
let options = match mode {
OpenMode::File => FILE_NON_DIRECTORY_FILE,
OpenMode::Dir => FILE_DIRECTORY_FILE,
} | FILE_OPEN_REPARSE_POINT;
match nt_open(
Some(self.0.as_raw_handle() as HANDLE),
&mut n,
GENERIC_READ,
FILE_OPEN,
options,
OBJ_DONT_REPARSE,
) {
Ok(h) => {
refuse_if_reparse(&h)?;
Ok(Some(to_file(h)))
}
Err(e) => Err(e),
}
}
fn into_file(self) -> File {
File::from(self.0)
}
fn sync_name_durability(&self) -> Result<(), GuardIo> {
Ok(())
}
}
fn id_from(h: HANDLE) -> io::Result<NodeId> {
let mut info: FILE_ID_INFO = unsafe { std::mem::zeroed() };
let ok = unsafe {
GetFileInformationByHandleEx(
h,
FileIdInfo,
(&raw mut info).cast(),
size_of::<FILE_ID_INFO>() as u32,
)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(NodeId {
volume: info.VolumeSerialNumber,
file: u128::from_le_bytes(info.FileId.Identifier),
})
}
pub(super) fn identity_of(file: &File) -> io::Result<NodeId> {
id_from(file.as_raw_handle() as HANDLE)
}
pub(super) fn identity_at(path: &Path) -> io::Result<NodeId> {
use std::os::windows::fs::OpenOptionsExt;
let f = std::fs::OpenOptions::new()
.access_mode(FILE_READ_ATTRIBUTES)
.share_mode(SHARE_ALL)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)
.open(path)?;
identity_of(&f)
}
pub(super) fn normalized_name(file: &File) -> io::Result<Option<PathBuf>> {
let h = file.as_raw_handle() as HANDLE;
let needed = unsafe {
GetFinalPathNameByHandleW(
h,
ptr::null_mut(),
0,
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS,
)
};
if needed == 0 {
return Err(io::Error::last_os_error());
}
let mut buf = vec![0u16; needed as usize];
let written = unsafe {
GetFinalPathNameByHandleW(
h,
buf.as_mut_ptr(),
buf.len() as u32,
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS,
)
};
if written == 0 {
return Err(io::Error::last_os_error());
}
Ok(Some(PathBuf::from(OsString::from_wide(
&buf[..written as usize],
))))
}