diff --git a/src/uu/lsns/src/errors.rs b/src/uu/lsns/src/errors.rs index 036a0bf7..3be70a68 100644 --- a/src/uu/lsns/src/errors.rs +++ b/src/uu/lsns/src/errors.rs @@ -29,6 +29,8 @@ pub enum LsnsError { FailedToGetPid(String), /// Failed to read process information FailedToReadProcess(String), + // Invalid PID argument + InvalidPidArgument(String), } impl LsnsError { @@ -74,6 +76,9 @@ impl fmt::Display for LsnsError { Self::FailedToReadProcess(s) => { write!(f, "Failed to read process information: {}", s) } + Self::InvalidPidArgument(s) => { + write!(f, "invalid PID argument: '{}'", s) + } } } } diff --git a/src/uu/lsns/src/lsns.rs b/src/uu/lsns/src/lsns.rs index 03d62e8a..43dc5780 100644 --- a/src/uu/lsns/src/lsns.rs +++ b/src/uu/lsns/src/lsns.rs @@ -87,6 +87,7 @@ struct Lsns { noheadings: bool, persistent: bool, namespace_type: Option, + task: Option, } #[uucore::main] @@ -98,6 +99,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|s| NamespaceType::try_from(s.as_str())) .transpose()?; + let task = matches + .get_one::("task") + .map(|s| { + s.parse::() + .map_err(|_| LsnsError::InvalidPidArgument(s.clone())) + }) + .transpose()?; + // Initialize lsns struct let mut lsns = Lsns { processes: Vec::new(), @@ -105,6 +114,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { noheadings: matches.get_flag("noheadings"), persistent: matches.get_flag("persistent"), namespace_type, + task, }; read_processes(PATH_PROC, &mut lsns)?; @@ -145,6 +155,15 @@ pub fn uu_app() -> Command { .value_name("name") .help("namespace type (mnt, net, ipc, user, pid, uts, cgroup, time)"), ) + .arg( + Arg::new("task") + .short('p') + .long("task") + .action(ArgAction::Set) + .required(false) + .value_name("pid") + .help("print process namespaces"), + ) } /// Read information of all the processes from /proc @@ -561,6 +580,13 @@ fn get_table_with_columns() -> Result { Ok(table) } +/// Check if a namespace contains a specific process +fn namespace_has_process(lsns: &Lsns, ns: &Namespace, pid: i32) -> bool { + lsns.processes + .iter() + .any(|proc| proc.pid == pid && proc.ns_ids[ns.ns_type as usize] == ns.id) +} + /// Display namespaces in default format using smartcols #[cfg(target_os = "linux")] fn display_namespaces(lsns: &Lsns) -> Result<(), LsnsError> { @@ -586,19 +612,29 @@ fn display_namespaces(lsns: &Lsns) -> Result<(), LsnsError> { continue; } + // If --type argument is supplied then show only the specified namespace type if let Some(namespace_type) = &lsns.namespace_type && ns.ns_type != *namespace_type { continue; } - // Get namespace type name - let ns_type = NSNAMES[ns.ns_type as usize]; + // If --task argument is supplied then show only the namespaces of the specified process + // Note: task_pid == 0 means show all namespaces (no filtering) + if let Some(task_pid) = lsns.task + && task_pid != 0 + && !namespace_has_process(lsns, ns, task_pid) + { + continue; + } // Find representative process using O(1) HashMap lookup let rep_pid = ns.representative_pid.unwrap_or(0); let rep_proc = process_map.get(&rep_pid).copied(); + // Get namespace type name + let ns_type = NSNAMES[ns.ns_type as usize]; + // Get user name let uid = if let Some(proc) = rep_proc { proc.uid diff --git a/tests/by-util/test_lsns.rs b/tests/by-util/test_lsns.rs index 5976ad92..5f4713c6 100644 --- a/tests/by-util/test_lsns.rs +++ b/tests/by-util/test_lsns.rs @@ -212,3 +212,37 @@ fn test_invalid_namespace_type() { assert!(stdout.contains("lsns: unknown namespace type: invalid-namespace-type")); } + +#[test] +#[cfg(target_os = "linux")] +fn test_invalid_task_pid() { + let res = new_ucmd!().arg("--task").arg("not_a_number").fails(); + let stderr = res.stderr_str(); + + assert!(stderr.contains("invalid PID argument: 'not_a_number'")); +} + +// Write a test to verify that -p 0 part. +#[test] +#[cfg(target_os = "linux")] +fn test_task_pid_0() { + let res = new_ucmd!().arg("--task").arg("0").succeeds(); + let stdout = res.no_stderr().stdout_str(); + + // With -p 0, all namespaces should be shown + let line = stdout.lines().next().unwrap(); + + // Check for all regular headers + let columns = line.split_whitespace().count(); + assert!( + columns == 6, + "All 6 columns should be displayed when -p 0 is used" + ); + + // Check for at least one entry + let entry_count = stdout.lines().count(); + assert!( + entry_count > 1, + "There should be at least one entry when -p 0 is used" + ); +}