oliphaunt-wasix 0.1.0

Embedded Postgres for Rust tests and local apps. No Docker, works with SQLx and any Postgres client.
Documentation
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use std::process::Command;

use anyhow::{Context, Result};

use super::TEMPLATE_RUNTIME_STATE_FILES;

pub(super) fn clone_pgdata_template_dir(source_pgdata: &Path, dest_pgdata: &Path) -> Result<()> {
    if try_clone_dir(source_pgdata, dest_pgdata)? {
        return Ok(());
    }
    copy_pgdata_template_dir_inner(source_pgdata, dest_pgdata)
}

fn copy_pgdata_template_dir_inner(source_pgdata: &Path, dest_pgdata: &Path) -> Result<()> {
    fs::create_dir_all(dest_pgdata)
        .with_context(|| format!("create directory {}", dest_pgdata.display()))?;

    for entry in fs::read_dir(source_pgdata)
        .with_context(|| format!("read directory {}", source_pgdata.display()))?
    {
        let entry =
            entry.with_context(|| format!("read entry under {}", source_pgdata.display()))?;
        let file_name = entry.file_name();
        if should_skip_template_entry(&file_name) {
            continue;
        }

        let src_path = entry.path();
        let dest_path = dest_pgdata.join(&file_name);
        let file_type = entry
            .file_type()
            .with_context(|| format!("stat {}", src_path.display()))?;

        if file_type.is_dir() {
            copy_pgdata_template_dir_inner(&src_path, &dest_path)?;
        } else if file_type.is_file() {
            if let Some(parent) = dest_path.parent() {
                fs::create_dir_all(parent)
                    .with_context(|| format!("create directory {}", parent.display()))?;
            }
            clone_mutable_template_file(&src_path, &dest_path)?;
        } else if file_type.is_symlink() {
            copy_symlink(&src_path, &dest_path)?;
        }
    }

    Ok(())
}

fn clone_mutable_template_file(src: &Path, dest: &Path) -> Result<()> {
    if std::env::var_os("OLIPHAUNT_WASM_TEMPLATE_REFLINK").is_some() && try_reflink_file(src, dest)?
    {
        return Ok(());
    }
    copy_template_file(src, dest)
}

fn try_clone_dir(src: &Path, dest: &Path) -> Result<bool> {
    if dest.exists() {
        fs::remove_dir_all(dest).with_context(|| format!("remove {}", dest.display()))?;
    }
    if let Some(parent) = dest.parent() {
        fs::create_dir_all(parent).with_context(|| format!("create {}", parent.display()))?;
    }

    let status = clone_dir_command(src, dest);
    match status {
        Ok(status) if status.success() && dest.exists() => Ok(true),
        Ok(_) | Err(_) => {
            if dest.exists() {
                fs::remove_dir_all(dest).with_context(|| {
                    format!("remove failed cloned directory {}", dest.display())
                })?;
            }
            Ok(false)
        }
    }
}

#[cfg(target_os = "linux")]
fn clone_dir_command(src: &Path, dest: &Path) -> std::io::Result<std::process::ExitStatus> {
    Command::new("cp")
        .arg("-a")
        .arg("--reflink=auto")
        .arg("--")
        .arg(src)
        .arg(dest)
        .status()
}

#[cfg(target_os = "macos")]
fn clone_dir_command(src: &Path, dest: &Path) -> std::io::Result<std::process::ExitStatus> {
    Command::new("cp").arg("-cR").arg(src).arg(dest).status()
}

#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn clone_dir_command(_src: &Path, _dest: &Path) -> std::io::Result<std::process::ExitStatus> {
    Err(std::io::Error::new(
        std::io::ErrorKind::Unsupported,
        "directory clone is unsupported on this platform",
    ))
}

fn copy_template_file(src: &Path, dest: &Path) -> Result<()> {
    fs::copy(src, dest).with_context(|| format!("copy {} to {}", src.display(), dest.display()))?;
    Ok(())
}

#[cfg(target_os = "linux")]
fn try_reflink_file(src: &Path, dest: &Path) -> Result<bool> {
    let status = Command::new("cp")
        .arg("--reflink=always")
        .arg("--")
        .arg(src)
        .arg(dest)
        .status();
    match status {
        Ok(status) if status.success() && dest.exists() => Ok(true),
        Ok(_) | Err(_) => {
            let _ = fs::remove_file(dest);
            Ok(false)
        }
    }
}

#[cfg(target_os = "macos")]
fn try_reflink_file(src: &Path, dest: &Path) -> Result<bool> {
    let status = Command::new("cp").arg("-c").arg(src).arg(dest).status();
    match status {
        Ok(status) if status.success() && dest.exists() => Ok(true),
        Ok(_) | Err(_) => {
            let _ = fs::remove_file(dest);
            Ok(false)
        }
    }
}

#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn try_reflink_file(_src: &Path, _dest: &Path) -> Result<bool> {
    Ok(false)
}

fn should_skip_template_entry(file_name: &OsStr) -> bool {
    let name = file_name.to_string_lossy();
    name.starts_with(".s.PGSQL.") || TEMPLATE_RUNTIME_STATE_FILES.contains(&name.as_ref())
}

#[cfg(unix)]
fn copy_symlink(src: &Path, dest: &Path) -> Result<()> {
    if let Some(parent) = dest.parent() {
        fs::create_dir_all(parent)
            .with_context(|| format!("create directory {}", parent.display()))?;
    }
    let target = fs::read_link(src).with_context(|| format!("read symlink {}", src.display()))?;
    std::os::unix::fs::symlink(&target, dest)
        .with_context(|| format!("create symlink {} -> {}", dest.display(), target.display()))?;
    Ok(())
}

#[cfg(not(unix))]
fn copy_symlink(src: &Path, dest: &Path) -> Result<()> {
    let target = fs::read_link(src).with_context(|| format!("read symlink {}", src.display()))?;
    let target_path = if target.is_absolute() {
        target
    } else {
        src.parent().unwrap_or_else(|| Path::new(".")).join(target)
    };

    if target_path.is_dir() {
        copy_pgdata_template_dir_inner(&target_path, dest)
    } else {
        if let Some(parent) = dest.parent() {
            fs::create_dir_all(parent)
                .with_context(|| format!("create directory {}", parent.display()))?;
        }
        fs::copy(&target_path, dest)
            .with_context(|| format!("copy {} to {}", target_path.display(), dest.display()))?;
        Ok(())
    }
}

#[cfg(test)]
fn copy_template_pgdata(template_root: &Path, dest_root: &Path) -> Result<()> {
    let source_pgdata = template_root.join("tmp/oliphaunt/base");
    clone_pgdata_template_dir(&source_pgdata, &dest_root.join("tmp/oliphaunt/base"))
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn template_copy_keeps_cluster_files_and_skips_runtime_state() -> Result<()> {
        let source = TempDir::new()?;
        let pgdata = source.path().join("tmp/oliphaunt/base");
        fs::create_dir_all(&pgdata)?;
        fs::write(pgdata.join("PG_VERSION"), b"17\n")?;
        fs::write(pgdata.join("postmaster.pid"), b"stale pid")?;
        fs::write(pgdata.join("postmaster.opts"), b"stale opts")?;
        fs::write(pgdata.join(".s.PGSQL.5432"), b"socket")?;
        fs::write(pgdata.join(".s.PGSQL.5432.lock"), b"lock")?;

        let dest = TempDir::new()?;
        let dest_pgdata = dest.path().join("tmp/oliphaunt/base");
        copy_pgdata_template_dir_inner(&pgdata, &dest_pgdata)?;

        assert!(
            dest_pgdata.join("PG_VERSION").exists(),
            "destination entries: {}",
            list_test_entries(dest.path())?
        );
        assert!(!dest_pgdata.join("postmaster.pid").exists());
        assert!(!dest_pgdata.join("postmaster.opts").exists());
        assert!(!dest_pgdata.join(".s.PGSQL.5432").exists());
        assert!(!dest_pgdata.join(".s.PGSQL.5432.lock").exists());
        Ok(())
    }

    #[cfg(unix)]
    #[test]
    fn template_clone_does_not_hardlink_mutable_pgdata_files() -> Result<()> {
        use std::os::unix::fs::MetadataExt;

        let source = TempDir::new()?;
        let pgdata = source.path().join("tmp/oliphaunt/base");
        fs::create_dir_all(&pgdata)?;
        fs::write(pgdata.join("PG_VERSION"), b"17\n")?;

        let dest = TempDir::new()?;
        let dest_pgdata = dest.path().join("tmp/oliphaunt/base");
        copy_pgdata_template_dir_inner(&pgdata, &dest_pgdata)?;

        let source_pg_version = pgdata.join("PG_VERSION");
        let dest_pg_version = dest_pgdata.join("PG_VERSION");
        assert!(
            source_pg_version.exists(),
            "source PG_VERSION should exist at {}",
            source_pg_version.display()
        );
        assert!(
            dest_pg_version.exists(),
            "cloned PG_VERSION should exist at {}; destination entries: {}",
            dest_pg_version.display(),
            list_test_entries(dest.path())?
        );
        let source_meta = fs::metadata(&source_pg_version)?;
        let dest_meta = fs::metadata(&dest_pg_version)?;
        assert_ne!(
            (source_meta.dev(), source_meta.ino()),
            (dest_meta.dev(), dest_meta.ino()),
            "mutable PGDATA template files must be copied or reflinked, not hardlinked"
        );
        Ok(())
    }

    #[cfg(unix)]
    #[test]
    fn fallback_template_pgdata_copy_does_not_hardlink_mutable_files() -> Result<()> {
        use std::os::unix::fs::MetadataExt;

        let source = TempDir::new()?;
        let pgdata = source.path().join("tmp/oliphaunt/base");
        fs::create_dir_all(&pgdata)?;
        fs::write(pgdata.join("PG_VERSION"), b"17\n")?;

        let dest = TempDir::new()?;
        copy_template_pgdata(source.path(), dest.path())?;

        let source_pg_version = pgdata.join("PG_VERSION");
        let dest_pg_version = dest.path().join("tmp/oliphaunt/base/PG_VERSION");
        assert!(dest_pg_version.exists());
        let source_meta = fs::metadata(&source_pg_version)?;
        let dest_meta = fs::metadata(&dest_pg_version)?;
        assert_ne!(
            (source_meta.dev(), source_meta.ino()),
            (dest_meta.dev(), dest_meta.ino()),
            "fallback PGDATA template copy must not hardlink mutable files"
        );
        Ok(())
    }

    #[test]
    fn fallback_template_pgdata_copy_does_not_share_mutable_files() -> Result<()> {
        let source = TempDir::new()?;
        let pgdata = source.path().join("base");
        fs::create_dir_all(&pgdata)?;
        fs::write(pgdata.join("PG_VERSION"), b"17\n")?;

        let dest = TempDir::new()?;
        let cloned = dest.path().join("base");
        copy_pgdata_template_dir_inner(&pgdata, &cloned)?;
        fs::write(cloned.join("PG_VERSION"), b"changed\n")?;

        assert_eq!(
            fs::read(pgdata.join("PG_VERSION"))?,
            b"17\n",
            "fallback PGDATA template copy must not share mutable file storage with the source"
        );
        Ok(())
    }

    fn list_test_entries(root: &Path) -> Result<String> {
        let mut entries = Vec::new();
        collect_test_entries(root, root, &mut entries)?;
        entries.sort();
        Ok(entries.join(", "))
    }

    fn collect_test_entries(root: &Path, current: &Path, entries: &mut Vec<String>) -> Result<()> {
        for entry in fs::read_dir(current)? {
            let entry = entry?;
            let path = entry.path();
            let relative = path.strip_prefix(root).unwrap_or(&path);
            entries.push(relative.display().to_string());
            if entry.file_type()?.is_dir() {
                collect_test_entries(root, &path, entries)?;
            }
        }
        Ok(())
    }
}