1#[cfg(feature = "python-bindings")]
4mod python;
5
6use std::error::Error;
7use std::ffi::OsString;
8use std::io::{self, IsTerminal};
9use std::sync::LazyLock;
10use std::time::{Duration, Instant};
11
12use caps::{CapSet, Capability};
13use clap::Parser;
14use colored::{ColoredString, Colorize};
15use indoc::{eprintdoc, indoc, printdoc};
16use linux_memutils::agesa::{
17 AgesaVersion, SearchError, SearchResult, find_agesa_version,
18 find_agesa_version_in_memory_region, get_reserved_regions_in_extended_memory,
19};
20use linux_memutils::iomem::MemoryRegion;
21
22#[derive(Parser, Debug)]
23#[command(version, about, after_help = indoc! {"
24 Exit Codes:
25 0: An AGESA version was found
26 1: No version was found in any searched memory region
27 2: /proc/iomem could not be read, e.g. due to insufficient permissions
28 3: /dev/mem could not be opened, e.g. due to insufficient permissions
29 4: An unhandled error occurred while reading a byte in /dev/mem
30"})]
31struct Cli {
32 #[arg(short, long, conflicts_with = "verbose")]
34 quiet: bool,
35 #[arg(short, long)]
37 verbose: bool,
38}
39
40#[repr(u8)]
41pub enum CliExitCode {
42 VersionFound = 0,
43 NoVersionFound = 1,
44 ProcIomemReadFailure = 2,
45 DevMemOpenFailure = 3,
46 DevMemReadFailure = 4,
47}
48
49static STATUS_PREFIX: LazyLock<ColoredString> = LazyLock::new(|| "::".blue().bold());
50static RESULT_PREFIX: LazyLock<ColoredString> = LazyLock::new(|| "->".yellow().bold());
51static ERROR_PREFIX: LazyLock<ColoredString> = LazyLock::new(|| "ERR".red().bold());
52
53#[must_use]
54#[allow(clippy::missing_panics_doc)]
55pub fn run_cli(args_os: Vec<OsString>) -> CliExitCode {
56 let cli = Cli::parse_from(args_os);
57
58 if !caps::has_cap(None, CapSet::Effective, Capability::CAP_SYS_ADMIN).unwrap() {
59 eprintdoc! {"
60 {} Missing privileges for reading a memory map from /proc/iomem.
61 Please run agesafetch as root or add the SYS_ADMIN capability.
62 ",
63 *ERROR_PREFIX,
64 }
65 return CliExitCode::ProcIomemReadFailure;
66 }
67
68 match find_and_print_agesa_version(&cli) {
69 Ok(Some(_)) => CliExitCode::VersionFound,
70 Ok(None) => CliExitCode::NoVersionFound,
71 Err(SearchError::IomemUnreadable(_)) => {
72 eprintln!(
73 "{} Could not read /proc/iomem. Are its permissions correct?",
74 *ERROR_PREFIX,
75 );
76 CliExitCode::ProcIomemReadFailure
77 }
78 Err(SearchError::DevMemUnopenable(_)) => {
79 eprintdoc! {"
80 {} Could not open /dev/mem.
81 Please run agesafetch as root or add suitable capabilities.
82 ",
83 *ERROR_PREFIX,
84 }
85 CliExitCode::DevMemOpenFailure
86 }
87 Err(err @ SearchError::ByteUnreadable(_)) => {
88 eprintln!(
89 "{} Unhandled error while reading byte in physical memory: {}",
90 *ERROR_PREFIX,
91 err.source().expect("search error should have a source"),
92 );
93 CliExitCode::DevMemReadFailure
94 }
95 }
96}
97
98fn find_and_print_agesa_version(cli: &Cli) -> SearchResult {
99 if !io::stdout().is_terminal() || cli.quiet {
100 let maybe_found_version = find_agesa_version()?;
101 match maybe_found_version {
102 Some(ref found_version) => println!("{}", found_version.version_string.trim_end()),
103 None => eprintln!("Did not find AGESA version."),
104 }
105 return Ok(maybe_found_version);
106 }
107
108 let reserved_regions =
109 get_reserved_regions_in_extended_memory().map_err(SearchError::IomemUnreadable)?;
110
111 if cli.verbose {
112 println!(
113 "{} Memory map lists {} regions of type {} in extended memory.",
114 *STATUS_PREFIX,
115 reserved_regions.len().to_string().blue(),
116 "Reserved".italic(),
117 );
118 }
119
120 let search_start_time = Instant::now();
121 let maybe_found_version = search_regions_and_print_statuses(reserved_regions)?;
122 let search_duration = search_start_time.elapsed();
123
124 match maybe_found_version {
125 Some(ref found_version) => {
126 println!(
127 "{} Found AGESA version: {}",
128 *RESULT_PREFIX,
129 found_version.version_string.trim_end().green().bold(),
130 );
131
132 if cli.verbose {
133 print_search_summary(found_version, &search_duration);
134 }
135 }
136 None => eprintln!("{} Did not find AGESA version.", *RESULT_PREFIX),
137 }
138
139 Ok(maybe_found_version)
140}
141
142fn search_regions_and_print_statuses(regions: Vec<MemoryRegion>) -> SearchResult {
146 for (i, region) in regions.into_iter().enumerate() {
147 println!(
148 "{} Searching {} region {} ({} KiB)...",
149 *STATUS_PREFIX,
150 region.region_type.to_string().italic(),
151 format!("#{}", i + 1).blue(),
152 region.size() / 1024,
153 );
154
155 match find_agesa_version_in_memory_region(region) {
156 Ok(None) => (),
157 result => return result,
158 }
159 }
160
161 Ok(None)
162}
163
164#[allow(clippy::cast_precision_loss)]
166fn print_search_summary(found_version: &AgesaVersion, search_duration: &Duration) {
167 printdoc! {"
168 {} Search Summary:
169 * Found at Physical Address: {:#x} (in {dev_mem})
170 * Surrounding Memory Region: {}
171 * Region Size: {} KiB
172 * Bytes Processed in Region: {} KiB
173 * Search Time: {:.1} ms
174 ",
175 *STATUS_PREFIX,
176 found_version.absolute_address,
177 found_version.surrounding_region,
178 found_version.surrounding_region.size() / 1024,
179 found_version.offset_in_region() / 1024,
180 search_duration.as_micros() as f64 / 1000.0,
181 dev_mem = "/dev/mem".dimmed(),
182 }
183}