use std::{
env,
path::{Component, Path, PathBuf},
};
pub(crate) fn find_on_path(program: &str) -> Option<PathBuf> {
let mut components = Path::new(program).components();
if !matches!(components.next(), Some(Component::Normal(_))) || components.next().is_some() {
return None;
}
let paths = env::var_os("PATH")?;
find_in(program, env::split_paths(&paths))
}
fn find_in(program: &str, directories: impl IntoIterator<Item = PathBuf>) -> Option<PathBuf> {
directories
.into_iter()
.map(|directory| executable_path(&directory, program))
.find(|candidate| is_executable_file(candidate))
}
#[cfg(windows)]
fn executable_path(directory: &Path, program: &str) -> PathBuf {
let mut path = directory.join(program);
if !program.contains('.') {
path.set_extension("exe");
}
path
}
#[cfg(not(windows))]
fn executable_path(directory: &Path, program: &str) -> PathBuf {
directory.join(program)
}
#[cfg(unix)]
fn is_executable_file(path: &Path) -> bool {
use std::{ffi::CString, os::unix::ffi::OsStrExt};
if !path.is_file() {
return false;
}
let Ok(path) = CString::new(path.as_os_str().as_bytes()) else {
return false;
};
unsafe { libc::faccessat(libc::AT_FDCWD, path.as_ptr(), libc::X_OK, libc::AT_EACCESS) == 0 }
}
#[cfg(not(unix))]
fn is_executable_file(path: &Path) -> bool {
path.is_file()
}
#[cfg(test)]
#[path = "executable_tests.rs"]
mod tests;