use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::Result;
use super::immutable;
use crate::enforce::{Backend, Plan};
use crate::matcher::Matcher;
use crate::policy::{Policy, POLICY_FILE};
use crate::scan;
pub const AVAILABLE: bool = true;
pub const BACKEND: Backend = Backend::Immutable;
static INTERRUPTED: AtomicBool = AtomicBool::new(false);
extern "C" fn on_signal(_signal: libc::c_int) {
INTERRUPTED.store(true, Ordering::SeqCst);
}
pub struct Session {
applied: Vec<PathBuf>,
directories: usize,
pub warnings: Vec<String>,
}
impl Session {
pub fn files(&self) -> usize {
self.applied.len() - self.directories
}
pub fn directories(&self) -> usize {
self.directories
}
pub fn refused_directories(&self) -> usize {
self.directories
}
pub fn park(self) -> Result<()> {
unsafe {
libc::signal(libc::SIGINT, on_signal as *const () as libc::sighandler_t);
libc::signal(libc::SIGTERM, on_signal as *const () as libc::sighandler_t);
}
while !INTERRUPTED.load(Ordering::SeqCst) {
std::thread::sleep(std::time::Duration::from_millis(200));
}
Ok(())
}
}
impl Drop for Session {
fn drop(&mut self) {
for path in &self.applied {
let _ = immutable::clear(path);
}
}
}
pub fn start(_root: &Path, plan: &Plan) -> Result<Session> {
let (applied, directories, warnings) = apply(&plan.protected);
if applied.is_empty() && !plan.protected.is_empty() {
anyhow::bail!(
"nothing could be made immutable — {}",
warnings
.first()
.map(String::as_str)
.unwrap_or("no reason given")
);
}
Ok(Session {
applied,
directories,
warnings,
})
}
pub fn detach(root: &Path) -> Result<()> {
let protected = protected_paths(root)?;
let (applied, _, warnings) = apply(&protected);
for warning in &warnings {
eprintln!("ralon: warning: {warning}");
}
if applied.is_empty() && !protected.is_empty() {
anyhow::bail!(
"nothing could be made immutable — {}",
warnings
.first()
.map(String::as_str)
.unwrap_or("no reason given")
);
}
Ok(())
}
pub fn stop(root: &Path) -> Result<bool> {
let Ok(protected) = protected_paths(root) else {
return Ok(false);
};
Ok(!clear_leftovers(&protected).is_empty())
}
pub fn running(root: &Path) -> bool {
immutable::is_set(&root.join(POLICY_FILE))
}
pub fn silence_standard_handles() {}
pub fn leftovers(protected: &[PathBuf]) -> Vec<PathBuf> {
protected
.iter()
.flat_map(|path| immutable::targets(path))
.filter(|path| immutable::is_set(path))
.collect()
}
pub fn clear_leftovers(protected: &[PathBuf]) -> Vec<PathBuf> {
let mut cleared = Vec::new();
for path in protected {
let mut targets = immutable::targets(path);
targets.reverse();
for target in targets {
if !immutable::is_set(&target) {
continue;
}
let _ = immutable::clear(&target);
if !immutable::is_set(&target) {
cleared.push(target);
}
}
}
cleared
}
fn apply(protected: &[PathBuf]) -> (Vec<PathBuf>, usize, Vec<String>) {
let mut applied = Vec::new();
let mut directories = 0;
let mut warnings = Vec::new();
for path in protected {
for target in immutable::targets(path) {
let is_dir = target.is_dir();
match immutable::set(&target) {
Ok(()) => {
applied.push(target);
if is_dir {
directories += 1;
}
}
Err(errno) => warnings.push(immutable::explain(&target, errno)),
}
}
}
(applied, directories, warnings)
}
fn protected_paths(root: &Path) -> Result<Vec<PathBuf>> {
let policy = Policy::load(root)?;
let matcher = Matcher::new(&policy.patterns)?;
let found = scan::scan(&policy.root, &matcher)?;
scan::canonical_targets(&found)
}