WIP fixing arguments in title bug, need to validate fix, add to wezterm, push to our copy, refresh our cargo, and make a PR for wezterm. TODO: Learn how to do c-style buffer munging.

Mikayla Maki created

Change summary

Cargo.lock                      |   1 
crates/procinfo/Cargo.toml      |  23 +
crates/procinfo/src/lib.rs      |  93 +++++++
crates/procinfo/src/linux.rs    | 139 +++++++++++
crates/procinfo/src/macos.rs    | 231 +++++++++++++++++++
crates/procinfo/src/windows.rs  | 419 +++++++++++++++++++++++++++++++++++
crates/terminal/Cargo.toml      |   2 
crates/terminal/src/terminal.rs | 124 +++-------
8 files changed, 943 insertions(+), 89 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3824,7 +3824,6 @@ dependencies = [
 [[package]]
 name = "procinfo"
 version = "0.1.0"
-source = "git+https://github.com/zed-industries/wezterm?rev=40a7dbf93542fbe4178c2e4b4bd438126a6432b9#40a7dbf93542fbe4178c2e4b4bd438126a6432b9"
 dependencies = [
  "libc",
  "log",

crates/procinfo/Cargo.toml 🔗

@@ -0,0 +1,23 @@
+[package]
+name = "procinfo"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[features]
+
+[dependencies]
+libc = "0.2"
+log = "0.4"
+
+[target."cfg(windows)".dependencies]
+ntapi = "0.3"
+winapi = { version = "0.3", features = [
+    "handleapi",
+    "memoryapi",
+    "psapi",
+    "processthreadsapi",
+    "shellapi",
+    "tlhelp32",
+]}
+

crates/procinfo/src/lib.rs 🔗

@@ -0,0 +1,93 @@
+use std::collections::{HashMap, HashSet};
+use std::path::PathBuf;
+mod linux;
+mod macos;
+mod windows;
+
+#[derive(Debug, Copy, Clone)]
+pub enum LocalProcessStatus {
+    Idle,
+    Run,
+    Sleep,
+    Stop,
+    Zombie,
+    Tracing,
+    Dead,
+    Wakekill,
+    Waking,
+    Parked,
+    LockBlocked,
+    Unknown,
+}
+
+#[derive(Debug, Clone)]
+pub struct LocalProcessInfo {
+    /// The process identifier
+    pub pid: u32,
+    /// The parent process identifier
+    pub ppid: u32,
+    /// The COMM name of the process. May not bear any relation to
+    /// the executable image name. May be changed at runtime by
+    /// the process.
+    /// Many systems truncate this
+    /// field to 15-16 characters.
+    pub name: String,
+    /// Path to the executable image
+    pub executable: PathBuf,
+    /// The argument vector.
+    /// Some systems allow changing the argv block at runtime
+    /// eg: setproctitle().
+    pub argv: Vec<String>,
+    /// The current working directory for the process, or an empty
+    /// path if it was not accessible for some reason.
+    pub cwd: PathBuf,
+    /// The status of the process. Not all possible values are
+    /// portably supported on all systems.
+    pub status: LocalProcessStatus,
+    /// A clock value in unspecified system dependent units that
+    /// indicates the relative age of the process.
+    pub start_time: u64,
+    /// The console handle associated with the process, if any.
+    #[cfg(windows)]
+    pub console: u64,
+    /// Child processes, keyed by pid
+    pub children: HashMap<u32, LocalProcessInfo>,
+}
+#[cfg(feature = "lua")]
+luahelper::impl_lua_conversion_dynamic!(LocalProcessInfo);
+
+impl LocalProcessInfo {
+    /// Walk this sub-tree of processes and return a unique set
+    /// of executable base names. eg: `foo/bar` and `woot/bar`
+    /// produce a set containing just `bar`.
+    pub fn flatten_to_exe_names(&self) -> HashSet<String> {
+        let mut names = HashSet::new();
+
+        fn flatten(item: &LocalProcessInfo, names: &mut HashSet<String>) {
+            if let Some(exe) = item.executable.file_name() {
+                names.insert(exe.to_string_lossy().into_owned());
+            }
+            for proc in item.children.values() {
+                flatten(proc, names);
+            }
+        }
+
+        flatten(self, &mut names);
+        names
+    }
+
+    #[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
+    pub fn with_root_pid(_pid: u32) -> Option<Self> {
+        None
+    }
+
+    #[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
+    pub fn current_working_dir(_pid: u32) -> Option<PathBuf> {
+        None
+    }
+
+    #[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
+    pub fn executable_path(_pid: u32) -> Option<PathBuf> {
+        None
+    }
+}

crates/procinfo/src/linux.rs 🔗

@@ -0,0 +1,139 @@
+#![cfg(target_os = "linux")]
+use super::*;
+
+impl From<&str> for LocalProcessStatus {
+    fn from(s: &str) -> Self {
+        match s {
+            "R" => Self::Run,
+            "S" => Self::Sleep,
+            "D" => Self::Idle,
+            "Z" => Self::Zombie,
+            "T" => Self::Stop,
+            "t" => Self::Tracing,
+            "X" | "x" => Self::Dead,
+            "K" => Self::Wakekill,
+            "W" => Self::Waking,
+            "P" => Self::Parked,
+            _ => Self::Unknown,
+        }
+    }
+}
+
+impl LocalProcessInfo {
+    pub fn current_working_dir(pid: u32) -> Option<PathBuf> {
+        std::fs::read_link(format!("/proc/{}/cwd", pid)).ok()
+    }
+
+    pub fn executable_path(pid: u32) -> Option<PathBuf> {
+        std::fs::read_link(format!("/proc/{}/exe", pid)).ok()
+    }
+
+    pub fn with_root_pid(pid: u32) -> Option<Self> {
+        use libc::pid_t;
+
+        let pid = pid as pid_t;
+
+        fn all_pids() -> Vec<pid_t> {
+            let mut pids = vec![];
+            if let Ok(dir) = std::fs::read_dir("/proc") {
+                for entry in dir {
+                    if let Ok(entry) = entry {
+                        if let Ok(file_type) = entry.file_type() {
+                            if file_type.is_dir() {
+                                if let Some(name) = entry.file_name().to_str() {
+                                    if let Ok(pid) = name.parse::<pid_t>() {
+                                        pids.push(pid);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            pids
+        }
+
+        struct LinuxStat {
+            pid: pid_t,
+            name: String,
+            status: String,
+            ppid: pid_t,
+            // Time process started after boot, measured in ticks
+            starttime: u64,
+        }
+
+        fn info_for_pid(pid: pid_t) -> Option<LinuxStat> {
+            let data = std::fs::read_to_string(format!("/proc/{}/stat", pid)).ok()?;
+            let (_pid_space, name) = data.split_once('(')?;
+            let (name, fields) = name.rsplit_once(')')?;
+            let fields = fields.split_whitespace().collect::<Vec<_>>();
+
+            Some(LinuxStat {
+                pid,
+                name: name.to_string(),
+                status: fields.get(0)?.to_string(),
+                ppid: fields.get(1)?.parse().ok()?,
+                starttime: fields.get(20)?.parse().ok()?,
+            })
+        }
+
+        fn exe_for_pid(pid: pid_t) -> PathBuf {
+            std::fs::read_link(format!("/proc/{}/exe", pid)).unwrap_or_else(|_| PathBuf::new())
+        }
+
+        fn cwd_for_pid(pid: pid_t) -> PathBuf {
+            LocalProcessInfo::current_working_dir(pid as u32).unwrap_or_else(|| PathBuf::new())
+        }
+
+        fn parse_cmdline(pid: pid_t) -> Vec<String> {
+            let data = match std::fs::read(format!("/proc/{}/cmdline", pid)) {
+                Ok(data) => data,
+                Err(_) => return vec![],
+            };
+
+            let mut args = vec![];
+
+            let data = data.strip_suffix(&[0]).unwrap_or(&data);
+
+            for arg in data.split(|&c| c == 0) {
+                args.push(String::from_utf8_lossy(arg).to_owned().to_string());
+            }
+
+            args
+        }
+
+        let procs: Vec<_> = all_pids().into_iter().filter_map(info_for_pid).collect();
+
+        fn build_proc(info: &LinuxStat, procs: &[LinuxStat]) -> LocalProcessInfo {
+            let mut children = HashMap::new();
+
+            for kid in procs {
+                if kid.ppid == info.pid {
+                    children.insert(kid.pid as u32, build_proc(kid, procs));
+                }
+            }
+
+            let executable = exe_for_pid(info.pid);
+            let name = info.name.clone();
+            let argv = parse_cmdline(info.pid);
+
+            LocalProcessInfo {
+                pid: info.pid as _,
+                ppid: info.ppid as _,
+                name,
+                executable,
+                cwd: cwd_for_pid(info.pid),
+                argv,
+                start_time: info.starttime,
+                status: info.status.as_str().into(),
+                children,
+            }
+        }
+
+        if let Some(info) = procs.iter().find(|info| info.pid == pid) {
+            Some(build_proc(info, &procs))
+        } else {
+            None
+        }
+    }
+}

crates/procinfo/src/macos.rs 🔗

@@ -0,0 +1,231 @@
+#![cfg(target_os = "macos")]
+use super::*;
+use std::ffi::{OsStr, OsString};
+use std::os::unix::ffi::{OsStrExt, OsStringExt};
+
+impl From<u32> for LocalProcessStatus {
+    fn from(s: u32) -> Self {
+        match s {
+            1 => Self::Idle,
+            2 => Self::Run,
+            3 => Self::Sleep,
+            4 => Self::Stop,
+            5 => Self::Zombie,
+            _ => Self::Unknown,
+        }
+    }
+}
+
+impl LocalProcessInfo {
+    pub fn current_working_dir(pid: u32) -> Option<PathBuf> {
+        let mut pathinfo: libc::proc_vnodepathinfo = unsafe { std::mem::zeroed() };
+        let size = std::mem::size_of_val(&pathinfo) as libc::c_int;
+        let ret = unsafe {
+            libc::proc_pidinfo(
+                pid as _,
+                libc::PROC_PIDVNODEPATHINFO,
+                0,
+                &mut pathinfo as *mut _ as *mut _,
+                size,
+            )
+        };
+        if ret != size {
+            return None;
+        }
+
+        // Workaround a workaround for an old rustc version supported by libc;
+        // the type of vip_path should just be [c_char; MAXPATHLEN] but it
+        // is defined as a horrible nested array by the libc crate:
+        // `[[c_char; 32]; 32]`.
+        // Urgh.  Let's re-cast it as the correct kind of slice.
+        let vip_path = unsafe {
+            std::slice::from_raw_parts(
+                pathinfo.pvi_cdir.vip_path.as_ptr() as *const u8,
+                libc::MAXPATHLEN as usize,
+            )
+        };
+        let nul = vip_path.iter().position(|&c| c == 0)?;
+        Some(OsStr::from_bytes(&vip_path[0..nul]).into())
+    }
+
+    pub fn executable_path(pid: u32) -> Option<PathBuf> {
+        let mut buffer: Vec<u8> = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _);
+        let x = unsafe {
+            libc::proc_pidpath(
+                pid as _,
+                buffer.as_mut_ptr() as *mut _,
+                libc::PROC_PIDPATHINFO_MAXSIZE as _,
+            )
+        };
+        if x <= 0 {
+            return None;
+        }
+
+        unsafe { buffer.set_len(x as usize) };
+        Some(OsString::from_vec(buffer).into())
+    }
+
+    pub fn with_root_pid(pid: u32) -> Option<Self> {
+        /// Enumerate all current process identifiers
+        fn all_pids() -> Vec<libc::pid_t> {
+            let num_pids = unsafe { libc::proc_listallpids(std::ptr::null_mut(), 0) };
+            if num_pids < 1 {
+                return vec![];
+            }
+
+            // Give a bit of padding to avoid looping if processes are spawning
+            // rapidly while we're trying to collect this info
+            const PADDING: usize = 32;
+            let mut pids: Vec<libc::pid_t> = Vec::with_capacity(num_pids as usize + PADDING);
+            loop {
+                let n = unsafe {
+                    libc::proc_listallpids(
+                        pids.as_mut_ptr() as *mut _,
+                        (pids.capacity() * std::mem::size_of::<libc::pid_t>()) as _,
+                    )
+                };
+
+                if n < 1 {
+                    return vec![];
+                }
+
+                let n = n as usize;
+
+                if n > pids.capacity() {
+                    pids.reserve(n + PADDING);
+                    continue;
+                }
+
+                unsafe { pids.set_len(n) };
+                return pids;
+            }
+        }
+
+        /// Obtain info block for a pid.
+        /// Note that the process could have gone away since we first
+        /// observed the pid and the time we call this, so we must
+        /// be able to tolerate this failing.
+        fn info_for_pid(pid: libc::pid_t) -> Option<libc::proc_bsdinfo> {
+            let mut info: libc::proc_bsdinfo = unsafe { std::mem::zeroed() };
+            let wanted_size = std::mem::size_of::<libc::proc_bsdinfo>() as _;
+            let res = unsafe {
+                libc::proc_pidinfo(
+                    pid,
+                    libc::PROC_PIDTBSDINFO,
+                    0,
+                    &mut info as *mut _ as *mut _,
+                    wanted_size,
+                )
+            };
+
+            if res == wanted_size {
+                Some(info)
+            } else {
+                None
+            }
+        }
+
+        fn cwd_for_pid(pid: libc::pid_t) -> PathBuf {
+            LocalProcessInfo::current_working_dir(pid as _).unwrap_or_else(PathBuf::new)
+        }
+
+        fn exe_and_args_for_pid_sysctl(pid: libc::pid_t) -> Option<(PathBuf, Vec<String>)> {
+            use libc::c_int;
+            let mut size = 64 * 1024;
+            let mut buf: Vec<u8> = Vec::with_capacity(size);
+            let mut mib = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid as c_int];
+
+            let res = unsafe {
+                libc::sysctl(
+                    mib.as_mut_ptr(),
+                    mib.len() as _,
+                    buf.as_mut_ptr() as *mut _,
+                    &mut size,
+                    std::ptr::null_mut(),
+                    0,
+                )
+            };
+            if res == -1 {
+                return None;
+            }
+            if size < (std::mem::size_of::<c_int>() * 2) {
+                // Not big enough
+                return None;
+            }
+            unsafe { buf.set_len(size) };
+
+            // The data in our buffer is laid out like this:
+            // argc - c_int
+            // exe_path - NUL terminated string
+            // argv[0] - NUL terminated string
+            // argv[1] - NUL terminated string
+            // ...
+            // argv[n] - NUL terminated string
+            // envp[0] - NUL terminated string
+            // ...
+
+            let mut ptr = &buf[0..size];
+
+            let argc: c_int = unsafe { std::ptr::read(ptr.as_ptr() as *const c_int) };
+            ptr = &ptr[std::mem::size_of::<c_int>()..];
+
+            fn consume_cstr(ptr: &mut &[u8]) -> Option<String> {
+                let nul = ptr.iter().position(|&c| c == 0)?;
+                let s = String::from_utf8_lossy(&ptr[0..nul]).to_owned().to_string();
+                *ptr = ptr.get(nul + 1..)?;
+                Some(s)
+            }
+
+            let exe_path = consume_cstr(&mut ptr)?.into();
+
+            let mut args = vec![];
+            for _ in 0..argc {
+                args.push(consume_cstr(&mut ptr)?);
+            }
+
+            dbg!(&exe_path);
+            dbg!(&args);
+            Some((exe_path, args))
+        }
+
+        fn exe_for_pid(pid: libc::pid_t) -> PathBuf {
+            LocalProcessInfo::executable_path(pid as _).unwrap_or_else(PathBuf::new)
+        }
+
+        let procs: Vec<_> = all_pids().into_iter().filter_map(info_for_pid).collect();
+
+        fn build_proc(info: &libc::proc_bsdinfo, procs: &[libc::proc_bsdinfo]) -> LocalProcessInfo {
+            let mut children = HashMap::new();
+
+            for kid in procs {
+                if kid.pbi_ppid == info.pbi_pid {
+                    children.insert(kid.pbi_pid, build_proc(kid, procs));
+                }
+            }
+
+            let (executable, argv) = exe_and_args_for_pid_sysctl(info.pbi_pid as _)
+                .unwrap_or_else(|| (exe_for_pid(info.pbi_pid as _), vec![]));
+
+            let name = unsafe { std::ffi::CStr::from_ptr(info.pbi_comm.as_ptr() as _) };
+            let name = name.to_str().unwrap_or("").to_string();
+
+            LocalProcessInfo {
+                pid: info.pbi_pid,
+                ppid: info.pbi_ppid,
+                name,
+                executable,
+                cwd: cwd_for_pid(info.pbi_pid as _),
+                argv,
+                start_time: info.pbi_start_tvsec,
+                status: LocalProcessStatus::from(info.pbi_status),
+                children,
+            }
+        }
+
+        if let Some(info) = procs.iter().find(|info| info.pbi_pid == pid) {
+            Some(build_proc(info, &procs))
+        } else {
+            None
+        }
+    }
+}

crates/procinfo/src/windows.rs 🔗

@@ -0,0 +1,419 @@
+#![cfg(windows)]
+use super::*;
+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::{DWORD, FILETIME, LPVOID, MAX_PATH};
+use winapi::shared::ntdef::{FALSE, NT_SUCCESS};
+use winapi::um::handleapi::CloseHandle;
+use winapi::um::memoryapi::ReadProcessMemory;
+use winapi::um::processthreadsapi::{GetCurrentProcessId, GetProcessTimes, OpenProcess};
+use winapi::um::shellapi::CommandLineToArgvW;
+use winapi::um::tlhelp32::*;
+use winapi::um::winbase::{LocalFree, QueryFullProcessImageNameW};
+use winapi::um::winnt::{HANDLE, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
+
+/// Manages a Toolhelp32 snapshot handle
+struct Snapshot(HANDLE);
+
+impl Snapshot {
+    pub fn new() -> Option<Self> {
+        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,
+        }
+    }
+
+    pub fn entries() -> Vec<PROCESSENTRY32W> {
+        match Self::new() {
+            Some(snapshot) => snapshot.iter().collect(),
+            None => vec![],
+        }
+    }
+}
+
+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<Self::Item> {
+        let mut entry: PROCESSENTRY32W = unsafe { std::mem::zeroed() };
+        entry.dwSize = std::mem::size_of::<PROCESSENTRY32W>() 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)
+        }
+    }
+}
+
+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<String>,
+    cwd: PathBuf,
+    console: HANDLE,
+}
+
+/// A handle to an opened process
+struct ProcHandle {
+    pid: u32,
+    proc: HANDLE,
+}
+
+impl ProcHandle {
+    pub fn new(pid: u32) -> Option<Self> {
+        if pid == unsafe { GetCurrentProcessId() } {
+            // Avoid the potential for deadlock if we're examining ourselves
+            log::trace!("ProcHandle::new({}): skip because it is my own pid", pid);
+            return None;
+        }
+        let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;
+        log::trace!("ProcHandle::new({}): OpenProcess", pid);
+        let handle = unsafe { OpenProcess(options, FALSE as _, pid) };
+        log::trace!("ProcHandle::new({}): OpenProcess -> {:?}", pid, handle);
+        if handle.is_null() {
+            return None;
+        }
+        Some(Self { pid, proc: handle })
+    }
+
+    /// Returns the executable image for the process
+    pub fn executable(&self) -> Option<PathBuf> {
+        let mut buf = [0u16; MAX_PATH + 1];
+        let mut len = buf.len() as DWORD;
+        let res = unsafe { QueryFullProcessImageNameW(self.proc, 0, buf.as_mut_ptr(), &mut len) };
+        if res == 0 {
+            None
+        } else {
+            Some(wstr_to_path(&buf))
+        }
+    }
+
+    /// Wrapper around NtQueryInformationProcess that fetches `what` as `T`
+    fn query_proc<T>(&self, what: u32) -> Option<T> {
+        let mut data = MaybeUninit::<T>::uninit();
+        let res = unsafe {
+            NtQueryInformationProcess(
+                self.proc,
+                what,
+                data.as_mut_ptr() as _,
+                std::mem::size_of::<T>() as _,
+                std::ptr::null_mut(),
+            )
+        };
+        if !NT_SUCCESS(res) {
+            return None;
+        }
+        let data = unsafe { data.assume_init() };
+        Some(data)
+    }
+
+    /// Read a `T` from the target process at the specified address
+    fn read_struct<T>(&self, addr: LPVOID) -> Option<T> {
+        let mut data = MaybeUninit::<T>::uninit();
+        let res = unsafe {
+            ReadProcessMemory(
+                self.proc,
+                addr as _,
+                data.as_mut_ptr() as _,
+                std::mem::size_of::<T>() as _,
+                std::ptr::null_mut(),
+            )
+        };
+        if res == 0 {
+            return None;
+        }
+        let data = unsafe { data.assume_init() };
+        Some(data)
+    }
+
+    /// If the process is a 32-bit process running on Win64, return the address
+    /// of its process parameters.
+    /// Otherwise, return None to indicate a native win64 process.
+    fn get_peb32_addr(&self) -> Option<LPVOID> {
+        let peb32_addr: LPVOID = self.query_proc(ProcessWow64Information)?;
+        if peb32_addr.is_null() {
+            None
+        } else {
+            Some(peb32_addr)
+        }
+    }
+
+    /// Returns the cwd and args for the process
+    pub fn get_params(&self) -> Option<ProcParams> {
+        match self.get_peb32_addr() {
+            Some(peb32) => self.get_params_32(peb32),
+            None => self.get_params_64(),
+        }
+    }
+
+    fn get_basic_info(&self) -> Option<PROCESS_BASIC_INFORMATION> {
+        self.query_proc(ProcessBasicInformation)
+    }
+
+    fn get_peb(&self, info: &PROCESS_BASIC_INFORMATION) -> Option<PEB> {
+        self.read_struct(info.PebBaseAddress as _)
+    }
+
+    fn get_proc_params(&self, peb: &PEB) -> Option<RTL_USER_PROCESS_PARAMETERS> {
+        self.read_struct(peb.ProcessParameters as _)
+    }
+
+    /// Returns the cwd and args for a 64 bit process
+    fn get_params_64(&self) -> Option<ProcParams> {
+        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),
+            console: params.ConsoleHandle,
+        })
+    }
+
+    fn get_proc_params_32(&self, peb32: LPVOID) -> Option<RTL_USER_PROCESS_PARAMETERS32> {
+        self.read_struct(peb32)
+    }
+
+    /// Returns the cwd and args for a 32 bit process
+    fn get_params_32(&self, peb32: LPVOID) -> Option<ProcParams> {
+        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),
+            console: params.ConsoleHandle as _,
+        })
+    }
+
+    /// Copies a sized WSTR from the address in the process
+    fn read_process_wchar(&self, ptr: LPVOID, byte_size: usize) -> Option<Vec<u16>> {
+        if byte_size > MAX_PATH * 4 {
+            // Defend against implausibly large paths, just in
+            // case we're reading the wrong offset into a kernel struct
+            return None;
+        }
+
+        let mut buf = vec![0u16; byte_size / 2];
+        let mut bytes_read = 0;
+
+        let res = unsafe {
+            ReadProcessMemory(
+                self.proc,
+                ptr as _,
+                buf.as_mut_ptr() as _,
+                byte_size,
+                &mut bytes_read,
+            )
+        };
+        if res == 0 {
+            return None;
+        }
+
+        // In the unlikely event that we have a short read,
+        // truncate the buffer to fit.
+        let wide_chars_read = bytes_read / 2;
+        buf.resize(wide_chars_read, 0);
+
+        // Ensure that it is NUL terminated
+        match buf.iter().position(|&c| c == 0) {
+            Some(n) => {
+                // Truncate to include existing NUL but no later chars
+                buf.resize(n + 1, 0);
+            }
+            None => {
+                // Add a NUL
+                buf.push(0);
+            }
+        }
+
+        Some(buf)
+    }
+
+    /// Retrieves the start time of the process
+    fn start_time(&self) -> Option<u64> {
+        const fn empty() -> FILETIME {
+            FILETIME {
+                dwLowDateTime: 0,
+                dwHighDateTime: 0,
+            }
+        }
+
+        let mut start = empty();
+        let mut exit = empty();
+        let mut kernel = empty();
+        let mut user = empty();
+
+        let res =
+            unsafe { GetProcessTimes(self.proc, &mut start, &mut exit, &mut kernel, &mut user) };
+        if res == 0 {
+            return None;
+        }
+
+        Some((start.dwHighDateTime as u64) << 32 | start.dwLowDateTime as u64)
+    }
+}
+
+/// Parse a command line string into an argv array
+fn cmd_line_to_argv(buf: &[u16]) -> Vec<String> {
+    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) {
+        log::trace!("ProcHandle::drop(pid={} proc={:?})", self.pid, self.proc);
+        unsafe { CloseHandle(self.proc) };
+    }
+}
+
+impl LocalProcessInfo {
+    pub fn current_working_dir(pid: u32) -> Option<PathBuf> {
+        log::trace!("current_working_dir({})", pid);
+        let proc = ProcHandle::new(pid)?;
+        let params = proc.get_params()?;
+        Some(params.cwd)
+    }
+
+    pub fn executable_path(pid: u32) -> Option<PathBuf> {
+        log::trace!("executable_path({})", pid);
+        let proc = ProcHandle::new(pid)?;
+        proc.executable()
+    }
+
+    pub fn with_root_pid(pid: u32) -> Option<Self> {
+        log::trace!("LocalProcessInfo::with_root_pid({}), getting snapshot", pid);
+        let procs = Snapshot::entries();
+        log::trace!("Got snapshot");
+
+        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 = None;
+            let mut start_time = 0;
+            let mut cwd = PathBuf::new();
+            let mut argv = vec![];
+            let mut console = 0;
+
+            if let Some(proc) = ProcHandle::new(info.th32ProcessID) {
+                if let Some(exe) = proc.executable() {
+                    executable.replace(exe);
+                }
+                if let Some(params) = proc.get_params() {
+                    cwd = params.cwd;
+                    argv = params.argv;
+                    console = params.console as _;
+                }
+                if let Some(start) = proc.start_time() {
+                    start_time = start;
+                }
+            }
+
+            let executable = executable.unwrap_or_else(|| wstr_to_path(&info.szExeFile));
+            let name = match executable.file_name() {
+                Some(name) => name.to_string_lossy().into_owned(),
+                None => String::new(),
+            };
+
+            LocalProcessInfo {
+                pid: info.th32ProcessID,
+                ppid: info.th32ParentProcessID,
+                name,
+                executable,
+                cwd,
+                argv,
+                start_time,
+                status: LocalProcessStatus::Run,
+                children,
+                console,
+            }
+        }
+
+        if let Some(info) = procs.iter().find(|info| info.th32ProcessID == pid) {
+            Some(build_proc(info, &procs))
+        } else {
+            None
+        }
+    }
+}

crates/terminal/Cargo.toml 🔗

@@ -9,7 +9,7 @@ doctest = false
 
 [dependencies]
 alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "4e1f0c6177975a040b37f942dfb0e723e46a9971" }
-procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "40a7dbf93542fbe4178c2e4b4bd438126a6432b9", default-features = false }
+procinfo = { path = "../procinfo" }
 editor = { path = "../editor" }
 util = { path = "../util" }
 gpui = { path = "../gpui" }

crates/terminal/src/terminal.rs 🔗

@@ -3,7 +3,7 @@ pub mod modal;
 pub mod terminal_container_view;
 pub mod terminal_element;
 pub mod terminal_view;
-
+// procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "40a7dbf93542fbe4178c2e4b4bd438126a6432b9", default-features = false }
 use alacritty_terminal::{
     ansi::{ClearMode, Handler},
     config::{Config, Program, PtyConfig, Scrolling},
@@ -237,28 +237,12 @@ impl TerminalError {
         self.shell
             .clone()
             .map(|shell| match shell {
-                Shell::System => {
-                    let mut buf = [0; 1024];
-                    let pw = alacritty_unix::get_pw_entry(&mut buf).ok();
+                Shell::System => "<system defined shell>".to_string(),
 
-                    match pw {
-                        Some(pw) => format!("<system defined shell> {}", pw.shell),
-                        None => "<could not access the password file>".to_string(),
-                    }
-                }
                 Shell::Program(s) => s,
                 Shell::WithArguments { program, args } => format!("{} {}", program, args.join(" ")),
             })
-            .unwrap_or_else(|| {
-                let mut buf = [0; 1024];
-                let pw = alacritty_unix::get_pw_entry(&mut buf).ok();
-                match pw {
-                    Some(pw) => {
-                        format!("<none specified, using system defined shell> {}", pw.shell)
-                    }
-                    None => "<none specified, could not access the password file> {}".to_string(),
-                }
-            })
+            .unwrap_or_else(|| "<none specified, using system defined shell>".to_string())
     }
 }
 
@@ -538,6 +522,7 @@ impl Terminal {
             AlacTermEvent::Wakeup => {
                 cx.emit(Event::Wakeup);
 
+                dbg!("*********");
                 if self.update_process_info() {
                     cx.emit(Event::TitleChanged)
                 }
@@ -1039,84 +1024,49 @@ fn make_search_matches<'a, T>(
 
 #[cfg(test)]
 mod tests {
+    use libc::c_int;
+
     pub mod terminal_test_context;
-}
 
-//TODO Move this around and clean up the code
-mod alacritty_unix {
-    use alacritty_terminal::config::Program;
-    use gpui::anyhow::{bail, Result};
+    #[test]
+    pub fn wez_test() {
+        fn test() -> Option<Vec<String>> {
+            let size = 28;
 
-    use std::ffi::CStr;
-    use std::mem::MaybeUninit;
-    use std::ptr;
+            //Test data pulled from running the code
+            let buf = [
+                2, 0, 0, 0, 47, 98, 105, 110, 47, 115, 108, 101, 101, 112, 0, 0, 0, 0, 0, 0, 115,
+                108, 101, 101, 112, 0, 53, 0,
+            ];
 
-    #[derive(Debug)]
-    pub struct Passwd<'a> {
-        _name: &'a str,
-        _dir: &'a str,
-        pub shell: &'a str,
-    }
+            let mut ptr = &buf[0..size];
 
-    /// Return a Passwd struct with pointers into the provided buf.
-    ///
-    /// # Unsafety
-    ///
-    /// If `buf` is changed while `Passwd` is alive, bad thing will almost certainly happen.
-    pub fn get_pw_entry(buf: &mut [i8; 1024]) -> Result<Passwd<'_>> {
-        // Create zeroed passwd struct.
-        let mut entry: MaybeUninit<libc::passwd> = MaybeUninit::uninit();
-
-        let mut res: *mut libc::passwd = ptr::null_mut();
-
-        // Try and read the pw file.
-        let uid = unsafe { libc::getuid() };
-        let status = unsafe {
-            libc::getpwuid_r(
-                uid,
-                entry.as_mut_ptr(),
-                buf.as_mut_ptr() as *mut _,
-                buf.len(),
-                &mut res,
-            )
-        };
-        let entry = unsafe { entry.assume_init() };
+            let argc: c_int = unsafe { std::ptr::read(ptr.as_ptr() as *const c_int) };
+            ptr = &ptr[std::mem::size_of::<c_int>()..];
 
-        if status < 0 {
-            bail!("getpwuid_r failed");
-        }
-
-        if res.is_null() {
-            bail!("pw not found");
-        }
+            fn consume_cstr(ptr: &mut &[u8]) -> Option<String> {
+                let nul = ptr.iter().position(|&c| c == 0)?;
+                let s = String::from_utf8_lossy(&ptr[0..nul]).to_owned().to_string();
+                *ptr = ptr.get(nul + 1..)?;
+                Some(s)
+            }
 
-        // Sanity check.
-        assert_eq!(entry.pw_uid, uid);
+            let _exe_path: Option<String> = consume_cstr(&mut ptr)?.into();
 
-        // Build a borrowed Passwd struct.
-        Ok(Passwd {
-            _name: unsafe { CStr::from_ptr(entry.pw_name).to_str().unwrap() },
-            _dir: unsafe { CStr::from_ptr(entry.pw_dir).to_str().unwrap() },
-            shell: unsafe { CStr::from_ptr(entry.pw_shell).to_str().unwrap() },
-        })
-    }
+            //Clear out the trailing null pointers
+            while ptr[0] == 0 {
+                ptr = ptr.get(1..)?;
+            }
 
-    #[cfg(target_os = "macos")]
-    pub fn _default_shell(pw: &Passwd<'_>) -> Program {
-        let shell_name = pw.shell.rsplit('/').next().unwrap();
-        let argv = vec![
-            String::from("-c"),
-            format!("exec -a -{} {}", shell_name, pw.shell),
-        ];
-
-        Program::WithArgs {
-            program: "/bin/bash".to_owned(),
-            args: argv,
+            let mut args = vec![];
+            for _ in 0..argc {
+                args.push(consume_cstr(&mut ptr)?);
+            }
+            Some(args)
         }
-    }
 
-    #[cfg(not(target_os = "macos"))]
-    pub fn default_shell(pw: &Passwd<'_>) -> Program {
-        Program::Just(env::var("SHELL").unwrap_or_else(|_| pw.shell.to_owned()))
+        assert_eq!(test(), Some(vec!["sleep".to_string(), "5".to_string()]));
     }
 }
+
+mod wez_proc_info {}