use std::fs::File;
use std::os::unix::io::AsRawFd;
use nix::sys::stat::fstat;
use oci_spec::runtime::{LinuxNamespaceType, Spec};
use rust_criu::{Criu, criu_ns_to_key};
use crate::error::LibcontainerError;
pub const CRIU_VERSION_MINIMUM: u32 = 31500;
fn compare_criu_version(version: u32, min_version: u32) -> Result<(), LibcontainerError> {
if version < min_version {
return Err(LibcontainerError::Other(format!(
"CRIU version {} is below minimum required version {}",
version, min_version,
)));
}
Ok(())
}
pub fn check_criu_version(min_version: u32) -> Result<(), LibcontainerError> {
let mut criu = Criu::new()
.map_err(|e| LibcontainerError::Other(format!("failed to create CRIU instance: {}", e)))?;
let version = criu
.get_criu_version()
.map_err(|e| LibcontainerError::Other(format!("CRIU version check failed: {}", e)))?;
compare_criu_version(version, min_version)
}
fn ns_name(ns_type: LinuxNamespaceType) -> &'static str {
match ns_type {
LinuxNamespaceType::Network => "net",
LinuxNamespaceType::Pid => "pid",
LinuxNamespaceType::Mount => "mnt",
LinuxNamespaceType::Ipc => "ipc",
LinuxNamespaceType::Uts => "uts",
LinuxNamespaceType::User => "user",
LinuxNamespaceType::Cgroup => "cgroup",
LinuxNamespaceType::Time => "time",
}
}
fn get_namespace_path(spec: &Spec, ns_type: LinuxNamespaceType) -> Option<String> {
let linux = spec.linux().as_ref()?;
let namespaces = linux.namespaces().as_ref()?;
namespaces
.iter()
.find_map(|ns: &oci_spec::runtime::LinuxNamespace| {
if ns.typ() == ns_type {
ns.path().as_ref().map(|p| p.to_string_lossy().to_string())
} else {
None
}
})
}
pub fn handle_checkpointing_external_namespaces(
criu: &mut Criu,
spec: &Spec,
ns_type: LinuxNamespaceType,
) -> Result<(), LibcontainerError> {
let ns_path = match get_namespace_path(spec, ns_type) {
Some(path) => path,
None => return Ok(()),
};
let ns_file = File::open(&ns_path).map_err(|err| {
tracing::error!(?ns_path, ?err, "failed to open namespace for checkpoint");
LibcontainerError::OtherIO(err)
})?;
let stat = fstat(ns_file.as_raw_fd()).map_err(|err| {
tracing::error!(?ns_path, ?err, "failed to stat namespace");
LibcontainerError::Other(format!("failed to stat namespace: {}", err))
})?;
let name = ns_name(ns_type);
let external = format!("{}[{}]:{}", name, stat.st_ino, criu_ns_to_key(name));
tracing::debug!(?external, "adding external namespace for checkpoint");
criu.add_external(external);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compare_criu_version_ok() {
assert!(compare_criu_version(31500, 31500).is_ok());
assert!(compare_criu_version(31600, 31500).is_ok());
assert!(compare_criu_version(40000, 31500).is_ok());
}
#[test]
fn test_compare_criu_version_too_low() {
assert!(compare_criu_version(31499, 31500).is_err());
assert!(compare_criu_version(30000, 31500).is_err());
}
}