feat: added automatic shell detection

so when you run `, -s <pname>` you'll be sent into the shell you were in, not into bash
This commit is contained in:
heinwol 2023-11-30 11:34:33 +03:00
parent 5ecd5024b4
commit 9533a9df1c
2 changed files with 73 additions and 3 deletions

View File

@ -1,9 +1,10 @@
mod index;
mod shell;
use std::{
env,
io::Write,
os::unix::prelude::CommandExt,
process::{Command, ExitCode, Stdio},
process::{Command, ExitCode, Stdio, self},
};
use clap::crate_version;
@ -52,7 +53,9 @@ fn run_command_or_open_shell(use_channel: bool, choice: &str, command: &str, tra
if !command.is_empty() {
run_cmd.args(["--command", command]);
run_cmd.args(trail);
if !trail.is_empty() {
run_cmd.args(trail);
}
};
run_cmd.exec();
@ -132,7 +135,8 @@ fn main() -> ExitCode {
.args(["-f", "<nixpkgs>", "-iA", choice.rsplit('.').last().unwrap()])
.exec();
} else if args.shell {
run_command_or_open_shell(use_channel, &choice, "", &[String::new()], &args.nixpkgs_flake);
let shell_cmd = shell::select_shell_from_pid(process::id()).unwrap_or("bash".into());
run_command_or_open_shell(use_channel, &choice, &shell_cmd, &[], &args.nixpkgs_flake);
} else {
run_command_or_open_shell(use_channel, &choice, command, trail, &args.nixpkgs_flake);
}

66
src/shell.rs Normal file
View File

@ -0,0 +1,66 @@
use std::{error::Error, fs};
type ResultDyn<T> = Result<T, Box<dyn Error>>;
const KNOWN_SHELLS: &[&str] = &[
"ash", //
"bash", //
"elvish", //
"fish", //
"nu", //
"pwsh", //
"tcsh", //
"zsh", //
];
fn get_process_status_field(pid: u32, field: &str) -> ResultDyn<String> {
if pid <= 0 {
return Err(format!("invalid pid: {pid}").into());
};
let status_bytes =
fs::read(format!("/proc/{pid:?}/status")).map_err(|_| format!("no such pid: {pid:?}"))?;
let status_str = String::from_utf8(status_bytes)?;
let status_str = status_str
.split('\n')
.find(|&x| x.starts_with(field))
.ok_or_else(|| format!("error parsing /proc/{pid:?}/status"))?;
let field_contents = status_str
.strip_prefix(&format!("{field}:"))
.ok_or_else(|| format!("bad parsing"))?
.trim()
.to_owned();
Ok(field_contents)
}
fn get_parent_pid(pid: u32) -> ResultDyn<u32> {
Ok(get_process_status_field(pid, &"PPid")?.parse::<u32>()?)
}
fn get_process_name(pid: u32) -> ResultDyn<String> {
Ok(get_process_status_field(pid, &"Name")?)
}
fn get_all_parents_pid(pid: u32) -> ResultDyn<Vec<u32>> {
let mut res = Vec::<u32>::new();
let mut pid = pid;
loop {
match get_parent_pid(pid) {
Ok(parent_id) if parent_id != 0 => {
res.push(parent_id);
pid = parent_id;
}
Ok(_) => return Ok(res),
Err(e) => return Err(e),
}
}
}
pub fn select_shell_from_pid(pid: u32) -> ResultDyn<String> {
let parents = get_all_parents_pid(pid)?;
let parents_names: Result<Vec<_>, _> =
parents.iter().map(|&pid| get_process_name(pid)).collect();
let shell = parents_names?
.into_iter()
.find(|x| KNOWN_SHELLS.contains(&x.as_str()));
shell.ok_or("no shell found".into())
}