1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
use std::{env, ffi, fs, path};
use std::os::unix::fs::PermissionsExt;
extern crate libc;
pub fn which<S: AsRef<ffi::OsStr>>(name: S) -> Option<path::PathBuf> {
let name: &ffi::OsStr = name.as_ref();
let var = match env::var_os("PATH") {
Some(var) => var,
None => return None,
};
let paths_iter = env::split_paths(&var);
let dirs_iter = paths_iter.filter_map(|path| fs::read_dir(path).ok());
for dir in dirs_iter {
let mut matches_iter = dir.filter_map(|file| file.ok())
.filter(|file| file.file_name() == name)
.filter(is_executable);
if let Some(file) = matches_iter.next() {
return Some(file.path());
}
}
None
}
fn is_executable(file: &fs::DirEntry) -> bool {
let file_metadata = match file.path().metadata() {
Ok(metadata) => metadata,
Err(..) => return false,
};
let file_path = match file.path()
.to_str()
.and_then(|p| ffi::CString::new(p).ok()) {
Some(path) => path,
None => return false,
};
let is_executable_by_user = unsafe {
libc::access(file_path.into_raw(), libc::X_OK) == libc::EXIT_SUCCESS
};
static EXECUTABLE_FLAGS: u32 =
(libc::S_IEXEC | libc::S_IXGRP | libc::S_IXOTH) as u32;
let has_executable_flag =
file_metadata.permissions().mode() & EXECUTABLE_FLAGS != 0;
is_executable_by_user && has_executable_flag && file_metadata.is_file()
}
#[cfg(test)]
mod tests {
use std::path;
use super::which;
#[test]
fn test_sh() {
let expected = path::PathBuf::from("/bin/sh");
let actual = which("sh");
assert_eq!(Some(expected), actual);
}
#[test]
fn test_none() {
let actual = which("foofoofoobar");
assert_eq!(None, actual);
}
}