Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions src/uu/pmap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ version.workspace = true
[dependencies]
uucore = { workspace = true }
clap = { workspace = true }
dirs = { workspace = true }

[lib]
path = "src/pmap.rs"
Expand Down
116 changes: 105 additions & 11 deletions src/uu/pmap/src/pmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)? {
println!("pmap: the file already exists - delete or rename it first");
println!(
"pmap: couldn't create {}",
pmap_config::get_rc_default_path_str()
);
set_exit_code(1);
} else {
create_rc(&path)?;
println!(
"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)? {
println!("pmap: the file already exists - delete or rename it first");
println!("pmap: couldn't create the rc file");
set_exit_code(1);
} else {
create_rc(&path)?;
println!("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)? {
println!(
"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)? {
println!("pmap: couldn't read the rc file");
set_exit_code(1);
return Ok(());
}
pmap_config.read_rc(&path)?;
}

// Options independent with field selection:
Expand Down Expand Up @@ -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')
Expand Down
95 changes: 95 additions & 0 deletions src/uu/pmap/src/pmap_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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"
}
68 changes: 68 additions & 0 deletions tests/by-util/test_pmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,74 @@ fn test_no_args() {
new_ucmd!().fails().code_is(1);
}

#[test]
#[cfg(target_os = "linux")]
fn test_default_rc() {
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();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can run the functionality of this test in the CI only, otherwise the test will fail if there is already a .pmaprc in the home directory. Using is_ci() should do the trick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you both!
As @cakebaker mentioned, test_default_rc is meant to be run in CI; otherwise you’d need to delete ~/.pmaprc before each run. I've updated the test to ensure it always runs in CI, as suggested.


#[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() {
Expand Down
Loading