use std::path::Path;
use crate::platform::error::PlatformError;
pub const EPHEMERAL_DIRS: &[&str] = &["run", "tmp"];
pub fn prepare_ephemeral_tier(data_root: &Path) -> Result<(), PlatformError> {
for name in EPHEMERAL_DIRS {
let dir = data_root.join(name);
match std::fs::symlink_metadata(&dir) {
Ok(meta) if meta.file_type().is_symlink() => {
return Err(PlatformError::new(
"PLATFORM_EPHEMERAL_SYMLINK",
format!(
"'{}' is a symlink; the EPHEMERAL tier is wiped on every boot \
and wiping through a symlink would delete outside the data \
root — replace it with a real directory",
dir.display()
),
));
}
Ok(meta) if meta.is_dir() => wipe_directory_contents(&dir)?,
Ok(_) => {
std::fs::remove_file(&dir).map_err(|error| {
PlatformError::new(
"PLATFORM_EPHEMERAL_WIPE",
format!("failed removing stray file '{}': {error}", dir.display()),
)
})?;
std::fs::create_dir_all(&dir)?;
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
std::fs::create_dir_all(&dir)?;
}
Err(error) => {
return Err(PlatformError::new(
"PLATFORM_EPHEMERAL_WIPE",
format!("failed inspecting '{}': {error}", dir.display()),
));
}
}
}
Ok(())
}
fn wipe_directory_contents(dir: &Path) -> Result<(), PlatformError> {
let entries = std::fs::read_dir(dir).map_err(|error| {
PlatformError::new(
"PLATFORM_EPHEMERAL_WIPE",
format!("failed reading '{}': {error}", dir.display()),
)
})?;
for entry in entries {
let entry = match entry {
Ok(entry) => entry,
Err(error) => {
eprintln!(
"warning: ephemeral wipe skipped an entry in '{}': {error}",
dir.display()
);
continue;
}
};
let path = entry.path();
let result = match entry.file_type() {
Ok(file_type) if file_type.is_dir() => std::fs::remove_dir_all(&path),
Ok(_) => std::fs::remove_file(&path),
Err(error) => Err(error),
};
if let Err(error) = result {
eprintln!(
"warning: ephemeral wipe left '{}' behind: {error}",
path.display()
);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fresh_root_gets_both_directories() {
let root = tempfile::tempdir().expect("root");
prepare_ephemeral_tier(root.path()).expect("prepare");
assert!(root.path().join("run").is_dir());
assert!(root.path().join("tmp").is_dir());
}
#[test]
fn the_wipe_removes_contents_but_keeps_the_directories() {
let root = tempfile::tempdir().expect("root");
let run = root.path().join("run");
let tmp = root.path().join("tmp");
std::fs::create_dir_all(run.join("locks")).unwrap();
std::fs::write(run.join("server.pid"), b"123").unwrap();
std::fs::create_dir_all(tmp.join("transfer/export-bundle-x")).unwrap();
std::fs::write(tmp.join("transfer/export-bundle-x/manifest.json"), b"{}").unwrap();
std::fs::write(tmp.join("chunk.bin"), b"bytes").unwrap();
prepare_ephemeral_tier(root.path()).expect("prepare");
assert!(run.is_dir() && tmp.is_dir());
assert_eq!(std::fs::read_dir(&run).unwrap().count(), 0);
assert_eq!(std::fs::read_dir(&tmp).unwrap().count(), 0);
}
#[cfg(unix)]
#[test]
fn a_symlinked_tier_directory_refuses() {
let root = tempfile::tempdir().expect("root");
let elsewhere = tempfile::tempdir().expect("elsewhere");
let precious = elsewhere.path().join("precious.txt");
std::fs::write(&precious, b"keep me").unwrap();
std::os::unix::fs::symlink(elsewhere.path(), root.path().join("tmp")).unwrap();
let error = prepare_ephemeral_tier(root.path()).expect_err("must refuse");
assert_eq!(error.code, "PLATFORM_EPHEMERAL_SYMLINK");
assert_eq!(std::fs::read(&precious).unwrap(), b"keep me");
}
#[cfg(unix)]
#[test]
fn a_symlink_entry_inside_the_tier_is_removed_without_following_it() {
let root = tempfile::tempdir().expect("root");
let elsewhere = tempfile::tempdir().expect("elsewhere");
let precious = elsewhere.path().join("precious.txt");
std::fs::write(&precious, b"keep me").unwrap();
let tmp = root.path().join("tmp");
std::fs::create_dir_all(&tmp).unwrap();
std::os::unix::fs::symlink(elsewhere.path(), tmp.join("escape")).unwrap();
prepare_ephemeral_tier(root.path()).expect("prepare");
assert_eq!(std::fs::read_dir(&tmp).unwrap().count(), 0);
assert_eq!(std::fs::read(&precious).unwrap(), b"keep me");
}
#[test]
fn a_plain_file_on_the_tier_name_is_replaced_by_the_directory() {
let root = tempfile::tempdir().expect("root");
std::fs::write(root.path().join("run"), b"not a dir").unwrap();
prepare_ephemeral_tier(root.path()).expect("prepare");
assert!(root.path().join("run").is_dir());
}
}