mirror of
https://github.com/uutils/procps.git
synced 2026-06-10 16:14:00 -07:00
pmap: implemented more-extended and most-extended options (#415)
* pmap: implemented more-extended and most-extended options * fixed lint errors * fixed test_pmap lint error * Update tests/by-util/test_pmap.rs Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com> * Update tests/by-util/test_pmap.rs Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com> * fixed typo --------- Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
This commit is contained in:
@@ -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<MapLine, Error> {
|
||||
let inode = inode
|
||||
.parse::<u64>()
|
||||
.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<String, Error> {
|
||||
Ok(format!("{major:0>3}:{minor:0>5}"))
|
||||
}
|
||||
|
||||
fn parse_mapping(mapping: &str) -> String {
|
||||
if mapping == "[stack]" {
|
||||
return " [ stack ]".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 mapping.is_empty() || mapping.starts_with('[') || mapping.starts_with("anon") {
|
||||
return " [ anon ]".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));
|
||||
}
|
||||
}
|
||||
|
||||
+195
-53
@@ -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::<String>(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<F>(pid: &str, header: Option<&str>, mut process_entry: F) -> Result<(), Error>
|
||||
where
|
||||
F: FnMut(&SmapEntry),
|
||||
{
|
||||
fn get_smap_table(pid: &str) -> Result<SmapTable, Error> {
|
||||
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')
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+124
-5
@@ -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]
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user