rdar 0.6.14

radar - the repository cartographer for AI agents: compiles a repo into tiny committed MAP.md routers, with measured token benchmarks
Documentation
//! Concurrency guard: one radar writer per repo.
//!
//! Supported Unix platforms use a kernel-held advisory lock. The lock file is
//! persistent, but process exit releases ownership automatically, including
//! aborts and signals, so recovery never deletes a possibly-new pathname.

use std::fs;
use std::io;
use std::path::Path;

#[cfg(not(unix))]
use std::path::PathBuf;

pub struct Lock {
    #[cfg(unix)]
    _file: fs::File,
    #[cfg(not(unix))]
    path: PathBuf,
}

#[cfg(unix)]
const LOCK_MAGIC: &str = "RDARLOCK2";

impl Lock {
    /// Acquire or fail fast with `WouldBlock`.
    pub fn acquire(root: &Path) -> io::Result<Lock> {
        let dir = crate::cache::radar_dir(root);
        fs::create_dir_all(&dir)?;
        let path = dir.join("lock");
        acquire_platform(&path)
    }
}

#[cfg(unix)]
fn acquire_platform(path: &Path) -> io::Result<Lock> {
    use std::io::{Read, Seek, SeekFrom, Write};
    use std::os::fd::AsRawFd;
    use std::os::unix::fs::OpenOptionsExt;

    let open = |create_new| {
        let mut options = fs::OpenOptions::new();
        options
            .read(true)
            .write(true)
            .create_new(create_new)
            .custom_flags(O_NOFOLLOW);
        options.open(path)
    };
    let (mut file, created) = match open(true) {
        Ok(file) => (file, true),
        Err(error) if error.kind() == io::ErrorKind::AlreadyExists => (open(false)?, false),
        Err(error) => return Err(error),
    };
    if !file.metadata()?.file_type().is_file() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            ".radar/lock is not a regular file",
        ));
    }
    // LOCK_EX | LOCK_NB. flock ownership belongs to this open file
    // description and the kernel releases it when the process exits.
    let result = unsafe { flock(file.as_raw_fd(), 2 | 4) };
    if result != 0 {
        let error = io::Error::last_os_error();
        return if error.kind() == io::ErrorKind::WouldBlock {
            Err(io::Error::new(
                io::ErrorKind::WouldBlock,
                "another radar run holds the lock (.radar/lock)",
            ))
        } else {
            Err(error)
        };
    }
    if !created {
        let mut prefix = [0u8; LOCK_MAGIC.len()];
        if file.read_exact(&mut prefix).is_err() || prefix != LOCK_MAGIC.as_bytes() {
            return Err(io::Error::new(
                io::ErrorKind::WouldBlock,
                "a pre-upgrade radar lock exists; stop the old writer or remove .radar/lock",
            ));
        }
    }
    file.set_len(0)?;
    file.seek(SeekFrom::Start(0))?;
    write!(file, "{LOCK_MAGIC} {}", std::process::id())?;
    Ok(Lock { _file: file })
}

#[cfg(any(target_os = "linux", target_os = "android"))]
const O_NOFOLLOW: std::os::raw::c_int = 0o400000;

#[cfg(all(unix, not(any(target_os = "linux", target_os = "android"))))]
const O_NOFOLLOW: std::os::raw::c_int = 0x100;

#[cfg(unix)]
unsafe extern "C" {
    fn flock(fd: std::os::raw::c_int, operation: std::os::raw::c_int) -> std::os::raw::c_int;
}

#[cfg(not(unix))]
fn acquire_platform(path: &Path) -> io::Result<Lock> {
    match fs::OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(path)
    {
        Ok(mut file) => {
            use std::io::Write;
            write!(file, "{}", std::process::id())?;
            Ok(Lock {
                path: path.to_path_buf(),
            })
        }
        Err(error) if error.kind() == io::ErrorKind::AlreadyExists => Err(io::Error::new(
            io::ErrorKind::WouldBlock,
            "another radar run holds the lock (.radar/lock)",
        )),
        Err(error) => Err(error),
    }
}

#[cfg(not(unix))]
impl Drop for Lock {
    fn drop(&mut self) {
        let _ = fs::remove_file(&self.path);
    }
}

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

    #[test]
    fn exclusive_then_released() {
        let dir = std::env::temp_dir().join(format!("radar-lock-{}", std::process::id()));
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(&dir).expect("mkdir");
        let first = Lock::acquire(&dir).expect("first lock");
        let second = Lock::acquire(&dir);
        assert!(matches!(second, Err(ref error) if error.kind() == io::ErrorKind::WouldBlock));
        drop(first);
        let third = Lock::acquire(&dir).expect("re-acquire after release");
        drop(third);
        let _ = fs::remove_dir_all(&dir);
    }

    #[cfg(unix)]
    #[test]
    fn existing_unowned_persistent_file_is_reclaimed_without_deletion() {
        let dir = std::env::temp_dir().join(format!("radar-dead-lock-{}", std::process::id()));
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(crate::cache::radar_dir(&dir)).expect("mkdir");
        let path = crate::cache::radar_dir(&dir).join("lock");
        fs::write(&path, format!("{LOCK_MAGIC} old-owner")).expect("write old lock file");

        let lock = Lock::acquire(&dir).expect("kernel lock has no live owner");
        assert!(path.exists(), "advisory lock path stays stable");
        drop(lock);
        let _ = fs::remove_dir_all(&dir);
    }

    #[cfg(unix)]
    #[test]
    fn legacy_lock_is_blocked_and_untouched() {
        let dir = std::env::temp_dir().join(format!("radar-legacy-lock-{}", std::process::id()));
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(crate::cache::radar_dir(&dir)).expect("mkdir");
        let path = crate::cache::radar_dir(&dir).join("lock");
        fs::write(&path, "12345").expect("write legacy lock");

        let result = Lock::acquire(&dir);
        assert!(matches!(result, Err(ref error) if error.kind() == io::ErrorKind::WouldBlock));
        assert_eq!(fs::read_to_string(&path).unwrap(), "12345");
        let _ = fs::remove_dir_all(&dir);
    }

    #[cfg(unix)]
    #[test]
    fn lock_symlink_is_rejected_without_touching_target() {
        use std::os::unix::fs::symlink;

        let dir = std::env::temp_dir().join(format!("radar-link-lock-{}", std::process::id()));
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(crate::cache::radar_dir(&dir)).expect("mkdir");
        let victim = dir.join("victim");
        fs::write(&victim, "keep me").expect("write victim");
        symlink(&victim, crate::cache::radar_dir(&dir).join("lock")).expect("link lock");

        assert!(Lock::acquire(&dir).is_err());
        assert_eq!(fs::read_to_string(&victim).unwrap(), "keep me");
        let _ = fs::remove_dir_all(&dir);
    }

    #[cfg(unix)]
    #[test]
    fn oversized_legacy_lock_is_checked_with_a_fixed_prefix() {
        let dir = std::env::temp_dir().join(format!("radar-large-lock-{}", std::process::id()));
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(crate::cache::radar_dir(&dir)).expect("mkdir");
        let path = crate::cache::radar_dir(&dir).join("lock");
        let file = fs::File::create(&path).expect("create lock");
        file.set_len(1 << 30).expect("sparse lock");

        let result = Lock::acquire(&dir);
        assert!(matches!(result, Err(ref error) if error.kind() == io::ErrorKind::WouldBlock));
        assert_eq!(fs::metadata(&path).unwrap().len(), 1 << 30);
        let _ = fs::remove_dir_all(&dir);
    }
}