Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions src/uu/lsns/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub enum LsnsError {
FailedToGetPid(String),
/// Failed to read process information
FailedToReadProcess(String),
// Invalid PID argument
InvalidPidArgument(String),
}

impl LsnsError {
Expand Down Expand Up @@ -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)
}
}
}
}
Expand Down
40 changes: 38 additions & 2 deletions src/uu/lsns/src/lsns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ struct Lsns {
noheadings: bool,
persistent: bool,
namespace_type: Option<NamespaceType>,
task: Option<i32>,
}

#[uucore::main]
Expand All @@ -98,13 +99,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.map(|s| NamespaceType::try_from(s.as_str()))
.transpose()?;

let task = matches
.get_one::<String>("task")
.map(|s| {
s.parse::<i32>()
.map_err(|_| LsnsError::InvalidPidArgument(s.clone()))
})
.transpose()?;

// Initialize lsns struct
let mut lsns = Lsns {
processes: Vec::new(),
namespaces: Vec::new(),
noheadings: matches.get_flag("noheadings"),
persistent: matches.get_flag("persistent"),
namespace_type,
task,
};

read_processes(PATH_PROC, &mut lsns)?;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -561,6 +580,13 @@ fn get_table_with_columns() -> Result<Table, LsnsError> {
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> {
Expand All @@ -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
Expand Down
34 changes: 34 additions & 0 deletions tests/by-util/test_lsns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
}
Loading