whyno-core 0.5.0

Permission check pipeline, fix engine, and state types
Documentation
//! Shell command rendering for fix actions.
//!
//! Converts [`FixAction`] values into executable shell commands.
//! Paths containing spaces or shell metacharacters are quoted.

use super::FixAction;

/// Renders [`FixAction`] as executable shell command string.
///
/// Suitable for display or copy-paste into terminal.
/// Paths with spaces are single-quoted.
#[must_use]
pub fn render_command(action: &FixAction) -> String {
    match action {
        FixAction::Chmod { path, mode_change } => {
            format!("chmod {} {}", mode_change, quote_path(path))
        }
        FixAction::Chown { path, owner, group } => render_chown(path, *owner, *group),
        FixAction::SetAcl { path, entry } => {
            format!("setfacl -m {} {}", entry, quote_path(path))
        }
        FixAction::Remount {
            mountpoint,
            options,
        } => {
            format!("mount -o remount,{} {}", options, quote_path(mountpoint))
        }
        FixAction::Chattr { path, flags } => {
            format!("chattr {} {}", flags, quote_path(path))
        }
        FixAction::GrantCap { path, capability } => render_grant_cap(path.as_deref(), capability),
    }
}

fn render_chown(path: &std::path::Path, owner: Option<u32>, group: Option<u32>) -> String {
    let spec = match (owner, group) {
        (Some(u), Some(g)) => format!("{u}:{g}"),
        (Some(u), None) => format!("{u}"),
        (None, Some(g)) => format!(":{g}"),
        (None, None) => String::new(),
    };
    format!("chown {} {}", spec, quote_path(path))
}

/// Renders setcap command for granting a file capability.
///
/// When `path` is `None`, renders a descriptive recommendation without
/// a concrete path — the user must supply `--executable` at runtime.
fn render_grant_cap(path: Option<&std::path::Path>, capability: &str) -> String {
    match path {
        Some(p) => format!("setcap {capability}+ep {}", quote_path(p)),
        None => format!("setcap {capability}+ep <executable> (pass --executable to specify)"),
    }
}

fn quote_path(path: &std::path::Path) -> String {
    let s = path.display().to_string();
    if s.contains(' ') || s.contains('\'') || s.contains('"') {
        format!("'{}'", s.replace('\'', "'\\''"))
    } else {
        s
    }
}

#[cfg(test)]
#[path = "commands_tests.rs"]
mod tests;