From 2aa491af1ad9c60823a047769fadb2eafdde9c71 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Fri, 31 Dec 2021 19:20:51 -0700 Subject: [PATCH] mux: remove dep on sysinfo; we now do our own win32 stuff This also removes the sysinfo cache; will replace it with something else in a follow up commit. --- Cargo.lock | 17 +- mux/Cargo.toml | 7 +- mux/src/lib.rs | 1 - mux/src/localpane.rs | 7 + mux/src/procinfo.rs | 445 +++++++++++++++++++++++++++++++++++++------ mux/src/sysinfo.rs | 57 ------ 6 files changed, 400 insertions(+), 134 deletions(-) delete mode 100644 mux/src/sysinfo.rs diff --git a/Cargo.lock b/Cargo.lock index 5102fcd6a..00070c142 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2292,6 +2292,7 @@ dependencies = [ "luahelper", "metrics", "mlua", + "ntapi", "portable-pty", "promise", "rangeset", @@ -2299,7 +2300,6 @@ dependencies = [ "regex", "serde", "smol", - "sysinfo", "terminfo", "termwiz", "textwrap 0.14.2", @@ -3944,21 +3944,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "sysinfo" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645b59c59114c25d3d554f781b0a1f1f01545d1d02f271bfb1c897bdfdfdcf3" -dependencies = [ - "cfg-if 1.0.0", - "core-foundation-sys 0.8.3", - "libc", - "ntapi", - "once_cell", - "rayon", - "winapi 0.3.9", -] - [[package]] name = "tabout" version = "0.3.0" diff --git a/mux/Cargo.toml b/mux/Cargo.toml index 85a36699c..9e007c92b 100644 --- a/mux/Cargo.toml +++ b/mux/Cargo.toml @@ -39,10 +39,13 @@ wezterm-ssh = { path = "../wezterm-ssh" } wezterm-term = { path = "../term", features=["use_serde"] } [target."cfg(windows)".dependencies] -sysinfo = "0.22" +ntapi = "0.3" winapi = { version = "0.3", features = [ "handleapi", - "processthreadsapi" + "memoryapi", + "psapi", + "processthreadsapi", + "tlhelp32", ]} [dev-dependencies] diff --git a/mux/src/lib.rs b/mux/src/lib.rs index 406d11c87..250560ede 100644 --- a/mux/src/lib.rs +++ b/mux/src/lib.rs @@ -32,7 +32,6 @@ pub mod pane; pub mod procinfo; pub mod renderable; pub mod ssh; -mod sysinfo; pub mod tab; pub mod termwiztermtab; pub mod tmux; diff --git a/mux/src/localpane.rs b/mux/src/localpane.rs index 3c27e0d74..827251d79 100644 --- a/mux/src/localpane.rs +++ b/mux/src/localpane.rs @@ -844,6 +844,12 @@ impl LocalPane { return LocalProcessInfo::with_root_pid_linux(*pid); } + #[cfg(windows)] + if let ProcessState::Running { pid: Some(pid), .. } = &*self.process.borrow() { + return dbg!(LocalProcessInfo::with_root_pid_windows(*pid)); + } + + /* #[cfg(windows)] if let ProcessState::Running { pid: Some(pid), .. } = &*self.process.borrow() { return LocalProcessInfo::with_root_pid( @@ -855,6 +861,7 @@ impl LocalPane { *pid, ); } + */ None } diff --git a/mux/src/procinfo.rs b/mux/src/procinfo.rs index b4974992f..a6ba783f8 100644 --- a/mux/src/procinfo.rs +++ b/mux/src/procinfo.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; +use std::time::{Duration, SystemTime}; #[derive(Debug, Serialize, Deserialize, Copy, Clone)] pub enum LocalProcessStatus { @@ -18,26 +19,6 @@ pub enum LocalProcessStatus { Unknown, } -#[cfg(windows)] -impl LocalProcessStatus { - fn from_process_status(status: sysinfo::ProcessStatus) -> Self { - match status { - sysinfo::ProcessStatus::Idle => Self::Idle, - sysinfo::ProcessStatus::Run => Self::Run, - sysinfo::ProcessStatus::Sleep => Self::Sleep, - sysinfo::ProcessStatus::Stop => Self::Stop, - sysinfo::ProcessStatus::Zombie => Self::Zombie, - sysinfo::ProcessStatus::Tracing => Self::Tracing, - sysinfo::ProcessStatus::Dead => Self::Dead, - sysinfo::ProcessStatus::Wakekill => Self::Wakekill, - sysinfo::ProcessStatus::Waking => Self::Waking, - sysinfo::ProcessStatus::Parked => Self::Parked, - sysinfo::ProcessStatus::LockBlocked => Self::LockBlocked, - sysinfo::ProcessStatus::Unknown(_) => Self::Unknown, - } - } -} - #[cfg(target_os = "linux")] impl From<&str> for LocalProcessStatus { fn from(s: &str) -> Self { @@ -81,7 +62,7 @@ pub struct LocalProcessInfo { pub cwd: PathBuf, pub status: LocalProcessStatus, pub children: HashMap, - pub start_time: u64, + pub start_time: SystemTime, } luahelper::impl_lua_conversion!(LocalProcessInfo); @@ -160,6 +141,7 @@ impl LocalProcessInfo { fn cwd_for_pid(pid: pid_t) -> PathBuf { std::fs::read_link(format!("/proc/{}/cwd", pid)).unwrap_or_else(|_| PathBuf::new()) } + fn parse_cmdline(pid: pid_t) -> Vec { let data = match std::fs::read(format!("/proc/{}/cmdline", pid)) { Ok(data) => data, @@ -213,6 +195,390 @@ impl LocalProcessInfo { } } +#[cfg(windows)] +impl LocalProcessInfo { + pub(crate) fn with_root_pid_windows(pid: u32) -> Option { + use ntapi::ntpebteb::PEB; + use ntapi::ntpsapi::{ + NtQueryInformationProcess, ProcessBasicInformation, ProcessWow64Information, + PROCESS_BASIC_INFORMATION, + }; + use ntapi::ntrtl::RTL_USER_PROCESS_PARAMETERS; + use ntapi::ntwow64::RTL_USER_PROCESS_PARAMETERS32; + use std::ffi::OsString; + use std::mem::MaybeUninit; + use std::os::windows::ffi::OsStringExt; + use winapi::shared::minwindef::{FILETIME, HMODULE, LPVOID, MAX_PATH}; + use winapi::shared::ntdef::{FALSE, NT_SUCCESS}; + use winapi::um::handleapi::CloseHandle; + use winapi::um::memoryapi::ReadProcessMemory; + use winapi::um::processthreadsapi::{GetProcessTimes, OpenProcess}; + use winapi::um::psapi::{EnumProcessModulesEx, GetModuleFileNameExW, LIST_MODULES_ALL}; + use winapi::um::shellapi::CommandLineToArgvW; + use winapi::um::tlhelp32::*; + use winapi::um::winbase::LocalFree; + use winapi::um::winnt::{HANDLE, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ}; + + struct Snapshot(HANDLE); + + impl Snapshot { + pub fn new() -> Option { + let handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }; + if handle.is_null() { + None + } else { + Some(Self(handle)) + } + } + + pub fn iter(&self) -> ProcIter { + ProcIter { + snapshot: &self, + first: true, + } + } + } + + impl Drop for Snapshot { + fn drop(&mut self) { + unsafe { CloseHandle(self.0) }; + } + } + + struct ProcIter<'a> { + snapshot: &'a Snapshot, + first: bool, + } + + impl<'a> Iterator for ProcIter<'a> { + type Item = PROCESSENTRY32W; + + fn next(&mut self) -> Option { + let mut entry: PROCESSENTRY32W = unsafe { std::mem::zeroed() }; + entry.dwSize = std::mem::size_of::() as _; + let res = if self.first { + self.first = false; + unsafe { Process32FirstW(self.snapshot.0, &mut entry) } + } else { + unsafe { Process32NextW(self.snapshot.0, &mut entry) } + }; + if res == 0 { + None + } else { + Some(entry) + } + } + } + + let snapshot = Snapshot::new()?; + let procs: Vec<_> = snapshot.iter().collect(); + + fn wstr_to_path(slice: &[u16]) -> PathBuf { + match slice.iter().position(|&c| c == 0) { + Some(nul) => OsString::from_wide(&slice[..nul]), + None => OsString::from_wide(slice), + } + .into() + } + fn wstr_to_string(slice: &[u16]) -> String { + wstr_to_path(slice).to_string_lossy().into_owned() + } + + struct ProcParams { + argv: Vec, + cwd: PathBuf, + } + + struct ProcHandle(HANDLE); + impl ProcHandle { + fn new(pid: u32) -> Option { + let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + let handle = unsafe { OpenProcess(options, FALSE as _, pid) }; + if handle.is_null() { + return None; + } + Some(Self(handle)) + } + + fn hmodule(&self) -> Option { + let mut needed = 0; + let mut hmod = [0 as HMODULE]; + let size = std::mem::size_of_val(&hmod); + let res = unsafe { + EnumProcessModulesEx( + self.0, + hmod.as_mut_ptr(), + size as _, + &mut needed, + LIST_MODULES_ALL, + ) + }; + if res == 0 { + None + } else { + Some(hmod[0]) + } + } + + fn executable(&self) -> Option { + let hmod = self.hmodule()?; + let mut buf = [0u16; MAX_PATH + 1]; + let res = + unsafe { GetModuleFileNameExW(self.0, hmod, buf.as_mut_ptr(), buf.len() as _) }; + if res == 0 { + None + } else { + Some(wstr_to_path(&buf)) + } + } + + fn get_peb32_addr(&self) -> Option { + let mut peb32_addr = MaybeUninit::::uninit(); + let res = unsafe { + NtQueryInformationProcess( + self.0, + ProcessWow64Information, + peb32_addr.as_mut_ptr() as _, + std::mem::size_of::() as _, + std::ptr::null_mut(), + ) + }; + if !NT_SUCCESS(res) { + return None; + } + let peb32_addr = unsafe { peb32_addr.assume_init() }; + if peb32_addr.is_null() { + None + } else { + Some(peb32_addr) + } + } + + fn get_params(&self) -> Option { + match self.get_peb32_addr() { + Some(peb32) => self.get_params_32(peb32), + None => self.get_params_64(), + } + } + + fn get_basic_info(&self) -> Option { + let mut info = MaybeUninit::::uninit(); + let res = unsafe { + NtQueryInformationProcess( + self.0, + ProcessBasicInformation, + info.as_mut_ptr() as _, + std::mem::size_of::() as _, + std::ptr::null_mut(), + ) + }; + if !NT_SUCCESS(res) { + return None; + } + let info = unsafe { info.assume_init() }; + Some(info) + } + + fn read_struct(&self, addr: LPVOID) -> Option { + let mut data = MaybeUninit::::uninit(); + let res = unsafe { + ReadProcessMemory( + self.0, + addr as _, + data.as_mut_ptr() as _, + std::mem::size_of::() as _, + std::ptr::null_mut(), + ) + }; + if res == 0 { + return None; + } + let data = unsafe { data.assume_init() }; + Some(data) + } + + fn get_peb(&self, info: &PROCESS_BASIC_INFORMATION) -> Option { + self.read_struct(info.PebBaseAddress as _) + } + + fn get_proc_params(&self, peb: &PEB) -> Option { + self.read_struct(peb.ProcessParameters as _) + } + + fn get_params_64(&self) -> Option { + let info = self.get_basic_info()?; + let peb = self.get_peb(&info)?; + let params = self.get_proc_params(&peb)?; + + let cmdline = self.read_process_wchar( + params.CommandLine.Buffer as _, + params.CommandLine.Length as _, + )?; + let cwd = self.read_process_wchar( + params.CurrentDirectory.DosPath.Buffer as _, + params.CurrentDirectory.DosPath.Length as _, + )?; + + Some(ProcParams { + argv: cmd_line_to_argv(&cmdline), + cwd: wstr_to_path(&cwd), + }) + } + + fn get_proc_params_32(&self, peb32: LPVOID) -> Option { + self.read_struct(peb32) + } + + fn get_params_32(&self, peb32: LPVOID) -> Option { + let params = self.get_proc_params_32(peb32)?; + + let cmdline = self.read_process_wchar( + params.CommandLine.Buffer as _, + params.CommandLine.Length as _, + )?; + let cwd = self.read_process_wchar( + params.CurrentDirectory.DosPath.Buffer as _, + params.CurrentDirectory.DosPath.Length as _, + )?; + + Some(ProcParams { + argv: cmd_line_to_argv(&cmdline), + cwd: wstr_to_path(&cwd), + }) + } + + fn read_process_wchar(&self, ptr: LPVOID, size: usize) -> Option> { + let mut buf = vec![0u16; size / 2]; + + let res = unsafe { + ReadProcessMemory( + self.0, + ptr as _, + buf.as_mut_ptr() as _, + size, + std::ptr::null_mut(), + ) + }; + if res == 0 { + return None; + } + + Some(buf) + } + + fn start_time(&self) -> Option { + let mut start = FILETIME { + dwLowDateTime: 0, + dwHighDateTime: 0, + }; + let mut exit = FILETIME { + dwLowDateTime: 0, + dwHighDateTime: 0, + }; + let mut kernel = FILETIME { + dwLowDateTime: 0, + dwHighDateTime: 0, + }; + let mut user = FILETIME { + dwLowDateTime: 0, + dwHighDateTime: 0, + }; + let res = unsafe { + GetProcessTimes(self.0, &mut start, &mut exit, &mut kernel, &mut user) + }; + if res == 0 { + return None; + } + + // Units are 100 nanoseconds + let start = (start.dwHighDateTime as u64) << 32 | start.dwLowDateTime as u64; + let start = Duration::from_nanos(start * 100); + + // Difference between the windows epoch and the unix epoch + const WINDOWS_EPOCH: Duration = Duration::from_secs(11_644_473_600); + + Some(SystemTime::UNIX_EPOCH + start - WINDOWS_EPOCH) + } + } + + fn cmd_line_to_argv(buf: &[u16]) -> Vec { + let mut argc = 0; + let argvp = unsafe { CommandLineToArgvW(buf.as_ptr(), &mut argc) }; + if argvp.is_null() { + return vec![]; + } + + let argv = unsafe { std::slice::from_raw_parts(argvp, argc as usize) }; + let mut args = vec![]; + for &arg in argv { + let len = unsafe { libc::wcslen(arg) }; + let arg = unsafe { std::slice::from_raw_parts(arg, len) }; + args.push(wstr_to_string(arg)); + } + unsafe { LocalFree(argvp as _) }; + args + } + + impl Drop for ProcHandle { + fn drop(&mut self) { + unsafe { CloseHandle(self.0) }; + } + } + + fn build_proc(info: &PROCESSENTRY32W, procs: &[PROCESSENTRY32W]) -> LocalProcessInfo { + let mut children = HashMap::new(); + + for kid in procs { + if kid.th32ParentProcessID == info.th32ProcessID { + children.insert(kid.th32ProcessID, build_proc(kid, procs)); + } + } + + let mut executable = wstr_to_path(&info.szExeFile); + + let name = match executable.file_name() { + Some(name) => name.to_string_lossy().into_owned(), + None => String::new(), + }; + + let mut start_time = SystemTime::now(); + let mut cwd = PathBuf::new(); + let mut argv = vec![]; + + if let Some(proc) = ProcHandle::new(info.th32ProcessID) { + if let Some(exe) = proc.executable() { + executable = exe; + } + if let Some(params) = proc.get_params() { + cwd = params.cwd; + argv = params.argv; + } + if let Some(start) = proc.start_time() { + start_time = start; + } + } + + LocalProcessInfo { + pid: info.th32ProcessID, + ppid: info.th32ParentProcessID, + name, + executable, + cwd, + argv, + start_time, + status: LocalProcessStatus::Run, + children, + } + } + + if let Some(info) = procs.iter().find(|info| info.th32ProcessID == pid) { + Some(build_proc(info, &procs)) + } else { + None + } + } +} + #[cfg(target_os = "macos")] impl LocalProcessInfo { pub(crate) fn with_root_pid_macos(pid: u32) -> Option { @@ -410,40 +776,3 @@ impl LocalProcessInfo { } } } - -#[cfg(windows)] -impl LocalProcessInfo { - pub(crate) fn with_root_pid(system: &sysinfo::System, pid: u32) -> Option { - use sysinfo::{AsU32, Pid, Process, ProcessExt, SystemExt}; - - fn build_proc(proc: &Process, processes: &HashMap) -> LocalProcessInfo { - // Process has a `tasks` field but it does not correspond to child processes, - // so we need to repeatedly walk the full process list and establish that - // linkage for ourselves here - let mut children = HashMap::new(); - let pid = proc.pid(); - for (child_pid, child_proc) in processes { - if child_proc.parent() == Some(pid) { - children.insert(child_pid.as_u32(), build_proc(child_proc, processes)); - } - } - - LocalProcessInfo { - pid: proc.pid().as_u32(), - ppid: proc.parent().map(|pid| pid.as_u32()).unwrap_or(1), - name: proc.name().to_string(), - executable: proc.exe().to_path_buf(), - cwd: proc.cwd().to_path_buf(), - argv: proc.cmd().to_vec(), - start_time: proc.start_time(), - status: LocalProcessStatus::from_process_status(proc.status()), - children, - } - } - - let proc = system.process(pid as Pid)?; - let procs = system.processes(); - - Some(build_proc(&proc, &procs)) - } -} diff --git a/mux/src/sysinfo.rs b/mux/src/sysinfo.rs deleted file mode 100644 index edfc55aec..000000000 --- a/mux/src/sysinfo.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![cfg(windows)] -use std::sync::{Mutex, MutexGuard}; -use std::time::{Duration, Instant}; -use sysinfo::{ProcessRefreshKind, RefreshKind, System, SystemExt}; - -lazy_static::lazy_static! { - static ref SYSTEM: Mutex = Mutex::new(CachedSystemInfo::new()); -} - -pub struct CachedSystemInfo { - last_update: Instant, - system: sysinfo::System, -} - -impl std::ops::Deref for CachedSystemInfo { - type Target = sysinfo::System; - - fn deref(&self) -> &sysinfo::System { - &self.system - } -} - -impl CachedSystemInfo { - pub fn new() -> Self { - Self { - system: System::new_with_specifics( - RefreshKind::new().with_processes(ProcessRefreshKind::new()), - ), - last_update: Instant::now(), - } - } - - pub fn refresh_now(&mut self) { - self.system - .refresh_processes_specifics(ProcessRefreshKind::new()); - self.last_update = Instant::now(); - } - - pub fn check_refresh(&mut self) { - if self.last_update.elapsed() < Duration::from_millis(300) { - return; - } - self.refresh_now(); - } -} - -pub fn get() -> MutexGuard<'static, CachedSystemInfo> { - let mut guard = SYSTEM.lock().unwrap(); - guard.check_refresh(); - guard -} - -pub fn get_with_forced_refresh() -> MutexGuard<'static, CachedSystemInfo> { - let mut guard = SYSTEM.lock().unwrap(); - guard.refresh_now(); - guard -}