remem-ai 0.6.88

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use std::ffi::OsString;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};

use anyhow::{bail, Context, Result};

use super::{capture_control_file, StoredPermissions};

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

pub(super) fn atomic_replace_control_file(
    path: &Path,
    content: &[u8],
    expected_current: Option<&[u8]>,
    permissions: Option<&StoredPermissions>,
) -> Result<()> {
    let (temp_path, mut temp_file) = create_transaction_temp(path)?;
    let mut cleanup = TempFileCleanup::new(temp_path.clone());
    temp_file
        .write_all(content)
        .with_context(|| format!("write control temp {}", temp_path.display()))?;
    if let Some(permissions) = permissions {
        permissions.apply(&temp_file)?;
    }
    temp_file
        .sync_all()
        .with_context(|| format!("sync control temp {}", temp_path.display()))?;
    drop(temp_file);
    validate_expected_control_file(path, expected_current)?;
    replace_file(&temp_path, path)?;
    cleanup.disarm();
    sync_parent(
        path.parent()
            .context("active revision ref must have a parent")?,
    )
}

fn validate_expected_control_file(path: &Path, expected: Option<&[u8]>) -> Result<()> {
    match (capture_control_file(path)?, expected) {
        (None, None) => Ok(()),
        (Some(actual), Some(expected)) if actual.content == expected => Ok(()),
        (Some(_), _) => bail!(
            "active revision ref changed during transaction: {}",
            path.display()
        ),
        (None, Some(_)) => bail!(
            "active revision ref disappeared during transaction: {}",
            path.display()
        ),
    }
}

pub(super) fn remove_candidate_control_file(path: &Path, candidate: &[u8]) -> Result<()> {
    validate_expected_control_file(path, Some(candidate))?;
    std::fs::remove_file(path)
        .with_context(|| format!("remove candidate active revision {}", path.display()))?;
    sync_parent(
        path.parent()
            .context("active revision ref must have a parent")?,
    )
}

pub(super) fn create_transaction_temp(path: &Path) -> Result<(PathBuf, File)> {
    let parent = path
        .parent()
        .context("transaction path must have a parent")?;
    let file_name = path
        .file_name()
        .context("transaction path must have a file name")?;
    for _ in 0..32 {
        let mut name = OsString::from(".");
        name.push(file_name);
        name.push(format!(
            ".remem-txn.{}.{}",
            std::process::id(),
            TRANSACTION_TEMP_SEQUENCE.fetch_add(1, Ordering::Relaxed)
        ));
        let temp_path = parent.join(name);
        match OpenOptions::new()
            .write(true)
            .create_new(true)
            .open(&temp_path)
        {
            Ok(file) => return Ok((temp_path, file)),
            Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
            Err(error) => {
                return Err(error).with_context(|| {
                    format!("create activation transaction temp {}", temp_path.display())
                });
            }
        }
    }
    bail!("could not create transaction temp for {}", path.display())
}

pub(super) struct TempFileCleanup {
    path: PathBuf,
    armed: bool,
}

impl TempFileCleanup {
    pub(super) fn new(path: PathBuf) -> Self {
        Self { path, armed: true }
    }

    pub(super) fn disarm(&mut self) {
        self.armed = false;
    }
}

impl Drop for TempFileCleanup {
    fn drop(&mut self) {
        if !self.armed {
            return;
        }
        if let Err(error) = std::fs::remove_file(&self.path) {
            if error.kind() != std::io::ErrorKind::NotFound {
                crate::log::error(
                    "embedding",
                    &format!(
                        "remove failed activation temp {}: {error}",
                        self.path.display()
                    ),
                );
            }
        }
    }
}

#[cfg(not(windows))]
pub(super) fn replace_file(temp_path: &Path, path: &Path) -> Result<()> {
    std::fs::rename(temp_path, path)
        .with_context(|| format!("atomically replace control file {}", path.display()))
}

#[cfg(windows)]
pub(super) fn replace_file(temp_path: &Path, path: &Path) -> Result<()> {
    use std::os::windows::ffi::OsStrExt;

    const MOVEFILE_REPLACE_EXISTING: u32 = 0x1;
    const MOVEFILE_WRITE_THROUGH: u32 = 0x8;

    #[link(name = "Kernel32")]
    unsafe extern "system" {
        #[link_name = "MoveFileExW"]
        fn move_file_ex_w(existing: *const u16, new: *const u16, flags: u32) -> i32;
    }

    let existing = temp_path
        .as_os_str()
        .encode_wide()
        .chain(std::iter::once(0))
        .collect::<Vec<_>>();
    let new = path
        .as_os_str()
        .encode_wide()
        .chain(std::iter::once(0))
        .collect::<Vec<_>>();
    let ok = unsafe {
        move_file_ex_w(
            existing.as_ptr(),
            new.as_ptr(),
            MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
        )
    };
    if ok == 0 {
        return Err(std::io::Error::last_os_error())
            .with_context(|| format!("atomically replace control file {}", path.display()));
    }
    Ok(())
}

#[cfg(unix)]
pub(super) fn sync_parent(parent: &Path) -> Result<()> {
    File::open(parent)
        .with_context(|| format!("open activation parent {}", parent.display()))?
        .sync_all()
        .with_context(|| format!("sync activation parent {}", parent.display()))
}

#[cfg(not(unix))]
pub(super) fn sync_parent(_parent: &Path) -> Result<()> {
    Ok(())
}