diff --git a/src/uu/pidof/src/pidof.rs b/src/uu/pidof/src/pidof.rs index 6515298..898ec1a 100644 --- a/src/uu/pidof/src/pidof.rs +++ b/src/uu/pidof/src/pidof.rs @@ -49,18 +49,33 @@ fn match_process_name( process: &mut ProcessInformation, name_to_match: &str, with_workers: bool, + match_scripts: bool, ) -> bool { let binding = process.cmdline.split(' ').collect::>(); - let mut path = binding.first().unwrap().to_string(); + let path = binding.first().unwrap().to_string(); if path.is_empty() { if !with_workers { return false; } - path.clone_from(&process.status()["Name"]); + return process.name().unwrap() == name_to_match; }; - PathBuf::from(path).file_name().unwrap().to_str().unwrap() == name_to_match + if PathBuf::from(path).file_name().unwrap().to_str().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]) + .file_name() + .map(|f| f.to_str().unwrap()) + .is_some_and(|f| f == name_to_match && f.starts_with(&process.name().unwrap())); + } + + false } fn collect_matched_pids(matches: &ArgMatches) -> Vec { @@ -70,6 +85,7 @@ fn collect_matched_pids(matches: &ArgMatches) -> Vec { .cloned() .collect(); let with_workers = matches.get_flag("with-workers"); + let match_scripts = matches.get_flag("x"); let collected = walk_process().collect::>(); let arg_omit_pid = matches @@ -83,7 +99,8 @@ fn collect_matched_pids(matches: &ArgMatches) -> Vec { .flat_map(|program| { let mut processed = Vec::new(); for mut process in collected.clone() { - let contains = match_process_name(&mut process, &program, with_workers); + let contains = + match_process_name(&mut process, &program, with_workers, match_scripts); let should_omit = arg_omit_pid.contains(&process.pid); if contains && !should_omit { @@ -184,10 +201,10 @@ pub fn uu_app() -> Command { .help("Show kernel worker threads as well") .action(ArgAction::SetTrue), ) - // .arg( - // Arg::new("x") - // .short('x') - // .help("Return PIDs of shells running scripts with a matching name") - // .action(ArgAction::SetTrue), - // ) + .arg( + Arg::new("x") + .short('x') + .help("Return PIDs of shells running scripts with a matching name") + .action(ArgAction::SetTrue), + ) } diff --git a/tests/by-util/test_pidof.rs b/tests/by-util/test_pidof.rs index 08f319b..c9083e0 100644 --- a/tests/by-util/test_pidof.rs +++ b/tests/by-util/test_pidof.rs @@ -111,3 +111,40 @@ fn test_threads() { .join() .unwrap(); } + +#[test] +#[cfg(target_os = "linux")] +fn test_script() { + use std::os::unix::fs::PermissionsExt; + + let temp_dir = tempfile::tempdir().unwrap(); + let script_path = temp_dir + .path() + .join("dummy_test_script_with_very_long_name"); + std::fs::write(&script_path, "#!/bin/sh\nsleep 2").unwrap(); + std::fs::set_permissions(&script_path, std::fs::Permissions::from_mode(0o755)).unwrap(); + + let mut directly_executed_child = std::process::Command::new(&script_path).spawn().unwrap(); + new_ucmd!() + .arg("dummy_test_script_with_very_long_name") + .fails(); + new_ucmd!() + .args(&["-x", "dummy_test_script_with_very_long_name"]) + .succeeds() + .stdout_contains(directly_executed_child.id().to_string()); + directly_executed_child.kill().unwrap(); + directly_executed_child.wait().unwrap(); + + let mut executed_via_sh_child = std::process::Command::new("/bin/sh") + .arg(&script_path) + .spawn() + .unwrap(); + new_ucmd!() + .arg("dummy_test_script_with_very_long_name") + .fails(); + new_ucmd!() + .args(&["-x", "dummy_test_script_with_very_long_name"]) + .fails(); + executed_via_sh_child.kill().unwrap(); + executed_via_sh_child.wait().unwrap(); +}