diff --git a/Cargo.toml b/Cargo.toml index 293b9247..fe4a31d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/uu/pmap/Cargo.toml b/src/uu/pmap/Cargo.toml index dd17918a..60a42ab0 100644 --- a/src/uu/pmap/Cargo.toml +++ b/src/uu/pmap/Cargo.toml @@ -13,6 +13,7 @@ version.workspace = true [dependencies] uucore = { workspace = true } clap = { workspace = true } +dirs = { workspace = true } [lib] path = "src/pmap.rs" diff --git a/src/uu/pmap/src/pmap.rs b/src/uu/pmap/src/pmap.rs index 34073b46..bc05802a 100644 --- a/src/uu/pmap/src/pmap.rs +++ b/src/uu/pmap/src/pmap.rs @@ -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::(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::(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') diff --git a/src/uu/pmap/src/pmap_config.rs b/src/uu/pmap/src/pmap_config.rs index 5f544c89..073aa6c9 100644 --- a/src/uu/pmap/src/pmap_config.rs +++ b/src/uu/pmap/src/pmap_config.rs @@ -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" } diff --git a/tests/by-util/test_pmap.rs b/tests/by-util/test_pmap.rs index fab26be9..e289f066 100644 --- a/tests/by-util/test_pmap.rs +++ b/tests/by-util/test_pmap.rs @@ -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() {