which/
checker.rs

1use crate::finder::Checker;
2use crate::{NonFatalError, NonFatalErrorHandler};
3use std::fs;
4use std::path::Path;
5
6pub struct ExecutableChecker;
7
8impl ExecutableChecker {
9    pub fn new() -> ExecutableChecker {
10        ExecutableChecker
11    }
12}
13
14impl Checker for ExecutableChecker {
15    #[cfg(any(unix, target_os = "wasi", target_os = "redox"))]
16    fn is_valid<F: NonFatalErrorHandler>(
17        &self,
18        path: &Path,
19        nonfatal_error_handler: &mut F,
20    ) -> bool {
21        use std::io;
22
23        use rustix::fs as rfs;
24        let ret = rfs::access(path, rfs::Access::EXEC_OK)
25            .map_err(|e| {
26                nonfatal_error_handler.handle(NonFatalError::Io(io::Error::from_raw_os_error(
27                    e.raw_os_error(),
28                )))
29            })
30            .is_ok();
31        #[cfg(feature = "tracing")]
32        tracing::trace!("{} EXEC_OK = {ret}", path.display());
33        ret
34    }
35
36    #[cfg(windows)]
37    fn is_valid<F: NonFatalErrorHandler>(
38        &self,
39        _path: &Path,
40        _nonfatal_error_handler: &mut F,
41    ) -> bool {
42        true
43    }
44}
45
46pub struct ExistedChecker;
47
48impl ExistedChecker {
49    pub fn new() -> ExistedChecker {
50        ExistedChecker
51    }
52}
53
54impl Checker for ExistedChecker {
55    #[cfg(target_os = "windows")]
56    fn is_valid<F: NonFatalErrorHandler>(
57        &self,
58        path: &Path,
59        nonfatal_error_handler: &mut F,
60    ) -> bool {
61        let ret = fs::symlink_metadata(path)
62            .map(|metadata| {
63                let file_type = metadata.file_type();
64                #[cfg(feature = "tracing")]
65                tracing::trace!(
66                    "{} is_file() = {}, is_symlink() = {}",
67                    path.display(),
68                    file_type.is_file(),
69                    file_type.is_symlink()
70                );
71                file_type.is_file() || file_type.is_symlink()
72            })
73            .map_err(|e| {
74                nonfatal_error_handler.handle(NonFatalError::Io(e));
75            })
76            .unwrap_or(false)
77            && (path.extension().is_some() || matches_arch(path, nonfatal_error_handler));
78        #[cfg(feature = "tracing")]
79        tracing::trace!(
80            "{} has_extension = {}, ExistedChecker::is_valid() = {ret}",
81            path.display(),
82            path.extension().is_some()
83        );
84        ret
85    }
86
87    #[cfg(not(target_os = "windows"))]
88    fn is_valid<F: NonFatalErrorHandler>(
89        &self,
90        path: &Path,
91        nonfatal_error_handler: &mut F,
92    ) -> bool {
93        let ret = fs::metadata(path).map(|metadata| metadata.is_file());
94        #[cfg(feature = "tracing")]
95        tracing::trace!("{} is_file() = {ret:?}", path.display());
96        match ret {
97            Ok(ret) => ret,
98            Err(e) => {
99                nonfatal_error_handler.handle(NonFatalError::Io(e));
100                false
101            }
102        }
103    }
104}
105
106#[cfg(target_os = "windows")]
107fn matches_arch<F: NonFatalErrorHandler>(path: &Path, nonfatal_error_handler: &mut F) -> bool {
108    use std::io;
109
110    let ret = winsafe::GetBinaryType(&path.display().to_string())
111        .map_err(|e| {
112            nonfatal_error_handler.handle(NonFatalError::Io(io::Error::from_raw_os_error(
113                e.raw() as i32
114            )))
115        })
116        .is_ok();
117    #[cfg(feature = "tracing")]
118    tracing::trace!("{} matches_arch() = {ret}", path.display());
119    ret
120}
121
122pub struct CompositeChecker {
123    existed_checker: ExistedChecker,
124    executable_checker: ExecutableChecker,
125}
126
127impl CompositeChecker {
128    pub fn new() -> CompositeChecker {
129        CompositeChecker {
130            executable_checker: ExecutableChecker::new(),
131            existed_checker: ExistedChecker::new(),
132        }
133    }
134}
135
136impl Checker for CompositeChecker {
137    fn is_valid<F: NonFatalErrorHandler>(
138        &self,
139        path: &Path,
140        nonfatal_error_handler: &mut F,
141    ) -> bool {
142        self.existed_checker.is_valid(path, nonfatal_error_handler)
143            && self
144                .executable_checker
145                .is_valid(path, nonfatal_error_handler)
146    }
147}