blob: 6a2328491b42a05c43bd1a457597960965104411 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
extern crate getopts;
#[macro_use]
extern crate lazy_static;
mod bindings;
mod logging;
use std::error::Error;
use std::ffi::{CStr, CString, NulError};
use std::fmt;
use std::io;
use std::ptr::null;
use std::str::Utf8Error;
use std::sync::Mutex;
use bindings::*;
use getopts::Options;
use logging::{Log, LogError};
const PROG_NAME: &'static str = "mosys";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const LOCK_TIMEOUT_SECS: i32 = 180;
lazy_static! {
pub static ref INSTANCES: Mutex<u8> = Mutex::new(0);
}
#[derive(Debug)]
pub struct Mosys {
program: String,
args: Vec<String>,
style: kv_pair_style,
single_key: CString,
platform_override: CString,
}
impl Mosys {
pub fn new(mut args: Vec<String>) -> Result<Mosys> {
let mut m = INSTANCES.lock().unwrap();
*m += 1;
if *m == 1 {
// Safe because function only mutates state owned by C components.
unsafe {
mosys_globals_init();
}
}
Log::init(&PROG_NAME, Log::Warning)?;
Ok(Mosys {
program: args.remove(0),
args: args,
style: kv_pair_style_KV_STYLE_VALUE,
single_key: CString::default(),
platform_override: CString::default(),
})
}
fn print_usage(&self, opts: Options) {
let brief = format!("Usage: {} [options] {{commands}}", &self.program);
print!("{}", opts.usage(&brief));
}
pub fn run(&mut self) -> Result<()> {
let mut opts = Options::new();
opts.optflag("k", "keyvalue", "print data in key=value format");
opts.optflag("l", "long", "print data in long format");
opts.optopt("s", "singlekey", "print value for single key", "[key]");
opts.optflagmulti("v", "verbose", "verbose (can be used multiple times)");
opts.optflag(
"f",
"force",
"force (ignore mosys lock, sanity checks, etc)",
);
opts.optflag("t", "tree", "display command tree for detected platform");
opts.optflag("S", "supported", "print supported platform ID's");
opts.optopt(
"p",
"platform",
"specify platform id (bypass auto-detection)",
"[id]",
);
opts.optflag("h", "help", "print this help");
opts.optflag("V", "version", "print version");
let matches = opts.parse(&self.args)?;
if matches.opt_present("h") {
self.print_usage(opts);
return Err(MosysError::Help);
}
if matches.opt_present("V") {
Log::Warning.logln(&format!("{} version {}", &self.program, VERSION))?;
return Ok(());
}
for _ in 0..matches.opt_count("v") {
Log::inc_threshold();
}
if let Some(s) = matches.opt_str("s") {
self.style = kv_pair_style_KV_STYLE_SINGLE;
self.single_key = CString::new(s)?;
// Safe because kv_set_single_key stores a const pointer to this string and this
// CString will live for the duration of the program.
unsafe {
kv_set_single_key(self.single_key.as_ptr());
}
} else if matches.opt_present("k") {
self.style = kv_pair_style_KV_STYLE_PAIR;
} else if matches.opt_present("l") {
self.style = kv_pair_style_KV_STYLE_LONG;
}
// Safe because the parameter is an enum constant
unsafe {
mosys_set_kv_pair_style(self.style);
}
if matches.opt_present("S") {
// Safe because all resources in print_platforms are under C control
unsafe {
return match print_platforms() {
0 => Ok(()),
_ => Err(MosysError::NonzeroPlatformListRet),
};
}
}
if !matches.opt_present("f") {
// Safe because argument and return are primitives. Note: this requires root perms
let rc = unsafe { mosys_acquire_big_lock(LOCK_TIMEOUT_SECS) };
if rc < 0 {
Log::Err.logln("Acquiring lock failed")?;
return Err(MosysError::AcqLockFail);
}
}
let platform_interface = match matches.opt_str("p") {
Some(p) => {
self.platform_override = CString::new(p)?;
// Safe because mosys_platform_setup only examines the string pointer during init
// and the pointer lives for the duration of the program.
unsafe { mosys_platform_setup(self.platform_override.as_ptr()) }
}
None => {
// Safe because mosys_platform_setup uses null to skip override
unsafe { mosys_platform_setup(null()) }
}
};
if platform_interface.is_null() {
Log::Err.logln("Platform not supported")?;
return Err(MosysError::PlatformNotSupported);
}
// This is valid so convert it to a regular, mutable reference
let platform_interface = unsafe { &mut *platform_interface };
// Safe because the strings stored on the platform_intf struct are static
let platform_name = unsafe { CStr::from_ptr(platform_interface.name).to_str()? };
Log::Debug.logln(&format!("Platform: {}", platform_name))?;
if matches.opt_present("t") {
// Safe because the pointer is guaranteed to be valid at this point
unsafe { print_tree(platform_interface) };
return Ok(());
}
let _commands = match matches.free.get(0) {
Some(c) => c,
None => {
self.print_usage(opts);
return Err(MosysError::NoCommands);
}
};
Log::Debug.logln("Completed successfully")?;
Ok(())
}
}
impl Drop for Mosys {
fn drop(&mut self) {
let mut m = INSTANCES.lock().unwrap();
*m -= 1;
Log::drop();
}
}
#[derive(Debug)]
pub enum MosysError {
Flag(getopts::Fail),
Io(io::Error),
Log(LogError),
NullString(NulError),
InvalidUtf8(Utf8Error),
Help,
NoCommands,
NonzeroPlatformListRet,
AcqLockFail,
PlatformNotSupported,
}
impl fmt::Display for MosysError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MosysError::Flag(ref err) => write!(f, "Flag error: {}", err),
MosysError::Io(ref err) => write!(f, "IO error: {}", err),
MosysError::Log(ref err) => write!(f, "Logging error: {}", err),
MosysError::NullString(ref err) => write!(f, "String with null byte passed: {}", err),
MosysError::InvalidUtf8(ref err) => {
write!(f, "C string with invalid UTF-8 passed: {}", err)
}
MosysError::Help => write!(f, "Requested help"),
MosysError::NoCommands => write!(f, "No commands given"),
MosysError::NonzeroPlatformListRet => write!(f, "Platform list failed"),
MosysError::AcqLockFail => write!(f, "Aquiring global, system lock failed"),
MosysError::PlatformNotSupported => write!(f, "Platform not supported"),
}
}
}
impl Error for MosysError {
fn description(&self) -> &str {
match *self {
MosysError::Flag(ref err) => err.description(),
MosysError::Io(ref err) => err.description(),
MosysError::Log(ref err) => err.description(),
MosysError::NullString(ref err) => err.description(),
MosysError::InvalidUtf8(ref err) => err.description(),
MosysError::Help => "Requested help",
MosysError::NoCommands => "No commands given",
MosysError::NonzeroPlatformListRet => "Platform list failure",
MosysError::AcqLockFail => "Aquiring global, system lock failed",
MosysError::PlatformNotSupported => "Platform not supported",
}
}
fn cause(&self) -> Option<&Error> {
match *self {
MosysError::Flag(ref err) => Some(err),
MosysError::Io(ref err) => Some(err),
MosysError::Log(ref err) => Some(err),
MosysError::NullString(ref err) => Some(err),
MosysError::InvalidUtf8(ref err) => Some(err),
_ => None,
}
}
}
impl From<getopts::Fail> for MosysError {
fn from(err: getopts::Fail) -> MosysError {
MosysError::Flag(err)
}
}
impl From<io::Error> for MosysError {
fn from(err: io::Error) -> MosysError {
MosysError::Io(err)
}
}
impl From<LogError> for MosysError {
fn from(err: LogError) -> MosysError {
MosysError::Log(err)
}
}
impl From<NulError> for MosysError {
fn from(err: NulError) -> MosysError {
MosysError::NullString(err)
}
}
impl From<Utf8Error> for MosysError {
fn from(err: Utf8Error) -> MosysError {
MosysError::InvalidUtf8(err)
}
}
type Result<T> = std::result::Result<T, MosysError>;
#[cfg(test)]
mod tests {
macro_rules! vec_of_strings {
($($x:expr),*) => (vec![$($x.to_string()),*]);
}
use super::*;
use logging::LAST_LOG;
// Verbosity and kv_pair_style are global so this lock allows us to run tests at the same time
// that won't stomp on each other.
lazy_static! {
static ref LOCK: Mutex<u8> = Mutex::new(0);
}
#[test]
fn test_new() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings!["someprogname", "-f", "-p", "Link", "command"];
Mosys::new(args).expect("Instantiation failed");
}
#[test]
fn test_help() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings!["someprogname", "-h", "command"];
let mut mosys = Mosys::new(args).unwrap();
match mosys.run() {
Err(MosysError::Help) => (),
_ => panic!("Should have returned help error code"),
}
}
/// This test will always fail as expected when not run as root (as tests should never be).
#[test]
fn test_lock_fail() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings!["someprogname", "command"];
let mut mosys = Mosys::new(args).unwrap();
match mosys.run() {
Err(MosysError::AcqLockFail) => (),
_ => panic!("Should have failed to acquire lock when not running as root"),
}
}
#[test]
fn test_platform_not_supported() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings![
"someprogname",
"-f",
"-p",
"nonexistant_platform",
"command"
];
let mut mosys = Mosys::new(args).unwrap();
match mosys.run() {
Err(MosysError::PlatformNotSupported) => (),
_ => panic!("Should have failed to find a platform"),
}
}
#[test]
fn test_version() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings!["someprogname", "-V"];
let mut mosys = Mosys::new(args).unwrap();
mosys.run().expect("Should have exited Ok(())");
assert_eq!(
&**LAST_LOG.lock().unwrap(),
&format!("someprogname version {}\n", env!("CARGO_PKG_VERSION"))
);
}
#[test]
fn test_verbosity() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings![
"someprogname",
"-v",
"-v",
"-v",
"-v",
"-v",
"-f",
"-p",
"Link",
"command"
];
let mut mosys = Mosys::new(args).unwrap();
mosys.run().expect("Should have exited Ok(())");
let t = Log::get_threshold();
assert_eq!(t, Log::Spew, "Should have incremented verbosity to max");
}
#[test]
fn test_platform_list() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings!["someprogname", "-S", "-v"];
let mut mosys = Mosys::new(args).unwrap();
mosys.run().expect("Should have exited Ok(())");
}
#[test]
fn test_misc_args() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings![
"someprogname",
"-s",
"keyname",
"-l",
"-t",
"-f",
"-p",
"Link",
"command"
];
let mut mosys = Mosys::new(args).unwrap();
mosys
.run()
.expect("Should have succeeded with getopts arguments");
let t = Log::get_threshold();
assert_eq!(t, Log::Warning, "Should not have incremented verbosity");
}
#[test]
fn test_single_kv_pair() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings![
"someprogname",
"-f",
"-p",
"Link",
"-s",
"keyname",
"command"
];
let mut mosys = Mosys::new(args).unwrap();
mosys
.run()
.expect("Should have succeeded with getopts arguments");
let r;
unsafe {
r = mosys_get_kv_pair_style();
}
assert_eq!(
r,
kv_pair_style_KV_STYLE_SINGLE,
"Should have change kv_pair_style"
);
}
#[test]
fn test_long_kv_pair() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings!["someprogname", "-f", "-p", "Link", "-l", "command"];
let mut mosys = Mosys::new(args).unwrap();
mosys
.run()
.expect("Should have succeeded with getopts arguments");
let r;
unsafe {
r = mosys_get_kv_pair_style();
}
assert_eq!(
r,
kv_pair_style_KV_STYLE_LONG,
"Should have change kv_pair_style"
);
}
#[test]
fn test_pair_kv_pair() {
let _test_lock = LOCK.lock().unwrap();
let args = vec_of_strings!["someprogname", "-f", "-p", "Link", "-k", "command"];
let mut mosys = Mosys::new(args).unwrap();
mosys
.run()
.expect("Should have succeeded with getopts arguments");
let r;
unsafe {
r = mosys_get_kv_pair_style();
}
assert_eq!(
r,
kv_pair_style_KV_STYLE_PAIR,
"Should have change kv_pair_style"
);
}
#[test]
fn test_ffi() {
// Safe because functions only mutate state owned by C components.
unsafe {
mosys_globals_init();
mosys_set_kv_pair_style(kv_pair_style_KV_STYLE_PAIR);
let ret = mosys_get_kv_pair_style();
assert_eq!(ret, kv_pair_style_KV_STYLE_PAIR);;
}
}
}