use std::{
ffi::{OsStr, OsString},
fs::{self, DirBuilder, File, OpenOptions},
io::{self, ErrorKind, Write},
path::{Component, Path, PathBuf},
sync::atomic::{AtomicU64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
use std::os::unix::{
fs::{DirBuilderExt, MetadataExt, OpenOptionsExt},
io::AsRawFd,
};
static TEMPORARY_FILE_COUNTER: AtomicU64 = AtomicU64::new(0);
#[derive(Debug, Clone, Copy)]
pub struct AnchoredPathOptions {
pub directory_mode: Option<u32>,
pub file_mode: Option<u32>,
}
impl AnchoredPathOptions {
#[must_use]
pub const fn new(directory_mode: Option<u32>, file_mode: Option<u32>) -> Self {
Self {
directory_mode,
file_mode,
}
}
}
pub struct AnchoredTarget {
parent_dir: File,
parent_path: PathBuf,
file_name: OsString,
}
impl AnchoredTarget {
#[must_use]
pub const fn new(parent_dir: File, parent_path: PathBuf, file_name: OsString) -> Self {
Self {
parent_dir,
parent_path,
file_name,
}
}
#[must_use]
pub const fn parent_dir(&self) -> &File {
&self.parent_dir
}
#[must_use]
pub fn file_name(&self) -> &OsStr {
&self.file_name
}
#[must_use]
pub fn final_path(&self) -> PathBuf {
fd_child_path(&self.parent_dir, &self.file_name)
}
#[must_use]
pub fn logical_path(&self) -> PathBuf {
self.parent_path.join(&self.file_name)
}
}
pub fn open_anchored_target(
root: &Path,
path: &Path,
options: AnchoredPathOptions,
invalid_path_error: fn() -> io::Error,
) -> io::Result<AnchoredTarget> {
let parent_path = path.parent().ok_or_else(invalid_path_error)?;
let file_name = path.file_name().ok_or_else(invalid_path_error)?;
let relative_parent = parent_path
.strip_prefix(root)
.map_err(|_error| invalid_path_error())?;
let mut current = match open_directory(root) {
Ok(directory) => directory,
Err(error) if error.kind() == ErrorKind::NotFound => {
create_directory_all(root, options.directory_mode)?;
open_directory(root)?
}
Err(error) => return Err(error),
};
for component in relative_parent.components() {
let Component::Normal(segment) = component else {
return Err(invalid_path_error());
};
current = open_or_create_child_directory(¤t, segment, true, options.directory_mode)?;
}
Ok(AnchoredTarget::new(
current,
parent_path.to_path_buf(),
file_name.to_os_string(),
))
}
pub fn open_directory_chain(
path: &Path,
create_missing: bool,
directory_mode: Option<u32>,
invalid_path_error: fn() -> io::Error,
) -> io::Result<File> {
let mut current = if path.is_absolute() {
open_directory(Path::new("/"))?
} else {
open_directory(Path::new("."))?
};
for component in path.components() {
match component {
Component::RootDir | Component::CurDir => {}
Component::ParentDir => {
current = open_directory(&fd_child_path(¤t, OsStr::new("..")))?;
}
Component::Normal(segment) => {
current = open_or_create_child_directory(
¤t,
segment,
create_missing,
directory_mode,
)?;
}
Component::Prefix(_prefix) => return Err(invalid_path_error()),
}
}
Ok(current)
}
pub fn open_or_create_child_directory(
parent: &File,
segment: &OsStr,
create_missing: bool,
directory_mode: Option<u32>,
) -> io::Result<File> {
let child_path = fd_child_path(parent, segment);
match open_directory(&child_path) {
Ok(directory) => Ok(directory),
Err(error) if error.kind() == ErrorKind::NotFound && create_missing => {
match create_directory(&child_path, directory_mode) {
Ok(()) => {}
Err(create_error) if create_error.kind() == ErrorKind::AlreadyExists => {}
Err(create_error) => return Err(create_error),
}
open_directory(&child_path)
}
Err(error) => Err(error),
}
}
pub fn write_anchored_temporary_file(
anchored: &AnchoredTarget,
bytes: &[u8],
file_mode: Option<u32>,
) -> io::Result<PathBuf> {
loop {
let temporary = fd_child_path(
anchored.parent_dir(),
&temporary_file_name(anchored.file_name()),
);
match open_new_file(&temporary, file_mode) {
Ok(mut file) => {
file.write_all(bytes)?;
file.flush()?;
return Ok(temporary);
}
Err(error) if error.kind() == ErrorKind::AlreadyExists => {}
Err(error) => return Err(error),
}
}
}
#[must_use]
pub fn temporary_file_name(file_name: &OsStr) -> OsString {
let counter = TEMPORARY_FILE_COUNTER.fetch_add(1, Ordering::Relaxed);
let unix_nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0_u128, |duration| duration.as_nanos());
let mut name = file_name.to_os_string();
name.push(format!(".tmp-{unix_nanos}-{counter}"));
name
}
pub fn ensure_parent_path_matches_anchor(
anchored: &AnchoredTarget,
changed_message: &'static str,
) -> io::Result<()> {
let anchored_metadata = anchored.parent_dir.metadata()?;
let current_metadata = fs::symlink_metadata(&anchored.parent_path)?;
if current_metadata.file_type().is_symlink() || !current_metadata.is_dir() {
return Err(io::Error::new(ErrorKind::InvalidData, changed_message));
}
if anchored_metadata.dev() != current_metadata.dev()
|| anchored_metadata.ino() != current_metadata.ino()
{
return Err(io::Error::new(ErrorKind::InvalidData, changed_message));
}
Ok(())
}
pub fn open_directory(path: &Path) -> io::Result<File> {
let mut options = OpenOptions::new();
options.read(true);
#[cfg(not(target_os = "macos"))]
options.custom_flags(libc::O_DIRECTORY | libc::O_NOFOLLOW);
#[cfg(target_os = "macos")]
options.custom_flags(libc::O_DIRECTORY);
options.open(path)
}
pub fn open_new_file(path: &Path, file_mode: Option<u32>) -> io::Result<File> {
let mut options = OpenOptions::new();
options
.write(true)
.create_new(true)
.custom_flags(libc::O_NOFOLLOW);
if let Some(mode) = file_mode {
options.mode(mode);
}
options.open(path)
}
pub fn create_directory(path: &Path, directory_mode: Option<u32>) -> io::Result<()> {
let mut builder = DirBuilder::new();
if let Some(mode) = directory_mode {
builder.mode(mode);
}
builder.create(path)
}
pub fn create_directory_all(path: &Path, directory_mode: Option<u32>) -> io::Result<()> {
let mut builder = DirBuilder::new();
builder.recursive(true);
if let Some(mode) = directory_mode {
builder.mode(mode);
}
builder.create(path)
}
#[must_use]
pub fn fd_child_path(directory: &File, child: &OsStr) -> PathBuf {
let mut path = fd_base_path(directory);
path.push(child);
path
}
#[cfg(target_os = "macos")]
pub fn rename_at(parent: &File, old_name: &OsStr, new_name: &OsStr) -> io::Result<()> {
use std::ffi::CString;
use std::os::unix::io::AsRawFd;
let old_cstr = CString::new(old_name.as_encoded_bytes()).map_err(|e| {
io::Error::new(
ErrorKind::InvalidInput,
format!("name contains null byte: {e}"),
)
})?;
let new_cstr = CString::new(new_name.as_encoded_bytes()).map_err(|e| {
io::Error::new(
ErrorKind::InvalidInput,
format!("name contains null byte: {e}"),
)
})?;
let result = unsafe {
libc::renameat(
parent.as_raw_fd(),
old_cstr.as_ptr(),
parent.as_raw_fd(),
new_cstr.as_ptr(),
)
};
if result == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(not(target_os = "macos"))]
pub fn rename_at(parent: &File, old_name: &OsStr, new_name: &OsStr) -> io::Result<()> {
let old_path = fd_child_path(parent, old_name);
let new_path = fd_child_path(parent, new_name);
std::fs::rename(old_path, new_path)
}
#[cfg(target_os = "macos")]
pub fn remove_at(parent: &File, name: &OsStr) -> io::Result<()> {
use std::ffi::CString;
use std::os::unix::io::AsRawFd;
let cstr = CString::new(name.as_encoded_bytes()).map_err(|e| {
io::Error::new(
ErrorKind::InvalidInput,
format!("name contains null byte: {e}"),
)
})?;
let result = unsafe { libc::unlinkat(parent.as_raw_fd(), cstr.as_ptr(), 0) };
if result == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(not(target_os = "macos"))]
pub fn remove_at(parent: &File, name: &OsStr) -> io::Result<()> {
let path = fd_child_path(parent, name);
std::fs::remove_file(path)
}
pub fn remove_if_present(path: &Path) -> io::Result<()> {
match fs::remove_file(path) {
Ok(()) => Ok(()),
Err(error) if error.kind() == ErrorKind::NotFound => Ok(()),
Err(error) => Err(error),
}
}
#[cfg(target_os = "macos")]
fn macos_fd_real_path(fd: std::os::unix::io::RawFd) -> PathBuf {
let mut buf = [0i8; 1024];
let result = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_mut_ptr()) };
if result < 0 {
return PathBuf::from(format!("/dev/fd/{fd}"));
}
let c_str = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) };
PathBuf::from(c_str.to_string_lossy().as_ref())
}
fn fd_base_path(directory: &File) -> PathBuf {
#[cfg(target_os = "linux")]
{
PathBuf::from(format!("/proc/self/fd/{}", directory.as_raw_fd()))
}
#[cfg(target_os = "macos")]
{
macos_fd_real_path(directory.as_raw_fd())
}
#[cfg(not(target_os = "linux"))]
#[cfg(not(target_os = "macos"))]
{
PathBuf::from(format!("/dev/fd/{}", directory.as_raw_fd()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::os::unix::ffi::OsStrExt;
use tempfile::tempdir;
fn invalid_path_error() -> io::Error {
io::Error::new(ErrorKind::InvalidInput, "invalid path")
}
#[test]
fn anchored_path_options_with_modes() {
let opts = AnchoredPathOptions::new(Some(0o755), Some(0o644));
assert_eq!(opts.directory_mode, Some(0o755));
assert_eq!(opts.file_mode, Some(0o644));
}
#[test]
fn anchored_path_options_none() {
let opts = AnchoredPathOptions::new(None, None);
assert!(opts.directory_mode.is_none());
assert!(opts.file_mode.is_none());
}
#[test]
fn anchored_target_accessors() {
let dir = tempdir().unwrap();
let parent_path = dir.path().join("sub");
fs::create_dir(&parent_path).unwrap();
let file = fs::OpenOptions::new()
.read(true)
.open(&parent_path)
.unwrap();
let file_name: OsString = "file.txt".into();
let target = AnchoredTarget::new(file, parent_path.clone(), file_name.clone());
assert_eq!(target.file_name(), file_name.as_os_str());
assert_eq!(target.parent_path, parent_path);
}
#[test]
#[allow(clippy::redundant_clone)]
fn anchored_target_final_path() {
let dir = tempdir().unwrap();
let parent_path = dir.path().to_path_buf();
let file = fs::OpenOptions::new()
.read(true)
.open(&parent_path)
.unwrap();
let file_name: OsString = "output.bin".into();
let target = AnchoredTarget::new(file, parent_path, file_name.clone());
let final_path = target.final_path();
assert_eq!(final_path.file_name().unwrap(), file_name.as_os_str());
}
#[test]
#[allow(clippy::redundant_clone)]
fn anchored_target_logical_path() {
let dir = tempdir().unwrap();
let parent_path = dir.path().to_path_buf();
let file = fs::OpenOptions::new()
.read(true)
.open(&parent_path)
.unwrap();
let file_name: OsString = "data.txt".into();
let target = AnchoredTarget::new(file, parent_path.clone(), file_name);
assert_eq!(target.logical_path(), parent_path.join("data.txt"));
}
#[test]
fn open_anchored_target_creates_missing_root() {
let dir = tempdir().unwrap();
let root = dir.path().join("nonexistent-root");
assert!(!root.exists());
let path = root.join("file.txt");
let result = open_anchored_target(
&root,
&path,
AnchoredPathOptions::new(None, None),
invalid_path_error,
);
assert!(result.is_ok());
assert!(root.exists());
}
#[test]
fn open_anchored_target_no_parent_returns_invalid_path_error() {
let dir = tempdir().unwrap();
let path = PathBuf::from("just_a_file.txt");
let result = open_anchored_target(
dir.path(),
&path,
AnchoredPathOptions::new(None, None),
invalid_path_error,
);
assert!(result.is_err());
}
#[test]
fn open_anchored_target_with_mode_creates_directories() {
let dir = tempdir().unwrap();
let root = dir.path().join("moded-root");
let path = root.join("sub/dir/file.txt");
let result = open_anchored_target(
&root,
&path,
AnchoredPathOptions::new(Some(0o755), Some(0o644)),
invalid_path_error,
);
assert!(result.is_ok());
assert!(root.join("sub/dir").exists());
}
#[test]
fn open_anchored_target_nested_path() {
let root = tempdir().unwrap();
let path = root.path().join("sub/dir/file.txt");
let result = open_anchored_target(
root.path(),
&path,
AnchoredPathOptions::new(None, None),
invalid_path_error,
);
assert!(result.is_ok());
let target = result.unwrap();
assert_eq!(target.file_name(), OsStr::new("file.txt"));
assert_eq!(target.parent_path, root.path().join("sub/dir"));
}
#[test]
fn open_anchored_target_path_outside_root() {
let root = tempdir().unwrap();
let path = PathBuf::from("/tmp/definitely-not-inside-root.txt");
let result = open_anchored_target(
root.path(),
&path,
AnchoredPathOptions::new(None, None),
invalid_path_error,
);
assert!(result.is_err());
}
#[test]
fn open_anchored_target_traversal_rejected() {
let root = tempdir().unwrap();
let path = root.path().join("../escape.txt");
let result = open_anchored_target(
root.path(),
&path,
AnchoredPathOptions::new(None, None),
invalid_path_error,
);
assert!(result.is_err());
}
#[test]
fn open_directory_chain_existing() {
let dir = tempdir().unwrap();
let sub = dir.path().join("existing");
fs::create_dir(&sub).unwrap();
let result = open_directory_chain(&sub, false, None, invalid_path_error);
assert!(result.is_ok());
}
#[test]
fn open_directory_chain_create_missing() {
let dir = tempdir().unwrap();
let sub = dir.path().join("new");
let result = open_directory_chain(&sub, true, None, invalid_path_error);
assert!(result.is_ok());
assert!(sub.exists());
}
#[test]
fn open_directory_chain_no_create_missing() {
let dir = tempdir().unwrap();
let sub = dir.path().join("missing");
let result = open_directory_chain(&sub, false, None, invalid_path_error);
assert!(result.is_err());
}
#[cfg(unix)]
#[test]
fn open_directory_chain_rejects_symlink_in_path() {
use std::os::unix::fs::symlink;
let dir = tempdir().unwrap();
let target = dir.path().join("target");
fs::create_dir(&target).unwrap();
let link = dir.path().join("link");
symlink(&target, &link).unwrap();
let path = link.join("subdir");
let result = open_directory_chain(&path, true, None, invalid_path_error);
assert!(result.is_err());
}
#[test]
fn open_directory_chain_with_parent_dir_component() {
let dir = tempdir().unwrap();
let sub = dir.path().join("sub");
fs::create_dir(&sub).unwrap();
let path = sub.join("..");
let result = open_directory_chain(&path, false, None, invalid_path_error);
assert!(result.is_ok());
}
#[test]
fn open_directory_chain_absolute_path() {
let dir = tempdir().unwrap();
let result = open_directory_chain(dir.path(), false, None, invalid_path_error);
assert!(result.is_ok());
}
#[test]
fn open_directory_chain_with_file_mode() {
let dir = tempdir().unwrap();
let sub = dir.path().join("new-dir");
let result = open_directory_chain(&sub, true, Some(0o755), invalid_path_error);
assert!(result.is_ok());
assert!(sub.exists());
}
#[test]
fn open_or_create_child_existing() {
let dir = tempdir().unwrap();
let child_name: &OsStr = "child".as_ref();
fs::create_dir(dir.path().join("child")).unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let result = open_or_create_child_directory(&parent, child_name, false, None);
assert!(result.is_ok());
}
#[test]
fn open_or_create_child_create_missing() {
let dir = tempdir().unwrap();
let child_name: &OsStr = "newchild".as_ref();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let result = open_or_create_child_directory(&parent, child_name, true, None);
assert!(result.is_ok());
assert!(dir.path().join("newchild").exists());
}
#[test]
fn open_or_create_child_no_create_missing() {
let dir = tempdir().unwrap();
let child_name: &OsStr = "nochild".as_ref();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let result = open_or_create_child_directory(&parent, child_name, false, None);
assert!(result.is_err());
}
#[test]
fn open_or_create_child_race_condition_already_exists() {
let dir = tempdir().unwrap();
let child_name: &OsStr = "racechild".as_ref();
fs::create_dir(dir.path().join("racechild")).unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let result = open_or_create_child_directory(&parent, child_name, true, None);
assert!(result.is_ok());
}
#[test]
fn write_anchored_temporary_file_basic() {
let dir = tempdir().unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let target = AnchoredTarget::new(parent, dir.path().to_path_buf(), "target.txt".into());
let payload = b"hello world";
let result = write_anchored_temporary_file(&target, payload, None);
assert!(result.is_ok());
let tmp_path = result.unwrap();
let tmp_name = tmp_path.file_name().unwrap().to_string_lossy();
assert!(
tmp_name.starts_with("target.txt.tmp-"),
"unexpected temp name: {tmp_name}"
);
let contents = fs::read(&tmp_path).unwrap();
assert_eq!(contents, payload);
}
#[test]
fn write_anchored_temporary_file_with_file_mode() {
let dir = tempdir().unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let target = AnchoredTarget::new(parent, dir.path().to_path_buf(), "moded.txt".into());
let payload = b"mode test";
let result = write_anchored_temporary_file(&target, payload, Some(0o600));
assert!(result.is_ok());
let tmp_path = result.unwrap();
assert!(tmp_path.exists());
let contents = fs::read(&tmp_path).unwrap();
assert_eq!(contents, payload);
}
#[test]
fn temporary_file_name_has_tmp_suffix() {
let name = temporary_file_name(OsStr::new("file.txt"));
let name_str = name.to_string_lossy();
assert!(name_str.starts_with("file.txt.tmp-"));
}
#[test]
fn temporary_file_name_unique() {
let a = temporary_file_name(OsStr::new("file.txt"));
let b = temporary_file_name(OsStr::new("file.txt"));
assert_ne!(a, b);
}
#[test]
fn ensure_parent_path_matches_anchor_ok() {
let dir = tempdir().unwrap();
let parent_path = dir.path().to_path_buf();
let file = fs::OpenOptions::new()
.read(true)
.open(&parent_path)
.unwrap();
let target = AnchoredTarget::new(file, parent_path, "f.txt".into());
assert!(ensure_parent_path_matches_anchor(&target, "changed").is_ok());
}
#[cfg(unix)]
#[test]
fn ensure_parent_path_matches_anchor_symlink_replaced() {
use std::os::unix::fs::symlink;
let dir = tempdir().unwrap();
let real_dir = dir.path().join("real");
fs::create_dir(&real_dir).unwrap();
let file = fs::OpenOptions::new().read(true).open(&real_dir).unwrap();
let target = AnchoredTarget::new(file, real_dir.clone(), "f.txt".into());
fs::remove_dir(&real_dir).unwrap();
symlink("/tmp", &real_dir).unwrap();
let result = ensure_parent_path_matches_anchor(&target, "symlink detected");
assert!(result.is_err());
}
#[cfg(unix)]
#[test]
fn ensure_parent_path_matches_anchor_renamed() {
let dir = tempdir().unwrap();
let real_dir = dir.path().join("real");
fs::create_dir(&real_dir).unwrap();
let file = fs::OpenOptions::new().read(true).open(&real_dir).unwrap();
let target = AnchoredTarget::new(file, real_dir.clone(), "f.txt".into());
let new_name = dir.path().join("renamed");
fs::rename(&real_dir, &new_name).unwrap();
let result = ensure_parent_path_matches_anchor(&target, "renamed detected");
assert!(result.is_err());
}
#[test]
fn open_directory_existing() {
let dir = tempdir().unwrap();
let result = open_directory(dir.path());
assert!(result.is_ok());
}
#[test]
fn open_directory_not_a_directory() {
let dir = tempdir().unwrap();
let file_path = dir.path().join("file.txt");
fs::write(&file_path, "hello").unwrap();
let result = open_directory(&file_path);
assert!(result.is_err());
}
#[test]
fn open_directory_nonexistent() {
let dir = tempdir().unwrap();
let missing = dir.path().join("nope");
let result = open_directory(&missing);
assert!(result.is_err());
}
#[test]
fn open_directory_chain_rejects_prefix_component() {
#[cfg(not(unix))]
{
let dir = tempdir().unwrap();
let path = std::path::Path::new(r"\\?\C:\");
let result = open_directory_chain(path, false, None, invalid_path_error);
assert!(result.is_err());
}
#[cfg(unix)]
{
let _ =
open_directory_chain(std::path::Path::new("/"), false, None, invalid_path_error);
}
}
#[test]
fn open_or_create_child_directory_create_error() {
let dir = tempdir().unwrap();
let parent = std::fs::OpenOptions::new()
.read(true)
.open(dir.path())
.unwrap();
let child_name: &std::ffi::OsStr = "child_name".as_ref();
std::fs::write(dir.path().join("child_name"), b"data").unwrap();
let result = open_or_create_child_directory(&parent, child_name, true, None);
assert!(result.is_err());
}
#[test]
fn write_anchored_temporary_file_race_loop() {
let dir = tempdir().unwrap();
let parent = std::fs::OpenOptions::new()
.read(true)
.open(dir.path())
.unwrap();
let target = AnchoredTarget::new(parent, dir.path().to_path_buf(), "target.txt".into());
let payload = b"race test";
let first_tmp = super::fd_child_path(
target.parent_dir(),
&super::temporary_file_name(target.file_name()),
);
std::fs::write(&first_tmp, b"stale").unwrap();
let result = write_anchored_temporary_file(&target, payload, None);
assert!(result.is_ok());
let tmp_path = result.unwrap();
let contents = std::fs::read(&tmp_path).unwrap();
assert_eq!(contents, payload);
}
#[test]
fn remove_if_present_io_error() {
let dir = tempdir().unwrap();
let dir_path = dir.path().join("a_directory");
std::fs::create_dir(&dir_path).unwrap();
let result = remove_if_present(&dir_path);
assert!(result.is_err() || result.is_ok());
}
#[cfg(unix)]
#[test]
fn open_directory_symlink_rejected() {
use std::os::unix::fs::symlink;
let dir = tempdir().unwrap();
let real_dir = dir.path().join("real");
fs::create_dir(&real_dir).unwrap();
let link_path = dir.path().join("link");
symlink(&real_dir, &link_path).unwrap();
#[cfg(not(target_os = "macos"))]
{
let result = open_directory(&link_path);
assert!(result.is_err());
}
#[cfg(target_os = "macos")]
{
let result = open_directory(&link_path);
assert!(result.is_ok());
}
}
#[test]
fn open_new_file_creates() {
let dir = tempdir().unwrap();
let path = dir.path().join("new.txt");
let result = open_new_file(&path, None);
assert!(result.is_ok());
assert!(path.exists());
}
#[test]
fn open_new_file_already_exists() {
let dir = tempdir().unwrap();
let path = dir.path().join("existing.txt");
fs::write(&path, "data").unwrap();
let result = open_new_file(&path, None);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::AlreadyExists);
}
#[cfg(unix)]
#[test]
fn open_new_file_via_symlink_rejected() {
use std::os::unix::fs::symlink;
let dir = tempdir().unwrap();
let real_file = dir.path().join("real.txt");
fs::write(&real_file, "data").unwrap();
let link_path = dir.path().join("link.txt");
symlink(&real_file, &link_path).unwrap();
let result = open_new_file(&link_path, None);
assert!(result.is_err());
}
#[test]
fn create_directory_ok() {
let dir = tempdir().unwrap();
let new_dir = dir.path().join("created");
assert!(create_directory(&new_dir, None).is_ok());
assert!(new_dir.is_dir());
}
#[test]
fn create_directory_already_exists() {
let dir = tempdir().unwrap();
let new_dir = dir.path().join("exists");
fs::create_dir(&new_dir).unwrap();
let result = create_directory(&new_dir, None);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::AlreadyExists);
}
#[test]
fn create_directory_with_mode() {
let dir = tempdir().unwrap();
let new_dir = dir.path().join("with-mode");
let result = create_directory(&new_dir, Some(0o755));
assert!(result.is_ok());
assert!(new_dir.is_dir());
}
#[test]
fn create_directory_all_nested() {
let dir = tempdir().unwrap();
let nested = dir.path().join("a/b/c");
assert!(create_directory_all(&nested, None).is_ok());
assert!(nested.is_dir());
}
#[test]
fn create_directory_all_idempotent() {
let dir = tempdir().unwrap();
let nested = dir.path().join("x/y");
assert!(create_directory_all(&nested, None).is_ok());
assert!(create_directory_all(&nested, None).is_ok());
}
#[test]
fn create_directory_all_with_mode() {
let dir = tempdir().unwrap();
let nested = dir.path().join("p/q/r");
assert!(create_directory_all(&nested, Some(0o755)).is_ok());
assert!(nested.is_dir());
}
#[test]
fn open_new_file_with_file_mode() {
let dir = tempdir().unwrap();
let path = dir.path().join("moded-new.txt");
let result = open_new_file(&path, Some(0o600));
assert!(result.is_ok());
assert!(path.exists());
}
#[test]
fn fd_child_path_beneath_parent() {
let dir = tempdir().unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let child = fd_child_path(&parent, OsStr::new("file.txt"));
assert_eq!(child.file_name().unwrap(), OsStr::new("file.txt"));
}
#[test]
fn rename_at_ok() {
let dir = tempdir().unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
fs::write(dir.path().join("old.txt"), "data").unwrap();
assert!(rename_at(&parent, OsStr::new("old.txt"), OsStr::new("new.txt")).is_ok());
assert!(dir.path().join("new.txt").exists());
assert!(!dir.path().join("old.txt").exists());
}
#[test]
fn rename_at_null_byte_in_name() {
let dir = tempdir().unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let bad_name = OsStr::from_bytes(b"old\x00txt");
let result = rename_at(&parent, bad_name, OsStr::new("new.txt"));
assert!(result.is_err());
}
#[test]
fn rename_at_nonexistent_source() {
let dir = tempdir().unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let result = rename_at(
&parent,
OsStr::new("nonexistent.txt"),
OsStr::new("new.txt"),
);
assert!(result.is_err());
}
#[test]
fn remove_at_existing() {
let dir = tempdir().unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
fs::write(dir.path().join("file.txt"), "data").unwrap();
assert!(remove_at(&parent, OsStr::new("file.txt")).is_ok());
assert!(!dir.path().join("file.txt").exists());
}
#[test]
fn remove_at_nonexistent() {
let dir = tempdir().unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let result = remove_at(&parent, OsStr::new("nope.txt"));
assert!(result.is_err());
}
#[test]
fn remove_at_null_byte_in_name() {
let dir = tempdir().unwrap();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
let bad_name = OsStr::from_bytes(b"bad\x00file");
let result = remove_at(&parent, bad_name);
assert!(result.is_err());
}
#[test]
fn remove_if_present_existing() {
let dir = tempdir().unwrap();
let path = dir.path().join("file.txt");
fs::write(&path, "data").unwrap();
assert!(remove_if_present(&path).is_ok());
assert!(!path.exists());
}
#[test]
fn remove_if_present_absent() {
let dir = tempdir().unwrap();
let path = dir.path().join("nope.txt");
assert!(remove_if_present(&path).is_ok());
}
#[test]
fn open_anchored_target_root_open_error() {
let dir = tempdir().unwrap();
let file_path = dir.path().join("a_file");
std::fs::write(&file_path, b"not a directory").unwrap();
let path = file_path.join("child/key.txt");
let result = open_anchored_target(
&file_path,
&path,
AnchoredPathOptions::new(None, None),
invalid_path_error,
);
assert!(result.is_err());
}
#[test]
fn open_directory_chain_relative_path() {
let dir = tempdir().unwrap();
let sub = dir.path().join("rel");
std::fs::create_dir(&sub).unwrap();
let result =
open_directory_chain(std::path::Path::new("."), false, None, invalid_path_error);
assert!(result.is_ok());
}
#[test]
fn open_or_create_child_directory_create_already_exists() {
let dir = tempdir().unwrap();
let child_name: &OsStr = "already".as_ref();
let parent = fs::OpenOptions::new().read(true).open(dir.path()).unwrap();
std::fs::create_dir(dir.path().join("already")).unwrap();
let result = open_or_create_child_directory(&parent, child_name, true, None);
assert!(
result.is_ok(),
"should handle concurrent directory creation"
);
}
#[test]
fn open_directory_chain_rejects_prefix_via_invalid_path() {
let dir = tempdir().unwrap();
let result = open_directory_chain(dir.path(), false, None, invalid_path_error);
assert!(result.is_ok());
}
#[test]
fn open_anchored_target_traversal_in_relative_parent_segment() {
let dir = tempdir().unwrap();
let root = dir.path().join("root");
std::fs::create_dir(&root).unwrap();
let sub = root.join("sub");
std::fs::create_dir(&sub).unwrap();
let path = root.join("sub/../escape.txt");
let result = open_anchored_target(
&root,
&path,
AnchoredPathOptions::new(None, None),
invalid_path_error,
);
assert!(
result.is_err(),
"traversal in parent path should be rejected"
);
}
#[cfg(unix)]
#[test]
fn ensure_parent_path_matches_anchor_parent_is_symlink() {
use std::os::unix::fs::symlink;
let dir = tempdir().unwrap();
let real_dir = dir.path().join("real");
fs::create_dir(&real_dir).unwrap();
let file = fs::OpenOptions::new().read(true).open(&real_dir).unwrap();
let target = AnchoredTarget::new(file, real_dir.clone(), "f.txt".into());
fs::remove_dir(&real_dir).unwrap();
symlink("/tmp", &real_dir).unwrap();
let result = ensure_parent_path_matches_anchor(&target, "parent replaced with symlink");
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
}
}