uv_fs/
which.rs

1use std::path::Path;
2
3#[cfg(windows)]
4#[allow(unsafe_code)] // We need to do an FFI call through the windows-* crates.
5fn get_binary_type(path: &Path) -> windows::core::Result<u32> {
6    use std::os::windows::ffi::OsStrExt;
7    use windows::Win32::Storage::FileSystem::GetBinaryTypeW;
8    use windows::core::PCWSTR;
9
10    // References:
11    // https://github.com/denoland/deno/blob/01a6379505712be34ebf2cdc874fa7f54a6e9408/runtime/permissions/which.rs#L131-L154
12    // https://github.com/conradkleinespel/rooster/blob/afa78dc9918535752c4af59d2f812197ad754e5a/src/quale.rs#L51-L77
13    let mut binary_type = 0u32;
14    let name = path
15        .as_os_str()
16        .encode_wide()
17        .chain(Some(0))
18        .collect::<Vec<u16>>();
19    // SAFETY: winapi call
20    unsafe { GetBinaryTypeW(PCWSTR(name.as_ptr()), &raw mut binary_type)? };
21    Ok(binary_type)
22}
23
24/// Check whether a path in PATH is a valid executable.
25///
26/// Derived from `which`'s `Checker`.
27pub fn is_executable(path: &Path) -> bool {
28    #[cfg(any(unix, target_os = "wasi", target_os = "redox"))]
29    {
30        if rustix::fs::access(path, rustix::fs::Access::EXEC_OK).is_err() {
31            return false;
32        }
33    }
34
35    #[cfg(target_os = "windows")]
36    {
37        let Ok(file_type) = fs_err::symlink_metadata(path).map(|metadata| metadata.file_type())
38        else {
39            return false;
40        };
41        if !file_type.is_file() && !file_type.is_symlink() {
42            return false;
43        }
44        if path.extension().is_none() && get_binary_type(path).is_err() {
45            return false;
46        }
47    }
48
49    #[cfg(not(target_os = "windows"))]
50    {
51        use std::os::unix::fs::PermissionsExt;
52
53        if !fs_err::metadata(path)
54            .map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
55            .unwrap_or(false)
56        {
57            return false;
58        }
59    }
60
61    true
62}