#![allow(unsafe_code)]
use crate::{
error::Backtrace,
utils::{RawFdExt, ToCString},
};
use std::{
ffi::OsStr,
fmt,
fs::File,
io::Error as IOError,
os::unix::{
ffi::OsStrExt,
io::{FromRawFd, RawFd},
},
path::{Path, PathBuf},
};
use libc::{c_int, dev_t, mode_t, stat, statfs};
use snafu::ResultExt;
#[derive(Clone, Debug)]
pub struct FrozenFd(c_int, Option<PathBuf>);
impl From<RawFd> for FrozenFd {
fn from(fd: RawFd) -> Self {
FrozenFd(fd, fd.as_unsafe_path().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(Debug, Snafu)]
pub enum Error {
#[snafu(display("fcntl({}, F_DUPFD_CLOEXEC, 0)", fd))]
FcntlDup {
fd: FrozenFd,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("fcntl({}, F_GETFD)", fd))]
FcntlGetFlags {
fd: FrozenFd,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("fcntl({}, F_SETFD, 0x{:x})", fd, flags))]
FcntlSetFlags {
fd: FrozenFd,
flags: i32,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("openat({}, {:?}, 0x{:x}, 0o{:o})", dirfd, path, flags, mode))]
Openat {
dirfd: FrozenFd,
path: PathBuf,
flags: i32,
mode: u32,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("openat2({}, {:?}, {}, {})", dirfd, path, how, size))]
Openat2 {
dirfd: FrozenFd,
path: PathBuf,
how: unstable::OpenHow,
size: usize,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("readlinkat({}, {:?})", dirfd, path))]
Readlinkat {
dirfd: FrozenFd,
path: PathBuf,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("mkdirat({}, {:?}, 0o{:o})", dirfd, path, mode))]
Mkdirat {
dirfd: FrozenFd,
path: PathBuf,
mode: u32,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("mknodat({}, {:?}, 0o{:o}, {}:{})", dirfd, path, mode, major, minor))]
Mknodat {
dirfd: FrozenFd,
path: PathBuf,
mode: u32,
major: u32,
minor: u32,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("unlinkat({}, {:?}, 0x{:x})", dirfd, path, flags))]
Unlinkat {
dirfd: FrozenFd,
path: PathBuf,
flags: i32,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display(
"linkat({}, {:?}, {}, {:?}, 0x{:x})",
olddirfd,
oldpath,
newdirfd,
newpath,
flags
))]
Linkat {
olddirfd: FrozenFd,
oldpath: PathBuf,
newdirfd: FrozenFd,
newpath: PathBuf,
flags: i32,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("symlinkat({:?}, {}, {:?})", target, dirfd, path))]
Symlinkat {
dirfd: FrozenFd,
path: PathBuf,
target: PathBuf,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("renameat({}, {:?}, {}, {:?})", olddirfd, oldpath, newdirfd, newpath))]
Renameat {
olddirfd: FrozenFd,
oldpath: PathBuf,
newdirfd: FrozenFd,
newpath: PathBuf,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display(
"renameat2({}, {:?}, {}, {:?}, 0x{:x})",
olddirfd,
oldpath,
newdirfd,
newpath,
flags
))]
Renameat2 {
olddirfd: FrozenFd,
oldpath: PathBuf,
newdirfd: FrozenFd,
newpath: PathBuf,
flags: i32,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("fstatfs({})", fd))]
Fstatfs {
fd: FrozenFd,
source: IOError,
backtrace: Backtrace,
},
#[snafu(display("fstatat({}, {:?}, 0x{:x})", dirfd, path, flags))]
Fstatat {
dirfd: FrozenFd,
path: PathBuf,
flags: i32,
source: IOError,
backtrace: Backtrace,
},
}
impl Error {
pub(crate) fn root_cause(&self) -> &IOError {
match self {
Error::FcntlDup { source, .. } => source,
Error::FcntlGetFlags { source, .. } => source,
Error::FcntlSetFlags { 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,
}
}
}
pub(crate) fn fcntl_dupfd_cloxec(fd: RawFd) -> Result<File, Error> {
let newfd = unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) };
let err = IOError::last_os_error();
if newfd >= 0 {
Ok(unsafe { File::from_raw_fd(newfd) })
} else {
Err(err).context(FcntlDup { fd })
}
}
pub(crate) fn fcntl_unset_cloexec(fd: RawFd) -> Result<(), Error> {
let old = unsafe { libc::fcntl(fd, libc::F_GETFD) };
let err = IOError::last_os_error();
if old < 0 {
return Err(err).context(FcntlGetFlags { fd });
}
let new = old & !libc::FD_CLOEXEC;
if new == old {
return Ok(());
}
let ret = unsafe { libc::fcntl(fd, libc::F_SETFD, new) };
let err = IOError::last_os_error();
if ret >= 0 {
Ok(())
} else {
Err(err).context(FcntlSetFlags { fd, flags: new })
}
}
pub(crate) fn openat_follow<P: AsRef<Path>>(
dirfd: RawFd,
path: P,
flags: c_int,
mode: mode_t,
) -> Result<File, Error> {
let path = path.as_ref();
let flags = libc::O_CLOEXEC | libc::O_NOCTTY | flags;
let fd = unsafe { libc::openat(dirfd, path.to_c_string().as_ptr(), flags, mode) };
let err = IOError::last_os_error();
if fd >= 0 {
Ok(unsafe { File::from_raw_fd(fd) })
} else {
Err(err).context(Openat {
dirfd,
path,
flags,
mode,
})
}
}
pub(crate) fn openat<P: AsRef<Path>>(
dirfd: RawFd,
path: P,
flags: c_int,
mode: mode_t,
) -> Result<File, Error> {
openat_follow(dirfd, path, libc::O_NOFOLLOW | flags, mode)
}
pub(crate) fn readlinkat<P: AsRef<Path>>(dirfd: RawFd, path: P) -> Result<PathBuf, Error> {
let path = path.as_ref();
let mut buffer = [0 as u8; 32 * libc::PATH_MAX as usize];
let len = unsafe {
libc::readlinkat(
dirfd,
path.to_c_string().as_ptr(),
buffer.as_mut_ptr() as *mut i8,
buffer.len(),
)
};
let mut err = IOError::last_os_error();
let maybe_truncated = len >= (buffer.len() as isize);
if len < 0 || maybe_truncated {
if maybe_truncated {
err = IOError::from_raw_os_error(libc::ENAMETOOLONG);
}
Err(err).context(Readlinkat { dirfd, path })
} else {
let content = OsStr::from_bytes(&buffer[..(len as usize)]);
Ok(PathBuf::from(content))
}
}
pub(crate) fn mkdirat<P: AsRef<Path>>(dirfd: RawFd, path: P, mode: mode_t) -> Result<(), Error> {
let path = path.as_ref();
let ret = unsafe { libc::mkdirat(dirfd, path.to_c_string().as_ptr(), mode) };
let err = IOError::last_os_error();
if ret >= 0 {
Ok(())
} else {
Err(err).context(Mkdirat { dirfd, path, mode })
}
}
pub(crate) fn mknodat<P: AsRef<Path>>(
dirfd: RawFd,
path: P,
mode: mode_t,
dev: dev_t,
) -> Result<(), Error> {
let path = path.as_ref();
let ret = unsafe { libc::mknodat(dirfd, path.to_c_string().as_ptr(), mode, dev) };
let err = IOError::last_os_error();
if ret >= 0 {
Ok(())
} else {
Err(err).context(Mknodat {
dirfd,
path,
mode,
major: unsafe { libc::major(dev) },
minor: unsafe { libc::minor(dev) },
})
}
}
pub(crate) fn unlinkat<P: AsRef<Path>>(dirfd: RawFd, path: P, flags: c_int) -> Result<(), Error> {
let path = path.as_ref();
let ret = unsafe { libc::unlinkat(dirfd, path.to_c_string().as_ptr(), flags) };
let err = IOError::last_os_error();
if ret >= 0 {
Ok(())
} else {
Err(err).context(Unlinkat { dirfd, path, flags })
}
}
pub(crate) fn linkat<P: AsRef<Path>>(
olddirfd: RawFd,
oldpath: P,
newdirfd: RawFd,
newpath: P,
flags: c_int,
) -> Result<(), Error> {
let (oldpath, newpath) = (oldpath.as_ref(), newpath.as_ref());
let ret = unsafe {
libc::linkat(
olddirfd,
oldpath.to_c_string().as_ptr(),
newdirfd,
newpath.to_c_string().as_ptr(),
flags,
)
};
let err = IOError::last_os_error();
if ret >= 0 {
Ok(())
} else {
Err(err).context(Linkat {
olddirfd,
oldpath,
newdirfd,
newpath,
flags,
})
}
}
pub(crate) fn symlinkat<P: AsRef<Path>>(target: P, dirfd: RawFd, path: P) -> Result<(), Error> {
let (target, path) = (target.as_ref(), path.as_ref());
let ret = unsafe {
libc::symlinkat(
target.to_c_string().as_ptr(),
dirfd,
path.to_c_string().as_ptr(),
)
};
let err = IOError::last_os_error();
if ret >= 0 {
Ok(())
} else {
Err(err).context(Symlinkat {
dirfd,
path,
target,
})
}
}
pub(crate) fn renameat<P: AsRef<Path>>(
olddirfd: RawFd,
oldpath: P,
newdirfd: RawFd,
newpath: P,
) -> Result<(), Error> {
let (oldpath, newpath) = (oldpath.as_ref(), newpath.as_ref());
let ret = unsafe {
libc::renameat(
olddirfd,
oldpath.to_c_string().as_ptr(),
newdirfd,
newpath.to_c_string().as_ptr(),
)
};
let err = IOError::last_os_error();
if ret >= 0 {
Ok(())
} else {
Err(err).context(Renameat {
olddirfd,
oldpath,
newdirfd,
newpath,
})
}
}
lazy_static! {
pub(crate) static ref RENAME_FLAGS_SUPPORTED: bool = {
match renameat2(
libc::AT_FDCWD,
".",
libc::AT_FDCWD,
".",
libc::RENAME_EXCHANGE,
) {
Ok(_) => true,
Err(err) => err.root_cause().raw_os_error() != Some(libc::ENOSYS),
}
};
}
pub(crate) fn renameat2<P: AsRef<Path>>(
olddirfd: RawFd,
oldpath: P,
newdirfd: RawFd,
newpath: P,
flags: i32,
) -> Result<(), Error> {
let (oldpath, newpath) = (oldpath.as_ref(), newpath.as_ref());
let ret = unsafe {
libc::syscall(
libc::SYS_renameat2,
olddirfd,
oldpath.to_c_string().as_ptr(),
newdirfd,
newpath.to_c_string().as_ptr(),
flags,
)
};
let err = IOError::last_os_error();
if ret >= 0 {
Ok(())
} else {
if flags == 0 {
return renameat(olddirfd, oldpath, newdirfd, newpath);
}
Err(err).context(Renameat2 {
olddirfd,
oldpath,
newdirfd,
newpath,
flags,
})
}
}
pub(crate) fn fstatfs(fd: RawFd) -> Result<statfs, Error> {
let mut buf: statfs = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::fstatfs(fd, &mut buf as *mut statfs) };
let err = IOError::last_os_error();
if ret >= 0 {
Ok(buf)
} else {
Err(err).context(Fstatfs { fd })
}
}
pub(crate) fn fstatat<P: AsRef<Path>>(dirfd: RawFd, path: P) -> Result<stat, Error> {
let mut buf: stat = unsafe { std::mem::zeroed() };
let path = path.as_ref();
let flags = libc::AT_NO_AUTOMOUNT | libc::AT_SYMLINK_NOFOLLOW | libc::AT_EMPTY_PATH;
let ret = unsafe {
libc::fstatat(
dirfd,
path.to_c_string().as_ptr(),
&mut buf as *mut stat,
flags,
)
};
let err = IOError::last_os_error();
if ret >= 0 {
Ok(buf)
} else {
Err(err).context(Fstatat { dirfd, path, flags })
}
}
pub(crate) mod unstable {
use super::*;
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub struct OpenHow {
pub flags: u64,
pub mode: u64,
pub resolve: u64,
}
const OPEN_HOW_SIZE: usize = std::mem::size_of::<OpenHow>();
impl fmt::Display for OpenHow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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: 0x{:x} }}", self.resolve)
}
}
#[allow(unused)]
pub const RESOLVE_NO_XDEV: u64 = 0x01;
#[allow(unused)]
pub const RESOLVE_NO_MAGICLINKS: u64 = 0x02;
#[allow(unused)]
pub const RESOLVE_NO_SYMLINKS: u64 = 0x04;
#[allow(unused)]
pub const RESOLVE_BENEATH: u64 = 0x08;
#[allow(unused)]
pub const RESOLVE_IN_ROOT: u64 = 0x10;
#[allow(non_upper_case_globals)]
const SYS_openat2: i64 = 437;
pub fn openat2<P: AsRef<Path>>(dirfd: RawFd, path: P, how: &OpenHow) -> Result<File, Error> {
let path = path.as_ref();
let mut how = how.clone();
how.flags |= libc::O_CLOEXEC as u64;
let fd = unsafe {
libc::syscall(
SYS_openat2,
dirfd,
path.to_c_string().as_ptr(),
&how as *const OpenHow,
OPEN_HOW_SIZE,
)
} as RawFd;
let err = IOError::last_os_error();
if fd >= 0 {
Ok(unsafe { File::from_raw_fd(fd) })
} else {
Err(err).context(Openat2 {
dirfd,
path,
how,
size: OPEN_HOW_SIZE,
})
}
}
}