You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

99 lines
2.7 KiB

#![cfg(unix)]
use super::super::{Bash, Fish, PowerShell, Shell, Zsh};
use log::debug;
use std::io::{Error, ErrorKind};
#[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 pid != None && visited < MAX_ITERATIONS {
let process_info = get_process_info(pid.unwrap()).ok()?;
let binary = process_info
.command
.trim_start_matches('-')
.split('/')
.last()
.expect("Can't read file name of process tree");
match binary {
"sh" | "bash" => return Some(Box::from(Bash)),
"zsh" => return Some(Box::from(Zsh)),
"fish" => return Some(Box::from(Fish)),
"pwsh" => return Some(Box::from(PowerShell)),
cmd_name => debug!("binary is not a supported shell: {:?}", cmd_name),
};
pid = process_info.parent_pid;
visited += 1;
}
None
}
fn get_process_info(pid: u32) -> std::io::Result<ProcessInfo> {
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()
.expect("Can't read the ppid from ps, should be the first item in the table");
let command = parts
.next()
.expect("Can't read the command from ps, should be the second item in the table");
Ok(ProcessInfo {
parent_pid: u32::from_str_radix(ppid, 10).ok(),
command: command.into(),
})
}
#[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()));
}
}