use std::os::unix::fs::PermissionsExt as _;
use std::path::Path;
use super::{HardenError, HardenProgress, harden_store_tree};
type TestResult = Result<(), Box<dyn std::error::Error>>;
fn mode(path: &Path) -> std::io::Result<u32> {
Ok(std::fs::metadata(path)?.permissions().mode() & 0o777)
}
#[test]
fn a_loosened_store_is_brought_to_private_modes_and_narrated() -> TestResult {
let sandbox = crate::test_support::private_tempdir()?;
let data_dir = sandbox.path().join("data");
std::fs::create_dir(&data_dir)?;
for shard in 0..3 {
let shard_dir = data_dir.join(format!("shard-{shard}"));
std::fs::create_dir_all(shard_dir.join("store").join("ab"))?;
std::fs::write(shard_dir.join("shard.wal"), b"wal")?;
std::fs::write(shard_dir.join("store").join("ab").join("node"), b"node")?;
std::fs::set_permissions(&shard_dir, std::fs::Permissions::from_mode(0o755))?;
std::fs::set_permissions(
shard_dir.join("shard.wal"),
std::fs::Permissions::from_mode(0o644),
)?;
std::fs::set_permissions(
shard_dir.join("store").join("ab").join("node"),
std::fs::Permissions::from_mode(0o664),
)?;
}
std::fs::write(data_dir.join("config.json"), b"{}")?;
std::fs::set_permissions(
data_dir.join("config.json"),
std::fs::Permissions::from_mode(0o644),
)?;
let mut narrated: Vec<HardenProgress> = Vec::new();
let report = harden_store_tree(&data_dir, |progress| narrated.push(progress.clone()))?;
assert_eq!(report.files, 7, "3 WALs + 3 nodes + config.json");
assert_eq!(
report.directories,
1 + 3 * 3,
"root + (shard, store, ab) × 3"
);
assert_eq!(mode(&data_dir)?, 0o700);
for shard in 0..3 {
let shard_dir = data_dir.join(format!("shard-{shard}"));
assert_eq!(mode(&shard_dir)?, 0o700);
assert_eq!(mode(&shard_dir.join("store"))?, 0o700);
assert_eq!(mode(&shard_dir.join("shard.wal"))?, 0o600);
assert_eq!(
mode(&shard_dir.join("store").join("ab").join("node"))?,
0o600
);
}
assert_eq!(mode(&data_dir.join("config.json"))?, 0o600);
let entries: Vec<&str> = narrated.iter().map(|step| step.entry.as_str()).collect();
assert_eq!(
entries,
["config.json", "shard-0", "shard-1", "shard-2"],
"one line per top-level entry, in name order"
);
assert_eq!(narrated[0].files, 1);
assert_eq!(narrated[1].files, 2);
assert_eq!(
narrated[3].total_files, 7,
"the running total ends at the report's count"
);
Ok(())
}
#[test]
fn a_store_whose_writer_lock_is_held_is_refused() -> TestResult {
let sandbox = crate::test_support::private_tempdir()?;
let data_dir = sandbox.path().join("data");
std::fs::create_dir(&data_dir)?;
let lock_path = data_dir.join("writer.lock");
let holder = std::fs::File::create(&lock_path)?;
holder.lock()?;
let error = harden_store_tree(&data_dir, |_| {})
.err()
.ok_or("a held store must refuse")?;
let sentence = error.to_string();
match error {
HardenError::WriterLockHeld {
data_dir: named,
lock_path: named_lock,
} => {
assert_eq!(named, data_dir);
assert_eq!(named_lock, lock_path);
}
other => return Err(format!("wrong refusal: {other}").into()),
}
assert!(
sentence.contains("aion stop"),
"the refusal tells the operator what to do: {sentence}"
);
drop(holder);
harden_store_tree(&data_dir, |_| {})?;
Ok(())
}
#[test]
fn a_symbolic_link_in_the_tree_is_refused_by_entry() -> TestResult {
let sandbox = crate::test_support::private_tempdir()?;
let data_dir = sandbox.path().join("data");
std::fs::create_dir_all(data_dir.join("shard-0"))?;
std::os::unix::fs::symlink(sandbox.path(), data_dir.join("shard-0").join("escape"))?;
let error = harden_store_tree(&data_dir, |_| {})
.err()
.ok_or("a link must refuse")?;
match error {
HardenError::Walk { entry, error, .. } => {
assert_eq!(entry, "shard-0");
assert!(error.to_string().contains("symbolic link"), "{error}");
}
other => return Err(format!("wrong refusal: {other}").into()),
}
Ok(())
}