use std::env;
use std::path::{Path, PathBuf};
use std::process::{self, Command};
use std::time::{Duration, SystemTime};
const POLL_INTERVAL: Duration = Duration::from_secs(2);
const TCC_DB_PATH: &str = "/Library/Application Support/com.apple.TCC/TCC.db";
fn read_mtime(path: &Path) -> Option<SystemTime> {
std::fs::metadata(path).ok()?.modified().ok()
}
fn probe_ax_in_fresh_subprocess() -> Option<bool> {
let exe = env::current_exe().ok()?;
let status = Command::new(exe).arg("ax-probe").status().ok()?;
Some(status.success())
}
pub fn spawn() {
tokio::spawn(async move {
let path = PathBuf::from(TCC_DB_PATH);
let mut last_mtime = read_mtime(&path);
log::info!(
"tcc_watch: watching {} (initial mtime: {:?})",
path.display(),
last_mtime
);
loop {
tokio::time::sleep(POLL_INTERVAL).await;
let current_mtime = read_mtime(&path);
if current_mtime == last_mtime {
continue;
}
log::info!(
"tcc_watch: TCC.db mtime changed ({last_mtime:?} -> {current_mtime:?}) — confirming AX via fresh subprocess"
);
last_mtime = current_mtime;
match probe_ax_in_fresh_subprocess() {
Some(true) => {
log::debug!("tcc_watch: probe confirms AX still granted");
}
Some(false) => {
log::error!(
"tcc_watch: AX revoked (TCC.db changed and fresh probe returned false) — daemon exiting"
);
process::exit(0);
}
None => {
log::warn!("tcc_watch: probe subprocess failed to spawn or run");
}
}
}
});
}