Skip to content

Commit e6261fa

Browse files
estodicakebaker
andauthored
pmap: implemented rc options (#456)
* pmap: implemented rc options * pmap: added tests for rc options * fixed lint errors * pmap: updated test_default_rc to run only in CI * Update src/uu/pmap/src/pmap.rs Co-authored-by: Daniel Hofstetter <[email protected]> * Update src/uu/pmap/src/pmap.rs Co-authored-by: Daniel Hofstetter <[email protected]> --------- Co-authored-by: Daniel Hofstetter <[email protected]>
1 parent bc68119 commit e6261fa

File tree

5 files changed

+274
-11
lines changed

5 files changed

+274
-11
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ clap_complete = "4.5.2"
5858
clap_mangen = "0.2.20"
5959
crossterm = "0.29.0"
6060
ctor = "0.4.1"
61+
dirs = "6.0.0"
6162
libc = "0.2.154"
6263
nix = { version = "0.30", default-features = false, features = ["process"] }
6364
phf = "0.12.1"

src/uu/pmap/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ version.workspace = true
1313
[dependencies]
1414
uucore = { workspace = true }
1515
clap = { workspace = true }
16+
dirs = { workspace = true }
1617

1718
[lib]
1819
path = "src/pmap.rs"

src/uu/pmap/src/pmap.rs

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
use clap::{crate_version, Arg, ArgAction, Command};
77
use maps_format_parser::{parse_map_line, MapLine};
8-
use pmap_config::{pmap_field_name, PmapConfig};
8+
use pmap_config::{create_rc, pmap_field_name, PmapConfig};
99
use smaps_format_parser::{parse_smaps, SmapTable};
1010
use std::env;
1111
use std::fs;
@@ -39,12 +39,61 @@ mod options {
3939
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
4040
let matches = uu_app().try_get_matches_from(args)?;
4141

42+
if matches.get_flag(options::CREATE_RC) {
43+
let path = pmap_config::get_rc_default_path();
44+
if std::fs::exists(&path)? {
45+
eprintln!("pmap: the file already exists - delete or rename it first");
46+
eprintln!(
47+
"pmap: couldn't create {}",
48+
pmap_config::get_rc_default_path_str()
49+
);
50+
set_exit_code(1);
51+
} else {
52+
create_rc(&path)?;
53+
eprintln!(
54+
"pmap: {} file successfully created, feel free to edit the content",
55+
pmap_config::get_rc_default_path_str()
56+
);
57+
}
58+
return Ok(());
59+
} else if let Some(path_str) = matches.get_one::<String>(options::CREATE_RC_TO) {
60+
let path = std::path::PathBuf::from(path_str);
61+
if std::fs::exists(&path)? {
62+
eprintln!("pmap: the file already exists - delete or rename it first");
63+
eprintln!("pmap: couldn't create the rc file");
64+
set_exit_code(1);
65+
} else {
66+
create_rc(&path)?;
67+
eprintln!("pmap: rc file successfully created, feel free to edit the content");
68+
}
69+
return Ok(());
70+
}
71+
4272
let mut pmap_config = PmapConfig::default();
4373

4474
if matches.get_flag(options::MORE_EXTENDED) {
4575
pmap_config.set_more_extended();
4676
} else if matches.get_flag(options::MOST_EXTENDED) {
4777
pmap_config.set_most_extended();
78+
} else if matches.get_flag(options::READ_RC) {
79+
let path = pmap_config::get_rc_default_path();
80+
if !std::fs::exists(&path)? {
81+
eprintln!(
82+
"pmap: couldn't read {}",
83+
pmap_config::get_rc_default_path_str()
84+
);
85+
set_exit_code(1);
86+
return Ok(());
87+
}
88+
pmap_config.read_rc(&path)?;
89+
} else if let Some(path) = matches.get_one::<String>(options::READ_RC_FROM) {
90+
let path = std::fs::canonicalize(path)?;
91+
if !std::fs::exists(&path)? {
92+
eprintln!("pmap: couldn't read the rc file");
93+
set_exit_code(1);
94+
return Ok(());
95+
}
96+
pmap_config.read_rc(&path)?;
4897
}
4998

5099
// Options independent with field selection:
@@ -405,36 +454,81 @@ pub fn uu_app() -> Command {
405454
.short('c')
406455
.long("read-rc")
407456
.help("read the default rc")
408-
.action(ArgAction::SetTrue),
409-
)
457+
.action(ArgAction::SetTrue)
458+
.conflicts_with_all([
459+
"read-rc-from",
460+
"device",
461+
"create-rc",
462+
"create-rc-to",
463+
"extended",
464+
"more-extended",
465+
"most-extended",
466+
]),
467+
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
410468
.arg(
411469
Arg::new(options::READ_RC_FROM)
412470
.short('C')
413471
.long("read-rc-from")
414472
.num_args(1)
415-
.help("read the rc from file"),
416-
)
473+
.help("read the rc from file")
474+
.conflicts_with_all([
475+
"read-rc",
476+
"device",
477+
"create-rc",
478+
"create-rc-to",
479+
"extended",
480+
"more-extended",
481+
"most-extended",
482+
]),
483+
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
417484
.arg(
418485
Arg::new(options::CREATE_RC)
419486
.short('n')
420487
.long("create-rc")
421488
.help("create new default rc")
422-
.action(ArgAction::SetTrue),
423-
)
489+
.action(ArgAction::SetTrue)
490+
.conflicts_with_all([
491+
"read-rc",
492+
"read-rc-from",
493+
"device",
494+
"create-rc-to",
495+
"extended",
496+
"more-extended",
497+
"most-extended",
498+
]),
499+
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
424500
.arg(
425501
Arg::new(options::CREATE_RC_TO)
426502
.short('N')
427503
.long("create-rc-to")
428504
.num_args(1)
429-
.help("create new rc to file"),
430-
)
505+
.help("create new rc to file")
506+
.conflicts_with_all([
507+
"read-rc",
508+
"read-rc-from",
509+
"device",
510+
"create-rc",
511+
"extended",
512+
"more-extended",
513+
"most-extended",
514+
]),
515+
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
431516
.arg(
432517
Arg::new(options::DEVICE)
433518
.short('d')
434519
.long("device")
435520
.help("show the device format")
436-
.action(ArgAction::SetTrue),
437-
)
521+
.action(ArgAction::SetTrue)
522+
.conflicts_with_all([
523+
"read-rc",
524+
"read-rc-from",
525+
"create-rc",
526+
"create-rc-to",
527+
"extended",
528+
"more-extended",
529+
"most-extended",
530+
]),
531+
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
438532
.arg(
439533
Arg::new(options::QUIET)
440534
.short('q')

src/uu/pmap/src/pmap_config.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6+
use dirs::home_dir;
7+
use std::io::Error;
8+
use std::path::PathBuf;
9+
610
pub mod pmap_field_name {
711
pub const ADDRESS: &str = "Address";
812
pub const PERM: &str = "Perm";
@@ -200,6 +204,10 @@ impl PmapConfig {
200204
}
201205
}
202206

207+
pub fn enable_field(&mut self, field_name: &str) {
208+
self.set_field(field_name, true);
209+
}
210+
203211
pub fn disable_field(&mut self, field_name: &str) {
204212
self.set_field(field_name, false);
205213
}
@@ -244,4 +252,91 @@ impl PmapConfig {
244252
self.anon_huge_pages = true;
245253
self.vmflags = true;
246254
}
255+
256+
pub fn read_rc(&mut self, path: &PathBuf) -> Result<(), Error> {
257+
self.custom_format_enabled = true;
258+
259+
let contents = std::fs::read_to_string(path)?;
260+
261+
let mut in_field_display = false;
262+
let mut in_mapping = false;
263+
264+
for line in contents.lines() {
265+
let line = line.trim_ascii();
266+
if line.starts_with("#") || line.is_empty() {
267+
continue;
268+
}
269+
270+
// The leftmost category on the line is recoginized.
271+
if line.starts_with("[Fields Display]") {
272+
in_field_display = true;
273+
in_mapping = false;
274+
continue;
275+
} else if line.starts_with("[Mapping]") {
276+
in_field_display = false;
277+
in_mapping = true;
278+
continue;
279+
}
280+
281+
if in_field_display {
282+
self.enable_field(line);
283+
} else if in_mapping && line == "ShowPath" {
284+
self.show_path = true;
285+
}
286+
}
287+
288+
Ok(())
289+
}
290+
}
291+
292+
pub fn create_rc(path: &PathBuf) -> Result<(), Error> {
293+
let contents = "# pmap's Config File\n".to_string()
294+
+ "\n"
295+
+ "# All the entries are case sensitive.\n"
296+
+ "# Unsupported entries are ignored!\n"
297+
+ "\n"
298+
+ "[Fields Display]\n"
299+
+ "\n"
300+
+ "# To enable a field uncomment its entry\n"
301+
+ "\n"
302+
+ "#Perm\n"
303+
+ "#Offset\n"
304+
+ "#Device\n"
305+
+ "#Inode\n"
306+
+ "#Size\n"
307+
+ "#Rss\n"
308+
+ "#Pss\n"
309+
+ "#Shared_Clean\n"
310+
+ "#Shared_Dirty\n"
311+
+ "#Private_Clean\n"
312+
+ "#Private_Dirty\n"
313+
+ "#Referenced\n"
314+
+ "#Anonymous\n"
315+
+ "#AnonHugePages\n"
316+
+ "#Swap\n"
317+
+ "#KernelPageSize\n"
318+
+ "#MMUPageSize\n"
319+
+ "#Locked\n"
320+
+ "#VmFlags\n"
321+
+ "#Mapping\n"
322+
+ "\n"
323+
+ "[Mapping]\n"
324+
+ "\n"
325+
+ "# to show paths in the mapping column uncomment the following line\n"
326+
+ "#ShowPath\n"
327+
+ "\n";
328+
329+
std::fs::write(path, contents)?;
330+
331+
Ok(())
332+
}
333+
334+
pub fn get_rc_default_path() -> PathBuf {
335+
let mut path = home_dir().expect("home directory should not be None");
336+
path.push(".pmaprc");
337+
path
338+
}
339+
340+
pub fn get_rc_default_path_str() -> &'static str {
341+
"~/.pmaprc"
247342
}

tests/by-util/test_pmap.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,78 @@ fn test_no_args() {
2020
new_ucmd!().fails().code_is(1);
2121
}
2222

23+
#[test]
24+
#[cfg(target_os = "linux")]
25+
fn test_default_rc() {
26+
if !uutests::util::is_ci() {
27+
return;
28+
}
29+
30+
let pid = process::id();
31+
let ts = TestScenario::new(util_name!());
32+
33+
// Fails to read before creating rc file
34+
for arg in ["-c", "--read-rc"] {
35+
ts.ucmd().arg(arg).arg(pid.to_string()).fails().code_is(1);
36+
}
37+
38+
// Create rc file
39+
ts.ucmd().arg("-n").succeeds();
40+
41+
// Fails to create because rc file already exists
42+
for arg in ["-n", "--create-rc"] {
43+
ts.ucmd().arg(arg).fails().code_is(1);
44+
}
45+
46+
// Succeeds to read now
47+
for arg in ["-c", "--read-rc"] {
48+
ts.ucmd().arg(arg).arg(pid.to_string()).succeeds();
49+
}
50+
}
51+
52+
#[test]
53+
#[cfg(target_os = "linux")]
54+
fn test_create_rc_to() {
55+
let ts = TestScenario::new(util_name!());
56+
57+
ts.ucmd().args(&["-N", "pmap_rc_file_name"]).succeeds();
58+
59+
// Fails to create because rc file already exists
60+
for arg in ["-N", "--create-rc-to"] {
61+
ts.ucmd()
62+
.args(&[arg, "pmap_rc_file_name"])
63+
.fails()
64+
.code_is(1);
65+
}
66+
}
67+
68+
#[test]
69+
#[cfg(target_os = "linux")]
70+
fn test_read_rc_from() {
71+
let pid = process::id();
72+
let ts = TestScenario::new(util_name!());
73+
74+
// Fails to read before creating rc file
75+
for arg in ["-C", "--read-rc-from"] {
76+
ts.ucmd()
77+
.args(&[arg, "pmap_rc_file_name"])
78+
.arg(pid.to_string())
79+
.fails()
80+
.code_is(1);
81+
}
82+
83+
// Create rc file
84+
ts.ucmd().args(&["-N", "pmap_rc_file_name"]).succeeds();
85+
86+
// Succeeds to read now
87+
for arg in ["-C", "--read-rc-from"] {
88+
ts.ucmd()
89+
.args(&[arg, "pmap_rc_file_name"])
90+
.arg(pid.to_string())
91+
.succeeds();
92+
}
93+
}
94+
2395
#[test]
2496
#[cfg(target_os = "linux")]
2597
fn test_existing_pid() {

0 commit comments

Comments
 (0)