use std::fmt;
use std::fs;
use std::io;
use std::marker::PhantomData;
use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle};
use std::path::{Component, Path, PathBuf, Prefix};
use win_shared_os_owned_handle::SharedHandle;
use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE};
use windows_sys::Win32::Storage::FileSystem::{
CREATE_NEW, FILE_FLAG_OPEN_REPARSE_POINT, OPEN_ALWAYS, OPEN_EXISTING, TRUNCATE_EXISTING,
};
use durable_ioring::{Capabilities, Geometry, GeometryError, WarrantyClass};
mod open_request;
#[cfg(test)]
mod tests;
const FILE_FLAG_NO_BUFFERING: u32 = 0x2000_0000;
mod sealed {
pub trait Sealed {}
}
pub trait Buffering: sealed::Sealed + Copy + fmt::Debug + Send + Sync + 'static {
const FLAGS: u32;
const WARRANTY: WarrantyClass;
const CAPABILITIES: Capabilities;
const LOCAL_DEVICE_ONLY: bool;
fn geometry() -> Geometry;
fn sector_geometry(sector_size: u32) -> Result<Geometry, GeometryError>;
}
#[derive(Clone, Copy, Debug)]
pub struct Cached;
#[derive(Clone, Copy, Debug)]
pub struct Unbuffered;
impl sealed::Sealed for Cached {}
impl sealed::Sealed for Unbuffered {}
impl Buffering for Cached {
const FLAGS: u32 = 0;
const WARRANTY: WarrantyClass = WarrantyClass::BestEffort;
const CAPABILITIES: Capabilities = Capabilities::NONE;
const LOCAL_DEVICE_ONLY: bool = false;
fn geometry() -> Geometry {
Geometry::UNALIGNED
}
fn sector_geometry(sector_size: u32) -> Result<Geometry, GeometryError> {
Geometry::new(sector_size, sector_size, 1)
}
}
impl Unbuffered {
pub const DEFAULT_SECTOR_SIZE: u32 = 4096;
}
impl Buffering for Unbuffered {
const FLAGS: u32 = FILE_FLAG_NO_BUFFERING;
const WARRANTY: WarrantyClass = WarrantyClass::SharedDevice;
const CAPABILITIES: Capabilities = Capabilities::NONE.with_write_through();
const LOCAL_DEVICE_ONLY: bool = true;
fn geometry() -> Geometry {
Geometry::uniform(Self::DEFAULT_SECTOR_SIZE).expect("4096 is a power of two")
}
fn sector_geometry(sector_size: u32) -> Result<Geometry, GeometryError> {
Geometry::uniform(sector_size)
}
}
#[derive(Clone, Copy, Debug, Default)]
#[must_use]
pub struct FileOptions {
read: bool,
write: bool,
create: bool,
create_new: bool,
truncate: bool,
}
impl FileOptions {
pub fn new() -> Self {
Self::default()
}
pub fn read(mut self, read: bool) -> Self {
self.read = read;
self
}
pub fn write(mut self, write: bool) -> Self {
self.write = write;
self
}
pub fn create(mut self, create: bool) -> Self {
self.create = create;
self
}
pub fn create_new(mut self, create_new: bool) -> Self {
self.create_new = create_new;
self
}
pub fn truncate(mut self, truncate: bool) -> Self {
self.truncate = truncate;
self
}
fn parameters(self, flags: u32) -> io::Result<open_request::Parameters> {
let access = match (self.read, self.write) {
(true, false) => GENERIC_READ,
(false, true) => GENERIC_WRITE,
(true, true) => GENERIC_READ | GENERIC_WRITE,
(false, false) => {
return Err(invalid_input(if self.creates_or_truncates() {
"creating or truncating a file requires write access"
} else {
"must specify at least one of read or write access"
}));
}
};
if !self.write && self.creates_or_truncates() {
return Err(invalid_input(
"creating or truncating a file requires write access",
));
}
let (disposition, truncate_after) = match (self.create, self.truncate, self.create_new) {
(false, false, false) => (OPEN_EXISTING, false),
(true, false, false) => (OPEN_ALWAYS, false),
(false, true, false) => (TRUNCATE_EXISTING, false),
(true, true, false) => (OPEN_ALWAYS, true),
(_, _, true) => (CREATE_NEW, false),
};
let reparse = if self.create_new {
FILE_FLAG_OPEN_REPARSE_POINT
} else {
0
};
Ok(open_request::Parameters {
access,
disposition,
flags: flags | reparse,
truncate_after,
})
}
fn creates_or_truncates(self) -> bool {
self.create || self.create_new || self.truncate
}
}
fn invalid_input(message: &'static str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, message)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RemoteDevice {
pub location: PathBuf,
}
impl fmt::Display for RemoteDevice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} is on a remote device, which cannot be opened unbuffered with the warranty that implies",
self.location.display()
)
}
}
impl std::error::Error for RemoteDevice {}
#[derive(Clone, Debug)]
pub struct OpenedFile<B: Buffering = Cached> {
handle: SharedHandle,
geometry: Geometry,
buffering: PhantomData<B>,
}
impl<B: Buffering> OpenedFile<B> {
pub fn open(path: impl AsRef<Path>, options: FileOptions) -> io::Result<Self> {
let path = path.as_ref();
if B::LOCAL_DEVICE_ONLY {
let location = final_location(path)?;
if names_a_remote_device(&location) {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
RemoteDevice { location },
));
}
}
let handle = open_request::open(path, options.parameters(B::FLAGS)?)?;
Ok(Self {
handle: SharedHandle::from(handle),
geometry: B::geometry(),
buffering: PhantomData,
})
}
#[must_use]
pub fn handle(&self) -> &SharedHandle {
&self.handle
}
#[must_use]
pub fn geometry(&self) -> Geometry {
self.geometry
}
}
impl OpenedFile<Unbuffered> {
pub fn with_sector_size(mut self, sector_size: u32) -> Result<Self, GeometryError> {
self.geometry = Geometry::uniform(sector_size)?;
Ok(self)
}
}
impl<B: Buffering> AsHandle for OpenedFile<B> {
fn as_handle(&self) -> BorrowedHandle<'_> {
self.handle.as_handle()
}
}
impl<B: Buffering> AsRawHandle for OpenedFile<B> {
fn as_raw_handle(&self) -> RawHandle {
self.handle.as_raw_handle()
}
}
fn final_location(path: &Path) -> io::Result<PathBuf> {
match fs::canonicalize(path) {
Err(error) if error.kind() == io::ErrorKind::NotFound => {
let parent = path
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
.unwrap_or(Path::new("."));
fs::canonicalize(parent)
}
other => other,
}
}
fn names_a_remote_device(location: &Path) -> bool {
matches!(
location.components().next(),
Some(Component::Prefix(prefix))
if matches!(prefix.kind(), Prefix::UNC(..) | Prefix::VerbatimUNC(..))
)
}