magi-code 0.96.2

Repository-aware CLI coding agent for terminal work
Documentation
#[cfg(unix)]
use crate::sessions::{apply_repair_plan, discover_repair_plan};
#[cfg(unix)]
use std::fs;
#[cfg(unix)]
use std::io::{self, BufRead, IsTerminal, Read, Write};
use std::path::Path;
#[cfg(unix)]
#[derive(Clone, Copy)]
struct TerminalState {
    stdin_is_tty: bool,
    stderr_is_tty: bool,
}

pub(crate) fn run(root: &Path, yes: bool, dry_run: bool) -> anyhow::Result<()> {
    #[cfg(not(unix))]
    {
        let _ = (root, yes, dry_run);
        anyhow::bail!(
            "session permission repair is unsupported on this platform; no permissions changed"
        )
    }
    #[cfg(unix)]
    {
        let mut stdout = io::stdout().lock();
        let mut stderr = io::stderr().lock();
        run_with_io(
            root,
            yes,
            dry_run,
            &mut io::stdin().lock(),
            &mut stdout,
            &mut stderr,
            TerminalState {
                stdin_is_tty: io::stdin().is_terminal(),
                stderr_is_tty: io::stderr().is_terminal(),
            },
        )
    }
}

#[cfg(unix)]
fn run_with_io<R: BufRead, W: Write, E: Write>(
    root: &Path,
    yes: bool,
    dry_run: bool,
    input: &mut R,
    stdout: &mut W,
    stderr: &mut E,
    terminal: TerminalState,
) -> anyhow::Result<()> {
    use std::os::unix::fs::MetadataExt;
    let metadata = match fs::symlink_metadata(root) {
        Ok(metadata) => metadata,
        Err(error) if error.kind() == io::ErrorKind::NotFound => {
            writeln!(stdout, "No session root exists; nothing to repair.")?;
            return Ok(());
        }
        Err(error) => return Err(error.into()),
    };
    if metadata.file_type().is_symlink() || !metadata.file_type().is_dir() {
        anyhow::bail!(
            "session root is not a non-symlink directory: {}",
            root.display()
        );
    }
    if metadata.uid() != unsafe { libc::geteuid() } {
        anyhow::bail!("session root owner is not current user: {}", root.display());
    }
    let plan = discover_repair_plan(root).map_err(|_| {
        anyhow::anyhow!(
            "session layout is unsafe, unreadable, or unrecognized; inspect {} manually",
            root.display()
        )
    })?;
    if plan.permissions_are_secure()? {
        writeln!(
            stdout,
            "Session permissions already secure; nothing to repair."
        )?;
        return Ok(());
    }
    let count = plan.target_count();
    if dry_run {
        writeln!(
            stdout,
            "Would repair permissions for {count} recognized session objects under {}.",
            root.display()
        )?;
        return Ok(());
    }
    if !yes && (!terminal.stdin_is_tty || !terminal.stderr_is_tty) {
        anyhow::bail!("interactive confirmation requires TTYs; use --yes or --dry-run")
    }
    if !yes {
        writeln!(
            stdout,
            "Found {count} recognized session objects under {}.",
            root.display()
        )?;
        write!(stderr, "Repair these permissions? [y/N] ")?;
        stderr.flush()?;
        let mut answer = String::new();
        input.take(4096).read_line(&mut answer)?;
        if !matches!(answer.trim().to_ascii_lowercase().as_str(), "y" | "yes") {
            writeln!(stdout, "Cancelled; no permissions changed.")?;
            return Ok(());
        }
    }
    match apply_repair_plan(&plan) {
        Ok(changed) => {
            writeln!(
                stdout,
                "Repaired permissions for {changed} recognized session objects."
            )?;
            Ok(())
        }
        Err(error) => Err(error),
    }
}