Repository: uutils/procps
Pull request #201 (2024-08-25 12:55:39)

pmap: fix "filename" column
This PR fixes the output of the "filename" column when running `cargo run pmap <PID>`. The output of the memory map should now match the output of the original `pmap`.

Before the changes:
```
000075ce972a3000      8K rw--- /usr/lib/ld-linux-x86-64.so.2
00007ffe29309000    132K rw--- [stack]
ffffffffff600000      4K --x-- [vsyscall]
```

After the changes:
```
000075ce972a3000      8K rw--- ld-linux-x86-64.so.2
00007ffe29309000    132K rw---   [ stack ]
ffffffffff600000      4K --x--   [ anon ]
```


Patch:
diff --git a/src/uu/pmap/src/pmap.rs b/src/uu/pmap/src/pmap.rs
index b996a2f..6e882aa 100644
--- a/src/uu/pmap/src/pmap.rs
+++ b/src/uu/pmap/src/pmap.rs
@@ -56,18 +56,25 @@ fn parse_maps(pid: &str) -> Result<(), Error> {
     let contents = fs::read_to_string(path)?;
 
     for line in contents.lines() {
-        let (memory_range, rest) = line.split_once(' ').expect("line should contain ' '");
-        let (start_address, size_in_kb) = parse_memory_range(memory_range);
+        println!("{}", parse_map_line(line));
+    }
+
+    Ok(())
+}
 
-        let (perms, rest) = rest.split_once(' ').expect("line should contain 2nd ' '");
-        let perms = parse_perms(perms);
+// Parses a single line from /proc/<PID>/maps.
+fn parse_map_line(line: &str) -> String {
+    let (memory_range, rest) = line.split_once(' ').expect("line should contain ' '");
+    let (start_address, size_in_kb) = parse_memory_range(memory_range);
 
-        let cmd: String = rest.split_whitespace().skip(3).collect();
+    let (perms, rest) = rest.split_once(' ').expect("line should contain 2nd ' '");
+    let perms = parse_perms(perms);
 
-        println!("{start_address} {size_in_kb:>6}K {perms} {cmd}");
-    }
+    let filename: String = rest.splitn(4, " ").skip(3).collect();
+    let filename = filename.trim_ascii_start();
+    let filename = parse_filename(filename);
 
-    Ok(())
+    format!("{start_address} {size_in_kb:>6}K {perms} {filename}")
 }
 
 // Returns the start address and the size of the provided memory range. The start address is always
@@ -96,6 +103,21 @@ fn parse_perms(perms: &str) -> String {
     format!("{perms}-")
 }
 
+fn parse_filename(filename: &str) -> String {
+    if filename == "[stack]" {
+        return "  [ stack ]".into();
+    }
+
+    if filename.is_empty() || filename.starts_with('[') || filename.starts_with("anon") {
+        return "  [ anon ]".into();
+    }
+
+    match filename.rsplit_once('/') {
+        Some((_, name)) => name.into(),
+        None => filename.into(),
+    }
+}
+
 pub fn uu_app() -> Command {
     Command::new(env!("CARGO_PKG_NAME"))
         .version(crate_version!())
@@ -182,6 +204,44 @@ pub fn uu_app() -> Command {
 mod test {
     use super::*;
 
+    #[test]
+    fn test_parse_map_line() {
+        let data = [
+            (
+                "000062442eb9e000     16K r---- konsole",
+                "62442eb9e000-62442eba2000 r--p 00000000 08:08 10813151                   /usr/bin/konsole"
+            ),
+            (
+                "000071af50000000    132K rw---   [ anon ]",
+                "71af50000000-71af50021000 rw-p 00000000 00:00 0 "
+            ),
+            (
+                "00007ffc3f8df000    132K rw---   [ stack ]",
+                "7ffc3f8df000-7ffc3f900000 rw-p 00000000 00:00 0                          [stack]"
+            ),
+            (
+                "000071af8c9e6000     16K rw-s-   [ anon ]",
+                "71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075                      anon_inode:i915.gem"
+            ),
+            (
+                "000071af6cf0c000   3560K rw-s- memfd:wayland-shm (deleted)",
+                "71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481                     /memfd:wayland-shm (deleted)"
+            ),
+            (
+                "ffffffffff600000      4K --x--   [ anon ]",
+                "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]"
+            ),
+            (
+                "00005e8187da8000     24K r---- hello   world",
+                "5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160                    /usr/bin/hello   world"
+            ),
+        ];
+
+        for (expected, line) in data {
+            assert_eq!(expected, parse_map_line(line));
+        }
+    }
+
     #[test]
     fn test_parse_memory_range() {
         let (start, size) = parse_memory_range("ffffffffff600000-ffffffffff601000");
@@ -199,4 +259,17 @@ mod test {
         assert_eq!("---s-", parse_perms("---s"));
         assert_eq!("rwx--", parse_perms("rwxp"));
     }
+
+    #[test]
+    fn test_parse_filename() {
+        assert_eq!("  [ anon ]", parse_filename(""));
+        assert_eq!("  [ anon ]", parse_filename("[vvar]"));
+        assert_eq!("  [ anon ]", parse_filename("[vdso]"));
+        assert_eq!("  [ anon ]", parse_filename("anon_inode:i915.gem"));
+        assert_eq!("  [ stack ]", parse_filename("[stack]"));
+        assert_eq!(
+            "ld-linux-x86-64.so.2",
+            parse_filename("/usr/lib/ld-linux-x86-64.so.2")
+        );
+    }
 }


Test patch:
diff --git a/tests/by-util/test_pmap.rs b/tests/by-util/test_pmap.rs
index dfff913..776cf89 100644
--- a/tests/by-util/test_pmap.rs
+++ b/tests/by-util/test_pmap.rs
@@ -18,7 +18,7 @@ fn test_existing_pid() {
     use regex::Regex;
 
     let pid = process::id();
-    // TODO ensure that the output format is correct, which is not the case currently
+
     let result = new_ucmd!()
         .arg(pid.to_string())
         .succeeds()
@@ -30,7 +30,7 @@ fn test_existing_pid() {
 
     let rest = rest.trim_end();
     let (memory_map, last_line) = rest.rsplit_once('\n').unwrap();
-    let re = Regex::new("(?m)^[0-9a-f]{16} +[1-9][0-9]*K (-|r)(-|w)(-|x)(-|s)- ").unwrap();
+    let re = Regex::new("(?m)^[0-9a-f]{16} +[1-9][0-9]*K (-|r)(-|w)(-|x)(-|s)- (  $$[ (anon|stack) $$]|[a-zA-Z0-9._-]+)$").unwrap();
     assert!(re.is_match(memory_map));
     // TODO ensure that "total" is followed by a total amount
     assert!(last_line.starts_with(" total"));