Skip to content
Merged
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
118 changes: 118 additions & 0 deletions src/uu/pgrep/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,106 @@ impl TryFrom<&str> for CgroupMembership {
}
}

// See https://www.man7.org/linux/man-pages/man7/namespaces.7.html
#[derive(Default)]
pub struct Namespace {
pub ipc: Option<String>,
pub mnt: Option<String>,
pub net: Option<String>,
pub pid: Option<String>,
pub user: Option<String>,
pub uts: Option<String>,
}

impl Namespace {
pub fn new() -> Self {
Namespace {
ipc: None,
mnt: None,
net: None,
pid: None,
user: None,
uts: None,
}
}

pub fn from_pid(pid: usize) -> Result<Self, io::Error> {
let mut ns = Namespace::new();
let path = PathBuf::from(format!("/proc/{pid}/ns"));
for entry in fs::read_dir(path)? {
let entry = entry?;
if let Some(name) = entry.file_name().to_str() {
if let Ok(value) = read_link(entry.path()) {
match name {
"ipc" => ns.ipc = Some(value.to_str().unwrap_or_default().to_string()),
"mnt" => ns.mnt = Some(value.to_str().unwrap_or_default().to_string()),
"net" => ns.net = Some(value.to_str().unwrap_or_default().to_string()),
"pid" => ns.pid = Some(value.to_str().unwrap_or_default().to_string()),
"user" => ns.user = Some(value.to_str().unwrap_or_default().to_string()),
"uts" => ns.uts = Some(value.to_str().unwrap_or_default().to_string()),
_ => {}
}
}
}
}
Ok(ns)
}

pub fn filter(&mut self, filters: &[&str]) {
if !filters.contains(&"ipc") {
self.ipc = None;
}
if !filters.contains(&"mnt") {
self.mnt = None;
}
if !filters.contains(&"net") {
self.net = None;
}
if !filters.contains(&"pid") {
self.pid = None;
}
if !filters.contains(&"user") {
self.user = None;
}
if !filters.contains(&"uts") {
self.uts = None;
}
}

pub fn matches(&self, ns: &Namespace) -> bool {
ns.ipc.is_some()
&& self
.ipc
.as_ref()
.is_some_and(|v| v == ns.ipc.as_ref().unwrap())
|| ns.mnt.is_some()
&& self
.mnt
.as_ref()
.is_some_and(|v| v == ns.mnt.as_ref().unwrap())
|| ns.net.is_some()
&& self
.net
.as_ref()
.is_some_and(|v| v == ns.net.as_ref().unwrap())
|| ns.pid.is_some()
&& self
.pid
.as_ref()
.is_some_and(|v| v == ns.pid.as_ref().unwrap())
|| ns.user.is_some()
&& self
.user
.as_ref()
.is_some_and(|v| v == ns.user.as_ref().unwrap())
|| ns.uts.is_some()
&& self
.uts
.as_ref()
.is_some_and(|v| v == ns.uts.as_ref().unwrap())
}
}

/// Process ID and its information
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ProcessInformation {
Expand Down Expand Up @@ -552,6 +652,10 @@ impl ProcessInformation {

Ok(env_vars)
}

pub fn namespaces(&self) -> Result<Namespace, io::Error> {
Namespace::from_pid(self.pid)
}
}
impl TryFrom<DirEntry> for ProcessInformation {
type Error = io::Error;
Expand Down Expand Up @@ -754,6 +858,20 @@ mod tests {
}
}

#[test]
#[cfg(target_os = "linux")]
fn test_namespaces() {
let pid_entry = ProcessInformation::current_process_info().unwrap();
let namespaces = pid_entry.namespaces().unwrap();

assert!(namespaces.ipc.is_some());
assert!(namespaces.mnt.is_some());
assert!(namespaces.net.is_some());
assert!(namespaces.pid.is_some());
assert!(namespaces.user.is_some());
assert!(namespaces.uts.is_some());
}

#[test]
#[cfg(target_os = "linux")]
fn test_environ() {
Expand Down
45 changes: 40 additions & 5 deletions src/uu/pgrep/src/process_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use uucore::{

use uucore::error::{UResult, USimpleError};

use crate::process::{walk_process, walk_threads, ProcessInformation, Teletype};
use crate::process::{walk_process, walk_threads, Namespace, ProcessInformation, Teletype};

pub struct Settings {
pub regex: Regex,
Expand All @@ -47,6 +47,7 @@ pub struct Settings {
pub pgroup: Option<HashSet<u64>>,
pub session: Option<HashSet<u64>>,
pub cgroup: Option<HashSet<String>>,
pub namespaces: Option<Namespace>,
pub env: Option<HashSet<String>>,
pub threads: bool,

Expand Down Expand Up @@ -112,6 +113,17 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
cgroup: matches
.get_many::<String>("cgroup")
.map(|groups| groups.cloned().collect()),
namespaces: matches
.get_one::<usize>("ns")
.map(|pid| {
get_namespaces(
*pid,
matches
.get_many::<String>("nslist")
.map(|v| v.into_iter().map(|s| s.as_str()).collect()),
)
})
.transpose()?,
env: matches
.get_many::<String>("env")
.map(|env_vars| env_vars.cloned().collect()),
Expand All @@ -133,6 +145,7 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
&& settings.pgroup.is_none()
&& settings.session.is_none()
&& settings.cgroup.is_none()
&& settings.namespaces.is_none()
&& settings.env.is_none()
&& !settings.require_handler
&& settings.pidfile.is_none()
Expand Down Expand Up @@ -216,6 +229,22 @@ fn get_ancestors(process_infos: &mut [ProcessInformation], mut pid: usize) -> Ha
ret
}

#[cfg(target_os = "linux")]
fn get_namespaces(pid: usize, list: Option<Vec<&str>>) -> UResult<Namespace> {
let mut ns = Namespace::from_pid(pid)
.map_err(|_| USimpleError::new(1, "Error reading reference namespace information"))?;
if let Some(list) = list {
ns.filter(&list);
}

Ok(ns)
}

#[cfg(not(target_os = "linux"))]
fn get_namespaces(_pid: usize, _list: Option<Vec<&str>>) -> UResult<Namespace> {
Ok(Namespace::new())
}

/// Collect pids with filter construct from command line arguments
fn collect_matched_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>> {
// Filtration general parameters
Expand Down Expand Up @@ -283,6 +312,10 @@ fn collect_matched_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>>
&settings.cgroup,
pid.cgroup_v2_path().unwrap_or("/".to_string()),
);
let namespace_matched = settings
.namespaces
.as_ref()
.is_none_or(|ns| ns.matches(&pid.namespaces().unwrap_or_default()));

let env_matched = match &settings.env {
Some(env_filters) => {
Expand Down Expand Up @@ -331,6 +364,7 @@ fn collect_matched_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>>
&& pgroup_matched
&& session_matched
&& cgroup_matched
&& namespace_matched
&& env_matched
&& ids_matched
&& handler_matched
Expand Down Expand Up @@ -552,10 +586,11 @@ pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
arg!(-A --"ignore-ancestors" "exclude our ancestors from results"),
arg!(--cgroup <grp> "match by cgroup v2 names").value_delimiter(','),
arg!(--env <"name[=val],..."> "match on environment variable").value_delimiter(','),
// arg!(--ns <PID> "match the processes that belong to the same namespace as <pid>"),
// arg!(--nslist <ns> "list which namespaces will be considered for the --ns option.")
// .value_delimiter(',')
// .value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]),
arg!(--ns <PID> "match the processes that belong to the same namespace as <pid>")
.value_parser(clap::value_parser!(usize)),
arg!(--nslist <ns> "list which namespaces will be considered for the --ns option.")
.value_delimiter(',')
.value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]),
Arg::new("pattern")
.help(pattern_help)
.action(ArgAction::Append)
Expand Down
Loading