Packages
ferricstore
0.11.12
0.11.12
0.11.11
0.11.10
0.11.9
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.1
0.9.0
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.0
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.2.0
0.1.0
FerricFlow durable workflows and queues with native-protocol storage, Raft durability, and Bitcask persistence.
Current section
Files
Jump to
Current section
Files
native/ferricstore_bitcask/src/path_open.rs
//! Descriptor-relative Unix path opens that reject symlink traversal.
#![cfg(unix)]
use std::ffi::{CStr, CString, OsStr};
use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::os::unix::ffi::OsStrExt;
use std::path::{Component, Path};
fn component_name(name: &OsStr) -> io::Result<CString> {
CString::new(name.as_bytes())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contains null byte"))
}
fn open_directory_at(
parent_fd: libc::c_int,
name: &OsStr,
allow_symlink: bool,
) -> io::Result<OwnedFd> {
let name = component_name(name)?;
let nofollow = if allow_symlink { 0 } else { libc::O_NOFOLLOW };
let fd = unsafe {
libc::openat(
parent_fd,
name.as_ptr(),
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NONBLOCK | nofollow,
)
};
if fd < 0 {
Err(io::Error::last_os_error())
} else {
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
}
fn starting_directory(path: &Path) -> io::Result<OwnedFd> {
let start = if path.is_absolute() { c"/" } else { c"." };
let fd = unsafe {
libc::open(
start.as_ptr(),
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NOFOLLOW,
)
};
if fd < 0 {
Err(io::Error::last_os_error())
} else {
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
}
#[cfg(target_os = "linux")]
fn openat2_nofollow(
path: &Path,
flags: libc::c_int,
mode: libc::mode_t,
) -> io::Result<Option<std::fs::File>> {
let path = component_name(path.as_os_str())?;
// `open_how` is non-exhaustive on Linux libc targets, so initialize the
// kernel ABI struct to zero before setting the fields this syscall uses.
let mut how = unsafe { std::mem::zeroed::<libc::open_how>() };
how.flags = flags as u64;
how.mode = u64::from(mode);
how.resolve = libc::RESOLVE_NO_SYMLINKS;
let fd = unsafe {
libc::syscall(
libc::SYS_openat2,
libc::AT_FDCWD,
path.as_ptr(),
std::ptr::addr_of!(how),
std::mem::size_of::<libc::open_how>(),
)
};
if fd >= 0 {
return Ok(Some(unsafe {
std::fs::File::from_raw_fd(fd as libc::c_int)
}));
}
let error = io::Error::last_os_error();
match error.raw_os_error() {
// Old kernels and restrictive container profiles fall back to the
// portable descriptor-walk below.
Some(libc::ENOSYS | libc::EINVAL | libc::EPERM) => Ok(None),
_other => Err(error),
}
}
pub(crate) fn open_directory_nofollow(path: &Path) -> io::Result<std::fs::File> {
#[cfg(target_os = "linux")]
if let Some(directory) = openat2_nofollow(
path,
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_NONBLOCK,
0,
)? {
return Ok(directory);
}
let mut directory = starting_directory(path)?;
let mut allow_root_alias = path.is_absolute();
for component in path.components() {
let name = match component {
Component::RootDir | Component::CurDir => continue,
Component::ParentDir => OsStr::new(".."),
Component::Normal(name) => name,
Component::Prefix(_) => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"unsupported path prefix",
));
}
};
// macOS commonly exposes root-owned aliases such as `/tmp` and `/var`.
// Only that first absolute component may be followed. Every component
// below it is resolved relative to an already-open directory fd with
// O_NOFOLLOW, so a writable-tree symlink cannot redirect the open.
directory = open_directory_at(directory.as_raw_fd(), name, allow_root_alias)?;
allow_root_alias = false;
}
Ok(std::fs::File::from(directory))
}
fn open_parent_and_name(path: &Path) -> io::Result<(std::fs::File, CString)> {
let file_name = path
.file_name()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "path has no file name"))?;
let parent = path
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
.unwrap_or_else(|| Path::new("."));
Ok((open_directory_nofollow(parent)?, component_name(file_name)?))
}
pub(crate) fn open_file_nofollow(
path: &Path,
flags: libc::c_int,
mode: libc::mode_t,
) -> io::Result<std::fs::File> {
let flags = flags | libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_NONBLOCK;
#[cfg(target_os = "linux")]
if let Some(file) = openat2_nofollow(path, flags, mode)? {
return Ok(file);
}
let (directory, file_name) = open_parent_and_name(path)?;
let fd = unsafe {
libc::openat(
directory.as_raw_fd(),
file_name.as_ptr(),
flags,
libc::c_uint::from(mode),
)
};
if fd < 0 {
Err(io::Error::last_os_error())
} else {
Ok(unsafe { std::fs::File::from_raw_fd(fd) })
}
}
pub(crate) fn rename_nofollow(source: &Path, destination: &Path) -> io::Result<()> {
let (source_directory, source_name) = open_parent_and_name(source)?;
let (destination_directory, destination_name) = open_parent_and_name(destination)?;
let result = unsafe {
libc::renameat(
source_directory.as_raw_fd(),
source_name.as_ptr(),
destination_directory.as_raw_fd(),
destination_name.as_ptr(),
)
};
if result == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub(crate) fn remove_file_nofollow(path: &Path) -> io::Result<()> {
let (directory, file_name) = open_parent_and_name(path)?;
let result = unsafe { libc::unlinkat(directory.as_raw_fd(), file_name.as_ptr(), 0) };
if result == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
struct DirectoryStream(*mut libc::DIR);
impl Drop for DirectoryStream {
fn drop(&mut self) {
let _ = unsafe { libc::closedir(self.0) };
}
}
fn remove_directory_contents(directory: &std::fs::File) -> io::Result<()> {
let duplicate_fd = unsafe { libc::fcntl(directory.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
if duplicate_fd < 0 {
return Err(io::Error::last_os_error());
}
let stream = unsafe { libc::fdopendir(duplicate_fd) };
if stream.is_null() {
let error = io::Error::last_os_error();
let _ = unsafe { libc::close(duplicate_fd) };
return Err(error);
}
let stream = DirectoryStream(stream);
loop {
let entry = unsafe { libc::readdir(stream.0) };
if entry.is_null() {
break;
}
let name = unsafe { CStr::from_ptr((*entry).d_name.as_ptr()) };
if name.to_bytes() == b"." || name.to_bytes() == b".." {
continue;
}
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
let stat_result = unsafe {
libc::fstatat(
directory.as_raw_fd(),
name.as_ptr(),
stat.as_mut_ptr(),
libc::AT_SYMLINK_NOFOLLOW,
)
};
if stat_result != 0 {
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::NotFound {
continue;
}
return Err(error);
}
let stat = unsafe { stat.assume_init() };
if stat.st_mode & libc::S_IFMT == libc::S_IFDIR {
let child_fd = unsafe {
libc::openat(
directory.as_raw_fd(),
name.as_ptr(),
libc::O_RDONLY
| libc::O_DIRECTORY
| libc::O_CLOEXEC
| libc::O_NOFOLLOW
| libc::O_NONBLOCK,
)
};
if child_fd < 0 {
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::NotFound {
continue;
}
return Err(error);
}
let child = unsafe { std::fs::File::from_raw_fd(child_fd) };
remove_directory_contents(&child)?;
if unsafe { libc::unlinkat(directory.as_raw_fd(), name.as_ptr(), libc::AT_REMOVEDIR) }
!= 0
{
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::NotFound {
return Err(error);
}
}
} else if unsafe { libc::unlinkat(directory.as_raw_fd(), name.as_ptr(), 0) } != 0 {
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::NotFound {
return Err(error);
}
}
}
Ok(())
}
pub(crate) fn remove_dir_all_nofollow(path: &Path) -> io::Result<()> {
let (parent, name) = open_parent_and_name(path)?;
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
if unsafe {
libc::fstatat(
parent.as_raw_fd(),
name.as_ptr(),
stat.as_mut_ptr(),
libc::AT_SYMLINK_NOFOLLOW,
)
} != 0
{
return Err(io::Error::last_os_error());
}
let stat = unsafe { stat.assume_init() };
if stat.st_mode & libc::S_IFMT != libc::S_IFDIR {
return remove_file_nofollow(path);
}
let directory_fd = unsafe {
libc::openat(
parent.as_raw_fd(),
name.as_ptr(),
libc::O_RDONLY
| libc::O_DIRECTORY
| libc::O_CLOEXEC
| libc::O_NOFOLLOW
| libc::O_NONBLOCK,
)
};
if directory_fd < 0 {
return Err(io::Error::last_os_error());
}
let directory = unsafe { std::fs::File::from_raw_fd(directory_fd) };
remove_directory_contents(&directory)?;
if unsafe { libc::unlinkat(parent.as_raw_fd(), name.as_ptr(), libc::AT_REMOVEDIR) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}