use std::fs;
use openmoat_hosts::Host;
use crate::hangs;
use crate::home::Home;
use crate::install::GUARD_BUDGET_S;
use crate::time;
const LEGACY_TIOCSTI: &str = "/proc/sys/dev/tty/legacy_tiocsti";
const HANG_WINDOW_MS: i64 = 7 * 24 * 3_600_000;
pub(super) fn legacy_tiocsti() -> Option<String> {
cfg!(target_os = "linux").then(|| fs::read_to_string(LEGACY_TIOCSTI).unwrap_or_default())
}
pub(super) fn tiocsti(codex: bool, setting: Option<&str>) -> Option<String> {
let value = setting.map(str::trim).filter(|v| codex && *v != "0")?;
let name = Host::Codex.display_name();
Some(if value.is_empty() {
format!(
"{name:<16} this kernel (before Linux 6.2) always lets a command type into your \
terminal (TIOCSTI), and Codex's sandbox cannot stop it; a 6.2+ kernel with \
dev.tty.legacy_tiocsti=0 does"
)
} else {
format!(
"{name:<16} dev.tty.legacy_tiocsti is {value}: Codex's sandbox cannot stop a command \
typing into your terminal (TIOCSTI); `sudo sysctl dev.tty.legacy_tiocsti=0` turns that off"
)
})
}
pub(super) fn hangs(home: &Home, now_ms: i64) -> Option<String> {
let path = home.hangs_path();
let (count, newest) = hangs::since(&path, now_ms - HANG_WINDOW_MS)?;
Some(format!(
"guard {count} call(s) denied with no decision within {GUARD_BUDGET_S} s \
in the last 7 days, the latest at {}; not in the audit log, listed in {}",
time::timestamp(newest),
path.display()
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tiocsti_warns_only_for_codex_on_a_kernel_that_allows_it() {
let warning = tiocsti(true, Some("1\n")).unwrap();
assert!(warning.contains("legacy_tiocsti is 1:"), "{warning}");
assert!(
warning.contains("sysctl dev.tty.legacy_tiocsti=0"),
"{warning}"
);
assert_eq!(tiocsti(true, Some("0\n")), None);
assert_eq!(tiocsti(true, None), None, "not Linux");
let old_kernel = tiocsti(true, Some("")).unwrap();
assert!(old_kernel.contains("before Linux 6.2"), "{old_kernel}");
assert_eq!(tiocsti(false, Some("1\n")), None);
}
#[test]
fn hangs_older_than_a_week_are_not_reported() {
let dir = tempfile::tempdir().unwrap();
let home = Home::at(dir.path().to_path_buf());
let now = HANG_WINDOW_MS * 2;
assert_eq!(hangs(&home, now), None);
fs::write(home.hangs_path(), format!("1 codex\n{now} cursor\n")).unwrap();
let row = hangs(&home, now).unwrap();
assert!(row.contains("1 call(s) denied"), "{row}");
}
}