diff --git a/src/uu/pmap/src/maps_format_parser.rs b/src/uu/pmap/src/maps_format_parser.rs index 273f2086..6528b9d7 100644 --- a/src/uu/pmap/src/maps_format_parser.rs +++ b/src/uu/pmap/src/maps_format_parser.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use crate::pmap_config::PmapConfig; use std::fmt; use std::io::{Error, ErrorKind}; @@ -40,13 +41,25 @@ impl From<&str> for Perms { } } -// Please note: While `Perms` has four boolean fields, it's string representation has five -// characters because pmap's default and device formats use five characters for the perms, -// with the last character always being '-'. impl fmt::Display for Perms { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, + "{}{}{}{}", + if self.readable { 'r' } else { '-' }, + if self.writable { 'w' } else { '-' }, + if self.executable { 'x' } else { '-' }, + if self.shared { 's' } else { 'p' }, + ) + } +} + +// Please note: While `Perms` has four boolean fields, its `Mode` representation +// used in pmap's default and device formats has five characters for the perms, +// with the last character always being '-'. +impl Perms { + pub fn mode(&self) -> String { + format!( "{}{}{}{}-", if self.readable { 'r' } else { '-' }, if self.writable { 'w' } else { '-' }, @@ -90,8 +103,7 @@ pub fn parse_map_line(line: &str) -> Result { let inode = inode .parse::() .map_err(|_| Error::from(ErrorKind::InvalidData))?; - let mapping = mapping.trim_ascii_start(); - let mapping = parse_mapping(mapping); + let mapping = mapping.trim_ascii_start().to_string(); Ok(MapLine { address, @@ -126,18 +138,29 @@ fn parse_device(device: &str) -> Result { Ok(format!("{major:0>3}:{minor:0>5}")) } -fn parse_mapping(mapping: &str) -> String { - if mapping == "[stack]" { - return " [ stack ]".into(); - } - - if mapping.is_empty() || mapping.starts_with('[') || mapping.starts_with("anon") { - return " [ anon ]".into(); - } +impl MapLine { + pub fn parse_mapping(&self, pmap_config: &PmapConfig) -> String { + if pmap_config.custom_format_enabled { + if self.mapping.starts_with('[') { + return self.mapping.clone(); + } + } else { + if self.mapping == "[stack]" { + return " [ stack ]".into(); + } + + if self.mapping.is_empty() + || self.mapping.starts_with('[') + || self.mapping.starts_with("anon") + { + return " [ anon ]".into(); + } + } - match mapping.rsplit_once('/') { - Some((_, name)) => name.into(), - None => mapping.into(), + match self.mapping.rsplit_once('/') { + Some((_, name)) => name.into(), + None => self.mapping.clone(), + } } } @@ -167,40 +190,47 @@ mod test { #[test] fn test_perms_to_string() { - assert_eq!("-----", Perms::from("---p").to_string()); - assert_eq!("---s-", Perms::from("---s").to_string()); - assert_eq!("rwx--", Perms::from("rwxp").to_string()); + assert_eq!("---p", Perms::from("---p").to_string()); + assert_eq!("---s", Perms::from("---s").to_string()); + assert_eq!("rwxp", Perms::from("rwxp").to_string()); + } + + #[test] + fn test_perms_mode() { + assert_eq!("-----", Perms::from("---p").mode()); + assert_eq!("---s-", Perms::from("---s").mode()); + assert_eq!("rwx--", Perms::from("rwxp").mode()); } #[test] fn test_parse_map_line() { let data = [ ( - create_map_line("000062442eb9e000", 16, Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "konsole"), + create_map_line("000062442eb9e000", 16, Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "/usr/bin/konsole"), "62442eb9e000-62442eba2000 r--p 00000000 08:08 10813151 /usr/bin/konsole" ), ( - create_map_line("000071af50000000", 132, Perms::from("rw-p"), "0000000000000000", "000:00000", 0, " [ anon ]"), + create_map_line("000071af50000000", 132, Perms::from("rw-p"), "0000000000000000", "000:00000", 0, ""), "71af50000000-71af50021000 rw-p 00000000 00:00 0 " ), ( - create_map_line("00007ffc3f8df000", 132, Perms::from("rw-p"), "0000000000000000", "000:00000", 0, " [ stack ]"), + create_map_line("00007ffc3f8df000", 132, Perms::from("rw-p"), "0000000000000000", "000:00000", 0, "[stack]"), "7ffc3f8df000-7ffc3f900000 rw-p 00000000 00:00 0 [stack]" ), ( - create_map_line("000071af8c9e6000", 16, Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, " [ anon ]"), + create_map_line("000071af8c9e6000", 16, Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem"), "71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem" ), ( - create_map_line("000071af6cf0c000", 3560, Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "memfd:wayland-shm (deleted)"), + create_map_line("000071af6cf0c000", 3560, Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)"), "71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481 /memfd:wayland-shm (deleted)" ), ( - create_map_line("ffffffffff600000", 4, Perms::from("--xp"), "0000000000000000", "000:00000", 0, " [ anon ]"), + create_map_line("ffffffffff600000", 4, Perms::from("--xp"), "0000000000000000", "000:00000", 0, "[vsyscall]"), "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]" ), ( - create_map_line("00005e8187da8000", 24, Perms::from("r--p"), "0000000000000000", "008:00008", 9524160, "hello world"), + create_map_line("00005e8187da8000", 24, Perms::from("r--p"), "0000000000000000", "008:00008", 9524160, "/usr/bin/hello world"), "5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160 /usr/bin/hello world" ), ]; @@ -250,14 +280,37 @@ mod test { #[test] fn test_parse_mapping() { - assert_eq!(" [ anon ]", parse_mapping("")); - assert_eq!(" [ anon ]", parse_mapping("[vvar]")); - assert_eq!(" [ anon ]", parse_mapping("[vdso]")); - assert_eq!(" [ anon ]", parse_mapping("anon_inode:i915.gem")); - assert_eq!(" [ stack ]", parse_mapping("[stack]")); - assert_eq!( - "ld-linux-x86-64.so.2", - parse_mapping("/usr/lib/ld-linux-x86-64.so.2") - ); + let mut mapline = MapLine::default(); + let mut pmap_config = PmapConfig::default(); + + mapline.mapping = "".to_string(); + pmap_config.custom_format_enabled = false; + assert_eq!(" [ anon ]", mapline.parse_mapping(&pmap_config)); + pmap_config.custom_format_enabled = true; + assert_eq!("", mapline.parse_mapping(&pmap_config)); + + mapline.mapping = "[vvar]".to_string(); + pmap_config.custom_format_enabled = false; + assert_eq!(" [ anon ]", mapline.parse_mapping(&pmap_config)); + pmap_config.custom_format_enabled = true; + assert_eq!("[vvar]", mapline.parse_mapping(&pmap_config)); + + mapline.mapping = "anon_inode:i915.gem".to_string(); + pmap_config.custom_format_enabled = false; + assert_eq!(" [ anon ]", mapline.parse_mapping(&pmap_config)); + pmap_config.custom_format_enabled = true; + assert_eq!("anon_inode:i915.gem", mapline.parse_mapping(&pmap_config)); + + mapline.mapping = "[stack]".to_string(); + pmap_config.custom_format_enabled = false; + assert_eq!(" [ stack ]", mapline.parse_mapping(&pmap_config)); + pmap_config.custom_format_enabled = true; + assert_eq!("[stack]", mapline.parse_mapping(&pmap_config)); + + mapline.mapping = "/usr/lib/ld-linux-x86-64.so.2".to_string(); + pmap_config.custom_format_enabled = false; + assert_eq!("ld-linux-x86-64.so.2", mapline.parse_mapping(&pmap_config)); + pmap_config.custom_format_enabled = true; + assert_eq!("ld-linux-x86-64.so.2", mapline.parse_mapping(&pmap_config)); } } diff --git a/src/uu/pmap/src/pmap.rs b/src/uu/pmap/src/pmap.rs index 9617304e..eca4a425 100644 --- a/src/uu/pmap/src/pmap.rs +++ b/src/uu/pmap/src/pmap.rs @@ -5,7 +5,8 @@ use clap::{crate_version, Arg, ArgAction, Command}; use maps_format_parser::{parse_map_line, MapLine}; -use smaps_format_parser::{parse_smap_entries, SmapEntry}; +use pmap_config::{pmap_field_name, PmapConfig}; +use smaps_format_parser::{parse_smaps, SmapTable}; use std::env; use std::fs; use std::io::Error; @@ -13,6 +14,7 @@ use uucore::error::{set_exit_code, UResult}; use uucore::{format_usage, help_about, help_usage}; mod maps_format_parser; +mod pmap_config; mod smaps_format_parser; const ABOUT: &str = help_about!("pmap.md"); @@ -36,6 +38,15 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; + + let mut pmap_config = PmapConfig::default(); + + if matches.get_flag(options::MORE_EXTENDED) { + pmap_config.set_more_extended(); + } else if matches.get_flag(options::MOST_EXTENDED) { + pmap_config.set_most_extended(); + } + let pids = matches .get_many::(options::PID) .expect("PID required"); @@ -52,13 +63,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } if matches.get_flag(options::EXTENDED) { - output_extended_format(pid) + output_extended_format(pid, &pmap_config) .map_err(|_| set_exit_code(1)) .ok(); } else if matches.get_flag(options::DEVICE) { - output_device_format(pid).map_err(|_| set_exit_code(1)).ok(); + output_device_format(pid, &pmap_config) + .map_err(|_| set_exit_code(1)) + .ok(); + } else if pmap_config.custom_format_enabled { + output_custom_format(pid, &mut pmap_config) + .map_err(|_| set_exit_code(1)) + .ok(); } else { - output_default_format(pid) + output_default_format(pid, &pmap_config) .map_err(|_| set_exit_code(1)) .ok(); } @@ -100,32 +117,22 @@ where Ok(()) } -fn process_smaps(pid: &str, header: Option<&str>, mut process_entry: F) -> Result<(), Error> -where - F: FnMut(&SmapEntry), -{ +fn get_smap_table(pid: &str) -> Result { let path = format!("/proc/{pid}/smaps"); let contents = fs::read_to_string(path)?; - let smap_entries = parse_smap_entries(&contents)?; - - if let Some(header) = header { - println!("{header}"); - } - - for entry in smap_entries { - process_entry(&entry); - } - - Ok(()) + parse_smaps(&contents) } -fn output_default_format(pid: &str) -> Result<(), Error> { +fn output_default_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error> { let mut total = 0; process_maps(pid, None, |map_line| { println!( "{} {:>6}K {} {}", - map_line.address, map_line.size_in_kb, map_line.perms, map_line.mapping + map_line.address, + map_line.size_in_kb, + map_line.perms.mode(), + map_line.parse_mapping(pmap_config) ); total += map_line.size_in_kb; })?; @@ -135,37 +142,145 @@ fn output_default_format(pid: &str) -> Result<(), Error> { Ok(()) } -fn output_extended_format(pid: &str) -> Result<(), Error> { - let mut total_mapped = 0; - let mut total_rss = 0; - let mut total_dirty = 0; +fn output_extended_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error> { + let smap_table = get_smap_table(pid)?; - process_smaps( - pid, - Some("Address Kbytes RSS Dirty Mode Mapping"), - |smap_entry| { - println!( - "{} {:>7} {:>7} {:>7} {} {}", - smap_entry.map_line.address, - 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, - smap_entry.map_line.mapping - ); - total_mapped += smap_entry.map_line.size_in_kb; - total_rss += smap_entry.rss_in_kb; - total_dirty += smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb; - }, - )?; + println!("Address Kbytes RSS Dirty Mode Mapping"); + + for smap_entry in smap_table.entries { + println!( + "{} {:>7} {:>7} {:>7} {} {}", + smap_entry.map_line.address, + 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) + ); + } println!("---------------- ------- ------- ------- "); - println!("total kB {total_mapped:>7} {total_rss:>7} {total_dirty:>7}"); + 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(()) +} + +fn output_custom_format(pid: &str, pmap_config: &mut PmapConfig) -> Result<(), Error> { + let smap_table = get_smap_table(pid)?; + + if !smap_table.info.has_ksm { + pmap_config.disable_field(pmap_field_name::KSM); + } + if !smap_table.info.has_protection_key { + pmap_config.disable_field(pmap_field_name::PROTECTION_KEY); + } + + // Header + { + let mut line = format!( + "{:>width$} ", + pmap_field_name::ADDRESS, + width = smap_table.info.get_width(pmap_field_name::ADDRESS) + ); + + for field_name in pmap_config.get_field_list() { + if pmap_config.is_enabled(field_name) { + line += &format!( + "{:>width$} ", + field_name, + width = smap_table.info.get_width(field_name) + ); + } + } + if pmap_config.is_enabled(pmap_field_name::MAPPING) { + line += pmap_field_name::MAPPING; + } + println!("{line}"); + } + + // Main + for smap_entry in smap_table.entries { + let mut line = format!( + "{:>width$} ", + smap_entry.get_field(pmap_field_name::ADDRESS), + width = smap_table.info.get_width(pmap_field_name::ADDRESS) + ); + for field_name in pmap_config.get_field_list() { + if pmap_config.is_enabled(field_name) { + line += &format!( + "{:>width$} ", + smap_entry.get_field(field_name), + width = smap_table.info.get_width(field_name) + ); + } + } + if pmap_config.is_enabled(pmap_field_name::MAPPING) { + line += &smap_entry.map_line.parse_mapping(pmap_config); + } + println!("{line}"); + } + + // Footer + { + // Separator + let mut line = format!( + "{:>width$} ", + "", + width = smap_table.info.get_width(pmap_field_name::ADDRESS) + ); + for field_name in pmap_config.get_field_list() { + if pmap_config.is_enabled(field_name) && field_name != pmap_field_name::VMFLAGS { + if pmap_config.needs_footer(field_name) { + line += &format!( + "{:=>width$} ", + "", + width = smap_table.info.get_width(field_name) + ); + } else { + line += &format!( + "{:>width$} ", + "", + width = smap_table.info.get_width(field_name) + ); + } + } + } + println!("{line}"); + + // Total values + let mut line = format!( + "{:>width$} ", + "", + width = smap_table.info.get_width(pmap_field_name::ADDRESS) + ); + for field_name in pmap_config.get_field_list() { + if pmap_config.is_enabled(field_name) && field_name != pmap_field_name::VMFLAGS { + if pmap_config.needs_footer(field_name) { + line += &format!( + "{:>width$} ", + smap_table.info.get_total(field_name), + width = smap_table.info.get_width(field_name) + ); + } else { + line += &format!( + "{:>width$} ", + "", + width = smap_table.info.get_width(field_name) + ); + } + } + } + println!("{line}KB "); + } Ok(()) } -fn output_device_format(pid: &str) -> Result<(), Error> { +fn output_device_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error> { let mut total_mapped = 0; let mut total_writeable_private = 0; let mut total_shared = 0; @@ -178,10 +293,10 @@ fn output_device_format(pid: &str) -> Result<(), Error> { "{} {:>7} {} {} {} {}", map_line.address, map_line.size_in_kb, - map_line.perms, + map_line.perms.mode(), map_line.offset, map_line.device, - map_line.mapping + map_line.parse_mapping(pmap_config) ); total_mapped += map_line.size_in_kb; @@ -220,20 +335,47 @@ pub fn uu_app() -> Command { .short('x') .long("extended") .help("show details") - .action(ArgAction::SetTrue), - ) + .action(ArgAction::SetTrue) + .conflicts_with_all([ + "read-rc", + "read-rc-from", + "device", + "create-rc", + "create-rc-to", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::MORE_EXTENDED) .short('X') .help("show even more details") - .action(ArgAction::SetTrue), - ) + .action(ArgAction::SetTrue) + .conflicts_with_all([ + "read-rc", + "read-rc-from", + "device", + "create-rc", + "create-rc-to", + "extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::MOST_EXTENDED) .long("XX") .help("show everything the kernel provides") - .action(ArgAction::SetTrue), - ) + .action(ArgAction::SetTrue) + .conflicts_with_all([ + "read-rc", + "read-rc-from", + "device", + "create-rc", + "create-rc-to", + "extended", + "more-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::READ_RC) .short('c') diff --git a/src/uu/pmap/src/pmap_config.rs b/src/uu/pmap/src/pmap_config.rs new file mode 100644 index 00000000..da6d6713 --- /dev/null +++ b/src/uu/pmap/src/pmap_config.rs @@ -0,0 +1,244 @@ +// This file is part of the uutils procps package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +pub mod pmap_field_name { + pub const ADDRESS: &str = "Address"; + pub const PERM: &str = "Perm"; + pub const OFFSET: &str = "Offset"; + pub const DEVICE: &str = "Device"; + pub const INODE: &str = "Inode"; + pub const SIZE: &str = "Size"; + pub const KERNEL_PAGE_SIZE: &str = "KernelPageSize"; + pub const MMU_PAGE_SIZE: &str = "MMUPageSize"; + pub const RSS: &str = "Rss"; + pub const PSS: &str = "Pss"; + pub const PSS_DIRTY: &str = "Pss_Dirty"; + pub const SHARED_CLEAN: &str = "Shared_Clean"; + pub const SHARED_DIRTY: &str = "Shared_Dirty"; + pub const PRIVATE_CLEAN: &str = "Private_Clean"; + pub const PRIVATE_DIRTY: &str = "Private_Dirty"; + pub const REFERENCED: &str = "Referenced"; + pub const ANONYMOUS: &str = "Anonymous"; + pub const KSM: &str = "KSM"; + pub const LAZY_FREE: &str = "LazyFree"; + pub const ANON_HUGE_PAGES: &str = "AnonHugePages"; + pub const SHMEM_PMD_MAPPED: &str = "ShmemPmdMapped"; + pub const FILE_PMD_MAPPED: &str = "FilePmdMapped"; + pub const SHARED_HUGETLB: &str = "Shared_Hugetlb"; + pub const PRIVATE_HUGETLB: &str = "Private_Hugetlb"; + pub const SWAP: &str = "Swap"; + pub const SWAP_PSS: &str = "SwapPss"; + pub const LOCKED: &str = "Locked"; + pub const THP_ELIGIBLE: &str = "THPeligible"; + pub const PROTECTION_KEY: &str = "ProtectionKey"; + pub const VMFLAGS: &str = "VmFlags"; + pub const MAPPING: &str = "Mapping"; +} + +// Represents the configuration for enabling specific fields. +// Note: Address field is always enabled. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct PmapConfig { + // [Fields Display] category + pub perm: bool, + pub offset: bool, + pub device: bool, + pub inode: bool, + pub size: bool, + pub kernel_page_size: bool, + pub mmu_page_size: bool, + pub rss: bool, + pub pss: bool, + pub pss_dirty: bool, + pub shared_clean: bool, + pub shared_dirty: bool, + pub private_clean: bool, + pub private_dirty: bool, + pub referenced: bool, + pub anonymous: bool, + pub ksm: bool, + pub lazy_free: bool, + pub anon_huge_pages: bool, + pub shmem_pmd_mapped: bool, + pub file_pmd_mapped: bool, + pub shared_hugetlb: bool, + pub private_hugetlb: bool, + pub swap: bool, + pub swap_pss: bool, + pub locked: bool, + pub thp_eligible: bool, + pub protection_key: bool, + pub vmflags: bool, + pub mapping: bool, + // Misc + pub custom_format_enabled: bool, +} + +impl PmapConfig { + pub fn get_field_list(&self) -> [&'static str; 29] { + // Note: Address and Mapping are treated separately from other fields. + [ + pmap_field_name::PERM, + pmap_field_name::OFFSET, + pmap_field_name::DEVICE, + pmap_field_name::INODE, + pmap_field_name::SIZE, + pmap_field_name::KERNEL_PAGE_SIZE, + pmap_field_name::MMU_PAGE_SIZE, + pmap_field_name::RSS, + pmap_field_name::PSS, + pmap_field_name::PSS_DIRTY, + pmap_field_name::SHARED_CLEAN, + pmap_field_name::SHARED_DIRTY, + pmap_field_name::PRIVATE_CLEAN, + pmap_field_name::PRIVATE_DIRTY, + pmap_field_name::REFERENCED, + pmap_field_name::ANONYMOUS, + pmap_field_name::KSM, + pmap_field_name::LAZY_FREE, + pmap_field_name::ANON_HUGE_PAGES, + pmap_field_name::SHMEM_PMD_MAPPED, + pmap_field_name::FILE_PMD_MAPPED, + pmap_field_name::SHARED_HUGETLB, + pmap_field_name::PRIVATE_HUGETLB, + pmap_field_name::SWAP, + pmap_field_name::SWAP_PSS, + pmap_field_name::LOCKED, + pmap_field_name::THP_ELIGIBLE, + pmap_field_name::PROTECTION_KEY, + pmap_field_name::VMFLAGS, + ] + } + + pub fn needs_footer(&self, field_name: &str) -> bool { + !matches!( + field_name, + pmap_field_name::ADDRESS + | pmap_field_name::PERM + | pmap_field_name::OFFSET + | pmap_field_name::DEVICE + | pmap_field_name::INODE + | pmap_field_name::VMFLAGS + | pmap_field_name::MAPPING + ) + } + + pub fn is_enabled(&self, field_name: &str) -> bool { + match field_name { + pmap_field_name::PERM => self.perm, + pmap_field_name::OFFSET => self.offset, + pmap_field_name::DEVICE => self.device, + pmap_field_name::INODE => self.inode, + pmap_field_name::SIZE => self.size, + pmap_field_name::KERNEL_PAGE_SIZE => self.kernel_page_size, + pmap_field_name::MMU_PAGE_SIZE => self.mmu_page_size, + pmap_field_name::RSS => self.rss, + pmap_field_name::PSS => self.pss, + pmap_field_name::PSS_DIRTY => self.pss_dirty, + pmap_field_name::SHARED_CLEAN => self.shared_clean, + pmap_field_name::SHARED_DIRTY => self.shared_dirty, + pmap_field_name::PRIVATE_CLEAN => self.private_clean, + pmap_field_name::PRIVATE_DIRTY => self.private_dirty, + pmap_field_name::REFERENCED => self.referenced, + pmap_field_name::ANONYMOUS => self.anonymous, + pmap_field_name::KSM => self.ksm, + pmap_field_name::LAZY_FREE => self.lazy_free, + pmap_field_name::ANON_HUGE_PAGES => self.anon_huge_pages, + pmap_field_name::SHMEM_PMD_MAPPED => self.shmem_pmd_mapped, + pmap_field_name::FILE_PMD_MAPPED => self.file_pmd_mapped, + pmap_field_name::SHARED_HUGETLB => self.shared_hugetlb, + pmap_field_name::PRIVATE_HUGETLB => self.private_hugetlb, + pmap_field_name::SWAP => self.swap, + pmap_field_name::SWAP_PSS => self.swap_pss, + pmap_field_name::LOCKED => self.locked, + pmap_field_name::THP_ELIGIBLE => self.thp_eligible, + pmap_field_name::PROTECTION_KEY => self.protection_key, + pmap_field_name::VMFLAGS => self.vmflags, + pmap_field_name::MAPPING => self.mapping, + _ => false, + } + } + + fn set_field(&mut self, field_name: &str, val: bool) { + match field_name { + pmap_field_name::PERM => self.perm = val, + pmap_field_name::OFFSET => self.offset = val, + pmap_field_name::DEVICE => self.device = val, + pmap_field_name::INODE => self.inode = val, + pmap_field_name::SIZE => self.size = val, + pmap_field_name::KERNEL_PAGE_SIZE => self.kernel_page_size = val, + pmap_field_name::MMU_PAGE_SIZE => self.mmu_page_size = val, + pmap_field_name::RSS => self.rss = val, + pmap_field_name::PSS => self.pss = val, + pmap_field_name::PSS_DIRTY => self.pss_dirty = val, + pmap_field_name::SHARED_CLEAN => self.shared_clean = val, + pmap_field_name::SHARED_DIRTY => self.shared_dirty = val, + pmap_field_name::PRIVATE_CLEAN => self.private_clean = val, + pmap_field_name::PRIVATE_DIRTY => self.private_dirty = val, + pmap_field_name::REFERENCED => self.referenced = val, + pmap_field_name::ANONYMOUS => self.anonymous = val, + pmap_field_name::KSM => self.ksm = val, + pmap_field_name::LAZY_FREE => self.lazy_free = val, + pmap_field_name::ANON_HUGE_PAGES => self.anon_huge_pages = val, + pmap_field_name::SHMEM_PMD_MAPPED => self.shmem_pmd_mapped = val, + pmap_field_name::FILE_PMD_MAPPED => self.file_pmd_mapped = val, + pmap_field_name::SHARED_HUGETLB => self.shared_hugetlb = val, + pmap_field_name::PRIVATE_HUGETLB => self.private_hugetlb = val, + pmap_field_name::SWAP => self.swap = val, + pmap_field_name::SWAP_PSS => self.swap_pss = val, + pmap_field_name::LOCKED => self.locked = val, + pmap_field_name::THP_ELIGIBLE => self.thp_eligible = val, + pmap_field_name::PROTECTION_KEY => self.protection_key = val, + pmap_field_name::VMFLAGS => self.vmflags = val, + pmap_field_name::MAPPING => self.mapping = val, + _ => (), + } + } + + pub fn disable_field(&mut self, field_name: &str) { + self.set_field(field_name, false); + } + + // Preset for more-extended option + pub fn set_more_extended(&mut self) { + self.custom_format_enabled = true; + self.perm = true; + self.offset = true; + self.device = true; + self.inode = true; + self.size = true; + self.rss = true; + self.pss = true; + self.pss_dirty = true; + self.referenced = true; + self.anonymous = true; + self.ksm = true; + self.lazy_free = true; + self.shmem_pmd_mapped = true; + self.file_pmd_mapped = true; + self.shared_hugetlb = true; + self.private_hugetlb = true; + self.swap = true; + self.swap_pss = true; + self.locked = true; + self.thp_eligible = true; + self.protection_key = true; + self.mapping = true; + } + + // Preset for most-extended option + pub fn set_most_extended(&mut self) { + self.custom_format_enabled = true; + self.set_more_extended(); + self.kernel_page_size = true; + self.mmu_page_size = true; + self.shared_clean = true; + self.shared_dirty = true; + self.private_clean = true; + self.private_dirty = true; + self.anon_huge_pages = true; + self.vmflags = true; + } +} diff --git a/src/uu/pmap/src/smaps_format_parser.rs b/src/uu/pmap/src/smaps_format_parser.rs index ae8e6ea0..1d21d412 100644 --- a/src/uu/pmap/src/smaps_format_parser.rs +++ b/src/uu/pmap/src/smaps_format_parser.rs @@ -4,6 +4,7 @@ // file that was distributed with this source code. use crate::maps_format_parser::{parse_map_line, MapLine}; +use crate::pmap_config::pmap_field_name; use std::io::{Error, ErrorKind}; // Represents a parsed single entry from /proc//smaps for the extended formats. @@ -32,9 +33,243 @@ pub struct SmapEntry { pub swap_pss_in_kb: u64, pub locked_in_kb: u64, pub thp_eligible: u64, + pub protection_key: u64, pub vmflags: String, } +impl SmapEntry { + pub fn get_field(&self, field_name: &str) -> String { + match field_name { + pmap_field_name::ADDRESS => self.map_line.address.clone(), + pmap_field_name::PERM => self.map_line.perms.to_string(), + pmap_field_name::OFFSET => self.map_line.offset.clone(), + pmap_field_name::DEVICE => self.map_line.device.clone(), + pmap_field_name::INODE => self.map_line.inode.to_string(), + pmap_field_name::SIZE => self.map_line.size_in_kb.to_string(), + pmap_field_name::KERNEL_PAGE_SIZE => self.kernel_page_size_in_kb.to_string(), + pmap_field_name::MMU_PAGE_SIZE => self.mmu_page_size_in_kb.to_string(), + pmap_field_name::RSS => self.rss_in_kb.to_string(), + pmap_field_name::PSS => self.pss_in_kb.to_string(), + pmap_field_name::PSS_DIRTY => self.pss_dirty_in_kb.to_string(), + pmap_field_name::SHARED_CLEAN => self.shared_clean_in_kb.to_string(), + pmap_field_name::SHARED_DIRTY => self.shared_dirty_in_kb.to_string(), + pmap_field_name::PRIVATE_CLEAN => self.private_clean_in_kb.to_string(), + pmap_field_name::PRIVATE_DIRTY => self.private_dirty_in_kb.to_string(), + pmap_field_name::REFERENCED => self.referenced_in_kb.to_string(), + pmap_field_name::ANONYMOUS => self.anonymous_in_kb.to_string(), + pmap_field_name::KSM => self.ksm_in_kb.to_string(), + pmap_field_name::LAZY_FREE => self.lazy_free_in_kb.to_string(), + pmap_field_name::ANON_HUGE_PAGES => self.anon_huge_pages_in_kb.to_string(), + pmap_field_name::SHMEM_PMD_MAPPED => self.shmem_pmd_mapped_in_kb.to_string(), + pmap_field_name::FILE_PMD_MAPPED => self.file_pmd_mapped_in_kb.to_string(), + pmap_field_name::SHARED_HUGETLB => self.shared_hugetlb_in_kb.to_string(), + pmap_field_name::PRIVATE_HUGETLB => self.private_hugetlb_in_kb.to_string(), + pmap_field_name::SWAP => self.swap_in_kb.to_string(), + pmap_field_name::SWAP_PSS => self.swap_pss_in_kb.to_string(), + pmap_field_name::LOCKED => self.locked_in_kb.to_string(), + pmap_field_name::THP_ELIGIBLE => self.thp_eligible.to_string(), + pmap_field_name::PROTECTION_KEY => self.protection_key.to_string(), + pmap_field_name::VMFLAGS => self.vmflags.clone(), + pmap_field_name::MAPPING => self.map_line.mapping.clone(), + _ => String::new(), + } + } +} + +// Internal info used to determine the print contents. +#[derive(Debug, Clone, PartialEq)] +pub struct SmapTableInfo { + pub has_ksm: bool, + pub has_protection_key: bool, + // Total value + pub total_size_in_kb: u64, + pub total_kernel_page_size_in_kb: u64, + pub total_mmu_page_size_in_kb: u64, + pub total_rss_in_kb: u64, + pub total_pss_in_kb: u64, + pub total_pss_dirty_in_kb: u64, + pub total_shared_clean_in_kb: u64, + pub total_shared_dirty_in_kb: u64, + pub total_private_clean_in_kb: u64, + pub total_private_dirty_in_kb: u64, + pub total_referenced_in_kb: u64, + pub total_anonymous_in_kb: u64, + pub total_ksm_in_kb: u64, + pub total_lazy_free_in_kb: u64, + pub total_anon_huge_pages_in_kb: u64, + pub total_shmem_pmd_mapped_in_kb: u64, + pub total_file_pmd_mapped_in_kb: u64, + pub total_shared_hugetlb_in_kb: u64, + pub total_private_hugetlb_in_kb: u64, + pub total_swap_in_kb: u64, + pub total_swap_pss_in_kb: u64, + pub total_locked_in_kb: u64, + pub total_thp_eligible: u64, + pub total_protection_key: u64, + // Width + pub size_in_kb_width: usize, + pub kernel_page_size_in_kb_width: usize, + pub mmu_page_size_in_kb_width: usize, + pub rss_in_kb_width: usize, + pub pss_in_kb_width: usize, + pub pss_dirty_in_kb_width: usize, + pub shared_clean_in_kb_width: usize, + pub shared_dirty_in_kb_width: usize, + pub private_clean_in_kb_width: usize, + pub private_dirty_in_kb_width: usize, + pub referenced_in_kb_width: usize, + pub anonymous_in_kb_width: usize, + pub ksm_in_kb_width: usize, + pub lazy_free_in_kb_width: usize, + pub anon_huge_pages_in_kb_width: usize, + pub shmem_pmd_mapped_in_kb_width: usize, + pub file_pmd_mapped_in_kb_width: usize, + pub shared_hugetlb_in_kb_width: usize, + pub private_hugetlb_in_kb_width: usize, + pub swap_in_kb_width: usize, + pub swap_pss_in_kb_width: usize, + pub locked_in_kb_width: usize, + pub thp_eligible_width: usize, + pub protection_key_width: usize, + pub vmflags_width: usize, +} + +impl Default for SmapTableInfo { + fn default() -> Self { + Self { + has_ksm: false, + has_protection_key: false, + + total_size_in_kb: 0, + total_kernel_page_size_in_kb: 0, + total_mmu_page_size_in_kb: 0, + total_rss_in_kb: 0, + total_pss_in_kb: 0, + total_pss_dirty_in_kb: 0, + total_shared_clean_in_kb: 0, + total_shared_dirty_in_kb: 0, + total_private_clean_in_kb: 0, + total_private_dirty_in_kb: 0, + total_referenced_in_kb: 0, + total_anonymous_in_kb: 0, + total_ksm_in_kb: 0, + total_lazy_free_in_kb: 0, + total_anon_huge_pages_in_kb: 0, + total_shmem_pmd_mapped_in_kb: 0, + total_file_pmd_mapped_in_kb: 0, + total_shared_hugetlb_in_kb: 0, + total_private_hugetlb_in_kb: 0, + total_swap_in_kb: 0, + total_swap_pss_in_kb: 0, + total_locked_in_kb: 0, + total_thp_eligible: 0, + total_protection_key: 0, + + size_in_kb_width: pmap_field_name::SIZE.len(), + kernel_page_size_in_kb_width: pmap_field_name::KERNEL_PAGE_SIZE.len(), + mmu_page_size_in_kb_width: pmap_field_name::MMU_PAGE_SIZE.len(), + rss_in_kb_width: pmap_field_name::RSS.len(), + pss_in_kb_width: pmap_field_name::PSS.len(), + pss_dirty_in_kb_width: pmap_field_name::PSS_DIRTY.len(), + shared_clean_in_kb_width: pmap_field_name::SHARED_CLEAN.len(), + shared_dirty_in_kb_width: pmap_field_name::SHARED_DIRTY.len(), + private_clean_in_kb_width: pmap_field_name::PRIVATE_CLEAN.len(), + private_dirty_in_kb_width: pmap_field_name::PRIVATE_DIRTY.len(), + referenced_in_kb_width: pmap_field_name::REFERENCED.len(), + anonymous_in_kb_width: pmap_field_name::ANONYMOUS.len(), + ksm_in_kb_width: pmap_field_name::KSM.len(), + lazy_free_in_kb_width: pmap_field_name::LAZY_FREE.len(), + anon_huge_pages_in_kb_width: pmap_field_name::ANON_HUGE_PAGES.len(), + shmem_pmd_mapped_in_kb_width: pmap_field_name::SHMEM_PMD_MAPPED.len(), + file_pmd_mapped_in_kb_width: pmap_field_name::FILE_PMD_MAPPED.len(), + shared_hugetlb_in_kb_width: pmap_field_name::SHARED_HUGETLB.len(), + private_hugetlb_in_kb_width: pmap_field_name::PRIVATE_HUGETLB.len(), + swap_in_kb_width: pmap_field_name::SWAP.len(), + swap_pss_in_kb_width: pmap_field_name::SWAP_PSS.len(), + locked_in_kb_width: pmap_field_name::LOCKED.len(), + thp_eligible_width: pmap_field_name::THP_ELIGIBLE.len(), + protection_key_width: pmap_field_name::PROTECTION_KEY.len(), + vmflags_width: pmap_field_name::VMFLAGS.len(), + } + } +} + +impl SmapTableInfo { + // Used to determine the field width in custom format. + pub fn get_width(&self, field_name: &str) -> usize { + match field_name { + pmap_field_name::ADDRESS => 16, // See maps_format_parser.rs + pmap_field_name::PERM => 4, // See maps_format_parser.rs + pmap_field_name::OFFSET => 16, // See maps_format_parser.rs + pmap_field_name::DEVICE => 9, // See maps_format_parser.rs + pmap_field_name::INODE => 10, // See maps_format_parser.rs + pmap_field_name::SIZE => self.size_in_kb_width, + pmap_field_name::KERNEL_PAGE_SIZE => self.kernel_page_size_in_kb_width, + pmap_field_name::MMU_PAGE_SIZE => self.mmu_page_size_in_kb_width, + pmap_field_name::RSS => self.rss_in_kb_width, + pmap_field_name::PSS => self.pss_in_kb_width, + pmap_field_name::PSS_DIRTY => self.pss_dirty_in_kb_width, + pmap_field_name::SHARED_CLEAN => self.shared_clean_in_kb_width, + pmap_field_name::SHARED_DIRTY => self.shared_dirty_in_kb_width, + pmap_field_name::PRIVATE_CLEAN => self.private_clean_in_kb_width, + pmap_field_name::PRIVATE_DIRTY => self.private_dirty_in_kb_width, + pmap_field_name::REFERENCED => self.referenced_in_kb_width, + pmap_field_name::ANONYMOUS => self.anonymous_in_kb_width, + pmap_field_name::KSM => self.ksm_in_kb_width, + pmap_field_name::LAZY_FREE => self.lazy_free_in_kb_width, + pmap_field_name::ANON_HUGE_PAGES => self.anon_huge_pages_in_kb_width, + pmap_field_name::SHMEM_PMD_MAPPED => self.shmem_pmd_mapped_in_kb_width, + pmap_field_name::FILE_PMD_MAPPED => self.file_pmd_mapped_in_kb_width, + pmap_field_name::SHARED_HUGETLB => self.shared_hugetlb_in_kb_width, + pmap_field_name::PRIVATE_HUGETLB => self.private_hugetlb_in_kb_width, + pmap_field_name::SWAP => self.swap_in_kb_width, + pmap_field_name::SWAP_PSS => self.swap_pss_in_kb_width, + pmap_field_name::LOCKED => self.locked_in_kb_width, + pmap_field_name::THP_ELIGIBLE => self.thp_eligible_width, + pmap_field_name::PROTECTION_KEY => self.protection_key_width, + pmap_field_name::VMFLAGS => self.vmflags_width, + _ => 0, + } + } + + pub fn get_total(&self, field_name: &str) -> u64 { + match field_name { + pmap_field_name::SIZE => self.total_size_in_kb, + pmap_field_name::KERNEL_PAGE_SIZE => self.total_kernel_page_size_in_kb, + pmap_field_name::MMU_PAGE_SIZE => self.total_mmu_page_size_in_kb, + pmap_field_name::RSS => self.total_rss_in_kb, + pmap_field_name::PSS => self.total_pss_in_kb, + pmap_field_name::PSS_DIRTY => self.total_pss_dirty_in_kb, + pmap_field_name::SHARED_CLEAN => self.total_shared_clean_in_kb, + pmap_field_name::SHARED_DIRTY => self.total_shared_dirty_in_kb, + pmap_field_name::PRIVATE_CLEAN => self.total_private_clean_in_kb, + pmap_field_name::PRIVATE_DIRTY => self.total_private_dirty_in_kb, + pmap_field_name::REFERENCED => self.total_referenced_in_kb, + pmap_field_name::ANONYMOUS => self.total_anonymous_in_kb, + pmap_field_name::KSM => self.total_ksm_in_kb, + pmap_field_name::LAZY_FREE => self.total_lazy_free_in_kb, + pmap_field_name::ANON_HUGE_PAGES => self.total_anon_huge_pages_in_kb, + pmap_field_name::SHMEM_PMD_MAPPED => self.total_shmem_pmd_mapped_in_kb, + pmap_field_name::FILE_PMD_MAPPED => self.total_file_pmd_mapped_in_kb, + pmap_field_name::SHARED_HUGETLB => self.total_shared_hugetlb_in_kb, + pmap_field_name::PRIVATE_HUGETLB => self.total_private_hugetlb_in_kb, + pmap_field_name::SWAP => self.total_swap_in_kb, + pmap_field_name::SWAP_PSS => self.total_swap_pss_in_kb, + pmap_field_name::LOCKED => self.total_locked_in_kb, + pmap_field_name::THP_ELIGIBLE => self.total_thp_eligible, + pmap_field_name::PROTECTION_KEY => self.total_protection_key, + _ => 0, + } + } +} + +// Represents the entire parsed entries from /proc//smaps for the extended formats. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct SmapTable { + pub entries: Vec, + pub info: SmapTableInfo, +} + // Parses entries from /proc//smaps. See // https://www.kernel.org/doc/html/latest/filesystems/proc.html for details about the expected // format. @@ -42,17 +277,18 @@ pub struct SmapEntry { // # Errors // // Will return an `Error` if the format is incorrect. -pub fn parse_smap_entries(contents: &str) -> Result, Error> { - let mut smap_entries = Vec::new(); +pub fn parse_smaps(contents: &str) -> Result { + let mut smap_table = SmapTable::default(); let mut smap_entry = SmapEntry::default(); for (i, line) in contents.lines().enumerate() { let map_line = parse_map_line(line); if let Ok(map_line) = map_line { if i > 0 { - smap_entries.push(smap_entry.clone()); + smap_table.entries.push(smap_entry.clone()); smap_entry = SmapEntry::default(); } + smap_table.info.total_size_in_kb += map_line.size_in_kb; smap_entry.map_line = map_line; } else { let (key, val) = line @@ -60,39 +296,113 @@ pub fn parse_smap_entries(contents: &str) -> Result, Error> { .ok_or_else(|| Error::from(ErrorKind::InvalidData))?; let val = val.trim(); - if key == "VmFlags" { + if key == pmap_field_name::VMFLAGS { smap_entry.vmflags = val.into(); + smap_table.info.vmflags_width = + smap_table.info.vmflags_width.max(smap_entry.vmflags.len()); } else { let val = val.strip_suffix(" kB").unwrap_or(val); let val = get_smap_item_value(val)?; match key { - "Size" => { + pmap_field_name::SIZE => { if smap_entry.map_line.size_in_kb != val { return Err(Error::from(ErrorKind::InvalidData)); } } - "KernelPageSize" => smap_entry.kernel_page_size_in_kb = val, - "MMUPageSize" => smap_entry.mmu_page_size_in_kb = val, - "Rss" => smap_entry.rss_in_kb = val, - "Pss" => smap_entry.pss_in_kb = val, - "Pss_Dirty" => smap_entry.pss_dirty_in_kb = val, - "Shared_Clean" => smap_entry.shared_clean_in_kb = val, - "Shared_Dirty" => smap_entry.shared_dirty_in_kb = val, - "Private_Clean" => smap_entry.private_clean_in_kb = val, - "Private_Dirty" => smap_entry.private_dirty_in_kb = val, - "Referenced" => smap_entry.referenced_in_kb = val, - "Anonymous" => smap_entry.anonymous_in_kb = val, - "KSM" => smap_entry.ksm_in_kb = val, - "LazyFree" => smap_entry.lazy_free_in_kb = val, - "AnonHugePages" => smap_entry.anon_huge_pages_in_kb = val, - "ShmemPmdMapped" => smap_entry.shmem_pmd_mapped_in_kb = val, - "FilePmdMapped" => smap_entry.file_pmd_mapped_in_kb = val, - "Shared_Hugetlb" => smap_entry.shared_hugetlb_in_kb = val, - "Private_Hugetlb" => smap_entry.private_hugetlb_in_kb = val, - "Swap" => smap_entry.swap_in_kb = val, - "SwapPss" => smap_entry.swap_pss_in_kb = val, - "Locked" => smap_entry.locked_in_kb = val, - "THPeligible" => smap_entry.thp_eligible = val, + pmap_field_name::KERNEL_PAGE_SIZE => { + smap_entry.kernel_page_size_in_kb = val; + smap_table.info.total_kernel_page_size_in_kb += val; + } + pmap_field_name::MMU_PAGE_SIZE => { + smap_entry.mmu_page_size_in_kb = val; + smap_table.info.total_mmu_page_size_in_kb += val; + } + pmap_field_name::RSS => { + smap_entry.rss_in_kb = val; + smap_table.info.total_rss_in_kb += val; + } + pmap_field_name::PSS => { + smap_entry.pss_in_kb = val; + smap_table.info.total_pss_in_kb += val; + } + pmap_field_name::PSS_DIRTY => { + smap_entry.pss_dirty_in_kb = val; + smap_table.info.total_pss_dirty_in_kb += val; + } + pmap_field_name::SHARED_CLEAN => { + smap_entry.shared_clean_in_kb = val; + smap_table.info.total_shared_clean_in_kb += val; + } + pmap_field_name::SHARED_DIRTY => { + smap_entry.shared_dirty_in_kb = val; + smap_table.info.total_shared_dirty_in_kb += val; + } + pmap_field_name::PRIVATE_CLEAN => { + smap_entry.private_clean_in_kb = val; + smap_table.info.total_private_clean_in_kb += val; + } + pmap_field_name::PRIVATE_DIRTY => { + smap_entry.private_dirty_in_kb = val; + smap_table.info.total_private_dirty_in_kb += val; + } + pmap_field_name::REFERENCED => { + smap_entry.referenced_in_kb = val; + smap_table.info.total_referenced_in_kb += val; + } + pmap_field_name::ANONYMOUS => { + smap_entry.anonymous_in_kb = val; + smap_table.info.total_anonymous_in_kb += val; + } + pmap_field_name::KSM => { + smap_entry.ksm_in_kb = val; + smap_table.info.total_ksm_in_kb += val; + smap_table.info.has_ksm = true; + } + pmap_field_name::LAZY_FREE => { + smap_entry.lazy_free_in_kb = val; + smap_table.info.total_lazy_free_in_kb += val; + } + pmap_field_name::ANON_HUGE_PAGES => { + smap_entry.anon_huge_pages_in_kb = val; + smap_table.info.total_anon_huge_pages_in_kb += val; + } + pmap_field_name::SHMEM_PMD_MAPPED => { + smap_entry.shmem_pmd_mapped_in_kb = val; + smap_table.info.total_shmem_pmd_mapped_in_kb += val; + } + pmap_field_name::FILE_PMD_MAPPED => { + smap_entry.file_pmd_mapped_in_kb = val; + smap_table.info.total_file_pmd_mapped_in_kb += val; + } + pmap_field_name::SHARED_HUGETLB => { + smap_entry.shared_hugetlb_in_kb = val; + smap_table.info.total_shared_hugetlb_in_kb += val; + } + pmap_field_name::PRIVATE_HUGETLB => { + smap_entry.private_hugetlb_in_kb = val; + smap_table.info.total_private_hugetlb_in_kb += val; + } + pmap_field_name::SWAP => { + smap_entry.swap_in_kb = val; + smap_table.info.total_swap_in_kb += val; + } + pmap_field_name::SWAP_PSS => { + smap_entry.swap_pss_in_kb = val; + smap_table.info.total_swap_pss_in_kb += val; + } + pmap_field_name::LOCKED => { + smap_entry.locked_in_kb = val; + smap_table.info.total_locked_in_kb += val; + } + pmap_field_name::THP_ELIGIBLE => { + smap_entry.thp_eligible = val; + smap_table.info.total_thp_eligible += val; + } + pmap_field_name::PROTECTION_KEY => { + smap_entry.protection_key = val; + smap_table.info.total_protection_key += val; + smap_table.info.has_protection_key = true; + } _ => (), } } @@ -100,10 +410,125 @@ pub fn parse_smap_entries(contents: &str) -> Result, Error> { } if !contents.is_empty() { - smap_entries.push(smap_entry); + smap_table.entries.push(smap_entry); } - Ok(smap_entries) + // Update width information + smap_table.info.size_in_kb_width = smap_table + .info + .size_in_kb_width + .max(smap_table.info.total_size_in_kb.to_string().len()); + smap_table.info.kernel_page_size_in_kb_width = + smap_table.info.kernel_page_size_in_kb_width.max( + smap_table + .info + .total_kernel_page_size_in_kb + .to_string() + .len(), + ); + smap_table.info.mmu_page_size_in_kb_width = smap_table + .info + .mmu_page_size_in_kb_width + .max(smap_table.info.total_mmu_page_size_in_kb.to_string().len()); + smap_table.info.rss_in_kb_width = smap_table + .info + .rss_in_kb_width + .max(smap_table.info.total_rss_in_kb.to_string().len()); + smap_table.info.pss_in_kb_width = smap_table + .info + .pss_in_kb_width + .max(smap_table.info.total_pss_in_kb.to_string().len()); + smap_table.info.pss_dirty_in_kb_width = smap_table + .info + .pss_dirty_in_kb_width + .max(smap_table.info.total_pss_dirty_in_kb.to_string().len()); + smap_table.info.shared_clean_in_kb_width = smap_table + .info + .shared_clean_in_kb_width + .max(smap_table.info.total_shared_clean_in_kb.to_string().len()); + smap_table.info.shared_dirty_in_kb_width = smap_table + .info + .shared_dirty_in_kb_width + .max(smap_table.info.total_shared_dirty_in_kb.to_string().len()); + smap_table.info.private_clean_in_kb_width = smap_table + .info + .private_clean_in_kb_width + .max(smap_table.info.total_private_clean_in_kb.to_string().len()); + smap_table.info.private_dirty_in_kb_width = smap_table + .info + .private_dirty_in_kb_width + .max(smap_table.info.total_private_dirty_in_kb.to_string().len()); + smap_table.info.referenced_in_kb_width = smap_table + .info + .referenced_in_kb_width + .max(smap_table.info.total_referenced_in_kb.to_string().len()); + smap_table.info.anonymous_in_kb_width = smap_table + .info + .anonymous_in_kb_width + .max(smap_table.info.total_anonymous_in_kb.to_string().len()); + smap_table.info.ksm_in_kb_width = smap_table + .info + .ksm_in_kb_width + .max(smap_table.info.total_ksm_in_kb.to_string().len()); + smap_table.info.lazy_free_in_kb_width = smap_table + .info + .lazy_free_in_kb_width + .max(smap_table.info.total_lazy_free_in_kb.to_string().len()); + smap_table.info.anon_huge_pages_in_kb_width = smap_table.info.anon_huge_pages_in_kb_width.max( + smap_table + .info + .total_anon_huge_pages_in_kb + .to_string() + .len(), + ); + smap_table.info.shmem_pmd_mapped_in_kb_width = + smap_table.info.shmem_pmd_mapped_in_kb_width.max( + smap_table + .info + .total_shmem_pmd_mapped_in_kb + .to_string() + .len(), + ); + smap_table.info.file_pmd_mapped_in_kb_width = smap_table.info.file_pmd_mapped_in_kb_width.max( + smap_table + .info + .total_file_pmd_mapped_in_kb + .to_string() + .len(), + ); + smap_table.info.shared_hugetlb_in_kb_width = smap_table + .info + .shared_hugetlb_in_kb_width + .max(smap_table.info.total_shared_hugetlb_in_kb.to_string().len()); + smap_table.info.private_hugetlb_in_kb_width = smap_table.info.private_hugetlb_in_kb_width.max( + smap_table + .info + .total_private_hugetlb_in_kb + .to_string() + .len(), + ); + smap_table.info.swap_in_kb_width = smap_table + .info + .swap_in_kb_width + .max(smap_table.info.total_swap_in_kb.to_string().len()); + smap_table.info.swap_pss_in_kb_width = smap_table + .info + .swap_pss_in_kb_width + .max(smap_table.info.total_swap_pss_in_kb.to_string().len()); + smap_table.info.locked_in_kb_width = smap_table + .info + .locked_in_kb_width + .max(smap_table.info.total_locked_in_kb.to_string().len()); + smap_table.info.thp_eligible_width = smap_table + .info + .thp_eligible_width + .max(smap_table.info.total_thp_eligible.to_string().len()); + smap_table.info.protection_key_width = smap_table + .info + .protection_key_width + .max(smap_table.info.total_protection_key.to_string().len()); + + Ok(smap_table) } fn get_smap_item_value(val: &str) -> Result { @@ -146,6 +571,7 @@ mod test { swap_pss_in_kb: u64, locked_in_kb: u64, thp_eligible: u64, + protection_key: u64, vmflags: &str, ) -> SmapEntry { SmapEntry { @@ -180,18 +606,19 @@ mod test { swap_pss_in_kb, locked_in_kb, thp_eligible, + protection_key, vmflags: vmflags.to_string(), } } #[test] - fn test_parse_smap_entries() { + fn test_parse_smaps() { let data = [ ( vec![create_smap_entry( - "0000560880413000", Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "konsole", + "0000560880413000", Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "/usr/bin/konsole", 180, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, "rd mr mw me dw sd")], + 22, 0, "rd mr mw me dw sd")], concat!( "560880413000-560880440000 r--p 00000000 08:08 10813151 /usr/bin/konsole\n", "Size: 180 kB\n", @@ -223,9 +650,9 @@ mod test { ), ( vec![create_smap_entry( - "000071af50000000", Perms::from("rw-p"), "0000000000000000", "000:00000", 0, " [ anon ]", + "000071af50000000", Perms::from("rw-p"), "0000000000000000", "000:00000", 0, "", 132, 4, 4, 128, 9, 9, 128, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, "rd mr mw me sd")], + 0, 2, "rd mr mw me sd")], concat!( "71af50000000-71af50021000 rw-p 00000000 00:00 0 \n", "Size: 132 kB\n", @@ -251,13 +678,14 @@ mod test { "SwapPss: 0 kB\n", "Locked: 0 kB\n", "THPeligible: 0\n", + "ProtectionKey: 2\n", "VmFlags: rd mr mw me sd \n") ), ( vec![create_smap_entry( - "00007ffc3f8df000", Perms::from("rw-p"), "0000000000000000", "000:00000", 0, " [ stack ]", + "00007ffc3f8df000", Perms::from("rw-p"), "0000000000000000", "000:00000", 0, "[stack]", 132, 4, 4, 108, 108, 108, 0, 0, 0, 108, 108, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, "rd wr mr mw me gd ac")], + 0, 3, "rd wr mr mw me gd ac")], concat!( "7ffc3f8df000-7ffc3f900000 rw-p 00000000 00:00 0 [stack]\n", "Size: 132 kB\n", @@ -283,13 +711,14 @@ mod test { "SwapPss: 0 kB\n", "Locked: 0 kB\n", "THPeligible: 0\n", + "ProtectionKey: 3\n", "VmFlags: rd wr mr mw me gd ac\n") ), ( vec![create_smap_entry( - "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, " [ anon ]", + "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem", 16, 4, 4, 16, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, "rd wr mr mw me ac sd")], + 0, 0, "rd wr mr mw me ac sd")], concat!( "71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem\n", "Size: 16 kB\n", @@ -319,9 +748,9 @@ mod test { ), ( vec![create_smap_entry( - "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "memfd:wayland-shm (deleted)", + "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)", 3560, 4, 4, 532, 108, 0, 524, 0, 8, 0, 532, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, "rd mr mw me sd")], + 0, 0, "rd mr mw me sd")], concat!( "71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481 /memfd:wayland-shm (deleted)\n", "Size: 3560 kB\n", @@ -351,9 +780,9 @@ mod test { ), ( vec![create_smap_entry( - "ffffffffff600000", Perms::from("--xp"), "0000000000000000", "000:00000", 0, " [ anon ]", + "ffffffffff600000", Perms::from("--xp"), "0000000000000000", "000:00000", 0, "[vsyscall]", 4, 4, 4, 4, 4, 4, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, "rd wr mr mw me ac sd")], + 0, 0, "rd wr mr mw me ac sd")], concat!( "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n", "Size: 4 kB\n", @@ -383,9 +812,9 @@ mod test { ), ( vec![create_smap_entry( - "00005e8187da8000", Perms::from("r--p"), "0000000000000000", "008:00008", 9524160, "hello world", + "00005e8187da8000", Perms::from("r--p"), "0000000000000000", "008:00008", 9524160, "/usr/bin/hello world", 24, 4, 4, 24, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, "rd ex mr mw me sd")], + 0, 0, "rd ex mr mw me sd")], concat!( "5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160 /usr/bin/hello world\n", "Size: 24 kB\n", @@ -416,13 +845,13 @@ mod test { ( vec![ create_smap_entry( - "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, " [ anon ]", + "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem", 16, 4, 4, 16, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, "rd wr mr mw me ac sd"), + 0, 0, "rd wr mr mw me ac sd"), create_smap_entry( - "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "memfd:wayland-shm (deleted)", + "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)", 3560, 4, 4, 532, 108, 0, 524, 0, 8, 0, 532, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, "rd mr mw me sd"), + 0, 0, "rd mr mw me sd"), ], concat!( "71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem\n", @@ -479,17 +908,17 @@ mod test { ( vec![ create_smap_entry( - "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, " [ anon ]", + "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem", 16, 4, 4, 16, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, "rd wr mr mw me ac sd"), + 0, 3, "rd wr mr mw me ac sd"), create_smap_entry( - "0000560880413000", Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "konsole", + "0000560880413000", Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "/usr/bin/konsole", 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, ""), + 0, 0, ""), create_smap_entry( - "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "memfd:wayland-shm (deleted)", + "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)", 3560, 4, 4, 532, 108, 0, 524, 0, 8, 0, 532, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, "rd mr mw me sd"), + 0, 0, "rd mr mw me sd"), ], concat!( "71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem\n", @@ -516,6 +945,7 @@ mod test { "SwapPss: 0 kB\n", "Locked: 0 kB\n", "THPeligible: 0\n", + "ProtectionKey: 3\n", "VmFlags: rd wr mr mw me ac sd\n", "560880413000-560880440000 r--p 00000000 08:08 10813151 /usr/bin/konsole\n", "71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481 /memfd:wayland-shm (deleted)\n", @@ -542,12 +972,14 @@ mod test { "SwapPss: 0 kB\n", "Locked: 0 kB\n", "THPeligible: 0\n", + "ProtectionKey: 0\n", "VmFlags: rd mr mw me sd \n") ), ]; - for (expected_smap_entries, entries) in data { - assert_eq!(expected_smap_entries, parse_smap_entries(entries).unwrap()); + for (expected_smap_entries, text) in data { + let parsed = parse_smaps(text).unwrap(); + assert_eq!(expected_smap_entries, parsed.entries); } } } diff --git a/tests/by-util/test_pmap.rs b/tests/by-util/test_pmap.rs index e899ae0c..6c9d49a0 100644 --- a/tests/by-util/test_pmap.rs +++ b/tests/by-util/test_pmap.rs @@ -107,6 +107,36 @@ fn test_extended() { } } +#[test] +#[cfg(target_os = "linux")] +fn test_more_extended() { + let pid = process::id(); + + let arg = "-X"; + let result = new_ucmd!() + .arg(arg) + .arg(pid.to_string()) + .succeeds() + .stdout_move_str(); + + assert_more_extended_format(pid, &result); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_most_extended() { + let pid = process::id(); + + let arg = "--XX"; + let result = new_ucmd!() + .arg(arg) + .arg(pid.to_string()) + .succeeds() + .stdout_move_str(); + + assert_most_extended_format(pid, &result); +} + #[test] #[cfg(target_os = "linux")] fn test_extended_permission_denied() { @@ -211,10 +241,10 @@ fn assert_extended_format(pid: u32, s: &str) { let line_count = lines.len(); let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap(); - assert!(re.is_match(lines[0])); + 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]); + assert_eq!(expected_header, lines[1], "failing line: '{}'", lines[1]); let re = Regex::new( r"^[0-9a-f]{16} +[1-9][0-9]* +\d+ +\d+ (-|r)(-|w)(-|x)(-|s)- ( \[ (anon|stack) \]|[a-zA-Z0-9._-]+)$", @@ -222,16 +252,105 @@ fn assert_extended_format(pid: u32, s: &str) { .unwrap(); for line in lines.iter().take(line_count - 2).skip(2) { - assert!(re.is_match(line), "failing line: {line}"); + assert!(re.is_match(line), "failing line: '{line}'"); } let expected_separator = "---------------- ------- ------- ------- "; - assert_eq!(expected_separator, lines[line_count - 2]); + 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: {}", + "failing line: '{}'", + lines[line_count - 1] + ); +} + +// Ensure `s` has the following more extended format (-X): +// +// 1234: /some/path +// Address Perm Offset Device Inode Size Rss Pss Pss_Dirty Referenced Anonymous LazyFree ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible Mapping +// 000073eb5f4c7000 r-xp 0000000000036000 008:00008 2274176 1284 1148 1148 0 1148 0 0 0 0 0 0 0 0 0 0 ld-linux-x86-64.so.2 +// 00007ffd588fc000 r--p 0000000000000000 000:00000 2274176 20 20 20 20 20 20 0 0 0 0 0 0 0 0 0 [stack] +// ffffffffff600000 rw-p 0000000000000000 000:00000 2274176 36 36 36 36 36 36 0 0 0 0 0 0 0 0 0 (one intentional trailing space) +// ... +// ==== ==== ==== ========= ========== ========= ======== ============== ============= ============== =============== ==== ======= ====== =========== (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) { + 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]); + + let re = Regex::new(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+)? (|\[[a-zA-Z_ ]+\]|[a-zA-Z0-9._-]+)$").unwrap(); + + 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] + ); +} + +// Ensure `s` has the following most extended format (--XX): +// +// 1234: /some/path +// Address Perm Offset Device Inode Size KernelPageSize MMUPageSize Rss Pss Pss_Dirty Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous LazyFree AnonHugePages ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible VmFlags Mapping +// 000073eb5f4c7000 r-xp 0000000000036000 008:00008 2274176 1284 4 4 1148 1148 0 0 0 1148 0 1148 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me ld-linux-x86-64.so.2 +// 00007ffd588fc000 r--p 0000000000000000 000:00000 2274176 20 4 4 20 20 20 0 0 0 20 20 20 0 0 0 0 0 0 0 0 0 0 rd mr mw me ac [stack] +// ffffffffff600000 rw-p 0000000000000000 000:00000 2274176 36 4 4 36 36 36 0 0 0 36 36 36 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac (one intentional trailing space) +// ... +// ==== ============== =========== ==== ==== ========= ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== =========== (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) { + 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]); + + let re = Regex::new(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] )*(|\[[a-zA-Z_ ]+\]|[a-zA-Z0-9._-]+)$").unwrap(); + + 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] ); }