use super::*;
pub(crate) fn ensure_kvm() -> Result<()> {
match std::fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/kvm")
{
Ok(_) => Ok(()),
Err(e) => {
let errno = e.raw_os_error();
if matches!(
errno,
Some(libc::ENOMEM)
| Some(libc::EBUSY)
| Some(libc::EMFILE)
| Some(libc::ENFILE)
| Some(libc::EAGAIN)
) {
let snapshot = vmm::host_resource_snapshot();
let errno_label = match errno {
Some(libc::ENOMEM) => "ENOMEM",
Some(libc::EBUSY) => "EBUSY",
Some(libc::EMFILE) => "EMFILE",
Some(libc::ENFILE) => "ENFILE",
Some(libc::EAGAIN) => "EAGAIN",
_ => unreachable!(),
};
Err(anyhow::Error::new(
crate::vmm::host_topology::ResourceContention {
reason: format!(
"/dev/kvm open: transient host errno {errno_label}: \
host resources: {snapshot}\n \
hint: KVM device open failed with a host-resource \
errno; another peer may be holding the budget. \
nextest will not retry; the SKIP banner records \
this attempt for stats tooling.",
),
},
))
} else {
Err(anyhow::Error::new(e).context(
"/dev/kvm not accessible — KVM is required for ktstr_test. \
Check that KVM is enabled and your user is in the kvm group.",
))
}
}
}
}
#[derive(Debug)]
pub struct KernelUnavailable {
pub diagnostic: String,
}
impl std::fmt::Display for KernelUnavailable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.diagnostic)
}
}
impl std::error::Error for KernelUnavailable {}
pub fn resolve_test_kernel() -> Result<PathBuf> {
if let Ok(path) = std::env::var(crate::KTSTR_TEST_KERNEL_ENV) {
let p = PathBuf::from(&path);
anyhow::ensure!(p.exists(), "KTSTR_TEST_KERNEL not found: {path}");
return Ok(p);
}
if let Some(p) = crate::find_kernel()? {
return Ok(p);
}
let image_name = if cfg!(target_arch = "aarch64") {
"Image"
} else {
"bzImage"
};
Err(anyhow::Error::new(KernelUnavailable {
diagnostic: format!(
"no kernel found — the test harness was likely invoked \
outside `cargo ktstr test` (which builds and injects a \
kernel automatically).\n \
hint: run `cargo ktstr test --kernel <path-or-version>` \
to drive this test, or set KTSTR_TEST_KERNEL=/path/to/{image_name} \
to point at a pre-built bootable image directly.\n \
hint: {kernel_hint}",
kernel_hint = crate::KTSTR_KERNEL_HINT,
),
}))
}
pub(crate) fn is_flock_timeout_message(rendered: &str) -> bool {
rendered.contains("timed out after") && rendered.contains("flock LOCK_")
}
pub(crate) fn acquire_test_kernel_lock_if_cached(
kernel_path: &Path,
) -> Result<Option<crate::cache::SharedLockGuard>> {
let Some(entry_dir) = kernel_path.parent() else {
return Ok(None);
};
let Some(key_os) = entry_dir.file_name() else {
return Ok(None);
};
let Some(cache_key) = key_os.to_str() else {
return Ok(None);
};
let Some(candidate_root) = entry_dir.parent() else {
return Ok(None);
};
let candidate_root_canon = match candidate_root.canonicalize() {
Ok(p) => p,
Err(_) => return Ok(None),
};
let resolved_root = match crate::cache::CacheDir::default_root() {
Ok(p) => p,
Err(_) => return Ok(None),
};
let resolved_root_canon = match resolved_root.canonicalize() {
Ok(p) => p,
Err(_) => return Ok(None),
};
if candidate_root_canon != resolved_root_canon {
return Ok(None);
}
let cache = crate::cache::CacheDir::with_root(resolved_root_canon);
match cache.acquire_shared_lock(cache_key) {
Ok(guard) => Ok(Some(guard)),
Err(e) => {
let rendered = format!("{e:#}");
if is_flock_timeout_message(&rendered) {
let snapshot = crate::vmm::host_resource_snapshot();
Err(anyhow::Error::new(
crate::vmm::host_topology::ResourceContention {
reason: format!(
"test kernel cache lock: {rendered}. host resources: \
{snapshot}\n \
hint: a concurrent `cargo ktstr kernel build` or \
another lockholder is preventing the test VM from \
reading the cached kernel image. nextest will not \
retry; the SKIP banner records this attempt for \
stats tooling. Wait for the holder PIDs above to \
finish, or kill them, then retry.",
),
},
))
} else {
Err(e)
}
}
}
}