liminal-server 0.24.9

Standalone server for the liminal messaging bus
Documentation
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};

#[cfg(unix)]
use std::os::unix::fs::MetadataExt;

type FixtureResult<T> = Result<T, Box<dyn Error>>;

static NEXT_COPY: AtomicU64 = AtomicU64::new(0);

#[derive(Debug, Default)]
struct CopyCounts {
    files: u64,
    bytes: u64,
    directories: u64,
}

fn copy_directory(source: &Path, destination: &Path) -> FixtureResult<CopyCounts> {
    let mut counts = CopyCounts::default();
    for entry in fs::read_dir(source)? {
        let entry = entry?;
        let source = entry.path();
        let destination = destination.join(entry.file_name());
        let kind = entry.file_type()?;
        if kind.is_dir() {
            fs::create_dir(&destination)?;
            fs::set_permissions(&destination, entry.metadata()?.permissions())?;
            let nested = copy_directory(&source, &destination)?;
            counts.files = counts
                .files
                .checked_add(nested.files)
                .ok_or("fixture file count overflow")?;
            counts.bytes = counts
                .bytes
                .checked_add(nested.bytes)
                .ok_or("fixture byte count overflow")?;
            counts.directories = counts
                .directories
                .checked_add(nested.directories)
                .and_then(|count| count.checked_add(1))
                .ok_or("fixture directory count overflow")?;
        } else if kind.is_file() {
            let bytes = fs::copy(&source, &destination)?;
            counts.files = counts
                .files
                .checked_add(1)
                .ok_or("fixture file count overflow")?;
            counts.bytes = counts
                .bytes
                .checked_add(bytes)
                .ok_or("fixture byte count overflow")?;
            let before = fs::metadata(&source)?;
            let after = fs::metadata(&destination)?;
            #[cfg(unix)]
            if before.dev() == after.dev() && before.ino() == after.ino() {
                return Err("persistence fixture copy shares its source inode".into());
            }
            if before.len() != bytes
                || after.len() != bytes
                || fs::read(&source)? != fs::read(&destination)?
            {
                return Err("persistence fixture copy differs from its source bytes".into());
            }
        } else {
            return Err("persistence fixture contains a link or non-regular file".into());
        }
    }
    Ok(counts)
}

pub struct Persistence {
    path: PathBuf,
}

impl Persistence {
    pub(super) fn copied() -> FixtureResult<Self> {
        let root = std::env::var_os("LIMINAL_DURABILITY_FIXTURES")
            .ok_or("persistence fixture setup did not provide LIMINAL_DURABILITY_FIXTURES")?;
        let source = Path::new(&root).join("full-services");
        if !fs::symlink_metadata(&source)?.file_type().is_dir() {
            return Err("full-services persistence fixture image is not a directory".into());
        }
        let number = NEXT_COPY
            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |count| {
                count.checked_add(1)
            })
            .map_err(|_| "persistence fixture copy counter overflow")?;
        let path = Path::new(&root).join(format!(
            "full-services-copy-{}-{number}",
            std::process::id()
        ));
        fs::create_dir(&path)?;
        let fixture = Self { path };
        let counts = copy_directory(&source, fixture.path())?;
        if counts.files == 0
            || counts.bytes == 0
            || !fixture.path().join("durability/config.json").is_file()
        {
            return Err("full-services persistence fixture has no database configuration".into());
        }
        println!("full-services fixture copy: {counts:?}");
        Ok(fixture)
    }

    #[must_use]
    pub(super) fn path(&self) -> &Path {
        &self.path
    }
}

impl Drop for Persistence {
    fn drop(&mut self) {
        if std::thread::panicking() {
            eprintln!(
                "persistence fixture unwind retained {}",
                self.path.display()
            );
        } else {
            let removed = fs::remove_dir_all(&self.path);
            assert!(
                removed.is_ok(),
                "persistence fixture removal failed at {}: {removed:?}",
                self.path.display()
            );
        }
    }
}