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.
121 lines
3.0 KiB
121 lines
3.0 KiB
#![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() -> anyhow::Result<()> { |
|
let subprocess = Command::new("bash") |
|
.stdin(Stdio::piped()) |
|
.stdout(Stdio::piped()) |
|
.stderr(Stdio::piped()) |
|
.spawn()?; |
|
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())); |
|
Ok(()) |
|
} |
|
}
|
|
|