#[cfg(windows)]
extern crate winapi;
use crate::apiset;
use crate::common::LookupError;
#[cfg(windows)]
use crate::knowndlls;
use fs_err as fs;
use std::collections::HashMap;
#[cfg(windows)]
use std::ffi::OsString;
#[cfg(windows)]
use std::os::windows::ffi::OsStringExt;
use std::path::{Path, PathBuf};
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct KnownDLLList {
pub entries: HashMap<String, PathBuf>,
}
impl KnownDLLList {
pub fn search_dll_in_known_dlls(&self, library: &str) -> Result<Option<PathBuf>, LookupError> {
if let Some(lp) = self.entries.get(&library.to_ascii_lowercase()) {
Ok(Some(lp.clone()))
} else {
Ok(None)
}
}
}
#[derive(Debug, Clone)]
pub struct WindowsSystem {
pub safe_dll_search_mode_on: Option<bool>,
pub apiset_map: Option<apiset::ApisetMap>,
pub known_dlls: Option<KnownDLLList>,
pub win_dir: PathBuf,
pub sys_dir: PathBuf,
pub system_path: Option<Vec<PathBuf>>,
}
impl WindowsSystem {
#[cfg(windows)]
pub fn current() -> Result<Self, LookupError> {
let win_dir = get_windows_directory()?;
let sys_dir = get_system_directory()?;
let apiset = match apiset::parse_apiset(sys_dir.join("apisetschema.dll")) {
Ok(apiset) => Some(apiset),
Err(e) => {
eprintln!("{:?}", e);
None
}
};
let path_str = std::env::var("PATH");
let path = path_str.map(|s| s.split(";")
.filter_map(|subs| fs::canonicalize(subs).ok())
.collect())
.ok();
let known_dlls = knowndlls::get_known_dlls().ok().map(|v| KnownDLLList {
entries: v
.iter()
.map(|kd| (kd.to_lowercase(), sys_dir.join(kd)))
.collect(),
});
Ok(Self {
safe_dll_search_mode_on: None,
apiset_map: apiset,
known_dlls,
win_dir,
sys_dir,
system_path: path,
})
}
#[cfg(not(windows))]
pub fn from_exe_location<P: AsRef<Path>>(p: P) -> Result<Option<Self>, LookupError> {
if let Some(root) = Self::find_root(&p) {
Ok(Self::from_root(root))
} else {
Ok(None)
}
}
#[cfg(not(windows))]
fn find_root<P: AsRef<Path>>(p: P) -> Option<PathBuf> {
for a in p.as_ref().parent()?.ancestors() {
if Self::from_root(a).is_some() {
return Some(a.to_owned());
}
}
None
}
pub fn from_root<P: AsRef<Path>>(root_path: P) -> Option<Self> {
let win_dir = root_path.as_ref().join("Windows");
let sys_dir = win_dir.join("System32");
if sys_dir.exists() {
Some(Self {
safe_dll_search_mode_on: None,
apiset_map: apiset::parse_apiset(sys_dir.join("apisetschema.dll")).ok(),
known_dlls: None,
win_dir,
sys_dir,
system_path: None,
})
} else {
None
}
}
}
impl PartialEq for WindowsSystem {
fn eq(&self, other: &Self) -> bool {
self.sys_dir == other.sys_dir
&& self.win_dir == other.win_dir
&& self.safe_dll_search_mode_on == other.safe_dll_search_mode_on
&& self.known_dlls == other.known_dlls
&& self.system_path == other.system_path
}
}
#[cfg(windows)]
fn get_winapi_directory(
a: unsafe extern "system" fn(
winapi::um::winnt::LPWSTR,
winapi::shared::minwindef::UINT,
) -> winapi::shared::minwindef::UINT,
) -> Result<PathBuf, std::io::Error> {
use std::io::Error;
const BFR_SIZE: usize = 512;
let mut bfr: [u16; BFR_SIZE] = [0; BFR_SIZE];
let ret: u32 = unsafe { a(bfr.as_mut_ptr(), BFR_SIZE as u32) };
if ret == 0 {
Err(Error::last_os_error())
} else {
let valid_bfr = &bfr[..ret as usize];
fs::canonicalize(OsString::from_wide(valid_bfr))
}
}
#[cfg(windows)]
fn get_system_directory() -> Result<PathBuf, std::io::Error> {
get_winapi_directory(winapi::um::sysinfoapi::GetSystemDirectoryW)
}
#[cfg(windows)]
fn get_windows_directory() -> Result<PathBuf, std::io::Error> {
get_winapi_directory(winapi::um::sysinfoapi::GetWindowsDirectoryW)
}
pub(crate) struct WinFileSystemCache {
files_in_dirs: HashMap<String, HashMap<String, PathBuf>>,
}
impl WinFileSystemCache {
pub(crate) fn new() -> Self {
Self {
files_in_dirs: HashMap::new(),
}
}
pub(crate) fn test_file_in_folder_case_insensitive<P: AsRef<Path>, Q: AsRef<Path>>(
&mut self,
filename: P,
folder: Q,
) -> Result<Option<PathBuf>, LookupError> {
let folder_str: String = folder
.as_ref()
.to_str()
.ok_or_else(|| {
LookupError::ScanError(format!(
"Could not scan directory {:?}",
&folder.as_ref().to_str()
))
})?
.to_owned();
if !self.files_in_dirs.contains_key(&folder_str) {
self.scan_folder(&folder)?;
}
let dir = self.files_in_dirs.get(&folder_str).ok_or_else(|| {
LookupError::ScanError(format!(
"Could not scan directory {:?}",
&folder.as_ref().to_str()
))
})?;
Ok(dir
.get(&filename.as_ref().to_str().unwrap().to_lowercase())
.map(|p| folder.as_ref().join(p)))
}
pub(crate) fn scan_folder<P: AsRef<Path>>(&mut self, folder: P) -> Result<(), LookupError> {
let folder_str: String = folder
.as_ref()
.to_str()
.ok_or_else(|| {
LookupError::ScanError(format!(
"Could not scan directory {:?}",
&folder.as_ref().to_str()
))
})?
.to_owned();
if let std::collections::hash_map::Entry::Vacant(e) = self.files_in_dirs.entry(folder_str) {
let matching_entries: HashMap<String, PathBuf> = fs::read_dir(folder.as_ref())?
.filter_map(|entry| entry.ok())
.filter(|entry| entry.metadata().map_or_else(|_| false, |m| m.is_file()))
.filter_map(|entry| {
entry
.file_name()
.to_str()
.map(|s| (s.to_lowercase(), entry.file_name().into()))
})
.collect();
e.insert(matching_entries);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::common::LookupError;
use crate::system::WinFileSystemCache;
#[cfg(windows)]
#[test]
fn context_win10() -> Result<(), LookupError> {
use super::WindowsSystem;
use fs_err as fs;
let ctx = WindowsSystem::current()?;
assert_eq!(ctx.win_dir, fs::canonicalize("C:\\Windows")?);
assert_eq!(ctx.sys_dir, fs::canonicalize("C:\\Windows\\System32")?);
let user_path = ctx.system_path;
assert!(user_path.is_some());
assert!(user_path
.unwrap()
.contains(&fs::canonicalize("C:\\Windows")?));
Ok(())
}
#[test]
fn fscache() -> Result<(), LookupError> {
let d = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let test_file_path =
d.join("test_data/test_project1/DepRunTest/build-same-output/bin/Debug/DepRunTest.exe");
assert!(test_file_path.exists());
let folder = std::fs::canonicalize(test_file_path.parent().unwrap())?;
let mut fscache = WinFileSystemCache::new();
let expected_res = Some(folder.join("DepRunTest.exe"));
assert_eq!(
fscache.test_file_in_folder_case_insensitive("depruntest.exe", &folder)?,
expected_res
);
assert_eq!(
fscache.test_file_in_folder_case_insensitive("Depruntest.exe", &folder)?,
expected_res
);
assert_eq!(
fscache.test_file_in_folder_case_insensitive("somerandomstring.txt", &folder)?,
None
);
Ok(())
}
}