blob: 318baf267430433cd3b6d528db52b60c2095a3a7 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{Result, anyhow, bail};
use ffx_scrutiny_shell_args::ScrutinyShellCommand;
use ffx_writer::SimpleWriter;
use fho::{FfxMain, FfxTool};
use scrutiny_frontend::{
BlobFsExtractController, FarMetaExtractController, FvmExtractController,
ZbiExtractBootfsPackageIndex, ZbiExtractCmdlineController, ZbiExtractController,
ZbiListBootfsController,
};
use serde::Deserialize;
use serde_json::{Value, json};
use std::collections::{HashMap, VecDeque};
use std::path::PathBuf;
#[derive(FfxTool)]
pub struct ScrutinyShellTool {
#[command]
pub cmd: ScrutinyShellCommand,
}
fho::embedded_plugin!(ScrutinyShellTool);
#[async_trait::async_trait(?Send)]
impl FfxMain for ScrutinyShellTool {
type Writer = SimpleWriter;
async fn main(self, _writer: Self::Writer) -> fho::Result<()> {
let (namespace, args) =
parse_command(self.cmd.command).map_err(|e| fho::Error::User(e.into()))?;
let value = match namespace.as_str() {
"tool.blobfs.extract" => {
BlobFsExtractController::extract(args.input, args.output.unwrap())
}
"tool.far.extract.meta" => FarMetaExtractController::extract(args.input),
"tool.fvm.extract" => FvmExtractController::extract(args.input, args.output.unwrap()),
"tool.zbi.extract" => ZbiExtractController::extract(args.input, args.output.unwrap()),
"tool.zbi.extract.cmdline" => ZbiExtractCmdlineController::extract(args.input),
"tool.zbi.list.bootfs" => ZbiListBootfsController::extract(args.input),
"tool.zbi.extract.bootfs.packages" => ZbiExtractBootfsPackageIndex::extract(args.input),
_ => Err(anyhow!("Invalid command: {}", namespace)),
}?;
let s = serde_json::to_string_pretty(&value).map_err(|e| fho::Error::User(e.into()))?;
println!("{}", s);
Ok(())
}
}
#[derive(Deserialize, Debug, PartialEq)]
pub struct ToolArgs {
input: PathBuf,
output: Option<PathBuf>,
}
fn parse_command(command: String) -> Result<(String, ToolArgs)> {
let mut tokens: VecDeque<String> =
command.split_whitespace().map(|s| String::from(s)).collect();
if tokens.len() == 0 {
bail!("Empty command");
}
let namespace = tokens.pop_front().unwrap();
// Parse the command arguments.
let empty_command: HashMap<String, String> = HashMap::new();
let mut query = json!(empty_command);
if !tokens.is_empty() {
if tokens.front().unwrap().starts_with("--") {
query = args_to_json(&tokens);
}
}
let args: ToolArgs = serde_json::from_value(query)?;
Ok((namespace, args))
}
/// Converts a series of tokens into a single json value. For example:
/// --foo bar --baz a b would produce the json:
/// {
/// "foo": "bar",
/// "baz": "a b",
/// }
pub fn args_to_json(tokens: &VecDeque<String>) -> Value {
let mut name: Option<String> = None;
let mut map: HashMap<String, String> = HashMap::new();
for token in tokens.iter() {
if token.starts_with("--") {
let stripped_name = token.strip_prefix("--").unwrap().to_string();
map.insert(stripped_name.clone(), String::new());
name = Some(stripped_name);
} else if let Some(name) = &name {
if let Some(entry) = map.get_mut(name) {
if !entry.is_empty() {
entry.push_str(" ");
}
entry.push_str(token);
}
}
}
json!(map)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_command() {
assert_eq!(parse_command("foo".into()).is_ok(), false);
assert_eq!(
parse_command("foo --input in".into()).unwrap(),
("foo".into(), ToolArgs { input: "in".into(), output: None })
);
assert_eq!(
parse_command("foo --input in --output out".into()).unwrap(),
("foo".into(), ToolArgs { input: "in".into(), output: Some("out".into()) })
);
assert_eq!(parse_command("foo --output out".into()).is_ok(), false);
}
}