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 @@
@@ -1,34 +1,21 @@
|
||||
use super::{Bash, Fish, PowerShell, Shell, WindowsCmd, Zsh}; |
||||
use log::debug; |
||||
use std::ffi::OsStr; |
||||
use sysinfo::{ProcessExt, System, SystemExt}; |
||||
mod unix; |
||||
|
||||
pub fn infer_shell() -> Option<Box<dyn Shell>> { |
||||
let mut system = System::new(); |
||||
let mut current_pid = sysinfo::get_current_pid().ok(); |
||||
mod windows; |
||||
|
||||
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); |
||||
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; |
||||
} |
||||
} |
||||
#[cfg(unix)] |
||||
pub use self::unix::infer_shell; |
||||
#[cfg(not(unix))] |
||||
pub use self::windows::infer_shell; |
||||
|
||||
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 |
||||
} |
||||
|
@ -0,0 +1,121 @@
@@ -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 @@
@@ -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