use crate::error::{FezError, Result};
const PROTECTED: &[&str] = &[
"sshd.service",
"sshd.socket",
"ssh.service",
"ssh.socket",
"cockpit*",
"fez*",
];
fn matches_pattern(pattern: &str, unit: &str) -> bool {
match pattern.strip_suffix('*') {
Some(prefix) => unit.starts_with(prefix),
None => unit == pattern,
}
}
pub fn protected_match(unit: &str) -> Option<&'static str> {
PROTECTED.iter().copied().find(|p| matches_pattern(p, unit))
}
pub fn check_protected(unit: &str, force: bool) -> Result<()> {
if !force && protected_match(unit).is_some() {
return Err(FezError::Protected {
unit: unit.to_string(),
});
}
Ok(())
}
pub fn should_prompt(destructive: bool, is_tty: bool, force: bool) -> bool {
destructive && is_tty && !force
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_exact_and_glob() {
assert_eq!(protected_match("sshd.service"), Some("sshd.service"));
assert_eq!(protected_match("cockpit.service"), Some("cockpit*"));
assert_eq!(protected_match("cockpit.socket"), Some("cockpit*"));
assert_eq!(protected_match("fez.service"), Some("fez*"));
assert_eq!(protected_match("chronyd.service"), None);
}
#[test]
fn check_refuses_protected_without_force() {
let err = check_protected("sshd.service", false).unwrap_err();
assert_eq!(err.code(), "protected-unit");
}
#[test]
fn check_allows_protected_with_force() {
assert!(check_protected("sshd.service", true).is_ok());
}
#[test]
fn check_allows_unprotected() {
assert!(check_protected("chronyd.service", false).is_ok());
}
#[test]
fn prompt_only_for_destructive_human_without_force() {
assert!(should_prompt(true, true, false)); assert!(!should_prompt(true, false, false)); assert!(!should_prompt(true, true, true)); assert!(!should_prompt(false, true, false)); }
}