diff --git a/src/uu/pmap/src/pmap.rs b/src/uu/pmap/src/pmap.rs index ef1ed475..34073b46 100644 --- a/src/uu/pmap/src/pmap.rs +++ b/src/uu/pmap/src/pmap.rs @@ -48,6 +48,7 @@ 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; } @@ -142,7 +143,9 @@ fn output_default_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Erro total += map_line.size_in_kb; })?; - println!(" total {total:>16}K"); + if !pmap_config.quiet { + println!(" total {total:>16}K"); + } Ok(()) } @@ -150,7 +153,9 @@ fn output_default_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Erro fn output_extended_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error> { let smap_table = get_smap_table(pid)?; - println!("Address Kbytes RSS Dirty Mode Mapping"); + if !pmap_config.quiet { + println!("Address Kbytes RSS Dirty Mode Mapping"); + } for smap_entry in smap_table.entries { println!( @@ -164,13 +169,16 @@ fn output_extended_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Err ); } - 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, - ); + 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, + ); + } + Ok(()) } @@ -185,15 +193,20 @@ fn output_custom_format(pid: &str, pmap_config: &mut PmapConfig) -> Result<(), E } // Header - { + if !pmap_config.quiet { let mut line = format!( "{:>width$} ", pmap_field_name::ADDRESS, width = smap_table.info.get_width(pmap_field_name::ADDRESS) ); + pmap_config.quiet = true; for field_name in pmap_config.get_field_list() { if pmap_config.is_enabled(field_name) { + // If there is any field that needs footer, we can't suppress the footer + if pmap_config.needs_footer(field_name) { + pmap_config.quiet = false; + } line += &format!( "{:>width$} ", field_name, @@ -230,7 +243,7 @@ fn output_custom_format(pid: &str, pmap_config: &mut PmapConfig) -> Result<(), E } // Footer - { + if !pmap_config.quiet { // Separator let mut line = format!( "{:>width$} ", @@ -292,7 +305,11 @@ fn output_device_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error process_maps( pid, - Some("Address Kbytes Mode Offset Device Mapping"), + if !pmap_config.quiet { + Some("Address Kbytes Mode Offset Device Mapping") + } else { + None + }, |map_line| { println!( "{} {:>7} {} {} {} {}", @@ -315,9 +332,11 @@ fn output_device_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error }, )?; - println!( - "mapped: {total_mapped}K writeable/private: {total_writeable_private}K shared: {total_shared}K" - ); + if !pmap_config.quiet { + println!( + "mapped: {total_mapped}K writeable/private: {total_writeable_private}K shared: {total_shared}K" + ); + } Ok(()) } diff --git a/src/uu/pmap/src/pmap_config.rs b/src/uu/pmap/src/pmap_config.rs index 68579a10..5f544c89 100644 --- a/src/uu/pmap/src/pmap_config.rs +++ b/src/uu/pmap/src/pmap_config.rs @@ -75,6 +75,7 @@ pub struct PmapConfig { // [Mapping] category pub show_path: bool, // Misc + pub quiet: bool, pub custom_format_enabled: bool, } diff --git a/tests/by-util/test_pmap.rs b/tests/by-util/test_pmap.rs index 9a406560..fab26be9 100644 --- a/tests/by-util/test_pmap.rs +++ b/tests/by-util/test_pmap.rs @@ -30,7 +30,7 @@ fn test_existing_pid() { .succeeds() .stdout_move_str(); - assert_format(pid, &result, false); + assert_format(pid, &result, false, false); } #[test] @@ -50,8 +50,8 @@ fn test_multiple_existing_pids() { let pos_second_pid = result.iter().rposition(|line| re.is_match(line)).unwrap(); let (left, right) = result.split_at(pos_second_pid); - assert_format(pid, &left.join("\n"), false); - assert_format(pid, &right.join("\n"), false); + assert_format(pid, &left.join("\n"), false, false); + assert_format(pid, &right.join("\n"), false, false); } #[test] @@ -65,7 +65,7 @@ fn test_non_existing_and_existing_pid() { .fails(); let result = result.code_is(42).no_stderr().stdout_str(); - assert_format(pid, result, false); + assert_format(pid, result, false, false); } #[test] @@ -103,7 +103,7 @@ fn test_extended() { .succeeds() .stdout_move_str(); - assert_extended_format(pid, &result, false); + assert_extended_format(pid, &result, false, false); } } @@ -119,7 +119,7 @@ fn test_more_extended() { .succeeds() .stdout_move_str(); - assert_more_extended_format(pid, &result, false); + assert_more_extended_format(pid, &result, false, false); } #[test] @@ -134,7 +134,7 @@ fn test_most_extended() { .succeeds() .stdout_move_str(); - assert_most_extended_format(pid, &result, false); + assert_most_extended_format(pid, &result, false, false); } #[test] @@ -166,7 +166,7 @@ fn test_device() { .succeeds() .stdout_move_str(); - assert_device_format(pid, &result, false); + assert_device_format(pid, &result, false, false); } } @@ -187,61 +187,86 @@ fn test_device_permission_denied() { } } +#[test] +#[cfg(target_os = "linux")] +fn test_quiet() { + let pid = process::id(); + + for arg in ["-q", "--quiet"] { + _test_multiple_formats(pid, arg, true, false); + } +} + #[test] #[cfg(target_os = "linux")] fn test_showpath() { let pid = process::id(); for arg in ["-p", "--show-path"] { - // default format - let result = new_ucmd!() - .arg(arg) - .arg(pid.to_string()) - .succeeds() - .stdout_move_str(); + _test_multiple_formats(pid, arg, false, true); + } +} - assert_format(pid, &result, true); +#[test] +#[cfg(target_os = "linux")] +fn test_quiet_showpath() { + let pid = process::id(); - // extended format - let result = new_ucmd!() - .arg(arg) - .arg("--extended") - .arg(pid.to_string()) - .succeeds() - .stdout_move_str(); + for arg in ["-qp", "-pq"] { + _test_multiple_formats(pid, arg, true, true); + } +} - assert_extended_format(pid, &result, true); +#[cfg(target_os = "linux")] +fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { + // default format + let result = new_ucmd!() + .arg(arg) + .arg(pid.to_string()) + .succeeds() + .stdout_move_str(); - // more-extended format - let result = new_ucmd!() - .arg(arg) - .arg("-X") - .arg(pid.to_string()) - .succeeds() - .stdout_move_str(); + assert_format(pid, &result, quiet, show_path); - assert_more_extended_format(pid, &result, true); + // extended format + let result = new_ucmd!() + .arg(arg) + .arg("--extended") + .arg(pid.to_string()) + .succeeds() + .stdout_move_str(); - // most-extended format - let result = new_ucmd!() - .arg(arg) - .arg("--XX") - .arg(pid.to_string()) - .succeeds() - .stdout_move_str(); + assert_extended_format(pid, &result, quiet, show_path); - assert_most_extended_format(pid, &result, true); + // more-extended format + let result = new_ucmd!() + .arg(arg) + .arg("-X") + .arg(pid.to_string()) + .succeeds() + .stdout_move_str(); - // device format - let result = new_ucmd!() - .arg(arg) - .arg("--device") - .arg(pid.to_string()) - .succeeds() - .stdout_move_str(); + assert_more_extended_format(pid, &result, quiet, show_path); - assert_device_format(pid, &result, true); - } + // most-extended format + let result = new_ucmd!() + .arg(arg) + .arg("--XX") + .arg(pid.to_string()) + .succeeds() + .stdout_move_str(); + + assert_most_extended_format(pid, &result, quiet, show_path); + + // device format + let result = new_ucmd!() + .arg(arg) + .arg("--device") + .arg(pid.to_string()) + .succeeds() + .stdout_move_str(); + + assert_device_format(pid, &result, quiet, show_path); } #[test] @@ -268,13 +293,11 @@ fn assert_cmdline_only(pid: &str, s: &str) { // ... // total 1040320K #[cfg(target_os = "linux")] -fn assert_format(pid: u32, s: &str, show_path: bool) { +fn assert_format(pid: u32, s: &str, quiet: bool, show_path: bool) { let (first_line, rest) = s.split_once('\n').unwrap(); let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap(); assert!(re.is_match(first_line)); - let rest = rest.trim_end(); - let (memory_map, last_line) = rest.rsplit_once('\n').unwrap(); let base_pattern = r"(?m)^[0-9a-f]{16} +[1-9][0-9]*K (-|r)(-|w)(-|x)(-|s)-"; let mapping_pattern = if show_path { r" ( \[ (anon|stack) \]|/[/a-zA-Z0-9._-]+)$" @@ -282,10 +305,17 @@ fn assert_format(pid: u32, s: &str, show_path: bool) { r" ( \[ (anon|stack) \]|[a-zA-Z0-9._-]+)$" }; let re = Regex::new(&format!("{base_pattern}{mapping_pattern}")).unwrap(); - assert!(re.is_match(memory_map)); - let re = Regex::new("^ total +[1-9][0-9]*K$").unwrap(); - assert!(re.is_match(last_line)); + let rest = rest.trim_end(); + if quiet { + assert!(re.is_match(rest)); + } else { + let (memory_map, last_line) = rest.rsplit_once('\n').unwrap(); + assert!(re.is_match(memory_map)); + + let re = Regex::new("^ total +[1-9][0-9]*K$").unwrap(); + assert!(re.is_match(last_line)); + } } // Ensure `s` has the following extended format (--extended): @@ -299,15 +329,17 @@ fn assert_format(pid: u32, s: &str, show_path: bool) { // ---------------- ------- ------- ------- (one intentional trailing space) // total kB 144 7 14 #[cfg(target_os = "linux")] -fn assert_extended_format(pid: u32, s: &str, show_path: bool) { +fn assert_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) { let lines: Vec<_> = s.lines().collect(); let line_count = lines.len(); let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap(); assert!(re.is_match(lines[0]), "failing line: '{}'", lines[0]); - let expected_header = "Address Kbytes RSS Dirty Mode Mapping"; - assert_eq!(expected_header, lines[1], "failing line: '{}'", lines[1]); + if !quiet { + let expected_header = "Address Kbytes RSS Dirty Mode Mapping"; + assert_eq!(expected_header, lines[1], "failing line: '{}'", lines[1]); + } let base_pattern = r"^[0-9a-f]{16} +[1-9][0-9]* +\d+ +\d+ (-|r)(-|w)(-|x)(-|s)-"; let mapping_pattern = if show_path { @@ -317,24 +349,30 @@ fn assert_extended_format(pid: u32, s: &str, show_path: bool) { }; let re = Regex::new(&format!("{base_pattern}{mapping_pattern}")).unwrap(); - for line in lines.iter().take(line_count - 2).skip(2) { - assert!(re.is_match(line), "failing line: '{line}'"); + if quiet { + for line in lines.iter().skip(1) { + assert!(re.is_match(line), "failing line: '{line}'"); + } + } else { + for line in lines.iter().take(line_count - 2).skip(2) { + assert!(re.is_match(line), "failing line: '{line}'"); + } + + let expected_separator = "---------------- ------- ------- ------- "; + assert_eq!( + expected_separator, + lines[line_count - 2], + "failing line: '{}'", + lines[line_count - 2] + ); + + let re = Regex::new(r"^total kB +[1-9][0-9]* +\d+ +\d+$").unwrap(); + assert!( + re.is_match(lines[line_count - 1]), + "failing line: '{}'", + lines[line_count - 1] + ); } - - let expected_separator = "---------------- ------- ------- ------- "; - assert_eq!( - expected_separator, - lines[line_count - 2], - "failing line: '{}'", - lines[line_count - 2] - ); - - let re = Regex::new(r"^total kB +[1-9][0-9]* +\d+ +\d+$").unwrap(); - assert!( - re.is_match(lines[line_count - 1]), - "failing line: '{}'", - lines[line_count - 1] - ); } // Ensure `s` has the following more extended format (-X): @@ -348,15 +386,17 @@ fn assert_extended_format(pid: u32, s: &str, show_path: bool) { // ==== ==== ==== ========= ========== ========= ======== ============== ============= ============== =============== ==== ======= ====== =========== (one intentional trailing space) // 4164 3448 2826 552 3448 552 0 0 0 0 0 0 0 0 0 KB (one intentional trailing space) #[cfg(target_os = "linux")] -fn assert_more_extended_format(pid: u32, s: &str, show_path: bool) { +fn assert_more_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) { let lines: Vec<_> = s.lines().collect(); let line_count = lines.len(); let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap(); assert!(re.is_match(lines[0])); - let re = Regex::new(r"^ Address Perm Offset Device Inode +Size +Rss +Pss +Pss_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +Mapping$").unwrap(); - assert!(re.is_match(lines[1]), "failing line: '{}'", lines[1]); + if !quiet { + let re = Regex::new(r"^ Address Perm Offset Device Inode +Size +Rss +Pss +Pss_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +Mapping$").unwrap(); + assert!(re.is_match(lines[1]), "failing line: '{}'", lines[1]); + } let base_pattern = r"^[0-9a-f]{16} (-|r)(-|w)(-|x)(p|s) [0-9a-f]{16} [0-9a-f]{3}:[0-9a-f]{5} +\d+ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)?"; let mapping_pattern = if show_path { @@ -366,23 +406,29 @@ fn assert_more_extended_format(pid: u32, s: &str, show_path: bool) { }; let re = Regex::new(&format!("{base_pattern}{mapping_pattern}")).unwrap(); - for line in lines.iter().take(line_count - 2).skip(2) { - assert!(re.is_match(line), "failing line: '{line}'"); + if quiet { + for line in lines.iter().skip(1) { + assert!(re.is_match(line), "failing line: '{line}'"); + } + } else { + for line in lines.iter().take(line_count - 2).skip(2) { + assert!(re.is_match(line), "failing line: '{line}'"); + } + + let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); + assert!( + re.is_match(lines[line_count - 2]), + "failing line: '{}'", + lines[line_count - 2] + ); + + let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); + assert!( + re.is_match(lines[line_count - 1]), + "failing line: '{}'", + lines[line_count - 1] + ); } - - let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); - assert!( - re.is_match(lines[line_count - 2]), - "failing line: '{}'", - lines[line_count - 2] - ); - - let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); - assert!( - re.is_match(lines[line_count - 1]), - "failing line: '{}'", - lines[line_count - 1] - ); } // Ensure `s` has the following most extended format (--XX): @@ -396,15 +442,17 @@ fn assert_more_extended_format(pid: u32, s: &str, show_path: bool) { // ==== ============== =========== ==== ==== ========= ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== =========== (one intentional trailing space) // 4164 92 92 3448 2880 552 1132 0 1764 552 3448 552 0 0 0 0 0 0 0 0 0 0 KB (one intentional trailing space) #[cfg(target_os = "linux")] -fn assert_most_extended_format(pid: u32, s: &str, show_path: bool) { +fn assert_most_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) { let lines: Vec<_> = s.lines().collect(); let line_count = lines.len(); let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap(); assert!(re.is_match(lines[0])); - let re = Regex::new(r"^ Address Perm Offset Device Inode +Size +KernelPageSize +MMUPageSize +Rss +Pss +Pss_Dirty +Shared_Clean +Shared_Dirty +Private_Clean +Private_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +AnonHugePages +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +VmFlags +Mapping$").unwrap(); - assert!(re.is_match(lines[1]), "failing line: '{}'", lines[1]); + if !quiet { + let re = Regex::new(r"^ Address Perm Offset Device Inode +Size +KernelPageSize +MMUPageSize +Rss +Pss +Pss_Dirty +Shared_Clean +Shared_Dirty +Private_Clean +Private_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +AnonHugePages +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +VmFlags +Mapping$").unwrap(); + assert!(re.is_match(lines[1]), "failing line: '{}'", lines[1]); + } let base_pattern = r"^[0-9a-f]{16} (-|r)(-|w)(-|x)(p|s) [0-9a-f]{16} [0-9a-f]{3}:[0-9a-f]{5} +\d+ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +([a-z][a-z] )*"; let mapping_pattern = if show_path { @@ -414,23 +462,29 @@ fn assert_most_extended_format(pid: u32, s: &str, show_path: bool) { }; let re = Regex::new(&format!("{base_pattern}{mapping_pattern}")).unwrap(); - for line in lines.iter().take(line_count - 2).skip(2) { - assert!(re.is_match(line), "failing line: '{line}'"); + if quiet { + for line in lines.iter().skip(1) { + assert!(re.is_match(line), "failing line: '{line}'"); + } + } else { + for line in lines.iter().take(line_count - 2).skip(2) { + assert!(re.is_match(line), "failing line: '{line}'"); + } + + let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); + assert!( + re.is_match(lines[line_count - 2]), + "failing line: '{}'", + lines[line_count - 2] + ); + + let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); + assert!( + re.is_match(lines[line_count - 1]), + "failing line: '{}'", + lines[line_count - 1] + ); } - - let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); - assert!( - re.is_match(lines[line_count - 2]), - "failing line: '{}'", - lines[line_count - 2] - ); - - let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); - assert!( - re.is_match(lines[line_count - 1]), - "failing line: '{}'", - lines[line_count - 1] - ); } // Ensure `s` has the following device format (--device): @@ -443,15 +497,17 @@ fn assert_most_extended_format(pid: u32, s: &str, show_path: bool) { // ... // mapped: 3060K writeable/private: 348K shared: 0K #[cfg(target_os = "linux")] -fn assert_device_format(pid: u32, s: &str, show_path: bool) { +fn assert_device_format(pid: u32, s: &str, quiet: bool, show_path: bool) { let lines: Vec<_> = s.lines().collect(); let line_count = lines.len(); let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap(); assert!(re.is_match(lines[0])); - let expected_header = "Address Kbytes Mode Offset Device Mapping"; - assert_eq!(expected_header, lines[1]); + if !quiet { + let expected_header = "Address Kbytes Mode Offset Device Mapping"; + assert_eq!(expected_header, lines[1]); + } let base_pattern = r"^[0-9a-f]{16} +[1-9][0-9]* (-|r)(-|w)(-|x)(-|s)- [0-9a-f]{16} [0-9a-f]{3}:[0-9a-f]{5}"; @@ -462,14 +518,21 @@ fn assert_device_format(pid: u32, s: &str, show_path: bool) { }; let re = Regex::new(&format!("{base_pattern}{mapping_pattern}")).unwrap(); - for line in lines.iter().take(line_count - 1).skip(2) { - assert!(re.is_match(line), "failing line: {line}"); + if quiet { + for line in lines.iter().skip(1) { + assert!(re.is_match(line), "failing line: {line}"); + } + } else { + for line in lines.iter().take(line_count - 1).skip(2) { + assert!(re.is_match(line), "failing line: {line}"); + } + + let re = + Regex::new(r"^mapped: \d+K\s{4}writeable/private: \d+K\s{4}shared: \d+K$").unwrap(); + assert!( + re.is_match(lines[line_count - 1]), + "failing line: {}", + lines[line_count - 1] + ); } - - let re = Regex::new(r"^mapped: \d+K\s{4}writeable/private: \d+K\s{4}shared: \d+K$").unwrap(); - assert!( - re.is_match(lines[line_count - 1]), - "failing line: {}", - lines[line_count - 1] - ); }