use bitflags::bitflags;
#[cfg(not(any(windows, target_os = "redox")))]
use posish::fs::{getfl, setfl, OFlags};
use std::io;
use unsafe_io::AsUnsafeFile;
#[cfg(windows)]
use {
cap_fs_ext::{OpenOptions, Reopen},
std::os::windows::fs::OpenOptionsExt,
std::os::windows::io::AsRawHandle,
unsafe_io::FromUnsafeFile,
winapi::um::winbase::FILE_FLAG_WRITE_THROUGH,
winx::file::{AccessMode, FileModeInformation},
};
pub trait GetSetFdFlags {
fn get_fd_flags(&self) -> io::Result<FdFlags>;
fn set_fd_flags(&mut self, flags: FdFlags) -> io::Result<()>;
}
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: AsUnsafeFile> GetSetFdFlags for T {
fn get_fd_flags(&self) -> io::Result<FdFlags> {
let mut fd_flags = FdFlags::empty();
let flags = getfl(self)?;
fd_flags.set(FdFlags::APPEND, flags.contains(OFlags::APPEND));
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 set_fd_flags(&mut self, fd_flags: FdFlags) -> io::Result<()> {
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",
));
}
setfl(self, flags)
}
}
#[cfg(windows)]
impl<T: AsUnsafeFile + FromUnsafeFile> GetSetFdFlags for T {
fn get_fd_flags(&self) -> io::Result<FdFlags> {
let mut fd_flags = FdFlags::empty();
let handle = self.as_unsafe_file().as_raw_handle();
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::SYNC;
}
Ok(fd_flags)
}
fn set_fd_flags(&mut self, fd_flags: FdFlags) -> io::Result<()> {
let mut flags = 0;
if fd_flags.contains(FdFlags::SYNC)
|| fd_flags.contains(FdFlags::DSYNC)
|| fd_flags.contains(FdFlags::RSYNC)
{
flags |= FILE_FLAG_WRITE_THROUGH;
}
if fd_flags.contains(FdFlags::NONBLOCK) {
return Err(io::Error::new(
io::ErrorKind::Other,
"Non-blocking I/O is not yet supported on Windows",
));
}
let handle = self.as_unsafe_file().as_raw_handle();
let access_mode = winx::file::query_access_information(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.as_file_view().reopen(&options)?;
*self = T::from_filelike(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
}