#![forbid(unsafe_code)]
use crate::syscalls;
use bitflags::bitflags;
use rustix::fs as rustix_fs;
bitflags! {
#[derive(Default, PartialEq, Eq, Debug, Clone, Copy)]
pub struct OpenFlags: u64 {
const O_RDWR = libc::O_RDWR as _;
const O_RDONLY = libc::O_RDONLY as _;
const O_WRONLY = libc::O_WRONLY as _;
const O_PATH = libc::O_PATH as _;
const O_CLOEXEC = libc::O_CLOEXEC as _;
const O_NOFOLLOW = libc::O_NOFOLLOW as _;
const O_DIRECTORY = libc::O_DIRECTORY as _;
const O_NOCTTY = libc::O_NOCTTY as _;
const O_TMPFILE = libc::O_TMPFILE as _;
const O_CREAT = libc::O_CREAT as _;
const O_EXCL = libc::O_EXCL as _;
const O_TRUNC = libc::O_TRUNC as _;
const O_APPEND = libc::O_APPEND as _;
const O_SYNC = libc::O_SYNC as _;
const O_ASYNC = libc::O_ASYNC as _;
const O_DSYNC = libc::O_DSYNC as _;
#[cfg(not(target_env = "musl"))] const O_FSYNC = libc::O_FSYNC as _;
const O_RSYNC = libc::O_RSYNC as _;
const O_DIRECT = libc::O_DIRECT as _;
const O_NDELAY = libc::O_NDELAY as _;
const O_NOATIME = libc::O_NOATIME as _;
const O_NONBLOCK = libc::O_NONBLOCK as _;
const _ = !0;
}
}
#[doc(hidden)]
impl TryFrom<OpenFlags> for rustix_fs::OFlags {
type Error = ();
fn try_from(flags: OpenFlags) -> Result<Self, Self::Error> {
flags
.bits()
.try_into()
.map(Self::from_bits_retain)
.map_err(|_| ())
}
}
impl OpenFlags {
#[inline]
pub fn access_mode(self) -> Option<libc::c_int> {
if self.contains(OpenFlags::O_PATH) {
None
} else {
let acc_mode = self.bits() & (libc::O_ACCMODE as u64);
debug_assert!(
acc_mode <= 0b11,
"{:X} masked by O_ACCMODE ({:X}) mask should only set bottom two bits",
self.bits(),
libc::O_ACCMODE,
);
Some(acc_mode as _)
}
}
#[inline]
pub fn wants_read(self) -> bool {
match self.access_mode() {
None => false, Some(acc) => acc == libc::O_RDONLY || acc == libc::O_RDWR,
}
}
#[inline]
pub fn wants_write(self) -> bool {
match self.access_mode() {
None => false, Some(acc) => {
acc == libc::O_WRONLY
|| acc == libc::O_RDWR
|| self.intersects(OpenFlags::O_TRUNC | OpenFlags::O_CREAT)
}
}
}
}
bitflags! {
#[derive(Default, PartialEq, Eq, Debug, Clone, Copy)]
pub struct RenameFlags: u64 {
const RENAME_EXCHANGE = libc::RENAME_EXCHANGE as _;
const RENAME_NOREPLACE = libc::RENAME_NOREPLACE as _;
const RENAME_WHITEOUT = libc::RENAME_WHITEOUT as _;
const _ = !0;
}
}
impl From<RenameFlags> for rustix::fs::RenameFlags {
fn from(flags: RenameFlags) -> Self {
debug_assert!(
flags.bits() < (1u64 << 32),
"RenameFlags cannot contain anything in the top 32 bits."
);
Self::from_bits_retain(flags.bits() as _)
}
}
impl RenameFlags {
pub fn is_supported(self) -> bool {
self.is_empty() || *syscalls::RENAME_FLAGS_SUPPORTED
}
}
#[cfg(test)]
mod tests {
use crate::{
flags::{OpenFlags, RenameFlags},
syscalls,
};
macro_rules! openflags_tests {
($($test_name:ident ( $($flag:ident)|+ ) == {accmode: $accmode:expr, read: $wants_read:expr, write: $wants_write:expr} );+ $(;)?) => {
$(
paste::paste! {
#[test]
fn [<openflags_ $test_name _access_mode>]() {
let flags = $(OpenFlags::$flag)|*;
let accmode: Option<i32> = $accmode;
assert_eq!(flags.access_mode(), accmode, "{flags:?} access mode should be {:?}", accmode.map(|flags| OpenFlags::from_bits_retain(flags as _)));
}
#[test]
fn [<openflags_ $test_name _wants_read>]() {
let flags = $(OpenFlags::$flag)|*;
assert_eq!(flags.wants_read(), $wants_read, "{flags:?} wants_read should be {:?}", $wants_read);
}
#[test]
fn [<openflags_ $test_name _wants_write>]() {
let flags = $(OpenFlags::$flag)|*;
assert_eq!(flags.wants_write(), $wants_write, "{flags:?} wants_write should be {:?}", $wants_write);
}
}
)*
}
}
openflags_tests! {
plain_rdonly(O_RDONLY) == {accmode: Some(libc::O_RDONLY), read: true, write: false};
plain_wronly(O_WRONLY) == {accmode: Some(libc::O_WRONLY), read: false, write: true};
plain_rdwr(O_RDWR) == {accmode: Some(libc::O_RDWR), read: true, write: true};
plain_opath(O_PATH) == {accmode: None, read: false, write: false};
rdwr_opath(O_RDWR|O_PATH) == {accmode: None, read: false, write: false};
wronly_opath(O_WRONLY|O_PATH) == {accmode: None, read: false, write: false};
trunc_rdonly(O_RDONLY|O_TRUNC) == {accmode: Some(libc::O_RDONLY), read: true, write: true};
trunc_wronly(O_WRONLY|O_TRUNC) == {accmode: Some(libc::O_WRONLY), read: false, write: true};
trunc_rdwr(O_RDWR|O_TRUNC) == {accmode: Some(libc::O_RDWR), read: true, write: true};
trunc_path(O_PATH|O_TRUNC) == {accmode: None, read: false, write: false};
creat_rdonly(O_RDONLY|O_CREAT) == {accmode: Some(libc::O_RDONLY), read: true, write: true};
creat_wronly(O_WRONLY|O_CREAT) == {accmode: Some(libc::O_WRONLY), read: false, write: true};
creat_rdwr(O_RDWR|O_CREAT) == {accmode: Some(libc::O_RDWR), read: true, write: true};
creat_path(O_PATH|O_CREAT) == {accmode: None, read: false, write: false};
}
#[test]
fn rename_flags_is_supported() {
assert!(
RenameFlags::empty().is_supported(),
"empty flags should be supported"
);
assert_eq!(
RenameFlags::RENAME_EXCHANGE.is_supported(),
*syscalls::RENAME_FLAGS_SUPPORTED,
"rename flags being supported should be identical to RENAME_FLAGS_SUPPORTED"
);
}
}
bitflags! {
#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct ResolverFlags: u64 {
const NO_SYMLINKS = libc::RESOLVE_NO_SYMLINKS;
}
}