blob: 3178b9bc4037fcf17a2e8a3f24f98113cf9e91e7 [file] [log] [blame]
// Take a look at the license at the top of the repository in the LICENSE file.
use crate::sys::system::is_proc_running;
use crate::windows::Sid;
use crate::{DiskUsage, Gid, Pid, ProcessRefreshKind, ProcessStatus, Signal, Uid};
use std::ffi::OsString;
use std::fmt;
#[cfg(feature = "debug")]
use std::io;
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;
use ntapi::ntexapi::{SystemProcessIdInformation, SYSTEM_PROCESS_ID_INFORMATION};
use ntapi::ntrtl::RTL_USER_PROCESS_PARAMETERS;
use ntapi::ntwow64::{PEB32, RTL_USER_PROCESS_PARAMETERS32};
use once_cell::sync::Lazy;
use windows::core::PCWSTR;
use windows::Wdk::System::SystemInformation::{NtQuerySystemInformation, SYSTEM_INFORMATION_CLASS};
use windows::Wdk::System::SystemServices::RtlGetVersion;
use windows::Wdk::System::Threading::{
NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation,
ProcessWow64Information, PROCESSINFOCLASS,
};
use windows::Win32::Foundation::{
CloseHandle, LocalFree, ERROR_INSUFFICIENT_BUFFER, FILETIME, HANDLE, HINSTANCE, HLOCAL,
MAX_PATH, STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH,
UNICODE_STRING,
};
use windows::Win32::Security::{GetTokenInformation, TokenUser, TOKEN_QUERY, TOKEN_USER};
use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
use windows::Win32::System::Memory::{
GetProcessHeap, HeapAlloc, HeapFree, LocalAlloc, VirtualQueryEx, HEAP_ZERO_MEMORY, LMEM_FIXED,
LMEM_ZEROINIT, MEMORY_BASIC_INFORMATION,
};
use windows::Win32::System::ProcessStatus::{
GetModuleFileNameExW, GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS_EX,
};
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
use windows::Win32::System::SystemInformation::OSVERSIONINFOEXW;
use windows::Win32::System::Threading::{
GetProcessIoCounters, GetProcessTimes, GetSystemTimes, OpenProcess, OpenProcessToken,
CREATE_NO_WINDOW, IO_COUNTERS, PEB, PROCESS_BASIC_INFORMATION, PROCESS_QUERY_INFORMATION,
PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ,
};
use windows::Win32::UI::Shell::CommandLineToArgvW;
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;
HandleWrapper::new(unsafe { OpenProcess(options, false, pid.0 as u32).unwrap_or_default() })
.or_else(|| {
sysinfo_debug!(
"OpenProcess failed, error: {:?}",
io::Error::last_os_error()
);
HandleWrapper::new(unsafe {
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid.0 as u32)
.unwrap_or_default()
})
})
.or_else(|| {
sysinfo_debug!(
"OpenProcess limited failed, error: {:?}",
io::Error::last_os_error()
);
None
})
}
unsafe fn get_process_user_id(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) {
struct HeapWrap<T>(*mut T);
impl<T> HeapWrap<T> {
unsafe fn new(size: u32) -> Option<Self> {
let ptr = HeapAlloc(GetProcessHeap().ok()?, 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 {
if let Ok(heap) = GetProcessHeap() {
let _err = HeapFree(heap, Default::default(), Some(self.0.cast()));
}
}
}
}
}
let handle = match &process.handle {
// We get back the pointer so we don't need to clone the wrapping `Arc`.
Some(handle) => ***handle,
None => return,
};
if !refresh_kind
.user()
.needs_update(|| process.user_id.is_none())
{
return;
}
let mut token = Default::default();
if OpenProcessToken(handle, TOKEN_QUERY, &mut token).is_err() {
sysinfo_debug!("OpenProcessToken failed");
return;
}
let token = match HandleWrapper::new(token) {
Some(token) => token,
None => return,
};
let mut size = 0;
if let Err(err) = GetTokenInformation(*token, TokenUser, None, 0, &mut size) {
if err.code() != ERROR_INSUFFICIENT_BUFFER.to_hresult() {
sysinfo_debug!("GetTokenInformation failed, error: {:?}", err);
return;
}
}
let ptu: HeapWrap<TOKEN_USER> = match HeapWrap::new(size) {
Some(ptu) => ptu,
None => return,
};
if let Err(_err) = GetTokenInformation(*token, TokenUser, Some(ptu.0.cast()), size, &mut size) {
sysinfo_debug!(
"GetTokenInformation failed (returned {_err:?}), error: {:?}",
io::Error::last_os_error()
);
return;
}
// We force this check to prevent overwritting a `Some` with a `None`.
if let Some(uid) = Sid::from_psid((*ptu.0).User.Sid).map(Uid) {
process.user_id = Some(uid);
}
}
struct HandleWrapper(HANDLE);
impl HandleWrapper {
fn new(handle: HANDLE) -> Option<Self> {
if handle.is_invalid() {
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) {
let _err = unsafe { CloseHandle(self.0) };
}
}
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for HandleWrapper {}
unsafe impl Sync for HandleWrapper {}
pub(crate) struct ProcessInner {
name: String,
cmd: Vec<String>,
exe: Option<PathBuf>,
pid: Pid,
user_id: Option<Uid>,
environ: Vec<String>,
cwd: Option<PathBuf>,
root: Option<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: OSVERSIONINFOEXW = MaybeUninit::zeroed().assume_init();
version_info.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOEXW>() as u32;
if RtlGetVersion((&mut version_info as *mut OSVERSIONINFOEXW).cast()).is_err() {
return true;
}
// Windows 8.1 is 6.3
version_info.dwMajorVersion > 6
|| version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 3
});
#[cfg(feature = "debug")]
unsafe fn display_ntstatus_error(ntstatus: windows::core::HRESULT) {
let code = ntstatus.0;
let message = ntstatus.message();
sysinfo_debug!(
"Couldn't get process infos: NtQuerySystemInformation returned {}: {}",
code,
message
);
}
// Take a look at https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm
// for explanations.
unsafe fn get_process_name(pid: Pid) -> Option<String> {
let mut info = SYSTEM_PROCESS_ID_INFORMATION {
ProcessId: pid.0 as _,
ImageName: MaybeUninit::zeroed().assume_init(),
};
// `MaximumLength` MUST BE a power of 2: here 32768 because the the returned name may be a full
// UNC path (up to 32767).
info.ImageName.MaximumLength = 1 << 15;
for i in 0.. {
let local_alloc = LocalAlloc(
LMEM_FIXED | LMEM_ZEROINIT,
info.ImageName.MaximumLength as _,
);
match local_alloc {
Ok(buf) if !buf.0.is_null() => info.ImageName.Buffer = buf.0.cast(),
_ => {
sysinfo_debug!("Couldn't get process infos: LocalAlloc failed");
return None;
}
}
match NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS(SystemProcessIdInformation as _),
&mut info as *mut _ as *mut _,
size_of::<SYSTEM_PROCESS_ID_INFORMATION>() as _,
null_mut(),
)
.ok()
{
Ok(()) => break,
Err(err) if err.code() == STATUS_INFO_LENGTH_MISMATCH.to_hresult() => {
if !info.ImageName.Buffer.is_null() {
let _err = LocalFree(HLOCAL(info.ImageName.Buffer.cast()));
}
if i > 2 {
// Too many iterations, we should have the correct length at this point
// normally, aborting name retrieval.
sysinfo_debug!(
"NtQuerySystemInformation returned `STATUS_INFO_LENGTH_MISMATCH` too many times"
);
return None;
}
// New length has been set into `MaximumLength` so we just continue the loop.
}
Err(_err) => {
if !info.ImageName.Buffer.is_null() {
let _err = LocalFree(HLOCAL(info.ImageName.Buffer.cast()));
}
#[cfg(feature = "debug")]
{
display_ntstatus_error(_err.code());
}
return None;
}
}
}
if info.ImageName.Buffer.is_null() {
return None;
}
let s = std::slice::from_raw_parts(
info.ImageName.Buffer,
// The length is in bytes, not the length of string
info.ImageName.Length as usize / std::mem::size_of::<u16>(),
);
let os_str = OsString::from_wide(s);
let name = Path::new(&os_str)
.file_name()
.map(|s| s.to_string_lossy().to_string());
let _err = LocalFree(HLOCAL(info.ImageName.Buffer.cast()));
name
}
unsafe fn get_exe(process_handler: &HandleWrapper) -> Option<PathBuf> {
let mut exe_buf = [0u16; MAX_PATH as usize + 1];
GetModuleFileNameExW(
**process_handler,
HINSTANCE::default(),
exe_buf.as_mut_slice(),
);
Some(PathBuf::from(null_terminated_wchar_to_string(&exe_buf)))
}
impl ProcessInner {
pub(crate) fn new_from_pid(
pid: Pid,
now: u64,
refresh_kind: ProcessRefreshKind,
) -> Option<Self> {
unsafe {
let process_handler = get_process_handler(pid)?;
let mut info: MaybeUninit<PROCESS_BASIC_INFORMATION> = MaybeUninit::uninit();
if NtQueryInformationProcess(
process_handler.0,
ProcessBasicInformation,
info.as_mut_ptr().cast(),
size_of::<PROCESS_BASIC_INFORMATION>() as _,
null_mut(),
)
.is_err()
{
return None;
}
let info = info.assume_init();
let name = get_process_name(pid).unwrap_or_default();
let exe = if refresh_kind.exe().needs_update(|| true) {
get_exe(&process_handler)
} else {
None
};
let (start_time, run_time) = get_start_and_run_time(*process_handler, now);
let parent = if info.InheritedFromUniqueProcessId != 0 {
Some(Pid(info.InheritedFromUniqueProcessId as _))
} else {
None
};
let mut p = Self {
handle: Some(Arc::new(process_handler)),
name,
pid,
parent,
user_id: None,
cmd: Vec::new(),
environ: Vec::new(),
exe,
cwd: None,
root: None,
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,
};
get_process_user_id(&mut p, refresh_kind);
get_process_params(&mut p, refresh_kind);
Some(p)
}
}
pub(crate) fn new_full(
pid: Pid,
parent: Option<Pid>,
memory: u64,
virtual_memory: u64,
name: String,
now: u64,
refresh_kind: ProcessRefreshKind,
) -> Self {
if let Some(handle) = get_process_handler(pid) {
unsafe {
let exe = if refresh_kind.exe().needs_update(|| true) {
get_exe(&handle)
} else {
None
};
let (start_time, run_time) = get_start_and_run_time(*handle, now);
let mut p = Self {
handle: Some(Arc::new(handle)),
name,
pid,
user_id: None,
parent,
cmd: Vec::new(),
environ: Vec::new(),
exe,
cwd: None,
root: None,
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,
};
get_process_user_id(&mut p, refresh_kind);
get_process_params(&mut p, refresh_kind);
p
}
} else {
let exe = if refresh_kind.exe().needs_update(|| true) {
get_executable_path(pid)
} else {
None
};
Self {
handle: None,
name,
pid,
user_id: None,
parent,
cmd: Vec::new(),
environ: Vec::new(),
exe,
cwd: None,
root: None,
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);
}
if refresh_kind.memory() {
update_memory(self);
}
unsafe {
get_process_user_id(self, refresh_kind);
get_process_params(self, refresh_kind);
}
if refresh_kind.exe().needs_update(|| self.exe.is_none()) {
unsafe {
self.exe = match self.handle.as_ref() {
Some(handle) => get_exe(handle),
None => get_executable_path(self.pid),
};
}
}
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)
}
pub(crate) fn get_start_time(&self) -> Option<u64> {
self.handle.as_ref().map(|handle| get_start_time(***handle))
}
pub(crate) fn kill_with(&self, signal: Signal) -> Option<bool> {
crate::sys::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.0);
match kill.output() {
Ok(o) => Some(o.status.success()),
Err(_) => Some(false),
}
}
pub(crate) fn name(&self) -> &str {
&self.name
}
pub(crate) fn cmd(&self) -> &[String] {
&self.cmd
}
pub(crate) fn exe(&self) -> Option<&Path> {
self.exe.as_deref()
}
pub(crate) fn pid(&self) -> Pid {
self.pid
}
pub(crate) fn environ(&self) -> &[String] {
&self.environ
}
pub(crate) fn cwd(&self) -> Option<&Path> {
self.cwd.as_deref()
}
pub(crate) fn root(&self) -> Option<&Path> {
self.root.as_deref()
}
pub(crate) fn memory(&self) -> u64 {
self.memory
}
pub(crate) fn virtual_memory(&self) -> u64 {
self.virtual_memory
}
pub(crate) fn parent(&self) -> Option<Pid> {
self.parent
}
pub(crate) fn status(&self) -> ProcessStatus {
self.status
}
pub(crate) fn start_time(&self) -> u64 {
self.start_time
}
pub(crate) fn run_time(&self) -> u64 {
self.run_time
}
pub(crate) fn cpu_usage(&self) -> f32 {
self.cpu_usage
}
pub(crate) fn disk_usage(&self) -> DiskUsage {
DiskUsage {
written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
total_written_bytes: self.written_bytes,
read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
total_read_bytes: self.read_bytes,
}
}
pub(crate) fn user_id(&self) -> Option<&Uid> {
self.user_id.as_ref()
}
pub(crate) fn effective_user_id(&self) -> Option<&Uid> {
None
}
pub(crate) fn group_id(&self) -> Option<Gid> {
None
}
pub(crate) fn effective_group_id(&self) -> Option<Gid> {
None
}
pub(crate) fn wait(&self) {
if let Some(handle) = self.get_handle() {
while is_proc_running(handle) {
if get_start_time(handle) != self.start_time() {
// PID owner changed so the previous process was finished!
return;
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
} else {
// In this case, we can't do anything so we just return.
sysinfo_debug!("can't wait on this process so returning");
}
}
pub(crate) fn session_id(&self) -> Option<Pid> {
unsafe {
let mut out = 0;
if ProcessIdToSessionId(self.pid.as_u32(), &mut out).is_ok() {
return Some(Pid(out as _));
}
sysinfo_debug!(
"ProcessIdToSessionId failed, error: {:?}",
io::Error::last_os_error()
);
None
}
}
}
#[inline]
unsafe fn get_process_times(handle: HANDLE) -> u64 {
let mut fstart: FILETIME = zeroed();
let mut x = zeroed();
let _err = GetProcessTimes(
handle,
&mut fstart as *mut FILETIME,
&mut x as *mut FILETIME,
&mut x as *mut FILETIME,
&mut x as *mut FILETIME,
);
super::utils::filetime_to_u64(fstart)
}
// On Windows, the root folder is always the current drive. So we get it from its `cwd`.
fn update_root(refresh_kind: ProcessRefreshKind, cwd: &Path, root: &mut Option<PathBuf>) {
if !refresh_kind.root().needs_update(|| root.is_none()) {
return;
}
if cwd.has_root() {
let mut ancestors = cwd.ancestors().peekable();
while let Some(path) = ancestors.next() {
if ancestors.peek().is_none() {
*root = Some(path.into());
return;
}
}
} else {
*root = None;
}
}
#[inline]
fn compute_start(process_times: u64) -> u64 {
// 11_644_473_600 is the number of seconds between the Windows epoch (1601-01-01) and
// the Linux epoch (1970-01-01).
process_times / 10_000_000 - 11_644_473_600
}
fn get_start_and_run_time(handle: HANDLE, now: u64) -> (u64, u64) {
unsafe {
let process_times = get_process_times(handle);
let start = compute_start(process_times);
let run_time = check_sub(now, start);
(start, run_time)
}
}
#[inline]
pub(crate) fn get_start_time(handle: HANDLE) -> u64 {
unsafe {
let process_times = get_process_times(handle);
compute_start(process_times)
}
}
unsafe fn ph_query_process_variable_size(
process_handle: HANDLE,
process_information_class: PROCESSINFOCLASS,
) -> Option<Vec<u16>> {
let mut return_length = MaybeUninit::<u32>::uninit();
if let Err(err) = NtQueryInformationProcess(
process_handle,
process_information_class as _,
null_mut(),
0,
return_length.as_mut_ptr() as *mut _,
)
.ok()
{
if ![
STATUS_BUFFER_OVERFLOW.into(),
STATUS_BUFFER_TOO_SMALL.into(),
STATUS_INFO_LENGTH_MISMATCH.into(),
]
.contains(&err.code())
{
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);
if NtQueryInformationProcess(
process_handle,
process_information_class as _,
buffer.as_mut_ptr() as *mut _,
return_length,
&mut return_length as *mut _,
)
.is_err()
{
return None;
}
buffer.set_len(buf_len);
buffer.push(0);
Some(buffer)
}
unsafe fn get_cmdline_from_buffer(buffer: PCWSTR) -> Vec<String> {
// Get argc and argv from the command line
let mut argc = MaybeUninit::<i32>::uninit();
let argv_p = 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 {
res.push(String::from_utf16_lossy(arg.as_wide()));
}
let _err = LocalFree(HLOCAL(argv_p as _));
res
}
unsafe fn get_region_size(handle: HANDLE, ptr: *const c_void) -> Result<usize, &'static str> {
let mut meminfo = MaybeUninit::<MEMORY_BASIC_INFORMATION>::uninit();
if VirtualQueryEx(
handle,
Some(ptr),
meminfo.as_mut_ptr().cast(),
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)
}
unsafe fn get_process_data(
handle: HANDLE,
ptr: *const c_void,
size: usize,
) -> Result<Vec<u16>, &'static str> {
let mut buffer: Vec<u16> = Vec::with_capacity(size / 2 + 1);
let mut bytes_read = 0;
if ReadProcessMemory(
handle,
ptr,
buffer.as_mut_ptr().cast(),
size,
Some(&mut bytes_read),
)
.is_err()
{
return Err("Unable to read process data");
}
// Documentation states that the function fails if not all data is accessible.
if bytes_read != size {
return Err("ReadProcessMemory returned unexpected number of bytes read");
}
buffer.set_len(size / 2);
buffer.push(0);
Ok(buffer)
}
trait RtlUserProcessParameters {
fn get_cmdline(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str>;
fn get_cwd(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str>;
fn get_environ(&self, handle: HANDLE) -> Result<Vec<u16>, &'static str>;
}
macro_rules! impl_RtlUserProcessParameters {
($t:ty) => {
impl RtlUserProcessParameters for $t {
fn get_cmdline(&self, handle: HANDLE) -> 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: HANDLE) -> 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: HANDLE) -> Result<Vec<u16>, &'static str> {
let ptr = self.Environment;
unsafe {
let size = get_region_size(handle, ptr as _)?;
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(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) {
if !(refresh_kind.cmd().needs_update(|| process.cmd.is_empty())
|| refresh_kind
.environ()
.needs_update(|| process.environ.is_empty())
|| refresh_kind.cwd().needs_update(|| process.cwd.is_none())
|| refresh_kind.root().needs_update(|| process.root.is_none()))
{
return;
}
let handle = match process.handle.as_ref().map(|handle| handle.0) {
Some(h) => h,
None => return,
};
// First check if target process is running in wow64 compatibility emulator
let mut pwow32info = MaybeUninit::<*const c_void>::uninit();
if NtQueryInformationProcess(
handle,
ProcessWow64Information,
pwow32info.as_mut_ptr().cast(),
size_of::<*const c_void>() as u32,
null_mut(),
)
.is_err()
{
sysinfo_debug!("Unable to check WOW64 information about the process");
return;
}
let pwow32info = pwow32info.assume_init();
if pwow32info.is_null() {
// target is a 64 bit process
let mut pbasicinfo = MaybeUninit::<PROCESS_BASIC_INFORMATION>::uninit();
if NtQueryInformationProcess(
handle,
ProcessBasicInformation,
pbasicinfo.as_mut_ptr().cast(),
size_of::<PROCESS_BASIC_INFORMATION>() as u32,
null_mut(),
)
.is_err()
{
sysinfo_debug!("Unable to get basic process information");
return;
}
let pinfo = pbasicinfo.assume_init();
let mut peb = MaybeUninit::<PEB>::uninit();
if ReadProcessMemory(
handle,
pinfo.PebBaseAddress.cast(),
peb.as_mut_ptr().cast(),
size_of::<PEB>(),
None,
)
.is_err()
{
sysinfo_debug!("Unable to read process PEB");
return;
}
let peb = peb.assume_init();
let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS>::uninit();
if ReadProcessMemory(
handle,
peb.ProcessParameters.cast(),
proc_params.as_mut_ptr().cast(),
size_of::<RTL_USER_PROCESS_PARAMETERS>(),
None,
)
.is_err()
{
sysinfo_debug!("Unable to read process parameters");
return;
}
let proc_params = proc_params.assume_init();
get_cmd_line(&proc_params, handle, refresh_kind, &mut process.cmd);
get_proc_env(&proc_params, handle, refresh_kind, &mut process.environ);
get_cwd_and_root(
&proc_params,
handle,
refresh_kind,
&mut process.cwd,
&mut process.root,
);
}
// target is a 32 bit process in wow64 mode
let mut peb32 = MaybeUninit::<PEB32>::uninit();
if ReadProcessMemory(
handle,
pwow32info,
peb32.as_mut_ptr().cast(),
size_of::<PEB32>(),
None,
)
.is_err()
{
sysinfo_debug!("Unable to read PEB32");
return;
}
let peb32 = peb32.assume_init();
let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS32>::uninit();
if ReadProcessMemory(
handle,
peb32.ProcessParameters as *mut _,
proc_params.as_mut_ptr().cast(),
size_of::<RTL_USER_PROCESS_PARAMETERS32>(),
None,
)
.is_err()
{
sysinfo_debug!("Unable to read 32 bit process parameters");
return;
}
let proc_params = proc_params.assume_init();
get_cmd_line(&proc_params, handle, refresh_kind, &mut process.cmd);
get_proc_env(&proc_params, handle, refresh_kind, &mut process.environ);
get_cwd_and_root(
&proc_params,
handle,
refresh_kind,
&mut process.cwd,
&mut process.root,
);
}
fn get_cwd_and_root<T: RtlUserProcessParameters>(
params: &T,
handle: HANDLE,
refresh_kind: ProcessRefreshKind,
cwd: &mut Option<PathBuf>,
root: &mut Option<PathBuf>,
) {
let cwd_needs_update = refresh_kind.cwd().needs_update(|| cwd.is_none());
let root_needs_update = refresh_kind.root().needs_update(|| root.is_none());
if !cwd_needs_update && !root_needs_update {
return;
}
match params.get_cwd(handle) {
Ok(buffer) => unsafe {
let tmp_cwd = PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice()));
// Should always be called after we refreshed `cwd`.
update_root(refresh_kind, &tmp_cwd, root);
if cwd_needs_update {
*cwd = Some(tmp_cwd);
}
},
Err(_e) => {
sysinfo_debug!("get_cwd_and_root failed to get data: {:?}", _e);
*cwd = None;
}
}
}
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: HANDLE) -> Vec<String> {
match params.get_cmdline(handle) {
Ok(buffer) => unsafe { get_cmdline_from_buffer(PCWSTR::from_raw(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: HANDLE) -> 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(PCWSTR::from_raw(buffer.as_ptr()))
} else {
vec![]
}
}
}
fn get_cmd_line<T: RtlUserProcessParameters>(
params: &T,
handle: HANDLE,
refresh_kind: ProcessRefreshKind,
cmd_line: &mut Vec<String>,
) {
if !refresh_kind.cmd().needs_update(|| cmd_line.is_empty()) {
return;
}
if *WINDOWS_8_1_OR_NEWER {
*cmd_line = get_cmd_line_new(handle);
} else {
*cmd_line = get_cmd_line_old(params, handle);
}
}
fn get_proc_env<T: RtlUserProcessParameters>(
params: &T,
handle: HANDLE,
refresh_kind: ProcessRefreshKind,
environ: &mut Vec<String>,
) {
if !refresh_kind.environ().needs_update(|| environ.is_empty()) {
return;
}
match params.get_environ(handle) {
Ok(buffer) => {
let equals = "=".encode_utf16().next().unwrap();
let raw_env = buffer;
environ.clear();
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) {
environ.push(
OsString::from_wide(&raw_env[begin..end])
.to_string_lossy()
.into_owned(),
);
begin = end + 1;
} else {
break;
}
}
}
Err(_e) => {
sysinfo_debug!("get_proc_env failed to get data: {}", _e);
*environ = Vec::new();
}
}
}
pub(crate) fn get_executable_path(_pid: Pid) -> Option<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();
}
}*/
None
}
#[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 ProcessInner, 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() {
let _err = GetProcessTimes(handle, &mut ftime, &mut ftime, &mut fsys, &mut fuser);
}
// FIXME: should these values be stored in one place to make use of
// `MINIMUM_CPU_UPDATE_INTERVAL`?
let _err = GetSystemTimes(
Some(&mut fglobal_idle_time),
Some(&mut fglobal_kernel_time),
Some(&mut fglobal_user_time),
);
let sys = filetime_to_u64(fsys);
let user = filetime_to_u64(fuser);
let global_kernel_time = filetime_to_u64(fglobal_kernel_time);
let global_user_time = filetime_to_u64(fglobal_user_time);
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)
* nb_cpus as f32;
}
}
pub(crate) fn update_disk_usage(p: &mut ProcessInner) {
let mut counters = MaybeUninit::<IO_COUNTERS>::uninit();
if let Some(handle) = p.get_handle() {
unsafe {
if GetProcessIoCounters(handle, counters.as_mut_ptr()).is_err() {
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 ProcessInner) {
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).cast(),
size_of::<PROCESS_MEMORY_COUNTERS_EX>() as _,
)
.is_ok()
{
p.memory = pmc.WorkingSetSize as _;
p.virtual_memory = pmc.PrivateUsage as _;
}
}
}
}
#[inline(always)]
const fn filetime_to_u64(ft: FILETIME) -> u64 {
((ft.dwHighDateTime as u64) << 32) + ft.dwLowDateTime as u64
}