use std::path::{Component, Path, PathBuf};
use crate::murmur_home::HomeUnusable;
use crate::protected_paths::canonicalize_existing_prefix;
use crate::workdir_fs::WorkdirRoot;
pub const RECORDS_DIR_NAME: &str = "records";
pub const RECORDS_DIR_ENV: &str = "MURMUR_RECORDS_DIR";
pub const HOOKS_SUBDIR: &str = "hooks";
pub const HOME_SUBDIR: &str = "home";
pub const RESULTS_SUBDIR: &str = "out";
pub const RESULT_FILE: &str = "out/result.txt";
const PRIVATE_DIR_MODE: u32 = crate::murmur_home::MURMUR_HOME_DIR_MODE;
pub fn records_dir_root() -> Result<PathBuf, HomeUnusable> {
Ok(crate::murmur_home::usable_murmur_home()?.join(RECORDS_DIR_NAME))
}
pub fn records_group(workdir_root: &Path) -> Result<PathBuf, HomeUnusable> {
Ok(records_group_under(
&crate::murmur_home::usable_murmur_home()?,
workdir_root,
))
}
pub fn records_group_under(murmur_home: &Path, workdir_root: &Path) -> PathBuf {
murmur_home
.join(RECORDS_DIR_NAME)
.join(mirrored(workdir_root))
}
fn mirrored(workdir_root: &Path) -> PathBuf {
let absolute = std::path::absolute(workdir_root).unwrap_or_else(|_| workdir_root.to_path_buf());
let canonical = canonicalize_existing_prefix(&absolute);
let mut mirror = PathBuf::new();
for component in canonical.components() {
match component {
Component::Normal(name) => mirror.push(name),
Component::ParentDir => {
mirror.pop();
}
Component::RootDir | Component::CurDir | Component::Prefix(_) => {}
}
}
mirror
}
#[derive(Debug, Clone)]
pub struct RecordsDir {
path: PathBuf,
root: WorkdirRoot,
}
impl RecordsDir {
pub fn create(group: &Path, session_id: &str, latch_of: &WorkdirRoot) -> Result<Self, String> {
let located = |path: &Path, reason: String| format!("{}: {reason}", path.display());
let home = crate::murmur_home::ensure_murmur_home()?;
let records = home.join(RECORDS_DIR_NAME);
let rel = group
.strip_prefix(&records)
.map_err(|_| located(group, format!("is not beneath {}", records.display())))?
.join(session_id);
crate::murmur_home::hold_private_dir(&records)
.map_err(|reason| located(&records, reason))?;
let records_tree = WorkdirRoot::open(&records).map_err(|error| error.to_string())?;
records_tree
.create_dir_all(&rel, PRIVATE_DIR_MODE)
.map_err(|error| error.to_string())?;
let rel_text = rel.to_string_lossy();
let walked = records_tree
.open_beneath(&rel_text)
.map_err(|error| error.to_string())?
.ok_or_else(|| located(&records.join(&rel), "vanished after its creation".into()))?;
let path = records.join(&rel);
let root = WorkdirRoot::open_sharing_latch_absolute(&path, latch_of)
.map_err(|error| error.to_string())?;
if root.identity().map_err(|error| error.to_string())?
!= walked.identity().map_err(|error| error.to_string())?
{
return Err(located(
&path,
"changed between its creation and its open".to_string(),
));
}
Ok(Self { path, root })
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
#[must_use]
pub fn root(&self) -> &WorkdirRoot {
&self.root
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn records_dir_mirror_drops_the_root_and_every_dot_segment() {
let temp = tempfile::tempdir().unwrap();
let real = temp.path().canonicalize().unwrap();
let mirror = mirrored(&real.join("proj/./workdir/../workdir"));
assert_eq!(
mirror,
real.strip_prefix("/").unwrap().join("proj").join("workdir")
);
assert!(mirror.is_relative());
assert_eq!(mirrored(Path::new("/../../etc")), PathBuf::from("etc"));
}
#[test]
fn records_dir_mirror_resolves_a_link_above_the_workdir_root() {
let temp = tempfile::tempdir().unwrap();
let real = temp.path().join("real");
std::fs::create_dir_all(&real).unwrap();
let link = temp.path().join("link");
std::os::unix::fs::symlink(&real, &link).unwrap();
assert_eq!(
mirrored(&link.join("workdir")),
mirrored(&real.join("workdir"))
);
}
#[test]
fn records_dir_is_created_owner_only_beneath_the_murmur_home() {
let home = tempfile::tempdir().unwrap();
crate::murmur_home::run_with_home(
"records_dir::tests::records_dir_create_inner",
home.path(),
);
}
#[test]
#[ignore = "run by records_dir_is_created_owner_only_beneath_the_murmur_home"]
fn records_dir_create_inner() {
if !crate::murmur_home::in_scratch_home() {
return;
}
let home = PathBuf::from(std::env::var_os("HOME").unwrap());
let project = home.join("proj");
std::fs::create_dir_all(&project).unwrap();
let group = records_group(&project.join("workdir")).unwrap();
assert_eq!(
group,
home.join(".murmur/records")
.join(mirrored(&project.join("workdir")))
);
let accessible = WorkdirRoot::open(&project).unwrap();
let created = RecordsDir::create(&group, "ses_0001", &accessible).unwrap();
assert_eq!(created.path(), group.join("ses_0001"));
let mut level = created.path().to_path_buf();
while level != home.join(".murmur") {
assert_eq!(
crate::murmur_home::mode_of(&level),
0o700,
"{}",
level.display()
);
level.pop();
}
created
.root()
.write_file("trace.jsonl", b"{}\n", crate::workdir_fs::DEFAULT_FILE_MODE)
.unwrap();
let elsewhere = home.join("elsewhere");
std::fs::create_dir_all(&elsewhere).unwrap();
let planted_group = records_group(&home.join("planted/workdir")).unwrap();
std::fs::create_dir_all(planted_group.parent().unwrap()).unwrap();
std::os::unix::fs::symlink(&elsewhere, &planted_group).unwrap();
assert!(RecordsDir::create(&planted_group, "ses_0002", &accessible).is_err());
assert!(!elsewhere.join("ses_0002").exists());
}
}