use caps::{CapSet, Capability};
use clap::Parser;
use colored::{ColoredString, Colorize};
use indoc::{eprintdoc, indoc, printdoc};
use linux_memutils::agesa::{
find_agesa_version, find_agesa_version_in_memory_region,
get_reserved_regions_in_extended_memory, FoundVersion, SearchError, SearchResult,
};
use linux_memutils::iomem::MemoryRegion;
use std::error::Error;
use std::io;
use std::io::IsTerminal;
use std::process::ExitCode;
use std::sync::LazyLock;
use std::time::{Duration, Instant};
#[derive(Parser, Debug)]
#[command(version, about, after_help = indoc! {"
Exit Codes:
0: An AGESA version was found
1: No version was found in any searched memory region
2: /proc/iomem could not be read, e.g. due to insufficient permissions
3: /dev/mem could not be opened, e.g. due to insufficient permissions
4: An unhandled error occurred while reading a byte in /dev/mem
"})]
struct Cli {
#[arg(short, long, conflicts_with = "verbose")]
quiet: bool,
#[arg(short, long)]
verbose: bool,
}
static STATUS_PREFIX: LazyLock<ColoredString> = LazyLock::new(|| "::".blue().bold());
static RESULT_PREFIX: LazyLock<ColoredString> = LazyLock::new(|| "->".yellow().bold());
static ERROR_PREFIX: LazyLock<ColoredString> = LazyLock::new(|| "ERR".red().bold());
fn main() -> ExitCode {
let cli = Cli::parse();
if !caps::has_cap(None, CapSet::Effective, Capability::CAP_SYS_ADMIN).unwrap() {
eprintdoc! {"
{} Missing privileges for reading a memory map from /proc/iomem.
Please run agesafetch as root or add the SYS_ADMIN capability.
",
*ERROR_PREFIX,
}
return ExitCode::from(2);
}
match find_and_print_agesa_version(&cli) {
Err(SearchError::IomemUnreadable(_)) => {
eprintln!(
"{} Could not read /proc/iomem. Are its permissions correct?",
*ERROR_PREFIX,
);
ExitCode::from(2)
}
Err(SearchError::DevMemUnopenable(_)) => {
eprintdoc! {"
{} Could not open /dev/mem.
Please run agesafetch as root or add suitable capabilities.
",
*ERROR_PREFIX,
}
ExitCode::from(3)
}
Err(err @ SearchError::ByteUnreadable(_)) => {
eprintln!(
"{} Unhandled error while reading byte in physical memory: {}",
*ERROR_PREFIX,
err.source().unwrap(),
);
ExitCode::from(4)
}
Ok(()) => ExitCode::SUCCESS,
}
}
fn find_and_print_agesa_version(cli: &Cli) -> Result<(), SearchError> {
if !io::stdout().is_terminal() || cli.quiet {
if let Some(found_version) = find_agesa_version()? {
println!("{}", found_version.agesa_version.trim_end());
} else {
eprintln!("Did not find AGESA version.");
}
return Ok(());
}
let reserved_regions =
get_reserved_regions_in_extended_memory().map_err(SearchError::IomemUnreadable)?;
if cli.verbose {
println!(
"{} Memory map lists {} regions of type {} in extended memory.",
*STATUS_PREFIX,
reserved_regions.len().to_string().blue(),
"Reserved".italic(),
);
}
let search_start_time = Instant::now();
let found_version = search_regions_and_print_statuses(reserved_regions)?;
let search_duration = search_start_time.elapsed();
if let Some(found_version) = found_version {
println!(
"{} Found AGESA version: {}",
*RESULT_PREFIX,
found_version.agesa_version.trim_end().green().bold(),
);
if cli.verbose {
print_search_summary(&found_version, &search_duration);
}
} else {
eprintln!("{} Did not find AGESA version.", *RESULT_PREFIX);
}
Ok(())
}
fn search_regions_and_print_statuses(regions: Vec<MemoryRegion>) -> SearchResult {
for (i, region) in regions.into_iter().enumerate() {
println!(
"{} Searching {} region {} ({} KiB)...",
*STATUS_PREFIX,
region.region_type.to_string().italic(),
format!("#{}", i + 1).blue(),
region.size() / 1024,
);
match find_agesa_version_in_memory_region(region) {
Ok(None) => (),
result => return result,
}
}
Ok(None)
}
fn print_search_summary(found_version: &FoundVersion, search_duration: &Duration) {
printdoc! {"
{} Search Summary:
* Found at Physical Address: {:#x} (in {dev_mem})
* Surrounding Memory Region: {}
* Region Size: {} KiB
* Bytes Processed in Region: {} KiB
* Search Time: {} ms
",
*STATUS_PREFIX,
found_version.absolute_address,
found_version.surrounding_region,
found_version.surrounding_region.size() / 1024,
found_version.offset_in_region() / 1024,
search_duration.as_millis(),
dev_mem = "/dev/mem".dimmed(),
}
}