use std::io;
use std::mem::size_of;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE};
use windows::Win32::Storage::FileSystem::{
CreateFileW, DELETE, FILE_DISPOSITION_INFO, FILE_FLAGS_AND_ATTRIBUTES, FILE_GENERIC_READ,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_STANDARD_INFO, FileDispositionInfo,
FileStandardInfo, GetFileInformationByHandleEx, MOVEFILE_REPLACE_EXISTING,
MOVEFILE_WRITE_THROUGH, MoveFileExW, OPEN_EXISTING, ReadFile, SetFileInformationByHandle,
};
use windows::core::PCWSTR;
fn last_error() -> io::Error {
let code = unsafe { GetLastError() };
io::Error::from_raw_os_error(code.0.cast_signed())
}
pub(crate) fn move_file_replace(tmp: &Path, dest: &Path) -> io::Result<()> {
let src: Vec<u16> = tmp.as_os_str().encode_wide().chain([0]).collect();
let dst: Vec<u16> = dest.as_os_str().encode_wide().chain([0]).collect();
unsafe {
MoveFileExW(
PCWSTR(src.as_ptr()),
PCWSTR(dst.as_ptr()),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
)
}
.map_err(io::Error::other)
}
pub(crate) struct RecordFile {
handle: HANDLE,
}
impl RecordFile {
pub(crate) fn open(path: &Path) -> io::Result<Self> {
let wide: Vec<u16> = path.as_os_str().encode_wide().chain([0]).collect();
let handle = unsafe {
CreateFileW(
PCWSTR(wide.as_ptr()),
FILE_GENERIC_READ.0 | DELETE.0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
None,
OPEN_EXISTING,
FILE_FLAGS_AND_ATTRIBUTES(0),
None,
)
}
.map_err(|_error| last_error())?;
Ok(Self { handle })
}
pub(crate) fn read(&self) -> io::Result<Vec<u8>> {
let len = usize::try_from(self.standard_info()?.EndOfFile)
.map_err(|_error| io::Error::from(io::ErrorKind::InvalidData))?;
let mut buf = vec![0_u8; len];
let mut filled = 0_usize;
while filled < buf.len() {
let dest = buf
.get_mut(filled..)
.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
let mut transferred = 0_u32;
unsafe { ReadFile(self.handle, Some(dest), Some(&raw mut transferred), None) }
.map_err(|_error| last_error())?;
if transferred == 0 {
buf.truncate(filled);
break;
}
filled = filled
.checked_add(transferred as usize)
.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
}
Ok(buf)
}
pub(crate) fn delete(&self) -> io::Result<()> {
let disposition = FILE_DISPOSITION_INFO { DeleteFile: true };
let size = u32::try_from(size_of::<FILE_DISPOSITION_INFO>())
.expect("FILE_DISPOSITION_INFO fits in u32");
let set = unsafe {
SetFileInformationByHandle(
self.handle,
FileDispositionInfo,
(&raw const disposition).cast(),
size,
)
};
if set.is_ok() {
return Ok(());
}
let error = last_error();
if self.is_going_away().unwrap_or(false) {
return Ok(());
}
Err(error)
}
fn is_going_away(&self) -> io::Result<bool> {
let info = self.standard_info()?;
Ok(info.DeletePending || info.NumberOfLinks == 0)
}
fn standard_info(&self) -> io::Result<FILE_STANDARD_INFO> {
let mut info = FILE_STANDARD_INFO::default();
let size =
u32::try_from(size_of::<FILE_STANDARD_INFO>()).expect("FILE_STANDARD_INFO fits in u32");
unsafe {
GetFileInformationByHandleEx(
self.handle,
FileStandardInfo,
(&raw mut info).cast(),
size,
)
}
.map_err(|_error| last_error())?;
Ok(info)
}
}
impl Drop for RecordFile {
fn drop(&mut self) {
_ = unsafe { CloseHandle(self.handle) };
}
}