use crate::common::LookupError;
use crate::query::LookupQuery;
use crate::system::{WinFileSystemCache, WindowsSystem};
#[cfg(windows)]
use fs_err as fs;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum LookupPathEntry {
KnownDLLs,
ExecutableDir(PathBuf),
ApiSet,
SystemDir(PathBuf),
WindowsDir(PathBuf),
WorkingDir(PathBuf),
SystemPath(PathBuf),
UserPath(PathBuf),
}
impl LookupPathEntry {
pub fn is_system(&self) -> bool {
matches!(
self,
Self::KnownDLLs | Self::ApiSet | Self::WindowsDir(_) | Self::SystemDir(_)
)
}
pub fn get_path(&self) -> Option<PathBuf> {
match self {
Self::KnownDLLs => None,
Self::ApiSet => None,
Self::ExecutableDir(p)
| Self::SystemDir(p)
| Self::WindowsDir(p)
| Self::WorkingDir(p)
| Self::SystemPath(p)
| Self::UserPath(p) => Some(p.clone()),
}
}
}
pub struct LookupResult {
pub location: LookupPathEntry,
pub fullpath: PathBuf,
}
pub struct LookupPath {
pub entries: Vec<LookupPathEntry>,
fs_cache: std::cell::RefCell<WinFileSystemCache>,
}
impl LookupPath {
pub fn deduce(query: &LookupQuery) -> Self {
let entries = if let Some(system) = query.system.as_ref() {
let system_entries = vec![
LookupPathEntry::SystemDir(system.sys_dir.clone()),
LookupPathEntry::WindowsDir(system.win_dir.clone()),
];
if system.safe_dll_search_mode_on.unwrap_or(true) {
[
vec![LookupPathEntry::KnownDLLs],
vec![LookupPathEntry::ApiSet],
vec![LookupPathEntry::ExecutableDir(query.target.app_dir.clone())],
system_entries,
vec![LookupPathEntry::WorkingDir(
query.target.working_dir.clone(),
)],
Self::system_path_entries(system),
Self::user_path_entries(query),
]
.concat()
} else {
[
vec![LookupPathEntry::KnownDLLs],
vec![LookupPathEntry::ApiSet],
vec![
LookupPathEntry::ExecutableDir(query.target.app_dir.clone()),
LookupPathEntry::WorkingDir(query.target.working_dir.clone()),
],
system_entries,
Self::system_path_entries(system),
Self::user_path_entries(query),
]
.concat()
}
} else {
[
vec![
LookupPathEntry::ExecutableDir(query.target.app_dir.clone()),
LookupPathEntry::WorkingDir(query.target.working_dir.clone()),
],
Self::user_path_entries(query),
]
.concat()
};
Self {
entries,
fs_cache: std::cell::RefCell::new(WinFileSystemCache::new()),
}
}
#[cfg(windows)]
fn dwp_string_to_context_entry(
s: &str,
q: &LookupQuery,
) -> Result<Vec<LookupPathEntry>, LookupError> {
if s.is_empty() {
return Ok(vec![]);
}
if [':', ';', '/', '\'', '#'].contains(&s.chars().next().unwrap()) {
return Ok(vec![]);
}
match s {
"SxS" => Ok(vec![]), "KnownDLLs" => Ok(vec![LookupPathEntry::KnownDLLs]),
"AppDir" => Ok(vec![LookupPathEntry::ExecutableDir(
q.target.app_dir.clone(),
)]),
"32BitSysDir" => Ok(if let Some(system) = &q.system {
vec![LookupPathEntry::SystemDir(system.sys_dir.clone())]
} else {
vec![]
}),
"16BitSysDir" => Ok(vec![]), "OSDir" => Ok(if let Some(system) = &q.system {
vec![LookupPathEntry::WindowsDir(system.win_dir.clone())]
} else {
vec![]
}),
"AppPath" => Ok(vec![]), "SysPath" => Ok(
if let Some(path) = &q.system.as_ref().and_then(|s| s.system_path.as_ref()) {
path.iter()
.map(|e| LookupPathEntry::UserPath(e.clone()))
.collect()
} else {
vec![]
},
),
_ if s.starts_with("UserDir ") => {
Ok(vec![LookupPathEntry::UserPath(PathBuf::from(&s[8..]))])
}
_ => Err(LookupError::ParseError(format!(
"Unknown key in dwp file: {}",
s
))),
}
}
#[cfg(windows)]
pub fn from_dwp_file<P: AsRef<Path>>(
dwp_path: P,
query: &LookupQuery,
) -> Result<Self, LookupError> {
let comment_chars = [':', ';', '/', '\'', '#'];
let lines: Vec<String> = fs::read_to_string(dwp_path)?
.lines()
.filter(|s| !(s.is_empty() || comment_chars.contains(&s.chars().next().unwrap())))
.map(str::to_owned)
.collect();
let entries_vecs = lines
.iter()
.map(|e| Self::dwp_string_to_context_entry(e, query))
.collect::<Result<Vec<Vec<LookupPathEntry>>, LookupError>>()?;
Ok(Self {
entries: entries_vecs.concat(),
fs_cache: std::cell::RefCell::new(WinFileSystemCache::new()),
})
}
pub fn search_path(&self) -> Vec<PathBuf> {
self.entries.iter().flat_map(|e| e.get_path()).collect()
}
pub fn search_dll(&self, library: &str, system: &Option<WindowsSystem>) -> Result<Option<LookupResult>, LookupError> {
for e in &self.entries {
match e {
LookupPathEntry::KnownDLLs => {
if let Some(Some(Ok(Some(lp)))) = system.as_ref().map(|s| s.known_dlls.as_ref().map(|kd| kd.search_dll_in_known_dlls(library))) {
let ret = Some(LookupResult {
location: LookupPathEntry::KnownDLLs,
fullpath: lp,
});
return Ok(ret);
}
}
LookupPathEntry::ApiSet => {
let apiset_name = library.to_lowercase().trim_end_matches(".dll").to_owned();
if system.as_ref().map(|s| s.apiset_map.as_ref().map(|apis| apis.contains_key(&apiset_name))).flatten().unwrap_or(false){
if let Some(system32_dir) = self
.entries
.iter()
.find(|e| std::matches!(e, LookupPathEntry::SystemDir(_)))
{
let p = self.search_file_in_folder(
OsStr::new(library),
system32_dir.get_path().unwrap().join("downlevel"),
);
return p.map(|p| {
Some(LookupResult {
location: e.clone(),
fullpath: p?,
})
});
}
}
}
LookupPathEntry::ExecutableDir(p)
| LookupPathEntry::SystemDir(p)
| LookupPathEntry::WindowsDir(p)
| LookupPathEntry::SystemPath(p)
| LookupPathEntry::UserPath(p)
| LookupPathEntry::WorkingDir(p) => {
if let Some(r) = self.search_file_in_folder(OsStr::new(library), p)? {
return Ok(Some(LookupResult {
location: e.clone(),
fullpath: r,
}));
}
}
}
}
Ok(None)
}
fn search_file_in_folder<P: AsRef<Path>>(
&self,
filename: &OsStr,
p: P,
) -> Result<Option<PathBuf>, LookupError> {
self.fs_cache
.borrow_mut()
.test_file_in_folder_case_insensitive(filename, p.as_ref())
}
fn system_path_entries(system: &'_ WindowsSystem) -> Vec<LookupPathEntry> {
system
.system_path
.as_ref()
.unwrap_or(&Vec::new())
.iter()
.map(|s| LookupPathEntry::SystemPath(s.clone()))
.collect::<Vec<_>>()
}
fn user_path_entries(q: &'_ LookupQuery) -> Vec<LookupPathEntry> {
q.target
.user_path
.iter()
.map(|s| LookupPathEntry::UserPath(s.clone()))
.collect::<Vec<_>>()
}
}
#[cfg(windows)]
#[cfg(test)]
mod tests {
use crate::common::LookupError;
use crate::path::{LookupPath, LookupPathEntry};
use crate::query::LookupQuery;
#[test]
fn parse_dwp() -> Result<(), LookupError> {
let d = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let relative_path = "test_data/dwp/lookup_path.dwp";
let dwp_file_path = d.join(relative_path);
let exe_relative_path =
"test_data/test_project1/DepRunTest/build-same-output/bin/Debug/DepRunTest.exe";
let exe_path = d.join(exe_relative_path);
let query = LookupQuery::deduce_from_executable_location(&exe_path)?;
let path = LookupPath::from_dwp_file(&dwp_file_path, &query)?;
if query.system.is_some() {
assert!(std::matches!(path.entries.first().unwrap(), LookupPathEntry::KnownDLLs));
assert!(std::matches!(path.entries[1], LookupPathEntry::ExecutableDir(_)));
assert!(std::matches!(path.entries[2], LookupPathEntry::SystemDir(_)));
assert!(std::matches!(path.entries[3], LookupPathEntry::WindowsDir(_)));
assert!(std::matches!(path.entries[4], LookupPathEntry::UserPath(_)));
} else {
assert!(std::matches!(path.entries.first().unwrap(), LookupPathEntry::KnownDLLs));
assert!(std::matches!(path.entries[1], LookupPathEntry::ExecutableDir(_)));
assert!(std::matches!(path.entries[2], LookupPathEntry::SystemDir(_)));
}
Ok(())
}
}