use std::path::PathBuf;
use std::process::Command;
use crate::kernel::{
encode_kernel_list, path_kernel_label, resolve_kernel_image, resolve_kernel_set,
};
fn sweep_stale_result_dirs(temp_root: &std::path::Path) {
let Ok(entries) = std::fs::read_dir(temp_root) else {
return;
};
let self_pid = std::process::id();
for entry in entries.flatten() {
let name = entry.file_name();
let Some(pid) = name
.to_str()
.and_then(|n| n.strip_prefix("ktstr-verifier-results-"))
.and_then(|p| p.parse::<i32>().ok())
else {
continue;
};
if pid <= 0 || pid == self_pid as i32 {
continue;
}
let dead = matches!(
nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), None),
Err(nix::errno::Errno::ESRCH),
);
if dead {
let _ = std::fs::remove_dir_all(entry.path());
}
}
}
pub(crate) fn run_verifier(
kernel: Vec<String>,
raw: bool,
profile: Option<String>,
nextest_profile: Option<String>,
scheduler: Option<String>,
include_eol: bool,
args: Vec<String>,
) -> Result<(), String> {
let mut cmd = Command::new("cargo");
cmd.args(ktstr::verifier::build_nextest_args(
nextest_profile.as_deref(),
&args,
));
if raw {
cmd.env(ktstr::KTSTR_VERIFIER_RAW_ENV, "1");
}
if let Some(p) = &profile {
cmd.env(ktstr::KTSTR_SCHEDULER_PROFILE_ENV, p);
}
if let Some(s) = &scheduler {
cmd.env(ktstr::KTSTR_VERIFIER_SCHEDULER_ENV, s);
}
let resolved: Vec<(String, PathBuf)> = if !kernel.is_empty() {
let r = resolve_kernel_set(&kernel, include_eol)?;
if r.is_empty() {
return Err(
"--kernel: every supplied value parsed to empty / whitespace; \
omit the flag for auto-discovery, or supply a kernel \
identifier"
.to_string(),
);
}
r
} else {
let path = resolve_kernel_image(None)?;
let label = path_kernel_label(&path);
vec![(label, path)]
};
cmd.env(ktstr::KTSTR_KERNEL_ENV, &resolved[0].1);
let encoded = encode_kernel_list(&resolved)?;
cmd.env(ktstr::KTSTR_KERNEL_LIST_ENV, encoded);
cmd.env(ktstr::KTSTR_ORCHESTRATED_ENV, "1");
let temp_root = std::env::temp_dir();
sweep_stale_result_dirs(&temp_root);
let result_dir = temp_root.join(format!("ktstr-verifier-results-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&result_dir);
if let Err(e) = std::fs::create_dir_all(&result_dir) {
return Err(format!(
"create verifier result dir {}: {e}",
result_dir.display()
));
}
cmd.env(ktstr::KTSTR_VERIFIER_RESULT_DIR_ENV, &result_dir);
let kernel_count = resolved.len();
eprintln!(
"cargo ktstr verifier: dispatching to nextest (verifier/ cells only) \
on {kernel_count} resolved kernel(s){raw}{fwd}",
raw = if raw { " (raw output)" } else { "" },
fwd = if args.is_empty() {
String::new()
} else {
format!(" forwarding to nextest: {}", args.join(" "))
},
);
let interrupt_guard = crate::interrupt::InterruptGuard::install();
let status = cmd
.status()
.map_err(|e| format!("spawn cargo nextest run: {e}"))?;
let records = ktstr::verifier::read_cell_records(&result_dir);
if let Some(tables) = ktstr::verifier::render_instruction_count_tables(&records) {
print!("{tables}");
}
if let Some(table) = ktstr::verifier::render_result_table(&records) {
print!("{table}");
}
let _ = std::fs::remove_dir_all(&result_dir);
let caught = interrupt_guard.interrupted();
drop(interrupt_guard);
if let Some(sig) = caught {
crate::interrupt::reraise(sig);
}
ktstr::verifier::classify_run_outcome(
status.success(),
records.is_empty(),
scheduler.as_deref(),
status.code(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sweep_removes_dead_pid_result_dirs_keeps_live() {
let base = std::env::temp_dir().join(format!("ktstr-vsweep-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&base).expect("mk temp root");
let dead = base.join("ktstr-verifier-results-2147483647");
let live = base.join(format!("ktstr-verifier-results-{}", std::process::id()));
std::fs::create_dir(&dead).expect("mk dead dir");
std::fs::create_dir(&live).expect("mk live dir");
sweep_stale_result_dirs(&base);
assert!(!dead.exists(), "a dead-owner result dir is reclaimed");
assert!(live.exists(), "our own (live) result dir is kept");
let _ = std::fs::remove_dir_all(&base);
}
}