Skip to content

Commit cc6d948

Browse files
committed
pmap: implement --device
1 parent 8358ecb commit cc6d948

File tree

2 files changed

+114
-11
lines changed

2 files changed

+114
-11
lines changed

src/uu/pmap/src/pmap.rs

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// file that was distributed with this source code.
55

66
use clap::{crate_version, Arg, ArgAction, Command};
7-
use maps_format_parser::parse_map_line;
7+
use maps_format_parser::{parse_map_line, MapLine};
88
use std::env;
99
use std::fs;
1010
use std::io::Error;
@@ -49,11 +49,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
4949
}
5050
}
5151

52-
match parse_maps(pid) {
53-
Ok(total) => println!(" total {total:>16}K"),
54-
Err(_) => {
55-
set_exit_code(1);
56-
}
52+
if matches.get_flag(options::DEVICE) {
53+
output_device_format(pid).map_err(|_| set_exit_code(1)).ok();
54+
} else {
55+
output_default_format(pid)
56+
.map_err(|_| set_exit_code(1))
57+
.ok();
5758
}
5859
}
5960

@@ -74,21 +75,70 @@ fn parse_cmdline(pid: &str) -> Result<String, Error> {
7475
Ok(cmdline.into())
7576
}
7677

77-
fn parse_maps(pid: &str) -> Result<u64, Error> {
78+
fn process_maps<F>(pid: &str, mut process_line: F) -> Result<(), Error>
79+
where
80+
F: FnMut(&MapLine),
81+
{
7882
let path = format!("/proc/{pid}/maps");
7983
let contents = fs::read_to_string(path)?;
80-
let mut total = 0;
8184

8285
for line in contents.lines() {
8386
let map_line = parse_map_line(line)?;
87+
process_line(&map_line);
88+
}
89+
90+
Ok(())
91+
}
92+
93+
fn output_default_format(pid: &str) -> Result<(), Error> {
94+
let mut total = 0;
95+
96+
process_maps(pid, |map_line| {
8497
println!(
8598
"{} {:>6}K {} {}",
8699
map_line.address, map_line.size_in_kb, map_line.perms, map_line.mapping
87100
);
88101
total += map_line.size_in_kb;
89-
}
102+
})?;
103+
104+
println!(" total {total:>16}K");
105+
106+
Ok(())
107+
}
108+
109+
fn output_device_format(pid: &str) -> Result<(), Error> {
110+
let mut total_mapped = 0;
111+
let mut total_writeable_private = 0;
112+
let mut total_shared = 0;
113+
114+
println!("Address Kbytes Mode Offset Device Mapping");
115+
116+
process_maps(pid, |map_line| {
117+
println!(
118+
"{} {:>7} {} {} {} {}",
119+
map_line.address,
120+
map_line.size_in_kb,
121+
map_line.perms,
122+
map_line.offset,
123+
map_line.device,
124+
map_line.mapping
125+
);
126+
total_mapped += map_line.size_in_kb;
90127

91-
Ok(total)
128+
if map_line.perms.writable && !map_line.perms.shared {
129+
total_writeable_private += map_line.size_in_kb;
130+
}
131+
132+
if map_line.perms.shared {
133+
total_shared += map_line.size_in_kb;
134+
}
135+
})?;
136+
137+
println!(
138+
"mapped: {total_mapped}K writeable/private: {total_writeable_private}K shared: {total_shared}K"
139+
);
140+
141+
Ok(())
92142
}
93143

94144
pub fn uu_app() -> Command {

tests/by-util/test_pmap.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ fn test_non_existing_pid() {
7373
.no_output();
7474
}
7575

76+
#[test]
77+
#[cfg(target_os = "linux")]
78+
fn test_device() {
79+
let pid = process::id();
80+
81+
for arg in ["-d", "--device"] {
82+
let result = new_ucmd!()
83+
.arg(arg)
84+
.arg(pid.to_string())
85+
.succeeds()
86+
.stdout_move_str();
87+
88+
assert_device_format(pid, &result);
89+
}
90+
}
91+
7692
#[test]
7793
fn test_invalid_arg() {
7894
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
@@ -94,9 +110,46 @@ fn assert_format(pid: u32, s: &str) {
94110

95111
let rest = rest.trim_end();
96112
let (memory_map, last_line) = rest.rsplit_once('\n').unwrap();
97-
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();
113+
let re = Regex::new(r"(?m)^[0-9a-f]{16} +[1-9][0-9]*K (-|r)(-|w)(-|x)(-|s)- ( \[ (anon|stack) \]|[a-zA-Z0-9._-]+)$").unwrap();
98114
assert!(re.is_match(memory_map));
99115

100116
let re = Regex::new("^ total +[1-9][0-9]*K$").unwrap();
101117
assert!(re.is_match(last_line));
102118
}
119+
120+
// Ensure `s` has the following device format (--device):
121+
//
122+
// 1234: /some/path
123+
// Address Kbytes Mode Offset Device Mapping
124+
// 000073eb5f4c7000 8 rw--- 0000000000036000 008:00008 ld-linux-x86-64.so.2
125+
// 00007ffd588fc000 132 rw--- 0000000000000000 000:00000 [ stack ]
126+
// ffffffffff600000 4 --x-- 0000000000000000 000:00000 [ anon ]
127+
// ...
128+
// mapped: 3060K writeable/private: 348K shared: 0K
129+
#[cfg(target_os = "linux")]
130+
fn assert_device_format(pid: u32, s: &str) {
131+
let lines: Vec<_> = s.lines().collect();
132+
let line_count = lines.len();
133+
134+
let re = Regex::new(&format!("^{pid}: .+[^ ]$")).unwrap();
135+
assert!(re.is_match(lines[0]));
136+
137+
let expected_header = "Address Kbytes Mode Offset Device Mapping";
138+
assert_eq!(expected_header, lines[1]);
139+
140+
let re = Regex::new(
141+
r"^[0-9a-f]{16} +[1-9][0-9]* (-|r)(-|w)(-|x)(-|s)- [0-9a-f]{16} [0-9a-f]{3}:[0-9a-f]{5} ( \[ (anon|stack) \]|[a-zA-Z0-9._-]+)$",
142+
)
143+
.unwrap();
144+
145+
for line in lines.iter().take(line_count - 1).skip(2) {
146+
assert!(re.is_match(line), "failing line: {line}");
147+
}
148+
149+
let re = Regex::new(r"^mapped: \d+K\s{4}writeable/private: \d+K\s{4}shared: \d+K$").unwrap();
150+
assert!(
151+
re.is_match(lines[line_count - 1]),
152+
"failing line: {}",
153+
lines[line_count - 1]
154+
);
155+
}

0 commit comments

Comments
 (0)