diff --git a/src/uu/pgrep/src/process_matcher.rs b/src/uu/pgrep/src/process_matcher.rs index c362a7e5..430abf7f 100644 --- a/src/uu/pgrep/src/process_matcher.rs +++ b/src/uu/pgrep/src/process_matcher.rs @@ -5,8 +5,9 @@ // Common process matcher logic shared by pgrep, pkill and pidwait -use std::fs; use std::hash::Hash; +#[cfg(unix)] +use std::os::fd::AsRawFd; use std::{collections::HashSet, io}; use clap::{arg, Arg, ArgAction, ArgMatches}; @@ -49,6 +50,7 @@ pub struct Settings { pub threads: bool, pub pidfile: Option, + pub logpidfile: bool, } pub fn get_match_settings(matches: &ArgMatches) -> UResult { @@ -110,6 +112,7 @@ pub fn get_match_settings(matches: &ArgMatches) -> UResult { .map(|groups| groups.cloned().collect()), threads: false, pidfile: matches.get_one::("pidfile").cloned(), + logpidfile: matches.get_flag("logpidfile"), }; if !settings.newest @@ -208,7 +211,7 @@ fn collect_matched_pids(settings: &Settings) -> UResult> let pid_from_pidfile = settings .pidfile .as_ref() - .map(|filename| read_pidfile(filename)) + .map(|filename| read_pidfile(filename, settings.logpidfile)) .transpose()?; for mut pid in pids { @@ -392,7 +395,7 @@ fn parse_gid_or_group_name(gid_or_group_name: &str) -> io::Result { .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid group name")) } -pub fn parse_pidfile_content(content: &str) -> Option { +fn parse_pidfile_content(content: &str) -> Option { let re = Regex::new(r"(?-m)^[[:blank:]]*(-?[0-9]+)(?:\s|$)").unwrap(); re.captures(content)?.get(1)?.as_str().parse::().ok() } @@ -411,8 +414,48 @@ fn test_parse_pidfile_content_valid() { assert_eq!(parse_pidfile_content("\n123\n"), None); } -pub fn read_pidfile(filename: &str) -> UResult { - let content = fs::read_to_string(filename) +#[cfg(unix)] +fn is_locked(file: &std::fs::File) -> bool { + // On Linux, fcntl and flock locks are independent, so need to check both + let mut flock_struct = uucore::libc::flock { + l_type: uucore::libc::F_RDLCK as uucore::libc::c_short, + l_whence: uucore::libc::SEEK_SET as uucore::libc::c_short, + l_start: 0, + l_len: 0, + l_pid: 0, + }; + let fd = file.as_raw_fd(); + let result = unsafe { uucore::libc::fcntl(fd, uucore::libc::F_GETLK, &mut flock_struct) }; + if result == 0 && flock_struct.l_type != uucore::libc::F_UNLCK as uucore::libc::c_short { + return true; + } + + let result = unsafe { uucore::libc::flock(fd, uucore::libc::LOCK_SH | uucore::libc::LOCK_NB) }; + if result == -1 && std::io::Error::last_os_error().kind() == std::io::ErrorKind::WouldBlock { + return true; + } + + false +} + +#[cfg(not(unix))] +fn is_locked(_file: &std::fs::File) -> bool { + // Dummy implementation just to make it compile + false +} + +fn read_pidfile(filename: &str, check_locked: bool) -> UResult { + let file = std::fs::File::open(filename) + .map_err(|e| USimpleError::new(1, format!("Failed to open pidfile {}: {}", filename, e)))?; + + if check_locked && !is_locked(&file) { + return Err(USimpleError::new( + 1, + format!("Pidfile {} is not locked", filename), + )); + } + + let content = std::fs::read_to_string(filename) .map_err(|e| USimpleError::new(1, format!("Failed to read pidfile {}: {}", filename, e)))?; let pid = parse_pidfile_content(&content) @@ -462,7 +505,7 @@ pub fn clap_args(pattern_help: &'static str, enable_v_flag: bool) -> Vec { .value_parser(parse_uid_or_username), arg!(-x --exact "match exactly with the command name"), arg!(-F --pidfile "read PIDs from file"), - // arg!(-L --logpidfile "fail if PID file is not locked"), + arg!(-L --logpidfile "fail if PID file is not locked"), arg!(-r --runstates "match runstates [D,S,Z,...]"), // arg!(-A --"ignore-ancestors" "exclude our ancestors from results"), arg!(--cgroup "match by cgroup v2 names").value_delimiter(','), diff --git a/tests/by-util/test_pgrep.rs b/tests/by-util/test_pgrep.rs index 5f168d71..c25f9692 100644 --- a/tests/by-util/test_pgrep.rs +++ b/tests/by-util/test_pgrep.rs @@ -601,5 +601,75 @@ fn test_pidfile_nonexistent_file() { .arg("--pidfile") .arg("/nonexistent/file") .fails() - .stderr_contains("Failed to read pidfile"); + .stderr_contains("Failed to open pidfile"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_pidfile_not_locked() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + std::fs::write(temp_file.path(), "1").unwrap(); + + new_ucmd!() + .arg("--logpidfile") + .arg("--pidfile") + .arg(temp_file.path()) + .fails() + .stderr_matches(&Regex::new("Pidfile .* is not locked").unwrap()); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_pidfile_flock_locked() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + std::fs::write(temp_file.path(), "1").unwrap(); + + // spawn a flock process that locks the file + let mut flock_process = Command::new("flock") + .arg(temp_file.path()) + .arg("sleep") + .arg("2") + .spawn() + .unwrap(); + + new_ucmd!() + .arg("--logpidfile") + .arg("--pidfile") + .arg(temp_file.path()) + .succeeds() + .stdout_is("1\n"); + + flock_process.kill().unwrap(); + flock_process.wait().unwrap(); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_pidfile_fcntl_locked() { + if uutests::util::is_ci() { + // CI runner doesn't support flock --fcntl + return; + } + + let temp_file = tempfile::NamedTempFile::new().unwrap(); + std::fs::write(temp_file.path(), "1").unwrap(); + + // spawn a flock process that locks the file + let mut flock_process = Command::new("flock") + .arg("--fcntl") + .arg(temp_file.path()) + .arg("sleep") + .arg("2") + .spawn() + .unwrap(); + + new_ucmd!() + .arg("--logpidfile") + .arg("--pidfile") + .arg(temp_file.path()) + .succeeds() + .stdout_is("1\n"); + + flock_process.kill().unwrap(); + flock_process.wait().unwrap(); }