#![cfg(target_os = "windows")]
#![allow(unsafe_code)]
use std::ffi::OsStr;
use std::io::{self, Read, Write};
use std::os::windows::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use windows::core::PCWSTR;
use windows::Win32::Foundation::ERROR_ALREADY_EXISTS;
use windows::Win32::Foundation::{CloseHandle, GENERIC_READ, GENERIC_WRITE, HANDLE, LUID};
use windows::Win32::Security::{
AdjustTokenPrivileges, LookupPrivilegeValueW, LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED,
TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES, TOKEN_QUERY,
};
use windows::Win32::Storage::FileSystem::{
BackupRead, BackupWrite, CreateDirectoryW, CreateFileW, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
FILE_FLAG_BACKUP_SEMANTICS, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
pub(crate) fn to_extended_wide(path: &Path) -> io::Result<Vec<u16>> {
const VERBATIM_PREFIX: &[u16] = &['\\' as u16, '\\' as u16, '?' as u16, '\\' as u16];
const DEVICE_PREFIX: &[u16] = &['\\' as u16, '\\' as u16, '.' as u16, '\\' as u16];
const UNC_PREFIX: &[u16] = &['\\' as u16, '\\' as u16];
let abs: PathBuf = match path.canonicalize() {
Ok(p) => p,
Err(_) => match std::path::absolute(path) {
Ok(p) => p,
Err(_) => {
if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()?.join(path)
}
}
},
};
let wide_in: Vec<u16> = abs.as_os_str().encode_wide().collect();
if wide_in.starts_with(VERBATIM_PREFIX) || wide_in.starts_with(DEVICE_PREFIX) {
let mut out = wide_in;
out.push(0);
return Ok(out);
}
let mut out: Vec<u16>;
if wide_in.starts_with(UNC_PREFIX) {
out = Vec::with_capacity(wide_in.len() + 6);
out.extend("\\\\?\\UNC".encode_utf16());
out.extend_from_slice(&wide_in[1..]);
} else {
out = Vec::with_capacity(wide_in.len() + 4);
out.extend("\\\\?\\".encode_utf16());
out.extend_from_slice(&wide_in);
}
out.push(0);
Ok(out)
}
pub(crate) fn create_long_path_file(path: &Path) -> io::Result<std::fs::File> {
use std::os::windows::io::FromRawHandle;
let wide = to_extended_wide(path)?;
let handle = unsafe {
CreateFileW(
PCWSTR::from_raw(wide.as_ptr()),
GENERIC_WRITE.0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
None,
)
}
.map_err(|e| io::Error::other(format!("CreateFileW(CREATE_ALWAYS): {e}")))?;
let file = unsafe { std::fs::File::from_raw_handle(handle.0.cast()) };
Ok(file)
}
pub(crate) fn create_long_path_dir(path: &Path) -> io::Result<()> {
let wide = to_extended_wide(path)?;
let res = unsafe { CreateDirectoryW(PCWSTR::from_raw(wide.as_ptr()), None) };
if let Err(e) = res {
#[allow(clippy::cast_sign_loss)]
let raw = e.code().0 as u32;
let already_exists_hresult = 0x8007_0000u32 | (ERROR_ALREADY_EXISTS.0 & 0xFFFF);
if raw == already_exists_hresult || raw == ERROR_ALREADY_EXISTS.0 {
return Ok(());
}
return Err(io::Error::other(format!("CreateDirectoryW: {e}")));
}
Ok(())
}
pub fn enable_backup_restore_privileges() -> io::Result<()> {
unsafe {
let mut token = HANDLE::default();
OpenProcessToken(
GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&mut token,
)
.map_err(|e| io::Error::other(format!("OpenProcessToken: {e}")))?;
let backup_res = enable_named_privilege(token, "SeBackupPrivilege");
let restore_res = enable_named_privilege(token, "SeRestorePrivilege");
if let Err(e) = CloseHandle(token) {
tracing::debug!("CloseHandle(token) failed: {e}");
}
backup_res?;
restore_res?;
}
Ok(())
}
unsafe fn enable_named_privilege(token: HANDLE, name: &str) -> io::Result<()> {
let name_w: Vec<u16> = OsStr::new(name)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let mut luid = LUID::default();
unsafe {
LookupPrivilegeValueW(PCWSTR::null(), PCWSTR::from_raw(name_w.as_ptr()), &mut luid)
.map_err(|e| io::Error::other(format!("LookupPrivilegeValueW({name}): {e}")))?;
let privs = TOKEN_PRIVILEGES {
PrivilegeCount: 1,
Privileges: [LUID_AND_ATTRIBUTES {
Luid: luid,
Attributes: SE_PRIVILEGE_ENABLED,
}],
};
AdjustTokenPrivileges(token, false, Some(&raw const privs), 0, None, None)
.map_err(|e| io::Error::other(format!("AdjustTokenPrivileges({name}): {e}")))?;
}
Ok(())
}
pub struct BackupStreamWriter {
handle: HANDLE,
context: *mut core::ffi::c_void,
}
unsafe impl Send for BackupStreamWriter {}
impl BackupStreamWriter {
pub fn create(path: &Path) -> io::Result<Self> {
const WRITE_DAC: u32 = 0x0004_0000;
const WRITE_OWNER: u32 = 0x0008_0000;
const ACCESS_SYSTEM_SECURITY: u32 = 0x0100_0000;
let wide = to_extended_wide(path)?;
let desired_access = GENERIC_WRITE.0 | WRITE_DAC | WRITE_OWNER | ACCESS_SYSTEM_SECURITY;
let handle = unsafe {
CreateFileW(
PCWSTR::from_raw(wide.as_ptr()),
desired_access,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL,
None,
)
}
.map_err(|e| io::Error::other(format!("CreateFileW: {e}")))?;
Ok(Self {
handle,
context: std::ptr::null_mut(),
})
}
pub fn create_new(path: &Path) -> io::Result<Self> {
drop(create_long_path_file(path)?);
Self::create(path)
}
}
impl Write for BackupStreamWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut bytes_written: u32 = 0;
unsafe {
BackupWrite(
self.handle,
buf,
&mut bytes_written,
false, true, &mut self.context,
)
}
.map_err(|e| io::Error::other(format!("BackupWrite: {e}")))?;
Ok(bytes_written as usize)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Drop for BackupStreamWriter {
fn drop(&mut self) {
let mut bytes_written: u32 = 0;
unsafe {
let _ = BackupWrite(
self.handle,
&[],
&mut bytes_written,
true,
false,
&mut self.context,
);
let _ = CloseHandle(self.handle);
}
}
}
pub struct BackupStreamReader {
handle: HANDLE,
context: *mut core::ffi::c_void,
}
unsafe impl Send for BackupStreamReader {}
impl BackupStreamReader {
pub fn open(path: &Path) -> io::Result<Self> {
let wide = to_extended_wide(path)?;
let handle = unsafe {
CreateFileW(
PCWSTR::from_raw(wide.as_ptr()),
GENERIC_READ.0,
FILE_SHARE_READ,
None,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
None,
)
}
.map_err(|e| io::Error::other(format!("CreateFileW: {e}")))?;
Ok(Self {
handle,
context: std::ptr::null_mut(),
})
}
}
impl Read for BackupStreamReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut bytes_read: u32 = 0;
unsafe {
BackupRead(
self.handle,
buf,
&mut bytes_read,
false,
false,
&mut self.context,
)
}
.map_err(|e| io::Error::other(format!("BackupRead: {e}")))?;
Ok(bytes_read as usize)
}
}
impl Drop for BackupStreamReader {
fn drop(&mut self) {
let mut bytes_read: u32 = 0;
let mut scratch: [u8; 1] = [0];
unsafe {
let _ = BackupRead(
self.handle,
&mut scratch[..0],
&mut bytes_read,
true,
false,
&mut self.context,
);
let _ = CloseHandle(self.handle);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn render(wide: &[u16]) -> String {
let nul_pos = wide
.iter()
.position(|&c| c == 0)
.expect("to_extended_wide must null-terminate");
String::from_utf16(&wide[..nul_pos]).expect("valid UTF-16")
}
#[test]
fn extended_wide_prefixes_local_absolute_path() {
let p = Path::new(r"C:\foo\bar\does-not-exist-zlayer-test");
let wide = to_extended_wide(p).expect("absolutize ok");
let s = render(&wide);
assert_eq!(s, r"\\?\C:\foo\bar\does-not-exist-zlayer-test");
assert_eq!(*wide.last().unwrap(), 0u16, "must be null-terminated");
}
#[test]
fn extended_wide_converts_unc_path_to_unc_verbatim() {
let p = Path::new(r"\\server\share\baz\zlayer-test");
let wide = to_extended_wide(p).expect("absolutize ok");
let s = render(&wide);
assert_eq!(s, r"\\?\UNC\server\share\baz\zlayer-test");
}
#[test]
fn extended_wide_passes_through_already_verbatim_prefix() {
let p = Path::new(r"\\?\C:\already\zlayer-test");
let wide = to_extended_wide(p).expect("absolutize ok");
let s = render(&wide);
assert_eq!(s, r"\\?\C:\already\zlayer-test");
}
#[test]
fn extended_wide_passes_through_device_prefix() {
let p = Path::new(r"\\.\PhysicalDrive0");
let wide = to_extended_wide(p).expect("absolutize ok");
let s = render(&wide);
assert_eq!(s, r"\\.\PhysicalDrive0");
}
}