use std::collections::HashMap;
use std::io;
use windows_sys::Win32::Foundation::{CloseHandle, HWND, LPARAM};
use windows_sys::Win32::NetworkManagement::IpHelper::{FreeMibTable, GetIfTable2, MIB_IF_TABLE2};
use windows_sys::Win32::NetworkManagement::Ndis::IfOperStatusUp;
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_TERMINATE, TerminateProcess};
use windows_sys::Win32::UI::WindowsAndMessaging::{
EnumWindows, GetWindowThreadProcessId, PostMessageW, WM_CLOSE,
};
use windows_sys::core::BOOL;
use crate::snapshot::NetworkStats;
use crate::source::ProcessSignal;
pub(crate) fn send_signal(pid: u32, signal: ProcessSignal) -> io::Result<()> {
if pid <= 4 {
return Err(io::Error::from(io::ErrorKind::InvalidInput));
}
match signal {
ProcessSignal::Kill => terminate_process(pid),
ProcessSignal::Terminate => close_windows(pid),
}
}
fn terminate_process(pid: u32) -> io::Result<()> {
unsafe {
let handle = OpenProcess(PROCESS_TERMINATE, 0, pid);
if handle.is_null() {
return Err(io::Error::last_os_error());
}
let ok = TerminateProcess(handle, 1) != 0;
let error = io::Error::last_os_error();
CloseHandle(handle);
if ok { Ok(()) } else { Err(error) }
}
}
struct CloseRequest {
pid: u32,
posted: usize,
}
unsafe extern "system" fn post_close(hwnd: HWND, lparam: LPARAM) -> BOOL {
let request = unsafe { &mut *(lparam as *mut CloseRequest) };
let mut owner = 0u32;
unsafe { GetWindowThreadProcessId(hwnd, &mut owner) };
if owner == request.pid && unsafe { PostMessageW(hwnd, WM_CLOSE, 0, 0) } != 0 {
request.posted += 1;
}
1 }
fn close_windows(pid: u32) -> io::Result<()> {
let mut request = CloseRequest { pid, posted: 0 };
unsafe {
EnumWindows(
Some(post_close),
&mut request as *mut CloseRequest as LPARAM,
)
};
if request.posted > 0 {
Ok(())
} else {
Err(io::Error::other(
"the process has no window to close; use force kill instead",
))
}
}
pub(crate) fn annotate_links(networks: &mut [NetworkStats]) {
let links = read_links();
for net in networks {
if let Some(&(speed, up)) = links.get(&net.name) {
net.link_speed_mbps = speed;
net.link_up = Some(up);
}
}
}
fn read_links() -> HashMap<String, (Option<u32>, bool)> {
let mut links = HashMap::new();
let mut table: *mut MIB_IF_TABLE2 = std::ptr::null_mut();
unsafe {
if GetIfTable2(&mut table) != 0 || table.is_null() {
return links;
}
let count = (*table).NumEntries as usize;
let rows = std::slice::from_raw_parts((*table).Table.as_ptr(), count);
for row in rows {
let len = row
.Alias
.iter()
.position(|&c| c == 0)
.unwrap_or(row.Alias.len());
let alias = String::from_utf16_lossy(&row.Alias[..len]);
let up = row.OperStatus == IfOperStatusUp;
let speed = (row.ReceiveLinkSpeed > 0 && row.ReceiveLinkSpeed != u64::MAX)
.then(|| u32::try_from(row.ReceiveLinkSpeed / 1_000_000).ok())
.flatten();
links.insert(alias, (speed, up));
}
FreeMibTable(table as *const _);
}
links
}
#[cfg(test)]
mod tests {
use super::*;
use std::process::{Command, Stdio};
fn spawn_console_child() -> std::process::Child {
Command::new("ping")
.args(["-n", "30", "127.0.0.1"])
.stdout(Stdio::null())
.spawn()
.expect("spawn ping")
}
#[test]
fn refuses_system_pids() {
for pid in [0, 4] {
let err = send_signal(pid, ProcessSignal::Kill).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
}
#[test]
fn terminate_needs_a_window_and_kill_ends_the_process() {
let mut child = spawn_console_child();
let err = send_signal(child.id(), ProcessSignal::Terminate).unwrap_err();
assert!(err.to_string().contains("no window"), "{err}");
send_signal(child.id(), ProcessSignal::Kill).unwrap();
assert!(!child.wait().unwrap().success());
}
#[test]
fn link_table_is_readable() {
assert!(!read_links().is_empty());
}
}