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.
This commit is contained in:
parent
aabc6ce8bd
commit
90bae80bb2
8 changed files with 953 additions and 99 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3824,7 +3824,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "procinfo"
|
name = "procinfo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/wezterm?rev=40a7dbf93542fbe4178c2e4b4bd438126a6432b9#40a7dbf93542fbe4178c2e4b4bd438126a6432b9"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
|
23
crates/procinfo/Cargo.toml
Normal file
23
crates/procinfo/Cargo.toml
Normal file
|
@ -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",
|
||||||
|
]}
|
||||||
|
|
93
crates/procinfo/src/lib.rs
Normal file
93
crates/procinfo/src/lib.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
139
crates/procinfo/src/linux.rs
Normal file
139
crates/procinfo/src/linux.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
231
crates/procinfo/src/macos.rs
Normal file
231
crates/procinfo/src/macos.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
419
crates/procinfo/src/windows.rs
Normal file
419
crates/procinfo/src/windows.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "4e1f0c6177975a040b37f942dfb0e723e46a9971" }
|
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" }
|
editor = { path = "../editor" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
|
|
|
@ -3,7 +3,7 @@ pub mod modal;
|
||||||
pub mod terminal_container_view;
|
pub mod terminal_container_view;
|
||||||
pub mod terminal_element;
|
pub mod terminal_element;
|
||||||
pub mod terminal_view;
|
pub mod terminal_view;
|
||||||
|
// procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "40a7dbf93542fbe4178c2e4b4bd438126a6432b9", default-features = false }
|
||||||
use alacritty_terminal::{
|
use alacritty_terminal::{
|
||||||
ansi::{ClearMode, Handler},
|
ansi::{ClearMode, Handler},
|
||||||
config::{Config, Program, PtyConfig, Scrolling},
|
config::{Config, Program, PtyConfig, Scrolling},
|
||||||
|
@ -237,28 +237,12 @@ impl TerminalError {
|
||||||
self.shell
|
self.shell
|
||||||
.clone()
|
.clone()
|
||||||
.map(|shell| match shell {
|
.map(|shell| match shell {
|
||||||
Shell::System => {
|
Shell::System => "<system defined shell>".to_string(),
|
||||||
let mut buf = [0; 1024];
|
|
||||||
let pw = alacritty_unix::get_pw_entry(&mut buf).ok();
|
|
||||||
|
|
||||||
match pw {
|
|
||||||
Some(pw) => format!("<system defined shell> {}", pw.shell),
|
|
||||||
None => "<could not access the password file>".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Shell::Program(s) => s,
|
Shell::Program(s) => s,
|
||||||
Shell::WithArguments { program, args } => format!("{} {}", program, args.join(" ")),
|
Shell::WithArguments { program, args } => format!("{} {}", program, args.join(" ")),
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| "<none specified, using system defined shell>".to_string())
|
||||||
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(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,6 +522,7 @@ impl Terminal {
|
||||||
AlacTermEvent::Wakeup => {
|
AlacTermEvent::Wakeup => {
|
||||||
cx.emit(Event::Wakeup);
|
cx.emit(Event::Wakeup);
|
||||||
|
|
||||||
|
dbg!("*********");
|
||||||
if self.update_process_info() {
|
if self.update_process_info() {
|
||||||
cx.emit(Event::TitleChanged)
|
cx.emit(Event::TitleChanged)
|
||||||
}
|
}
|
||||||
|
@ -1039,84 +1024,49 @@ fn make_search_matches<'a, T>(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use libc::c_int;
|
||||||
|
|
||||||
pub mod terminal_test_context;
|
pub mod terminal_test_context;
|
||||||
}
|
|
||||||
|
|
||||||
//TODO Move this around and clean up the code
|
#[test]
|
||||||
mod alacritty_unix {
|
pub fn wez_test() {
|
||||||
use alacritty_terminal::config::Program;
|
fn test() -> Option<Vec<String>> {
|
||||||
use gpui::anyhow::{bail, Result};
|
let size = 28;
|
||||||
|
|
||||||
use std::ffi::CStr;
|
//Test data pulled from running the code
|
||||||
use std::mem::MaybeUninit;
|
let buf = [
|
||||||
use std::ptr;
|
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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() };
|
|
||||||
|
|
||||||
if status < 0 {
|
|
||||||
bail!("getpwuid_r failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.is_null() {
|
|
||||||
bail!("pw not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity check.
|
|
||||||
assert_eq!(entry.pw_uid, uid);
|
|
||||||
|
|
||||||
// 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() },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {
|
let mut ptr = &buf[0..size];
|
||||||
program: "/bin/bash".to_owned(),
|
|
||||||
args: argv,
|
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: Option<String> = consume_cstr(&mut ptr)?.into();
|
||||||
|
|
||||||
|
//Clear out the trailing null pointers
|
||||||
|
while ptr[0] == 0 {
|
||||||
|
ptr = ptr.get(1..)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut args = vec![];
|
||||||
|
for _ in 0..argc {
|
||||||
|
args.push(consume_cstr(&mut ptr)?);
|
||||||
|
}
|
||||||
|
Some(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(test(), Some(vec!["sleep".to_string(), "5".to_string()]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
mod wez_proc_info {}
|
||||||
pub fn default_shell(pw: &Passwd<'_>) -> Program {
|
|
||||||
Program::Just(env::var("SHELL").unwrap_or_else(|_| pw.shell.to_owned()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue