use crate::error::Result;
use crate::process::matcher::ModuleMatcher;
use crate::process::model::{
FindResult, ModuleMatch, ProcessListResult, ProcessMatch, ProcessSummary, SkippedProcess,
};
#[cfg(windows)]
use crate::platform::windows as winapi;
const SYSTEM_IDLE_PID: u32 = 0;
const SYSTEM_KERNEL_PID: u32 = 4;
#[cfg(windows)]
pub fn find_processes_by_module(patterns: &[String]) -> Result<FindResult> {
let processes = winapi::enum_processes()?;
let total = processes.len();
let matcher = ModuleMatcher::from_raw(patterns);
if matcher.is_empty() {
return Ok(FindResult {
matches: Vec::new(),
total_processes: total,
skipped_processes: Vec::new(),
});
}
let mut matches = Vec::new();
let mut skipped = Vec::new();
for proc in processes {
if proc.pid == SYSTEM_IDLE_PID || proc.pid == SYSTEM_KERNEL_PID {
continue;
}
let modules = match winapi::enum_modules_with_skip(proc.pid) {
Ok(m) => m,
Err(reason) => {
skipped.push(SkippedProcess {
pid: proc.pid,
image_name: proc.image_name.clone(),
reason: reason.to_string(),
kind: reason.kind().to_string(),
});
continue;
}
};
let mut matched = Vec::new();
for m in &modules {
let name_lc = m.name.to_lowercase();
let path_lc = m.path.to_string_lossy().to_lowercase();
if let Some(pat) = matcher.first_match(&name_lc, &path_lc) {
matched.push(ModuleMatch {
name: m.name.clone(),
path: m.path.clone(),
base: m.base,
size: m.size,
pattern: pat.original.clone(),
});
}
}
if matched.is_empty() {
continue;
}
matches.push(ProcessMatch {
pid: proc.pid,
parent_pid: proc.parent_pid,
image_name: proc.image_name.clone(),
image_path: winapi::process_image_path(proc.pid),
threads: proc.threads,
matched_modules: matched,
enumeration_status: "ok".into(),
});
}
Ok(FindResult {
matches,
total_processes: total,
skipped_processes: skipped,
})
}
#[cfg(not(windows))]
pub fn find_processes_by_module(_patterns: &[String]) -> Result<FindResult> {
Err(crate::error::Error::Other(
"process discovery is only implemented on Windows".into(),
))
}
#[cfg(windows)]
pub fn list_processes() -> Result<Vec<ProcessSummary>> {
let processes = winapi::enum_processes()?;
Ok(processes
.into_iter()
.map(|p| ProcessSummary {
pid: p.pid,
image_name: p.image_name,
parent_pid: p.parent_pid,
threads: p.threads,
})
.collect())
}
#[cfg(not(windows))]
pub fn list_processes() -> Result<Vec<ProcessSummary>> {
Err(crate::error::Error::Other(
"process listing is only implemented on Windows".into(),
))
}
pub fn list_processes_result() -> Result<ProcessListResult> {
let processes = list_processes()?;
let total = processes.len();
Ok(ProcessListResult {
processes,
total_processes: total,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(windows)]
fn list_processes_returns_non_empty() {
let list = list_processes().expect("enum_processes should succeed");
assert!(
!list.is_empty(),
"at least System + current process should exist"
);
assert!(
list.iter().any(|p| p.pid == std::process::id()),
"current pid must be present"
);
}
#[test]
#[cfg(windows)]
fn find_self_by_ntdll() {
let res = find_processes_by_module(&["ntdll.dll".into()]).unwrap();
assert!(
res.matches.iter().any(|m| m.pid == std::process::id()),
"current process should match ntdll.dll"
);
}
}