Files
s390-tools/rust/pvapconfig/src/main.rs
Steffen Eiden 459a257568 rust/pvapconfig: Fix typos and clippy findings
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00

669 lines
24 KiB
Rust

// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! pvapconfig - Tool to automatically set up the AP configuration
//! within an IBM Secure Execution guest.
//
mod ap;
mod cli;
mod config;
mod helper;
mod uv;
use ap::{Apqn, ApqnList};
use cli::ARGS;
use config::{ApConfigEntry, ApConfigList};
use helper::{LockFile, PATH_PVAPCONFIG_LOCK};
use pv_core::uv::{ListableSecretType, SecretList};
use std::process::ExitCode;
use utils::release_string;
/// Simple macro for
/// if Cli::verbose() {
/// print!(...);
/// }
macro_rules! info {
($($arg:tt)*) => {{
if ARGS.verbose() {
print!($($arg)*);
}
}};
}
/// Simple macro for the main function only
/// Does a eprintln of the arguments and then
/// return with exit failure.
macro_rules! println_and_exit_failure {
($($arg:tt)*) => {{
eprintln!($($arg)*);
return ExitCode::FAILURE;
}};
}
/// Simple macro for the main function only
/// Check if given object has is_err() true and
/// then eprintln the unwrapped error and
/// returns with exit failure.
macro_rules! on_error_print_and_exit {
($r:expr) => {
if $r.is_err() {
eprintln!("{}", $r.unwrap_err());
return ExitCode::FAILURE;
}
};
}
fn main() -> ExitCode {
// handle version option
if cli::ARGS.version {
println!(
"{} version {}\nCopyright IBM Corp. 2023",
env!("CARGO_PKG_NAME"),
release_string!()
);
return ExitCode::SUCCESS;
}
// make sure only one pvapconfig instance is running
let r = LockFile::try_lock(PATH_PVAPCONFIG_LOCK);
on_error_print_and_exit!(r);
let _lockfile = r.unwrap();
// AP bus check
info!("Checking AP bus support and facilities...\n");
let r = ap::check_ap_bus_support();
on_error_print_and_exit!(r);
let r = ap::ap_bus_has_apsb_support();
on_error_print_and_exit!(r);
info!("AP bus support and facilities are ok.\n");
// UV check
info!("Checking UV support and environment...\n");
if !pv_core::misc::pv_guest_bit_set() {
println_and_exit_failure!("Failure: this is not a SE guest.");
}
let r = uv::has_list_secrets_facility();
on_error_print_and_exit!(r);
info!("UV support and environment is ok.\n");
// read configuration
let configfile: &str = match &cli::ARGS.config {
Some(f) => f,
_ => cli::PATH_DEFAULT_CONFIG_FILE,
};
info!(
"Reading AP configuration entries from file '{}'...\n",
configfile
);
let apconfig: ApConfigList = match ApConfigList::read_and_validate_yaml_file(configfile) {
Ok(apcfg) => apcfg,
Err(err) => println_and_exit_failure!("{}", err),
};
if apconfig.is_empty() {
println!(
"No AP configuration entries in config file '{}': Nothing to do.",
configfile
);
return ExitCode::SUCCESS;
}
info!("Found {} AP configuration entries.\n", apconfig.len());
// get list of secrets from UV
info!("Fetching list of secrets from UV...\n");
let secrets: SecretList = match uv::gather_secrets() {
Err(e) => println_and_exit_failure!("{}", e),
Ok(los) => los,
};
info!("Fetched {} Secret entries from UV.\n", secrets.len());
// Warning if no UV secrets given but AP config entries require it
let non_accel_apc = apconfig
.iter()
.filter(|apc| apc.mode != config::STR_MODE_ACCEL)
.count();
if non_accel_apc > 0 && secrets.is_empty() {
println!(
"Warning: No UV Secrets given but at least one AP config entry requires a Secret."
);
}
info!("Waiting for AP bus bindings complete...\n");
if !ap::wait_for_ap_bus_bindings_complete() {
return ExitCode::FAILURE;
}
info!("Fetching list of available APQNs...\n");
let mut apqns: ApqnList = match ApqnList::gather_apqns() {
Some(l) => l,
None => return ExitCode::FAILURE,
};
if apqns.is_empty() {
info!("List of available APQNs is empty: So there's nothing to do.\n");
return ExitCode::SUCCESS;
}
info!("Found {} APQNs.\n", apqns.len());
// check MK restriction
if !apqns.check_mk_restriction() {
return ExitCode::FAILURE;
}
// now the real work
info!("Applying AP configuration...\n");
let n = match do_ap_config(&mut apqns, &secrets, &apconfig, false) {
Err(e) => println_and_exit_failure!("{}", e),
Ok(n) => n,
};
if n == 0 {
println_and_exit_failure!(
"None out of {} AP config entries could be applied.",
apconfig.len()
);
} else if ARGS.strict() && n != apconfig.len() {
println_and_exit_failure!(
"Strict flag given and only {} out of {} AP config entries have been applied.",
n,
apconfig.len()
);
}
info!(
"Successfully applied {} out of {} AP config entries.\n",
n,
apconfig.len()
);
ExitCode::SUCCESS
}
/// The real worker function
///
/// This is the real algorithm which is trying to apply the
/// AP configuration read from the config file to the existing
/// APQNs with the info from the list of secrets from the UV.
/// Returns the nr of AP config entries which are fulfilled
/// after the function ended.
/// apqns needs to be mutable as the function does a resort
/// but content stays the same.
fn do_ap_config(
apqns: &mut ApqnList,
secrets: &SecretList,
apconfig: &ApConfigList,
fntest: bool,
) -> Result<usize, String> {
let mut resolved_entries = 0;
let mut apconfig_done = vec![false; apconfig.len()];
let mut apqn_done = vec![false; apqns.len()];
// Preparation: Sort APQNs by generation.
// All the following steps iterate through the list
// of APQNs. So by sorting the APQNs starting with
// highest card generation down to the older card
// generations we prefer newer card generations over
// older card generations.
apqns.sort_by_gen();
// Step 1:
// Go through all AP config entries and try to find an APQN
// which already matches to this entry. If such an APQN is
// found mark the AP config entry as done, and mark the APQN
// as used so that entry and APQN will get skipped over in
// the next steps.
for (ci, apc) in apconfig.iter().enumerate() {
let cistr = if !apc.name.is_empty() {
format!("#{} '{}'", ci + 1, apc.name)
} else {
format!("#{}", ci + 1)
};
for (ai, apqn) in apqns.iter().enumerate() {
if apqn_done[ai] {
continue;
}
if !config_and_apqn_match(apc, apqn) {
continue;
}
if fntest {
continue;
}
match apqn.mode {
ap::ApqnMode::Accel => {
// check bind state of this APQN
let bind_state_ok = match apqn.bind_state() {
Err(err) => {
eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}");
false
}
Ok(ap::BindState::Bound) => true,
Ok(_) => false,
};
if !bind_state_ok {
continue;
}
// This APQN matches to the current AP config entry and is already bound.
// So this AP config entry is satisfied: mark this config entry as done
// and mark this APQN as used.
info!("Accelerator APQN {apqn} already satisfies AP config entry {cistr}.\n");
apconfig_done[ci] = true;
apqn_done[ai] = true;
resolved_entries += 1;
break;
}
ap::ApqnMode::Ep11 => {
// check association state of this APQN
let (assoc_state_ok, assoc_idx) = match apqn.associate_state() {
Err(err) => {
eprintln!(
"Warning: Failure reading APQN {apqn} associate state: {err}"
);
(false, 0)
}
Ok(ap::AssocState::Associated(idx)) => (true, idx),
Ok(_) => (false, 0),
};
if !assoc_state_ok {
continue;
}
// check association index
let r = secrets.iter().find(|&se| {
se.stype() == ListableSecretType::Association
&& se.id().len() == uv::AP_ASSOC_SECRET_ID_SIZE
&& se.index() == assoc_idx
&& helper::u8_to_hexstring(se.id()) == apc.secretid
});
if r.is_none() {
continue;
}
// This APQN matches to the current AP config entry and is already
// associated with the right secret id. So this AP config entry is
// satisfied: mark this config entry as done and mark this APQN as used.
info!("EP11 APQN {apqn} already satisfies AP config entry {cistr}.\n");
apconfig_done[ci] = true;
apqn_done[ai] = true;
resolved_entries += 1;
break;
}
_ => {
// (currently) unknown/unsupported APQN mode
}
}
}
}
// Step 2:
// All APQNs NOT marked as done are now examined for their bind
// and association state and maybe reset to "unbound".
for (ai, apqn) in apqns.iter().enumerate() {
if apqn_done[ai] || fntest {
continue;
}
match apqn.bind_state() {
Err(err) => eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}"),
Ok(ap::BindState::Bound) => {
info!("Unbind APQN {apqn} as this bind/associate does not match to any AP config entry.\n");
if !ARGS.dryrun() {
if let Err(err) = apqn.set_bind_state(ap::BindState::Unbound) {
return Err(format!("Failure unbinding APQN {apqn}: {err}"));
}
}
}
Ok(_) => {}
};
}
// Step 3:
// Go through all remaining AP config entries and try to fulfill each
// by searching for an APQN which would match to this config entry and
// then prepare this APQN (bind, maybe associate).
for (ci, apc) in apconfig.iter().enumerate() {
let cistr = if !apc.name.is_empty() {
format!("#{} '{}'", ci + 1, apc.name)
} else {
format!("#{}", ci + 1)
};
if apconfig_done[ci] {
continue;
}
for (ai, apqn) in apqns.iter().enumerate() {
if apqn_done[ai] {
continue;
}
if !config_and_apqn_match(apc, apqn) {
continue;
}
match apqn.mode {
ap::ApqnMode::Accel => {
// try to bind this accelerator APQN
if ARGS.verbose() || fntest {
println!("Bind APQN {apqn} to match to AP config entry {cistr}.");
}
if !(ARGS.dryrun() || fntest) {
if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) {
// bind failed, unbind/reset this apqn, return with failure
let _ = apqn.set_bind_state(ap::BindState::Unbound);
return Err(format!("Failure binding APQN {apqn}: {err}"));
}
}
apconfig_done[ci] = true;
apqn_done[ai] = true;
resolved_entries += 1;
break;
}
ap::ApqnMode::Ep11 => {
// EP11 needs bind and associate, but before doing this let's
// check out which secret index to use with the associate
let se = match secrets.iter().find(|&se| {
se.stype() == ListableSecretType::Association
&& se.id().len() == uv::AP_ASSOC_SECRET_ID_SIZE
&& helper::u8_to_hexstring(se.id()) == apc.secretid
}) {
None => {
eprintln!("Warning: Secret id '{}' from config entry {} not found in UV secrets list.",
apc.secretid, cistr);
break;
}
Some(se) => se,
};
// try to bind
if ARGS.verbose() || fntest {
println!(
"Bind APQN {apqn} to match to AP config entry {cistr} (step 1/2)."
);
}
if !(ARGS.dryrun() || fntest) {
if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) {
// bind failed, unbind/reset this apqn, return with failure
let _ = apqn.set_bind_state(ap::BindState::Unbound);
return Err(format!("Failure binding APQN {}: {}", apqn, err));
}
}
// try to associate
if ARGS.verbose() || fntest {
println!(
"Associate APQN {} with uv secrets index {} to match AP config entry {} (step 2/2).",
apqn, se.index(), cistr
);
}
if !(ARGS.dryrun() || fntest) {
let apas = ap::AssocState::Associated(se.index());
apqn.set_associate_state(apas)
.map_err(|err| format!("Failure associating APQN {apqn}: {err}"))?;
}
apconfig_done[ci] = true;
apqn_done[ai] = true;
resolved_entries += 1;
break;
}
_ => {
// (currently) unknown/unsupported APQN mode
}
}
}
}
Ok(resolved_entries)
}
/// # Panics
/// Panics if mingen for an accelerator has not a number as the 4th character.
/// Panics if mingen for an ep11 has not a number as the 4th character.
/// Please note this can not happen, as mingen is already checked via RE
/// during storing the value into mingen.
fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool {
if apc.mode == config::STR_MODE_ACCEL && apqn.mode == ap::ApqnMode::Accel {
// config and apqn are accelerators
// maybe check mingen
if !apc.mingen.is_empty() {
let mingen = &apc.mingen[3..].parse::<u32>().unwrap();
if mingen < &apqn.gen {
return false;
}
}
return true;
} else if apc.mode == config::STR_MODE_EP11 && apqn.mode == ap::ApqnMode::Ep11 {
// config and apqn are ep11
let info = match &apqn.info {
Some(ap::ApqnInfo::Ep11(i)) => i,
_ => return false,
};
// maybe check mingen
if !apc.mingen.is_empty() {
let mingen = &apc.mingen[3..].parse::<u32>().unwrap();
if mingen < &apqn.gen {
return false;
}
}
// maybe check serialnr
if !apc.serialnr.is_empty() && apc.serialnr != info.serialnr {
return false;
}
// check mkvp, currently an ep11 config entry must state an mkvp value
// whereas an ep11 info from an APQN may have an empty mkvp value to
// indicate that there is no WK set on this APQN.
if apc.mkvp != info.mkvp {
return false;
}
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use helper::hexstring_to_u8;
use pv_core::uv::SecretEntry;
// This is more or less only a test for the do_ap_config() function
// However, this is THE main functionality of the whole application.
fn make_test_apqns() -> Vec<Apqn> {
vec![
ap::Apqn {
name: String::from("10.0007"),
card: 16,
domain: 7,
gen: 8,
mode: ap::ApqnMode::Accel,
info: Option::Some(ap::ApqnInfo::Accel(ap::ApqnInfoAccel {})),
},
ap::Apqn {
name: String::from("11.0008"),
card: 17,
domain: 8,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
serialnr: String::from("93AADFK719460083"),
mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"),
})),
},
ap::Apqn {
name: String::from("12.0009"),
card: 18,
domain: 9,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
serialnr: String::from("93AADHZU42082261"),
mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"),
})),
},
ap::Apqn {
name: String::from("12.000a"),
card: 18,
domain: 10,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
serialnr: String::from("93AADHZU42082261"),
mkvp: String::from("383d2a9ab781f35343554c5b3d9337cd"),
})),
},
ap::Apqn {
name: String::from("13.000d"),
card: 19,
domain: 13,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
serialnr: String::from("87HU397G150TZGR"),
mkvp: String::new(),
})),
},
ap::Apqn {
name: String::from("13.000f"),
card: 19,
domain: 15,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::None,
},
]
}
fn make_assoc_secretentry(idx: u16, hexidstr: &str) -> SecretEntry {
let id = hexstring_to_u8(hexidstr);
let idlen: u32 = id.len().try_into().unwrap();
let idarray = <&[u8; 32]>::try_from(id.as_slice()).unwrap();
SecretEntry::new(idx, ListableSecretType::Association, *idarray, idlen)
}
fn make_test_secrets() -> Vec<SecretEntry> {
vec![
make_assoc_secretentry(
33,
"3333333333333333333333333333333333333333333333333333333333333333",
),
make_assoc_secretentry(
13,
"bc9d46c052bc3574454c5715757274629a283767ed237922cfb8651c0e77320a",
),
make_assoc_secretentry(
44,
"4444444444444444444444444444444444444444444444444444444444444444",
),
make_assoc_secretentry(
15,
"06cdbbac76a595b481110d108154bc05ebbf900a0f16e36a24045998934fb1e9",
),
make_assoc_secretentry(
17,
"6831af07f8c8e7309a3ace9f3b5554d34e3eaa4a27a08fdee469e367c3fa3e9e",
),
]
}
fn make_test_apconfigs() -> Vec<ApConfigEntry> {
vec![
config::ApConfigEntry {
name: String::from("test_1"),
description: String::from("test_1"),
mode: String::from("accel"),
mkvp: String::from(""),
serialnr: String::from(""),
mingen: String::from("cex8"),
secretid: String::from(""),
},
config::ApConfigEntry {
name: String::from("test_2"),
description: String::from("test_2"),
mode: String::from("ep11"),
mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"),
serialnr: String::from("93AADFK719460083"),
mingen: String::from("cex8"),
secretid: String::from(
"bc9d46c052bc3574454c5715757274629a283767ed237922cfb8651c0e77320a",
),
},
config::ApConfigEntry {
name: String::from("test_3"),
description: String::from("test_3"),
mode: String::from("ep11"),
mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"),
serialnr: String::from(""),
mingen: String::from("cex8"),
secretid: String::from(
"06cdbbac76a595b481110d108154bc05ebbf900a0f16e36a24045998934fb1e9",
),
},
config::ApConfigEntry {
name: String::from("test_4"),
description: String::from("test_4"),
mode: String::from("ep11"),
mkvp: String::from("8be1eaf5c44e2fa8b18804551b604b1b"),
serialnr: String::from(""),
mingen: String::from("cex8"),
secretid: String::from(
"6831af07f8c8e7309a3ace9f3b5554d34e3eaa4a27a08fdee469e367c3fa3e9e",
),
},
]
}
#[test]
fn test_do_ap_config_invocation_1() {
let test_apqns = make_test_apqns();
let mut apqns: Vec<Apqn> = Vec::new();
apqns.push(test_apqns[0].clone());
let secrets: Vec<SecretEntry> = Vec::new();
let secretlist = SecretList::new(secrets.len() as u16, secrets);
let test_apconfigs = make_test_apconfigs();
let mut apconfig: Vec<ApConfigEntry> = Vec::new();
apconfig.push(test_apconfigs[0].clone());
let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig);
let mut apqnlist = ApqnList::from_apqn_vec(apqns);
let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true);
assert!(r.is_ok());
let n = r.unwrap();
assert!(n == 1);
}
#[test]
fn test_do_ap_config_invocation_2() {
let test_apqns = make_test_apqns();
let mut apqns: Vec<Apqn> = Vec::new();
apqns.push(test_apqns[1].clone());
let mut secrets = make_test_secrets();
while secrets.len() > 2 {
secrets.pop();
}
let secretlist = SecretList::new(secrets.len() as u16, secrets);
let test_apconfigs = make_test_apconfigs();
let mut apconfig: Vec<ApConfigEntry> = Vec::new();
apconfig.push(test_apconfigs[1].clone());
let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig);
let mut apqnlist = ApqnList::from_apqn_vec(apqns);
let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true);
assert!(r.is_ok());
let n = r.unwrap();
assert!(n == 1);
}
#[test]
fn test_do_ap_config_invocation_3() {
let test_apqns = make_test_apqns();
let mut apqns: Vec<Apqn> = Vec::new();
for a in test_apqns.iter() {
apqns.push(a.clone());
}
apqns.reverse();
let secrets = make_test_secrets();
let secretlist = SecretList::new(secrets.len() as u16, secrets);
let test_apconfigs = make_test_apconfigs();
let mut apconfig: Vec<ApConfigEntry> = Vec::new();
for c in test_apconfigs.iter() {
apconfig.push(c.clone());
}
let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig);
let mut apqnlist = ApqnList::from_apqn_vec(apqns);
let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true);
assert!(r.is_ok());
let n = r.unwrap();
assert!(n == 3, "n = {} != 3", n);
}
}