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 <daniel.hofstetter@42dh.com>

* Update src/uu/pmap/src/pmap.rs

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>

---------

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
This commit is contained in:
estodi
2025-07-03 07:08:41 -07:00
committed by GitHub
parent bc6811964c
commit e6261faad1
5 changed files with 274 additions and 11 deletions
+1
View File
@@ -58,6 +58,7 @@ clap_complete = "4.5.2"
clap_mangen = "0.2.20"
crossterm = "0.29.0"
ctor = "0.4.1"
dirs = "6.0.0"
libc = "0.2.154"
nix = { version = "0.30", default-features = false, features = ["process"] }
phf = "0.12.1"
+1
View File
@@ -13,6 +13,7 @@ version.workspace = true
[dependencies]
uucore = { workspace = true }
clap = { workspace = true }
dirs = { workspace = true }
[lib]
path = "src/pmap.rs"
+105 -11
View File
@@ -5,7 +5,7 @@
use clap::{crate_version, Arg, ArgAction, Command};
use maps_format_parser::{parse_map_line, MapLine};
use pmap_config::{pmap_field_name, PmapConfig};
use pmap_config::{create_rc, pmap_field_name, PmapConfig};
use smaps_format_parser::{parse_smaps, SmapTable};
use std::env;
use std::fs;
@@ -39,12 +39,61 @@ mod options {
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
if matches.get_flag(options::CREATE_RC) {
let path = pmap_config::get_rc_default_path();
if std::fs::exists(&path)? {
eprintln!("pmap: the file already exists - delete or rename it first");
eprintln!(
"pmap: couldn't create {}",
pmap_config::get_rc_default_path_str()
);
set_exit_code(1);
} else {
create_rc(&path)?;
eprintln!(
"pmap: {} file successfully created, feel free to edit the content",
pmap_config::get_rc_default_path_str()
);
}
return Ok(());
} else if let Some(path_str) = matches.get_one::<String>(options::CREATE_RC_TO) {
let path = std::path::PathBuf::from(path_str);
if std::fs::exists(&path)? {
eprintln!("pmap: the file already exists - delete or rename it first");
eprintln!("pmap: couldn't create the rc file");
set_exit_code(1);
} else {
create_rc(&path)?;
eprintln!("pmap: rc file successfully created, feel free to edit the content");
}
return Ok(());
}
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();
} else if matches.get_flag(options::READ_RC) {
let path = pmap_config::get_rc_default_path();
if !std::fs::exists(&path)? {
eprintln!(
"pmap: couldn't read {}",
pmap_config::get_rc_default_path_str()
);
set_exit_code(1);
return Ok(());
}
pmap_config.read_rc(&path)?;
} else if let Some(path) = matches.get_one::<String>(options::READ_RC_FROM) {
let path = std::fs::canonicalize(path)?;
if !std::fs::exists(&path)? {
eprintln!("pmap: couldn't read the rc file");
set_exit_code(1);
return Ok(());
}
pmap_config.read_rc(&path)?;
}
// Options independent with field selection:
@@ -405,36 +454,81 @@ pub fn uu_app() -> Command {
.short('c')
.long("read-rc")
.help("read the default rc")
.action(ArgAction::SetTrue),
)
.action(ArgAction::SetTrue)
.conflicts_with_all([
"read-rc-from",
"device",
"create-rc",
"create-rc-to",
"extended",
"more-extended",
"most-extended",
]),
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
.arg(
Arg::new(options::READ_RC_FROM)
.short('C')
.long("read-rc-from")
.num_args(1)
.help("read the rc from file"),
)
.help("read the rc from file")
.conflicts_with_all([
"read-rc",
"device",
"create-rc",
"create-rc-to",
"extended",
"more-extended",
"most-extended",
]),
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
.arg(
Arg::new(options::CREATE_RC)
.short('n')
.long("create-rc")
.help("create new default rc")
.action(ArgAction::SetTrue),
)
.action(ArgAction::SetTrue)
.conflicts_with_all([
"read-rc",
"read-rc-from",
"device",
"create-rc-to",
"extended",
"more-extended",
"most-extended",
]),
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
.arg(
Arg::new(options::CREATE_RC_TO)
.short('N')
.long("create-rc-to")
.num_args(1)
.help("create new rc to file"),
)
.help("create new rc to file")
.conflicts_with_all([
"read-rc",
"read-rc-from",
"device",
"create-rc",
"extended",
"more-extended",
"most-extended",
]),
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
.arg(
Arg::new(options::DEVICE)
.short('d')
.long("device")
.help("show the device format")
.action(ArgAction::SetTrue),
)
.action(ArgAction::SetTrue)
.conflicts_with_all([
"read-rc",
"read-rc-from",
"create-rc",
"create-rc-to",
"extended",
"more-extended",
"most-extended",
]),
) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive
.arg(
Arg::new(options::QUIET)
.short('q')
+95
View File
@@ -3,6 +3,10 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use dirs::home_dir;
use std::io::Error;
use std::path::PathBuf;
pub mod pmap_field_name {
pub const ADDRESS: &str = "Address";
pub const PERM: &str = "Perm";
@@ -200,6 +204,10 @@ impl PmapConfig {
}
}
pub fn enable_field(&mut self, field_name: &str) {
self.set_field(field_name, true);
}
pub fn disable_field(&mut self, field_name: &str) {
self.set_field(field_name, false);
}
@@ -244,4 +252,91 @@ impl PmapConfig {
self.anon_huge_pages = true;
self.vmflags = true;
}
pub fn read_rc(&mut self, path: &PathBuf) -> Result<(), Error> {
self.custom_format_enabled = true;
let contents = std::fs::read_to_string(path)?;
let mut in_field_display = false;
let mut in_mapping = false;
for line in contents.lines() {
let line = line.trim_ascii();
if line.starts_with("#") || line.is_empty() {
continue;
}
// The leftmost category on the line is recoginized.
if line.starts_with("[Fields Display]") {
in_field_display = true;
in_mapping = false;
continue;
} else if line.starts_with("[Mapping]") {
in_field_display = false;
in_mapping = true;
continue;
}
if in_field_display {
self.enable_field(line);
} else if in_mapping && line == "ShowPath" {
self.show_path = true;
}
}
Ok(())
}
}
pub fn create_rc(path: &PathBuf) -> Result<(), Error> {
let contents = "# pmap's Config File\n".to_string()
+ "\n"
+ "# All the entries are case sensitive.\n"
+ "# Unsupported entries are ignored!\n"
+ "\n"
+ "[Fields Display]\n"
+ "\n"
+ "# To enable a field uncomment its entry\n"
+ "\n"
+ "#Perm\n"
+ "#Offset\n"
+ "#Device\n"
+ "#Inode\n"
+ "#Size\n"
+ "#Rss\n"
+ "#Pss\n"
+ "#Shared_Clean\n"
+ "#Shared_Dirty\n"
+ "#Private_Clean\n"
+ "#Private_Dirty\n"
+ "#Referenced\n"
+ "#Anonymous\n"
+ "#AnonHugePages\n"
+ "#Swap\n"
+ "#KernelPageSize\n"
+ "#MMUPageSize\n"
+ "#Locked\n"
+ "#VmFlags\n"
+ "#Mapping\n"
+ "\n"
+ "[Mapping]\n"
+ "\n"
+ "# to show paths in the mapping column uncomment the following line\n"
+ "#ShowPath\n"
+ "\n";
std::fs::write(path, contents)?;
Ok(())
}
pub fn get_rc_default_path() -> PathBuf {
let mut path = home_dir().expect("home directory should not be None");
path.push(".pmaprc");
path
}
pub fn get_rc_default_path_str() -> &'static str {
"~/.pmaprc"
}
+72
View File
@@ -20,6 +20,78 @@ fn test_no_args() {
new_ucmd!().fails().code_is(1);
}
#[test]
#[cfg(target_os = "linux")]
fn test_default_rc() {
if !uutests::util::is_ci() {
return;
}
let pid = process::id();
let ts = TestScenario::new(util_name!());
// Fails to read before creating rc file
for arg in ["-c", "--read-rc"] {
ts.ucmd().arg(arg).arg(pid.to_string()).fails().code_is(1);
}
// Create rc file
ts.ucmd().arg("-n").succeeds();
// Fails to create because rc file already exists
for arg in ["-n", "--create-rc"] {
ts.ucmd().arg(arg).fails().code_is(1);
}
// Succeeds to read now
for arg in ["-c", "--read-rc"] {
ts.ucmd().arg(arg).arg(pid.to_string()).succeeds();
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_create_rc_to() {
let ts = TestScenario::new(util_name!());
ts.ucmd().args(&["-N", "pmap_rc_file_name"]).succeeds();
// Fails to create because rc file already exists
for arg in ["-N", "--create-rc-to"] {
ts.ucmd()
.args(&[arg, "pmap_rc_file_name"])
.fails()
.code_is(1);
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_read_rc_from() {
let pid = process::id();
let ts = TestScenario::new(util_name!());
// Fails to read before creating rc file
for arg in ["-C", "--read-rc-from"] {
ts.ucmd()
.args(&[arg, "pmap_rc_file_name"])
.arg(pid.to_string())
.fails()
.code_is(1);
}
// Create rc file
ts.ucmd().args(&["-N", "pmap_rc_file_name"]).succeeds();
// Succeeds to read now
for arg in ["-C", "--read-rc-from"] {
ts.ucmd()
.args(&[arg, "pmap_rc_file_name"])
.arg(pid.to_string())
.succeeds();
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_existing_pid() {