use std::fs::File;
use std::io::{self, Read as _};
use std::path::{Path, PathBuf};
#[path = "fs/private_directory.rs"]
mod private_directory;
pub use private_directory::{create_dir_all_private, ensure_dir_private};
pub fn open_shared_append(path: &Path) -> io::Result<File> {
use std::os::windows::fs::OpenOptionsExt as _;
use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE};
std::fs::OpenOptions::new()
.create(true)
.append(true)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.open(path)
}
pub fn user_runtime_dir(product: &str) -> PathBuf {
dirs::data_local_dir()
.unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"))
.join(product)
}
pub fn user_state_dir(product: &str) -> PathBuf {
user_runtime_dir(product)
}
pub fn user_run_data_root(product: &str) -> PathBuf {
user_runtime_dir(product)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FileIdentity {
pub device: u64,
pub file: u64,
}
pub fn file_identity(file: &File) -> io::Result<Option<FileIdentity>> {
use std::mem::MaybeUninit;
use std::os::windows::io::AsRawHandle;
use winapi::um::fileapi::{GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION};
use winapi::um::winnt::HANDLE;
let mut info = MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::uninit();
let result =
unsafe { GetFileInformationByHandle(file.as_raw_handle() as HANDLE, info.as_mut_ptr()) };
if result == 0 {
return Err(io::Error::last_os_error());
}
let info = unsafe { info.assume_init() };
Ok(Some(FileIdentity {
device: info.dwVolumeSerialNumber as u64,
file: ((info.nFileIndexHigh as u64) << 32) | info.nFileIndexLow as u64,
}))
}
pub fn path_identity(path: &Path) -> io::Result<Option<FileIdentity>> {
use std::os::windows::fs::OpenOptionsExt as _;
use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE};
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.open(path)?;
file_identity(&file)
}
pub fn open_lock_file(path: &Path) -> io::Result<File> {
use std::os::windows::fs::OpenOptionsExt as _;
use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE};
std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.open(path)
}
const LOCK_BYTE_OFFSET: u64 = 1 << 62;
fn lock_byte_overlapped() -> winapi::um::minwinbase::OVERLAPPED {
use std::mem;
use winapi::um::minwinbase::OVERLAPPED;
let mut overlapped: OVERLAPPED = unsafe { mem::zeroed() };
let offsets = unsafe { overlapped.u.s_mut() };
offsets.Offset = LOCK_BYTE_OFFSET as u32;
offsets.OffsetHigh = (LOCK_BYTE_OFFSET >> 32) as u32;
overlapped
}
fn lock_file(file: &File, flags: winapi::shared::minwindef::DWORD) -> io::Result<()> {
use std::os::windows::io::AsRawHandle as _;
use winapi::um::fileapi::LockFileEx;
use winapi::um::winnt::HANDLE;
let mut overlapped = lock_byte_overlapped();
let result = unsafe {
LockFileEx(
file.as_raw_handle() as HANDLE,
flags,
0,
1,
0,
&mut overlapped,
)
};
if result == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn try_lock_exclusive(file: &File) -> io::Result<()> {
use winapi::um::minwinbase::{LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY};
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY)
}
pub fn lock_exclusive(file: &File) -> io::Result<()> {
use winapi::um::minwinbase::LOCKFILE_EXCLUSIVE_LOCK;
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK)
}
pub fn lock_shared(file: &File) -> io::Result<()> {
lock_file(file, 0)
}
pub fn try_lock_shared(file: &File) -> io::Result<()> {
use winapi::um::minwinbase::LOCKFILE_FAIL_IMMEDIATELY;
lock_file(file, LOCKFILE_FAIL_IMMEDIATELY)
}
pub fn unlock(file: &File) -> io::Result<()> {
use std::os::windows::io::AsRawHandle as _;
use winapi::um::fileapi::UnlockFileEx;
use winapi::um::winnt::HANDLE;
let mut overlapped = lock_byte_overlapped();
let result = unsafe { UnlockFileEx(file.as_raw_handle() as HANDLE, 0, 1, 0, &mut overlapped) };
if result == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn is_lock_conflict(error: &io::Error) -> bool {
use winapi::shared::winerror::ERROR_LOCK_VIOLATION;
error.raw_os_error() == Some(ERROR_LOCK_VIOLATION as i32)
}
pub fn set_file_mtime(
path: &Path,
seconds_since_unix_epoch: i64,
nanoseconds: u32,
) -> io::Result<()> {
use std::os::windows::fs::OpenOptionsExt as _;
use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
use winapi::um::winnt::{
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES,
};
let time = unix_time_to_system_time(seconds_since_unix_epoch, nanoseconds)?;
let file = std::fs::OpenOptions::new()
.access_mode(FILE_WRITE_ATTRIBUTES)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
.open(path)?;
file.set_modified(time)
}
fn unix_time_to_system_time(
seconds_since_unix_epoch: i64,
nanoseconds: u32,
) -> io::Result<std::time::SystemTime> {
use std::time::{Duration, SystemTime};
let out_of_range = || {
io::Error::new(
io::ErrorKind::InvalidInput,
"modification time is out of range for this host's clock",
)
};
if seconds_since_unix_epoch >= 0 {
SystemTime::UNIX_EPOCH
.checked_add(Duration::new(seconds_since_unix_epoch as u64, nanoseconds))
.ok_or_else(out_of_range)
} else {
let magnitude = seconds_since_unix_epoch
.checked_neg()
.ok_or_else(out_of_range)? as u64;
SystemTime::UNIX_EPOCH
.checked_sub(Duration::new(magnitude, 0))
.and_then(|time| time.checked_add(Duration::new(0, nanoseconds)))
.ok_or_else(out_of_range)
}
}
pub fn encode_path_bytes(path: &Path) -> Vec<u8> {
use std::os::windows::ffi::OsStrExt as _;
path.as_os_str()
.encode_wide()
.flat_map(u16::to_le_bytes)
.collect()
}
pub fn decode_path_bytes(bytes: &[u8]) -> io::Result<PathBuf> {
use std::os::windows::ffi::OsStringExt as _;
if !bytes.len().is_multiple_of(2) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Windows path bytes must be little-endian UTF-16",
));
}
let wide = bytes
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect::<Vec<_>>();
Ok(PathBuf::from(std::ffi::OsString::from_wide(&wide)))
}
pub fn user_data_dir(product: &str) -> PathBuf {
dirs::data_dir()
.unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"))
.join(product)
}
pub fn user_config_dir(product: &str) -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"))
.join(product)
}
pub fn replace_file(tmp: &Path, target: &Path) -> io::Result<()> {
use std::os::windows::ffi::OsStrExt as _;
use windows_sys::Win32::Storage::FileSystem::{
MoveFileExW, MOVEFILE_REPLACE_EXISTING, MOVEFILE_WRITE_THROUGH,
};
fn wide(path: &Path) -> Vec<u16> {
path.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
let tmp_w = wide(tmp);
let target_w = wide(target);
let ok = unsafe {
MoveFileExW(
tmp_w.as_ptr(),
target_w.as_ptr(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
)
};
if ok == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn sync_directory(directory: &Path) -> io::Result<()> {
std::fs::metadata(directory)?;
Ok(())
}
pub fn create_private_file(path: &Path) -> io::Result<File> {
use std::os::windows::fs::OpenOptionsExt as _;
use winapi::um::winnt::{DELETE, GENERIC_WRITE, WRITE_DAC, WRITE_OWNER};
let file = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.access_mode(GENERIC_WRITE | WRITE_OWNER | WRITE_DAC | DELETE)
.open(path)?;
if let Err(error) = super::ipc_private_dir::apply_current_user_owner(&file) {
let _ = delete_file_on_close(&file);
return Err(error);
}
if let Err(error) = super::ipc_private_dir::apply_current_user_private_file_dacl(&file) {
let _ = delete_file_on_close(&file);
return Err(error);
}
Ok(file)
}
fn delete_file_on_close(file: &File) -> io::Result<()> {
use std::os::windows::io::AsRawHandle as _;
use windows_sys::Win32::Storage::FileSystem::{
FileDispositionInfo, SetFileInformationByHandle, FILE_DISPOSITION_INFO,
};
let disposition = FILE_DISPOSITION_INFO { DeleteFile: true };
if unsafe {
SetFileInformationByHandle(
file.as_raw_handle() as _,
FileDispositionInfo,
(&disposition as *const FILE_DISPOSITION_INFO).cast(),
std::mem::size_of::<FILE_DISPOSITION_INFO>() as u32,
)
} == 0
{
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn read_private_regular_file_bounded(path: &Path, max_bytes: usize) -> io::Result<Vec<u8>> {
use std::os::windows::fs::OpenOptionsExt as _;
use winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT;
use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE};
let parent = path.parent().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "private file path has no parent")
})?;
if !super::ipc_private_dir::owner_private_directory(parent)? {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"private file parent does not have the protected owner-private DACL",
));
}
let file = std::fs::OpenOptions::new()
.read(true)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT)
.open(path)?;
let metadata = file.metadata()?;
use std::mem::MaybeUninit;
use std::os::windows::io::AsRawHandle as _;
use winapi::um::fileapi::{GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION};
use winapi::um::winnt::{FILE_ATTRIBUTE_REPARSE_POINT, HANDLE};
let mut information = MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::uninit();
if unsafe {
GetFileInformationByHandle(file.as_raw_handle() as HANDLE, information.as_mut_ptr())
} == 0
{
return Err(io::Error::last_os_error());
}
let information = unsafe { information.assume_init() };
if information.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 || !metadata.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"private input is not a regular non-reparse file",
));
}
if !super::ipc_private_dir::opened_file_is_current_user_private(&file)? {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"private input is not current-user owner/SYSTEM DACL private",
));
}
let bound_plus_one = max_bytes.checked_add(1).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "private file limit overflows")
})?;
if metadata.len() > max_bytes as u64 {
return Err(io::Error::new(io::ErrorKind::InvalidData, "private input exceeds limit"));
}
let mut bytes = Vec::with_capacity(bound_plus_one);
(&mut &file)
.take(bound_plus_one as u64)
.read_to_end(&mut bytes)?;
if bytes.len() > max_bytes {
return Err(io::Error::new(io::ErrorKind::InvalidData, "private input exceeds limit"));
}
if path_identity(path)? != file_identity(&file)? {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"private input path changed while it was read",
));
}
Ok(bytes)
}
pub fn read_context_regular_file_bounded(
path: &Path,
max_bytes: usize,
) -> io::Result<crate::platform::fs::ContextFileObservation> {
use std::os::windows::fs::OpenOptionsExt as _;
use winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT;
use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE};
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;
let bound_plus_one = max_bytes.checked_add(1).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "context file limit overflows")
})?;
if !std::fs::symlink_metadata(path)?.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"context input is not a regular file",
));
}
let file = std::fs::OpenOptions::new()
.read(true)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT)
.open(path)?;
let before = file.metadata()?;
use std::os::windows::fs::MetadataExt as _;
if before.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 || !before.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"context input is not a regular file",
));
}
let identity = file_identity(&file)?.ok_or_else(|| {
io::Error::new(io::ErrorKind::Unsupported, "context file identity is unavailable")
})?;
let before_modified = before.modified()?;
if before.len() > max_bytes as u64 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"context input exceeds limit",
));
}
let mut bytes = Vec::with_capacity(bound_plus_one);
(&mut &file)
.take(bound_plus_one as u64)
.read_to_end(&mut bytes)?;
if bytes.len() > max_bytes {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"context input exceeds limit",
));
}
let after = file.metadata()?;
if after.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"context input final component is a reparse point",
));
}
let after_identity = file_identity(&file)?.ok_or_else(|| {
io::Error::new(io::ErrorKind::Unsupported, "context file identity is unavailable")
})?;
if after_identity != identity || after.len() != before.len() || after.modified()? != before_modified || bytes.len() as u64 != after.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"context input changed while it was read",
));
}
let path_file = std::fs::OpenOptions::new()
.read(true)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT)
.open(path)?;
let path_metadata = path_file.metadata()?;
if path_metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0
|| !path_metadata.is_file()
|| file_identity(&path_file)? != Some(identity)
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"context input path changed while it was read",
));
}
Ok(crate::platform::fs::ContextFileObservation {
bytes,
metadata: crate::platform::fs::context_regular_file_metadata(&after, identity)?,
})
}