factory_fai: implement collect data function
Implement the function to collect simple FAI data, and leave TODO items
for some complicated cases.
BUG=b:230061675
TEST=cargo test
TEST=boot factory shim on DUT and perform FAI.
Cq-Depend: chromium:3626980, chromium:3653785
Change-Id: I266243c32152b4b2d05065f42a4517ffcc9e9bac
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/factory_installer/+/3627383
Reviewed-by: Meng-Huan Yu <menghuan@chromium.org>
Tested-by: Yu-An Wang <wyuang@google.com>
Reviewed-by: Stimim Chen <stimim@chromium.org>
Commit-Queue: Yu-An Wang <wyuang@google.com>
diff --git a/factory_install.sh b/factory_install.sh
index a0499d5..36b1c78 100644
--- a/factory_install.sh
+++ b/factory_install.sh
@@ -70,7 +70,7 @@
# Each action x is implemented in an action_$x handler (e.g.,
# action_i); see the handlers for more information about what
# each option is.
-SUPPORTED_ACTIONS=bcdeimrstuvyz
+SUPPORTED_ACTIONS=bcdefimrstuvyz
SUPPORTED_ACTIONS_BOARD=
# Supported actions when RSU is required.
@@ -1152,6 +1152,9 @@
menu_line U "Update TPM firmware" "Update TPM firmware"
menu_line E "Perform RSU" "Perform RSU (RMA Server Unlock)"
menu_line M "Enable factory mode" "Enable TPM factory mode"
+ # TODO(wyuang): show up in menu after all FAI functions ready.
+ # menu_line F "Perform factory FAI" \
+ # "Perform Factory FAI(First Article Inspection)"
menu_board
@@ -1498,6 +1501,11 @@
tpm_enable_factory_mode
}
+# F = Perform Factory FAI process
+action_f() {
+ /usr/sbin/factory_fai
+}
+
try_set_default_action_rsu() {
if is_rsu_required; then
log "RSU is required. " \
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 21cfd36..1ee0b0c 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -4,12 +4,15 @@
edition = "2021"
[dependencies]
-anyhow = { version = "1.0.0", optional = true }
-serde = { version = "1.0.0", features = ["derive"], optional = true }
-glob = { version = "0.3.0", optional = true }
-tempfile = { version = "3.2.0", optional = true }
-bincode = { version = "1.0.1", optional = true }
+anyhow = "1.0.0"
+bincode = "1.0.1"
+clap = { version = "3.1.0", features = ["derive"] }
+glob = "0.3.0"
+serde = { version = "1.0.0", features = ["derive"] }
+serde_json = "1.0.0"
+tempfile = "3.2.0"
[features]
+default = ["factory-fai", "factory-ufs"]
factory-fai = []
-factory-ufs = ["anyhow", "serde", "glob", "tempfile", "bincode"]
+factory-ufs = []
diff --git a/rust/src/bin/factory_fai.rs b/rust/src/bin/factory_fai.rs
index f3c87b4..4044515 100644
--- a/rust/src/bin/factory_fai.rs
+++ b/rust/src/bin/factory_fai.rs
@@ -2,7 +2,30 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// TODO(wyuang): implement factory FAI.
-fn main() {
- println!("Factory FAI is not available yet.");
+use std::fs;
+
+use factory_installer::factory_fai;
+use factory_installer::factory_fai::args::{Args, Parser};
+use factory_installer::factory_fai::config;
+
+use anyhow::{Context, Result};
+
+fn main() -> Result<()> {
+ let args = Args::parse();
+
+ if args.dump_config {
+ println!("{}", config::DEFAULT_CONFIG);
+ return Ok(());
+ }
+
+ let fai_config = config::load_config(args.config_path).context("Failed to load config.")?;
+
+ let data = factory_fai::collect_fai_data(fai_config)?;
+
+ if let Some(path) = args.output_path {
+ println!("Writing result to {}.", path);
+ fs::write(path, &data).context("Failed to write result to output file.")?;
+ }
+ println!("{}", &data);
+ Ok(())
}
diff --git a/rust/src/factory_fai/args.rs b/rust/src/factory_fai/args.rs
new file mode 100644
index 0000000..d3520a2
--- /dev/null
+++ b/rust/src/factory_fai/args.rs
@@ -0,0 +1,22 @@
+// Copyright 2022 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.
+
+pub use clap::Parser;
+
+/// ChromeOS Factory First Article Inspection Process.
+#[derive(Parser, Debug)]
+#[clap(author, version, about, long_about = None)]
+pub struct Args {
+ /// Output the collected data to the file path
+ #[clap(short, long)]
+ pub output_path: Option<String>,
+
+ /// Path of config file.
+ #[clap(short, long)]
+ pub config_path: Option<String>,
+
+ /// Dump the default configuration.
+ #[clap(long)]
+ pub dump_config: bool,
+}
diff --git a/rust/src/factory_fai/config.rs b/rust/src/factory_fai/config.rs
new file mode 100644
index 0000000..5fd8014
--- /dev/null
+++ b/rust/src/factory_fai/config.rs
@@ -0,0 +1,113 @@
+// Copyright 2022 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.
+
+use std::collections::HashMap;
+use std::fs;
+use std::path::Path;
+
+use anyhow::Result;
+use serde::{Deserialize, Serialize};
+use serde_json;
+
+// TODO(wyuang): collect more default data:
+// 1. Partition Table ("cgpt show ...")
+// 2. Release image info ("mount $DEV; cat /$DEV/etc/lsb-release")
+// 3. Signing key
+// 4. CBI data ("ectool cbi get <tag> ...")
+// TODO(wyuang): VPD may contains "\n", we would probably want to read values from
+// `/sys/firmware/vpd` instead of `vpd` command.
+pub const DEFAULT_CONFIG: &str = r##"{
+ "ro_vpd": {
+ "DataCommand": {
+ "cmd": "vpd",
+ "args": ["-i", "RO_VPD", "-l"]
+ }
+ },
+ "rw_vpd": {
+ "DataCommand": {
+ "cmd": "vpd",
+ "args": ["-i", "RW_VPD", "-l"]
+ }
+ },
+ "stateful_partition": {
+ "DataCommand": {
+ "cmd": "find",
+ "args": ["/mnt/stateful_partition", "-not", "-type", "d"]
+ }
+ },
+ "tpm_version": {
+ "DataCommand": {
+ "cmd": "tpm_version"
+ }
+ },
+ "gsc_capabilities": {
+ "DataCommand": {
+ "cmd": "gsctool",
+ "args": ["-a", "-I", "-M"]
+ }
+ },
+ "gsc_board_id": {
+ "DataCommand": {
+ "cmd": "gsctool",
+ "args": ["-a", "-i", "-M"]
+ }
+ },
+ "gsc_firmware_version": {
+ "DataCommand": {
+ "cmd": "gsctool",
+ "args": ["-a", "-f", "-M"]
+ }
+ },
+ "crossystem": {
+ "DataCommand": {
+ "cmd": "crossystem"
+ }
+ },
+ "ap_write_protect": {
+ "DataCommand": {
+ "cmd": "flashrom",
+ "args": ["-p", "host", "--wp-status"]
+ }
+ },
+ "ec_write_protect": {
+ "DataCommand": {
+ "cmd": "flashrom",
+ "args": ["-p", "ec", "--wp-status"]
+ }
+ },
+ "factory_instal_shim_version": {
+ "DataCommand": {
+ "cmd": "cat",
+ "args": ["/etc/lsb-release"]
+ }
+ }
+}"##;
+
+#[derive(Serialize, Deserialize)]
+pub struct DataCommand {
+ pub cmd: String,
+
+ #[serde(default)]
+ pub args: Vec<String>,
+}
+
+#[derive(Serialize, Deserialize)]
+pub enum DataCollector {
+ // TODO(wyuang): support DataFunction and DataFiles for calling function and read files.
+ DataCommand(DataCommand),
+}
+
+pub type FAIConfig = HashMap<String, DataCollector>;
+
+fn load_config_file<P: AsRef<Path>>(path: P) -> Result<FAIConfig> {
+ let config_content = fs::read_to_string(path)?;
+ Ok(serde_json::from_str(&config_content)?)
+}
+
+pub fn load_config<P: AsRef<Path>>(config_path: Option<P>) -> Result<FAIConfig> {
+ match config_path {
+ Some(file_path) => load_config_file(&file_path),
+ None => Ok(serde_json::from_str(DEFAULT_CONFIG)?),
+ }
+}
diff --git a/rust/src/factory_fai/mod.rs b/rust/src/factory_fai/mod.rs
new file mode 100644
index 0000000..b105299
--- /dev/null
+++ b/rust/src/factory_fai/mod.rs
@@ -0,0 +1,37 @@
+// Copyright 2022 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.
+
+use crate::factory_fai::config::{DataCollector, FAIConfig};
+use crate::utils::process_utils::{Command, StringOutput};
+
+use anyhow::{Context, Result};
+use serde_json;
+use serde_json::{Map, Value};
+
+pub mod args;
+pub mod config;
+
+fn default_parse_function(output: String) -> Value {
+ // TODO(wyuang): implement customized parse functions for different
+ // collected data.
+ Value::String(output.trim().to_string())
+}
+
+pub fn collect_fai_data(fai_config: FAIConfig) -> Result<String> {
+ let mut fai_data = Map::new();
+ for (name, config) in fai_config.iter() {
+ let data = match config {
+ DataCollector::DataCommand(data_cmd) => {
+ let output = Command::new(&data_cmd.cmd)
+ .args(&data_cmd.args)
+ .output()
+ .with_context(|| format!("Failed to collect \"{}\".", name))?;
+ default_parse_function(output.stdout())
+ }
+ };
+
+ fai_data.insert(name.to_string(), data);
+ }
+ Ok(serde_json::to_string_pretty(&fai_data)?)
+}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index f4aadd5..7fadc71 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -4,4 +4,8 @@
#[cfg(feature = "factory-ufs")]
pub mod factory_ufs;
+
+#[cfg(feature = "factory-fai")]
+pub mod factory_fai;
+
pub mod utils;
diff --git a/rust/tests/factory_fai/test_config.json b/rust/tests/factory_fai/test_config.json
new file mode 100644
index 0000000..a4e27c9
--- /dev/null
+++ b/rust/tests/factory_fai/test_config.json
@@ -0,0 +1,13 @@
+{
+ "data1": {
+ "DataCommand": {
+ "cmd": "echo",
+ "args": ["some", "data"]
+ }
+ },
+ "data2": {
+ "DataCommand": {
+ "cmd": "echo"
+ }
+ }
+}
diff --git a/rust/tests/factory_fai/test_expected_data.json b/rust/tests/factory_fai/test_expected_data.json
new file mode 100644
index 0000000..52ffc11
--- /dev/null
+++ b/rust/tests/factory_fai/test_expected_data.json
@@ -0,0 +1,4 @@
+{
+ "data1": "some data",
+ "data2": ""
+}
diff --git a/rust/tests/factory_fai/test_factory_fai.rs b/rust/tests/factory_fai/test_factory_fai.rs
new file mode 100644
index 0000000..0bd5bdc
--- /dev/null
+++ b/rust/tests/factory_fai/test_factory_fai.rs
@@ -0,0 +1,21 @@
+// Copyright 2022 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.
+
+use std::fs;
+
+use factory_installer::factory_fai;
+use factory_installer::factory_fai::config;
+
+const TEST_CONFIG_PATH: &str = "tests/factory_fai/test_config.json";
+const TEST_EXPECTED_DATA_PATH: &str = "tests/factory_fai/test_expected_data.json";
+
+#[test]
+fn test_collect_fai_data_success() {
+ let output =
+ factory_fai::collect_fai_data(config::load_config(Some(TEST_CONFIG_PATH)).unwrap())
+ .unwrap();
+ let expected = fs::read_to_string(TEST_EXPECTED_DATA_PATH).unwrap();
+
+ assert_eq!(expected.trim(), output.trim());
+}
diff --git a/rust/tests/tests.rs b/rust/tests/tests.rs
index 76b5cdd..df80dbe 100644
--- a/rust/tests/tests.rs
+++ b/rust/tests/tests.rs
@@ -6,3 +6,8 @@
mod utils {
mod test_process_utils;
}
+
+#[cfg(test)]
+mod factory_fai {
+ mod test_factory_fai;
+}