Skip to content

Commit 4bf3068

Browse files
authored
Merge pull request #419 from dezgeg/pidfile
pgrep/pkill/pidwait: Implement --pidfile
2 parents 6cdec94 + fffff64 commit 4bf3068

5 files changed

Lines changed: 115 additions & 11 deletions

File tree

src/uu/pgrep/src/pgrep.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
3232
settings.threads = matches.get_flag("lightweight");
3333

3434
// Collect pids
35-
let pids = process_matcher::find_matching_pids(&settings);
35+
let pids = process_matcher::find_matching_pids(&settings)?;
3636

3737
// Processing output
3838
let output = if matches.get_flag("count") {

src/uu/pgrep/src/process_matcher.rs

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

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

8+
use std::fs;
89
use std::hash::Hash;
910
use std::{collections::HashSet, io};
1011

@@ -46,6 +47,8 @@ pub struct Settings {
4647
pub session: Option<HashSet<u64>>,
4748
pub cgroup: Option<HashSet<String>>,
4849
pub threads: bool,
50+
51+
pub pidfile: Option<String>,
4952
}
5053

5154
pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
@@ -106,6 +109,7 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
106109
.get_many::<String>("cgroup")
107110
.map(|groups| groups.cloned().collect()),
108111
threads: false,
112+
pidfile: matches.get_one::<String>("pidfile").cloned(),
109113
};
110114

111115
if !settings.newest
@@ -121,6 +125,7 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
121125
&& settings.session.is_none()
122126
&& settings.cgroup.is_none()
123127
&& !settings.require_handler
128+
&& settings.pidfile.is_none()
124129
&& pattern.is_empty()
125130
{
126131
return Err(USimpleError::new(
@@ -142,13 +147,13 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
142147
Ok(settings)
143148
}
144149

145-
pub fn find_matching_pids(settings: &Settings) -> Vec<ProcessInformation> {
146-
let mut pids = collect_matched_pids(settings);
150+
pub fn find_matching_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>> {
151+
let mut pids = collect_matched_pids(settings)?;
147152
if pids.is_empty() {
148153
uucore::error::set_exit_code(1);
149-
pids
154+
Ok(pids)
150155
} else {
151-
process_flag_o_n(settings, &mut pids)
156+
Ok(process_flag_o_n(settings, &mut pids))
152157
}
153158
}
154159

@@ -189,7 +194,7 @@ fn any_matches<T: Eq + Hash>(optional_ids: &Option<HashSet<T>>, id: T) -> bool {
189194
}
190195

191196
/// Collect pids with filter construct from command line arguments
192-
fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
197+
fn collect_matched_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>> {
193198
// Filtration general parameters
194199
let filtered: Vec<ProcessInformation> = {
195200
let mut tmp_vec = Vec::new();
@@ -200,6 +205,11 @@ fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
200205
walk_process().collect::<Vec<_>>()
201206
};
202207
let our_pid = std::process::id() as usize;
208+
let pid_from_pidfile = settings
209+
.pidfile
210+
.as_ref()
211+
.map(|filename| read_pidfile(filename))
212+
.transpose()?;
203213

204214
for mut pid in pids {
205215
if pid.pid == our_pid {
@@ -267,6 +277,8 @@ fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
267277
#[cfg(not(unix))]
268278
let handler_matched = true;
269279

280+
let pidfile_matched = pid_from_pidfile.is_none_or(|p| p == pid.pid as i64);
281+
270282
if (run_state_matched
271283
&& pattern_matched
272284
&& tty_matched
@@ -276,7 +288,8 @@ fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
276288
&& session_matched
277289
&& cgroup_matched
278290
&& ids_matched
279-
&& handler_matched)
291+
&& handler_matched
292+
&& pidfile_matched)
280293
^ settings.inverse
281294
{
282295
tmp_vec.push(pid);
@@ -285,7 +298,7 @@ fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
285298
tmp_vec
286299
};
287300

288-
filtered
301+
Ok(filtered)
289302
}
290303

291304
/// Sorting pids for flag `-o` and `-n`.
@@ -379,6 +392,35 @@ fn parse_gid_or_group_name(gid_or_group_name: &str) -> io::Result<u32> {
379392
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid group name"))
380393
}
381394

395+
pub fn parse_pidfile_content(content: &str) -> Option<i64> {
396+
let re = Regex::new(r"(?-m)^[[:blank:]]*(-?[0-9]+)(?:\s|$)").unwrap();
397+
re.captures(content)?.get(1)?.as_str().parse::<i64>().ok()
398+
}
399+
400+
#[test]
401+
fn test_parse_pidfile_content_valid() {
402+
assert_eq!(parse_pidfile_content(" 1234"), Some(1234));
403+
assert_eq!(parse_pidfile_content("-5678 "), Some(-5678));
404+
assert_eq!(parse_pidfile_content(" 42\nfoo\n"), Some(42));
405+
assert_eq!(parse_pidfile_content("\t-99\tbar\n"), Some(-99));
406+
407+
assert_eq!(parse_pidfile_content(""), None);
408+
assert_eq!(parse_pidfile_content("abc"), None);
409+
assert_eq!(parse_pidfile_content("0x42"), None);
410+
assert_eq!(parse_pidfile_content("2.3"), None);
411+
assert_eq!(parse_pidfile_content("\n123\n"), None);
412+
}
413+
414+
pub fn read_pidfile(filename: &str) -> UResult<i64> {
415+
let content = fs::read_to_string(filename)
416+
.map_err(|e| USimpleError::new(1, format!("Failed to read pidfile {}: {}", filename, e)))?;
417+
418+
let pid = parse_pidfile_content(&content)
419+
.ok_or_else(|| USimpleError::new(1, format!("Pidfile {} not valid", filename)))?;
420+
421+
Ok(pid)
422+
}
423+
382424
#[allow(clippy::cognitive_complexity)]
383425
pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
384426
vec![
@@ -419,7 +461,7 @@ pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
419461
.value_delimiter(',')
420462
.value_parser(parse_uid_or_username),
421463
arg!(-x --exact "match exactly with the command name"),
422-
// arg!(-F --pidfile <file> "read PIDs from file"),
464+
arg!(-F --pidfile <file> "read PIDs from file"),
423465
// arg!(-L --logpidfile "fail if PID file is not locked"),
424466
arg!(-r --runstates <state> "match runstates [D,S,Z,...]"),
425467
// arg!(-A --"ignore-ancestors" "exclude our ancestors from results"),

src/uu/pidwait/src/pidwait.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
1818
let matches = uu_app().try_get_matches_from(args)?;
1919

2020
let settings = process_matcher::get_match_settings(&matches)?;
21-
let mut proc_infos = process_matcher::find_matching_pids(&settings);
21+
let mut proc_infos = process_matcher::find_matching_pids(&settings)?;
2222

2323
// For empty result
2424
if proc_infos.is_empty() {

src/uu/pkill/src/pkill.rs

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

5555
// Collect pids
56-
let pids = process_matcher::find_matching_pids(&settings);
56+
let pids = process_matcher::find_matching_pids(&settings)?;
5757

5858
// Send signal
5959
// TODO: Implement -q

tests/by-util/test_pgrep.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,65 @@ fn test_threads() {
541541
.join()
542542
.unwrap();
543543
}
544+
545+
#[test]
546+
#[cfg(target_os = "linux")]
547+
fn test_pidfile_match() {
548+
let temp_file = tempfile::NamedTempFile::new().unwrap();
549+
std::fs::write(temp_file.path(), "1\tfoo\n").unwrap();
550+
551+
new_ucmd!()
552+
.arg("--pidfile")
553+
.arg(temp_file.path())
554+
.succeeds()
555+
.stdout_is("1\n");
556+
}
557+
558+
#[test]
559+
#[cfg(target_os = "linux")]
560+
fn test_pidfile_no_match() {
561+
let temp_file = tempfile::NamedTempFile::new().unwrap();
562+
std::fs::write(temp_file.path(), " -1").unwrap();
563+
564+
new_ucmd!()
565+
.arg("--pidfile")
566+
.arg(temp_file.path())
567+
.fails()
568+
.no_output();
569+
}
570+
571+
#[test]
572+
#[cfg(target_os = "linux")]
573+
fn test_pidfile_invert() {
574+
let temp_file = tempfile::NamedTempFile::new().unwrap();
575+
std::fs::write(temp_file.path(), " -1").unwrap();
576+
577+
new_ucmd!()
578+
.arg("-v")
579+
.arg("--pidfile")
580+
.arg(temp_file.path())
581+
.succeeds();
582+
}
583+
584+
#[test]
585+
#[cfg(target_os = "linux")]
586+
fn test_pidfile_invalid_content() {
587+
let temp_file = tempfile::NamedTempFile::new().unwrap();
588+
std::fs::write(temp_file.path(), "0x1").unwrap();
589+
590+
new_ucmd!()
591+
.arg("--pidfile")
592+
.arg(temp_file.path())
593+
.fails()
594+
.stderr_matches(&Regex::new("Pidfile .* not valid").unwrap());
595+
}
596+
597+
#[test]
598+
#[cfg(target_os = "linux")]
599+
fn test_pidfile_nonexistent_file() {
600+
new_ucmd!()
601+
.arg("--pidfile")
602+
.arg("/nonexistent/file")
603+
.fails()
604+
.stderr_contains("Failed to read pidfile");
605+
}

0 commit comments

Comments
 (0)