diff --git a/src/tools/kata-ctl/src/arch/x86_64/mod.rs b/src/tools/kata-ctl/src/arch/x86_64/mod.rs index 2f27dc2a44..5618a44512 100644 --- a/src/tools/kata-ctl/src/arch/x86_64/mod.rs +++ b/src/tools/kata-ctl/src/arch/x86_64/mod.rs @@ -7,11 +7,47 @@ pub use arch_specific::*; mod arch_specific { - use anyhow::Result; + use anyhow::{anyhow, Result}; + use crate::check; + + const PROC_CPUINFO: &str = "/proc/cpuinfo"; + const CPUINFO_DELIMITER: &str = "\nprocessor"; + const CPUINFO_FLAGS_TAG: &str = "flags"; + const CPU_FLAGS_INTEL: &'static [&'static str] = &["lm", "sse4_1", "vmx"]; + const CPU_ATTRIBS_INTEL: &'static [&'static str] = &["GenuineIntel"]; + + // check cpu + fn check_cpu() -> Result<()> { + println!("INFO: check CPU: x86_64"); + + let cpu_info = check::get_single_cpu_info(PROC_CPUINFO, CPUINFO_DELIMITER)?; + + let cpu_flags = check::get_cpu_flags(&cpu_info, CPUINFO_FLAGS_TAG) + .map_err(|e| anyhow!("Error parsing CPU flags, file {:?}, {:?}", PROC_CPUINFO, e))?; + + // perform checks + // TODO: Perform checks based on hypervisor type + // TODO: Add more information to output (see kata-check in go tool); adjust formatting + let missing_cpu_attributes = check::check_cpu_attribs(&cpu_info, CPU_ATTRIBS_INTEL)?; + if missing_cpu_attributes.len() > 0 { + eprintln!("WARNING: Missing CPU attributes {:?}", missing_cpu_attributes); + } + let missing_cpu_flags = check::check_cpu_flags(&cpu_flags, CPU_FLAGS_INTEL)?; + if missing_cpu_flags.len() > 0 { + eprintln!("WARNING: Missing CPU flags {:?}", missing_cpu_flags); + } + + Ok(()) + } + pub fn check(_global_args: clap::ArgMatches) -> Result<()> { println!("INFO: check: x86_64"); + let _cpu_result = check_cpu(); + + // TODO: collect outcome of tests to determine if checks pass or not + Ok(()) } } diff --git a/src/tools/kata-ctl/src/check.rs b/src/tools/kata-ctl/src/check.rs index be39e31e06..9317b903a1 100644 --- a/src/tools/kata-ctl/src/check.rs +++ b/src/tools/kata-ctl/src/check.rs @@ -5,8 +5,109 @@ // Contains checks that are not architecture-specific -use anyhow::{Result}; +use anyhow::{anyhow, Result}; +use std::fs; + +fn get_cpu_info(cpu_info_file: &str) -> Result { + let contents = fs::read_to_string(cpu_info_file)?; + Ok(contents) +} + +// get_single_cpu_info returns the contents of the first cpu from +// the specified cpuinfo file by parsing based on a specified delimiter +pub fn get_single_cpu_info(cpu_info_file: &str, substring: &str) -> Result { + let contents = get_cpu_info(cpu_info_file)?; + + if contents.is_empty() { + return Err(anyhow!("cpu_info string is empty"))?; + } + + let subcontents: Vec<&str> = contents.split(substring).collect(); + let result = subcontents + .first() + .ok_or("error splitting contents of cpuinfo") + .map_err(|e| anyhow!(e))? + .to_string(); + + Ok(result) +} + +// get_cpu_flags returns a string of cpu flags from cpuinfo, passed in +// as a string +pub fn get_cpu_flags(cpu_info: &str, cpu_flags_tag: &str) -> Result { + if cpu_info.is_empty() { + return Err(anyhow!("cpu_info string is empty"))?; + } + + let subcontents: Vec<&str> = cpu_info.split("\n").collect(); + for line in subcontents { + if line.starts_with(cpu_flags_tag) { + let line_data: Vec<&str> = line.split(":").collect(); + let flags = line_data + .last() + .ok_or("error splitting flags in cpuinfo") + .map_err(|e| anyhow!(e))? + .to_string(); + return Ok(flags); + } + } + + Ok("".to_string()) +} + +// get_missing_strings searches for required (strings) in data and returns +// a vector containing the missing strings +fn get_missing_strings(data: &str, required: &'static [&'static str]) -> Result> { + let data_vec: Vec <&str> = data.split_whitespace().collect(); + + let mut missing: Vec = Vec::new(); + + for item in required { + if !data_vec.contains(&item) { + missing.push(item.to_string()); + } + } + + Ok(missing) +} + +pub fn check_cpu_flags(retrieved_flags: &str, required_flags: &'static [&'static str]) -> Result> { + let missing_flags = get_missing_strings(retrieved_flags, required_flags)?; + + Ok(missing_flags) +} + +pub fn check_cpu_attribs(cpu_info: &str, required_attribs: &'static [&'static str]) -> Result> { + let mut cpu_info_processed = cpu_info.replace("\t", ""); + cpu_info_processed = cpu_info_processed.replace("\n", " "); + + let missing_attribs = get_missing_strings(&cpu_info_processed, required_attribs)?; + Ok(missing_attribs) +} pub fn run_network_checks() -> Result<()> { unimplemented!("Tests using network checks not implemented"); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_cpu_info_empty_input() { + let expected = "No such file or directory (os error 2)"; + let actual = get_cpu_info("").err().unwrap().to_string(); + assert_eq!(expected, actual); + + let actual = get_single_cpu_info("", "\nprocessor").err().unwrap().to_string(); + assert_eq!(expected, actual); + } + + #[test] + fn test_get_cpu_flags_empty_input() { + let expected = "cpu_info string is empty"; + let actual = get_cpu_flags("", "").err().unwrap().to_string(); + assert_eq!(expected, actual); + } +} +