|
| 1 | +// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +use std::fs::read_to_string; |
| 5 | +use std::sync::{Arc, Mutex}; |
| 6 | + |
| 7 | +use vmm::Vmm; |
| 8 | + |
| 9 | +use crate::fingerprint::Fingerprint; |
| 10 | + |
| 11 | +#[derive(Debug, thiserror::Error)] |
| 12 | +pub enum Error { |
| 13 | + /// Failed to dump CPU configuration. |
| 14 | + #[error("Failed to dump CPU config: {0}")] |
| 15 | + DumpCpuConfig(#[from] crate::template::dump::Error), |
| 16 | + /// Failed to read sysfs file. |
| 17 | + #[error("Failed to read {0}: {1}")] |
| 18 | + ReadSysfsFile(String, std::io::Error), |
| 19 | + /// Failed to get kernel version. |
| 20 | + #[error("Failed to get kernel version: {0}")] |
| 21 | + GetKernelVersion(std::io::Error), |
| 22 | + /// Shell command failed. |
| 23 | + #[error("`{0}` failed: {1}")] |
| 24 | + ShellCommand(String, String), |
| 25 | +} |
| 26 | + |
| 27 | +pub fn dump(vmm: Arc<Mutex<Vmm>>) -> Result<Fingerprint, Error> { |
| 28 | + Ok(Fingerprint { |
| 29 | + firecracker_version: crate::utils::CPU_TEMPLATE_HELPER_VERSION.to_string(), |
| 30 | + kernel_version: get_kernel_version()?, |
| 31 | + #[cfg(target_arch = "x86_64")] |
| 32 | + microcode_version: read_sysfs_file("/sys/devices/system/cpu/cpu0/microcode/version")?, |
| 33 | + #[cfg(target_arch = "aarch64")] |
| 34 | + microcode_version: read_sysfs_file( |
| 35 | + "/sys/devices/system/cpu/cpu0/regs/identification/revidr_el1", |
| 36 | + )?, |
| 37 | + bios_version: read_sysfs_file("/sys/devices/virtual/dmi/id/bios_version")?, |
| 38 | + // TODO: Replace this with `read_sysfs_file("/sys/devices/virtual/dmi/id/bios_release")` |
| 39 | + // after the end of kernel 4.14 support. |
| 40 | + // https://github.com/firecracker-microvm/firecracker/issues/3677 |
| 41 | + bios_revision: run_shell_command( |
| 42 | + "dmidecode -t bios | grep \"BIOS Revision\" | cut -d':' -f2 | tr -d ' \\n'", |
| 43 | + )?, |
| 44 | + guest_cpu_config: crate::template::dump::dump(vmm)?, |
| 45 | + }) |
| 46 | +} |
| 47 | + |
| 48 | +fn get_kernel_version() -> Result<String, Error> { |
| 49 | + // SAFETY: An all-zeroed value for `libc::utsname` is valid. |
| 50 | + let mut name: libc::utsname = unsafe { std::mem::zeroed() }; |
| 51 | + // SAFETY: The passed arg is a valid mutable reference of `libc::utsname`. |
| 52 | + let ret = unsafe { libc::uname(&mut name) }; |
| 53 | + if ret < 0 { |
| 54 | + return Err(Error::GetKernelVersion(std::io::Error::last_os_error())); |
| 55 | + } |
| 56 | + |
| 57 | + // SAFETY: The fields of `libc::utsname` are terminated by a null byte ('\0'). |
| 58 | + // https://man7.org/linux/man-pages/man2/uname.2.html |
| 59 | + let c_str = unsafe { std::ffi::CStr::from_ptr(name.release.as_ptr()) }; |
| 60 | + // SAFETY: The `release` field is an array of `char` in C, in other words, ASCII. |
| 61 | + let version = c_str.to_str().unwrap(); |
| 62 | + Ok(version.to_string()) |
| 63 | +} |
| 64 | + |
| 65 | +fn read_sysfs_file(path: &str) -> Result<String, Error> { |
| 66 | + let s = read_to_string(path).map_err(|err| Error::ReadSysfsFile(path.to_string(), err))?; |
| 67 | + Ok(s.trim_end_matches('\n').to_string()) |
| 68 | +} |
| 69 | + |
| 70 | +fn run_shell_command(cmd: &str) -> Result<String, Error> { |
| 71 | + let output = std::process::Command::new("sh") |
| 72 | + .args(["-c", cmd]) |
| 73 | + .output() |
| 74 | + .map_err(|err| Error::ShellCommand(cmd.to_string(), err.to_string()))?; |
| 75 | + if !output.status.success() { |
| 76 | + return Err(Error::ShellCommand( |
| 77 | + cmd.to_string(), |
| 78 | + format!( |
| 79 | + "code: {:?}\nstdout: {}\nstderr: {}", |
| 80 | + output.status.code(), |
| 81 | + std::str::from_utf8(&output.stdout).unwrap(), |
| 82 | + std::str::from_utf8(&output.stderr).unwrap(), |
| 83 | + ), |
| 84 | + )); |
| 85 | + } |
| 86 | + Ok(std::str::from_utf8(&output.stdout).unwrap().to_string()) |
| 87 | +} |
| 88 | + |
| 89 | +#[cfg(test)] |
| 90 | +mod tests { |
| 91 | + use super::*; |
| 92 | + |
| 93 | + #[test] |
| 94 | + fn test_get_kernel_version() { |
| 95 | + // `get_kernel_version()` should always succeed. |
| 96 | + get_kernel_version().unwrap(); |
| 97 | + } |
| 98 | + |
| 99 | + #[test] |
| 100 | + fn test_read_valid_sysfs_file() { |
| 101 | + // The sysfs file for microcode version should exist and be read. |
| 102 | + let valid_sysfs_path = "/sys/devices/virtual/dmi/id/bios_version"; |
| 103 | + read_sysfs_file(valid_sysfs_path).unwrap(); |
| 104 | + } |
| 105 | + |
| 106 | + #[test] |
| 107 | + fn test_read_invalid_sysfs_file() { |
| 108 | + let invalid_sysfs_path = "/sys/invalid/path"; |
| 109 | + if read_sysfs_file(invalid_sysfs_path).is_ok() { |
| 110 | + panic!("Should fail with `No such file or directory`"); |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + #[test] |
| 115 | + fn test_run_valid_shell_command() { |
| 116 | + let valid_cmd = "ls"; |
| 117 | + run_shell_command(valid_cmd).unwrap(); |
| 118 | + } |
| 119 | + |
| 120 | + #[test] |
| 121 | + fn test_run_invalid_shell_command() { |
| 122 | + let invalid_cmd = "unknown_command"; |
| 123 | + if run_shell_command(invalid_cmd).is_ok() { |
| 124 | + panic!("Should fail with `unknown_command: not found`"); |
| 125 | + } |
| 126 | + } |
| 127 | +} |
0 commit comments