Browse Source
* Add the `ps` code back * some quality improvements * cargo clippy --fix * fix * correct pathremotes/origin/feat/support-install-latest
![gal@spitfire.co.il](/assets/img/avatar_default.png)
![GitHub](/assets/img/avatar_default.png)
3 changed files with 171 additions and 29 deletions
@ -1,34 +1,21 @@ |
|||||||
use super::{Bash, Fish, PowerShell, Shell, WindowsCmd, Zsh}; |
mod unix; |
||||||
use log::debug; |
|
||||||
use std::ffi::OsStr; |
|
||||||
use sysinfo::{ProcessExt, System, SystemExt}; |
|
||||||
|
|
||||||
pub fn infer_shell() -> Option<Box<dyn Shell>> { |
mod windows; |
||||||
let mut system = System::new(); |
|
||||||
let mut current_pid = sysinfo::get_current_pid().ok(); |
|
||||||
|
|
||||||
while let Some(pid) = current_pid { |
#[cfg(unix)] |
||||||
system.refresh_process(pid); |
pub use self::unix::infer_shell; |
||||||
if let Some(process) = system.process(pid) { |
#[cfg(not(unix))] |
||||||
current_pid = process.parent(); |
pub use self::windows::infer_shell; |
||||||
let process_name = process |
|
||||||
.exe() |
|
||||||
.file_stem() |
|
||||||
.and_then(OsStr::to_str) |
|
||||||
.map(str::to_lowercase); |
|
||||||
let sliced = process_name.as_ref().map(|x| &x[..]); |
|
||||||
match sliced { |
|
||||||
Some("sh" | "bash") => return Some(Box::from(Bash)), |
|
||||||
Some("zsh") => return Some(Box::from(Zsh)), |
|
||||||
Some("fish") => return Some(Box::from(Fish)), |
|
||||||
Some("pwsh" | "powershell") => return Some(Box::from(PowerShell)), |
|
||||||
Some("cmd") => return Some(Box::from(WindowsCmd)), |
|
||||||
cmd_name => debug!("binary is not a supported shell: {:?}", cmd_name), |
|
||||||
}; |
|
||||||
} else { |
|
||||||
current_pid = None; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
pub(self) fn shell_from_string(shell: &str) -> Option<Box<dyn super::Shell>> { |
||||||
|
use super::{Bash, Fish, PowerShell, WindowsCmd, Zsh}; |
||||||
|
match shell { |
||||||
|
"sh" | "bash" => return Some(Box::from(Bash)), |
||||||
|
"zsh" => return Some(Box::from(Zsh)), |
||||||
|
"fish" => return Some(Box::from(Fish)), |
||||||
|
"pwsh" | "powershell" => return Some(Box::from(PowerShell)), |
||||||
|
"cmd" => return Some(Box::from(WindowsCmd)), |
||||||
|
cmd_name => log::debug!("binary is not a supported shell: {:?}", cmd_name), |
||||||
|
}; |
||||||
None |
None |
||||||
} |
} |
||||||
|
@ -0,0 +1,121 @@ |
|||||||
|
#![cfg(unix)] |
||||||
|
|
||||||
|
use crate::shell::Shell; |
||||||
|
use log::debug; |
||||||
|
use std::io::{Error, ErrorKind}; |
||||||
|
use thiserror::Error; |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
struct ProcessInfo { |
||||||
|
parent_pid: Option<u32>, |
||||||
|
command: String, |
||||||
|
} |
||||||
|
|
||||||
|
const MAX_ITERATIONS: u8 = 10; |
||||||
|
|
||||||
|
pub fn infer_shell() -> Option<Box<dyn Shell>> { |
||||||
|
let mut pid = Some(std::process::id()); |
||||||
|
let mut visited = 0; |
||||||
|
|
||||||
|
while let Some(current_pid) = pid { |
||||||
|
if visited > MAX_ITERATIONS { |
||||||
|
return None; |
||||||
|
} |
||||||
|
|
||||||
|
let process_info = get_process_info(current_pid) |
||||||
|
.map_err(|err| { |
||||||
|
debug!("{}", err); |
||||||
|
err |
||||||
|
}) |
||||||
|
.ok()?; |
||||||
|
let binary = process_info |
||||||
|
.command |
||||||
|
.trim_start_matches('-') |
||||||
|
.split('/') |
||||||
|
.last()?; |
||||||
|
|
||||||
|
if let Some(shell) = super::shell_from_string(binary) { |
||||||
|
return Some(shell); |
||||||
|
} |
||||||
|
|
||||||
|
pid = process_info.parent_pid; |
||||||
|
visited += 1; |
||||||
|
} |
||||||
|
|
||||||
|
None |
||||||
|
} |
||||||
|
|
||||||
|
fn get_process_info(pid: u32) -> Result<ProcessInfo, ProcessInfoError> { |
||||||
|
use std::io::{BufRead, BufReader}; |
||||||
|
use std::process::Command; |
||||||
|
|
||||||
|
let buffer = Command::new("ps") |
||||||
|
.arg("-o") |
||||||
|
.arg("ppid,comm") |
||||||
|
.arg(pid.to_string()) |
||||||
|
.stdout(std::process::Stdio::piped()) |
||||||
|
.spawn()? |
||||||
|
.stdout |
||||||
|
.ok_or_else(|| Error::from(ErrorKind::UnexpectedEof))?; |
||||||
|
|
||||||
|
let mut lines = BufReader::new(buffer).lines(); |
||||||
|
|
||||||
|
// skip header line
|
||||||
|
lines |
||||||
|
.next() |
||||||
|
.ok_or_else(|| Error::from(ErrorKind::UnexpectedEof))??; |
||||||
|
|
||||||
|
let line = lines |
||||||
|
.next() |
||||||
|
.ok_or_else(|| Error::from(ErrorKind::NotFound))??; |
||||||
|
|
||||||
|
let mut parts = line.trim().split_whitespace(); |
||||||
|
let ppid = parts.next().ok_or_else(|| ProcessInfoError::Parse { |
||||||
|
expectation: "Can't read the ppid from ps, should be the first item in the table", |
||||||
|
got: line.to_string(), |
||||||
|
})?; |
||||||
|
let command = parts.next().ok_or_else(|| ProcessInfoError::Parse { |
||||||
|
expectation: "Can't read the command from ps, should be the second item in the table", |
||||||
|
got: line.to_string(), |
||||||
|
})?; |
||||||
|
|
||||||
|
Ok(ProcessInfo { |
||||||
|
parent_pid: ppid.parse().ok(), |
||||||
|
command: command.into(), |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Error)] |
||||||
|
enum ProcessInfoError { |
||||||
|
#[error("Can't read process info: {source}")] |
||||||
|
Io { |
||||||
|
#[source] |
||||||
|
#[from] |
||||||
|
source: std::io::Error, |
||||||
|
}, |
||||||
|
#[error("Can't parse process info output. {expectation}. Got: {got}")] |
||||||
|
Parse { |
||||||
|
got: String, |
||||||
|
expectation: &'static str, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(all(test, unix))] |
||||||
|
mod tests { |
||||||
|
use super::*; |
||||||
|
use pretty_assertions::assert_eq; |
||||||
|
use std::process::{Command, Stdio}; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_get_process_info() { |
||||||
|
let subprocess = Command::new("bash") |
||||||
|
.stdin(Stdio::piped()) |
||||||
|
.stdout(Stdio::piped()) |
||||||
|
.stderr(Stdio::piped()) |
||||||
|
.spawn() |
||||||
|
.expect("Can't execute command"); |
||||||
|
let process_info = get_process_info(subprocess.id()); |
||||||
|
let parent_pid = process_info.ok().and_then(|x| x.parent_pid); |
||||||
|
assert_eq!(parent_pid, Some(std::process::id())); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
#![cfg(not(unix))] |
||||||
|
|
||||||
|
use crate::shell::Shell; |
||||||
|
use log::debug; |
||||||
|
use std::ffi::OsStr; |
||||||
|
use sysinfo::{ProcessExt, System, SystemExt}; |
||||||
|
|
||||||
|
pub fn infer_shell() -> Option<Box<dyn Shell>> { |
||||||
|
let mut system = System::new(); |
||||||
|
let mut current_pid = sysinfo::get_current_pid().ok(); |
||||||
|
|
||||||
|
while let Some(pid) = current_pid { |
||||||
|
system.refresh_process(pid); |
||||||
|
if let Some(process) = system.process(pid) { |
||||||
|
current_pid = process.parent(); |
||||||
|
let process_name = process |
||||||
|
.exe() |
||||||
|
.file_stem() |
||||||
|
.and_then(OsStr::to_str) |
||||||
|
.map(str::to_lowercase); |
||||||
|
if let Some(shell) = process_name |
||||||
|
.as_ref() |
||||||
|
.map(|x| &x[..]) |
||||||
|
.and_then(super::shell_from_string) |
||||||
|
{ |
||||||
|
return Some(shell); |
||||||
|
} |
||||||
|
} else { |
||||||
|
current_pid = None; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
None |
||||||
|
} |
Loading…
Reference in new issue