blob: 697ad08e6235c5e969178ccfecb4446d44669e5a [file] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fs;
use anyhow::{Context as _, Result};
use regex::Regex;
use crate::system::context::Context;
use crate::utils::process_utils::StringOutput;
use crate::utils::string_utils;
const GET_FIXED_DST_DRIVE_CMD: &str =
". /usr/sbin/write_gpt.sh; load_base_vars; get_fixed_dst_drive;";
/// Gets the path of fixed device storage.
///
/// # Return
/// The return string will be a path of device like `/dev/sda` or `/dev/mmcnlk0`.
pub fn get_fixed_device_storage(context: &mut dyn Context) -> Result<String> {
Ok(context
.command("sh")
.args(["-c", GET_FIXED_DST_DRIVE_CMD])
.output()?
.stdout()
.trim()
.to_string())
}
/// Gets the rootfs partition of release image.
/// There're two cases of storage device path:
/// /dev/sda -> release rootfs at /dev/sda5
/// /dev/mmcblk0 -> release rootfs at /dev/mmcblk0p5
pub fn get_release_rootfs_partition(context: &mut dyn Context) -> Result<String> {
let mut fixed_device_storage = get_fixed_device_storage(context)?;
// If the last character is digit, it is the case of `/dev/mmcblk0`, Otherwise is the case of
// `/dev/sda`.
if fixed_device_storage
.chars()
.last()
.unwrap_or(' ')
.is_digit(10)
{
fixed_device_storage.push_str("p5");
} else {
fixed_device_storage.push('5');
}
Ok(fixed_device_storage)
}
/// Gets the stateful partition of release image.
/// There're two cases of storage device path:
/// /dev/sda -> release stateful at /dev/sda1
/// /dev/mmcblk0 -> release stateful at /dev/mmcblk0p1
///
/// If the device enables LVM stateful partition, get the partition path from `pvdisplay`.
pub fn get_release_stateful_partition(context: &mut dyn Context) -> Result<String> {
let mut fixed_device_storage = get_fixed_device_storage(context)?;
// If the last character is digit, it is the case of `/dev/mmcblk0`, Otherwise is the case of
// `/dev/sda`.
if fixed_device_storage
.chars()
.last()
.unwrap_or(' ')
.is_digit(10)
{
fixed_device_storage.push_str("p1");
} else {
fixed_device_storage.push('1');
}
// Get the device path to LVM stateful partition.
let mut vg_name = context
.command("pvdisplay")
.args([
"-C",
"--quiet",
"--noheadings",
"--separator",
"'|'",
"-o",
"vg_name",
&fixed_device_storage,
])
.output()?
.stdout();
vg_name.retain(|c| !c.is_whitespace());
if vg_name.len() > 0 {
context
.command("vgchange")
.args(["-ay", &vg_name])
.output()?
.exit_ok()?;
return Ok(format!("/dev/{}/unencrypted", vg_name));
}
Ok(fixed_device_storage)
}
/// Gets the stateful partition of dev image.
pub fn get_dev_stateful_partition(context: &dyn Context) -> Result<String> {
// The format of `lsb-factory` is "shell-executable" which may contain "#" comments and empty
// lines.
let lsb_factory = string_utils::parse_dict_ignore_error(
fs::read_to_string(
context
.root_dir()
.join("mnt/stateful_partition/dev_image/etc/lsb-factory"),
)?
.trim()
.split("\n")
.map(|line| line.split("#").nth(0).unwrap())
.collect::<Vec<&str>>(),
"=",
);
// `REAL_USB_DEV` presents the rootfs partition (usually the 3rd partition).
// Replace with the 1st partition which is the stateful partition.
// E.g. /dev/sda3 -> /dev/sda1
let dev_stateful_partition = String::from(
Regex::new("[0-9]")?.replace(
lsb_factory
.get("REAL_USB_DEV")
.context("Unable to get the path of dev image.")?,
"1",
),
)
// Shell-executable allows value quoted by "".
.trim_matches('"')
.to_string();
Ok(dev_stateful_partition)
}
/// Gets the model name of device.
pub fn get_model_name(context: &mut dyn Context) -> Result<String> {
Ok(context
.command("cros_config")
.args(["/", "name"])
.output()?
.stdout())
}
/// Gets the firmware manifest key of device.
pub fn get_firmware_manifest_key(context: &mut dyn Context) -> Result<String> {
let crosid = string_utils::parse_dict_ignore_error(
context
.command("crosid")
.output()?
.stdout()
.trim()
.split("\n")
.collect::<Vec<&str>>(),
"=",
);
Ok(crosid
.get("FIRMWARE_MANIFEST_KEY")
.context("Unable to get firmware manifest key from crosid.")?
.trim_matches('\'')
.to_string())
}
/// Get the serial number of the device.
pub fn get_serial_number(context: &dyn Context) -> Result<String> {
Ok(fs::read_to_string(
context.root_dir().join("sys/firmware/vpd/ro/serial_number"),
)?)
}
/// Returns True if the device is using Ti50.
pub fn is_ti50(context: &mut dyn Context) -> Result<bool> {
let gsc_name = context
.command("sh")
.args(["-c", ". /usr/share/cros/gsc-constants.sh && gsc_name"])
.output()?
.stdout();
Ok(gsc_name == "ti50")
}
#[cfg(test)]
mod tests {
use std::fs;
use std::process::Output;
use crate::device;
use crate::system::context::{Context, ContextImpl};
use crate::utils::file_utils;
use crate::utils::process_utils::StringOutput;
#[test]
fn test_get_fix_device_storage_success() {
let mut context = ContextImpl::new();
context.set_command_stdout("sh", "/dev/sda".to_string());
assert_eq!(
device::get_fixed_device_storage(&mut context).unwrap(),
"/dev/sda".to_string()
);
}
#[test]
fn test_get_release_rootfs_partition_success_dev_sda() {
let mut context = ContextImpl::new();
context.set_command_stdout("sh", "/dev/sda".to_string());
assert_eq!(
device::get_release_rootfs_partition(&mut context).unwrap(),
"/dev/sda5".to_string()
);
}
#[test]
fn test_get_release_rootfs_partition_success_dev_mmcblk0() {
let mut context = ContextImpl::new();
context.set_command_stdout("sh", "/dev/mmcblk0".to_string());
assert_eq!(
device::get_release_rootfs_partition(&mut context).unwrap(),
"/dev/mmcblk0p5".to_string()
);
}
#[test]
fn test_get_release_stateful_partition_success_dev_sda() {
let mut context = ContextImpl::new();
context.set_command_stdout("sh", "/dev/sda".to_string());
context.set_command_stdout("pvdisplay", "".to_string());
assert_eq!(
device::get_release_stateful_partition(&mut context).unwrap(),
"/dev/sda1".to_string()
);
}
#[test]
fn test_get_release_stateful_partition_success_dev_mmcblk0() {
let mut context = ContextImpl::new();
context.set_command_stdout("sh", "/dev/mmcblk0".to_string());
context.set_command_stdout("pvdisplay", "".to_string());
assert_eq!(
device::get_release_stateful_partition(&mut context).unwrap(),
"/dev/mmcblk0p1".to_string()
);
}
#[test]
fn test_get_release_stateful_partition_success_lvm() {
let mut context = ContextImpl::new();
context.set_command_stdout("sh", "/dev/sda".to_string());
context.set_command_stdout("pvdisplay", "vg_name".to_string());
context.set_command_output("vgchange", Output::new(0, None, None));
assert_eq!(
device::get_release_stateful_partition(&mut context).unwrap(),
"/dev/vg_name/unencrypted".to_string()
);
}
#[test]
fn test_get_dev_stateful_partition_success() {
let context = ContextImpl::new();
let dir = context
.root_dir()
.join("mnt/stateful_partition/dev_image/etc");
fs::create_dir_all(&dir).unwrap();
fs::write(dir.join("lsb-factory"), "REAL_USB_DEV=/dev/sda3").unwrap();
assert_eq!(
device::get_dev_stateful_partition(&context).unwrap(),
"/dev/sda1".to_string()
);
}
#[test]
fn test_get_model_name_success() {
let mut context = ContextImpl::new();
context.set_command_stdout("cros_config", "model".to_string());
assert_eq!(
device::get_model_name(&mut context).unwrap(),
"model".to_string()
);
}
#[test]
fn test_get_firmware_manifest_key_success() {
let mut context = ContextImpl::new();
context.set_command_stdout("crosid", "FIRMWARE_MANIFEST_KEY=key".to_string());
assert_eq!(
device::get_firmware_manifest_key(&mut context).unwrap(),
"key".to_string()
);
}
#[test]
fn test_get_serial_number_success() {
let context = ContextImpl::new();
let filepath = context.root_dir().join("sys/firmware/vpd/ro/serial_number");
file_utils::create_file(filepath, "a_serial_number").unwrap();
assert_eq!(
device::get_serial_number(&context).unwrap(),
"a_serial_number".to_string()
);
}
#[test]
fn test_is_ti50_true() {
let mut context = ContextImpl::new();
context.set_command_stdout("sh", "ti50".to_string());
assert!(device::is_ti50(&mut context).unwrap());
}
#[test]
fn test_is_ti50_false() {
let mut context = ContextImpl::new();
context.set_command_stdout("sh", "cr50".to_string());
assert!(!device::is_ti50(&mut context).unwrap());
}
}