diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/sysinfo/src/windows | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/sysinfo/src/windows')
-rw-r--r-- | vendor/sysinfo/src/windows/component.rs | 374 | ||||
-rw-r--r-- | vendor/sysinfo/src/windows/cpu.rs | 548 | ||||
-rw-r--r-- | vendor/sysinfo/src/windows/disk.rs | 249 | ||||
-rw-r--r-- | vendor/sysinfo/src/windows/macros.rs | 16 | ||||
-rw-r--r-- | vendor/sysinfo/src/windows/mod.rs | 20 | ||||
-rw-r--r-- | vendor/sysinfo/src/windows/network.rs | 249 | ||||
-rw-r--r-- | vendor/sysinfo/src/windows/process.rs | 1019 | ||||
-rw-r--r-- | vendor/sysinfo/src/windows/system.rs | 623 | ||||
-rw-r--r-- | vendor/sysinfo/src/windows/tools.rs | 55 | ||||
-rw-r--r-- | vendor/sysinfo/src/windows/users.rs | 181 | ||||
-rw-r--r-- | vendor/sysinfo/src/windows/utils.rs | 36 |
11 files changed, 3370 insertions, 0 deletions
diff --git a/vendor/sysinfo/src/windows/component.rs b/vendor/sysinfo/src/windows/component.rs new file mode 100644 index 000000000..502594e38 --- /dev/null +++ b/vendor/sysinfo/src/windows/component.rs @@ -0,0 +1,374 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::ComponentExt; + +use std::ptr::null_mut; + +use winapi::shared::rpcdce::{ + RPC_C_AUTHN_LEVEL_CALL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + RPC_C_IMP_LEVEL_IMPERSONATE, +}; +use winapi::shared::winerror::{FAILED, SUCCEEDED, S_FALSE, S_OK}; +use winapi::shared::wtypesbase::CLSCTX_INPROC_SERVER; +use winapi::um::combaseapi::{ + CoCreateInstance, CoInitializeEx, CoInitializeSecurity, CoSetProxyBlanket, CoUninitialize, +}; +use winapi::um::oaidl::VARIANT; +use winapi::um::objidl::EOAC_NONE; +use winapi::um::oleauto::{SysAllocString, SysFreeString, VariantClear}; +use winapi::um::wbemcli::{ + CLSID_WbemLocator, IEnumWbemClassObject, IID_IWbemLocator, IWbemClassObject, IWbemLocator, + IWbemServices, WBEM_FLAG_FORWARD_ONLY, WBEM_FLAG_NONSYSTEM_ONLY, WBEM_FLAG_RETURN_IMMEDIATELY, +}; + +#[doc = include_str!("../../md_doc/component.md")] +pub struct Component { + temperature: f32, + max: f32, + critical: Option<f32>, + label: String, + connection: Option<Connection>, +} + +impl Component { + /// Creates a new `Component` with the given information. + fn new() -> Option<Component> { + let mut c = Connection::new() + .and_then(|x| x.initialize_security()) + .and_then(|x| x.create_instance()) + .and_then(|x| x.connect_server()) + .and_then(|x| x.set_proxy_blanket()) + .and_then(|x| x.exec_query())?; + + c.temperature(true) + .map(|(temperature, critical)| Component { + temperature, + label: "Computer".to_owned(), + max: temperature, + critical, + connection: Some(c), + }) + } +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + self.critical + } + + fn label(&self) -> &str { + &self.label + } + + fn refresh(&mut self) { + if self.connection.is_none() { + self.connection = Connection::new() + .and_then(|x| x.initialize_security()) + .and_then(|x| x.create_instance()) + .and_then(|x| x.connect_server()) + .and_then(|x| x.set_proxy_blanket()); + } + self.connection = if let Some(x) = self.connection.take() { + x.exec_query() + } else { + None + }; + if let Some(ref mut connection) = self.connection { + if let Some((temperature, _)) = connection.temperature(false) { + self.temperature = temperature; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } + } +} + +pub(crate) fn get_components() -> Vec<Component> { + match Component::new() { + Some(c) => vec![c], + None => Vec::new(), + } +} + +struct Instance(*mut IWbemLocator); + +impl Drop for Instance { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +struct ServerConnection(*mut IWbemServices); + +impl Drop for ServerConnection { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +struct Enumerator(*mut IEnumWbemClassObject); + +impl Drop for Enumerator { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +macro_rules! bstr { + ($($x:expr),*) => {{ + let x: &[u16] = &[$($x as u16),*, 0]; + SysAllocString(x.as_ptr()) + }} +} + +struct Connection { + instance: Option<Instance>, + server_connection: Option<ServerConnection>, + enumerator: Option<Enumerator>, + initialized: bool, +} + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for Connection {} +unsafe impl Sync for Connection {} + +impl Connection { + #[allow(clippy::unnecessary_wraps)] + fn new() -> Option<Connection> { + unsafe { + let val = CoInitializeEx(null_mut(), 0); + Some(Connection { + instance: None, + server_connection: None, + enumerator: None, + initialized: val == S_OK || val == S_FALSE, + }) + } + } + + fn initialize_security(self) -> Option<Connection> { + unsafe { + if FAILED(CoInitializeSecurity( + null_mut(), + -1, + null_mut(), + null_mut(), + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + null_mut(), + EOAC_NONE, + null_mut(), + )) { + None + } else { + Some(self) + } + } + } + + fn create_instance(mut self) -> Option<Connection> { + let mut p_loc = null_mut(); + + unsafe { + if FAILED(CoCreateInstance( + &CLSID_WbemLocator as *const _, + null_mut(), + CLSCTX_INPROC_SERVER, + &IID_IWbemLocator as *const _, + &mut p_loc as *mut _ as *mut _, + )) { + None + } else { + self.instance = Some(Instance(p_loc)); + Some(self) + } + } + } + + fn connect_server(mut self) -> Option<Connection> { + let mut p_svc = null_mut(); + + if let Some(ref instance) = self.instance { + unsafe { + // "root\WMI" + let s = bstr!('r', 'o', 'o', 't', '\\', 'W', 'M', 'I'); + let res = (*instance.0).ConnectServer( + s, + null_mut(), + null_mut(), + null_mut(), + 0, + null_mut(), + null_mut(), + &mut p_svc as *mut _, + ); + SysFreeString(s); + if FAILED(res) { + return None; + } + } + } else { + return None; + } + self.server_connection = Some(ServerConnection(p_svc)); + Some(self) + } + + fn set_proxy_blanket(self) -> Option<Connection> { + if let Some(ref server_connection) = self.server_connection { + unsafe { + if FAILED(CoSetProxyBlanket( + server_connection.0 as *mut _, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + null_mut(), + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + null_mut(), + EOAC_NONE, + )) { + return None; + } + } + } else { + return None; + } + Some(self) + } + + fn exec_query(mut self) -> Option<Connection> { + let mut p_enumerator = null_mut(); + + if let Some(ref server_connection) = self.server_connection { + unsafe { + // "WQL" + let s = bstr!('W', 'Q', 'L'); // query kind + // "SELECT * FROM MSAcpi_ThermalZoneTemperature" + let query = bstr!( + 'S', 'E', 'L', 'E', 'C', 'T', ' ', '*', ' ', 'F', 'R', 'O', 'M', ' ', 'M', 'S', + 'A', 'c', 'p', 'i', '_', 'T', 'h', 'e', 'r', 'm', 'a', 'l', 'Z', 'o', 'n', 'e', + 'T', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', 'r', 'e' + ); + let hres = (*server_connection.0).ExecQuery( + s, + query, + (WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY) as _, + null_mut(), + &mut p_enumerator as *mut _, + ); + SysFreeString(s); + SysFreeString(query); + if FAILED(hres) { + return None; + } + } + } else { + return None; + } + self.enumerator = Some(Enumerator(p_enumerator)); + Some(self) + } + + fn temperature(&mut self, get_critical: bool) -> Option<(f32, Option<f32>)> { + use winapi::um::wbemcli::WBEM_INFINITE; + + let p_enum = match self.enumerator.take() { + Some(x) => x, + None => { + return None; + } + }; + let mut p_obj: *mut IWbemClassObject = null_mut(); + let mut nb_returned = 0; + + unsafe { + (*p_enum.0).Next( + WBEM_INFINITE as _, // Time out + 1, // One object + &mut p_obj as *mut _, + &mut nb_returned, + ); + + if nb_returned == 0 { + return None; // not enough rights I suppose... + } + + (*p_obj).BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY as _); + + let mut p_val = std::mem::MaybeUninit::<VARIANT>::uninit(); + // "CurrentTemperature" + let temp = bstr!( + 'C', 'u', 'r', 'r', 'e', 'n', 't', 'T', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', + 'r', 'e' + ); + let res = (*p_obj).Get(temp, 0, p_val.as_mut_ptr(), null_mut(), null_mut()); + let mut p_val = p_val.assume_init(); + + SysFreeString(temp); + VariantClear(&mut p_val as *mut _ as *mut _); + + let temp = if SUCCEEDED(res) { + // temperature is given in tenth of degrees Kelvin + (p_val.n1.decVal().Lo64 / 10) as f32 - 273.15 + } else { + (*p_obj).Release(); + return None; + }; + + let mut critical = None; + if get_critical { + // "CriticalPoint" + let crit = bstr!( + 'C', 'r', 'i', 't', 'i', 'c', 'a', 'l', 'T', 'r', 'i', 'p', 'P', 'o', 'i', 'n', + 't' + ); + let res = (*p_obj).Get(crit, 0, &mut p_val, null_mut(), null_mut()); + + SysFreeString(crit); + VariantClear(&mut p_val as *mut _ as *mut _); + + if SUCCEEDED(res) { + // temperature is given in tenth of degrees Kelvin + critical = Some((p_val.n1.decVal().Lo64 / 10) as f32 - 273.15); + } + } + (*p_obj).Release(); + Some((temp, critical)) + } + } +} + +impl Drop for Connection { + fn drop(&mut self) { + // Those three calls are here to enforce that they get dropped in the good order. + self.enumerator.take(); + self.server_connection.take(); + self.instance.take(); + if self.initialized { + unsafe { + CoUninitialize(); + } + } + } +} diff --git a/vendor/sysinfo/src/windows/cpu.rs b/vendor/sysinfo/src/windows/cpu.rs new file mode 100644 index 000000000..bbaa27ad7 --- /dev/null +++ b/vendor/sysinfo/src/windows/cpu.rs @@ -0,0 +1,548 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::tools::KeyHandler; +use crate::{CpuExt, CpuRefreshKind, LoadAvg}; + +use std::collections::HashMap; +use std::io::Error; +use std::mem; +use std::ops::DerefMut; +use std::ptr::null_mut; +use std::sync::Mutex; + +use ntapi::ntpoapi::PROCESSOR_POWER_INFORMATION; + +use winapi::shared::minwindef::FALSE; +use winapi::shared::winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS}; +use winapi::um::handleapi::CloseHandle; +use winapi::um::pdh::{ + PdhAddEnglishCounterA, PdhAddEnglishCounterW, PdhCloseQuery, PdhCollectQueryData, + PdhCollectQueryDataEx, PdhGetFormattedCounterValue, PdhOpenQueryA, PdhRemoveCounter, + PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE, PDH_HCOUNTER, PDH_HQUERY, +}; +use winapi::um::powerbase::CallNtPowerInformation; +use winapi::um::synchapi::CreateEventA; +use winapi::um::sysinfoapi::GetLogicalProcessorInformationEx; +use winapi::um::sysinfoapi::SYSTEM_INFO; +use winapi::um::winbase::{RegisterWaitForSingleObject, INFINITE}; +use winapi::um::winnt::{ + ProcessorInformation, RelationAll, RelationProcessorCore, BOOLEAN, HANDLE, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PVOID, WT_EXECUTEDEFAULT, +}; + +// This formula comes from linux's include/linux/sched/loadavg.h +// https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23 +#[allow(clippy::excessive_precision)] +const LOADAVG_FACTOR_1F: f64 = 0.9200444146293232478931553241; +#[allow(clippy::excessive_precision)] +const LOADAVG_FACTOR_5F: f64 = 0.9834714538216174894737477501; +#[allow(clippy::excessive_precision)] +const LOADAVG_FACTOR_15F: f64 = 0.9944598480048967508795473394; +// The time interval in seconds between taking load counts, same as Linux +const SAMPLING_INTERVAL: usize = 5; + +// maybe use a read/write lock instead? +static LOAD_AVG: once_cell::sync::Lazy<Mutex<Option<LoadAvg>>> = + once_cell::sync::Lazy::new(|| unsafe { init_load_avg() }); + +pub(crate) fn get_load_average() -> LoadAvg { + if let Ok(avg) = LOAD_AVG.lock() { + if let Some(avg) = &*avg { + return avg.clone(); + } + } + LoadAvg::default() +} + +unsafe extern "system" fn load_avg_callback(counter: PVOID, _: BOOLEAN) { + let mut display_value = mem::MaybeUninit::<PDH_FMT_COUNTERVALUE>::uninit(); + + if PdhGetFormattedCounterValue( + counter as _, + PDH_FMT_DOUBLE, + null_mut(), + display_value.as_mut_ptr(), + ) != ERROR_SUCCESS as _ + { + return; + } + let display_value = display_value.assume_init(); + if let Ok(mut avg) = LOAD_AVG.lock() { + if let Some(avg) = avg.deref_mut() { + let current_load = display_value.u.doubleValue(); + + avg.one = avg.one * LOADAVG_FACTOR_1F + current_load * (1.0 - LOADAVG_FACTOR_1F); + avg.five = avg.five * LOADAVG_FACTOR_5F + current_load * (1.0 - LOADAVG_FACTOR_5F); + avg.fifteen = + avg.fifteen * LOADAVG_FACTOR_15F + current_load * (1.0 - LOADAVG_FACTOR_15F); + } + } +} + +unsafe fn init_load_avg() -> Mutex<Option<LoadAvg>> { + // You can see the original implementation here: https://github.com/giampaolo/psutil + let mut query = null_mut(); + + if PdhOpenQueryA(null_mut(), 0, &mut query) != ERROR_SUCCESS as _ { + sysinfo_debug!("init_load_avg: PdhOpenQueryA failed"); + return Mutex::new(None); + } + + let mut counter: PDH_HCOUNTER = mem::zeroed(); + if PdhAddEnglishCounterA( + query, + b"\\System\\Cpu Queue Length\0".as_ptr() as _, + 0, + &mut counter, + ) != ERROR_SUCCESS as _ + { + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: failed to get CPU queue length"); + return Mutex::new(None); + } + + let event = CreateEventA(null_mut(), FALSE, FALSE, b"LoadUpdateEvent\0".as_ptr() as _); + if event.is_null() { + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: failed to create event `LoadUpdateEvent`"); + return Mutex::new(None); + } + + if PdhCollectQueryDataEx(query, SAMPLING_INTERVAL as _, event) != ERROR_SUCCESS as _ { + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: PdhCollectQueryDataEx failed"); + return Mutex::new(None); + } + + let mut wait_handle = null_mut(); + if RegisterWaitForSingleObject( + &mut wait_handle, + event, + Some(load_avg_callback), + counter as _, + INFINITE, + WT_EXECUTEDEFAULT, + ) == 0 + { + PdhRemoveCounter(counter); + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: RegisterWaitForSingleObject failed"); + Mutex::new(None) + } else { + Mutex::new(Some(LoadAvg::default())) + } +} + +struct InternalQuery { + query: PDH_HQUERY, + event: HANDLE, + data: HashMap<String, PDH_HCOUNTER>, +} + +unsafe impl Send for InternalQuery {} +unsafe impl Sync for InternalQuery {} + +impl Drop for InternalQuery { + fn drop(&mut self) { + unsafe { + for (_, counter) in self.data.iter() { + PdhRemoveCounter(*counter); + } + + if !self.event.is_null() { + CloseHandle(self.event); + } + + if !self.query.is_null() { + PdhCloseQuery(self.query); + } + } + } +} + +pub(crate) struct Query { + internal: InternalQuery, +} + +impl Query { + pub fn new() -> Option<Query> { + let mut query = null_mut(); + unsafe { + if PdhOpenQueryA(null_mut(), 0, &mut query) == ERROR_SUCCESS as i32 { + let q = InternalQuery { + query, + event: null_mut(), + data: HashMap::new(), + }; + Some(Query { internal: q }) + } else { + sysinfo_debug!("Query::new: PdhOpenQueryA failed"); + None + } + } + } + + #[allow(clippy::ptr_arg)] + pub fn get(&self, name: &String) -> Option<f32> { + if let Some(counter) = self.internal.data.get(name) { + unsafe { + let mut display_value = mem::MaybeUninit::<PDH_FMT_COUNTERVALUE>::uninit(); + let counter: PDH_HCOUNTER = *counter; + + let ret = PdhGetFormattedCounterValue( + counter, + PDH_FMT_DOUBLE, + null_mut(), + display_value.as_mut_ptr(), + ) as u32; + let display_value = display_value.assume_init(); + return if ret == ERROR_SUCCESS as _ { + let data = *display_value.u.doubleValue(); + Some(data as f32) + } else { + sysinfo_debug!("Query::get: PdhGetFormattedCounterValue failed"); + Some(0.) + }; + } + } + None + } + + #[allow(clippy::ptr_arg)] + pub fn add_english_counter(&mut self, name: &String, getter: Vec<u16>) -> bool { + if self.internal.data.contains_key(name) { + sysinfo_debug!("Query::add_english_counter: doesn't have key `{:?}`", name); + return false; + } + unsafe { + let mut counter: PDH_HCOUNTER = std::mem::zeroed(); + let ret = PdhAddEnglishCounterW(self.internal.query, getter.as_ptr(), 0, &mut counter); + if ret == ERROR_SUCCESS as _ { + self.internal.data.insert(name.clone(), counter); + } else { + sysinfo_debug!( + "Query::add_english_counter: failed to add counter '{}': {:x}...", + name, + ret, + ); + return false; + } + } + true + } + + pub fn refresh(&self) { + unsafe { + if PdhCollectQueryData(self.internal.query) != ERROR_SUCCESS as _ { + sysinfo_debug!("failed to refresh CPU data"); + } + } + } +} + +pub(crate) struct CpusWrapper { + global: Cpu, + cpus: Vec<Cpu>, + got_cpu_frequency: bool, +} + +impl CpusWrapper { + pub fn new() -> Self { + Self { + global: Cpu::new_with_values("Total CPU".to_owned(), String::new(), String::new(), 0), + cpus: Vec::new(), + got_cpu_frequency: false, + } + } + + pub fn global_cpu(&self) -> &Cpu { + &self.global + } + + pub fn global_cpu_mut(&mut self) -> &mut Cpu { + &mut self.global + } + + pub fn cpus(&self) -> &[Cpu] { + &self.cpus + } + + fn init_if_needed(&mut self, refresh_kind: CpuRefreshKind) { + if self.cpus.is_empty() { + let (cpus, vendor_id, brand) = super::tools::init_cpus(refresh_kind); + self.cpus = cpus; + self.global.vendor_id = vendor_id; + self.global.brand = brand; + self.got_cpu_frequency = refresh_kind.frequency(); + } + } + + pub fn len(&mut self) -> usize { + self.init_if_needed(CpuRefreshKind::new()); + self.cpus.len() + } + + pub fn iter_mut(&mut self, refresh_kind: CpuRefreshKind) -> impl Iterator<Item = &mut Cpu> { + self.init_if_needed(refresh_kind); + self.cpus.iter_mut() + } + + pub fn get_frequencies(&mut self) { + if self.got_cpu_frequency { + return; + } + let frequencies = get_frequencies(self.cpus.len()); + + for (cpu, frequency) in self.cpus.iter_mut().zip(frequencies) { + cpu.set_frequency(frequency); + } + self.got_cpu_frequency = true; + } +} + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + name: String, + cpu_usage: f32, + key_used: Option<KeyHandler>, + vendor_id: String, + brand: String, + frequency: u64, +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + fn brand(&self) -> &str { + &self.brand + } +} + +impl Cpu { + pub(crate) fn new_with_values( + name: String, + vendor_id: String, + brand: String, + frequency: u64, + ) -> Cpu { + Cpu { + name, + cpu_usage: 0f32, + key_used: None, + vendor_id, + brand, + frequency, + } + } + + pub(crate) fn set_cpu_usage(&mut self, value: f32) { + self.cpu_usage = value; + } + + pub(crate) fn set_frequency(&mut self, value: u64) { + self.frequency = value; + } +} + +fn get_vendor_id_not_great(info: &SYSTEM_INFO) -> String { + use winapi::um::winnt; + // https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info + unsafe { + match info.u.s().wProcessorArchitecture { + winnt::PROCESSOR_ARCHITECTURE_INTEL => "Intel x86", + winnt::PROCESSOR_ARCHITECTURE_MIPS => "MIPS", + winnt::PROCESSOR_ARCHITECTURE_ALPHA => "RISC Alpha", + winnt::PROCESSOR_ARCHITECTURE_PPC => "PPC", + winnt::PROCESSOR_ARCHITECTURE_SHX => "SHX", + winnt::PROCESSOR_ARCHITECTURE_ARM => "ARM", + winnt::PROCESSOR_ARCHITECTURE_IA64 => "Intel Itanium-based x64", + winnt::PROCESSOR_ARCHITECTURE_ALPHA64 => "RISC Alpha x64", + winnt::PROCESSOR_ARCHITECTURE_MSIL => "MSIL", + winnt::PROCESSOR_ARCHITECTURE_AMD64 => "(Intel or AMD) x64", + winnt::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 => "Intel Itanium-based x86", + winnt::PROCESSOR_ARCHITECTURE_NEUTRAL => "unknown", + winnt::PROCESSOR_ARCHITECTURE_ARM64 => "ARM x64", + winnt::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 => "ARM", + winnt::PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 => "Intel Itanium-based x86", + _ => "unknown", + } + .to_owned() + } +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub(crate) fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) { + #[cfg(target_arch = "x86")] + use std::arch::x86::__cpuid; + #[cfg(target_arch = "x86_64")] + use std::arch::x86_64::__cpuid; + + unsafe fn add_u32(v: &mut Vec<u8>, i: u32) { + let i = &i as *const u32 as *const u8; + v.push(*i); + v.push(*i.offset(1)); + v.push(*i.offset(2)); + v.push(*i.offset(3)); + } + + unsafe { + // First, we try to get the complete name. + let res = __cpuid(0x80000000); + let n_ex_ids = res.eax; + let brand = if n_ex_ids >= 0x80000004 { + let mut extdata = Vec::with_capacity(5); + + for i in 0x80000000..=n_ex_ids { + extdata.push(__cpuid(i)); + } + + // 4 * u32 * nb_entries + let mut out = Vec::with_capacity(4 * std::mem::size_of::<u32>() * 3); + for data in extdata.iter().take(5).skip(2) { + add_u32(&mut out, data.eax); + add_u32(&mut out, data.ebx); + add_u32(&mut out, data.ecx); + add_u32(&mut out, data.edx); + } + let mut pos = 0; + for e in out.iter() { + if *e == 0 { + break; + } + pos += 1; + } + match std::str::from_utf8(&out[..pos]) { + Ok(s) => s.to_owned(), + _ => String::new(), + } + } else { + String::new() + }; + + // Failed to get full name, let's retry for the short version! + let res = __cpuid(0); + let mut x = Vec::with_capacity(3 * std::mem::size_of::<u32>()); + add_u32(&mut x, res.ebx); + add_u32(&mut x, res.edx); + add_u32(&mut x, res.ecx); + let mut pos = 0; + for e in x.iter() { + if *e == 0 { + break; + } + pos += 1; + } + let vendor_id = match std::str::from_utf8(&x[..pos]) { + Ok(s) => s.to_owned(), + Err(_) => get_vendor_id_not_great(info), + }; + (vendor_id, brand) + } +} + +#[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))] +pub(crate) fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) { + (get_vendor_id_not_great(info), String::new()) +} + +pub(crate) fn get_key_used(p: &mut Cpu) -> &mut Option<KeyHandler> { + &mut p.key_used +} + +// From https://stackoverflow.com/a/43813138: +// +// If your PC has 64 or fewer logical cpus installed, the above code will work fine. However, +// if your PC has more than 64 logical cpus installed, use GetActiveCpuCount() or +// GetLogicalCpuInformation() to determine the total number of logical cpus installed. +pub(crate) fn get_frequencies(nb_cpus: usize) -> Vec<u64> { + let size = nb_cpus * mem::size_of::<PROCESSOR_POWER_INFORMATION>(); + let mut infos: Vec<PROCESSOR_POWER_INFORMATION> = Vec::with_capacity(nb_cpus); + + unsafe { + if CallNtPowerInformation( + ProcessorInformation, + null_mut(), + 0, + infos.as_mut_ptr() as _, + size as _, + ) == 0 + { + infos.set_len(nb_cpus); + // infos.Number + return infos + .into_iter() + .map(|i| i.CurrentMhz as u64) + .collect::<Vec<_>>(); + } + } + sysinfo_debug!("get_frequencies: CallNtPowerInformation failed"); + vec![0; nb_cpus] +} + +pub(crate) fn get_physical_core_count() -> Option<usize> { + // we cannot use the number of cpus here to pre calculate the buf size + // GetLogicalCpuInformationEx with RelationProcessorCore passed to it not only returns + // the logical cores but also numa nodes + // + // GetLogicalCpuInformationEx: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex + + let mut needed_size = 0; + unsafe { + GetLogicalProcessorInformationEx(RelationAll, null_mut(), &mut needed_size); + + let mut buf: Vec<u8> = Vec::with_capacity(needed_size as _); + + loop { + if GetLogicalProcessorInformationEx( + RelationAll, + buf.as_mut_ptr() as *mut _, + &mut needed_size, + ) == FALSE + { + let e = Error::last_os_error(); + // For some reasons, the function might return a size not big enough... + match e.raw_os_error() { + Some(value) if value == ERROR_INSUFFICIENT_BUFFER as _ => {} + _ => { + sysinfo_debug!( + "get_physical_core_count: GetLogicalCpuInformationEx failed" + ); + return None; + } + } + } else { + break; + } + buf.reserve(needed_size as usize - buf.capacity()); + } + + buf.set_len(needed_size as _); + + let mut i = 0; + let raw_buf = buf.as_ptr(); + let mut count = 0; + while i < buf.len() { + let p = &*(raw_buf.add(i) as PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX); + i += p.Size as usize; + if p.Relationship == RelationProcessorCore { + // Only count the physical cores. + count += 1; + } + } + Some(count) + } +} diff --git a/vendor/sysinfo/src/windows/disk.rs b/vendor/sysinfo/src/windows/disk.rs new file mode 100644 index 000000000..ae393afb2 --- /dev/null +++ b/vendor/sysinfo/src/windows/disk.rs @@ -0,0 +1,249 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskExt, DiskType}; + +use std::ffi::{OsStr, OsString}; +use std::mem::size_of; +use std::path::Path; + +use winapi::ctypes::c_void; +use winapi::shared::minwindef::{DWORD, MAX_PATH}; +use winapi::um::fileapi::{ + CreateFileW, GetDiskFreeSpaceExW, GetDriveTypeW, GetLogicalDrives, GetVolumeInformationW, + OPEN_EXISTING, +}; +use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; +use winapi::um::ioapiset::DeviceIoControl; +use winapi::um::winbase::{DRIVE_FIXED, DRIVE_REMOVABLE}; +use winapi::um::winioctl::{ + DEVICE_TRIM_DESCRIPTOR, IOCTL_STORAGE_QUERY_PROPERTY, STORAGE_PROPERTY_QUERY, +}; +use winapi::um::winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, HANDLE, ULARGE_INTEGER}; + +#[doc = include_str!("../../md_doc/disk.md")] +pub struct Disk { + type_: DiskType, + name: OsString, + file_system: Vec<u8>, + mount_point: Vec<u16>, + s_mount_point: String, + total_space: u64, + available_space: u64, + is_removable: bool, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + self.type_ + } + + fn name(&self) -> &OsStr { + &self.name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + Path::new(&self.s_mount_point) + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + fn refresh(&mut self) -> bool { + if self.total_space != 0 { + unsafe { + let mut tmp: ULARGE_INTEGER = std::mem::zeroed(); + if GetDiskFreeSpaceExW( + self.mount_point.as_ptr(), + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut tmp, + ) != 0 + { + self.available_space = *tmp.QuadPart(); + return true; + } + } + } + false + } +} + +struct HandleWrapper(HANDLE); + +impl HandleWrapper { + unsafe fn new(drive_name: &[u16], open_rights: DWORD) -> Option<Self> { + let handle = CreateFileW( + drive_name.as_ptr(), + open_rights, + FILE_SHARE_READ | FILE_SHARE_WRITE, + std::ptr::null_mut(), + OPEN_EXISTING, + 0, + std::ptr::null_mut(), + ); + if handle == INVALID_HANDLE_VALUE { + CloseHandle(handle); + None + } else { + Some(Self(handle)) + } + } +} + +impl Drop for HandleWrapper { + fn drop(&mut self) { + unsafe { + CloseHandle(self.0); + } + } +} + +unsafe fn get_drive_size(mount_point: &[u16]) -> Option<(u64, u64)> { + let mut total_size: ULARGE_INTEGER = std::mem::zeroed(); + let mut available_space: ULARGE_INTEGER = std::mem::zeroed(); + if GetDiskFreeSpaceExW( + mount_point.as_ptr(), + std::ptr::null_mut(), + &mut total_size, + &mut available_space, + ) != 0 + { + Some(( + *total_size.QuadPart() as u64, + *available_space.QuadPart() as u64, + )) + } else { + None + } +} + +pub(crate) unsafe fn get_disks() -> Vec<Disk> { + let drives = GetLogicalDrives(); + if drives == 0 { + return Vec::new(); + } + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + crate::utils::into_iter(0..DWORD::BITS) + .filter_map(|x| { + if (drives >> x) & 1 == 0 { + return None; + } + let mount_point = [b'A' as u16 + x as u16, b':' as u16, b'\\' as u16, 0]; + + let drive_type = GetDriveTypeW(mount_point.as_ptr()); + + let is_removable = drive_type == DRIVE_REMOVABLE; + + if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE { + return None; + } + let mut name = [0u16; MAX_PATH + 1]; + let mut file_system = [0u16; 32]; + if GetVolumeInformationW( + mount_point.as_ptr(), + name.as_mut_ptr(), + name.len() as DWORD, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + file_system.as_mut_ptr(), + file_system.len() as DWORD, + ) == 0 + { + return None; + } + let mut pos = 0; + for x in name.iter() { + if *x == 0 { + break; + } + pos += 1; + } + let name = String::from_utf16_lossy(&name[..pos]); + let name = OsStr::new(&name); + + pos = 0; + for x in file_system.iter() { + if *x == 0 { + break; + } + pos += 1; + } + let file_system: Vec<u8> = file_system[..pos].iter().map(|x| *x as u8).collect(); + + let drive_name = [ + b'\\' as u16, + b'\\' as u16, + b'.' as u16, + b'\\' as u16, + b'A' as u16 + x as u16, + b':' as u16, + 0, + ]; + let handle = HandleWrapper::new(&drive_name, 0)?; + let (total_space, available_space) = get_drive_size(&mount_point)?; + if total_space == 0 { + return None; + } + /*let mut spq_trim: STORAGE_PROPERTY_QUERY = std::mem::zeroed(); + spq_trim.PropertyId = StorageDeviceTrimProperty; + spq_trim.QueryType = PropertyStandardQuery; + let mut dtd: DEVICE_TRIM_DESCRIPTOR = std::mem::zeroed();*/ + let mut spq_trim = STORAGE_PROPERTY_QUERY { + PropertyId: 8, + QueryType: 0, + AdditionalParameters: [0], + }; + let mut dtd: DEVICE_TRIM_DESCRIPTOR = std::mem::zeroed(); + + let mut dw_size = 0; + let type_ = if DeviceIoControl( + handle.0, + IOCTL_STORAGE_QUERY_PROPERTY, + &mut spq_trim as *mut STORAGE_PROPERTY_QUERY as *mut c_void, + size_of::<STORAGE_PROPERTY_QUERY>() as DWORD, + &mut dtd as *mut DEVICE_TRIM_DESCRIPTOR as *mut c_void, + size_of::<DEVICE_TRIM_DESCRIPTOR>() as DWORD, + &mut dw_size, + std::ptr::null_mut(), + ) == 0 + || dw_size != size_of::<DEVICE_TRIM_DESCRIPTOR>() as DWORD + { + DiskType::Unknown(-1) + } else { + let is_ssd = dtd.TrimEnabled != 0; + if is_ssd { + DiskType::SSD + } else { + DiskType::HDD + } + }; + Some(Disk { + type_, + name: name.to_owned(), + file_system: file_system.to_vec(), + mount_point: mount_point.to_vec(), + s_mount_point: String::from_utf16_lossy(&mount_point[..mount_point.len() - 1]), + total_space, + available_space, + is_removable, + }) + }) + .collect::<Vec<_>>() +} diff --git a/vendor/sysinfo/src/windows/macros.rs b/vendor/sysinfo/src/windows/macros.rs new file mode 100644 index 000000000..b0c024c7c --- /dev/null +++ b/vendor/sysinfo/src/windows/macros.rs @@ -0,0 +1,16 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +/// Allows to cast only when needed. +#[macro_export] +macro_rules! auto_cast { + ($t:expr, $cast:ty) => {{ + #[cfg(target_pointer_width = "32")] + { + $t as $cast + } + #[cfg(not(target_pointer_width = "32"))] + { + $t + } + }}; +} diff --git a/vendor/sysinfo/src/windows/mod.rs b/vendor/sysinfo/src/windows/mod.rs new file mode 100644 index 000000000..fa5d66beb --- /dev/null +++ b/vendor/sysinfo/src/windows/mod.rs @@ -0,0 +1,20 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +mod component; +mod cpu; +mod disk; +#[macro_use] +mod macros; +mod network; +mod process; +mod system; +mod tools; +mod users; +mod utils; + +pub use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo/src/windows/network.rs b/vendor/sysinfo/src/windows/network.rs new file mode 100644 index 000000000..6a09a0490 --- /dev/null +++ b/vendor/sysinfo/src/windows/network.rs @@ -0,0 +1,249 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{NetworkExt, NetworksExt, NetworksIter}; + +use std::collections::{hash_map, HashMap}; + +use winapi::shared::ifdef::{MediaConnectStateDisconnected, NET_LUID}; +use winapi::shared::netioapi::{ + FreeMibTable, GetIfEntry2, GetIfTable2, MIB_IF_ROW2, PMIB_IF_TABLE2, +}; +use winapi::shared::winerror::NO_ERROR; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $new_val; + }}; +} + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Networks { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + #[allow(clippy::needless_lifetimes)] + fn iter<'a>(&'a self) -> NetworksIter<'a> { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + let mut table: PMIB_IF_TABLE2 = std::ptr::null_mut(); + + unsafe { + if GetIfTable2(&mut table) != NO_ERROR { + return; + } + + for (_, data) in self.interfaces.iter_mut() { + data.updated = false; + } + + // In here, this is tricky: we have to filter out the software interfaces to only keep + // the hardware ones. To do so, we first check the connection potential speed (if 0, not + // interesting), then we check its state: if not open, not interesting either. And finally, + // we count the members of a same group: if there is more than 1, then it's software level. + let mut groups = HashMap::new(); + let mut indexes = Vec::new(); + let ptr = (*table).Table.as_ptr(); + for i in 0..(*table).NumEntries { + let ptr = &*ptr.offset(i as _); + if (ptr.TransmitLinkSpeed == 0 && ptr.ReceiveLinkSpeed == 0) + || ptr.MediaConnectState == MediaConnectStateDisconnected + || ptr.PhysicalAddressLength == 0 + { + continue; + } + let id = vec![ + ptr.InterfaceGuid.Data2, + ptr.InterfaceGuid.Data3, + ptr.InterfaceGuid.Data4[0] as _, + ptr.InterfaceGuid.Data4[1] as _, + ptr.InterfaceGuid.Data4[2] as _, + ptr.InterfaceGuid.Data4[3] as _, + ptr.InterfaceGuid.Data4[4] as _, + ptr.InterfaceGuid.Data4[5] as _, + ptr.InterfaceGuid.Data4[6] as _, + ptr.InterfaceGuid.Data4[7] as _, + ]; + let entry = groups.entry(id.clone()).or_insert(0); + *entry += 1; + if *entry > 1 { + continue; + } + indexes.push((i, id)); + } + for (i, id) in indexes { + let ptr = &*ptr.offset(i as _); + if *groups.get(&id).unwrap_or(&0) > 1 { + continue; + } + let mut pos = 0; + for x in ptr.Alias.iter() { + if *x == 0 { + break; + } + pos += 1; + } + let interface_name = match String::from_utf16(&ptr.Alias[..pos]) { + Ok(s) => s, + _ => continue, + }; + match self.interfaces.entry(interface_name) { + hash_map::Entry::Occupied(mut e) => { + let mut interface = e.get_mut(); + old_and_new!(interface, current_out, old_out, ptr.OutOctets); + old_and_new!(interface, current_in, old_in, ptr.InOctets); + old_and_new!( + interface, + packets_in, + old_packets_in, + ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts) + ); + old_and_new!( + interface, + packets_out, + old_packets_out, + ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts) + ); + old_and_new!(interface, errors_in, old_errors_in, ptr.InErrors); + old_and_new!(interface, errors_out, old_errors_out, ptr.OutErrors); + interface.updated = true; + } + hash_map::Entry::Vacant(e) => { + let packets_in = ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts); + let packets_out = ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts); + + e.insert(NetworkData { + id: ptr.InterfaceLuid, + current_out: ptr.OutOctets, + old_out: ptr.OutOctets, + current_in: ptr.InOctets, + old_in: ptr.InOctets, + packets_in, + old_packets_in: packets_in, + packets_out, + old_packets_out: packets_out, + errors_in: ptr.InErrors, + old_errors_in: ptr.InErrors, + errors_out: ptr.OutErrors, + old_errors_out: ptr.OutErrors, + updated: true, + }); + } + } + } + FreeMibTable(table as _); + } + // Remove interfaces which are gone. + self.interfaces.retain(|_, d| d.updated); + } + + fn refresh(&mut self) { + let entry = std::mem::MaybeUninit::<MIB_IF_ROW2>::zeroed(); + + unsafe { + let mut entry = entry.assume_init(); + for (_, interface) in self.interfaces.iter_mut() { + entry.InterfaceLuid = interface.id; + entry.InterfaceIndex = 0; // to prevent the function to pick this one as index + if GetIfEntry2(&mut entry) != NO_ERROR { + continue; + } + old_and_new!(interface, current_out, old_out, entry.OutOctets); + old_and_new!(interface, current_in, old_in, entry.InOctets); + old_and_new!( + interface, + packets_in, + old_packets_in, + entry.InUcastPkts.saturating_add(entry.InNUcastPkts) + ); + old_and_new!( + interface, + packets_out, + old_packets_out, + entry.OutUcastPkts.saturating_add(entry.OutNUcastPkts) + ); + old_and_new!(interface, errors_in, old_errors_in, entry.InErrors); + old_and_new!(interface, errors_out, old_errors_out, entry.OutErrors); + } + } + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +pub struct NetworkData { + id: NET_LUID, + current_out: u64, + old_out: u64, + current_in: u64, + old_in: u64, + packets_in: u64, + old_packets_in: u64, + packets_out: u64, + old_packets_out: u64, + errors_in: u64, + old_errors_in: u64, + errors_out: u64, + old_errors_out: u64, + updated: bool, +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.current_in.saturating_sub(self.old_in) + } + + fn total_received(&self) -> u64 { + self.current_in + } + + fn transmitted(&self) -> u64 { + self.current_out.saturating_sub(self.old_out) + } + + fn total_transmitted(&self) -> u64 { + self.current_out + } + + fn packets_received(&self) -> u64 { + self.packets_in.saturating_sub(self.old_packets_in) + } + + fn total_packets_received(&self) -> u64 { + self.packets_in + } + + fn packets_transmitted(&self) -> u64 { + self.packets_out.saturating_sub(self.old_packets_out) + } + + fn total_packets_transmitted(&self) -> u64 { + self.packets_out + } + + fn errors_on_received(&self) -> u64 { + self.errors_in.saturating_sub(self.old_errors_in) + } + + fn total_errors_on_received(&self) -> u64 { + self.errors_in + } + + fn errors_on_transmitted(&self) -> u64 { + self.errors_out.saturating_sub(self.old_errors_out) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.errors_out + } +} diff --git a/vendor/sysinfo/src/windows/process.rs b/vendor/sysinfo/src/windows/process.rs new file mode 100644 index 000000000..bdc35c53e --- /dev/null +++ b/vendor/sysinfo/src/windows/process.rs @@ -0,0 +1,1019 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::utils::to_str; +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; + +use std::ffi::OsString; +use std::fmt; +use std::mem::{size_of, zeroed, MaybeUninit}; +use std::ops::Deref; +use std::os::windows::ffi::OsStringExt; +use std::os::windows::process::CommandExt; +use std::path::{Path, PathBuf}; +use std::process; +use std::ptr::null_mut; +use std::str; +use std::sync::Arc; + +use libc::{c_void, memcpy}; + +use ntapi::ntpebteb::PEB; +use ntapi::ntwow64::{PEB32, PRTL_USER_PROCESS_PARAMETERS32, RTL_USER_PROCESS_PARAMETERS32}; +use once_cell::sync::Lazy; + +use ntapi::ntpsapi::{ + NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation, + ProcessWow64Information, PROCESSINFOCLASS, PROCESS_BASIC_INFORMATION, +}; +use ntapi::ntrtl::{RtlGetVersion, PRTL_USER_PROCESS_PARAMETERS, RTL_USER_PROCESS_PARAMETERS}; +use winapi::shared::basetsd::SIZE_T; +use winapi::shared::minwindef::{DWORD, FALSE, FILETIME, LPVOID, MAX_PATH, TRUE, ULONG}; +use winapi::shared::ntdef::{NT_SUCCESS, UNICODE_STRING}; +use winapi::shared::ntstatus::{ + STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH, +}; +use winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER; +use winapi::um::errhandlingapi::GetLastError; +use winapi::um::handleapi::CloseHandle; +use winapi::um::heapapi::{GetProcessHeap, HeapAlloc, HeapFree}; +use winapi::um::memoryapi::{ReadProcessMemory, VirtualQueryEx}; +use winapi::um::processthreadsapi::{ + GetProcessTimes, GetSystemTimes, OpenProcess, OpenProcessToken, +}; +use winapi::um::psapi::{ + EnumProcessModulesEx, GetModuleBaseNameW, GetModuleFileNameExW, GetProcessMemoryInfo, + LIST_MODULES_ALL, PROCESS_MEMORY_COUNTERS, PROCESS_MEMORY_COUNTERS_EX, +}; +use winapi::um::securitybaseapi::GetTokenInformation; +use winapi::um::winbase::{GetProcessIoCounters, CREATE_NO_WINDOW}; +use winapi::um::winnt::{ + TokenUser, HANDLE, HEAP_ZERO_MEMORY, IO_COUNTERS, MEMORY_BASIC_INFORMATION, + PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, RTL_OSVERSIONINFOEXW, TOKEN_QUERY, TOKEN_USER, + ULARGE_INTEGER, +}; + +impl fmt::Display for ProcessStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + ProcessStatus::Run => "Runnable", + _ => "Unknown", + }) + } +} + +fn get_process_handler(pid: Pid) -> Option<HandleWrapper> { + if pid.0 == 0 { + return None; + } + let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + + unsafe { HandleWrapper::new(OpenProcess(options, FALSE, pid.0 as DWORD)) } +} + +unsafe fn get_process_user_id( + handle: &HandleWrapper, + refresh_kind: ProcessRefreshKind, +) -> Option<Uid> { + struct HeapWrap<T>(*mut T); + + impl<T> HeapWrap<T> { + unsafe fn new(size: DWORD) -> Option<Self> { + let ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size as _) as *mut T; + if ptr.is_null() { + sysinfo_debug!("HeapAlloc failed"); + None + } else { + Some(Self(ptr)) + } + } + } + + impl<T> Drop for HeapWrap<T> { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + HeapFree(GetProcessHeap(), 0, self.0 as *mut _); + } + } + } + } + + if !refresh_kind.user() { + return None; + } + + let mut token = null_mut(); + + if OpenProcessToken(**handle, TOKEN_QUERY, &mut token) == 0 { + sysinfo_debug!("OpenProcessToken failed"); + return None; + } + + let token = HandleWrapper::new(token)?; + + let mut size = 0; + + if GetTokenInformation(*token, TokenUser, null_mut(), 0, &mut size) == 0 { + let err = GetLastError(); + if err != ERROR_INSUFFICIENT_BUFFER { + sysinfo_debug!("GetTokenInformation failed, error: {:?}", err); + return None; + } + } + + let ptu: HeapWrap<TOKEN_USER> = HeapWrap::new(size)?; + + if GetTokenInformation(*token, TokenUser, ptu.0 as *mut _, size, &mut size) == 0 { + sysinfo_debug!("GetTokenInformation failed, error: {:?}", GetLastError()); + return None; + } + + let mut name_use = 0; + let mut name = [0u16; 256]; + let mut domain_name = [0u16; 256]; + let mut size = 256; + + if winapi::um::winbase::LookupAccountSidW( + std::ptr::null_mut(), + (*ptu.0).User.Sid, + name.as_mut_ptr(), + &mut size, + domain_name.as_mut_ptr(), + &mut size, + &mut name_use, + ) == 0 + { + sysinfo_debug!( + "LookupAccountSidW failed: {:?}", + winapi::um::errhandlingapi::GetLastError(), + ); + None + } else { + Some(Uid(to_str(name.as_mut_ptr()).into_boxed_str())) + } +} + +struct HandleWrapper(HANDLE); + +impl HandleWrapper { + fn new(handle: HANDLE) -> Option<Self> { + if handle.is_null() { + None + } else { + Some(Self(handle)) + } + } +} + +impl Deref for HandleWrapper { + type Target = HANDLE; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for HandleWrapper { + fn drop(&mut self) { + unsafe { + CloseHandle(self.0); + } + } +} + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for HandleWrapper {} +unsafe impl Sync for HandleWrapper {} + +#[doc = include_str!("../../md_doc/process.md")] +pub struct Process { + name: String, + cmd: Vec<String>, + exe: PathBuf, + pid: Pid, + user_id: Option<Uid>, + environ: Vec<String>, + cwd: PathBuf, + root: PathBuf, + pub(crate) memory: u64, + pub(crate) virtual_memory: u64, + parent: Option<Pid>, + status: ProcessStatus, + handle: Option<Arc<HandleWrapper>>, + cpu_calc_values: CPUsageCalculationValues, + start_time: u64, + pub(crate) run_time: u64, + cpu_usage: f32, + pub(crate) updated: bool, + old_read_bytes: u64, + old_written_bytes: u64, + read_bytes: u64, + written_bytes: u64, +} + +struct CPUsageCalculationValues { + old_process_sys_cpu: u64, + old_process_user_cpu: u64, + old_system_sys_cpu: u64, + old_system_user_cpu: u64, +} + +impl CPUsageCalculationValues { + fn new() -> Self { + CPUsageCalculationValues { + old_process_sys_cpu: 0, + old_process_user_cpu: 0, + old_system_sys_cpu: 0, + old_system_user_cpu: 0, + } + } +} +static WINDOWS_8_1_OR_NEWER: Lazy<bool> = Lazy::new(|| unsafe { + let mut version_info: RTL_OSVERSIONINFOEXW = MaybeUninit::zeroed().assume_init(); + + version_info.dwOSVersionInfoSize = std::mem::size_of::<RTL_OSVERSIONINFOEXW>() as u32; + if !NT_SUCCESS(RtlGetVersion( + &mut version_info as *mut RTL_OSVERSIONINFOEXW as *mut _, + )) { + return true; + } + + // Windows 8.1 is 6.3 + version_info.dwMajorVersion > 6 + || version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 3 +}); + +unsafe fn get_process_name(process_handler: &HandleWrapper, h_mod: *mut c_void) -> String { + let mut process_name = [0u16; MAX_PATH + 1]; + + GetModuleBaseNameW( + **process_handler, + h_mod as _, + process_name.as_mut_ptr(), + MAX_PATH as DWORD + 1, + ); + null_terminated_wchar_to_string(&process_name) +} + +unsafe fn get_h_mod(process_handler: &HandleWrapper, h_mod: &mut *mut c_void) -> bool { + let mut cb_needed = 0; + EnumProcessModulesEx( + **process_handler, + h_mod as *mut *mut c_void as _, + size_of::<DWORD>() as DWORD, + &mut cb_needed, + LIST_MODULES_ALL, + ) != 0 +} + +unsafe fn get_exe(process_handler: &HandleWrapper, h_mod: *mut c_void) -> PathBuf { + let mut exe_buf = [0u16; MAX_PATH + 1]; + GetModuleFileNameExW( + **process_handler, + h_mod as _, + exe_buf.as_mut_ptr(), + MAX_PATH as DWORD + 1, + ); + + PathBuf::from(null_terminated_wchar_to_string(&exe_buf)) +} + +impl Process { + pub(crate) fn new_from_pid( + pid: Pid, + now: u64, + refresh_kind: ProcessRefreshKind, + ) -> Option<Process> { + unsafe { + let process_handler = get_process_handler(pid)?; + let mut info: MaybeUninit<PROCESS_BASIC_INFORMATION> = MaybeUninit::uninit(); + if NtQueryInformationProcess( + *process_handler, + ProcessBasicInformation, + info.as_mut_ptr() as *mut _, + size_of::<PROCESS_BASIC_INFORMATION>() as _, + null_mut(), + ) != 0 + { + return None; + } + let info = info.assume_init(); + let mut h_mod = null_mut(); + + let name = if get_h_mod(&process_handler, &mut h_mod) { + get_process_name(&process_handler, h_mod) + } else { + String::new() + }; + + let exe = get_exe(&process_handler, h_mod); + let mut root = exe.clone(); + root.pop(); + let (cmd, environ, cwd) = match get_process_params(&process_handler) { + Ok(args) => args, + Err(_e) => { + sysinfo_debug!("Failed to get process parameters: {}", _e); + (Vec::new(), Vec::new(), PathBuf::new()) + } + }; + let (start_time, run_time) = get_start_and_run_time(&process_handler, now); + let parent = if info.InheritedFromUniqueProcessId as usize != 0 { + Some(Pid(info.InheritedFromUniqueProcessId as _)) + } else { + None + }; + let user_id = get_process_user_id(&process_handler, refresh_kind); + Some(Process { + handle: Some(Arc::new(process_handler)), + name, + pid, + parent, + user_id, + cmd, + environ, + exe, + cwd, + root, + status: ProcessStatus::Run, + memory: 0, + virtual_memory: 0, + cpu_usage: 0., + cpu_calc_values: CPUsageCalculationValues::new(), + start_time, + run_time, + updated: true, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + }) + } + } + + pub(crate) fn new_full( + pid: Pid, + parent: Option<Pid>, + memory: u64, + virtual_memory: u64, + name: String, + now: u64, + refresh_kind: ProcessRefreshKind, + ) -> Process { + if let Some(handle) = get_process_handler(pid) { + let mut h_mod = null_mut(); + + unsafe { + let exe = if get_h_mod(&handle, &mut h_mod) { + get_exe(&handle, h_mod) + } else { + PathBuf::new() + }; + let mut root = exe.clone(); + root.pop(); + let (cmd, environ, cwd) = match get_process_params(&handle) { + Ok(args) => args, + Err(_e) => { + sysinfo_debug!("Failed to get process parameters: {}", _e); + (Vec::new(), Vec::new(), PathBuf::new()) + } + }; + let (start_time, run_time) = get_start_and_run_time(&handle, now); + let user_id = get_process_user_id(&handle, refresh_kind); + Process { + handle: Some(Arc::new(handle)), + name, + pid, + user_id, + parent, + cmd, + environ, + exe, + cwd, + root, + status: ProcessStatus::Run, + memory, + virtual_memory, + cpu_usage: 0., + cpu_calc_values: CPUsageCalculationValues::new(), + start_time, + run_time, + updated: true, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + } + } + } else { + Process { + handle: None, + name, + pid, + user_id: None, + parent, + cmd: Vec::new(), + environ: Vec::new(), + exe: get_executable_path(pid), + cwd: PathBuf::new(), + root: PathBuf::new(), + status: ProcessStatus::Run, + memory, + virtual_memory, + cpu_usage: 0., + cpu_calc_values: CPUsageCalculationValues::new(), + start_time: 0, + run_time: 0, + updated: true, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + } + } + } + + pub(crate) fn update( + &mut self, + refresh_kind: crate::ProcessRefreshKind, + nb_cpus: u64, + now: u64, + ) { + if refresh_kind.cpu() { + compute_cpu_usage(self, nb_cpus); + } + if refresh_kind.disk_usage() { + update_disk_usage(self); + } + self.run_time = now.saturating_sub(self.start_time()); + self.updated = true; + } + + pub(crate) fn get_handle(&self) -> Option<HANDLE> { + self.handle.as_ref().map(|h| ***h) + } +} + +impl ProcessExt for Process { + fn kill_with(&self, signal: Signal) -> Option<bool> { + super::system::convert_signal(signal)?; + let mut kill = process::Command::new("taskkill.exe"); + kill.arg("/PID").arg(self.pid.to_string()).arg("/F"); + kill.creation_flags(CREATE_NO_WINDOW); + match kill.output() { + Ok(o) => Some(o.status.success()), + Err(_) => Some(false), + } + } + + fn name(&self) -> &str { + &self.name + } + + fn cmd(&self) -> &[String] { + &self.cmd + } + + fn exe(&self) -> &Path { + self.exe.as_path() + } + + fn pid(&self) -> Pid { + self.pid + } + + fn environ(&self) -> &[String] { + &self.environ + } + + fn cwd(&self) -> &Path { + self.cwd.as_path() + } + + fn root(&self) -> &Path { + self.root.as_path() + } + + fn memory(&self) -> u64 { + self.memory + } + + fn virtual_memory(&self) -> u64 { + self.virtual_memory + } + + fn parent(&self) -> Option<Pid> { + self.parent + } + + fn status(&self) -> ProcessStatus { + self.status + } + + fn start_time(&self) -> u64 { + self.start_time + } + + fn run_time(&self) -> u64 { + self.run_time + } + + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn disk_usage(&self) -> DiskUsage { + DiskUsage { + written_bytes: self.written_bytes - self.old_written_bytes, + total_written_bytes: self.written_bytes, + read_bytes: self.read_bytes - self.old_read_bytes, + total_read_bytes: self.read_bytes, + } + } + + fn user_id(&self) -> Option<&Uid> { + self.user_id.as_ref() + } + + fn group_id(&self) -> Option<Gid> { + None + } +} + +unsafe fn get_start_and_run_time(handle: &HandleWrapper, now: u64) -> (u64, u64) { + let mut fstart: FILETIME = zeroed(); + let mut x = zeroed(); + + GetProcessTimes( + **handle, + &mut fstart as *mut FILETIME, + &mut x as *mut FILETIME, + &mut x as *mut FILETIME, + &mut x as *mut FILETIME, + ); + let tmp = super::utils::filetime_to_u64(fstart); + // 11_644_473_600 is the number of seconds between the Windows epoch (1601-01-01) and + // the linux epoch (1970-01-01). + let start = tmp / 10_000_000 - 11_644_473_600; + let run_time = check_sub(now, start); + (start, run_time) +} + +#[allow(clippy::uninit_vec)] +unsafe fn ph_query_process_variable_size( + process_handle: &HandleWrapper, + process_information_class: PROCESSINFOCLASS, +) -> Option<Vec<u16>> { + let mut return_length = MaybeUninit::<ULONG>::uninit(); + + let mut status = NtQueryInformationProcess( + **process_handle, + process_information_class, + null_mut(), + 0, + return_length.as_mut_ptr() as *mut _, + ); + + if status != STATUS_BUFFER_OVERFLOW + && status != STATUS_BUFFER_TOO_SMALL + && status != STATUS_INFO_LENGTH_MISMATCH + { + return None; + } + + let mut return_length = return_length.assume_init(); + let buf_len = (return_length as usize) / 2; + let mut buffer: Vec<u16> = Vec::with_capacity(buf_len + 1); + buffer.set_len(buf_len); + + status = NtQueryInformationProcess( + **process_handle, + process_information_class, + buffer.as_mut_ptr() as *mut _, + return_length, + &mut return_length as *mut _, + ); + if !NT_SUCCESS(status) { + return None; + } + buffer.push(0); + Some(buffer) +} + +unsafe fn get_cmdline_from_buffer(buffer: *const u16) -> Vec<String> { + // Get argc and argv from the command line + let mut argc = MaybeUninit::<i32>::uninit(); + let argv_p = winapi::um::shellapi::CommandLineToArgvW(buffer, argc.as_mut_ptr()); + if argv_p.is_null() { + return Vec::new(); + } + let argc = argc.assume_init(); + let argv = std::slice::from_raw_parts(argv_p, argc as usize); + + let mut res = Vec::new(); + for arg in argv { + let len = libc::wcslen(*arg); + let str_slice = std::slice::from_raw_parts(*arg, len); + res.push(String::from_utf16_lossy(str_slice)); + } + + winapi::um::winbase::LocalFree(argv_p as *mut _); + + res +} + +unsafe fn get_region_size(handle: &HandleWrapper, ptr: LPVOID) -> Result<usize, &'static str> { + let mut meminfo = MaybeUninit::<MEMORY_BASIC_INFORMATION>::uninit(); + if VirtualQueryEx( + **handle, + ptr, + meminfo.as_mut_ptr() as *mut _, + size_of::<MEMORY_BASIC_INFORMATION>(), + ) == 0 + { + return Err("Unable to read process memory information"); + } + let meminfo = meminfo.assume_init(); + Ok((meminfo.RegionSize as isize - ptr.offset_from(meminfo.BaseAddress)) as usize) +} + +#[allow(clippy::uninit_vec)] +unsafe fn get_process_data( + handle: &HandleWrapper, + ptr: LPVOID, + size: usize, +) -> Result<Vec<u16>, &'static str> { + let mut buffer: Vec<u16> = Vec::with_capacity(size / 2 + 1); + buffer.set_len(size / 2); + if ReadProcessMemory( + **handle, + ptr as *mut _, + buffer.as_mut_ptr() as *mut _, + size, + null_mut(), + ) != TRUE + { + return Err("Unable to read process data"); + } + Ok(buffer) +} + +trait RtlUserProcessParameters { + fn get_cmdline(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str>; + fn get_cwd(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str>; + fn get_environ(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str>; +} + +macro_rules! impl_RtlUserProcessParameters { + ($t:ty) => { + impl RtlUserProcessParameters for $t { + fn get_cmdline(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str> { + let ptr = self.CommandLine.Buffer; + let size = self.CommandLine.Length; + unsafe { get_process_data(handle, ptr as _, size as _) } + } + fn get_cwd(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str> { + let ptr = self.CurrentDirectory.DosPath.Buffer; + let size = self.CurrentDirectory.DosPath.Length; + unsafe { get_process_data(handle, ptr as _, size as _) } + } + fn get_environ(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str> { + let ptr = self.Environment; + unsafe { + let size = get_region_size(handle, ptr as LPVOID)?; + get_process_data(handle, ptr as _, size as _) + } + } + } + }; +} + +impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS32); +impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS); + +unsafe fn get_process_params( + handle: &HandleWrapper, +) -> Result<(Vec<String>, Vec<String>, PathBuf), &'static str> { + if !cfg!(target_pointer_width = "64") { + return Err("Non 64 bit targets are not supported"); + } + + // First check if target process is running in wow64 compatibility emulator + let mut pwow32info = MaybeUninit::<LPVOID>::uninit(); + let result = NtQueryInformationProcess( + **handle, + ProcessWow64Information, + pwow32info.as_mut_ptr() as *mut _, + size_of::<LPVOID>() as u32, + null_mut(), + ); + if !NT_SUCCESS(result) { + return Err("Unable to check WOW64 information about the process"); + } + let pwow32info = pwow32info.assume_init(); + + if pwow32info.is_null() { + // target is a 64 bit process + + let mut pbasicinfo = MaybeUninit::<PROCESS_BASIC_INFORMATION>::uninit(); + let result = NtQueryInformationProcess( + **handle, + ProcessBasicInformation, + pbasicinfo.as_mut_ptr() as *mut _, + size_of::<PROCESS_BASIC_INFORMATION>() as u32, + null_mut(), + ); + if !NT_SUCCESS(result) { + return Err("Unable to get basic process information"); + } + let pinfo = pbasicinfo.assume_init(); + + let mut peb = MaybeUninit::<PEB>::uninit(); + if ReadProcessMemory( + **handle, + pinfo.PebBaseAddress as *mut _, + peb.as_mut_ptr() as *mut _, + size_of::<PEB>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read process PEB"); + } + + let peb = peb.assume_init(); + + let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS>::uninit(); + if ReadProcessMemory( + **handle, + peb.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS as *mut _, + proc_params.as_mut_ptr() as *mut _, + size_of::<RTL_USER_PROCESS_PARAMETERS>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read process parameters"); + } + + let proc_params = proc_params.assume_init(); + return Ok(( + get_cmd_line(&proc_params, handle), + get_proc_env(&proc_params, handle), + get_cwd(&proc_params, handle), + )); + } + // target is a 32 bit process in wow64 mode + + let mut peb32 = MaybeUninit::<PEB32>::uninit(); + if ReadProcessMemory( + **handle, + pwow32info, + peb32.as_mut_ptr() as *mut _, + size_of::<PEB32>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read PEB32"); + } + let peb32 = peb32.assume_init(); + + let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS32>::uninit(); + if ReadProcessMemory( + **handle, + peb32.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS32 as *mut _, + proc_params.as_mut_ptr() as *mut _, + size_of::<RTL_USER_PROCESS_PARAMETERS32>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read 32 bit process parameters"); + } + let proc_params = proc_params.assume_init(); + Ok(( + get_cmd_line(&proc_params, handle), + get_proc_env(&proc_params, handle), + get_cwd(&proc_params, handle), + )) +} + +fn get_cwd<T: RtlUserProcessParameters>(params: &T, handle: &HandleWrapper) -> PathBuf { + match params.get_cwd(handle) { + Ok(buffer) => unsafe { PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())) }, + Err(_e) => { + sysinfo_debug!("get_cwd failed to get data: {}", _e); + PathBuf::new() + } + } +} + +unsafe fn null_terminated_wchar_to_string(slice: &[u16]) -> String { + match slice.iter().position(|&x| x == 0) { + Some(pos) => OsString::from_wide(&slice[..pos]) + .to_string_lossy() + .into_owned(), + None => OsString::from_wide(slice).to_string_lossy().into_owned(), + } +} + +fn get_cmd_line_old<T: RtlUserProcessParameters>( + params: &T, + handle: &HandleWrapper, +) -> Vec<String> { + match params.get_cmdline(handle) { + Ok(buffer) => unsafe { get_cmdline_from_buffer(buffer.as_ptr()) }, + Err(_e) => { + sysinfo_debug!("get_cmd_line_old failed to get data: {}", _e); + Vec::new() + } + } +} + +#[allow(clippy::cast_ptr_alignment)] +fn get_cmd_line_new(handle: &HandleWrapper) -> Vec<String> { + unsafe { + if let Some(buffer) = ph_query_process_variable_size(handle, ProcessCommandLineInformation) + { + let buffer = (*(buffer.as_ptr() as *const UNICODE_STRING)).Buffer; + + get_cmdline_from_buffer(buffer) + } else { + vec![] + } + } +} + +fn get_cmd_line<T: RtlUserProcessParameters>(params: &T, handle: &HandleWrapper) -> Vec<String> { + if *WINDOWS_8_1_OR_NEWER { + get_cmd_line_new(handle) + } else { + get_cmd_line_old(params, handle) + } +} + +fn get_proc_env<T: RtlUserProcessParameters>(params: &T, handle: &HandleWrapper) -> Vec<String> { + match params.get_environ(handle) { + Ok(buffer) => { + let equals = "=".encode_utf16().next().unwrap(); + let raw_env = buffer; + let mut result = Vec::new(); + let mut begin = 0; + while let Some(offset) = raw_env[begin..].iter().position(|&c| c == 0) { + let end = begin + offset; + if raw_env[begin..end].iter().any(|&c| c == equals) { + result.push( + OsString::from_wide(&raw_env[begin..end]) + .to_string_lossy() + .into_owned(), + ); + begin = end + 1; + } else { + break; + } + } + result + } + Err(_e) => { + sysinfo_debug!("get_proc_env failed to get data: {}", _e); + Vec::new() + } + } +} + +pub(crate) fn get_executable_path(_pid: Pid) -> PathBuf { + /*let where_req = format!("ProcessId={}", pid); + + if let Some(ret) = run_wmi(&["process", "where", &where_req, "get", "ExecutablePath"]) { + for line in ret.lines() { + if line.is_empty() || line == "ExecutablePath" { + continue + } + return line.to_owned(); + } + }*/ + PathBuf::new() +} + +#[inline] +fn check_sub(a: u64, b: u64) -> u64 { + if a < b { + a + } else { + a - b + } +} + +/// Before changing this function, you must consider the following: +/// https://github.com/GuillaumeGomez/sysinfo/issues/459 +pub(crate) fn compute_cpu_usage(p: &mut Process, nb_cpus: u64) { + unsafe { + let mut ftime: FILETIME = zeroed(); + let mut fsys: FILETIME = zeroed(); + let mut fuser: FILETIME = zeroed(); + let mut fglobal_idle_time: FILETIME = zeroed(); + let mut fglobal_kernel_time: FILETIME = zeroed(); // notice that it includes idle time + let mut fglobal_user_time: FILETIME = zeroed(); + + if let Some(handle) = p.get_handle() { + GetProcessTimes( + handle, + &mut ftime as *mut FILETIME, + &mut ftime as *mut FILETIME, + &mut fsys as *mut FILETIME, + &mut fuser as *mut FILETIME, + ); + } + GetSystemTimes( + &mut fglobal_idle_time as *mut FILETIME, + &mut fglobal_kernel_time as *mut FILETIME, + &mut fglobal_user_time as *mut FILETIME, + ); + + let mut sys: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut sys as *mut ULARGE_INTEGER as *mut c_void, + &mut fsys as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + let mut user: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut user as *mut ULARGE_INTEGER as *mut c_void, + &mut fuser as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + let mut global_kernel_time: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut global_kernel_time as *mut ULARGE_INTEGER as *mut c_void, + &mut fglobal_kernel_time as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + let mut global_user_time: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut global_user_time as *mut ULARGE_INTEGER as *mut c_void, + &mut fglobal_user_time as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + + let sys = *sys.QuadPart(); + let user = *user.QuadPart(); + let global_kernel_time = *global_kernel_time.QuadPart(); + let global_user_time = *global_user_time.QuadPart(); + + let delta_global_kernel_time = + check_sub(global_kernel_time, p.cpu_calc_values.old_system_sys_cpu); + let delta_global_user_time = + check_sub(global_user_time, p.cpu_calc_values.old_system_user_cpu); + let delta_user_time = check_sub(user, p.cpu_calc_values.old_process_user_cpu); + let delta_sys_time = check_sub(sys, p.cpu_calc_values.old_process_sys_cpu); + + p.cpu_calc_values.old_process_user_cpu = user; + p.cpu_calc_values.old_process_sys_cpu = sys; + p.cpu_calc_values.old_system_user_cpu = global_user_time; + p.cpu_calc_values.old_system_sys_cpu = global_kernel_time; + + let denominator = delta_global_user_time.saturating_add(delta_global_kernel_time) as f32; + + if denominator < 0.00001 { + p.cpu_usage = 0.; + return; + } + + p.cpu_usage = 100.0 + * (delta_user_time.saturating_add(delta_sys_time) as f32 / denominator as f32) + * nb_cpus as f32; + } +} + +pub(crate) fn update_disk_usage(p: &mut Process) { + let mut counters = MaybeUninit::<IO_COUNTERS>::uninit(); + + if let Some(handle) = p.get_handle() { + unsafe { + let ret = GetProcessIoCounters(handle, counters.as_mut_ptr()); + if ret == 0 { + sysinfo_debug!("GetProcessIoCounters call failed on process {}", p.pid()); + } else { + let counters = counters.assume_init(); + p.old_read_bytes = p.read_bytes; + p.old_written_bytes = p.written_bytes; + p.read_bytes = counters.ReadTransferCount; + p.written_bytes = counters.WriteTransferCount; + } + } + } +} + +pub(crate) fn update_memory(p: &mut Process) { + if let Some(handle) = p.get_handle() { + unsafe { + let mut pmc: PROCESS_MEMORY_COUNTERS_EX = zeroed(); + if GetProcessMemoryInfo( + handle, + &mut pmc as *mut PROCESS_MEMORY_COUNTERS_EX as *mut c_void + as *mut PROCESS_MEMORY_COUNTERS, + size_of::<PROCESS_MEMORY_COUNTERS_EX>() as DWORD, + ) != 0 + { + p.memory = (pmc.WorkingSetSize as u64) / 1_000; + p.virtual_memory = (pmc.PrivateUsage as u64) / 1_000; + } + } + } +} diff --git a/vendor/sysinfo/src/windows/system.rs b/vendor/sysinfo/src/windows/system.rs new file mode 100644 index 000000000..6abd30db5 --- /dev/null +++ b/vendor/sysinfo/src/windows/system.rs @@ -0,0 +1,623 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + CpuRefreshKind, LoadAvg, Networks, Pid, ProcessExt, ProcessRefreshKind, RefreshKind, SystemExt, + User, +}; +use winapi::um::winreg::HKEY_LOCAL_MACHINE; + +use crate::sys::component::{self, Component}; +use crate::sys::cpu::*; +use crate::sys::disk::{get_disks, Disk}; +use crate::sys::process::{update_memory, Process}; +use crate::sys::tools::*; +use crate::sys::users::get_users; +use crate::sys::utils::get_now; + +use crate::utils::into_iter; + +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::mem::{size_of, zeroed}; +use std::os::windows::ffi::OsStrExt; +use std::slice::from_raw_parts; +use std::time::SystemTime; + +use ntapi::ntexapi::{ + NtQuerySystemInformation, SystemProcessInformation, SYSTEM_PROCESS_INFORMATION, +}; +use winapi::ctypes::wchar_t; +use winapi::shared::minwindef::{DWORD, FALSE, HKEY, LPBYTE, TRUE}; +use winapi::shared::ntdef::{PVOID, ULONG}; +use winapi::shared::ntstatus::STATUS_INFO_LENGTH_MISMATCH; +use winapi::shared::winerror; +use winapi::um::minwinbase::STILL_ACTIVE; +use winapi::um::processthreadsapi::GetExitCodeProcess; +use winapi::um::psapi::{GetPerformanceInfo, PERFORMANCE_INFORMATION}; +use winapi::um::sysinfoapi::{ + ComputerNamePhysicalDnsHostname, GetComputerNameExW, GetTickCount64, GlobalMemoryStatusEx, + MEMORYSTATUSEX, +}; +use winapi::um::winnt::{HANDLE, KEY_READ}; +use winapi::um::winreg::{RegOpenKeyExW, RegQueryValueExW}; + +declare_signals! { + (), + Signal::Kill => (), + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: HashMap<Pid, Process>, + mem_total: u64, + mem_available: u64, + swap_total: u64, + swap_used: u64, + cpus: CpusWrapper, + components: Vec<Component>, + disks: Vec<Disk>, + query: Option<Query>, + networks: Networks, + boot_time: u64, + users: Vec<User>, +} + +// Useful for parallel iterations. +struct Wrap<T>(T); + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl<T> Send for Wrap<T> {} +unsafe impl<T> Sync for Wrap<T> {} + +unsafe fn boot_time() -> u64 { + match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + Ok(n) => n.as_secs().saturating_sub(GetTickCount64()) / 1000, + Err(_e) => { + sysinfo_debug!("Failed to compute boot time: {:?}", _e); + 0 + } + } +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + #[allow(non_snake_case)] + fn new_with_specifics(refreshes: RefreshKind) -> System { + let mut s = System { + process_list: HashMap::with_capacity(500), + mem_total: 0, + mem_available: 0, + swap_total: 0, + swap_used: 0, + cpus: CpusWrapper::new(), + components: Vec::new(), + disks: Vec::with_capacity(2), + query: None, + networks: Networks::new(), + boot_time: unsafe { boot_time() }, + users: Vec::new(), + }; + s.refresh_specifics(refreshes); + s + } + + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { + if self.query.is_none() { + self.query = Query::new(); + if let Some(ref mut query) = self.query { + add_english_counter( + r"\Processor(_Total)\% Processor Time".to_string(), + query, + get_key_used(self.cpus.global_cpu_mut()), + "tot_0".to_owned(), + ); + for (pos, proc_) in self.cpus.iter_mut(refresh_kind).enumerate() { + add_english_counter( + format!(r"\Processor({})\% Processor Time", pos), + query, + get_key_used(proc_), + format!("{}_0", pos), + ); + } + } + } + if let Some(ref mut query) = self.query { + query.refresh(); + let mut used_time = None; + if let Some(ref key_used) = *get_key_used(self.cpus.global_cpu_mut()) { + used_time = Some( + query + .get(&key_used.unique_id) + .expect("global_key_idle disappeared"), + ); + } + if let Some(used_time) = used_time { + self.cpus.global_cpu_mut().set_cpu_usage(used_time); + } + for p in self.cpus.iter_mut(refresh_kind) { + let mut used_time = None; + if let Some(ref key_used) = *get_key_used(p) { + used_time = Some( + query + .get(&key_used.unique_id) + .expect("key_used disappeared"), + ); + } + if let Some(used_time) = used_time { + p.set_cpu_usage(used_time); + } + } + if refresh_kind.frequency() { + self.cpus.get_frequencies(); + } + } + } + + fn refresh_memory(&mut self) { + unsafe { + let mut mem_info: MEMORYSTATUSEX = zeroed(); + mem_info.dwLength = size_of::<MEMORYSTATUSEX>() as u32; + GlobalMemoryStatusEx(&mut mem_info); + self.mem_total = auto_cast!(mem_info.ullTotalPhys, u64) / 1_000; + self.mem_available = auto_cast!(mem_info.ullAvailPhys, u64) / 1_000; + let mut perf_info: PERFORMANCE_INFORMATION = zeroed(); + if GetPerformanceInfo(&mut perf_info, size_of::<PERFORMANCE_INFORMATION>() as u32) + == TRUE + { + let swap_total = perf_info.PageSize.saturating_mul( + perf_info + .CommitLimit + .saturating_sub(perf_info.PhysicalTotal), + ); + let swap_used = perf_info.PageSize.saturating_mul( + perf_info + .CommitTotal + .saturating_sub(perf_info.PhysicalTotal), + ); + self.swap_total = (swap_total / 1000) as u64; + self.swap_used = (swap_used / 1000) as u64; + } + } + } + + fn refresh_components_list(&mut self) { + self.components = component::get_components(); + } + + #[allow(clippy::map_entry)] + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + if self.process_list.contains_key(&pid) { + return refresh_existing_process(self, pid, refresh_kind); + } + let now = get_now(); + if let Some(mut p) = Process::new_from_pid(pid, now, refresh_kind) { + p.update(refresh_kind, self.cpus.len() as u64, now); + p.updated = false; + self.process_list.insert(pid, p); + true + } else { + false + } + } + + #[allow(clippy::cast_ptr_alignment)] + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { + // Windows 10 notebook requires at least 512KiB of memory to make it in one go + let mut buffer_size: usize = 512 * 1024; + let now = get_now(); + + loop { + let mut process_information: Vec<u8> = Vec::with_capacity(buffer_size); + let mut cb_needed = 0; + + unsafe { + process_information.set_len(buffer_size); + let ntstatus = NtQuerySystemInformation( + SystemProcessInformation, + process_information.as_mut_ptr() as PVOID, + buffer_size as ULONG, + &mut cb_needed, + ); + + if ntstatus != STATUS_INFO_LENGTH_MISMATCH { + if ntstatus < 0 { + sysinfo_debug!( + "Couldn't get process infos: NtQuerySystemInformation returned {}", + ntstatus + ); + } + + // Parse the data block to get process information + let mut process_ids = Vec::with_capacity(500); + let mut process_information_offset = 0; + loop { + let p = process_information + .as_ptr() + .offset(process_information_offset) + as *const SYSTEM_PROCESS_INFORMATION; + let pi = &*p; + + process_ids.push(Wrap(p)); + + if pi.NextEntryOffset == 0 { + break; + } + + process_information_offset += pi.NextEntryOffset as isize; + } + let process_list = Wrap(UnsafeCell::new(&mut self.process_list)); + let nb_cpus = if refresh_kind.cpu() { + self.cpus.len() as u64 + } else { + 0 + }; + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + // TODO: instead of using parallel iterator only here, would be better to be + // able to run it over `process_information` directly! + let processes = into_iter(process_ids) + .filter_map(|pi| { + let pi = *pi.0; + let pid = Pid(pi.UniqueProcessId as _); + if let Some(proc_) = (*process_list.0.get()).get_mut(&pid) { + proc_.memory = (pi.WorkingSetSize as u64) / 1_000; + proc_.virtual_memory = (pi.VirtualSize as u64) / 1_000; + proc_.update(refresh_kind, nb_cpus, now); + return None; + } + let name = get_process_name(&pi, pid); + let mut p = Process::new_full( + pid, + if pi.InheritedFromUniqueProcessId as usize != 0 { + Some(Pid(pi.InheritedFromUniqueProcessId as _)) + } else { + None + }, + (pi.WorkingSetSize as u64) / 1_000, + (pi.VirtualSize as u64) / 1_000, + name, + now, + refresh_kind, + ); + p.update(refresh_kind, nb_cpus, now); + Some(p) + }) + .collect::<Vec<_>>(); + for p in processes.into_iter() { + self.process_list.insert(p.pid(), p); + } + self.process_list.retain(|_, v| { + let x = v.updated; + v.updated = false; + x + }); + + break; + } + + // GetNewBufferSize + if cb_needed == 0 { + buffer_size *= 2; + continue; + } + // allocating a few more kilo bytes just in case there are some new process + // kicked in since new call to NtQuerySystemInformation + buffer_size = (cb_needed + (1024 * 10)) as usize; + } + } + } + + fn refresh_disks_list(&mut self) { + self.disks = unsafe { get_disks() }; + } + + fn refresh_users_list(&mut self) { + self.users = unsafe { get_users() }; + } + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.get(&pid) + } + + fn global_cpu_info(&self) -> &Cpu { + self.cpus.global_cpu() + } + + fn cpus(&self) -> &[Cpu] { + self.cpus.cpus() + } + + fn physical_core_count(&self) -> Option<usize> { + get_physical_core_count() + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + // MEMORYSTATUSEX doesn't report free memory + self.mem_available + } + + fn available_memory(&self) -> u64 { + self.mem_available + } + + fn used_memory(&self) -> u64 { + self.mem_total - self.mem_available + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_total - self.swap_used + } + + fn used_swap(&self) -> u64 { + self.swap_used + } + + fn components(&self) -> &[Component] { + &self.components + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut self.components + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks + } + + fn users(&self) -> &[User] { + &self.users + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn uptime(&self) -> u64 { + unsafe { GetTickCount64() / 1000 } + } + + fn boot_time(&self) -> u64 { + self.boot_time + } + + fn load_average(&self) -> LoadAvg { + get_load_average() + } + + fn name(&self) -> Option<String> { + Some("Windows".to_owned()) + } + + fn long_os_version(&self) -> Option<String> { + get_reg_string_value( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "ProductName", + ) + } + + fn host_name(&self) -> Option<String> { + get_dns_hostname() + } + + fn kernel_version(&self) -> Option<String> { + get_reg_string_value( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "CurrentBuildNumber", + ) + } + + fn os_version(&self) -> Option<String> { + let major = get_reg_value_u32( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "CurrentMajorVersionNumber", + ); + + let build_number = get_reg_string_value( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "CurrentBuildNumber", + ); + + Some(format!( + "{} ({})", + u32::from_le_bytes(major.unwrap_or_default()), + build_number.unwrap_or_default() + )) + } +} + +impl Default for System { + fn default() -> System { + System::new() + } +} + +fn is_proc_running(handle: HANDLE) -> bool { + let mut exit_code = 0; + unsafe { + let ret = GetExitCodeProcess(handle, &mut exit_code); + !(ret == FALSE || exit_code != STILL_ACTIVE) + } +} + +fn refresh_existing_process(s: &mut System, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + if let Some(ref mut entry) = s.process_list.get_mut(&pid) { + if let Some(handle) = entry.get_handle() { + if !is_proc_running(handle) { + return false; + } + } else { + return false; + } + update_memory(entry); + entry.update(refresh_kind, s.cpus.len() as u64, get_now()); + entry.updated = false; + true + } else { + false + } +} + +#[allow(clippy::size_of_in_element_count)] +//^ needed for "name.Length as usize / std::mem::size_of::<u16>()" +pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: Pid) -> String { + let name = &process.ImageName; + if name.Buffer.is_null() { + match process_id.0 { + 0 => "Idle".to_owned(), + 4 => "System".to_owned(), + _ => format!("<no name> Process {}", process_id), + } + } else { + unsafe { + let slice = std::slice::from_raw_parts( + name.Buffer, + // The length is in bytes, not the length of string + name.Length as usize / std::mem::size_of::<u16>(), + ); + + String::from_utf16_lossy(slice) + } + } +} + +fn utf16_str<S: AsRef<OsStr> + ?Sized>(text: &S) -> Vec<u16> { + OsStr::new(text) + .encode_wide() + .chain(Some(0).into_iter()) + .collect::<Vec<_>>() +} + +fn get_reg_string_value(hkey: HKEY, path: &str, field_name: &str) -> Option<String> { + let c_path = utf16_str(path); + let c_field_name = utf16_str(field_name); + + let mut new_hkey: HKEY = std::ptr::null_mut(); + unsafe { + if RegOpenKeyExW(hkey, c_path.as_ptr(), 0, KEY_READ, &mut new_hkey) != 0 { + return None; + } + + let mut buf_len: DWORD = 2048; + let mut buf_type: DWORD = 0; + let mut buf: Vec<u8> = Vec::with_capacity(buf_len as usize); + loop { + match RegQueryValueExW( + new_hkey, + c_field_name.as_ptr(), + std::ptr::null_mut(), + &mut buf_type, + buf.as_mut_ptr() as LPBYTE, + &mut buf_len, + ) as DWORD + { + 0 => break, + winerror::ERROR_MORE_DATA => { + buf.reserve(buf_len as _); + } + _ => return None, + } + } + + buf.set_len(buf_len as _); + + let words = from_raw_parts(buf.as_ptr() as *const u16, buf.len() / 2); + let mut s = String::from_utf16_lossy(words); + while s.ends_with('\u{0}') { + s.pop(); + } + Some(s) + } +} + +fn get_reg_value_u32(hkey: HKEY, path: &str, field_name: &str) -> Option<[u8; 4]> { + let c_path = utf16_str(path); + let c_field_name = utf16_str(field_name); + + let mut new_hkey: HKEY = std::ptr::null_mut(); + unsafe { + if RegOpenKeyExW(hkey, c_path.as_ptr(), 0, KEY_READ, &mut new_hkey) != 0 { + return None; + } + + let mut buf_len: DWORD = 4; + let mut buf_type: DWORD = 0; + let mut buf = [0u8; 4]; + + match RegQueryValueExW( + new_hkey, + c_field_name.as_ptr(), + std::ptr::null_mut(), + &mut buf_type, + buf.as_mut_ptr() as LPBYTE, + &mut buf_len, + ) as DWORD + { + 0 => Some(buf), + _ => None, + } + } +} + +fn get_dns_hostname() -> Option<String> { + let mut buffer_size = 0; + // Running this first to get the buffer size since the DNS name can be longer than MAX_COMPUTERNAME_LENGTH + // setting the `lpBuffer` to null will return the buffer size + // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw + unsafe { + GetComputerNameExW( + ComputerNamePhysicalDnsHostname, + std::ptr::null_mut(), + &mut buffer_size, + ); + + // Setting the buffer with the new length + let mut buffer = vec![0_u16; buffer_size as usize]; + + // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-computer_name_format + if GetComputerNameExW( + ComputerNamePhysicalDnsHostname, + buffer.as_mut_ptr() as *mut wchar_t, + &mut buffer_size, + ) == TRUE + { + if let Some(pos) = buffer.iter().position(|c| *c == 0) { + buffer.resize(pos, 0); + } + + return String::from_utf16(&buffer).ok(); + } + } + + sysinfo_debug!("Failed to get computer hostname"); + None +} diff --git a/vendor/sysinfo/src/windows/tools.rs b/vendor/sysinfo/src/windows/tools.rs new file mode 100644 index 000000000..a0c334010 --- /dev/null +++ b/vendor/sysinfo/src/windows/tools.rs @@ -0,0 +1,55 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::cpu::{self, Cpu, Query}; +use crate::CpuRefreshKind; + +use std::mem::zeroed; + +use winapi::um::sysinfoapi::{GetSystemInfo, SYSTEM_INFO}; + +pub(crate) struct KeyHandler { + pub unique_id: String, +} + +impl KeyHandler { + pub fn new(unique_id: String) -> KeyHandler { + KeyHandler { unique_id } + } +} + +pub(crate) fn init_cpus(refresh_kind: CpuRefreshKind) -> (Vec<Cpu>, String, String) { + unsafe { + let mut sys_info: SYSTEM_INFO = zeroed(); + GetSystemInfo(&mut sys_info); + let (vendor_id, brand) = cpu::get_vendor_id_and_brand(&sys_info); + let nb_cpus = sys_info.dwNumberOfProcessors as usize; + let frequencies = if refresh_kind.frequency() { + cpu::get_frequencies(nb_cpus) + } else { + vec![0; nb_cpus] + }; + let mut ret = Vec::with_capacity(nb_cpus + 1); + for (nb, frequency) in frequencies.iter().enumerate() { + ret.push(Cpu::new_with_values( + format!("CPU {}", nb + 1), + vendor_id.clone(), + brand.clone(), + *frequency, + )); + } + (ret, vendor_id, brand) + } +} + +pub(crate) fn add_english_counter( + s: String, + query: &mut Query, + keys: &mut Option<KeyHandler>, + counter_name: String, +) { + let mut full = s.encode_utf16().collect::<Vec<_>>(); + full.push(0); + if query.add_english_counter(&counter_name, full) { + *keys = Some(KeyHandler::new(counter_name)); + } +} diff --git a/vendor/sysinfo/src/windows/users.rs b/vendor/sysinfo/src/windows/users.rs new file mode 100644 index 000000000..2fef05c3f --- /dev/null +++ b/vendor/sysinfo/src/windows/users.rs @@ -0,0 +1,181 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::utils::to_str; +use crate::{ + common::{Gid, Uid}, + User, +}; + +use std::ptr::null_mut; +use winapi::shared::lmcons::{MAX_PREFERRED_LENGTH, NET_API_STATUS}; +use winapi::shared::minwindef::DWORD; +use winapi::shared::ntstatus::STATUS_SUCCESS; +use winapi::shared::winerror::ERROR_MORE_DATA; +use winapi::um::lmaccess::{NetUserEnum, NetUserGetLocalGroups}; +use winapi::um::lmaccess::{ + FILTER_NORMAL_ACCOUNT, LG_INCLUDE_INDIRECT, LPLOCALGROUP_USERS_INFO_0, USER_INFO_0, +}; +use winapi::um::lmapibuf::NetApiBufferFree; +use winapi::um::ntlsa::{ + LsaEnumerateLogonSessions, LsaFreeReturnBuffer, LsaGetLogonSessionData, + PSECURITY_LOGON_SESSION_DATA, +}; +use winapi::um::winnt::{LPWSTR, PLUID}; + +// FIXME: once this is mreged in winapi, it can be removed. +#[allow(non_upper_case_globals)] +const NERR_Success: NET_API_STATUS = 0; + +unsafe fn get_groups_for_user(username: LPWSTR) -> Vec<String> { + let mut buf: LPLOCALGROUP_USERS_INFO_0 = null_mut(); + let mut nb_entries = 0; + let mut total_entries = 0; + let mut groups; + + let status = NetUserGetLocalGroups( + [0u16].as_ptr(), + username, + 0, + LG_INCLUDE_INDIRECT, + &mut buf as *mut _ as _, + MAX_PREFERRED_LENGTH, + &mut nb_entries, + &mut total_entries, + ); + + if status == NERR_Success { + groups = Vec::with_capacity(nb_entries as _); + + if !buf.is_null() { + for i in 0..nb_entries { + let tmp = buf.offset(i as _); + if tmp.is_null() { + break; + } + groups.push(to_str((*tmp).lgrui0_name)); + } + } + } else { + groups = Vec::new(); + sysinfo_debug!("NetUserGetLocalGroups failed with ret code {}", status); + } + if !buf.is_null() { + NetApiBufferFree(buf as *mut _); + } + + groups +} + +// FIXME: For now, the Uid is the user name, which is quite bad. Normally, there is `PSID` for +// that. But when getting the `PSID` from the processes, it doesn't match the ones we have for +// the users (`EqualSid`). Anyway, until I have time and motivation to fix this. It'll remain +// like that... +pub unsafe fn get_users() -> Vec<User> { + let mut users = Vec::new(); + let mut buffer: *mut USER_INFO_0 = null_mut(); + let mut nb_read = 0; + let mut total = 0; + let mut resume_handle: DWORD = 0; + + loop { + let status = NetUserEnum( + null_mut(), + 0, + FILTER_NORMAL_ACCOUNT, + &mut buffer as *mut _ as *mut _, + MAX_PREFERRED_LENGTH, + &mut nb_read, + &mut total, + &mut resume_handle as *mut _ as *mut _, + ); + if status == NERR_Success || status == ERROR_MORE_DATA { + let entries: &[USER_INFO_0] = std::slice::from_raw_parts(buffer, nb_read as _); + for entry in entries { + if entry.usri0_name.is_null() { + continue; + } + // let mut user: *mut USER_INFO_23 = null_mut(); + + // if NetUserGetInfo( + // null_mut(), + // entry.usri0_name, + // 23, + // &mut user as *mut _ as *mut _, + // ) == NERR_Success + // { + // let groups = get_groups_for_user((*user).usri23_name); + // users.push(User { + // uid: Uid(name.clone().into_boxed_str()), + // gid: Gid(0), + // name: to_str((*user).usri23_name), + // groups, + // }); + // } + // if !user.is_null() { + // NetApiBufferFree(user as *mut _); + // } + let groups = get_groups_for_user(entry.usri0_name); + let name = to_str(entry.usri0_name); + users.push(User { + uid: Uid(name.clone().into_boxed_str()), + gid: Gid(0), + name, + groups, + }); + } + } else { + sysinfo_debug!( + "NetUserEnum error: {}", + if status == winapi::shared::winerror::ERROR_ACCESS_DENIED { + "access denied" + } else if status == winapi::shared::winerror::ERROR_INVALID_LEVEL { + "invalid level" + } else { + "unknown error" + } + ); + } + if !buffer.is_null() { + NetApiBufferFree(buffer as *mut _); + buffer = null_mut(); + } + if status != ERROR_MORE_DATA { + break; + } + } + + // First part done. Second part now! + let mut nb_sessions = 0; + let mut uids: PLUID = null_mut(); + if LsaEnumerateLogonSessions(&mut nb_sessions, &mut uids) != STATUS_SUCCESS { + sysinfo_debug!("LsaEnumerateLogonSessions failed"); + } else { + for offset in 0..nb_sessions { + let entry = uids.add(offset as _); + let mut data: PSECURITY_LOGON_SESSION_DATA = null_mut(); + + if LsaGetLogonSessionData(entry, &mut data) == STATUS_SUCCESS && !data.is_null() { + let data = *data; + if data.LogonType == winapi::um::ntlsa::Network { + continue; + } + let name = to_str(data.UserName.Buffer); + if users.iter().any(|u| u.name == name) { + continue; + } + users.push(User { + uid: Uid(name.clone().into_boxed_str()), + gid: Gid(0), + name, + // There is no local groups for a non-local user. + groups: Vec::new(), + }); + } + if !data.is_null() { + LsaFreeReturnBuffer(data as *mut _); + } + } + } + + users +} diff --git a/vendor/sysinfo/src/windows/utils.rs b/vendor/sysinfo/src/windows/utils.rs new file mode 100644 index 000000000..419ee195c --- /dev/null +++ b/vendor/sysinfo/src/windows/utils.rs @@ -0,0 +1,36 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use winapi::shared::minwindef::FILETIME; +use winapi::um::winnt::LPWSTR; + +use std::time::SystemTime; + +#[inline] +pub(crate) fn filetime_to_u64(f: FILETIME) -> u64 { + (f.dwHighDateTime as u64) << 32 | (f.dwLowDateTime as u64) +} + +#[inline] +pub(crate) fn get_now() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|n| n.as_secs()) + .unwrap_or(0) +} + +pub(crate) unsafe fn to_str(p: LPWSTR) -> String { + let mut i = 0; + + loop { + let c = *p.offset(i); + if c == 0 { + break; + } + i += 1; + } + let s = std::slice::from_raw_parts(p, i as _); + String::from_utf16(s).unwrap_or_else(|_e| { + sysinfo_debug!("Failed to convert to UTF-16 string: {}", _e); + String::new() + }) +} |