mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
+ Sort and group the imports + Normalize and format comments (100 characters width) Command used: $ cargo +nightly fmt -- Acked-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Marc Hartmayer <marc@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
325 lines
10 KiB
Rust
325 lines
10 KiB
Rust
// SPDX-License-Identifier: MIT
|
|
//
|
|
// Copyright IBM Corp. 2025
|
|
|
|
//! I/O utilities for the PV Info Tool
|
|
|
|
use std::collections::HashMap;
|
|
use std::path::Path;
|
|
use std::str;
|
|
|
|
use anyhow::{Context, Result};
|
|
use pv_core::misc::{read_file, read_file_string, try_parse_u64, Flags, Msb0Flags64};
|
|
|
|
use crate::constants::*;
|
|
|
|
// Verify that the Ultravisor directory exists
|
|
pub fn check_uv_exists() -> Result<()> {
|
|
let uv_path = Path::new(BASE_DIR);
|
|
if !uv_path.exists() {
|
|
return Err(std::io::Error::new(
|
|
std::io::ErrorKind::NotFound,
|
|
"Ultravisor directory missing",
|
|
))
|
|
.context("This system is neither a SE-host or SE-guest");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// File readers
|
|
pub fn read_hex_from_file<P: AsRef<Path>>(path: P, context: &str) -> Option<u64> {
|
|
// Add precise context for which file failed
|
|
let content_str = read_file_string(path.as_ref(), context).ok()?;
|
|
let first_line = content_str.lines().next()?.trim();
|
|
if first_line.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
match try_parse_u64(first_line, context) {
|
|
Ok(val) if val != 0 => Some(val),
|
|
Ok(_) => None, // treat "0x0" as absent
|
|
Err(e) => panic!(
|
|
"Failed to parse hex value in {} ({}): {}",
|
|
path.as_ref().display(),
|
|
context,
|
|
e
|
|
),
|
|
}
|
|
}
|
|
|
|
pub fn read_integer_from_file<P: AsRef<Path>>(path: P, context: &str) -> Option<u64> {
|
|
let content_str = read_file_string(path.as_ref(), context).ok()?;
|
|
let first_line = content_str.lines().next()?.trim();
|
|
if first_line.is_empty() {
|
|
return None;
|
|
}
|
|
match first_line.parse::<u64>() {
|
|
Ok(val) => Some(val),
|
|
Err(e) => panic!(
|
|
"Failed to parse integer in {} ({}): {}",
|
|
path.as_ref().display(),
|
|
context,
|
|
e
|
|
),
|
|
}
|
|
}
|
|
|
|
pub fn read_bool_from_file<P: AsRef<Path>>(path: P, context: &str) -> bool {
|
|
let content = read_file(path.as_ref(), context).unwrap_or_else(|e| {
|
|
panic!(
|
|
"Failed to read {} ({}): {}",
|
|
path.as_ref().display(),
|
|
context,
|
|
e
|
|
)
|
|
});
|
|
|
|
let content_str = str::from_utf8(&content)
|
|
.unwrap_or_else(|_| panic!("Invalid UTF-8 in {} ({})", path.as_ref().display(), context));
|
|
|
|
match content_str.lines().next().unwrap_or("").trim() {
|
|
"1" => true,
|
|
"0" => false,
|
|
other => panic!(
|
|
"Invalid boolean value in {} ({}): {}",
|
|
path.as_ref().display(),
|
|
context,
|
|
other
|
|
),
|
|
}
|
|
}
|
|
|
|
// Collectors
|
|
pub fn collect_bit_messages(hex_value: u64, desc_content: &str) -> Vec<String> {
|
|
let flags = Msb0Flags64::from(hex_value);
|
|
let mut messages = Vec::new();
|
|
|
|
for (line_index, line) in desc_content.lines().enumerate() {
|
|
if flags.is_set(line_index as u8) {
|
|
if line.to_lowercase().contains("reserved") {
|
|
messages.push(format!("{line} Bit-{line_index}"));
|
|
} else {
|
|
messages.push(line.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
if messages.is_empty() {
|
|
messages.push("No matching messages. But these bits are ON:".into());
|
|
for bit in 0..64 {
|
|
if flags.is_set(bit) {
|
|
messages.push(format!("- Bit {bit} is ON"));
|
|
}
|
|
}
|
|
}
|
|
|
|
messages
|
|
}
|
|
|
|
pub fn collect_version_flags(hex_value: u64) -> Vec<String> {
|
|
let flags = Msb0Flags64::from(hex_value);
|
|
let mut versions = Vec::new();
|
|
|
|
for bit in 0..64 {
|
|
if flags.is_set(bit as u8) {
|
|
let version = (bit + 1) * 0x100;
|
|
versions.push(format!("version {version:x} hex is supported"));
|
|
}
|
|
}
|
|
|
|
versions
|
|
}
|
|
|
|
pub fn collect_limits(query_dir: &Path) -> HashMap<String, u64> {
|
|
let mut map = HashMap::new();
|
|
for (file, desc) in LIMITS {
|
|
if let Some(val) = read_integer_from_file(query_dir.join(file), file) {
|
|
map.insert(desc.to_string(), val);
|
|
}
|
|
}
|
|
map
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
//! Unit Tests for io_utils
|
|
|
|
use std::io::{self, Write};
|
|
use std::path::Path;
|
|
|
|
use tempfile::{tempdir, NamedTempFile};
|
|
|
|
use super::*;
|
|
|
|
// Tests check_uv_exists() by observing the real BASE_DIR at test runtime
|
|
// if BASE_DIR exists on the machine running the tests, check_uv_exists() must return Ok
|
|
// otherwise, it must return Err(anyhow::Error) whose cause is an io::ErrorKind::NotFound,
|
|
// and whose message also contains the added context string
|
|
|
|
#[test]
|
|
fn test_check_uv_exists_behaviour_matches_base_dir() {
|
|
let base_dir = BASE_DIR;
|
|
let uv_path = Path::new(base_dir);
|
|
let res = check_uv_exists();
|
|
|
|
if uv_path.exists() {
|
|
assert!(
|
|
res.is_ok(),
|
|
"BASE_DIR exists but check_uv_exists returned Err: {res:?}"
|
|
);
|
|
} else {
|
|
assert!(
|
|
res.is_err(),
|
|
"BASE_DIR missing but check_uv_exists returned Ok"
|
|
);
|
|
if let Err(e) = res {
|
|
// downcast anyhow::Error into std::io::Error
|
|
if let Some(io_err) = e.downcast_ref::<io::Error>() {
|
|
assert_eq!(io_err.kind(), io::ErrorKind::NotFound);
|
|
} else {
|
|
panic!("Expected std::io::Error inside anyhow::Error, got: {e:?}");
|
|
}
|
|
|
|
// ensure both the base error and context message appear
|
|
let msg = format!("{e}");
|
|
assert!(msg.contains("Ultravisor directory missing"));
|
|
assert!(msg.contains("This system is neither a SE-host or SE-guest"));
|
|
}
|
|
}
|
|
}
|
|
|
|
// read_hex_from_file tests
|
|
|
|
#[test]
|
|
fn test_read_hex_from_file_valid() {
|
|
let mut file = NamedTempFile::new().unwrap();
|
|
writeln!(file, "0x1a2b").unwrap();
|
|
let got = read_hex_from_file(file.path(), "test");
|
|
assert_eq!(got, Some(0x1a2b));
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_hex_from_file_zero_is_none() {
|
|
let mut file = NamedTempFile::new().unwrap();
|
|
writeln!(file, "0x0").unwrap();
|
|
assert_eq!(read_hex_from_file(file.path(), "test"), None);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "Failed to parse hex value")]
|
|
fn test_read_hex_from_file_invalid_panics() {
|
|
let mut file = NamedTempFile::new().unwrap();
|
|
writeln!(file, "not_hex").unwrap();
|
|
// should panic now
|
|
let _ = read_hex_from_file(file.path(), "test");
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_hex_from_file_missing_returns_none() {
|
|
// missing file -> None
|
|
let dir = tempdir().unwrap();
|
|
let missing = dir.path().join("no_such_file");
|
|
assert_eq!(read_hex_from_file(missing, "test"), None);
|
|
}
|
|
|
|
// read_integer_from_file tests
|
|
|
|
#[test]
|
|
fn test_read_integer_from_file_valid() {
|
|
let mut file = NamedTempFile::new().unwrap();
|
|
writeln!(file, "12345").unwrap();
|
|
assert_eq!(read_integer_from_file(file.path(), "test"), Some(12345));
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_integer_from_file_empty_returns_none() {
|
|
let mut file = NamedTempFile::new().unwrap();
|
|
writeln!(file).unwrap();
|
|
assert_eq!(read_integer_from_file(file.path(), "test"), None);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "Failed to parse integer")]
|
|
fn test_read_integer_from_file_invalid_panics() {
|
|
let mut file2 = NamedTempFile::new().unwrap();
|
|
writeln!(file2, "abc").unwrap();
|
|
// should panic now
|
|
let _ = read_integer_from_file(file2.path(), "test");
|
|
}
|
|
|
|
// read_bool_from_file tests
|
|
|
|
#[test]
|
|
fn test_read_bool_from_file_true() {
|
|
let mut file = NamedTempFile::new().unwrap();
|
|
writeln!(file, "1").unwrap();
|
|
assert!(read_bool_from_file(file.path(), "test"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_bool_from_file_false() {
|
|
let mut file = NamedTempFile::new().unwrap();
|
|
writeln!(file, "0").unwrap();
|
|
assert!(!read_bool_from_file(file.path(), "test"));
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "Invalid boolean value")]
|
|
fn test_read_bool_from_file_invalid_value_panics() {
|
|
let mut file = NamedTempFile::new().unwrap();
|
|
writeln!(file, "yes").unwrap();
|
|
// should panic with clear error
|
|
let _ = read_bool_from_file(file.path(), "test");
|
|
}
|
|
|
|
// collect_bit_messages tests
|
|
|
|
#[test]
|
|
fn test_collect_bit_messages_matches_and_reserved() {
|
|
// lines: index 0 -> bit 63, index 1 -> bit 62, index 2 -> bit 61
|
|
let desc = "First Feature\nreserved for future use\nThird Feature";
|
|
|
|
// set bit for line 0 and line 2
|
|
let hex = (1u64 << 63) | (1u64 << 61);
|
|
let messages = collect_bit_messages(hex, desc);
|
|
|
|
// Expect "First Feature" and "Third Feature" (and the reserved line should get the "
|
|
// Bit-<index>" if matched)
|
|
assert!(messages.iter().any(|m| m == "First Feature"));
|
|
assert!(messages.iter().any(|m| m == "Third Feature"));
|
|
// reserved wasn't set here; now test reserved specifically below
|
|
}
|
|
|
|
#[test]
|
|
fn test_collect_bit_messages_reserved_line() {
|
|
let desc = "one\nReserved entry\nthree";
|
|
// set the bit corresponding to line index 1 -> bit 62
|
|
let hex = 1u64 << (63 - 1);
|
|
let messages = collect_bit_messages(hex, desc);
|
|
assert_eq!(messages.len(), 1);
|
|
assert_eq!(messages[0], "Reserved entry Bit-1");
|
|
}
|
|
|
|
#[test]
|
|
fn test_collect_bit_messages_fallback_lists_bits() {
|
|
let desc = "only one line";
|
|
// pick a bit that does not map to line 0 (63) so fallback triggers; e.g. bit 5
|
|
let hex = 1u64 << 5;
|
|
let messages = collect_bit_messages(hex, desc);
|
|
assert!(!messages.is_empty());
|
|
assert!(messages[0].starts_with("No matching messages"));
|
|
assert!(messages.iter().any(|m| m.contains("Bit 5")));
|
|
}
|
|
|
|
// collect_version_flags tests
|
|
|
|
#[test]
|
|
fn test_collect_version_flags_two_bits() {
|
|
// top two bits set => versions 0x100 and 0x200 should be present
|
|
let hex = (1u64 << 63) | (1u64 << 62);
|
|
let versions = collect_version_flags(hex);
|
|
assert!(versions.contains(&"version 100 hex is supported".to_string()));
|
|
assert!(versions.contains(&"version 200 hex is supported".to_string()));
|
|
}
|
|
}
|