haematite-cli 0.21.3

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! Cooperative socket-path ownership, retained across stale-entry cleanup.
//!
//! The permanent sidecar anchor is never unlinked. All haematite instances hold its
//! exclusive OS lock before probing, binding or removing this socket pathname.
//! The private directory is operator-owned; hostile mutation by the same OS
//! account is outside this local transport's authority boundary.

use std::fs::{self, File, OpenOptions};
use std::io;
use std::os::unix::fs::{FileTypeExt, MetadataExt, OpenOptionsExt, PermissionsExt};
use std::os::unix::net::UnixStream;
use std::path::Path;

pub(super) fn check_parent(path: &Path) -> io::Result<()> {
    let parent = path
        .parent()
        .ok_or_else(|| io::Error::other("socket needs a parent directory"))?;
    let metadata = fs::metadata(parent)?;
    if !metadata.is_dir() || metadata.permissions().mode() & 0o777 != 0o700 {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "socket directory must be private (mode 0700)",
        ));
    }
    Ok(())
}

pub(super) fn acquire(path: &Path) -> io::Result<File> {
    check_parent(path)?;
    // Compatibility invariant across haematite versions: this exact socket path plus
    // ".lock" must name the same permanent anchor. Changing the suffix would let
    // different versions lock different files and lose mutual exclusion.
    let mut anchor = path.as_os_str().to_os_string();
    anchor.push(".lock");
    let anchor = Path::new(&anchor);
    match fs::symlink_metadata(anchor) {
        Ok(metadata) if !metadata.file_type().is_file() => {
            return Err(io::Error::new(
                io::ErrorKind::AlreadyExists,
                "socket lock anchor is not a regular file",
            ));
        }
        Ok(_) => {}
        Err(error) if error.kind() == io::ErrorKind::NotFound => {}
        Err(error) => return Err(error),
    }
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .truncate(false)
        .mode(0o600)
        .open(anchor)?;
    file.try_lock().map_err(io::Error::from)?;
    let opened = file.metadata()?;
    let named = fs::symlink_metadata(anchor)?;
    if !named.file_type().is_file() || (opened.dev(), opened.ino()) != (named.dev(), named.ino()) {
        return Err(io::Error::new(
            io::ErrorKind::AddrInUse,
            "socket lock anchor changed",
        ));
    }
    Ok(file)
}

pub(super) fn clear_stale(path: &Path) -> io::Result<()> {
    let metadata = match fs::symlink_metadata(path) {
        Ok(metadata) => metadata,
        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
        Err(error) => return Err(error),
    };
    if !metadata.file_type().is_socket() {
        return Err(io::Error::new(
            io::ErrorKind::AlreadyExists,
            "socket path contains another file",
        ));
    }
    match UnixStream::connect(path) {
        Ok(_) => Err(io::Error::new(
            io::ErrorKind::AddrInUse,
            "socket is already serving",
        )),
        Err(error) if error.kind() == io::ErrorKind::ConnectionRefused => {
            let now = fs::symlink_metadata(path)?;
            if (metadata.dev(), metadata.ino()) != (now.dev(), now.ino()) {
                return Err(io::Error::new(
                    io::ErrorKind::AddrInUse,
                    "socket changed during recovery",
                ));
            }
            fs::remove_file(path)
        }
        Err(error) => Err(error),
    }
}

fn name_anchor(endpoint: &Path) -> io::Result<std::path::PathBuf> {
    if !endpoint.is_absolute()
        || endpoint.file_name().is_none()
        || endpoint.components().any(|part| {
            !matches!(
                part,
                std::path::Component::RootDir | std::path::Component::Normal(_)
            )
        })
    {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "name endpoint must be an absolute file path",
        ));
    }
    let mut anchor = endpoint.as_os_str().to_os_string();
    anchor.push(".owner.lock");
    Ok(anchor.into())
}

fn name_directory(anchor: &Path, uid: u32) -> io::Result<fs::Metadata> {
    let parent = anchor
        .parent()
        .ok_or_else(|| io::Error::other("name anchor needs a parent"))?;
    let metadata = fs::symlink_metadata(parent)?;
    if !metadata.is_dir()
        || metadata.uid() != uid
        || metadata.permissions().mode() & 0o7777 != 0o700
    {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "name anchor directory must be owned and private (mode 0700)",
        ));
    }
    Ok(metadata)
}

fn valid_name_anchor(metadata: &fs::Metadata, uid: u32) -> io::Result<()> {
    if !metadata.is_file()
        || metadata.uid() != uid
        || metadata.permissions().mode() & 0o7777 != 0o600
        || metadata.len() != 0
    {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "name anchor must be an owned empty regular file (mode 0600)",
        ));
    }
    Ok(())
}

fn check_name_identity(
    anchor: &Path,
    file: &File,
    directory: &fs::Metadata,
    prior: Option<&fs::Metadata>,
    uid: u32,
) -> io::Result<()> {
    let parent = name_directory(anchor, uid)?;
    let opened = file.metadata()?;
    let named = fs::symlink_metadata(anchor)?;
    valid_name_anchor(&opened, uid)?;
    valid_name_anchor(&named, uid)?;
    if (parent.dev(), parent.ino()) != (directory.dev(), directory.ino())
        || (opened.dev(), opened.ino()) != (named.dev(), named.ino())
        || prior.is_some_and(|prior| (prior.dev(), prior.ino()) != (opened.dev(), opened.ino()))
    {
        return Err(io::Error::new(
            io::ErrorKind::AddrInUse,
            "name anchor or directory changed",
        ));
    }
    Ok(())
}

pub(super) fn acquire_name(endpoint: &Path, uid: u32) -> io::Result<File> {
    let anchor = name_anchor(endpoint)?;
    let directory = name_directory(&anchor, uid)?;
    let prior = match fs::symlink_metadata(&anchor) {
        Ok(metadata) => {
            valid_name_anchor(&metadata, uid)?;
            Some(metadata)
        }
        Err(error) if error.kind() == io::ErrorKind::NotFound => None,
        Err(error) => return Err(error),
    };
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .truncate(false)
        .mode(0o600)
        .open(&anchor)?;
    check_name_identity(&anchor, &file, &directory, prior.as_ref(), uid)?;
    file.try_lock().map_err(io::Error::from)?;
    check_name_identity(&anchor, &file, &directory, prior.as_ref(), uid)?;
    Ok(file)
}

pub(super) fn name_is_live(endpoint: &Path, uid: u32) -> io::Result<bool> {
    let anchor = name_anchor(endpoint)?;
    let directory = name_directory(&anchor, uid)?;
    let named = match fs::symlink_metadata(&anchor) {
        Ok(metadata) => metadata,
        Err(error) if error.kind() == io::ErrorKind::NotFound => {
            let now = name_directory(&anchor, uid)?;
            if (directory.dev(), directory.ino()) != (now.dev(), now.ino()) {
                return Err(io::Error::new(
                    io::ErrorKind::AddrInUse,
                    "name anchor directory changed",
                ));
            }
            return Ok(false);
        }
        Err(error) => return Err(error),
    };
    valid_name_anchor(&named, uid)?;
    // Opening an existing anchor never changes the runtime namespace or socket.
    let file = File::open(&anchor)?;
    check_name_identity(&anchor, &file, &directory, Some(&named), uid)?;
    let live = match file.try_lock().map_err(io::Error::from) {
        Ok(()) => false,
        Err(error) if error.kind() == io::ErrorKind::WouldBlock => true,
        Err(error) => return Err(error),
    };
    let checked = check_name_identity(&anchor, &file, &directory, Some(&named), uid);
    if live {
        checked?;
    } else {
        let released = file.unlock();
        match (checked, released) {
            (Ok(()), Ok(())) => {}
            (Err(error), Ok(())) | (Ok(()), Err(error)) => return Err(error),
            (Err(check), Err(release)) => {
                return Err(io::Error::other(format!(
                    "name anchor validation: {check}; probe lock release: {release}"
                )));
            }
        }
    }
    Ok(live)
}