Skip to content

Commit 2fce243

Browse files
authored
Merge pull request #449 from dezgeg/env
pgrep/pidwait/pkill: Implement --env
2 parents 76b025b + fb0be28 commit 2fce243

4 files changed

Lines changed: 84 additions & 1 deletion

File tree

src/uu/pgrep/src/process.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,19 @@ impl ProcessInformation {
491491
self.cached_thread_ids = Some(Rc::clone(&result));
492492
Rc::clone(&result)
493493
}
494+
495+
pub fn env_vars(&self) -> Result<HashMap<String, String>, io::Error> {
496+
let content = fs::read_to_string(format!("/proc/{}/environ", self.pid))?;
497+
498+
let mut env_vars = HashMap::new();
499+
for entry in content.split('\0') {
500+
if let Some((key, value)) = entry.split_once('=') {
501+
env_vars.insert(key.to_string(), value.to_string());
502+
}
503+
}
504+
505+
Ok(env_vars)
506+
}
494507
}
495508
impl TryFrom<DirEntry> for ProcessInformation {
496509
type Error = io::Error;
@@ -692,4 +705,16 @@ mod tests {
692705
}
693706
}
694707
}
708+
709+
#[test]
710+
#[cfg(target_os = "linux")]
711+
fn test_environ() {
712+
let pid_entry = ProcessInformation::current_process_info().unwrap();
713+
let env_vars = pid_entry.env_vars().unwrap();
714+
715+
assert_eq!(
716+
*env_vars.get("HOME").unwrap(),
717+
std::env::var("HOME").unwrap()
718+
);
719+
}
695720
}

src/uu/pgrep/src/process_matcher.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 env: Option<HashSet<String>>,
5051
pub threads: bool,
5152

5253
pub pidfile: Option<String>,
@@ -111,6 +112,9 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
111112
cgroup: matches
112113
.get_many::<String>("cgroup")
113114
.map(|groups| groups.cloned().collect()),
115+
env: matches
116+
.get_many::<String>("env")
117+
.map(|env_vars| env_vars.cloned().collect()),
114118
threads: false,
115119
pidfile: matches.get_one::<String>("pidfile").cloned(),
116120
logpidfile: matches.get_flag("logpidfile"),
@@ -129,6 +133,7 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
129133
&& settings.pgroup.is_none()
130134
&& settings.session.is_none()
131135
&& settings.cgroup.is_none()
136+
&& settings.env.is_none()
132137
&& !settings.require_handler
133138
&& settings.pidfile.is_none()
134139
&& pattern.is_empty()
@@ -279,6 +284,22 @@ fn collect_matched_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>>
279284
pid.cgroup_v2_path().unwrap_or("/".to_string()),
280285
);
281286

287+
let env_matched = match &settings.env {
288+
Some(env_filters) => {
289+
let env_vars = pid.env_vars().unwrap_or_default();
290+
env_filters.iter().any(|filter| {
291+
if let Some((key, expected_value)) = filter.split_once('=') {
292+
// Match specific key=value pair
293+
env_vars.get(key) == Some(&expected_value.to_string())
294+
} else {
295+
// Match key existence only
296+
env_vars.contains_key(filter)
297+
}
298+
})
299+
}
300+
None => true,
301+
};
302+
282303
let ids_matched = any_matches(&settings.uid, pid.uid().unwrap())
283304
&& any_matches(&settings.euid, pid.euid().unwrap())
284305
&& any_matches(&settings.gid, pid.gid().unwrap());
@@ -311,6 +332,7 @@ fn collect_matched_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>>
311332
&& pgroup_matched
312333
&& session_matched
313334
&& cgroup_matched
335+
&& env_matched
314336
&& ids_matched
315337
&& handler_matched
316338
&& pidfile_matched)
@@ -530,6 +552,7 @@ pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
530552
arg!(-r --runstates <state> "match runstates [D,S,Z,...]"),
531553
arg!(-A --"ignore-ancestors" "exclude our ancestors from results"),
532554
arg!(--cgroup <grp> "match by cgroup v2 names").value_delimiter(','),
555+
arg!(--env <"name[=val],..."> "match on environment variable").value_delimiter(','),
533556
// arg!(--ns <PID> "match the processes that belong to the same namespace as <pid>"),
534557
// arg!(--nslist <ns> "list which namespaces will be considered for the --ns option.")
535558
// .value_delimiter(',')

src/uu/pidwait/src/pidwait.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
4848
}
4949

5050
pub fn uu_app() -> Command {
51-
Command::new(env!("CARGO_PKG_NAME"))
51+
Command::new(uucore::util_name())
5252
.version(crate_version!())
5353
.about(ABOUT)
5454
.override_usage(format_usage(USAGE))

tests/by-util/test_pgrep.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,3 +685,38 @@ fn test_ignore_ancestors() {
685685
.stdout_does_not_match(&Regex::new(&format!("(?m)^{our_pid}$")).unwrap())
686686
.stdout_does_not_match(&Regex::new("(?m)^1$").unwrap());
687687
}
688+
689+
#[test]
690+
#[cfg(target_os = "linux")]
691+
fn test_env_nonexistent() {
692+
new_ucmd!().arg("--env=NONEXISTENT").fails().code_is(1);
693+
}
694+
695+
#[test]
696+
#[cfg(target_os = "linux")]
697+
fn test_env_nonmatching_value() {
698+
new_ucmd!()
699+
.arg("--env=PATH=not_a_valid_PATH")
700+
.fails()
701+
.code_is(1);
702+
}
703+
704+
#[test]
705+
#[cfg(target_os = "linux")]
706+
fn test_env_key_match() {
707+
new_ucmd!().arg("--env=PATH").succeeds();
708+
}
709+
710+
#[test]
711+
#[cfg(target_os = "linux")]
712+
fn test_env_key_value_match() {
713+
let home = std::env::var("HOME").unwrap();
714+
new_ucmd!().arg(format!("--env=HOME={}", home)).succeeds();
715+
}
716+
717+
#[test]
718+
#[cfg(target_os = "linux")]
719+
fn test_env_multiple_filters() {
720+
// Multiple filters use OR logic
721+
new_ucmd!().arg("--env=PATH,NONEXISTENT").succeeds();
722+
}

0 commit comments

Comments
 (0)