use std::collections::HashMap; use std::path::Path; use {Result,PathExt}; lazy_static! { static ref CMDLINE: CommandLine = match CommandLine::load() { Ok(cl) => cl, Err(err) => { warn!("Failed to load kernel command line: {}", err); CommandLine::new() } }; } /// Kernel command line parsed from /proc/cmdline into a map /// of Key / Value pairs. The value is optional since some /// variables are flags and do not have a value. /// /// This class is a lazy constructed singleton. #[derive(Clone)] pub struct CommandLine { varmap: HashMap>, } impl CommandLine { /// Returns true if the variable `name` is present on the kernel command line. pub fn var_exists(name: &str) -> bool { CMDLINE._var_exists(name) } /// Return a value for the variable `name` if a value is present on the kernel command line for this variable. /// Will return `None` if variable does not exist or if variable is present but does not have a value. pub fn get_value(name: &str) -> Option<&str> { CMDLINE._get_value(name) } /// Return `true` if variable citadel.noverity is present on kernel command line. pub fn noverity() -> bool { CommandLine::var_exists("citadel.noverity") } /// Return `true` if variable citadel.install is present on kernel command line. pub fn install_mode() -> bool { CommandLine::var_exists("citadel.install") } /// Return `true` if variable citadel.recovery is present on kernel command line. pub fn recovery_mode() -> bool { CommandLine::var_exists("citadel.recovery") } pub fn verbose() -> bool { CommandLine::var_exists("citadel.verbose") } fn new() -> CommandLine { CommandLine{ varmap: HashMap::new() } } fn load() -> Result { let s = Path::new("/proc/cmdline").read_as_string()?; let varmap = CommandLineParser::new(s).parse(); Ok(CommandLine{varmap}) } fn _var_exists(&self, name: &str) -> bool { self.varmap.contains_key(name) } fn _get_value(&self, name: &str) -> Option<&str> { if let Some(val) = self.varmap.get(name) { // 'name' exists if let Some(ref v) = *val { // has an associated value (name=value) return Some(v) } } // otherwise None None } } #[derive(Clone)] enum ParseState { // In whitespace between options Whitespace, // In option name, preceeding '=' char Name(String), // In value after '=' char Value(String,String), // First char was a '-', expecting double '--' InDash, // In quoted value, whitespace allowed InQuoted(String, String), // Last char was closing '"' char, expect only whitespace next QuotedEnd(String, String), // Failed to parse an option, remain in state BAD until whitespace Bad, } // Parser for kernel command line struct CommandLineParser { cmdline: String, varmap: HashMap>, pos: usize, } impl CommandLineParser { fn new(cmdline: String) -> CommandLineParser { CommandLineParser { cmdline, varmap: HashMap::new(), pos: 0, } } fn parse(mut self) -> HashMap> { // Append a space to cause final item to be processed let cmdline = self.cmdline.clone() + " "; let mut state = ParseState::Whitespace; for c in cmdline.chars() { state = match state { ParseState::Whitespace => self.parse_whitespace(c), ParseState::Name(name) => self.parse_name(c, name), ParseState::Value(name, value) => self.parse_value(c, name, value), ParseState::InDash => self.parse_in_dash(c), ParseState::InQuoted(name, value) => self.parse_in_quoted(c, name, value), ParseState::QuotedEnd(name, value) => self.parse_quoted_end(c, name, value), ParseState::Bad => self.parse_bad(c), }; self.pos += 1; } self.varmap } fn parse_whitespace(&mut self, c: char) -> ParseState { match c { ch if ch.is_whitespace() => ParseState::Whitespace, ch if ch.is_ascii_alphanumeric() => ParseState::Name(ch.to_string()), _ => { self.unexpected_char(c, "as initial character of option name") } } } fn parse_name(&mut self, c: char, mut name: String) -> ParseState { match c { '_' | '-' => { name.push('-'); ParseState::Name(name) }, '=' => ParseState::Value(name, String::new()), ch if ch.is_whitespace() => { self.varmap.insert(name, None); ParseState::Whitespace }, ch if ch.is_ascii_alphanumeric() || ch == '.' => { name.push(ch); ParseState::Name(name) }, _ => { self.unexpected_char(c, "parsing option name") }, } } fn parse_value(&mut self, c: char, name: String, mut value: String) -> ParseState { match c { '"' if value.is_empty() => ParseState::InQuoted(name, value), ch if ch.is_whitespace() => { self.varmap.insert(name, Some(value)); ParseState::Whitespace }, ch if ch.is_ascii() => { value.push(ch); ParseState::Value(name, value) }, _ => { self.unexpected_char(c, "parsing option value") } } } fn parse_in_dash(&mut self, c: char) -> ParseState { // Only supposed to be double dash, but we'll accept any number of consecutive dashes if c.is_whitespace() { ParseState::Whitespace } else if c == '-' { ParseState::InDash } else { self.unexpected_char(c, "after initial dash character") } } fn parse_in_quoted(&mut self, c: char, name: String, mut value: String) -> ParseState { if c == '"' { ParseState::QuotedEnd(name, value) } else { value.push(c); ParseState::InQuoted(name, value) } } fn parse_quoted_end(&mut self, c: char, name: String, value: String) -> ParseState { if c.is_whitespace() { self.varmap.insert(name, Some(value)); return ParseState::Whitespace } self.unexpected_char(c, "after closing quote character") } fn parse_bad(&mut self, c: char) -> ParseState { if c.is_whitespace() { ParseState::Whitespace } else { ParseState::Bad } } fn unexpected_char(&self, c: char, msg: &str) -> ParseState { warn!("Parsing kernel commandline: {}", self.cmdline); warn!("Unexpected char '{}' at position {} {}", c, self.pos, msg); ParseState::Bad } } #[test] fn foo() { let cline = CommandLine::load().unwrap(); println!("hello"); println!("cline: {:?}", cline.varmap); }