Skip to content

Commit 36a9cfb

Browse files
committed
process_matcher: Add support for --uid, --euid & --group
Resolving user/group names requires enabling "entries" feature in uucore.
1 parent c7ade99 commit 36a9cfb

File tree

5 files changed

+102
-18
lines changed

5 files changed

+102
-18
lines changed

src/uu/pgrep/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ categories = ["command-line-utilities"]
1313

1414

1515
[dependencies]
16-
uucore = { workspace = true }
16+
uucore = { workspace = true, features = ["entries"] }
1717
clap = { workspace = true }
1818
walkdir = { workspace = true }
1919
regex = { workspace = true }

src/uu/pgrep/src/process_matcher.rs

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@
55

66
// Common process matcher logic shared by pgrep, pkill and pidwait
77

8-
use std::collections::HashSet;
98
use std::hash::Hash;
9+
use std::{collections::HashSet, io};
1010

1111
use clap::{arg, Arg, ArgAction, ArgMatches};
1212
use regex::Regex;
13-
use uucore::error::{UResult, USimpleError};
1413
#[cfg(unix)]
15-
use uucore::{display::Quotable, signals::signal_by_name_or_value};
14+
use uucore::{
15+
display::Quotable,
16+
entries::{grp2gid, usr2uid},
17+
signals::signal_by_name_or_value,
18+
};
19+
20+
use uucore::error::{UResult, USimpleError};
1621

1722
use crate::process::{walk_process, ProcessInformation, Teletype};
1823

@@ -32,6 +37,9 @@ pub struct Settings {
3237
#[cfg(unix)]
3338
pub signal: usize,
3439
pub require_handler: bool,
40+
pub uid: Option<HashSet<u32>>,
41+
pub euid: Option<HashSet<u32>>,
42+
pub gid: Option<HashSet<u32>>,
3543
}
3644

3745
pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
@@ -59,14 +67,26 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
5967
#[cfg(unix)]
6068
signal: parse_signal_value(matches.get_one::<String>("signal").unwrap())?,
6169
require_handler: matches.get_flag("require-handler"),
70+
uid: matches
71+
.get_many::<u32>("uid")
72+
.map(|ids| ids.cloned().collect()),
73+
euid: matches
74+
.get_many::<u32>("euid")
75+
.map(|ids| ids.cloned().collect()),
76+
gid: matches
77+
.get_many::<u32>("group")
78+
.map(|ids| ids.cloned().collect()),
6279
};
6380

64-
if (!settings.newest
81+
if !settings.newest
6582
&& !settings.oldest
6683
&& settings.runstates.is_none()
6784
&& settings.older.is_none()
6885
&& settings.parent.is_none()
69-
&& settings.terminal.is_none())
86+
&& settings.terminal.is_none()
87+
&& settings.uid.is_none()
88+
&& settings.euid.is_none()
89+
&& settings.gid.is_none()
7090
&& pattern.is_empty()
7191
{
7292
return Err(USimpleError::new(
@@ -183,11 +203,16 @@ fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
183203

184204
let parent_matched = any_matches(&settings.parent, pid.ppid().unwrap());
185205

206+
let ids_matched = any_matches(&settings.uid, pid.uid().unwrap())
207+
&& any_matches(&settings.euid, pid.euid().unwrap())
208+
&& any_matches(&settings.gid, pid.gid().unwrap());
209+
186210
if (run_state_matched
187211
&& pattern_matched
188212
&& tty_matched
189213
&& older_matched
190-
&& parent_matched)
214+
&& parent_matched
215+
&& ids_matched)
191216
^ settings.inverse
192217
{
193218
tmp_vec.push(pid);
@@ -244,6 +269,36 @@ fn parse_signal_value(signal_name: &str) -> UResult<usize> {
244269
.ok_or_else(|| USimpleError::new(1, format!("Unknown signal {}", signal_name.quote())))
245270
}
246271

272+
#[cfg(not(unix))]
273+
pub fn usr2uid(_name: &str) -> io::Result<u32> {
274+
Err(io::Error::new(
275+
io::ErrorKind::InvalidInput,
276+
"unsupported on this platform",
277+
))
278+
}
279+
280+
#[cfg(not(unix))]
281+
pub fn grp2gid(_name: &str) -> io::Result<u32> {
282+
Err(io::Error::new(
283+
io::ErrorKind::InvalidInput,
284+
"unsupported on this platform",
285+
))
286+
}
287+
288+
fn parse_uid_or_username(uid_or_username: &str) -> io::Result<u32> {
289+
uid_or_username
290+
.parse::<u32>()
291+
.or_else(|_| usr2uid(uid_or_username))
292+
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid user name"))
293+
}
294+
295+
fn parse_gid_or_group_name(gid_or_group_name: &str) -> io::Result<u32> {
296+
gid_or_group_name
297+
.parse::<u32>()
298+
.or_else(|_| grp2gid(gid_or_group_name))
299+
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid group name"))
300+
}
301+
247302
#[allow(clippy::cognitive_complexity)]
248303
pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
249304
vec![
@@ -258,9 +313,9 @@ pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
258313
// arg!(-g --pgroup <PGID> "match listed process group IDs")
259314
// .value_delimiter(',')
260315
// .value_parser(clap::value_parser!(u64)),
261-
// arg!(-G --group <GID> "match real group IDs")
262-
// .value_delimiter(',')
263-
// .value_parser(clap::value_parser!(u64)),
316+
arg!(-G --group <GID> "match real group IDs")
317+
.value_delimiter(',')
318+
.value_parser(parse_gid_or_group_name),
264319
arg!(-i --"ignore-case" "match case insensitively"),
265320
arg!(-n --newest "select most recently started")
266321
.group("oldest_newest_inverse"),
@@ -277,12 +332,12 @@ pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
277332
arg!(--signal <sig> "signal to send (either number or name)")
278333
.default_value("SIGTERM"),
279334
arg!(-t --terminal <tty> "match by controlling terminal").value_delimiter(','),
280-
// arg!(-u --euid <ID> "match by effective IDs")
281-
// .value_delimiter(',')
282-
// .value_parser(clap::value_parser!(u64)),
283-
// arg!(-U --uid <ID> "match by real IDs")
284-
// .value_delimiter(',')
285-
// .value_parser(clap::value_parser!(u64)),
335+
arg!(-u --euid <ID> "match by effective IDs")
336+
.value_delimiter(',')
337+
.value_parser(parse_uid_or_username),
338+
arg!(-U --uid <ID> "match by real IDs")
339+
.value_delimiter(',')
340+
.value_parser(parse_uid_or_username),
286341
arg!(-x --exact "match exactly with the command name"),
287342
// arg!(-F --pidfile <file> "read PIDs from file"),
288343
// arg!(-L --logpidfile "fail if PID file is not locked"),

src/uu/pidwait/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ categories = ["command-line-utilities"]
1414

1515
[dependencies]
1616
nix = { workspace = true }
17-
uucore = { workspace = true }
17+
uucore = { workspace = true, features = ["entries"] }
1818
clap = { workspace = true }
1919
regex = { workspace = true }
2020
uu_pgrep = { path = "../pgrep" }

src/uu/pkill/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ categories = ["command-line-utilities"]
1313

1414

1515
[dependencies]
16-
uucore = { workspace = true }
16+
uucore = { workspace = true, features = ["entries"] }
1717
clap = { workspace = true }
1818
walkdir = { workspace = true }
1919
regex = { workspace = true }

tests/by-util/test_pgrep.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,3 +391,32 @@ fn test_too_long_pattern() {
391391
.code_is(1)
392392
.stderr_contains("pattern that searches for process name longer than 15 characters will result in zero matches");
393393
}
394+
395+
#[test]
396+
#[cfg(target_os = "linux")]
397+
fn test_invalid_username() {
398+
new_ucmd!()
399+
.arg("--uid=DOES_NOT_EXIST")
400+
.fails()
401+
.code_is(1)
402+
.stderr_contains("invalid user name");
403+
}
404+
405+
#[test]
406+
#[cfg(target_os = "linux")]
407+
fn test_invalid_group_name() {
408+
new_ucmd!()
409+
.arg("--group=DOES_NOT_EXIST")
410+
.fails()
411+
.code_is(1)
412+
.stderr_contains("invalid group name");
413+
}
414+
415+
#[test]
416+
#[cfg(target_os = "linux")]
417+
fn test_current_user() {
418+
new_ucmd!()
419+
.arg("-U")
420+
.arg(uucore::process::getuid().to_string())
421+
.succeeds();
422+
}

0 commit comments

Comments
 (0)