Skip to content

Commit e764d24

Browse files
authored
Merge pull request #340 from dezgeg/pgroup-session
pgrep/pkill/pidwait: Support --session and --pgroup
2 parents 33b471e + 5a432e3 commit e764d24

File tree

4 files changed

+158
-37
lines changed

4 files changed

+158
-37
lines changed

src/uu/pgrep/src/pgrep.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
4545
// pgrep from procps-ng outputs the process name inside square brackets
4646
// if /proc/<PID>/cmdline is empty
4747
if it.cmdline.is_empty() {
48-
format!("{} [{}]", it.pid, it.clone().status().get("Name").unwrap())
48+
format!("{} [{}]", it.pid, it.clone().name().unwrap())
4949
} else {
5050
format!("{} {}", it.pid, it.cmdline)
5151
}
5252
})
5353
.collect()
5454
} else if matches.get_flag("list-name") {
5555
pids.into_iter()
56-
.map(|it| format!("{} {}", it.pid, it.clone().status().get("Name").unwrap()))
56+
.map(|it| format!("{} {}", it.pid, it.clone().name().unwrap()))
5757
.collect()
5858
} else {
5959
pids.into_iter().map(|it| format!("{}", it.pid)).collect()

src/uu/pgrep/src/process.rs

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,21 @@ impl ProcessInformation {
274274
Rc::clone(&result)
275275
}
276276

277+
pub fn name(&mut self) -> Result<String, io::Error> {
278+
self.status()
279+
.get("Name")
280+
.cloned()
281+
.ok_or(io::ErrorKind::InvalidData.into())
282+
}
283+
284+
fn get_numeric_stat_field(&mut self, index: usize) -> Result<u64, io::Error> {
285+
self.stat()
286+
.get(index)
287+
.ok_or(io::ErrorKind::InvalidData)?
288+
.parse::<u64>()
289+
.map_err(|_| io::ErrorKind::InvalidData.into())
290+
}
291+
277292
/// Fetch start time from [ProcessInformation::cached_stat]
278293
///
279294
/// - [The /proc Filesystem: Table 1-4](https://docs.kernel.org/filesystems/proc.html#id10)
@@ -284,12 +299,7 @@ impl ProcessInformation {
284299

285300
// Kernel doc: https://docs.kernel.org/filesystems/proc.html#process-specific-subdirectories
286301
// Table 1-4
287-
let time = self
288-
.stat()
289-
.get(21)
290-
.ok_or(io::ErrorKind::InvalidData)?
291-
.parse::<u64>()
292-
.map_err(|_| io::ErrorKind::InvalidData)?;
302+
let time = self.get_numeric_stat_field(21)?;
293303

294304
self.cached_start_time = Some(time);
295305

@@ -299,11 +309,19 @@ impl ProcessInformation {
299309
pub fn ppid(&mut self) -> Result<u64, io::Error> {
300310
// the PPID is the fourth field in /proc/<PID>/stat
301311
// (https://www.kernel.org/doc/html/latest/filesystems/proc.html#id10)
302-
self.stat()
303-
.get(3)
304-
.ok_or(io::ErrorKind::InvalidData)?
305-
.parse::<u64>()
306-
.map_err(|_| io::ErrorKind::InvalidData.into())
312+
self.get_numeric_stat_field(3)
313+
}
314+
315+
pub fn pgid(&mut self) -> Result<u64, io::Error> {
316+
// the process group ID is the fifth field in /proc/<PID>/stat
317+
// (https://www.kernel.org/doc/html/latest/filesystems/proc.html#id10)
318+
self.get_numeric_stat_field(4)
319+
}
320+
321+
pub fn sid(&mut self) -> Result<u64, io::Error> {
322+
// the session ID is the sixth field in /proc/<PID>/stat
323+
// (https://www.kernel.org/doc/html/latest/filesystems/proc.html#id10)
324+
self.get_numeric_stat_field(5)
307325
}
308326

309327
fn get_uid_or_gid_field(&mut self, field: &str, index: usize) -> Result<u32, io::Error> {
@@ -475,6 +493,12 @@ mod tests {
475493
.unwrap()
476494
}
477495

496+
#[cfg(target_os = "linux")]
497+
fn current_process_info() -> ProcessInformation {
498+
ProcessInformation::try_new(PathBuf::from_str(&format!("/proc/{}", current_pid())).unwrap())
499+
.unwrap()
500+
}
501+
478502
#[test]
479503
#[cfg(target_os = "linux")]
480504
fn test_walk_pid() {
@@ -490,11 +514,7 @@ mod tests {
490514
fn test_pid_entry() {
491515
let current_pid = current_pid();
492516

493-
let pid_entry = ProcessInformation::try_new(
494-
PathBuf::from_str(&format!("/proc/{}", current_pid)).unwrap(),
495-
)
496-
.unwrap();
497-
517+
let pid_entry = current_process_info();
498518
let mut result = WalkDir::new(format!("/proc/{}/fd", current_pid))
499519
.into_iter()
500520
.flatten()
@@ -515,10 +535,7 @@ mod tests {
515535
fn test_thread_ids() {
516536
let main_tid = unsafe { uucore::libc::gettid() };
517537
std::thread::spawn(move || {
518-
let mut pid_entry = ProcessInformation::try_new(
519-
PathBuf::from_str(&format!("/proc/{}", current_pid())).unwrap(),
520-
)
521-
.unwrap();
538+
let mut pid_entry = current_process_info();
522539
let thread_ids = pid_entry.thread_ids();
523540

524541
assert!(thread_ids.contains(&(main_tid as usize)));
@@ -545,13 +562,26 @@ mod tests {
545562
assert!(stat_split(case)[1] == "sleep (2) .sh");
546563
}
547564

565+
#[test]
566+
#[cfg(target_os = "linux")]
567+
fn test_ids() {
568+
let mut pid_entry = current_process_info();
569+
assert_eq!(
570+
pid_entry.ppid().unwrap(),
571+
unsafe { uucore::libc::getppid() } as u64
572+
);
573+
assert_eq!(
574+
pid_entry.pgid().unwrap(),
575+
unsafe { uucore::libc::getpgid(0) } as u64
576+
);
577+
assert_eq!(pid_entry.sid().unwrap(), unsafe { uucore::libc::getsid(0) }
578+
as u64);
579+
}
580+
548581
#[test]
549582
#[cfg(target_os = "linux")]
550583
fn test_uid_gid() {
551-
let mut pid_entry = ProcessInformation::try_new(
552-
PathBuf::from_str(&format!("/proc/{}", current_pid())).unwrap(),
553-
)
554-
.unwrap();
584+
let mut pid_entry = current_process_info();
555585
assert_eq!(pid_entry.uid().unwrap(), uucore::process::getuid());
556586
assert_eq!(pid_entry.euid().unwrap(), uucore::process::geteuid());
557587
assert_eq!(pid_entry.gid().unwrap(), uucore::process::getgid());

src/uu/pgrep/src/process_matcher.rs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use std::{collections::HashSet, io};
1111
use clap::{arg, Arg, ArgAction, ArgMatches};
1212
use regex::Regex;
1313
#[cfg(unix)]
14+
use uucore::libc::{getpgrp, getsid};
15+
#[cfg(unix)]
1416
use uucore::{
1517
display::Quotable,
1618
entries::{grp2gid, usr2uid},
@@ -40,6 +42,8 @@ pub struct Settings {
4042
pub uid: Option<HashSet<u32>>,
4143
pub euid: Option<HashSet<u32>>,
4244
pub gid: Option<HashSet<u32>>,
45+
pub pgroup: Option<HashSet<u64>>,
46+
pub session: Option<HashSet<u64>>,
4347
}
4448

4549
pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
@@ -76,6 +80,26 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
7680
gid: matches
7781
.get_many::<u32>("group")
7882
.map(|ids| ids.cloned().collect()),
83+
pgroup: matches.get_many::<u64>("pgroup").map(|xs| {
84+
xs.map(|pg| {
85+
if *pg == 0 {
86+
unsafe { getpgrp() as u64 }
87+
} else {
88+
*pg
89+
}
90+
})
91+
.collect()
92+
}),
93+
session: matches.get_many::<u64>("session").map(|xs| {
94+
xs.map(|sid| {
95+
if *sid == 0 {
96+
unsafe { getsid(0) as u64 }
97+
} else {
98+
*sid
99+
}
100+
})
101+
.collect()
102+
}),
79103
};
80104

81105
if !settings.newest
@@ -87,6 +111,8 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
87111
&& settings.uid.is_none()
88112
&& settings.euid.is_none()
89113
&& settings.gid.is_none()
114+
&& settings.pgroup.is_none()
115+
&& settings.session.is_none()
90116
&& pattern.is_empty()
91117
{
92118
return Err(USimpleError::new(
@@ -182,12 +208,11 @@ fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
182208
_ => true,
183209
};
184210

185-
let binding = pid.status();
186-
let name = binding.get("Name").unwrap();
211+
let name = pid.name().unwrap();
187212
let name = if settings.ignore_case {
188213
name.to_lowercase()
189214
} else {
190-
name.into()
215+
name
191216
};
192217
let pattern_matched = {
193218
let want = if settings.full {
@@ -207,6 +232,8 @@ fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
207232
let older_matched = pid.start_time().unwrap() >= arg_older;
208233

209234
let parent_matched = any_matches(&settings.parent, pid.ppid().unwrap());
235+
let pgroup_matched = any_matches(&settings.pgroup, pid.pgid().unwrap());
236+
let session_matched = any_matches(&settings.session, pid.sid().unwrap());
210237

211238
let ids_matched = any_matches(&settings.uid, pid.uid().unwrap())
212239
&& any_matches(&settings.euid, pid.euid().unwrap())
@@ -217,6 +244,8 @@ fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
217244
&& tty_matched
218245
&& older_matched
219246
&& parent_matched
247+
&& pgroup_matched
248+
&& session_matched
220249
&& ids_matched)
221250
^ settings.inverse
222251
{
@@ -290,6 +319,22 @@ pub fn grp2gid(_name: &str) -> io::Result<u32> {
290319
))
291320
}
292321

322+
/// # Safety
323+
///
324+
/// Dummy implementation for unsupported platforms.
325+
#[cfg(not(unix))]
326+
pub unsafe fn getpgrp() -> u32 {
327+
panic!("unsupported on this platform");
328+
}
329+
330+
/// # Safety
331+
///
332+
/// Dummy implementation for unsupported platforms.
333+
#[cfg(not(unix))]
334+
pub unsafe fn getsid(_pid: u32) -> u32 {
335+
panic!("unsupported on this platform");
336+
}
337+
293338
fn parse_uid_or_username(uid_or_username: &str) -> io::Result<u32> {
294339
uid_or_username
295340
.parse::<u32>()
@@ -315,9 +360,9 @@ pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
315360
arg!(-H --"require-handler" "match only if signal handler is present"),
316361
arg!(-c --count "count of matching processes"),
317362
arg!(-f --full "use full process name to match"),
318-
// arg!(-g --pgroup <PGID> "match listed process group IDs")
319-
// .value_delimiter(',')
320-
// .value_parser(clap::value_parser!(u64)),
363+
arg!(-g --pgroup <PGID> "match listed process group IDs")
364+
.value_delimiter(',')
365+
.value_parser(clap::value_parser!(u64)),
321366
arg!(-G --group <GID> "match real group IDs")
322367
.value_delimiter(',')
323368
.value_parser(parse_gid_or_group_name),
@@ -331,9 +376,9 @@ pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
331376
arg!(-P --parent <PPID> "match only child processes of the given parent")
332377
.value_delimiter(',')
333378
.value_parser(clap::value_parser!(u64)),
334-
// arg!(-s --session <SID> "match session IDs")
335-
// .value_delimiter(',')
336-
// .value_parser(clap::value_parser!(u64)),
379+
arg!(-s --session <SID> "match session IDs")
380+
.value_delimiter(',')
381+
.value_parser(clap::value_parser!(u64)),
337382
arg!(--signal <sig> "signal to send (either number or name)")
338383
.default_value("SIGTERM"),
339384
arg!(-t --terminal <tty> "match by controlling terminal").value_delimiter(','),

tests/by-util/test_pgrep.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,11 +424,57 @@ fn test_current_user() {
424424
#[test]
425425
#[cfg(target_os = "linux")]
426426
fn test_does_not_match_current_process() {
427-
let our_pid = std::process::id();
428-
dbg!(&our_pid);
429427
new_ucmd!()
430428
.arg("-f")
431429
.arg("UNIQUE_STRING_THAT_DOES_NOT_MATCH_ANY_OTHER_PROCESS")
432430
.fails()
433431
.no_output();
434432
}
433+
434+
#[test]
435+
#[cfg(target_os = "linux")]
436+
fn test_pgroup() {
437+
let our_pid = std::process::id();
438+
let our_pgroup = unsafe { uucore::libc::getpgid(0) };
439+
new_ucmd!()
440+
.arg("--pgroup")
441+
.arg(our_pgroup.to_string())
442+
.succeeds()
443+
.stdout_contains(our_pid.to_string());
444+
445+
new_ucmd!()
446+
.arg("--pgroup")
447+
.arg("0")
448+
.succeeds()
449+
.stdout_contains(our_pid.to_string());
450+
}
451+
452+
#[test]
453+
#[cfg(target_os = "linux")]
454+
fn test_nonexisting_pgroup() {
455+
new_ucmd!().arg("--pgroup=9999999999").fails();
456+
}
457+
458+
#[test]
459+
#[cfg(target_os = "linux")]
460+
fn test_session() {
461+
let our_pid = std::process::id();
462+
let our_sid = unsafe { uucore::libc::getsid(0) };
463+
new_ucmd!()
464+
.arg("--session")
465+
.arg(our_sid.to_string())
466+
.succeeds()
467+
.stdout_contains(our_pid.to_string());
468+
469+
new_ucmd!()
470+
.arg("--session")
471+
.arg("0")
472+
.succeeds()
473+
.stdout_contains(our_pid.to_string());
474+
}
475+
476+
#[test]
477+
#[cfg(target_os = "linux")]
478+
fn test_nonexisting_session() {
479+
new_ucmd!().arg("--session=9999999999").fails();
480+
}

0 commit comments

Comments
 (0)