blob: 3513cddf1d2c67a912af6d057be6c9daaf86f80a [file] [log] [blame]
// 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.
// The legacy module registers dispatcher commands from the original shell implementation.
use std::io::{copy, Write};
use std::process::{self, Stdio};
use crate::dispatcher::{self, wait_for_result, Arguments, Command, Dispatcher};
use crate::util;
const BASE_COMMANDS: &[&str] = &[
"autest",
"authpolicy_debug",
"battery_firmware",
"battery_test",
"borealis",
"bt_console",
"chaps_debug",
"connectivity",
"cras",
"cryptohome_status",
"diag",
"dump_emk",
"enroll_status",
"evtest",
"exit", // Included for the "exit" help entry.
"ff_debug",
"free",
"gesture_prop",
"help", // Included for the "help" help entry.
"help_advanced", // Included for the "help_advanced" help entry.
"ipaddrs",
"meminfo",
"memory_test",
"modem",
"network_diag",
"p2p_update",
"ping",
"rlz",
"rollback",
"route",
"set_apn",
"set_arpgw",
"set_cellular_ppp",
"set_wake_on_lan",
"ssh",
"storage_test_1",
"storage_test_2",
"swap",
"sync",
"syslog",
"time_info",
"top",
"tpm_status",
"tracepath",
"u2f_flags",
"uname",
"update_over_cellular",
"upload_crashes",
"upload_devcoredumps",
"uptime",
"vmstat",
"vsh",
"wifi_fw_dump",
"wifi_power_save",
"wpa_debug",
];
const DEV_COMMANDS: &[&str] = &["live_in_a_coal_mine", "packet_capture", "systrace"];
const USB_COMMANDS: &[&str] = &["update_firmware", "install", "upgrade"];
pub fn register(dispatcher: &mut Dispatcher) {
register_legacy_commands(&BASE_COMMANDS, dispatcher)
}
pub fn register_dev_mode_commands(dispatcher: &mut Dispatcher) {
register_legacy_commands(&DEV_COMMANDS, dispatcher)
}
pub fn register_removable_commands(dispatcher: &mut Dispatcher) {
register_legacy_commands(&USB_COMMANDS, dispatcher)
}
fn register_legacy_commands(commands: &[&str], dispatcher: &mut Dispatcher) {
for cmd in commands {
dispatcher.register_command(legacy_command(cmd));
}
}
fn legacy_command(name: &str) -> dispatcher::Command {
Command::new(name.to_string(), "".to_string(), "".to_string())
.set_command_callback(Some(execute_legacy_command))
.set_help_callback(legacy_help_callback)
}
fn legacy_crosh() -> process::Command {
let mut sub = process::Command::new("crosh.sh");
if util::dev_commands_included() {
sub.arg("--dev");
}
if util::usb_commands_included() {
// This includes '--removable'.
sub.arg("--usb");
}
sub
}
fn execute_legacy_command(
_cmd: &dispatcher::Command,
args: &Arguments,
) -> Result<(), dispatcher::Error> {
wait_for_result(
legacy_crosh()
.arg("--")
.args(args.get_tokens())
.spawn()
.map_err(|_| dispatcher::Error::CommandReturnedError)?,
)
}
fn legacy_help_callback(cmd: &Command, w: &mut dyn Write, _level: usize) {
let mut sub = legacy_crosh()
.arg("--")
.arg("help")
.arg(cmd.get_name())
.stdin(Stdio::null())
.stdout(Stdio::piped())
.spawn()
.unwrap();
if copy(&mut sub.stdout.take().unwrap(), w).is_err() {
panic!();
}
if sub.wait().is_err() {
panic!();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use std::env;
use std::fs::File;
use std::io::{prelude::*, BufReader};
use std::path::PathBuf;
use crate::base;
use crate::dev;
const SOURCE_PATH_VAR: &str = "S";
const BIN_DIR_SHELL: &str = "/usr/bin/crosh.sh";
const DEFAULT_ROOT: &str = "/usr/share/crosh";
const BASE_SHELL: &str = "crosh";
const DEV_SHELL: &str = "dev.d/50-crosh.sh";
const USB_SHELL: &str = "removable.d/50-crosh.sh";
// Commands that are excluded from the checks because they are conditionally registered.
const IGNORE_COMMANDS: &[&str] = &["ccd_pass", "verify_ro", "vmc"];
enum ShellSource {
Base,
Dev,
USB,
}
fn get_shell_path(shell: &ShellSource) -> PathBuf {
let mut path = PathBuf::new();
// Try working directory first.
path.push(match shell {
ShellSource::Base => BASE_SHELL,
ShellSource::Dev => DEV_SHELL,
ShellSource::USB => USB_SHELL,
});
if path.exists() {
return path;
} else {
path = PathBuf::new();
}
// Fall back to source directory environmental variable, and lastly to installed path.
match env::var(SOURCE_PATH_VAR) {
Ok(s) => path.push(&s),
Err(_) => {
if let ShellSource::Base = shell {
path.push(BIN_DIR_SHELL);
return path;
}
path.push(DEFAULT_ROOT)
}
}
path.push(match shell {
ShellSource::Base => BASE_SHELL,
ShellSource::Dev => DEV_SHELL,
ShellSource::USB => USB_SHELL,
});
path
}
fn get_dispatcher(shell: &ShellSource) -> Dispatcher {
let mut dispatcher = Dispatcher::new();
match shell {
ShellSource::Base => {
register(&mut dispatcher);
base::register(&mut dispatcher);
}
ShellSource::Dev => {
register_dev_mode_commands(&mut dispatcher);
dev::register(&mut dispatcher);
}
ShellSource::USB => {
register_removable_commands(&mut dispatcher);
}
};
dispatcher
}
fn get_cmds(shell: &ShellSource) -> Vec<String> {
const PREFIX: &str = "cmd_";
const SUFFIX: &str = "() (";
let shell = File::open(get_shell_path(shell)).unwrap();
let mut result: Vec<String> = Vec::new();
for itr in BufReader::new(shell).lines() {
let line = itr.unwrap();
let trimmed = line.trim();
if trimmed.starts_with(PREFIX) && trimmed.ends_with(SUFFIX) {
result.push(
trimmed[PREFIX.len()..trimmed.len() - SUFFIX.len()]
.trim()
.to_string(),
);
}
}
result
}
fn verify_shell(shell: &ShellSource) {
let dispatcher = get_dispatcher(&shell);
let command_list = get_cmds(&shell);
let mut missing_commands: Vec<&str> = Vec::new();
let mut available_commands: HashSet<&str> = HashSet::new();
// Verify all the crosh.sh commands are registered in rust-crosh.
for command_name in &command_list {
available_commands.insert(command_name);
if dispatcher.find_by_name(&command_name).is_none()
&& !IGNORE_COMMANDS.contains(&&command_name[..])
{
missing_commands.push(command_name);
}
}
assert!(
missing_commands.is_empty(),
"commands not registered: {:?}",
missing_commands
);
// Verify all the legacy commands in rust-crosh have an implementation in crosh.sh.
let mut extra_commands: Vec<&str> = Vec::new();
for cmd in match shell {
ShellSource::Base => &BASE_COMMANDS[..],
ShellSource::Dev => &DEV_COMMANDS[..],
ShellSource::USB => &USB_COMMANDS[..],
} {
if !available_commands.contains(cmd) && !IGNORE_COMMANDS.contains(&cmd) {
extra_commands.push(cmd);
}
}
assert!(
missing_commands.is_empty(),
"commands registered without implementation: {:?}",
missing_commands
);
}
#[test]
fn test_all_base_commands_registered() {
verify_shell(&ShellSource::Base)
}
#[test]
fn test_all_dev_commands_registered() {
verify_shell(&ShellSource::Dev)
}
#[test]
fn test_all_usb_commands_registered() {
verify_shell(&ShellSource::USB)
}
}