use core::{num::ParseIntError, ops::Range};
use std::{fs::read_to_string, io::Error as IoError, path::Path};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("unable to read from /proc/iomem")]
Io(#[from] IoError),
#[error("unable to parse value")]
Parse(#[from] ParseIntError),
#[error("unable to parse line: {0}")]
ParseLine(String),
#[error("need CAP_SYS_ADMIN to read /proc/iomem")]
PermissionDenied,
}
pub fn parse() -> Result<Vec<Range<u64>>, Error> {
parse_file(Path::new("/proc/iomem"))
}
fn parse_file(path: &Path) -> Result<Vec<Range<u64>>, Error> {
let buffer = read_to_string(path)?;
let mut ranges = Vec::new();
for line in buffer.split_terminator('\n') {
if line.starts_with(' ') {
continue;
}
if !line.ends_with(" : System RAM") {
continue;
}
let mut line1 = line
.split_terminator(' ')
.next()
.ok_or_else(|| Error::ParseLine("invalid iomem line".to_string()))?
.split_terminator('-');
let start = line1
.next()
.ok_or_else(|| Error::ParseLine("invalid range start".to_string()))?;
let start = u64::from_str_radix(start, 16)?;
let end = line1
.next()
.ok_or_else(|| Error::ParseLine("invalid range end".to_string()))?;
let end = u64::from_str_radix(end, 16)?;
if start == 0 && end == 0 {
return Err(Error::PermissionDenied);
}
ranges.push(start..end);
}
Ok(merge_ranges(ranges))
}
#[must_use]
pub fn merge_ranges(mut ranges: Vec<Range<u64>>) -> Vec<Range<u64>> {
ranges.sort_unstable_by_key(|r| r.start);
let mut result: Vec<Range<u64>> = Vec::with_capacity(ranges.len());
for range in ranges {
match result.last_mut() {
Some(last) if last.end >= range.start => {
last.end = last.end.max(range.end);
}
_ => result.push(range),
}
}
result
}
#[must_use]
pub fn split_ranges(ranges: Vec<Range<u64>>, max_size: u64) -> Vec<Range<u64>> {
let mut result = vec![];
for mut range in ranges {
while range.end.saturating_sub(range.start) > max_size {
let end = range.start.saturating_add(max_size);
result.push(Range {
start: range.start,
end,
});
range.start = end;
}
if !range.is_empty() {
result.push(range);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_json_snapshot;
use std::path::PathBuf;
#[test]
fn test_merge_ranges() {
assert_json_snapshot!(merge_ranges(vec![0..3, 3..6, 7..10, 12..15]));
assert_json_snapshot!(merge_ranges(vec![0..3, 3..6, 6..10]));
}
#[test]
fn test_split_ranges() {
assert_json_snapshot!(split_ranges(vec![0..30; 1], 10));
assert_json_snapshot!(split_ranges(vec![0..30; 1], 7));
assert_json_snapshot!(split_ranges(vec![0..10, 10..20, 20..30], 7));
}
#[test]
fn test_parse_iomem() -> Result<(), Error> {
let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test");
for (filename, expected) in [
(
"iomem.txt",
vec![
4096..654_335,
1_048_576..1_073_676_287,
4_294_967_296..6_979_321_855,
],
),
(
"iomem-2.txt",
vec![
4096..655_359,
1_048_576..1_055_838_207,
1_056_026_624..1_073_328_127,
1_073_737_728..1_073_741_823,
4_294_967_296..6_979_321_855,
],
),
(
"iomem-3.txt",
vec![
65_536..649_215,
1_048_576..2_146_303_999,
2_146_435_072..2_147_483_647,
],
),
(
"iomem-4.txt",
vec![
4_096..655_359,
1_048_576..1_423_523_839,
1_423_585_280..1_511_186_431,
1_780_150_272..1_818_623_999,
1_818_828_800..1_843_613_695,
2_071_535_616..2_071_986_175,
4_294_967_296..414_464_344_063,
],
),
(
"iomem-5.txt",
vec![
4_096..655_359,
1_048_576..175_058_967,
175_058_968..175_067_223,
175_067_224..175_071_255,
175_071_256..175_077_463,
175_077_464..175_079_447,
175_079_448..175_087_703,
175_087_704..175_091_735,
175_091_736..175_124_567,
175_124_568..241_524_735,
241_643_520..251_310_079,
251_326_464..251_383_807,
251_424_768..264_671_231,
264_675_328..267_280_383,
267_739_136..267_866_111,
267_870_208..3_221_225_471,
4_294_967_296..13_958_643_711,
],
),
] {
let ranges = parse_file(&fixtures.join(filename))?;
assert_eq!(ranges, expected);
}
Ok(())
}
}