Skip to content

Commit f0feed7

Browse files
Merge pull request #499 from Bluemangoo/feature/impl-ns
pgrep&pidwait&snice&skill: implement `ns` `nslist`
2 parents 8a98b92 + 95d97fa commit f0feed7

File tree

2 files changed

+158
-5
lines changed

2 files changed

+158
-5
lines changed

src/uu/pgrep/src/process.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,106 @@ impl TryFrom<&str> for CgroupMembership {
218218
}
219219
}
220220

221+
// See https://www.man7.org/linux/man-pages/man7/namespaces.7.html
222+
#[derive(Default)]
223+
pub struct Namespace {
224+
pub ipc: Option<String>,
225+
pub mnt: Option<String>,
226+
pub net: Option<String>,
227+
pub pid: Option<String>,
228+
pub user: Option<String>,
229+
pub uts: Option<String>,
230+
}
231+
232+
impl Namespace {
233+
pub fn new() -> Self {
234+
Namespace {
235+
ipc: None,
236+
mnt: None,
237+
net: None,
238+
pid: None,
239+
user: None,
240+
uts: None,
241+
}
242+
}
243+
244+
pub fn from_pid(pid: usize) -> Result<Self, io::Error> {
245+
let mut ns = Namespace::new();
246+
let path = PathBuf::from(format!("/proc/{pid}/ns"));
247+
for entry in fs::read_dir(path)? {
248+
let entry = entry?;
249+
if let Some(name) = entry.file_name().to_str() {
250+
if let Ok(value) = read_link(entry.path()) {
251+
match name {
252+
"ipc" => ns.ipc = Some(value.to_str().unwrap_or_default().to_string()),
253+
"mnt" => ns.mnt = Some(value.to_str().unwrap_or_default().to_string()),
254+
"net" => ns.net = Some(value.to_str().unwrap_or_default().to_string()),
255+
"pid" => ns.pid = Some(value.to_str().unwrap_or_default().to_string()),
256+
"user" => ns.user = Some(value.to_str().unwrap_or_default().to_string()),
257+
"uts" => ns.uts = Some(value.to_str().unwrap_or_default().to_string()),
258+
_ => {}
259+
}
260+
}
261+
}
262+
}
263+
Ok(ns)
264+
}
265+
266+
pub fn filter(&mut self, filters: &[&str]) {
267+
if !filters.contains(&"ipc") {
268+
self.ipc = None;
269+
}
270+
if !filters.contains(&"mnt") {
271+
self.mnt = None;
272+
}
273+
if !filters.contains(&"net") {
274+
self.net = None;
275+
}
276+
if !filters.contains(&"pid") {
277+
self.pid = None;
278+
}
279+
if !filters.contains(&"user") {
280+
self.user = None;
281+
}
282+
if !filters.contains(&"uts") {
283+
self.uts = None;
284+
}
285+
}
286+
287+
pub fn matches(&self, ns: &Namespace) -> bool {
288+
ns.ipc.is_some()
289+
&& self
290+
.ipc
291+
.as_ref()
292+
.is_some_and(|v| v == ns.ipc.as_ref().unwrap())
293+
|| ns.mnt.is_some()
294+
&& self
295+
.mnt
296+
.as_ref()
297+
.is_some_and(|v| v == ns.mnt.as_ref().unwrap())
298+
|| ns.net.is_some()
299+
&& self
300+
.net
301+
.as_ref()
302+
.is_some_and(|v| v == ns.net.as_ref().unwrap())
303+
|| ns.pid.is_some()
304+
&& self
305+
.pid
306+
.as_ref()
307+
.is_some_and(|v| v == ns.pid.as_ref().unwrap())
308+
|| ns.user.is_some()
309+
&& self
310+
.user
311+
.as_ref()
312+
.is_some_and(|v| v == ns.user.as_ref().unwrap())
313+
|| ns.uts.is_some()
314+
&& self
315+
.uts
316+
.as_ref()
317+
.is_some_and(|v| v == ns.uts.as_ref().unwrap())
318+
}
319+
}
320+
221321
/// Process ID and its information
222322
#[derive(Debug, Clone, Default, PartialEq, Eq)]
223323
pub struct ProcessInformation {
@@ -552,6 +652,10 @@ impl ProcessInformation {
552652

553653
Ok(env_vars)
554654
}
655+
656+
pub fn namespaces(&self) -> Result<Namespace, io::Error> {
657+
Namespace::from_pid(self.pid)
658+
}
555659
}
556660
impl TryFrom<DirEntry> for ProcessInformation {
557661
type Error = io::Error;
@@ -754,6 +858,20 @@ mod tests {
754858
}
755859
}
756860

861+
#[test]
862+
#[cfg(target_os = "linux")]
863+
fn test_namespaces() {
864+
let pid_entry = ProcessInformation::current_process_info().unwrap();
865+
let namespaces = pid_entry.namespaces().unwrap();
866+
867+
assert!(namespaces.ipc.is_some());
868+
assert!(namespaces.mnt.is_some());
869+
assert!(namespaces.net.is_some());
870+
assert!(namespaces.pid.is_some());
871+
assert!(namespaces.user.is_some());
872+
assert!(namespaces.uts.is_some());
873+
}
874+
757875
#[test]
758876
#[cfg(target_os = "linux")]
759877
fn test_environ() {

src/uu/pgrep/src/process_matcher.rs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use uucore::{
2323

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

26-
use crate::process::{walk_process, walk_threads, ProcessInformation, Teletype};
26+
use crate::process::{walk_process, walk_threads, Namespace, ProcessInformation, Teletype};
2727

2828
pub struct Settings {
2929
pub regex: Regex,
@@ -47,6 +47,7 @@ pub struct Settings {
4747
pub pgroup: Option<HashSet<u64>>,
4848
pub session: Option<HashSet<u64>>,
4949
pub cgroup: Option<HashSet<String>>,
50+
pub namespaces: Option<Namespace>,
5051
pub env: Option<HashSet<String>>,
5152
pub threads: bool,
5253

@@ -112,6 +113,17 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
112113
cgroup: matches
113114
.get_many::<String>("cgroup")
114115
.map(|groups| groups.cloned().collect()),
116+
namespaces: matches
117+
.get_one::<usize>("ns")
118+
.map(|pid| {
119+
get_namespaces(
120+
*pid,
121+
matches
122+
.get_many::<String>("nslist")
123+
.map(|v| v.into_iter().map(|s| s.as_str()).collect()),
124+
)
125+
})
126+
.transpose()?,
115127
env: matches
116128
.get_many::<String>("env")
117129
.map(|env_vars| env_vars.cloned().collect()),
@@ -133,6 +145,7 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
133145
&& settings.pgroup.is_none()
134146
&& settings.session.is_none()
135147
&& settings.cgroup.is_none()
148+
&& settings.namespaces.is_none()
136149
&& settings.env.is_none()
137150
&& !settings.require_handler
138151
&& settings.pidfile.is_none()
@@ -216,6 +229,22 @@ fn get_ancestors(process_infos: &mut [ProcessInformation], mut pid: usize) -> Ha
216229
ret
217230
}
218231

232+
#[cfg(target_os = "linux")]
233+
fn get_namespaces(pid: usize, list: Option<Vec<&str>>) -> UResult<Namespace> {
234+
let mut ns = Namespace::from_pid(pid)
235+
.map_err(|_| USimpleError::new(1, "Error reading reference namespace information"))?;
236+
if let Some(list) = list {
237+
ns.filter(&list);
238+
}
239+
240+
Ok(ns)
241+
}
242+
243+
#[cfg(not(target_os = "linux"))]
244+
fn get_namespaces(_pid: usize, _list: Option<Vec<&str>>) -> UResult<Namespace> {
245+
Ok(Namespace::new())
246+
}
247+
219248
/// Collect pids with filter construct from command line arguments
220249
fn collect_matched_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>> {
221250
// Filtration general parameters
@@ -283,6 +312,10 @@ fn collect_matched_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>>
283312
&settings.cgroup,
284313
pid.cgroup_v2_path().unwrap_or("/".to_string()),
285314
);
315+
let namespace_matched = settings
316+
.namespaces
317+
.as_ref()
318+
.is_none_or(|ns| ns.matches(&pid.namespaces().unwrap_or_default()));
286319

287320
let env_matched = match &settings.env {
288321
Some(env_filters) => {
@@ -331,6 +364,7 @@ fn collect_matched_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>>
331364
&& pgroup_matched
332365
&& session_matched
333366
&& cgroup_matched
367+
&& namespace_matched
334368
&& env_matched
335369
&& ids_matched
336370
&& handler_matched
@@ -552,10 +586,11 @@ pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
552586
arg!(-A --"ignore-ancestors" "exclude our ancestors from results"),
553587
arg!(--cgroup <grp> "match by cgroup v2 names").value_delimiter(','),
554588
arg!(--env <"name[=val],..."> "match on environment variable").value_delimiter(','),
555-
// arg!(--ns <PID> "match the processes that belong to the same namespace as <pid>"),
556-
// arg!(--nslist <ns> "list which namespaces will be considered for the --ns option.")
557-
// .value_delimiter(',')
558-
// .value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]),
589+
arg!(--ns <PID> "match the processes that belong to the same namespace as <pid>")
590+
.value_parser(clap::value_parser!(usize)),
591+
arg!(--nslist <ns> "list which namespaces will be considered for the --ns option.")
592+
.value_delimiter(',')
593+
.value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]),
559594
Arg::new("pattern")
560595
.help(pattern_help)
561596
.action(ArgAction::Append)

0 commit comments

Comments
 (0)