diff --git a/src/libs/kata-sys-util/Cargo.toml b/src/libs/kata-sys-util/Cargo.toml index 40381b4d05..0d6cff91fb 100644 --- a/src/libs/kata-sys-util/Cargo.toml +++ b/src/libs/kata-sys-util/Cargo.toml @@ -11,6 +11,7 @@ license = "Apache-2.0" edition = "2018" [dependencies] +anyhow = "1.0.31" byteorder = "1.4.3" cgroups = { package = "cgroups-rs", version = "0.3.2" } chrono = "0.4.0" diff --git a/src/libs/kata-sys-util/src/cpu.rs b/src/libs/kata-sys-util/src/cpu.rs new file mode 100644 index 0000000000..7e1bef38de --- /dev/null +++ b/src/libs/kata-sys-util/src/cpu.rs @@ -0,0 +1,372 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use anyhow::{anyhow, Result}; + +#[allow(dead_code)] +const ERR_NO_CPUINFO: &str = "cpu_info string is empty"; + +pub const PROC_CPUINFO: &str = "/proc/cpuinfo"; + +#[cfg(target_arch = "x86_64")] +pub const CPUINFO_DELIMITER: &str = "\nprocessor"; +#[cfg(target_arch = "x86_64")] +pub const CPUINFO_FLAGS_TAG: &str = "flags"; + +fn read_file_contents(file_path: &str) -> Result { + let contents = std::fs::read_to_string(file_path)?; + 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 = read_file_contents(cpu_info_file)?; + + if contents.is_empty() { + return Err(anyhow!(ERR_NO_CPUINFO)); + } + + 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 +#[cfg(any(target_arch = "s390x", target_arch = "x86_64"))] +pub fn get_cpu_flags(cpu_info: &str, cpu_flags_tag: &str) -> Result { + if cpu_info.is_empty() { + return Err(anyhow!(ERR_NO_CPUINFO)); + } + + if cpu_flags_tag.is_empty() { + return Err(anyhow!("cpu flags delimiter string is empty"))?; + } + + get_cpu_flags_from_file(cpu_info, cpu_flags_tag) +} + +// get a list of cpu flags in cpu_info_flags +// +// cpu_info is the content of cpuinfo file passed in as a string +// returns empty Vec if no flags are found +#[cfg(any(target_arch = "s390x", target_arch = "x86_64"))] +pub fn get_cpu_flags_vec(cpu_info: &str, cpu_flags_tag: &str) -> Result> { + if cpu_info.is_empty() { + return Err(anyhow!(ERR_NO_CPUINFO)); + } + + if cpu_flags_tag.is_empty() { + return Err(anyhow!("cpu flags delimiter string is empty"))?; + } + + let flags = get_cpu_flags_from_file(cpu_info, cpu_flags_tag)?; + + // take each flag, trim whitespace, convert to String, and add to list + // skip the first token in the iterator since it is empty + let flags_vec: Vec = flags + .split(' ') + .skip(1) + .map(|f| f.trim().to_string()) + .collect::>(); + + Ok(flags_vec) +} + +// check if the given flag exists in the given flags_vec +// +// flags_vec can be created by calling get_cpu_flags_vec +#[cfg(any(target_arch = "s390x", target_arch = "x86_64"))] +pub fn contains_cpu_flag(flags_vec: &[String], flag: &str) -> Result { + if flag.is_empty() { + return Err(anyhow!("parameter specifying flag to look for is empty"))?; + } + + Ok(flags_vec.iter().any(|f| f == flag)) +} + +// get a String containing the cpu flags in cpu_info +// +// this function returns the list of flags as a single String +// if no flags are found, returns an empty String +#[cfg(any(target_arch = "s390x", target_arch = "x86_64"))] +fn get_cpu_flags_from_file(cpu_info: &str, cpu_flags_tag: &str) -> Result { + 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()) +} + +#[cfg(any(target_arch = "s390x", target_arch = "x86_64"))] +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::io::Write; + use tempfile::tempdir; + + #[test] + fn test_get_single_cpu_info() { + // Valid cpuinfo example + let dir = tempdir().unwrap(); + let file_path_full = dir.path().join("cpuinfo_full"); + let path_full = file_path_full.clone(); + let mut file_full = fs::File::create(file_path_full).unwrap(); + let contents = "processor : 0\nvendor_id : VendorExample\nflags : flag_1 flag_2 flag_3 flag_4\nprocessor : 1\n".to_string(); + writeln!(file_full, "{}", contents).unwrap(); + + // Empty cpuinfo example + let file_path_empty = dir.path().join("cpuinfo_empty"); + let path_empty = file_path_empty.clone(); + let mut _file_empty = fs::File::create(file_path_empty).unwrap(); + + #[derive(Debug)] + struct TestData<'a> { + cpuinfo_path: &'a str, + processor_delimiter_str: &'a str, + result: Result, + } + let tests = &[ + // Failure scenarios + TestData { + cpuinfo_path: "", + processor_delimiter_str: "", + result: Err(anyhow!("No such file or directory (os error 2)")), + }, + TestData { + cpuinfo_path: &path_empty.as_path().display().to_string(), + processor_delimiter_str: "\nprocessor", + result: Err(anyhow!(ERR_NO_CPUINFO)), + }, + // Success scenarios + TestData { + cpuinfo_path: &path_full.as_path().display().to_string(), + processor_delimiter_str: "\nprocessor", + result: Ok( + "processor : 0\nvendor_id : VendorExample\nflags : flag_1 flag_2 flag_3 flag_4" + .to_string(), + ), + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test[{}]: {:?}", i, d); + let result = get_single_cpu_info(d.cpuinfo_path, d.processor_delimiter_str); + let msg = format!("{}, result: {:?}", msg, result); + + if d.result.is_ok() { + assert_eq!( + result.as_ref().unwrap(), + d.result.as_ref().unwrap(), + "{}", + msg + ); + continue; + } + + let expected_error = format!("{}", d.result.as_ref().unwrap_err()); + let actual_error = format!("{}", result.unwrap_err()); + assert!(actual_error == expected_error, "{}", msg); + } + } + + #[test] + fn test_get_cpu_flags() { + let contents = "processor : 0\nvendor_id : VendorExample\nflags : flag_1 flag_2 flag_3 flag_4\nprocessor : 1\n"; + + #[derive(Debug)] + struct TestData<'a> { + cpu_info_str: &'a str, + cpu_flags_tag: &'a str, + result: Result, + } + let tests = &[ + // Failure scenarios + TestData { + cpu_info_str: "", + cpu_flags_tag: "", + result: Err(anyhow!(ERR_NO_CPUINFO)), + }, + TestData { + cpu_info_str: "", + cpu_flags_tag: "flags", + result: Err(anyhow!(ERR_NO_CPUINFO)), + }, + TestData { + cpu_info_str: contents, + cpu_flags_tag: "", + result: Err(anyhow!("cpu flags delimiter string is empty")), + }, + // Success scenarios + TestData { + cpu_info_str: contents, + cpu_flags_tag: "flags", + result: Ok(" flag_1 flag_2 flag_3 flag_4".to_string()), + }, + TestData { + cpu_info_str: contents, + cpu_flags_tag: "flags_err", + result: Ok("".to_string()), + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test[{}]: {:?}", i, d); + let result = get_cpu_flags(d.cpu_info_str, d.cpu_flags_tag); + let msg = format!("{}, result: {:?}", msg, result); + + if d.result.is_ok() { + assert_eq!( + result.as_ref().unwrap(), + d.result.as_ref().unwrap(), + "{}", + msg + ); + continue; + } + + let expected_error = format!("{}", d.result.as_ref().unwrap_err()); + let actual_error = format!("{}", result.unwrap_err()); + assert!(actual_error == expected_error, "{}", msg); + } + } + + #[test] + fn test_get_cpu_flags_vec() { + let contents = "processor : 0\nvendor_id : VendorExample\nflags : flag_1 flag_2 flag_3 flag_4\nprocessor : 1\n"; + + #[derive(Debug)] + struct TestData<'a> { + cpu_info_str: &'a str, + cpu_flags_tag: &'a str, + result: Result>, + } + let tests = &[ + // Failure scenarios + TestData { + cpu_info_str: "", + cpu_flags_tag: "", + result: Err(anyhow!(ERR_NO_CPUINFO)), + }, + TestData { + cpu_info_str: "", + cpu_flags_tag: "flags", + result: Err(anyhow!(ERR_NO_CPUINFO)), + }, + TestData { + cpu_info_str: contents, + cpu_flags_tag: "", + result: Err(anyhow!("cpu flags delimiter string is empty")), + }, + // Success scenarios + TestData { + cpu_info_str: contents, + cpu_flags_tag: "flags", + result: Ok(vec![ + "flag_1".to_string(), + "flag_2".to_string(), + "flag_3".to_string(), + "flag_4".to_string(), + ]), + }, + TestData { + cpu_info_str: contents, + cpu_flags_tag: "flags_err", + result: Ok(Vec::new()), + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test[{}]: {:?}", i, d); + let result = get_cpu_flags_vec(d.cpu_info_str, d.cpu_flags_tag); + let msg = format!("{}, result: {:?}", msg, result); + + if d.result.is_ok() { + assert_eq!( + result.as_ref().unwrap(), + d.result.as_ref().unwrap(), + "{}", + msg + ); + continue; + } + + let expected_error = format!("{}", d.result.as_ref().unwrap_err()); + let actual_error = format!("{}", result.unwrap_err()); + assert!(actual_error == expected_error, "{}", msg); + } + } + + #[test] + fn test_contains_cpu_flag() { + let flags_vec = vec![ + "flag_1".to_string(), + "flag_2".to_string(), + "flag_3".to_string(), + "flag_4".to_string(), + ]; + + #[derive(Debug)] + struct TestData<'a> { + cpu_flags_vec: &'a Vec, + cpu_flag: &'a str, + result: Result, + } + let tests = &[ + // Failure scenarios + TestData { + cpu_flags_vec: &flags_vec, + cpu_flag: "flag_5", + result: Ok(false), + }, + TestData { + cpu_flags_vec: &flags_vec, + cpu_flag: "", + result: Err(anyhow!("parameter specifying flag to look for is empty")), + }, + // Success scenarios + TestData { + cpu_flags_vec: &flags_vec, + cpu_flag: "flag_1", + result: Ok(true), + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test[{}]: {:?}", i, d); + let result = contains_cpu_flag(&d.cpu_flags_vec, d.cpu_flag); + let msg = format!("{}, result: {:?}", msg, result); + + if d.result.is_ok() { + assert_eq!( + result.as_ref().unwrap(), + d.result.as_ref().unwrap(), + "{}", + msg + ); + continue; + } + + let expected_error = format!("{}", d.result.as_ref().unwrap_err()); + let actual_error = format!("{}", result.unwrap_err()); + assert!(actual_error == expected_error, "{}", msg); + } + } +} diff --git a/src/libs/kata-sys-util/src/lib.rs b/src/libs/kata-sys-util/src/lib.rs index 2c90adb7c4..f7dad32906 100644 --- a/src/libs/kata-sys-util/src/lib.rs +++ b/src/libs/kata-sys-util/src/lib.rs @@ -6,6 +6,7 @@ #[macro_use] extern crate slog; +pub mod cpu; pub mod device; pub mod fs; pub mod hooks;