Skip to content

Commit cd03ea7

Browse files
committed
pgrep&pidwait&snice&skill: implement ns nslist
1 parent 8a98b92 commit cd03ea7

File tree

2 files changed

+157
-5
lines changed

2 files changed

+157
-5
lines changed

src/uu/pgrep/src/process.rs

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

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

553652
Ok(env_vars)
554653
}
654+
655+
pub fn namespaces(&self) -> Result<Namespace, io::Error> {
656+
Namespace::from_pid(self.pid)
657+
}
555658
}
556659
impl TryFrom<DirEntry> for ProcessInformation {
557660
type Error = io::Error;
@@ -754,6 +857,20 @@ mod tests {
754857
}
755858
}
756859

860+
#[test]
861+
#[cfg(target_os = "linux")]
862+
fn test_namespaces() {
863+
let pid_entry = ProcessInformation::current_process_info().unwrap();
864+
let namespaces = pid_entry.namespaces().unwrap();
865+
866+
assert!(namespaces.ipc.is_some());
867+
assert!(namespaces.mnt.is_some());
868+
assert!(namespaces.net.is_some());
869+
assert!(namespaces.pid.is_some());
870+
assert!(namespaces.user.is_some());
871+
assert!(namespaces.uts.is_some());
872+
}
873+
757874
#[test]
758875
#[cfg(target_os = "linux")]
759876
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<String>>) -> 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)