| // Copyright 2019 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. |
| |
| // Provides helper functions used by handler implementations of crosh commands. |
| |
| use rand::distributions::Alphanumeric; |
| use rand::{thread_rng, Rng}; |
| use std::env; |
| use std::error; |
| use std::fmt::{self, Display}; |
| use std::fs::read_to_string; |
| use std::io::Read; |
| use std::process::{Command, Stdio}; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| use std::time::Duration; |
| |
| use chrono::Local; |
| use dbus::blocking::Connection; |
| use libc::c_int; |
| use libchromeos::chromeos; |
| use regex::Regex; |
| use sys_util::{clear_signal_handler, error, register_signal_handler}; |
| |
| // 25 seconds is the default timeout for dbus-send. |
| pub const DEFAULT_DBUS_TIMEOUT: Duration = Duration::from_secs(25); |
| |
| const CROS_USER_ID_HASH: &str = "CROS_USER_ID_HASH"; |
| |
| static INCLUDE_DEV: AtomicBool = AtomicBool::new(false); |
| static INCLUDE_USB: AtomicBool = AtomicBool::new(false); |
| |
| #[derive(Debug)] |
| pub enum Error { |
| DbusChromeFeaturesService(dbus::Error, String), |
| DbusConnection(dbus::Error), |
| DbusGetUserIdHash(chromeos::Error), |
| NoMatchFound, |
| WrappedError(String), |
| } |
| |
| pub type Result<T> = std::result::Result<T, Error>; |
| |
| impl Display for Error { |
| #[remain::check] |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| use self::Error::*; |
| |
| #[sorted] |
| match self { |
| DbusChromeFeaturesService(err, m) => write!(f, "failed to call '{}': {}", m, err), |
| DbusConnection(err) => write!(f, "failed to connect to D-Bus: {}", err), |
| DbusGetUserIdHash(err) => { |
| write!(f, "failed to get user-id hash over to D-Bus: {:?}", err) |
| } |
| NoMatchFound => write!(f, "No match found."), |
| WrappedError(err) => write!(f, "{}", err), |
| } |
| } |
| } |
| |
| impl<T: error::Error> From<T> for Error { |
| fn from(err: T) -> Self { |
| Error::WrappedError(format!("{:?}", err)) |
| } |
| } |
| |
| // Return the user ID hash from the environment. If it is not available, fetch it from session |
| // manager and set the environment variable. |
| pub fn get_user_id_hash() -> Result<String> { |
| if let Ok(lookup) = env::var(CROS_USER_ID_HASH) { |
| return Ok(lookup); |
| } |
| |
| let user_id_hash = chromeos::get_user_id_hash().map_err(|err| { |
| error!("ERROR: D-Bus call failed: {}", err); |
| Error::DbusGetUserIdHash(err) |
| })?; |
| |
| env::set_var(CROS_USER_ID_HASH, &user_id_hash); |
| Ok(user_id_hash) |
| } |
| |
| // Return the output file path for the given output type in user's Downloads directory. |
| pub fn generate_output_file_path(output_type: &str, file_extension: &str) -> Result<String> { |
| let date = Local::now(); |
| let formatted_date = date.format("%Y-%m-%d_%H.%M.%S"); |
| let user_id_hash = get_user_id_hash()?; |
| let random_string: String = thread_rng().sample_iter(&Alphanumeric).take(6).collect(); |
| Ok(format!( |
| "/home/user/{}/Downloads/{}_{}_{}.{}", |
| user_id_hash, output_type, formatted_date, random_string, file_extension |
| )) |
| } |
| |
| pub fn is_chrome_feature_enabled(method_name: &str) -> Result<bool> { |
| let user_id_hash = get_user_id_hash()?; |
| |
| let connection = Connection::new_system().map_err(|err| { |
| error!("ERROR: Failed to get D-Bus connection: {}", err); |
| Error::DbusConnection(err) |
| })?; |
| |
| let proxy = connection.with_proxy( |
| "org.chromium.ChromeFeaturesService", |
| "/org/chromium/ChromeFeaturesService", |
| DEFAULT_DBUS_TIMEOUT, |
| ); |
| |
| let (reply,): (bool,) = proxy |
| .method_call( |
| "org.chromium.ChromeFeaturesServiceInterface", |
| method_name, |
| (user_id_hash,), |
| ) |
| .map_err(|err| { |
| error!("ERROR: D-Bus method call failed: {}", err); |
| Error::DbusChromeFeaturesService(err, method_name.to_string()) |
| })?; |
| |
| Ok(reply) |
| } |
| |
| pub fn is_removable() -> Result<bool> { |
| let dev = root_dev()?; |
| let groups = Regex::new(r#"/dev/([^/]+?)p?[0-9]+$"#)? |
| .captures(&dev) |
| .ok_or(Error::NoMatchFound)?; |
| |
| let dev = groups.get(1).unwrap().as_str(); |
| |
| match read_to_string(format!("/sys/block/{}/removable", dev)) { |
| Ok(contents) => Ok(contents.trim() == "1"), |
| Err(err) => Err(err.into()), |
| } |
| } |
| |
| pub fn set_dev_commands_included(value: bool) { |
| INCLUDE_DEV.store(value, Ordering::Release); |
| } |
| |
| pub fn set_usb_commands_included(value: bool) { |
| INCLUDE_USB.store(value, Ordering::Release); |
| } |
| |
| pub fn dev_commands_included() -> bool { |
| INCLUDE_DEV.load(Ordering::Acquire) |
| } |
| |
| pub fn usb_commands_included() -> bool { |
| INCLUDE_USB.load(Ordering::Acquire) |
| } |
| |
| /// # Safety |
| /// handler needs to be async safe. |
| pub unsafe fn set_signal_handlers(signums: &[c_int], handler: extern "C" fn(c_int)) { |
| for signum in signums { |
| if register_signal_handler(*signum, handler).is_err() { |
| error!("sigaction failed for {}", signum); |
| } |
| } |
| } |
| |
| pub fn clear_signal_handlers(signums: &[c_int]) { |
| for signum in signums { |
| if clear_signal_handler(*signum).is_err() { |
| error!("sigaction failed for {}", signum); |
| } |
| } |
| } |
| |
| fn root_dev() -> Result<String> { |
| let mut child = Command::new("rootdev") |
| .arg("-s") |
| .stdin(Stdio::null()) |
| .stdout(Stdio::piped()) |
| .spawn()?; |
| |
| let mut result = String::new(); |
| child.stdout.take().unwrap().read_to_string(&mut result)?; |
| child.wait()?; |
| |
| Ok(result.trim().to_string()) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn test_generate_output_file_path() { |
| // Set the user id hash env variable to a random value because it is necessary for output path generation. |
| env::set_var(CROS_USER_ID_HASH, "useridhashfortesting"); |
| let expected_path_re = |
| Regex::new(r"^/home/user/.+/Downloads/packet_capture_\d{4}-\d{2}-\d{2}_\d{2}.\d{2}.\d{2}_.{6}\.pcap$").unwrap(); |
| let result_output_path = generate_output_file_path("packet_capture", "pcap").unwrap(); |
| assert!(expected_path_re.is_match(&result_output_path)); |
| } |
| } |