diff options
Diffstat (limited to 'util/flashrom_tester/src')
-rw-r--r-- | util/flashrom_tester/src/cros_sysinfo.rs | 24 | ||||
-rw-r--r-- | util/flashrom_tester/src/logger.rs | 93 | ||||
-rw-r--r-- | util/flashrom_tester/src/main.rs | 80 | ||||
-rw-r--r-- | util/flashrom_tester/src/rand_util.rs | 11 | ||||
-rw-r--r-- | util/flashrom_tester/src/tester.rs | 396 | ||||
-rw-r--r-- | util/flashrom_tester/src/tests.rs | 183 | ||||
-rw-r--r-- | util/flashrom_tester/src/types.rs | 39 | ||||
-rw-r--r-- | util/flashrom_tester/src/utils.rs | 155 |
8 files changed, 436 insertions, 545 deletions
diff --git a/util/flashrom_tester/src/cros_sysinfo.rs b/util/flashrom_tester/src/cros_sysinfo.rs index ddb080265..37e1ec659 100644 --- a/util/flashrom_tester/src/cros_sysinfo.rs +++ b/util/flashrom_tester/src/cros_sysinfo.rs @@ -34,7 +34,7 @@ // use std::ffi::OsStr; -use std::fmt::Debug; +use std::fs; use std::io::Result as IoResult; use std::process::{Command, Stdio}; @@ -60,21 +60,11 @@ pub fn bios_info() -> IoResult<String> { dmidecode_dispatch(&["-q", "-t0"]) } -pub fn eventlog_list() -> Result<String, std::io::Error> { - mosys_dispatch(&["eventlog", "list"]) -} - -fn mosys_dispatch<S: AsRef<OsStr> + Debug>(args: &[S]) -> IoResult<String> { - info!("mosys_dispatch() running: /usr/sbin/mosys {:?}", args); - - let output = Command::new("/usr/sbin/mosys") - .args(args) - .stdin(Stdio::null()) - .output()?; - if !output.status.success() { - return Err(utils::translate_command_error(&output)); +pub fn release_description() -> IoResult<String> { + for l in fs::read_to_string("/etc/lsb-release")?.lines() { + if l.starts_with("CHROMEOS_RELEASE_DESCRIPTION") { + return Ok(l.to_string()); + } } - - let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); - Ok(stdout) + Err(std::io::ErrorKind::NotFound.into()) } diff --git a/util/flashrom_tester/src/logger.rs b/util/flashrom_tester/src/logger.rs index e1c00f5de..c9c364066 100644 --- a/util/flashrom_tester/src/logger.rs +++ b/util/flashrom_tester/src/logger.rs @@ -35,105 +35,81 @@ use flashrom_tester::types; use std::io::Write; -use std::path::PathBuf; -use std::sync::Mutex; -struct Logger<W: Write + Send> { +struct Logger { level: log::LevelFilter, - target: LogTarget<W>, + color: types::Color, } -enum LogTarget<W> -where - W: Write, -{ - Terminal, - Write(Mutex<W>), -} - -impl<W: Write + Send> log::Log for Logger<W> { +impl log::Log for Logger { fn enabled(&self, metadata: &log::Metadata) -> bool { metadata.level() <= self.level } fn log(&self, record: &log::Record) { - fn log_internal<W: Write>(mut w: W, record: &log::Record) -> std::io::Result<()> { - let now = chrono::Local::now(); - write!(w, "{}{} ", types::MAGENTA, now.format("%Y-%m-%dT%H:%M:%S"))?; - write!( - w, - "{}[ {} ]{} ", - types::YELLOW, - record.level(), - types::RESET - )?; - writeln!(w, "{}", record.args()) - } - // Write errors deliberately ignored - let _ = match self.target { - LogTarget::Terminal => { - let stdout = std::io::stdout(); - let mut lock = stdout.lock(); - log_internal(&mut lock, record) - } - LogTarget::Write(ref mutex) => { - let mut lock = mutex.lock().unwrap(); - log_internal(&mut *lock, record) - } - }; + let stdout = std::io::stdout(); + let mut lock = stdout.lock(); + let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Micros, true); + let _ = write!(lock, "{}{} ", self.color.magenta, now); + let _ = write!( + lock, + "{}[ {} ]{} ", + self.color.yellow, + record.level(), + self.color.reset + ); + let _ = writeln!(lock, "{}", record.args()); } fn flush(&self) { // Flush errors deliberately ignored - let _ = match self.target { - LogTarget::Terminal => std::io::stdout().flush(), - LogTarget::Write(ref w) => w.lock().unwrap().flush(), - }; + let _ = std::io::stdout().flush(); } } -pub fn init(to_file: Option<PathBuf>, debug: bool) { +pub fn init(debug: bool) { let mut logger = Logger { level: log::LevelFilter::Info, - target: LogTarget::Terminal, + color: if atty::is(atty::Stream::Stdout) { + types::COLOR + } else { + types::NOCOLOR + }, }; if debug { logger.level = log::LevelFilter::Debug; } - if let Some(path) = to_file { - logger.target = LogTarget::Write(Mutex::new( - std::fs::File::create(path).expect("Unable to open log file for writing"), - )); - } - log::set_max_level(logger.level); log::set_boxed_logger(Box::new(logger)).unwrap(); } #[cfg(test)] mod tests { - use super::{LogTarget, Logger}; + use std::io::Read; + + use super::Logger; + use flashrom_tester::types; use log::{Level, LevelFilter, Log, Record}; - use std::sync::Mutex; fn run_records(records: &[Record]) -> String { - let mut buf = Vec::<u8>::new(); + let buf = gag::BufferRedirect::stdout().unwrap(); { - let lock = Mutex::new(&mut buf); let logger = Logger { level: LevelFilter::Info, - target: LogTarget::Write(lock), + color: types::COLOR, }; for record in records { if logger.enabled(record.metadata()) { - logger.log(&record); + logger.log(record); } } } - String::from_utf8(buf).unwrap() + let mut ret = String::new(); + buf.into_inner().read_to_string(&mut ret).unwrap(); + ret } /// Log messages have the expected format @@ -146,9 +122,10 @@ mod tests { assert_eq!(&buf[..5], "\x1b[35m"); // Time is difficult to test, assume it's formatted okay + // Split on the UTC timezone char assert_eq!( - &buf[24..], - " \x1b[33m[ INFO ]\x1b[0m Test message at INFO\n" + buf.split_once("Z ").unwrap().1, + "\x1b[33m[ INFO ]\x1b[0m Test message at INFO\n" ); } diff --git a/util/flashrom_tester/src/main.rs b/util/flashrom_tester/src/main.rs index e589ee1e6..b8a2581ac 100644 --- a/util/flashrom_tester/src/main.rs +++ b/util/flashrom_tester/src/main.rs @@ -39,9 +39,8 @@ extern crate log; mod logger; use clap::{App, Arg}; -use flashrom::FlashChip; +use flashrom::{FlashChip, Flashrom, FlashromCmd, FlashromLib}; use flashrom_tester::{tester, tests}; -use std::path::PathBuf; use std::sync::atomic::AtomicBool; pub mod built_info { @@ -65,11 +64,25 @@ fn main() { built_info::BUILT_TIME_UTC, built_info::RUSTC_VERSION, )) - .arg(Arg::with_name("flashrom_binary").required(true)) + .arg( + Arg::with_name("libflashrom") + .long("libflashrom") + .takes_value(false) + .help("Test the flashrom library instead of a binary"), + ) + .arg( + Arg::with_name("flashrom_binary") + .long("flashrom_binary") + .short("b") + .takes_value(true) + .required_unless("libflashrom") + .conflicts_with("libflashrom") + .help("Path to flashrom binary to test"), + ) .arg( Arg::with_name("ccd_target_type") .required(true) - .possible_values(&["host", "ec", "servo"]), + .possible_values(&["host"]), ) .arg( Arg::with_name("print-layout") @@ -78,13 +91,6 @@ fn main() { .help("Print the layout file's contents before running tests"), ) .arg( - Arg::with_name("log-file") - .short("o") - .long("log-file") - .takes_value(true) - .help("Write logs to a file rather than stdout"), - ) - .arg( Arg::with_name("log_debug") .short("d") .long("debug") @@ -107,15 +113,13 @@ fn main() { ) .get_matches(); - logger::init( - matches.value_of_os("log-file").map(PathBuf::from), - matches.is_present("log_debug"), - ); + logger::init(matches.is_present("log_debug")); debug!("Args parsed and logging initialized OK"); - let flashrom_path = matches - .value_of("flashrom_binary") - .expect("flashrom_binary should be required"); + debug!("Collecting crossystem info"); + let crossystem = + flashrom_tester::utils::collect_crosssystem(&[]).expect("could not run crossystem"); + let ccd_type = FlashChip::from( matches .value_of("ccd_target_type") @@ -123,6 +127,25 @@ fn main() { ) .expect("ccd_target_type should admit only known types"); + let cmd: Box<dyn Flashrom> = if matches.is_present("libflashrom") { + Box::new(FlashromLib::new( + ccd_type, + if matches.is_present("log_debug") { + flashrom::FLASHROM_MSG_DEBUG + } else { + flashrom::FLASHROM_MSG_WARN + }, + )) + } else { + Box::new(FlashromCmd { + path: matches + .value_of("flashrom_binary") + .expect("flashrom_binary is required") + .to_string(), + fc: ccd_type, + }) + }; + let print_layout = matches.is_present("print-layout"); let output_format = matches .value_of("output-format") @@ -132,12 +155,13 @@ fn main() { let test_names = matches.values_of("test_name"); if let Err(e) = tests::generic( - flashrom_path, + cmd.as_ref(), ccd_type, print_layout, output_format, test_names, Some(handle_sigint()), + crossystem, ) { eprintln!("Failed to run tests: {:?}", e); std::process::exit(1); @@ -152,12 +176,11 @@ fn main() { /// Once a signal is trapped, the default behavior is restored (terminating /// the process) for future signals. fn handle_sigint() -> &'static AtomicBool { - use nix::libc::c_int; - use nix::sys::signal::{self, SigHandler, Signal}; + use libc::c_int; use std::sync::atomic::Ordering; unsafe { - let _ = signal::signal(Signal::SIGINT, SigHandler::Handler(sigint_handler)); + let _ = libc::signal(libc::SIGINT, sigint_handler as libc::sighandler_t); } static TERMINATE_FLAG: AtomicBool = AtomicBool::new(false); @@ -169,10 +192,17 @@ rendering your machine unbootable. Testing will end on completion of the current test, or press ^C again to exit immediately (possibly bricking your machine). "; - // Use raw write() because signal-safety is a very hard problem - let _ = nix::unistd::write(STDERR_FILENO, MESSAGE); + // Use raw write() because signal-safety is a very hard problem. Safe because this doesn't + // modify any memory. + let _ = unsafe { + libc::write( + STDERR_FILENO, + MESSAGE.as_ptr() as *const libc::c_void, + MESSAGE.len() as libc::size_t, + ) + }; unsafe { - let _ = signal::signal(Signal::SIGINT, SigHandler::SigDfl); + let _ = libc::signal(libc::SIGINT, libc::SIG_DFL); } TERMINATE_FLAG.store(true, Ordering::Release); } diff --git a/util/flashrom_tester/src/rand_util.rs b/util/flashrom_tester/src/rand_util.rs index 51411d0d5..a040c89a3 100644 --- a/util/flashrom_tester/src/rand_util.rs +++ b/util/flashrom_tester/src/rand_util.rs @@ -36,15 +36,14 @@ use std::fs::File; use std::io::prelude::*; use std::io::BufWriter; +use std::path::Path; use rand::prelude::*; -pub fn gen_rand_testdata(path: &str, size: usize) -> std::io::Result<()> { +pub fn gen_rand_testdata(path: &Path, size: usize) -> std::io::Result<()> { let mut buf = BufWriter::new(File::create(path)?); - let mut a: Vec<u8> = Vec::with_capacity(size); - // Pad out array to be filled in by Rng::fill(). - a.resize(size, 0b0); + let mut a: Vec<u8> = vec![0; size]; thread_rng().fill(a.as_mut_slice()); buf.write_all(a.as_slice())?; @@ -60,8 +59,8 @@ mod tests { fn gen_rand_testdata() { use super::gen_rand_testdata; - let path0 = "/tmp/idk_test00"; - let path1 = "/tmp/idk_test01"; + let path0 = Path::new("/tmp/idk_test00"); + let path1 = Path::new("/tmp/idk_test01"); let sz = 1024; gen_rand_testdata(path0, sz).unwrap(); diff --git a/util/flashrom_tester/src/tester.rs b/util/flashrom_tester/src/tester.rs index 3150a4354..4629c2eb7 100644 --- a/util/flashrom_tester/src/tester.rs +++ b/util/flashrom_tester/src/tester.rs @@ -36,11 +36,14 @@ use super::rand_util; use super::types; use super::utils::{self, LayoutSizes}; -use flashrom::{FlashChip, Flashrom, FlashromCmd}; +use flashrom::FlashromError; +use flashrom::{FlashChip, Flashrom}; use serde_json::json; -use std::mem::MaybeUninit; +use std::fs::File; +use std::io::Write; +use std::path::Path; +use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Mutex; // type-signature comes from the return type of lib.rs workers. type TestError = Box<dyn std::error::Error>; @@ -52,33 +55,38 @@ pub struct TestEnv<'a> { /// /// Where possible, prefer to use methods on the TestEnv rather than delegating /// to the raw flashrom functions. - pub cmd: &'a FlashromCmd, + pub cmd: &'a dyn Flashrom, layout: LayoutSizes, - pub wp: WriteProtectState<'a, 'static>, + pub wp: WriteProtectState<'a>, /// The path to a file containing the flash contents at test start. - // TODO(pmarheine) migrate this to a PathBuf for clarity - original_flash_contents: String, + original_flash_contents: PathBuf, /// The path to a file containing flash-sized random data - // TODO(pmarheine) make this a PathBuf too - random_data: String, + random_data: PathBuf, + /// The path to a file containing layout data. + pub layout_file: PathBuf, } impl<'a> TestEnv<'a> { - pub fn create(chip_type: FlashChip, cmd: &'a FlashromCmd) -> Result<Self, String> { + pub fn create( + chip_type: FlashChip, + cmd: &'a dyn Flashrom, + print_layout: bool, + ) -> Result<Self, FlashromError> { let rom_sz = cmd.get_size()?; let out = TestEnv { - chip_type: chip_type, - cmd: cmd, + chip_type, + cmd, layout: utils::get_layout_sizes(rom_sz)?, - wp: WriteProtectState::from_hardware(cmd)?, + wp: WriteProtectState::from_hardware(cmd, chip_type)?, original_flash_contents: "/tmp/flashrom_tester_golden.bin".into(), random_data: "/tmp/random_content.bin".into(), + layout_file: create_layout_file(rom_sz, Path::new("/tmp/"), print_layout), }; info!("Stashing golden image for verification/recovery on completion"); - flashrom::read(&out.cmd, &out.original_flash_contents)?; - flashrom::verify(&out.cmd, &out.original_flash_contents)?; + out.cmd.read_into_file(&out.original_flash_contents)?; + out.cmd.verify_from_file(&out.original_flash_contents)?; info!("Generating random flash-sized data"); rand_util::gen_rand_testdata(&out.random_data, rom_sz as usize) @@ -88,19 +96,10 @@ impl<'a> TestEnv<'a> { } pub fn run_test<T: TestCase>(&mut self, test: T) -> TestResult { - let use_dut_control = self.chip_type == FlashChip::SERVO; - if use_dut_control && flashrom::dut_ctrl_toggle_wp(false).is_err() { - error!("failed to dispatch dut_ctrl_toggle_wp()!"); - } - let name = test.get_name(); info!("Beginning test: {}", name); let out = test.run(self); info!("Completed test: {}; result {:?}", name, out); - - if use_dut_control && flashrom::dut_ctrl_toggle_wp(true).is_err() { - error!("failed to dispatch dut_ctrl_toggle_wp()!"); - } out } @@ -112,7 +111,7 @@ impl<'a> TestEnv<'a> { /// Return the path to a file that contains random data and is the same size /// as the flash chip. - pub fn random_data_file(&self) -> &str { + pub fn random_data_file(&self) -> &Path { &self.random_data } @@ -123,31 +122,36 @@ impl<'a> TestEnv<'a> { /// Return true if the current Flash contents are the same as the golden image /// that was present at the start of testing. pub fn is_golden(&self) -> bool { - flashrom::verify(&self.cmd, &self.original_flash_contents).is_ok() + self.cmd + .verify_from_file(&self.original_flash_contents) + .is_ok() } /// Do whatever is necessary to make the current Flash contents the same as they /// were at the start of testing. - pub fn ensure_golden(&mut self) -> Result<(), String> { + pub fn ensure_golden(&mut self) -> Result<(), FlashromError> { self.wp.set_hw(false)?.set_sw(false)?; - flashrom::write(&self.cmd, &self.original_flash_contents) + self.cmd.write_from_file(&self.original_flash_contents)?; + Ok(()) } /// Attempt to erase the flash. - pub fn erase(&self) -> Result<(), String> { - flashrom::erase(self.cmd) + pub fn erase(&self) -> Result<(), FlashromError> { + self.cmd.erase()?; + Ok(()) } /// Verify that the current Flash contents are the same as the file at the given /// path. /// /// Returns Err if they are not the same. - pub fn verify(&self, contents_path: &str) -> Result<(), String> { - flashrom::verify(self.cmd, contents_path) + pub fn verify(&self, contents_path: &Path) -> Result<(), FlashromError> { + self.cmd.verify_from_file(contents_path)?; + Ok(()) } } -impl Drop for TestEnv<'_> { +impl<'a> Drop for TestEnv<'a> { fn drop(&mut self) { info!("Verifying flash remains unmodified"); if !self.is_golden() { @@ -159,70 +163,41 @@ impl Drop for TestEnv<'_> { } } +struct WriteProtect { + hw: bool, + sw: bool, +} + /// RAII handle for setting write protect in either hardware or software. /// /// Given an instance, the state of either write protect can be modified by calling -/// `set` or `push`. When it goes out of scope, the write protects will be returned +/// `set`. When it goes out of scope, the write protects will be returned /// to the state they had then it was created. -/// -/// The lifetime `'p` on this struct is the parent state it derives from; `'static` -/// implies it is derived from hardware, while anything else is part of a stack -/// created by `push`ing states. An initial state is always static, and the stack -/// forms a lifetime chain `'static -> 'p -> 'p1 -> ... -> 'pn`. -pub struct WriteProtectState<'a, 'p> { - /// The parent state this derives from. - /// - /// If it's a root (gotten via `from_hardware`), then this is Hardware and the - /// liveness flag will be reset on drop. - initial: InitialState<'p>, - // Tuples are (hardware, software) - current: (bool, bool), - cmd: &'a FlashromCmd, -} - -enum InitialState<'p> { - Hardware(bool, bool), - Previous(&'p WriteProtectState<'p, 'p>), +pub struct WriteProtectState<'a> { + current: WriteProtect, + initial: WriteProtect, + cmd: &'a dyn Flashrom, + fc: FlashChip, } -impl InitialState<'_> { - fn get_target(&self) -> (bool, bool) { - match self { - InitialState::Hardware(hw, sw) => (*hw, *sw), - InitialState::Previous(s) => s.current, - } - } -} - -impl<'a> WriteProtectState<'a, 'static> { +impl<'a> WriteProtectState<'a> { /// Initialize a state from the current state of the hardware. - /// - /// Panics if there is already a live state derived from hardware. In such a situation the - /// new state must be derived from the live one, or the live one must be dropped first. - pub fn from_hardware(cmd: &'a FlashromCmd) -> Result<Self, String> { - let mut lock = Self::get_liveness_lock() - .lock() - .expect("Somebody panicked during WriteProtectState init from hardware"); - if *lock { - drop(lock); // Don't poison the lock - panic!("Attempted to create a new WriteProtectState when one is already live"); - } - + pub fn from_hardware(cmd: &'a dyn Flashrom, fc: FlashChip) -> Result<Self, FlashromError> { let hw = Self::get_hw(cmd)?; let sw = Self::get_sw(cmd)?; - info!("Initial hardware write protect: HW={} SW={}", hw, sw); + info!("Initial write protect state: HW={} SW={}", hw, sw); - *lock = true; Ok(WriteProtectState { - initial: InitialState::Hardware(hw, sw), - current: (hw, sw), + current: WriteProtect { hw, sw }, + initial: WriteProtect { hw, sw }, cmd, + fc, }) } /// Get the actual hardware write protect state. - fn get_hw(cmd: &FlashromCmd) -> Result<bool, String> { - if cmd.fc.can_control_hw_wp() { + fn get_hw(cmd: &dyn Flashrom) -> Result<bool, String> { + if cmd.can_control_hw_wp() { super::utils::get_hardware_wp() } else { Ok(false) @@ -230,98 +205,78 @@ impl<'a> WriteProtectState<'a, 'static> { } /// Get the actual software write protect state. - fn get_sw(cmd: &FlashromCmd) -> Result<bool, String> { - flashrom::wp_status(cmd, true) + fn get_sw(cmd: &dyn Flashrom) -> Result<bool, FlashromError> { + let b = cmd.wp_status(true)?; + Ok(b) } -} -impl<'a, 'p> WriteProtectState<'a, 'p> { /// Return true if the current programmer supports setting the hardware /// write protect. /// /// If false, calls to set_hw() will do nothing. pub fn can_control_hw_wp(&self) -> bool { - self.cmd.fc.can_control_hw_wp() + self.cmd.can_control_hw_wp() } - /// Set the software write protect. + /// Set the software write protect and check that the state is as expected. pub fn set_sw(&mut self, enable: bool) -> Result<&mut Self, String> { - info!("request={}, current={}", enable, self.current.1); - if self.current.1 != enable { - flashrom::wp_toggle(self.cmd, /* en= */ enable)?; - self.current.1 = enable; + info!("set_sw request={}, current={}", enable, self.current.sw); + if self.current.sw != enable { + self.cmd + .wp_toggle(/* en= */ enable) + .map_err(|e| e.to_string())?; } - Ok(self) - } - - /// Set the hardware write protect. - pub fn set_hw(&mut self, enable: bool) -> Result<&mut Self, String> { - if self.current.0 != enable { - if self.can_control_hw_wp() { - super::utils::toggle_hw_wp(/* dis= */ !enable)?; - self.current.0 = enable; - } else if enable { - info!( - "Ignoring attempt to enable hardware WP with {:?} programmer", - self.cmd.fc - ); - } + if Self::get_sw(self.cmd).map_err(|e| e.to_string())? != enable { + Err(format!( + "Software write protect did not change state to {} when requested", + enable + )) + } else { + self.current.sw = enable; + Ok(self) } - Ok(self) } - /// Stack a new write protect state on top of the current one. - /// - /// This is useful if you need to temporarily make a change to write protection: - /// - /// ```no_run - /// # fn main() -> Result<(), String> { - /// # let cmd: flashrom::FlashromCmd = unimplemented!(); - /// let wp = flashrom_tester::tester::WriteProtectState::from_hardware(&cmd)?; - /// { - /// let mut wp = wp.push(); - /// wp.set_sw(false)?; - /// // Do something with software write protect disabled - /// } - /// // Now software write protect returns to its original state, even if - /// // set_sw() failed. - /// # Ok(()) - /// # } - /// ``` - /// - /// This returns a new state which restores the original when it is dropped- the new state - /// refers to the old, so the compiler enforces that states are disposed of in the reverse - /// order of their creation and correctly restore the original state. - pub fn push<'p1>(&'p1 self) -> WriteProtectState<'a, 'p1> { - WriteProtectState { - initial: InitialState::Previous(self), - current: self.current, - cmd: self.cmd, + // Set software write protect with a custom range + pub fn set_range(&mut self, range: (i64, i64), enable: bool) -> Result<&mut Self, String> { + info!("set_range request={}, current={}", enable, self.current.sw); + self.cmd + .wp_range(range, enable) + .map_err(|e| e.to_string())?; + let actual_state = Self::get_sw(self.cmd).map_err(|e| e.to_string())?; + if actual_state != enable { + Err(format!( + "set_range request={}, real={}", + enable, actual_state + )) + } else { + self.current.sw = enable; + Ok(self) } } - fn get_liveness_lock() -> &'static Mutex<bool> { - static INIT: std::sync::Once = std::sync::Once::new(); - /// Value becomes true when there is a live WriteProtectState derived `from_hardware`, - /// blocking duplicate initialization. - /// - /// This is required because hardware access is not synchronized; it's possible to leave the - /// hardware in an unintended state by creating a state handle from it, modifying the state, - /// creating another handle from the hardware then dropping the first handle- then on drop - /// of the second handle it will restore the state to the modified one rather than the initial. - /// - /// This flag ensures that a duplicate root state cannot be created. - /// - /// This is a Mutex<bool> rather than AtomicBool because acquiring the flag needs to perform - /// several operations that may themselves fail- acquisitions must be fully synchronized. - static mut LIVE_FROM_HARDWARE: MaybeUninit<Mutex<bool>> = MaybeUninit::uninit(); - - unsafe { - INIT.call_once(|| { - LIVE_FROM_HARDWARE.as_mut_ptr().write(Mutex::new(false)); - }); - &*LIVE_FROM_HARDWARE.as_ptr() + /// Set the hardware write protect if supported and check that the state is as expected. + pub fn set_hw(&mut self, enable: bool) -> Result<&mut Self, String> { + info!("set_hw request={}, current={}", enable, self.current.hw); + if self.can_control_hw_wp() { + if self.current.hw != enable { + super::utils::toggle_hw_wp(/* dis= */ !enable)?; + } + // toggle_hw_wp does check this, but we might not have called toggle_hw_wp so check again. + if Self::get_hw(self.cmd)? != enable { + return Err(format!( + "Hardware write protect did not change state to {} when requested", + enable + )); + } + } else { + info!( + "Ignoring attempt to set hardware WP with {:?} programmer", + self.fc + ); } + self.current.hw = enable; + Ok(self) } /// Reset the hardware to what it was when this state was created, reporting errors. @@ -329,91 +284,30 @@ impl<'a, 'p> WriteProtectState<'a, 'p> { /// This behaves exactly like allowing a state to go out of scope, but it can return /// errors from that process rather than panicking. pub fn close(mut self) -> Result<(), String> { - unsafe { - let out = self.drop_internal(); - // We just ran drop, don't do it again - std::mem::forget(self); - out - } + let out = self.drop_internal(); + // We just ran drop, don't do it again + std::mem::forget(self); + out } - /// Internal Drop impl. - /// - /// This is unsafe because it effectively consumes self when clearing the - /// liveness lock. Callers must be able to guarantee that self will be forgotten - /// if the state was constructed from hardware in order to uphold the liveness - /// invariant (that only a single state constructed from hardware exists at any - /// time). - unsafe fn drop_internal(&mut self) -> Result<(), String> { - let lock = match self.initial { - InitialState::Hardware(_, _) => Some( - Self::get_liveness_lock() - .lock() - .expect("Somebody panicked during WriteProtectState drop from hardware"), - ), - _ => None, - }; - let (hw, sw) = self.initial.get_target(); - - fn enable_str(enable: bool) -> &'static str { - if enable { - "en" - } else { - "dis" - } - } - + /// Sets both write protects to the state they had when this state was created. + fn drop_internal(&mut self) -> Result<(), String> { // Toggle both protects back to their initial states. // Software first because we can't change it once hardware is enabled. - if sw != self.current.1 { - // Is the hw wp currently enabled? - if self.current.0 { - super::utils::toggle_hw_wp(/* dis= */ true).map_err(|e| { - format!( - "Failed to {}able hardware write protect: {}", - enable_str(false), - e - ) - })?; - } - flashrom::wp_toggle(self.cmd, /* en= */ sw).map_err(|e| { - format!( - "Failed to {}able software write protect: {}", - enable_str(sw), - e - ) - })?; - } - - assert!( - self.cmd.fc.can_control_hw_wp() || (!self.current.0 && !hw), - "HW WP must be disabled if it cannot be controlled" - ); - if hw != self.current.0 { - super::utils::toggle_hw_wp(/* dis= */ !hw).map_err(|e| { - format!( - "Failed to {}able hardware write protect: {}", - enable_str(hw), - e - ) - })?; + if self.set_sw(self.initial.sw).is_err() { + self.set_hw(false)?; + self.set_sw(self.initial.sw)?; } + self.set_hw(self.initial.hw)?; - if let Some(mut lock) = lock { - // Initial state was constructed via from_hardware, now we can clear the liveness - // lock since reset is complete. - *lock = false; - } Ok(()) } } -impl<'a, 'p> Drop for WriteProtectState<'a, 'p> { - /// Sets both write protects to the state they had when this state was created. - /// - /// Panics on error because there is no mechanism to report errors in Drop. +impl<'a> Drop for WriteProtectState<'a> { fn drop(&mut self) { - unsafe { self.drop_internal() }.expect("Error while dropping WriteProtectState") + self.drop_internal() + .expect("Error while dropping WriteProtectState") } } @@ -452,7 +346,7 @@ impl<T: TestCase + ?Sized> TestCase for &T { } #[allow(dead_code)] -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum TestConclusion { Pass, Fail, @@ -463,6 +357,7 @@ pub enum TestConclusion { pub struct ReportMetaData { pub chip_name: String, pub os_release: String, + pub cros_release: String, pub system_info: String, pub bios_info: String, } @@ -477,17 +372,38 @@ fn decode_test_result(res: TestResult, con: TestConclusion) -> (TestConclusion, } } +fn create_layout_file(rom_sz: i64, tmp_dir: &Path, print_layout: bool) -> PathBuf { + info!("Calculate ROM partition sizes & Create the layout file."); + let layout_sizes = utils::get_layout_sizes(rom_sz).expect("Could not partition rom"); + + let layout_file = tmp_dir.join("layout.file"); + let mut f = File::create(&layout_file).expect("Could not create layout file"); + let mut buf: Vec<u8> = vec![]; + utils::construct_layout_file(&mut buf, &layout_sizes).expect("Could not construct layout file"); + + f.write_all(&buf).expect("Writing layout file failed"); + if print_layout { + info!( + "Dumping layout file as requested:\n{}", + String::from_utf8_lossy(&buf) + ); + } + layout_file +} + pub fn run_all_tests<T, TS>( chip: FlashChip, - cmd: &FlashromCmd, + cmd: &dyn Flashrom, ts: TS, terminate_flag: Option<&AtomicBool>, + print_layout: bool, ) -> Vec<(String, (TestConclusion, Option<TestError>))> where T: TestCase + Copy, TS: IntoIterator<Item = T>, { - let mut env = TestEnv::create(chip, cmd).expect("Failed to set up test environment"); + let mut env = + TestEnv::create(chip, cmd, print_layout).expect("Failed to set up test environment"); let mut results = Vec::new(); for t in ts { @@ -504,7 +420,7 @@ where results } -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum OutputFormat { Pretty, Json, @@ -533,6 +449,11 @@ pub fn collate_all_test_runs( ) { match format { OutputFormat::Pretty => { + let color = if atty::is(atty::Stream::Stdout) { + types::COLOR + } else { + types::NOCOLOR + }; println!(); println!(" ============================="); println!(" ===== AVL qual RESULTS ===="); @@ -540,6 +461,7 @@ pub fn collate_all_test_runs( println!(); println!(" %---------------------------%"); println!(" os release: {}", meta_data.os_release); + println!(" cros release: {}", meta_data.cros_release); println!(" chip name: {}", meta_data.chip_name); println!(" system info: \n{}", meta_data.system_info); println!(" bios info: \n{}", meta_data.bios_info); @@ -551,8 +473,8 @@ pub fn collate_all_test_runs( if *result != TestConclusion::Pass { println!( " {} {}", - style!(format!(" <+> {} test:", name), types::BOLD), - style_dbg!(result, types::RED) + style!(format!(" <+> {} test:", name), color.bold, color), + style_dbg!(result, color.red, color) ); match error { None => {} @@ -561,8 +483,8 @@ pub fn collate_all_test_runs( } else { println!( " {} {}", - style!(format!(" <+> {} test:", name), types::BOLD), - style_dbg!(result, types::GREEN) + style!(format!(" <+> {} test:", name), color.bold, color), + style_dbg!(result, color.green, color) ); } } diff --git a/util/flashrom_tester/src/tests.rs b/util/flashrom_tester/src/tests.rs index 9ef98e5c7..721a789d5 100644 --- a/util/flashrom_tester/src/tests.rs +++ b/util/flashrom_tester/src/tests.rs @@ -36,13 +36,15 @@ use super::cros_sysinfo; use super::tester::{self, OutputFormat, TestCase, TestEnv, TestResult}; use super::utils::{self, LayoutNames}; -use flashrom::{FlashChip, Flashrom, FlashromCmd}; +use flashrom::{FlashChip, Flashrom}; use std::collections::{HashMap, HashSet}; -use std::fs::File; -use std::io::{BufRead, Write}; +use std::convert::TryInto; +use std::fs::{self, File}; +use std::io::BufRead; use std::sync::atomic::AtomicBool; -const LAYOUT_FILE: &'static str = "/tmp/layout.file"; +const ELOG_FILE: &str = "/tmp/elog.file"; +const FW_MAIN_B_PATH: &str = "/tmp/FW_MAIN_B.bin"; /// Iterate over tests, yielding only those tests with names matching filter_names. /// @@ -77,50 +79,29 @@ fn filter_tests<'n, 't: 'n, T: TestCase>( /// test_names is the case-insensitive names of tests to run; if None, then all /// tests are run. Provided names that don't match any known test will be logged /// as a warning. +#[allow(clippy::or_fun_call)] // This is used for to_string here and we don't care. pub fn generic<'a, TN: Iterator<Item = &'a str>>( - path: &str, + cmd: &dyn Flashrom, fc: FlashChip, print_layout: bool, output_format: OutputFormat, test_names: Option<TN>, terminate_flag: Option<&AtomicBool>, + crossystem: String, ) -> Result<(), Box<dyn std::error::Error>> { - let p = path.to_string(); - let cmd = FlashromCmd { path: p, fc }; - utils::ac_power_warning(); - info!("Calculate ROM partition sizes & Create the layout file."); - let rom_sz: i64 = cmd.get_size()?; - let layout_sizes = utils::get_layout_sizes(rom_sz)?; - { - let mut f = File::create(LAYOUT_FILE)?; - let mut buf: Vec<u8> = vec![]; - utils::construct_layout_file(&mut buf, &layout_sizes)?; - - f.write_all(&buf)?; - if print_layout { - info!( - "Dumping layout file as requested:\n{}", - String::from_utf8_lossy(&buf) - ); - } - } - - info!( - "Record crossystem information.\n{}", - utils::collect_crosssystem()? - ); + info!("Record crossystem information.\n{}", crossystem); // Register tests to run: let tests: &[&dyn TestCase] = &[ &("Get_device_name", get_device_name_test), &("Coreboot_ELOG_sanity", elog_sanity_test), &("Host_is_ChromeOS", host_is_chrome_test), - &("Toggle_WP", wp_toggle_test), + &("WP_Region_List", wp_region_list_test), &("Erase_and_Write", erase_write_test), &("Fail_to_verify", verify_fail_test), - &("Lock", lock_test), + &("HWWP_Locks_SWWP", hwwp_locks_swwp_test), &("Lock_top_quad", partial_lock_test(LayoutNames::TopQuad)), &( "Lock_bottom_quad", @@ -135,59 +116,60 @@ pub fn generic<'a, TN: Iterator<Item = &'a str>>( // Limit the tests to only those requested, unless none are requested // in which case all tests are included. - let mut filter_names: Option<HashSet<String>> = if let Some(names) = test_names { - Some(names.map(|s| s.to_lowercase()).collect()) - } else { - None - }; + let mut filter_names: Option<HashSet<String>> = + test_names.map(|names| names.map(|s| s.to_lowercase()).collect()); let tests = filter_tests(tests, &mut filter_names); + let chip_name = cmd + .name() + .map(|x| format!("vendor=\"{}\" name=\"{}\"", x.0, x.1)) + .unwrap_or("<Unknown chip>".into()); + // ------------------------. // Run all the tests and collate the findings: - let results = tester::run_all_tests(fc, &cmd, tests, terminate_flag); + let results = tester::run_all_tests(fc, cmd, tests, terminate_flag, print_layout); // Any leftover filtered names were specified to be run but don't exist for leftover in filter_names.iter().flatten() { warn!("No test matches filter name \"{}\"", leftover); } - let chip_name = flashrom::name(&cmd) - .map(|x| format!("vendor=\"{}\" name=\"{}\"", x.0, x.1)) - .unwrap_or("<Unknown chip>".into()); - let os_rel = sys_info::os_release().unwrap_or("<Unknown OS>".to_string()); + let os_release = sys_info::os_release().unwrap_or("<Unknown OS>".to_string()); + let cros_release = cros_sysinfo::release_description() + .unwrap_or("<Unknown or not a ChromeOS release>".to_string()); let system_info = cros_sysinfo::system_info().unwrap_or("<Unknown System>".to_string()); let bios_info = cros_sysinfo::bios_info().unwrap_or("<Unknown BIOS>".to_string()); let meta_data = tester::ReportMetaData { - chip_name: chip_name, - os_release: os_rel, - system_info: system_info, - bios_info: bios_info, + chip_name, + os_release, + cros_release, + system_info, + bios_info, }; tester::collate_all_test_runs(&results, meta_data, output_format); Ok(()) } +/// Query the programmer and chip name. +/// Success means we got something back, which is good enough. fn get_device_name_test(env: &mut TestEnv) -> TestResult { - // Success means we got something back, which is good enough. - flashrom::name(env.cmd)?; + env.cmd.name()?; Ok(()) } -fn wp_toggle_test(env: &mut TestEnv) -> TestResult { - // NOTE: This is not strictly a 'test' as it is allowed to fail on some platforms. - // However, we will warn when it does fail. - // List the write-protected regions of flash. - match flashrom::wp_list(env.cmd) { +/// List the write-protectable regions of flash. +/// NOTE: This is not strictly a 'test' as it is allowed to fail on some platforms. +/// However, we will warn when it does fail. +fn wp_region_list_test(env: &mut TestEnv) -> TestResult { + match env.cmd.wp_list() { Ok(list_str) => info!("\n{}", list_str), Err(e) => warn!("{}", e), }; - // Fails if unable to set either one - env.wp.set_hw(false)?; - env.wp.set_sw(false)?; Ok(()) } +/// Verify that enabling hardware and software write protect prevents chip erase. fn erase_write_test(env: &mut TestEnv) -> TestResult { if !env.is_golden() { info!("Memory has been modified; reflashing to ensure erasure can be detected"); @@ -214,47 +196,53 @@ fn erase_write_test(env: &mut TestEnv) -> TestResult { Ok(()) } -fn lock_test(env: &mut TestEnv) -> TestResult { +/// Verify that enabling hardware write protect prevents disabling software write protect. +fn hwwp_locks_swwp_test(env: &mut TestEnv) -> TestResult { if !env.wp.can_control_hw_wp() { return Err("Lock test requires ability to control hardware write protect".into()); } env.wp.set_hw(false)?.set_sw(true)?; - // Toggling software WP off should work when hardware is off. - // Then enable again for another go. - env.wp.push().set_sw(false)?; + // Toggling software WP off should work when hardware WP is off. + // Then enable software WP again for the next test. + env.wp.set_sw(false)?.set_sw(true)?; + // Toggling software WP off should not work when hardware WP is on. env.wp.set_hw(true)?; - // Clearing should fail when hardware is enabled if env.wp.set_sw(false).is_ok() { return Err("Software WP was reset despite hardware WP being enabled".into()); } Ok(()) } +/// Check that the elog contains *something*, as an indication that Coreboot +/// is actually able to write to the Flash. This only makes sense for chips +/// running Coreboot, which we assume is just host. fn elog_sanity_test(env: &mut TestEnv) -> TestResult { - // Check that the elog contains *something*, as an indication that Coreboot - // is actually able to write to the Flash. Because this invokes mosys on the - // host, it doesn't make sense to run for other chips. if env.chip_type() != FlashChip::HOST { info!("Skipping ELOG sanity check for non-host chip"); return Ok(()); } - // mosys reads the flash, it should be back in the golden state + // flash should be back in the golden state env.ensure_golden()?; - // Output is one event per line, drop empty lines in the interest of being defensive. - let event_count = cros_sysinfo::eventlog_list()? - .lines() - .filter(|l| !l.is_empty()) - .count(); - - if event_count == 0 { - Err("ELOG contained no events".into()) - } else { - Ok(()) + + const ELOG_RW_REGION_NAME: &str = "RW_ELOG"; + env.cmd + .read_region_into_file(ELOG_FILE.as_ref(), ELOG_RW_REGION_NAME)?; + + // Just checking for the magic numer + // TODO: improve this test to read the events + if fs::metadata(ELOG_FILE)?.len() < 4 { + return Err("ELOG contained no data".into()); + } + let data = fs::read(ELOG_FILE)?; + if u32::from_le_bytes(data[0..4].try_into()?) != 0x474f4c45 { + return Err("ELOG had bad magic number".into()); } + Ok(()) } +/// Check that we are running ChromiumOS. fn host_is_chrome_test(_env: &mut TestEnv) -> TestResult { let release_info = if let Ok(f) = File::open("/etc/os-release") { let buf = std::io::BufReader::new(f); @@ -280,24 +268,27 @@ fn host_is_chrome_test(_env: &mut TestEnv) -> TestResult { } } +/// Verify that software write protect for a range protects only the requested range. fn partial_lock_test(section: LayoutNames) -> impl Fn(&mut TestEnv) -> TestResult { move |env: &mut TestEnv| { // Need a clean image for verification env.ensure_golden()?; - let (name, start, len) = utils::layout_section(env.layout(), section); - // Disable software WP so we can do range protection, but hardware WP - // must remain enabled for (most) range protection to do anything. - env.wp.set_hw(false)?.set_sw(false)?; - flashrom::wp_range(env.cmd, (start, len), true)?; + let (wp_section_name, start, len) = utils::layout_section(env.layout(), section); + // Disable hardware WP so we can modify the protected range. + env.wp.set_hw(false)?; + // Then enable software WP so the range is enforced and enable hardware + // WP so that flashrom does not disable software WP during the + // operation. + env.wp.set_range((start, len), true)?; env.wp.set_hw(true)?; - let rws = flashrom::ROMWriteSpecifics { - layout_file: Some(LAYOUT_FILE), - write_file: Some(env.random_data_file()), - name_file: Some(name), - }; - if flashrom::write_file_with_layout(env.cmd, &rws).is_ok() { + // Check that we cannot write to the protected region. + if env + .cmd + .write_from_file_region(env.random_data_file(), wp_section_name, &env.layout_file) + .is_ok() + { return Err( "Section should be locked, should not have been overwritable with random data" .into(), @@ -306,12 +297,32 @@ fn partial_lock_test(section: LayoutNames) -> impl Fn(&mut TestEnv) -> TestResul if !env.is_golden() { return Err("Section didn't lock, has been overwritten with random data!".into()); } + + // Check that we can write to the non protected region. + let (non_wp_section_name, _, _) = + utils::layout_section(env.layout(), section.get_non_overlapping_section()); + env.cmd.write_from_file_region( + env.random_data_file(), + non_wp_section_name, + &env.layout_file, + )?; + Ok(()) } } +/// Check that flashrom 'verify' will fail if the provided data does not match the chip data. fn verify_fail_test(env: &mut TestEnv) -> TestResult { - // Comparing the flash contents to random data says they're not the same. + env.ensure_golden()?; + // Verify that verify is Ok when the data matches. We verify only a region + // and not the whole chip because coprocessors or firmware may have written + // some data in other regions. + env.cmd + .read_region_into_file(FW_MAIN_B_PATH.as_ref(), "FW_MAIN_B")?; + env.cmd + .verify_region_from_file(FW_MAIN_B_PATH.as_ref(), "FW_MAIN_B")?; + + // Verify that verify is false when the data does not match match env.verify(env.random_data_file()) { Ok(_) => Err("Verification says flash is full of random data".into()), Err(_) => Ok(()), diff --git a/util/flashrom_tester/src/types.rs b/util/flashrom_tester/src/types.rs index b22ded2b4..9cefb27e9 100644 --- a/util/flashrom_tester/src/types.rs +++ b/util/flashrom_tester/src/types.rs @@ -33,21 +33,40 @@ // Software Foundation. // -pub const BOLD: &str = "\x1b[1m"; +pub struct Color { + pub bold: &'static str, + pub reset: &'static str, + pub magenta: &'static str, + pub yellow: &'static str, + pub green: &'static str, + pub red: &'static str, +} + +pub const COLOR: Color = Color { + bold: "\x1b[1m", + reset: "\x1b[0m", + magenta: "\x1b[35m", + yellow: "\x1b[33m", + green: "\x1b[92m", + red: "\x1b[31m", +}; -pub const RESET: &str = "\x1b[0m"; -pub const MAGENTA: &str = "\x1b[35m"; -pub const YELLOW: &str = "\x1b[33m"; -pub const GREEN: &str = "\x1b[92m"; -pub const RED: &str = "\x1b[31m"; +pub const NOCOLOR: Color = Color { + bold: "", + reset: "", + magenta: "", + yellow: "", + green: "", + red: "", +}; macro_rules! style_dbg { - ($s: expr, $c: expr) => { - format!("{}{:?}{}", $c, $s, types::RESET) + ($s: expr, $c: expr, $col: expr) => { + format!("{}{:?}{}", $c, $s, $col.reset) }; } macro_rules! style { - ($s: expr, $c: expr) => { - format!("{}{}{}", $c, $s, types::RESET) + ($s: expr, $c: expr, $col: expr) => { + format!("{}{}{}", $c, $s, $col.reset) }; } diff --git a/util/flashrom_tester/src/utils.rs b/util/flashrom_tester/src/utils.rs index d17480bcb..4e8dd7cce 100644 --- a/util/flashrom_tester/src/utils.rs +++ b/util/flashrom_tester/src/utils.rs @@ -44,6 +44,18 @@ pub enum LayoutNames { BottomQuad, } +impl LayoutNames { + // Return a section that does not overlap + pub fn get_non_overlapping_section(&self) -> LayoutNames { + match self { + LayoutNames::TopQuad => LayoutNames::BottomQuad, + LayoutNames::TopHalf => LayoutNames::BottomHalf, + LayoutNames::BottomHalf => LayoutNames::TopHalf, + LayoutNames::BottomQuad => LayoutNames::TopQuad, + } + } +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct LayoutSizes { half_sz: i64, @@ -88,20 +100,20 @@ pub fn construct_layout_file<F: Write>(mut target: F, ls: &LayoutSizes) -> std:: } pub fn toggle_hw_wp(dis: bool) -> Result<(), String> { - // The easist way to toggle the harware write-protect is + // The easist way to toggle the hardware write-protect is // to {dis}connect the battery (and/or open the WP screw). let s = if dis { "dis" } else { "" }; - info!("Prompt for hardware WP {}able", s); - eprintln!(" > {}connect the battery (and/or open the WP screw)", s); - pause(); - let wp = get_hardware_wp()?; - if wp && dis { - eprintln!("Hardware write protect is still ENABLED!"); - return toggle_hw_wp(dis); - } - if !wp && !dis { - eprintln!("Hardware write protect is still DISABLED!"); - return toggle_hw_wp(dis); + // Print a failure message, but not on the first try. + let mut fail_msg = None; + while dis == get_hardware_wp()? { + if let Some(msg) = fail_msg { + eprintln!("{msg}"); + } + fail_msg = Some(format!("Hardware write protect is still {}!", !dis)); + // The following message is read by the tast test. Do not modify. + info!("Prompt for hardware WP {}able", s); + eprintln!(" > {}connect the battery (and/or open the WP screw)", s); + pause(); } Ok(()) } @@ -114,21 +126,36 @@ pub fn ac_power_warning() { } fn pause() { - let mut stdout = std::io::stdout(); - // We want the cursor to stay at the end of the line, so we print without a newline - // and flush manually. - stdout.write(b"Press any key to continue...").unwrap(); - stdout.flush().unwrap(); - std::io::stdin().read(&mut [0]).unwrap(); + // The following message is read by the tast test. Do not modify. + println!("Press enter to continue..."); + // Rust stdout is always LineBuffered at time of writing. + // But this is not guaranteed, so flush anyway. + std::io::stdout().flush().unwrap(); + // This reads one line, there is no guarantee the line came + // after the above prompt. But it is good enough. + if std::io::stdin().read_line(&mut String::new()).unwrap() == 0 { + panic!("stdin closed"); + } } pub fn get_hardware_wp() -> std::result::Result<bool, String> { - let (_, wp) = parse_crosssystem(&collect_crosssystem()?)?; - Ok(wp) + let wp_s_val = collect_crosssystem(&["wpsw_cur"])?.parse::<u32>(); + match wp_s_val { + Ok(v) => { + if v == 1 { + Ok(true) + } else if v == 0 { + Ok(false) + } else { + Err("Unknown write protect value".into()) + } + } + Err(_) => Err("Cannot parse write protect value".into()), + } } -pub fn collect_crosssystem() -> Result<String, String> { - let cmd = match Command::new("crossystem").output() { +pub fn collect_crosssystem(args: &[&str]) -> Result<String, String> { + let cmd = match Command::new("crossystem").args(args).output() { Ok(x) => x, Err(e) => return Err(format!("Failed to run crossystem: {}", e)), }; @@ -140,39 +167,6 @@ pub fn collect_crosssystem() -> Result<String, String> { Ok(String::from_utf8_lossy(&cmd.stdout).into_owned()) } -fn parse_crosssystem(s: &str) -> Result<(Vec<&str>, bool), &'static str> { - // grep -v 'fwid +=' | grep -v 'hwid +=' - let sysinfo = s - .split_terminator("\n") - .filter(|s| !s.contains("fwid +=") && !s.contains("hwid +=")); - - let state_line = match sysinfo.clone().filter(|s| s.starts_with("wpsw_cur")).next() { - None => return Err("No wpsw_cur in system info"), - Some(line) => line, - }; - let wp_s_val = state_line - .trim_start_matches("wpsw_cur") - .trim_start_matches(' ') - .trim_start_matches('=') - .trim_start_matches(' ') - .get(..1) - .unwrap() - .parse::<u32>(); - - match wp_s_val { - Ok(v) => { - if v == 1 { - return Ok((sysinfo.collect(), true)); - } else if v == 0 { - return Ok((sysinfo.collect(), false)); - } else { - return Err("Unknown state value"); - } - } - Err(_) => return Err("Cannot parse state value"), - } -} - pub fn translate_command_error(output: &std::process::Output) -> std::io::Error { use std::io::{Error, ErrorKind}; // There is two cases on failure; @@ -244,55 +238,4 @@ mod tests { } ); } - - #[test] - fn parse_crosssystem() { - use super::parse_crosssystem; - - assert_eq!( - parse_crosssystem("This is not the tool you are looking for").err(), - Some("No wpsw_cur in system info") - ); - - assert_eq!( - parse_crosssystem("wpsw_cur = ERROR").err(), - Some("Cannot parse state value") - ); - - assert_eq!( - parse_crosssystem("wpsw_cur = 3").err(), - Some("Unknown state value") - ); - - assert_eq!( - parse_crosssystem("wpsw_cur = 0"), - Ok((vec!["wpsw_cur = 0"], false)) - ); - - assert_eq!( - parse_crosssystem("wpsw_cur = 1"), - Ok((vec!["wpsw_cur = 1"], true)) - ); - - assert_eq!( - parse_crosssystem("wpsw_cur=1"), - Ok((vec!["wpsw_cur=1"], true)) - ); - - assert_eq!( - parse_crosssystem( - "fwid += 123wpsw_cur\n\ - hwid += aaaaa\n\ - wpsw_boot = 0 # [RO/int]\n\ - wpsw_cur = 1 # [RO/int]\n" - ), - Ok(( - vec![ - "wpsw_boot = 0 # [RO/int]", - "wpsw_cur = 1 # [RO/int]" - ], - true - )) - ); - } } |