use std::fmt;
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_authorization_sys::{SecurityAttributes, SecurityDescriptor};
use win_shared_os_owned_handle::SharedHandle;
use windows_namespace_request_sys::open::OpenFile;
use windows_namespace_request_sys::{FinalPathError, QueryFinalPath};
use windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION;
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT,
FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS,
FILE_FLAG_SEQUENTIAL_SCAN, FILE_READ_DATA, FILE_WRITE_DATA,
};
use wtf_string::{Wtf16Str, Wtf16String};
use durable_ioring::{
Access, Capabilities, DataAccess, Geometry, GeometryError, NoData, ReadWrite, WarrantyClass,
};
use super::disposition::Disposition;
use super::file_attributes::FileAttributes;
use super::file_id::FileIdentifier;
use super::file_rights::FileRights;
use super::share_mode::ShareMode;
mod by_id;
mod open_request;
mod reopen;
pub use by_id::{ByIdOptions, ByIdRequest, IdentityMismatch};
pub use reopen::{ReopenOptions, ReopenRequest};
#[cfg(test)]
mod creation_tests;
#[cfg(test)]
mod flag_tests;
#[cfg(test)]
mod request_tests;
#[cfg(test)]
mod share_tests;
#[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)]
#[must_use]
pub struct FileOptions<A: DataAccess = NoData> {
access: PhantomData<A>,
rights: FileRights,
share: ShareMode,
disposition: Disposition,
open_reparse_point: bool,
attributes: FileAttributes,
delete_on_close: bool,
open_no_recall: bool,
posix_semantics: bool,
overlapped: bool,
random_access: bool,
sequential_scan: bool,
}
impl Default for FileOptions<NoData> {
fn default() -> Self {
Self::new()
}
}
impl FileOptions<NoData> {
pub fn new() -> Self {
Self {
access: PhantomData,
rights: FileRights::NONE,
share: ShareMode::ALL,
disposition: Disposition::OpenExisting,
open_reparse_point: false,
attributes: FileAttributes::NONE,
delete_on_close: false,
open_no_recall: false,
posix_semantics: false,
overlapped: false,
random_access: false,
sequential_scan: false,
}
}
}
impl<A: DataAccess> FileOptions<A> {
pub fn read(self) -> FileOptions<A::WithRead> {
self.with_access()
}
pub fn write(self) -> FileOptions<A::WithWrite> {
self.with_access()
}
pub fn rights(mut self, rights: FileRights) -> Self {
self.rights = rights;
self
}
pub fn share(mut self, share: ShareMode) -> Self {
self.share = share;
self
}
fn with_access<A2: DataAccess>(self) -> FileOptions<A2> {
FileOptions {
access: PhantomData,
rights: self.rights,
share: self.share,
disposition: self.disposition,
open_reparse_point: self.open_reparse_point,
attributes: self.attributes,
delete_on_close: self.delete_on_close,
open_no_recall: self.open_no_recall,
posix_semantics: self.posix_semantics,
overlapped: self.overlapped,
random_access: self.random_access,
sequential_scan: self.sequential_scan,
}
}
pub fn disposition(mut self, disposition: Disposition) -> Self {
self.disposition = disposition;
self
}
pub fn open_reparse_point(mut self, open_reparse_point: bool) -> Self {
self.open_reparse_point = open_reparse_point;
self
}
pub fn attributes(mut self, attributes: FileAttributes) -> Self {
self.attributes = attributes;
self
}
pub fn delete_on_close(mut self, delete_on_close: bool) -> Self {
self.delete_on_close = delete_on_close;
self
}
pub fn open_no_recall(mut self, open_no_recall: bool) -> Self {
self.open_no_recall = open_no_recall;
self
}
pub fn posix_semantics(mut self, posix_semantics: bool) -> Self {
self.posix_semantics = posix_semantics;
self
}
pub fn overlapped(mut self, overlapped: bool) -> Self {
self.overlapped = overlapped;
self
}
pub fn random_access(mut self, random_access: bool) -> Self {
self.random_access = random_access;
self
}
pub fn sequential_scan(mut self, sequential_scan: bool) -> Self {
self.sequential_scan = sequential_scan;
self
}
fn parameters(self, flags: u32) -> open_request::Parameters {
let reparse = if self.open_reparse_point {
FILE_FLAG_OPEN_REPARSE_POINT
} else {
0
};
let mut named = 0;
if self.delete_on_close {
named |= FILE_FLAG_DELETE_ON_CLOSE;
}
if self.open_no_recall {
named |= FILE_FLAG_OPEN_NO_RECALL;
}
if self.posix_semantics {
named |= FILE_FLAG_POSIX_SEMANTICS;
}
if self.overlapped {
named |= FILE_FLAG_OVERLAPPED;
}
let mut hints = 0;
if self.random_access {
hints |= FILE_FLAG_RANDOM_ACCESS;
}
if self.sequential_scan {
hints |= FILE_FLAG_SEQUENTIAL_SCAN;
}
open_request::Parameters {
access: data_rights::<A>() | self.rights.bits(),
disposition: self.disposition.bits(),
flags: flags | reparse | named | hints | self.attributes.bits(),
share: self.share.bits(),
}
}
}
pub(super) const fn data_rights<A: DataAccess>() -> u32 {
(if A::READ { FILE_READ_DATA } else { 0 }) | (if A::WRITE { FILE_WRITE_DATA } else { 0 })
}
#[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(Debug)]
pub struct UnknownLocation {
pub cause: io::Error,
}
impl fmt::Display for UnknownLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"the handle's final path could not be read, so it is not known to be a file on a local volume, which an unbuffered file needs: {}",
self.cause
)
}
}
impl std::error::Error for UnknownLocation {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.cause)
}
}
#[derive(Debug)]
pub struct SharingViolation {
pub by: OpenedBy,
pub data: Option<Access>,
pub rights: FileRights,
pub share: ShareMode,
os_error: io::Error,
}
impl fmt::Display for SharingViolation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} is open in a way that conflicts with this open (data access {}, {:?}, sharing {:?}): \
the other open does not share what this one asked for, or this one does not share what \
the other holds",
self.by,
self.data
.map_or_else(|| "none".to_owned(), |data| data.to_string()),
self.rights,
self.share,
)
}
}
impl std::error::Error for SharingViolation {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.os_error)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OpenedBy {
Path(PathBuf),
Identifier(FileIdentifier),
Reopen,
}
impl fmt::Display for OpenedBy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Path(path) => write!(f, "{}", path.display()),
Self::Identifier(identifier) => write!(f, "the file identified by {identifier:?}"),
Self::Reopen => write!(f, "the file the reopened handle names"),
}
}
}
pub(super) fn data_access<A: DataAccess>() -> Option<Access> {
match (A::READ, A::WRITE) {
(false, false) => None,
(true, false) => Some(Access::ReadOnly),
(false, true) => Some(Access::WriteOnly),
(true, true) => Some(Access::ReadWrite),
}
}
pub(super) fn refine_sharing(
error: io::Error,
path: &Wtf16Str,
data: Option<Access>,
rights: FileRights,
share: ShareMode,
) -> io::Error {
refine_sharing_by(
error,
|| OpenedBy::Path(PathBuf::from(path.to_os_string())),
data,
rights,
share,
)
}
fn refine_sharing_by(
error: io::Error,
by: impl FnOnce() -> OpenedBy,
data: Option<Access>,
rights: FileRights,
share: ShareMode,
) -> io::Error {
if error.raw_os_error() != Some(ERROR_SHARING_VIOLATION.cast_signed()) {
return error;
}
io::Error::new(
io::ErrorKind::ResourceBusy,
SharingViolation {
by: by(),
data,
rights,
share,
os_error: error,
},
)
}
#[derive(Clone, Debug)]
pub struct OpenedFile<B: Buffering = Cached, A: DataAccess = ReadWrite> {
handle: SharedHandle,
geometry: Geometry,
mode: PhantomData<(B, A)>,
}
impl<B: Buffering, A: DataAccess> OpenedFile<B, A> {
pub fn open(path: Wtf16String, options: FileOptions<A>) -> io::Result<Self> {
Self::request(path, options)?.perform()
}
pub fn request(path: Wtf16String, options: FileOptions<A>) -> io::Result<OpenRequest<B, A>> {
let (rights, share) = (options.rights, options.share);
let request = open_request::request(path, options.parameters(B::FLAGS))?;
Ok(OpenRequest {
request,
rights,
share,
mode: PhantomData,
})
}
#[must_use]
pub fn handle(&self) -> &SharedHandle {
&self.handle
}
#[must_use]
pub fn geometry(&self) -> Geometry {
self.geometry
}
}
#[derive(Debug)]
#[must_use = "a request that is not performed opens nothing"]
pub struct OpenRequest<B: Buffering = Cached, A: DataAccess = ReadWrite> {
request: OpenFile,
rights: FileRights,
share: ShareMode,
mode: PhantomData<fn() -> (B, A)>,
}
impl<B: Buffering, A: DataAccess> OpenRequest<B, A> {
pub fn with_security_descriptor(self, descriptor: SecurityDescriptor) -> Self {
Self {
request: self
.request
.with_security(Some(SecurityAttributes::new(Some(descriptor), false))),
..self
}
}
pub fn perform(&self) -> io::Result<OpenedFile<B, A>> {
let handle = open_request::perform(&self.request).map_err(|error| {
refine_sharing(
error,
self.request.path(),
data_access::<A>(),
self.rights,
self.share,
)
})?;
OpenedFile::admit(SharedHandle::from(handle))
}
}
impl<B: Buffering, A: DataAccess> OpenedFile<B, A> {
fn admit(handle: SharedHandle) -> io::Result<Self> {
if B::LOCAL_DEVICE_ONLY {
let location = final_location(&handle).map_err(|cause| {
io::Error::new(io::ErrorKind::Unsupported, UnknownLocation { cause })
})?;
if names_a_remote_device(&location) {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
RemoteDevice { location },
));
}
}
Ok(OpenedFile {
handle,
geometry: B::geometry(),
mode: PhantomData,
})
}
}
impl<A: DataAccess> OpenedFile<Unbuffered, A> {
pub fn with_sector_size(mut self, sector_size: u32) -> Result<Self, GeometryError> {
self.geometry = Geometry::uniform(sector_size)?;
Ok(self)
}
}
impl<B: Buffering, A: DataAccess> AsHandle for OpenedFile<B, A> {
fn as_handle(&self) -> BorrowedHandle<'_> {
self.handle.as_handle()
}
}
impl<B: Buffering, A: DataAccess> AsRawHandle for OpenedFile<B, A> {
fn as_raw_handle(&self) -> RawHandle {
self.handle.as_raw_handle()
}
}
fn final_location(handle: &SharedHandle) -> io::Result<PathBuf> {
let path = QueryFinalPath::new(handle.clone())
.perform()
.map_err(|error| match error {
FinalPathError::Win32(code) => {
io::Error::from_raw_os_error(open_request::raw_code(code.code()))
}
other => io::Error::other(other),
})?;
Ok(PathBuf::from(path.to_os_string()))
}
fn names_a_remote_device(location: &Path) -> bool {
matches!(
location.components().next(),
Some(Component::Prefix(prefix))
if matches!(prefix.kind(), Prefix::UNC(..) | Prefix::VerbatimUNC(..))
)
}