From b535de3b227c59e61f7c16974809ab917cd7699a Mon Sep 17 00:00:00 2001 From: estodi <83288835+estodi@users.noreply.github.com> Date: Mon, 7 Jul 2025 09:05:55 -0700 Subject: [PATCH 1/3] pmap: implemented range option --- src/uu/pmap/src/maps_format_parser.rs | 7 ++ src/uu/pmap/src/pmap.rs | 132 ++++++++++++++++++-------- src/uu/pmap/src/pmap_config.rs | 2 + 3 files changed, 100 insertions(+), 41 deletions(-) diff --git a/src/uu/pmap/src/maps_format_parser.rs b/src/uu/pmap/src/maps_format_parser.rs index 0e077609..722c32e8 100644 --- a/src/uu/pmap/src/maps_format_parser.rs +++ b/src/uu/pmap/src/maps_format_parser.rs @@ -39,6 +39,13 @@ impl Address { pub fn zero_pad(&self) -> String { format!("{:0>16}", self.start) } + + // Checks whether an entry's address range overlaps the limits specified by the range option. + // Note: Even if a reversed range (high < low) is given, an entry still hits + // only if the specified range lies entirely within the entry's address range. + pub fn is_within_range(&self, pmap_config: &PmapConfig) -> bool { + pmap_config.range_low < self.high && self.low <= pmap_config.range_high + } } // Represents a set of permissions from the "perms" column of /proc//maps. diff --git a/src/uu/pmap/src/pmap.rs b/src/uu/pmap/src/pmap.rs index aa5bb943..180eb337 100644 --- a/src/uu/pmap/src/pmap.rs +++ b/src/uu/pmap/src/pmap.rs @@ -9,8 +9,8 @@ use pmap_config::{create_rc, pmap_field_name, PmapConfig}; use smaps_format_parser::{parse_smaps, SmapTable}; use std::env; use std::fs; -use std::io::Error; -use uucore::error::{set_exit_code, UResult}; +use std::io::{Error, ErrorKind}; +use uucore::error::{set_exit_code, UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage}; mod maps_format_parser; @@ -98,8 +98,45 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Options independent with field selection: pmap_config.quiet = matches.get_flag(options::QUIET); - if matches.get_flag(options::SHOW_PATH) { - pmap_config.show_path = true; + pmap_config.show_path = matches.get_flag(options::SHOW_PATH); + + if let Some(range) = matches.get_one::(options::RANGE) { + match range.matches(',').count() { + 0 => { + let address = u64::from_str_radix(range, 16).map_err(|_| { + USimpleError::new(1, format!("failed to parse argument: '{}'", range)) + })?; + pmap_config.range_low = address; + pmap_config.range_high = address; + } + 1 => { + let (low, high) = range + .split_once(',') + .ok_or_else(|| Error::from(ErrorKind::InvalidData))?; + pmap_config.range_low = if low.is_empty() { + 0 + } else { + u64::from_str_radix(low, 16).map_err(|_| { + USimpleError::new(1, format!("failed to parse argument: '{}'", range)) + })? + }; + pmap_config.range_high = if high.is_empty() { + u64::MAX + } else { + u64::from_str_radix(high, 16).map_err(|_| { + USimpleError::new(1, format!("failed to parse argument: '{}'", range)) + })? + }; + } + _ => { + eprintln!("pmap: failed to parse argument: '{range}'"); + set_exit_code(1); + return Ok(()); + } + } + } else { + pmap_config.range_low = 0; + pmap_config.range_high = u64::MAX; } let pids = matches @@ -182,14 +219,16 @@ fn output_default_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Erro let mut total = 0; process_maps(pid, None, |map_line| { - println!( - "{} {:>6}K {} {}", - map_line.address.zero_pad(), - map_line.size_in_kb, - map_line.perms.mode(), - map_line.parse_mapping(pmap_config) - ); - total += map_line.size_in_kb; + if map_line.address.is_within_range(pmap_config) { + println!( + "{} {:>6}K {} {}", + map_line.address.zero_pad(), + map_line.size_in_kb, + map_line.perms.mode(), + map_line.parse_mapping(pmap_config) + ); + total += map_line.size_in_kb; + } })?; if !pmap_config.quiet { @@ -206,25 +245,32 @@ fn output_extended_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Err println!("Address Kbytes RSS Dirty Mode Mapping"); } + let mut total_size_in_kb = 0; + let mut total_rss_in_kb = 0; + let mut total_dirty_in_kb = 0; + for smap_entry in smap_table.entries { - println!( - "{} {:>7} {:>7} {:>7} {} {}", - smap_entry.map_line.address.zero_pad(), - smap_entry.map_line.size_in_kb, - smap_entry.rss_in_kb, - smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb, - smap_entry.map_line.perms.mode(), - smap_entry.map_line.parse_mapping(pmap_config) - ); + if smap_entry.map_line.address.is_within_range(pmap_config) { + println!( + "{} {:>7} {:>7} {:>7} {} {}", + smap_entry.map_line.address.zero_pad(), + smap_entry.map_line.size_in_kb, + smap_entry.rss_in_kb, + smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb, + smap_entry.map_line.perms.mode(), + smap_entry.map_line.parse_mapping(pmap_config) + ); + total_size_in_kb += smap_entry.map_line.size_in_kb; + total_rss_in_kb += smap_entry.rss_in_kb; + total_dirty_in_kb += smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb; + } } if !pmap_config.quiet { println!("---------------- ------- ------- ------- "); println!( "total kB {:>7} {:>7} {:>7}", - smap_table.info.total_size_in_kb, - smap_table.info.total_rss_in_kb, - smap_table.info.total_shared_dirty_in_kb + smap_table.info.total_private_dirty_in_kb, + total_size_in_kb, total_rss_in_kb, total_dirty_in_kb, ); } @@ -360,23 +406,25 @@ fn output_device_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error None }, |map_line| { - println!( - "{} {:>7} {} {:0>16} {} {}", - map_line.address.zero_pad(), - map_line.size_in_kb, - map_line.perms.mode(), - map_line.offset, - map_line.device.device(), - map_line.parse_mapping(pmap_config) - ); - total_mapped += map_line.size_in_kb; + if map_line.address.is_within_range(pmap_config) { + println!( + "{} {:>7} {} {:0>16} {} {}", + map_line.address.zero_pad(), + map_line.size_in_kb, + map_line.perms.mode(), + map_line.offset, + map_line.device.device(), + map_line.parse_mapping(pmap_config) + ); + total_mapped += map_line.size_in_kb; - if map_line.perms.writable && !map_line.perms.shared { - total_writeable_private += map_line.size_in_kb; - } + if map_line.perms.writable && !map_line.perms.shared { + total_writeable_private += map_line.size_in_kb; + } - if map_line.perms.shared { - total_shared += map_line.size_in_kb; + if map_line.perms.shared { + total_shared += map_line.size_in_kb; + } } }, )?; @@ -547,7 +595,9 @@ pub fn uu_app() -> Command { Arg::new(options::RANGE) .short('A') .long("range") - .num_args(1..=2) - .help("limit results to the given range"), + .num_args(1) + .help("limit results to the given range [,]"), + // This option applies only to the default, extended, or device formats, + // yet it will not raise an error in any other case. ) } diff --git a/src/uu/pmap/src/pmap_config.rs b/src/uu/pmap/src/pmap_config.rs index 073aa6c9..d399eb69 100644 --- a/src/uu/pmap/src/pmap_config.rs +++ b/src/uu/pmap/src/pmap_config.rs @@ -81,6 +81,8 @@ pub struct PmapConfig { // Misc pub quiet: bool, pub custom_format_enabled: bool, + pub range_low: u64, + pub range_high: u64, } impl PmapConfig { From 8dbe00f1360a3600a23c09180a8b20c51cacebeb Mon Sep 17 00:00:00 2001 From: estodi <83288835+estodi@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:00:07 -0700 Subject: [PATCH 2/3] pmap: added tests for range option --- src/uu/pmap/src/maps_format_parser.rs | 84 +++++++++++++++++++++++++ tests/by-util/test_pmap.rs | 89 +++++++++++++++++++++++---- 2 files changed, 161 insertions(+), 12 deletions(-) diff --git a/src/uu/pmap/src/maps_format_parser.rs b/src/uu/pmap/src/maps_format_parser.rs index 722c32e8..c1418ca0 100644 --- a/src/uu/pmap/src/maps_format_parser.rs +++ b/src/uu/pmap/src/maps_format_parser.rs @@ -348,6 +348,90 @@ mod test { assert!(parse_address("ffffffffff600000-zfffffffff601000").is_err()); } + fn limit_address_range_and_assert(address: &Address, low: u64, high: u64, expected: bool) { + let mut pmap_config = PmapConfig::default(); + (pmap_config.range_low, pmap_config.range_high) = (low, high); + assert_eq!( + address.is_within_range(&pmap_config), + expected, + "`--range 0x{low:x},0x{high:x}` expected to be {expected} for address 0x{:x},0x{:x}", + address.low, + address.high, + ); + } + + #[test] + fn test_limit_address_range() { + let low: u64 = 0x71af50000000; + let high: u64 = 0x71af50021000; + let address = Address { + start: "0x71af50000000".to_string(), + low, + high, + }; + + limit_address_range_and_assert(&address, 0x0, u64::MAX, true); + limit_address_range_and_assert(&address, 0x70000000, 0xffffffffffff, true); + + limit_address_range_and_assert(&address, 0x0, 0x0, false); + limit_address_range_and_assert(&address, 0x0, 0x70000000, false); + limit_address_range_and_assert(&address, 0x0, low - 1, false); + limit_address_range_and_assert(&address, low - 1, low - 1, false); + + limit_address_range_and_assert(&address, 0x0, low + 0, true); + limit_address_range_and_assert(&address, low - 1, low + 0, true); + limit_address_range_and_assert(&address, low + 0, low + 0, true); + + limit_address_range_and_assert(&address, low - 1, high - 1, true); + limit_address_range_and_assert(&address, low - 1, high + 0, true); + limit_address_range_and_assert(&address, low - 1, high + 1, true); + limit_address_range_and_assert(&address, low + 0, high - 1, true); + limit_address_range_and_assert(&address, low + 0, high + 0, true); + limit_address_range_and_assert(&address, low + 0, high + 1, true); + limit_address_range_and_assert(&address, low + 1, high - 1, true); + limit_address_range_and_assert(&address, low + 1, high + 0, true); + limit_address_range_and_assert(&address, low + 1, high + 1, true); + + limit_address_range_and_assert(&address, high - 1, high - 1, true); + limit_address_range_and_assert(&address, high - 1, high + 0, true); + limit_address_range_and_assert(&address, high - 1, u64::MAX, true); + + limit_address_range_and_assert(&address, high + 0, high + 0, false); + limit_address_range_and_assert(&address, high + 0, high + 1, false); + limit_address_range_and_assert(&address, high + 0, u64::MAX, false); + limit_address_range_and_assert(&address, 0xffffffffffff, u64::MAX, false); + limit_address_range_and_assert(&address, u64::MAX, u64::MAX, false); + + // Reversed range + + limit_address_range_and_assert(&address, u64::MAX, 0, false); + limit_address_range_and_assert(&address, 0xffffffffffff, 0x70000000, false); + + limit_address_range_and_assert(&address, 0x70000000, 0x0, false); + limit_address_range_and_assert(&address, low - 1, 0x0, false); + limit_address_range_and_assert(&address, low - 1, low - 1, false); + + limit_address_range_and_assert(&address, low + 0, 0x0, false); + limit_address_range_and_assert(&address, low + 0, low - 1, false); + + limit_address_range_and_assert(&address, high - 1, low - 1, false); + limit_address_range_and_assert(&address, high + 0, low - 1, false); + limit_address_range_and_assert(&address, high + 1, low - 1, false); + limit_address_range_and_assert(&address, high - 1, low + 0, true); // true + limit_address_range_and_assert(&address, high + 0, low + 0, false); + limit_address_range_and_assert(&address, high + 1, low + 0, false); + limit_address_range_and_assert(&address, high - 1, low + 1, true); // true + limit_address_range_and_assert(&address, high + 0, low + 1, false); + limit_address_range_and_assert(&address, high + 1, low + 1, false); + + limit_address_range_and_assert(&address, high + 0, high - 1, false); + limit_address_range_and_assert(&address, u64::MAX, high - 1, false); + + limit_address_range_and_assert(&address, high + 1, high + 0, false); + limit_address_range_and_assert(&address, u64::MAX, high + 0, false); + limit_address_range_and_assert(&address, u64::MAX, 0xffffffffffff, false); + } + #[test] fn test_parse_device() { assert_eq!("12:34", parse_device("12:34").unwrap().to_string()); diff --git a/tests/by-util/test_pmap.rs b/tests/by-util/test_pmap.rs index e4b9f8ce..472078c0 100644 --- a/tests/by-util/test_pmap.rs +++ b/tests/by-util/test_pmap.rs @@ -264,8 +264,8 @@ fn test_device_permission_denied() { fn test_quiet() { let pid = process::id(); - for arg in ["-q", "--quiet"] { - _test_multiple_formats(pid, arg, true, false); + for args in [&["-q"], &["--quiet"]] { + _test_multiple_formats(pid, args, true, false); } } @@ -274,8 +274,8 @@ fn test_quiet() { fn test_showpath() { let pid = process::id(); - for arg in ["-p", "--show-path"] { - _test_multiple_formats(pid, arg, false, true); + for args in [&["-p"], &["--show-path"]] { + _test_multiple_formats(pid, args, false, true); } } @@ -284,16 +284,26 @@ fn test_showpath() { fn test_quiet_showpath() { let pid = process::id(); - for arg in ["-qp", "-pq"] { - _test_multiple_formats(pid, arg, true, true); + for args in [&["-qp"], &["-pq"]] { + _test_multiple_formats(pid, args, true, true); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_range() { + let pid = process::id(); + + for args in [&["-A", ","], &["--range", ","]] { + _test_multiple_formats(pid, args, false, false); } } #[cfg(target_os = "linux")] -fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { +fn _test_multiple_formats(pid: u32, args: &[&str], quiet: bool, show_path: bool) { // default format let result = new_ucmd!() - .arg(arg) + .args(args) .arg(pid.to_string()) .succeeds() .stdout_move_str(); @@ -302,7 +312,7 @@ fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { // extended format let result = new_ucmd!() - .arg(arg) + .args(args) .arg("--extended") .arg(pid.to_string()) .succeeds() @@ -312,7 +322,7 @@ fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { // more-extended format let result = new_ucmd!() - .arg(arg) + .args(args) .arg("-X") .arg(pid.to_string()) .succeeds() @@ -322,7 +332,7 @@ fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { // most-extended format let result = new_ucmd!() - .arg(arg) + .args(args) .arg("--XX") .arg(pid.to_string()) .succeeds() @@ -332,7 +342,7 @@ fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { // device format let result = new_ucmd!() - .arg(arg) + .args(args) .arg("--device") .arg(pid.to_string()) .succeeds() @@ -341,6 +351,61 @@ fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { assert_device_format(pid, &result, quiet, show_path); } +#[test] +#[cfg(target_os = "linux")] +fn test_range_arg() { + let pid_s = process::id().to_string(); + + for opt in ["-A", "--range"] { + // option without an argument + new_ucmd!().arg(&pid_s).arg(opt).fails().code_is(1); + + // valid arguments + for arg in [ + ",", + "c00fee", + "c00fee,", + ",c00fee", + "c00,fee", + "0", + "0,", + ",0", + "0,0", + "ffffffffffffffff", + "ffffffffffffffff,", + ",ffffffffffffffff", + "ffffffffffffffff,ffffffffffffffff", + ] { + new_ucmd!().arg(&pid_s).arg(opt).arg(arg).succeeds(); + } + + // invalid arguments + for arg in [ + // white spaces + ", ", + " ,", + " , ", + "bad ", + " bad", + // multiple commas + ",,", + ",bad,", + // underscore separator + "bad_beef", + // non-numeric value + "someinvalidtext", + "someinvalidtext,", + ",someinvalidtext", + "someinvalidtext,someinvalidtext", + // too large value (> u64) + "f0000000000000000", + "f0000000000000000,f0000000000000000", + ] { + new_ucmd!().arg(&pid_s).arg(opt).arg(arg).fails().code_is(1); + } + } +} + #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); From 6837565b1cb7046aa835f1da4cbb8b9f69cc7e40 Mon Sep 17 00:00:00 2001 From: estodi <83288835+estodi@users.noreply.github.com> Date: Mon, 7 Jul 2025 20:21:00 -0700 Subject: [PATCH 3/3] fixed lint errors --- src/uu/pmap/src/pmap.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/uu/pmap/src/pmap.rs b/src/uu/pmap/src/pmap.rs index 180eb337..05a046e6 100644 --- a/src/uu/pmap/src/pmap.rs +++ b/src/uu/pmap/src/pmap.rs @@ -104,7 +104,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { match range.matches(',').count() { 0 => { let address = u64::from_str_radix(range, 16).map_err(|_| { - USimpleError::new(1, format!("failed to parse argument: '{}'", range)) + USimpleError::new(1, format!("failed to parse argument: '{range}'")) })?; pmap_config.range_low = address; pmap_config.range_high = address; @@ -117,14 +117,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { 0 } else { u64::from_str_radix(low, 16).map_err(|_| { - USimpleError::new(1, format!("failed to parse argument: '{}'", range)) + USimpleError::new(1, format!("failed to parse argument: '{range}'")) })? }; pmap_config.range_high = if high.is_empty() { u64::MAX } else { u64::from_str_radix(high, 16).map_err(|_| { - USimpleError::new(1, format!("failed to parse argument: '{}'", range)) + USimpleError::new(1, format!("failed to parse argument: '{range}'")) })? }; } @@ -269,8 +269,7 @@ fn output_extended_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Err if !pmap_config.quiet { println!("---------------- ------- ------- ------- "); println!( - "total kB {:>7} {:>7} {:>7}", - total_size_in_kb, total_rss_in_kb, total_dirty_in_kb, + "total kB {total_size_in_kb:>7} {total_rss_in_kb:>7} {total_dirty_in_kb:>7}" ); }