Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions src/uu/pidof/src/pidof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::fs;
use std::path::PathBuf;

use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
Expand Down Expand Up @@ -50,8 +51,16 @@ fn match_process_name(
with_workers: bool,
match_scripts: bool,
) -> bool {
let binding = process.cmdline.split(' ').collect::<Vec<_>>();
let path = binding.first().unwrap().to_string();
// `cmdline` has the arguments joined with spaces, so read them again to
// get argv[0] as it is.
let Ok(raw) = fs::read(format!("/proc/{}/cmdline", process.pid)) else {
return false;
};
let binding = raw
.split(|&b| b == 0)
.map(String::from_utf8_lossy)
.collect::<Vec<_>>();
let path = binding.first().map(|it| it.to_string()).unwrap_or_default();

if path.is_empty() {
if !with_workers {
Expand All @@ -60,15 +69,32 @@ fn match_process_name(
return process.name().unwrap() == name_to_match;
};

if PathBuf::from(path).file_name().unwrap().to_str().unwrap() == name_to_match {
if PathBuf::from(&path).file_name().and_then(|f| f.to_str()) == Some(name_to_match) {
return true;
}

// Like procps-ng, also match the executable, so a process started with
// another argv[0] (`exec -a`) is still found, also by the 15-byte name
// the kernel cuts it to.
if let Ok(exe) = fs::read_link(format!("/proc/{}/exe", process.pid)) {
if let Some(exe) = exe.file_name().and_then(|f| f.to_str()) {
if exe == name_to_match || exe.as_bytes().get(..15) == Some(name_to_match.as_bytes()) {
return true;
}
}
}

// A process that rewrites its title, like "sshd: /usr/sbin/sshd -D", is
// matched by its process name too.
if path.contains(' ') && process.name().unwrap() == name_to_match {
return true;
}

// When a script (ie. file starting with e.g. #!/bin/sh) is run like `./script.sh`, then
// its cmdline will look like `/bin/sh ./script.sh` but its .name() will be `script.sh`.
// As name() gets truncated to 15 characters, the original pidof seems to always do a prefix match.
if match_scripts && binding.len() > 1 {
return PathBuf::from(binding[1])
return PathBuf::from(binding[1].as_ref())
.file_name()
.map(|f| f.to_str().unwrap())
.is_some_and(|f| f == name_to_match && f.starts_with(&process.name().unwrap()));
Expand Down
43 changes: 43 additions & 0 deletions tests/by-util/test_pidof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,46 @@ fn test_script() {
executed_via_sh_child.kill().unwrap();
executed_via_sh_child.wait().unwrap();
}

#[test]
#[cfg(target_os = "linux")]
fn test_process_with_another_argv0() {
use std::os::unix::process::CommandExt;

// A copy of the shell under its own name, so the executable and the
// process name are known.
let temp_dir = tempfile::tempdir().unwrap();
let program = temp_dir.path().join("pidoftitletest");
std::fs::copy("/bin/sh", &program).unwrap();

for argv0 in ["pidoftitletest: worker process", "another-argv0"] {
// "; :" keeps the shell from replacing itself with sleep. Other tests
// forking while the copy was open for writing make exec fail with
// ETXTBSY for a moment, so retry that.
let mut child = (0..100)
.find_map(|_| {
match std::process::Command::new(&program)
.arg0(argv0)
.args(["-c", "sleep 5; :"])
.spawn()
{
Err(e) if e.kind() == std::io::ErrorKind::ExecutableFileBusy => {
std::thread::sleep(std::time::Duration::from_millis(10));
None
}
result => Some(result.unwrap()),
}
})
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(200));

new_ucmd!()
.arg("pidoftitletest")
.succeeds()
.stdout_contains(child.id().to_string());
new_ucmd!().arg("pidoftitletest:").fails();

child.kill().unwrap();
child.wait().unwrap();
}
}
Loading