use bitflags::bitflags;
use io_lifetimes::{AsFilelike, FromFilelike};
#[cfg(not(any(windows, target_os = "redox")))]
use rustix::fs::{fcntl_getfl, fcntl_setfl, OFlags};
#[cfg(not(windows))]
use std::marker::PhantomData;
use std::{fs, io};
#[cfg(windows)]
use {
cap_fs_ext::{OpenOptions, Reopen},
cap_std::fs::OpenOptionsExt,
io_lifetimes::AsHandle,
windows_sys::Win32::Storage::FileSystem::FILE_FLAG_WRITE_THROUGH,
winx::file::{AccessMode, FileModeInformation},
};
#[cfg(windows)]
pub struct SetFdFlags<T> {
reopened: T,
}
#[cfg(not(windows))]
pub struct SetFdFlags<T> {
flags: OFlags,
_phantom: PhantomData<T>,
}
pub trait GetSetFdFlags {
fn get_fd_flags(&self) -> io::Result<FdFlags>
where
Self: AsFilelike;
fn new_set_fd_flags(&self, flags: FdFlags) -> io::Result<SetFdFlags<Self>>
where
Self: AsFilelike + FromFilelike + Sized;
fn set_fd_flags(&mut self, set_fd_flags: SetFdFlags<Self>) -> io::Result<()>
where
Self: AsFilelike + Sized;
}
bitflags! {
pub struct FdFlags: u32 {
const APPEND = 0x01;
const DSYNC = 0x02;
const NONBLOCK = 0x04;
const RSYNC = 0x08;
const SYNC = 0x10;
}
}
#[cfg(not(windows))]
impl<T> GetSetFdFlags for T {
fn get_fd_flags(&self) -> io::Result<FdFlags>
where
Self: AsFilelike,
{
let mut fd_flags = FdFlags::empty();
let flags = fcntl_getfl(self)?;
fd_flags.set(FdFlags::APPEND, flags.contains(OFlags::APPEND));
#[cfg(not(target_os = "freebsd"))]
fd_flags.set(FdFlags::DSYNC, flags.contains(OFlags::DSYNC));
fd_flags.set(FdFlags::NONBLOCK, flags.contains(OFlags::NONBLOCK));
#[cfg(any(
target_os = "ios",
target_os = "macos",
target_os = "freebsd",
target_os = "fuchsia"
))]
{
fd_flags.set(FdFlags::SYNC, flags.contains(OFlags::SYNC));
}
#[cfg(not(any(
target_os = "ios",
target_os = "macos",
target_os = "freebsd",
target_os = "fuchsia"
)))]
{
fd_flags.set(FdFlags::RSYNC, flags.contains(OFlags::RSYNC));
fd_flags.set(FdFlags::SYNC, flags.contains(OFlags::SYNC));
}
Ok(fd_flags)
}
fn new_set_fd_flags(&self, fd_flags: FdFlags) -> io::Result<SetFdFlags<Self>>
where
Self: AsFilelike,
{
let mut flags = OFlags::empty();
flags.set(OFlags::APPEND, fd_flags.contains(FdFlags::APPEND));
flags.set(OFlags::NONBLOCK, fd_flags.contains(FdFlags::NONBLOCK));
if fd_flags.intersects(FdFlags::DSYNC | FdFlags::SYNC | FdFlags::RSYNC) {
return Err(io::Error::new(
io::ErrorKind::Other,
"setting fd_flags SYNC, DSYNC, and RSYNC is not supported",
));
}
Ok(SetFdFlags {
flags,
_phantom: PhantomData,
})
}
fn set_fd_flags(&mut self, set_fd_flags: SetFdFlags<Self>) -> io::Result<()>
where
Self: AsFilelike + Sized,
{
Ok(fcntl_setfl(
&*self.as_filelike_view::<fs::File>(),
set_fd_flags.flags,
)?)
}
}
#[cfg(windows)]
impl<T> GetSetFdFlags for T {
fn get_fd_flags(&self) -> io::Result<FdFlags>
where
Self: AsFilelike,
{
let mut fd_flags = FdFlags::empty();
let handle = self.as_filelike();
let access_mode = winx::file::query_access_information(handle)?;
let mode = winx::file::query_mode_information(handle)?;
if access_mode.contains(AccessMode::FILE_APPEND_DATA)
&& !access_mode.contains(AccessMode::FILE_WRITE_DATA)
{
fd_flags |= FdFlags::APPEND;
}
if mode.contains(FileModeInformation::FILE_WRITE_THROUGH) {
fd_flags |= FdFlags::DSYNC;
}
Ok(fd_flags)
}
fn new_set_fd_flags(&self, fd_flags: FdFlags) -> io::Result<SetFdFlags<Self>>
where
Self: AsFilelike + FromFilelike,
{
let mut flags = 0;
if fd_flags.contains(FdFlags::SYNC) || fd_flags.contains(FdFlags::DSYNC) {
flags |= FILE_FLAG_WRITE_THROUGH;
}
let file = self.as_filelike_view::<fs::File>();
let access_mode = winx::file::query_access_information(file.as_handle())?;
let new_access_mode = file_access_mode_from_fd_flags(
fd_flags,
access_mode.contains(AccessMode::FILE_READ_DATA),
access_mode.contains(AccessMode::FILE_WRITE_DATA)
| access_mode.contains(AccessMode::FILE_APPEND_DATA),
);
let mut options = OpenOptions::new();
options.access_mode(new_access_mode.bits());
options.custom_flags(flags);
let reopened = Self::from_into_filelike(file.reopen(&options)?);
Ok(SetFdFlags { reopened })
}
fn set_fd_flags(&mut self, set_fd_flags: SetFdFlags<Self>) -> io::Result<()>
where
Self: AsFilelike,
{
*self = set_fd_flags.reopened;
Ok(())
}
}
#[cfg(windows)]
fn file_access_mode_from_fd_flags(fd_flags: FdFlags, read: bool, write: bool) -> AccessMode {
let mut access_mode = AccessMode::READ_CONTROL;
access_mode.insert(AccessMode::FILE_WRITE_ATTRIBUTES);
if read {
access_mode.insert(AccessMode::FILE_GENERIC_READ);
}
if write {
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
}
if fd_flags.contains(FdFlags::APPEND) {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
access_mode.remove(AccessMode::FILE_WRITE_DATA);
}
access_mode
}