Skip to content

Commit d53660d

Browse files
authored
Merge pull request #421 from dezgeg/logpidfile
pgrep/pkill/pidwait: Implement --logpidfile (sic)
2 parents 259e68d + dd10be1 commit d53660d

File tree

2 files changed

+120
-7
lines changed

2 files changed

+120
-7
lines changed

src/uu/pgrep/src/process_matcher.rs

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

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

8-
use std::fs;
98
use std::hash::Hash;
9+
#[cfg(unix)]
10+
use std::os::fd::AsRawFd;
1011
use std::{collections::HashSet, io};
1112

1213
use clap::{arg, Arg, ArgAction, ArgMatches};
@@ -49,6 +50,7 @@ pub struct Settings {
4950
pub threads: bool,
5051

5152
pub pidfile: Option<String>,
53+
pub logpidfile: bool,
5254
}
5355

5456
pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
@@ -110,6 +112,7 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
110112
.map(|groups| groups.cloned().collect()),
111113
threads: false,
112114
pidfile: matches.get_one::<String>("pidfile").cloned(),
115+
logpidfile: matches.get_flag("logpidfile"),
113116
};
114117

115118
if !settings.newest
@@ -208,7 +211,7 @@ fn collect_matched_pids(settings: &Settings) -> UResult<Vec<ProcessInformation>>
208211
let pid_from_pidfile = settings
209212
.pidfile
210213
.as_ref()
211-
.map(|filename| read_pidfile(filename))
214+
.map(|filename| read_pidfile(filename, settings.logpidfile))
212215
.transpose()?;
213216

214217
for mut pid in pids {
@@ -392,7 +395,7 @@ fn parse_gid_or_group_name(gid_or_group_name: &str) -> io::Result<u32> {
392395
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid group name"))
393396
}
394397

395-
pub fn parse_pidfile_content(content: &str) -> Option<i64> {
398+
fn parse_pidfile_content(content: &str) -> Option<i64> {
396399
let re = Regex::new(r"(?-m)^[[:blank:]]*(-?[0-9]+)(?:\s|$)").unwrap();
397400
re.captures(content)?.get(1)?.as_str().parse::<i64>().ok()
398401
}
@@ -411,8 +414,48 @@ fn test_parse_pidfile_content_valid() {
411414
assert_eq!(parse_pidfile_content("\n123\n"), None);
412415
}
413416

414-
pub fn read_pidfile(filename: &str) -> UResult<i64> {
415-
let content = fs::read_to_string(filename)
417+
#[cfg(unix)]
418+
fn is_locked(file: &std::fs::File) -> bool {
419+
// On Linux, fcntl and flock locks are independent, so need to check both
420+
let mut flock_struct = uucore::libc::flock {
421+
l_type: uucore::libc::F_RDLCK as uucore::libc::c_short,
422+
l_whence: uucore::libc::SEEK_SET as uucore::libc::c_short,
423+
l_start: 0,
424+
l_len: 0,
425+
l_pid: 0,
426+
};
427+
let fd = file.as_raw_fd();
428+
let result = unsafe { uucore::libc::fcntl(fd, uucore::libc::F_GETLK, &mut flock_struct) };
429+
if result == 0 && flock_struct.l_type != uucore::libc::F_UNLCK as uucore::libc::c_short {
430+
return true;
431+
}
432+
433+
let result = unsafe { uucore::libc::flock(fd, uucore::libc::LOCK_SH | uucore::libc::LOCK_NB) };
434+
if result == -1 && std::io::Error::last_os_error().kind() == std::io::ErrorKind::WouldBlock {
435+
return true;
436+
}
437+
438+
false
439+
}
440+
441+
#[cfg(not(unix))]
442+
fn is_locked(_file: &std::fs::File) -> bool {
443+
// Dummy implementation just to make it compile
444+
false
445+
}
446+
447+
fn read_pidfile(filename: &str, check_locked: bool) -> UResult<i64> {
448+
let file = std::fs::File::open(filename)
449+
.map_err(|e| USimpleError::new(1, format!("Failed to open pidfile {}: {}", filename, e)))?;
450+
451+
if check_locked && !is_locked(&file) {
452+
return Err(USimpleError::new(
453+
1,
454+
format!("Pidfile {} is not locked", filename),
455+
));
456+
}
457+
458+
let content = std::fs::read_to_string(filename)
416459
.map_err(|e| USimpleError::new(1, format!("Failed to read pidfile {}: {}", filename, e)))?;
417460

418461
let pid = parse_pidfile_content(&content)
@@ -462,7 +505,7 @@ pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec<Arg> {
462505
.value_parser(parse_uid_or_username),
463506
arg!(-x --exact "match exactly with the command name"),
464507
arg!(-F --pidfile <file> "read PIDs from file"),
465-
// arg!(-L --logpidfile "fail if PID file is not locked"),
508+
arg!(-L --logpidfile "fail if PID file is not locked"),
466509
arg!(-r --runstates <state> "match runstates [D,S,Z,...]"),
467510
// arg!(-A --"ignore-ancestors" "exclude our ancestors from results"),
468511
arg!(--cgroup <grp> "match by cgroup v2 names").value_delimiter(','),

tests/by-util/test_pgrep.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,5 +601,75 @@ fn test_pidfile_nonexistent_file() {
601601
.arg("--pidfile")
602602
.arg("/nonexistent/file")
603603
.fails()
604-
.stderr_contains("Failed to read pidfile");
604+
.stderr_contains("Failed to open pidfile");
605+
}
606+
607+
#[test]
608+
#[cfg(target_os = "linux")]
609+
fn test_pidfile_not_locked() {
610+
let temp_file = tempfile::NamedTempFile::new().unwrap();
611+
std::fs::write(temp_file.path(), "1").unwrap();
612+
613+
new_ucmd!()
614+
.arg("--logpidfile")
615+
.arg("--pidfile")
616+
.arg(temp_file.path())
617+
.fails()
618+
.stderr_matches(&Regex::new("Pidfile .* is not locked").unwrap());
619+
}
620+
621+
#[test]
622+
#[cfg(target_os = "linux")]
623+
fn test_pidfile_flock_locked() {
624+
let temp_file = tempfile::NamedTempFile::new().unwrap();
625+
std::fs::write(temp_file.path(), "1").unwrap();
626+
627+
// spawn a flock process that locks the file
628+
let mut flock_process = Command::new("flock")
629+
.arg(temp_file.path())
630+
.arg("sleep")
631+
.arg("2")
632+
.spawn()
633+
.unwrap();
634+
635+
new_ucmd!()
636+
.arg("--logpidfile")
637+
.arg("--pidfile")
638+
.arg(temp_file.path())
639+
.succeeds()
640+
.stdout_is("1\n");
641+
642+
flock_process.kill().unwrap();
643+
flock_process.wait().unwrap();
644+
}
645+
646+
#[test]
647+
#[cfg(target_os = "linux")]
648+
fn test_pidfile_fcntl_locked() {
649+
if uutests::util::is_ci() {
650+
// CI runner doesn't support flock --fcntl
651+
return;
652+
}
653+
654+
let temp_file = tempfile::NamedTempFile::new().unwrap();
655+
std::fs::write(temp_file.path(), "1").unwrap();
656+
657+
// spawn a flock process that locks the file
658+
let mut flock_process = Command::new("flock")
659+
.arg("--fcntl")
660+
.arg(temp_file.path())
661+
.arg("sleep")
662+
.arg("2")
663+
.spawn()
664+
.unwrap();
665+
666+
new_ucmd!()
667+
.arg("--logpidfile")
668+
.arg("--pidfile")
669+
.arg(temp_file.path())
670+
.succeeds()
671+
.stdout_is("1\n");
672+
673+
flock_process.kill().unwrap();
674+
flock_process.wait().unwrap();
605675
}

0 commit comments

Comments
 (0)