#![allow(unsafe_code)]
use crate::{
flags::{OpenFlags, RenameFlags},
utils::{FdExt, ToCString},
};
use std::{
borrow::Cow,
ffi::OsStr,
fmt,
io::Error as IOError,
mem::MaybeUninit,
os::unix::{
ffi::OsStrExt,
io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
},
path::{Path, PathBuf},
};
use bitflags::bitflags;
use once_cell::sync::Lazy;
use rustix::{
fs::{
self as rustix_fs, Access, AtFlags, Dev, FileType, Mode, RawMode, Stat, StatFs, Statx,
StatxFlags,
},
io::Errno,
mount::{self as rustix_mount, FsMountFlags, FsOpenFlags, MountAttrFlags, OpenTreeFlags},
process::{self as rustix_process, DumpableBehavior},
thread as rustix_thread,
};
pub(crate) const AT_FDCWD: BorrowedFd<'static> = rustix_fs::CWD;
pub(crate) const BADFD: BorrowedFd<'static> = unsafe { BorrowedFd::borrow_raw(-libc::EBADF) };
#[derive(Clone, Debug)]
pub(crate) struct FrozenFd(RawFd, Option<PathBuf>);
impl<Fd: AsFd> From<Fd> for FrozenFd {
fn from(fd: Fd) -> Self {
Self(fd.as_fd().as_raw_fd(), fd.as_unsafe_path_unchecked().ok())
}
}
impl fmt::Display for FrozenFd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
libc::AT_FDCWD => write!(f, "[AT_FDCWD]")?,
fd => write!(f, "[{fd}]")?,
};
match &self.1 {
Some(path) => write!(f, "<{path:?}>")?,
None => write!(f, "<unknown>")?,
};
Ok(())
}
}
#[derive(thiserror::Error, Debug)]
pub(crate) enum Error {
#[error("invalid file descriptor {fd} (see <https://github.com/bytecodealliance/rustix/issues/1187> for more details)")]
InvalidFd { fd: RawFd, source: Errno },
#[error("accessat({dirfd}, {path:?}, {access:?}, {flags:?})")]
Accessat {
dirfd: FrozenFd,
path: PathBuf,
access: Access,
flags: AtFlags,
source: Errno,
},
#[error("openat({dirfd}, {path:?}, {flags:?}, 0o{mode:o})")]
Openat {
dirfd: FrozenFd,
path: PathBuf,
flags: OpenFlags,
mode: u32,
source: Errno,
},
#[error("openat2({dirfd}, {path:?}, {how}, {size})")]
Openat2 {
dirfd: FrozenFd,
path: PathBuf,
how: OpenHow,
size: usize,
source: Errno,
},
#[error("readlinkat({dirfd}, {path:?})")]
Readlinkat {
dirfd: FrozenFd,
path: PathBuf,
source: Errno,
},
#[error("mkdirat({dirfd}, {path:?}, 0o{mode:o})")]
Mkdirat {
dirfd: FrozenFd,
path: PathBuf,
mode: u32,
source: Errno,
},
#[error("mknodat({dirfd}, {path:?}, 0o{mode:o}, {major}:{minor})")]
Mknodat {
dirfd: FrozenFd,
path: PathBuf,
mode: u32,
major: u32,
minor: u32,
source: Errno,
},
#[error("unlinkat({dirfd}, {path:?}, {flags:?})")]
Unlinkat {
dirfd: FrozenFd,
path: PathBuf,
flags: AtFlags,
source: Errno,
},
#[error("linkat({old_dirfd}, {old_path:?}, {new_dirfd}, {new_path:?}, {flags:?})")]
Linkat {
old_dirfd: FrozenFd,
old_path: PathBuf,
new_dirfd: FrozenFd,
new_path: PathBuf,
flags: AtFlags,
source: Errno,
},
#[error("symlinkat({dirfd}, {path:?}, {target:?})")]
Symlinkat {
dirfd: FrozenFd,
path: PathBuf,
target: PathBuf,
source: Errno,
},
#[error("renameat({old_dirfd}, {old_path:?}, {new_dirfd}, {new_path:?})")]
Renameat {
old_dirfd: FrozenFd,
old_path: PathBuf,
new_dirfd: FrozenFd,
new_path: PathBuf,
source: Errno,
},
#[error("renameat2({old_dirfd}, {old_path:?}, {new_dirfd}, {new_path:?}, {flags:?})")]
Renameat2 {
old_dirfd: FrozenFd,
old_path: PathBuf,
new_dirfd: FrozenFd,
new_path: PathBuf,
flags: RenameFlags,
source: Errno,
},
#[error("fstatfs({fd})")]
Fstatfs { fd: FrozenFd, source: Errno },
#[error("fstatat({dirfd}, {path:?}, {flags:?})")]
Fstatat {
dirfd: FrozenFd,
path: PathBuf,
flags: AtFlags,
source: Errno,
},
#[error("statx({dirfd}, {path:?}, flags={flags:?}, mask={mask:?})")]
Statx {
dirfd: FrozenFd,
path: PathBuf,
flags: AtFlags,
mask: StatxFlags,
source: Errno,
},
#[error("fsopen({fstype:?}, {flags:?})")]
Fsopen {
fstype: String,
flags: FsOpenFlags,
source: Errno,
},
#[error("fsconfig({sfd}, FSCONFIG_CMD_CREATE)")]
FsconfigCreate { sfd: FrozenFd, source: Errno },
#[error("fsconfig({sfd}, FSCONFIG_SET_STRING, {key:?}, {value:?})")]
FsconfigSetString {
sfd: FrozenFd,
key: String,
value: String,
source: Errno,
},
#[error("fsmount({sfd}, {flags:?}, {mount_attrs:?})")]
Fsmount {
sfd: FrozenFd,
flags: FsMountFlags,
mount_attrs: MountAttrFlags,
source: Errno,
},
#[error("open_tree({dirfd}, {path:?}, {flags:?})")]
OpenTree {
dirfd: FrozenFd,
path: PathBuf,
flags: OpenTreeFlags,
source: Errno,
},
#[error("prctl({get_flag})")]
PrctlGet {
get_flag: Cow<'static, str>,
source: Errno,
},
}
impl Error {
pub(crate) fn errno(&self) -> Errno {
*match self {
Error::InvalidFd { source, .. } => source,
Error::Accessat { source, .. } => source,
Error::Openat { source, .. } => source,
Error::Openat2 { source, .. } => source,
Error::Readlinkat { source, .. } => source,
Error::Mkdirat { source, .. } => source,
Error::Mknodat { source, .. } => source,
Error::Unlinkat { source, .. } => source,
Error::Linkat { source, .. } => source,
Error::Symlinkat { source, .. } => source,
Error::Renameat { source, .. } => source,
Error::Renameat2 { source, .. } => source,
Error::Fstatfs { source, .. } => source,
Error::Fstatat { source, .. } => source,
Error::Statx { source, .. } => source,
Error::Fsopen { source, .. } => source,
Error::FsconfigCreate { source, .. } => source,
Error::FsconfigSetString { source, .. } => source,
Error::Fsmount { source, .. } => source,
Error::OpenTree { source, .. } => source,
Error::PrctlGet { source, .. } => source,
}
}
pub(crate) fn root_cause(&self) -> IOError {
IOError::from_raw_os_error(self.errno().raw_os_error())
}
}
trait CheckRustixFd: Sized {
fn check_rustix_fd(self) -> Result<Self, Error>;
}
impl<Fd: AsFd + Sized> CheckRustixFd for Fd {
fn check_rustix_fd(self) -> Result<Self, Error> {
const BADFD: RawFd = -libc::EBADF as _;
match self.as_fd().as_raw_fd() {
libc::AT_FDCWD | BADFD | 0.. => Ok(self),
fd => Err(Error::InvalidFd {
fd,
source: Errno::BADF,
}),
}
}
}
pub(crate) fn accessat(
dirfd: impl AsFd,
path: impl AsRef<Path>,
access: Access,
mut flags: AtFlags,
) -> Result<(), Error> {
let (dirfd, path) = (dirfd.as_fd().check_rustix_fd()?, path.as_ref());
flags |= AtFlags::SYMLINK_NOFOLLOW;
rustix_fs::accessat(dirfd, path, access, flags).map_err(|errno| Error::Accessat {
dirfd: dirfd.into(),
path: path.into(),
flags,
access,
source: errno,
})
}
pub(crate) fn openat_follow(
dirfd: impl AsFd,
path: impl AsRef<Path>,
mut flags: OpenFlags,
mode: RawMode, ) -> Result<OwnedFd, Error> {
let dirfd = dirfd.as_fd().check_rustix_fd()?;
let path = path.as_ref();
flags.insert(OpenFlags::O_CLOEXEC | OpenFlags::O_NOCTTY);
rustix_fs::openat(dirfd, path, flags.into(), Mode::from_raw_mode(mode)).map_err(|errno| {
Error::Openat {
dirfd: dirfd.into(),
path: path.into(),
flags,
mode,
source: errno,
}
})
}
pub(crate) fn openat(
dirfd: impl AsFd,
path: impl AsRef<Path>,
mut flags: OpenFlags,
mode: RawMode, ) -> Result<OwnedFd, Error> {
flags.insert(OpenFlags::O_NOFOLLOW);
openat_follow(dirfd, path, flags, mode)
}
pub(crate) fn readlinkat(dirfd: impl AsFd, path: impl AsRef<Path>) -> Result<PathBuf, Error> {
let dirfd = dirfd.as_fd().check_rustix_fd()?;
let path = path.as_ref();
let mut linkbuf: [MaybeUninit<u8>; 32 * 4096] =
[MaybeUninit::uninit(); 32 * libc::PATH_MAX as usize];
let (target, trailing) =
rustix_fs::readlinkat_raw(dirfd, path, &mut linkbuf[..]).map_err(|errno| {
Error::Readlinkat {
dirfd: dirfd.into(),
path: path.into(),
source: errno,
}
})?;
if trailing.is_empty() {
Err(Error::Readlinkat {
dirfd: dirfd.into(),
path: path.into(),
source: Errno::NAMETOOLONG,
})
} else {
Ok(PathBuf::from(OsStr::from_bytes(target)))
}
}
pub(crate) fn mkdirat(
dirfd: impl AsFd,
path: impl AsRef<Path>,
mode: RawMode, ) -> Result<(), Error> {
let dirfd = dirfd.as_fd().check_rustix_fd()?;
let path = path.as_ref();
rustix_fs::mkdirat(dirfd, path, Mode::from_raw_mode(mode)).map_err(|errno| Error::Mkdirat {
dirfd: dirfd.into(),
path: path.into(),
mode,
source: errno,
})
}
pub(crate) fn devmajorminor(dev: Dev) -> (u32, u32) {
(rustix_fs::major(dev), rustix_fs::minor(dev))
}
pub(crate) fn mknodat(
dirfd: impl AsFd,
path: impl AsRef<Path>,
raw_mode: RawMode, dev: Dev,
) -> Result<(), Error> {
let dirfd = dirfd.as_fd().check_rustix_fd()?;
let path = path.as_ref();
let (file_type, mode) = (
FileType::from_raw_mode(raw_mode),
Mode::from_raw_mode(raw_mode),
);
rustix_fs::mknodat(dirfd, path, file_type, mode, dev).map_err(|errno| {
let (major, minor) = devmajorminor(dev);
Error::Mknodat {
dirfd: dirfd.into(),
path: path.into(),
mode: raw_mode,
major,
minor,
source: errno,
}
})
}
pub(crate) fn unlinkat(
dirfd: impl AsFd,
path: impl AsRef<Path>,
flags: AtFlags,
) -> Result<(), Error> {
let dirfd = dirfd.as_fd().check_rustix_fd()?;
let path = path.as_ref();
rustix_fs::unlinkat(dirfd, path, flags).map_err(|errno| Error::Unlinkat {
dirfd: dirfd.into(),
path: path.into(),
flags,
source: errno,
})
}
pub(crate) fn linkat(
old_dirfd: impl AsFd,
old_path: impl AsRef<Path>,
new_dirfd: impl AsFd,
new_path: impl AsRef<Path>,
flags: AtFlags,
) -> Result<(), Error> {
let (old_dirfd, old_path) = (old_dirfd.as_fd().check_rustix_fd()?, old_path.as_ref());
let (new_dirfd, new_path) = (new_dirfd.as_fd().check_rustix_fd()?, new_path.as_ref());
rustix_fs::linkat(old_dirfd, old_path, new_dirfd, new_path, flags).map_err(|errno| {
Error::Linkat {
old_dirfd: old_dirfd.into(),
old_path: old_path.into(),
new_dirfd: new_dirfd.into(),
new_path: new_path.into(),
flags,
source: errno,
}
})
}
pub(crate) fn symlinkat(
target: impl AsRef<Path>,
dirfd: impl AsFd,
path: impl AsRef<Path>,
) -> Result<(), Error> {
let (dirfd, path) = (dirfd.as_fd().check_rustix_fd()?, path.as_ref());
let target = target.as_ref();
rustix_fs::symlinkat(target, dirfd, path).map_err(|errno| Error::Symlinkat {
dirfd: dirfd.into(),
path: path.into(),
target: target.into(),
source: errno,
})
}
pub(crate) fn renameat(
old_dirfd: impl AsFd,
old_path: impl AsRef<Path>,
new_dirfd: impl AsFd,
new_path: impl AsRef<Path>,
) -> Result<(), Error> {
let (old_dirfd, old_path) = (old_dirfd.as_fd().check_rustix_fd()?, old_path.as_ref());
let (new_dirfd, new_path) = (new_dirfd.as_fd().check_rustix_fd()?, new_path.as_ref());
rustix_fs::renameat(old_dirfd, old_path, new_dirfd, new_path).map_err(|errno| Error::Renameat {
old_dirfd: old_dirfd.into(),
old_path: old_path.into(),
new_dirfd: new_dirfd.into(),
new_path: new_path.into(),
source: errno,
})
}
pub(crate) static RENAME_FLAGS_SUPPORTED: Lazy<bool> = Lazy::new(|| {
match renameat2(AT_FDCWD, ".", AT_FDCWD, ".", RenameFlags::RENAME_EXCHANGE) {
Ok(_) => true,
Err(err) => err.root_cause().raw_os_error() != Some(libc::ENOSYS),
}
});
pub(crate) fn renameat2(
old_dirfd: impl AsFd,
old_path: impl AsRef<Path>,
new_dirfd: impl AsFd,
new_path: impl AsRef<Path>,
flags: RenameFlags,
) -> Result<(), Error> {
if flags.is_empty() {
return renameat(old_dirfd, old_path, new_dirfd, new_path);
}
let (old_dirfd, old_path) = (old_dirfd.as_fd().check_rustix_fd()?, old_path.as_ref());
let (new_dirfd, new_path) = (new_dirfd.as_fd().check_rustix_fd()?, new_path.as_ref());
rustix_fs::renameat_with(old_dirfd, old_path, new_dirfd, new_path, flags.into()).map_err(
|errno| Error::Renameat2 {
old_dirfd: old_dirfd.into(),
old_path: old_path.into(),
new_dirfd: new_dirfd.into(),
new_path: new_path.into(),
flags,
source: errno,
},
)
}
pub(crate) fn fstatfs(fd: impl AsFd) -> Result<StatFs, Error> {
let fd = fd.as_fd().check_rustix_fd()?;
rustix_fs::fstatfs(fd).map_err(|errno| Error::Fstatfs {
fd: fd.into(),
source: errno,
})
}
pub(crate) fn fstatat(dirfd: impl AsFd, path: impl AsRef<Path>) -> Result<Stat, Error> {
let dirfd = dirfd.as_fd().check_rustix_fd()?;
let path = path.as_ref();
let flags = AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW | AtFlags::EMPTY_PATH;
rustix_fs::statat(dirfd, path, flags).map_err(|errno| Error::Fstatat {
dirfd: dirfd.into(),
path: path.into(),
flags,
source: errno,
})
}
pub(crate) fn statx(
dirfd: impl AsFd,
path: impl AsRef<Path>,
mask: StatxFlags,
) -> Result<Statx, Error> {
let dirfd = dirfd.as_fd().check_rustix_fd()?;
let path = path.as_ref();
let flags = AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW | AtFlags::EMPTY_PATH;
rustix_fs::statx(dirfd, path, flags, mask).map_err(|errno| Error::Statx {
dirfd: dirfd.into(),
path: path.into(),
flags,
mask,
source: errno,
})
}
pub(crate) mod openat2 {
use super::*;
use once_cell::sync::OnceCell;
static SAW_OPENAT2_FAILURE: OnceCell<()> = OnceCell::new();
pub(crate) fn saw_openat2_failure() -> bool {
SAW_OPENAT2_FAILURE.get().is_some()
}
pub(crate) fn openat2_is_not_supported() -> bool {
SAW_OPENAT2_FAILURE.get().map_or_else(
|| match openat2(AT_FDCWD, ".", Default::default()) {
Ok(_) => false,
Err(_) => {
let _ = SAW_OPENAT2_FAILURE.set(());
true
}
},
|_| true, )
}
bitflags! {
#[derive(Default, PartialEq, Eq, Debug, Clone, Copy)]
pub(crate) struct ResolveFlags: u64 {
const RESOLVE_BENEATH = libc::RESOLVE_BENEATH;
const RESOLVE_IN_ROOT = libc::RESOLVE_IN_ROOT;
const RESOLVE_NO_MAGICLINKS = libc::RESOLVE_NO_MAGICLINKS;
const RESOLVE_NO_SYMLINKS = libc::RESOLVE_NO_SYMLINKS;
const RESOLVE_NO_XDEV = libc::RESOLVE_NO_XDEV;
const RESOLVE_CACHED = libc::RESOLVE_CACHED;
const _ = !0;
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct OpenHow {
pub flags: u64,
pub mode: u64,
pub resolve: u64,
}
impl fmt::Display for OpenHow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{ ")?;
if let Ok(oflags) = i32::try_from(self.flags) {
write!(f, "flags: {:?}, ", OpenFlags::from_bits_retain(oflags))?;
} else {
write!(f, "flags: 0x{:x}, ", self.flags)?;
}
if self.flags & (libc::O_CREAT | libc::O_TMPFILE) as u64 != 0 {
write!(f, "mode: 0o{:o}, ", self.mode)?;
}
write!(
f,
"resolve: {:?}",
ResolveFlags::from_bits_retain(self.resolve)
)?;
write!(f, " }}")
}
}
impl OpenHow {
const fn is_scoped_lookup(&self) -> bool {
const SCOPED_LOOKUP_FLAGS: ResolveFlags =
ResolveFlags::RESOLVE_BENEATH.union(ResolveFlags::RESOLVE_IN_ROOT);
ResolveFlags::from_bits_retain(self.resolve).intersects(SCOPED_LOOKUP_FLAGS)
}
}
pub(crate) fn openat2_follow(
dirfd: impl AsFd,
path: impl AsRef<Path>,
mut how: OpenHow,
) -> Result<OwnedFd, Error> {
let dirfd = dirfd.as_fd().check_rustix_fd()?;
let path = path.as_ref();
how.flags |= libc::O_CLOEXEC as u64;
if how.flags & libc::O_PATH as u64 == 0 {
how.flags |= libc::O_NOCTTY as u64;
}
const MAX_RETRIES: u8 = 128;
let mut tries = 0u8;
let (fd, err) = loop {
let fd = unsafe {
libc::syscall(
libc::SYS_openat2,
dirfd.as_raw_fd(),
path.to_c_string().as_ptr(),
&how as *const OpenHow,
std::mem::size_of::<OpenHow>(),
)
} as RawFd;
let err = if fd < 0 {
Some(
IOError::last_os_error()
.raw_os_error()
.map(Errno::from_raw_os_error)
.expect("last_os_error must return a raw OS std::io::Error"),
)
} else {
None
};
if fd >= 0
|| !how.is_scoped_lookup()
|| err != Some(Errno::AGAIN)
|| tries >= MAX_RETRIES
{
break (fd, err);
}
tries += 1;
};
if fd >= 0 {
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
} else {
Err(Error::Openat2 {
dirfd: dirfd.into(),
path: path.into(),
how,
size: std::mem::size_of::<OpenHow>(),
source: err.expect("syscall failure must result in an error"),
})
}
}
pub(crate) fn openat2(
dirfd: impl AsFd,
path: impl AsRef<Path>,
mut how: OpenHow,
) -> Result<OwnedFd, Error> {
how.flags |= libc::O_NOFOLLOW as u64;
openat2_follow(dirfd, path, how)
}
}
pub(crate) use openat2::{openat2, openat2_follow, OpenHow, ResolveFlags};
#[cfg(test)]
mod personality {
#[cfg(not(target_env = "musl"))]
pub(crate) const PER_UNAME26: u32 = libc::UNAME26 as _;
#[cfg(target_env = "musl")]
pub(crate) const PER_UNAME26: u32 = 0x0020000;
pub(crate) fn personality(persona: Option<u32>) -> u32 {
unsafe { libc::personality(persona.unwrap_or(0xFFFF_FFFF) as _) as _ }
}
#[must_use]
pub(crate) fn scoped_personality(persona: u32) -> impl Drop {
scopeguard::guard(personality(Some(persona)), |old_persona| {
personality(Some(old_persona));
})
}
}
#[cfg(test)]
pub(crate) use personality::*;
#[cfg(test)]
pub(crate) fn getpid() -> rustix_process::RawPid {
rustix_process::Pid::as_raw(Some(rustix_process::getpid()))
}
pub(crate) fn gettid() -> rustix_process::RawPid {
rustix_process::Pid::as_raw(Some(rustix_thread::gettid()))
}
pub(crate) fn geteuid() -> rustix_process::RawUid {
rustix_process::geteuid().as_raw()
}
#[cfg(test)]
pub(crate) fn getegid() -> rustix_process::RawGid {
rustix_process::getegid().as_raw()
}
#[cfg(test)]
pub(crate) fn getcwd() -> Result<PathBuf, anyhow::Error> {
let buffer = Vec::with_capacity(libc::PATH_MAX as usize);
Ok(OsStr::from_bytes(rustix_process::getcwd(buffer)?.to_bytes()).into())
}
pub(crate) fn prctl_get_dumpable() -> Result<DumpableBehavior, Error> {
rustix_process::dumpable_behavior().map_err(|errno| Error::PrctlGet {
get_flag: "PR_GET_DUMPABLE".into(),
source: errno,
})
}
pub(crate) fn fsopen(fstype: &str, flags: FsOpenFlags) -> Result<OwnedFd, Error> {
rustix_mount::fsopen(fstype, flags).map_err(|errno| Error::Fsopen {
fstype: fstype.into(),
flags,
source: errno,
})
}
pub(crate) fn fsconfig_set_string(sfd: impl AsFd, key: &str, value: &str) -> Result<(), Error> {
let sfd = sfd.as_fd().check_rustix_fd()?;
rustix_mount::fsconfig_set_string(sfd, key, value).map_err(|errno| Error::FsconfigSetString {
sfd: sfd.into(),
key: key.into(),
value: value.into(),
source: errno,
})
}
pub(crate) fn fsconfig_create(sfd: impl AsFd) -> Result<(), Error> {
let sfd = sfd.as_fd().check_rustix_fd()?;
rustix_mount::fsconfig_create(sfd).map_err(|errno| Error::FsconfigCreate {
sfd: sfd.into(),
source: errno,
})
}
pub(crate) fn fsmount(
sfd: impl AsFd,
flags: FsMountFlags,
mount_attrs: MountAttrFlags,
) -> Result<OwnedFd, Error> {
let sfd = sfd.as_fd().check_rustix_fd()?;
rustix_mount::fsmount(sfd, flags, mount_attrs).map_err(|errno| Error::Fsmount {
sfd: sfd.into(),
flags,
mount_attrs,
source: errno,
})
}
pub(crate) fn open_tree(
dirfd: impl AsFd,
path: impl AsRef<Path>,
flags: OpenTreeFlags,
) -> Result<OwnedFd, Error> {
let dirfd = dirfd.as_fd().check_rustix_fd()?;
let path = path.as_ref();
rustix_mount::open_tree(dirfd, path, flags).map_err(|errno| Error::OpenTree {
dirfd: dirfd.into(),
path: path.into(),
flags,
source: errno,
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::{
fs::File,
os::unix::io::{AsRawFd, BorrowedFd},
};
use pretty_assertions::{assert_eq, assert_matches};
use tempfile::NamedTempFile;
#[test]
fn frozen_fd_display() {
let cwd = getcwd().expect("getcwd");
assert_eq!(
format!("{}", FrozenFd::from(AT_FDCWD)),
format!("[AT_FDCWD]<{cwd:?}>"),
"FrozenFd::from(AT_FDCWD)"
);
assert_eq!(
format!("{}", FrozenFd::from(BADFD)),
"[-9]<unknown>",
"FrozenFd::from(-EBADF)"
);
let file = NamedTempFile::new().expect("mktemp file");
assert_eq!(
format!("{}", FrozenFd::from(file.as_file())),
format!("[{}]<{:?}>", file.as_file().as_raw_fd(), file.path()),
"FrozenFd::from(<tempfile>)"
);
let (frozen_fd, fd, path) = {
let file = NamedTempFile::new().expect("mktemp file");
(
FrozenFd::from(file.as_file()),
file.as_file().as_raw_fd(),
file.path().to_path_buf(),
)
};
assert_eq!(
format!("{}", frozen_fd),
format!("[{}]<{:?}>", fd, path),
"FrozenFd::from(<tempfile>) after closing"
);
}
#[test]
fn check_rustix_fd() {
assert_matches!(
AT_FDCWD.check_rustix_fd(),
Ok(_),
"AT_FDCWD.check_rustix_fd() should be allowed"
);
assert_matches!(
BADFD.check_rustix_fd(),
Ok(_),
"BADFD.check_rustix_fd() should be allowed"
);
assert_matches!(
File::open(".").expect("open .").check_rustix_fd(),
Ok(_),
"<fd>.check_rustix_fd() should be allowed"
);
assert_matches!(
unsafe { BorrowedFd::borrow_raw(-1234) }.check_rustix_fd(),
Err(Error::InvalidFd { .. }),
"<-1234>.check_rustix_fd() should fail"
);
}
}