vj 0.2.0

vj: a SystemVerilog (IEEE 1800-2023) simulator built on slang and LLVM.
//! A fresh scratch directory only this user can write, for files the CLI
//! later executes or loads (`run --aot` builds `design.so` and `dlopen`s it).
//!
//! A predictable name under a shared temp dir, created with
//! `create_dir_all`, lets another local user create the directory first and
//! swap the library in before it is loaded. Here the name carries a
//! per-process random suffix, the directory is created non-recursively with
//! mode 0700, and an existing path is never reused (`AlreadyExists` retries
//! with a new name).

use std::io;
use std::os::unix::fs::DirBuilderExt;
use std::path::{Path, PathBuf};

/// Creates `<parent>/<prefix>-<pid>-<random>` with mode 0700. Fails rather
/// than reuse an existing directory.
pub fn create_in(parent: &Path, prefix: &str) -> io::Result<PathBuf> {
    let seed = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_nanos())
        .unwrap_or(0);
    static CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
    let call = CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
    let mut state = seed ^ (u128::from(std::process::id()) << 64) ^ (u128::from(call) << 32) | 1;
    create_with(parent, prefix, || {
        // xorshift: distinct names per attempt, unpredictable across runs
        state ^= state << 13;
        state ^= state >> 7;
        state ^= state << 17;
        state as u64
    })
}

/// [`create_in`] with the name source injected so a test can force a
/// collision. Up to 64 candidate names are tried; an `AlreadyExists` path is
/// skipped (never reused or chmod'ed), any other error is returned.
fn create_with(parent: &Path, prefix: &str, mut next: impl FnMut() -> u64) -> io::Result<PathBuf> {
    for _ in 0..64 {
        let dir = parent.join(format!("{prefix}-{}-{:016x}", std::process::id(), next()));
        match std::fs::DirBuilder::new().mode(0o700).create(&dir) {
            Ok(()) => return Ok(dir),
            Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
            Err(e) => return Err(e),
        }
    }
    Err(io::Error::new(
        io::ErrorKind::AlreadyExists,
        format!(
            "no fresh `{prefix}-*` directory name under {}",
            parent.display()
        ),
    ))
}

/// [`create_in`] under the system temp dir.
pub fn create(prefix: &str) -> io::Result<PathBuf> {
    create_in(&std::env::temp_dir(), prefix)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::os::unix::fs::PermissionsExt;

    #[test]
    fn a_private_dir_is_owner_only_and_fresh_each_time() {
        let a = create("vj-private-dir-test").unwrap();
        let b = create("vj-private-dir-test").unwrap();
        assert_ne!(a, b);
        let mode = std::fs::metadata(&a).unwrap().permissions().mode() & 0o777;
        assert_eq!(mode, 0o700, "{}", a.display());
        let _ = std::fs::remove_dir(&a);
        let _ = std::fs::remove_dir(&b);
    }

    #[test]
    fn an_existing_path_is_skipped_never_reused() {
        let parent = create("vj-private-dir-parent").unwrap();
        let name = |n: u64| parent.join(format!("p-{}-{n:016x}", std::process::id()));
        // A pre-existing directory and a file hold the first two names.
        std::fs::create_dir(name(1)).unwrap();
        std::fs::write(name(1).join("marker"), b"x").unwrap();
        std::fs::write(name(2), b"x").unwrap();
        let mut seq = [1u64, 1, 2, 3].into_iter();
        let got = create_with(&parent, "p", || seq.next().unwrap()).unwrap();
        assert_eq!(got, name(3));
        assert_eq!(seq.next(), None, "all four candidate names consumed");
        let mode = std::fs::metadata(&got).unwrap().permissions().mode() & 0o777;
        assert_eq!(mode, 0o700);
        assert!(
            name(1).join("marker").exists(),
            "pre-existing dir untouched"
        );
        assert_eq!(std::fs::read_dir(&got).unwrap().count(), 0);
        // Exhaustion fails rather than reusing.
        let err = create_with(&parent, "p", || 1).unwrap_err();
        assert_eq!(err.kind(), io::ErrorKind::AlreadyExists);
        let _ = std::fs::remove_dir_all(&parent);
    }
}