Initial commit for fern
This commit is contained in:
116
src/shellenv/exec_ctx.rs
Normal file
116
src/shellenv/exec_ctx.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Copy,Clone,Debug,PartialEq,PartialOrd)]
|
||||
pub struct ExecFlags: u32 {
|
||||
const NO_FORK = 0x00000001;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ExecCtx {
|
||||
redirs: Vec<Redir>,
|
||||
flags: ExecFlags,
|
||||
io_masks: IoMasks,
|
||||
saved_io: Option<SavedIo>
|
||||
}
|
||||
|
||||
impl ExecCtx {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
redirs: vec![],
|
||||
flags: ExecFlags::empty(),
|
||||
io_masks: IoMasks::new(),
|
||||
saved_io: None
|
||||
}
|
||||
}
|
||||
pub fn masks(&self) -> &IoMasks {
|
||||
&self.io_masks
|
||||
}
|
||||
pub fn push_rdr(&mut self, redir: Redir) {
|
||||
self.redirs.push(redir)
|
||||
}
|
||||
pub fn saved_io(&mut self) -> &mut Option<SavedIo> {
|
||||
&mut self.saved_io
|
||||
}
|
||||
pub fn activate_rdrs(&mut self) -> ShResult<()> {
|
||||
let mut redirs = CmdRedirs::new(core::mem::take(&mut self.redirs));
|
||||
self.redirs = vec![];
|
||||
redirs.activate()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn flags(&self) -> ExecFlags {
|
||||
self.flags
|
||||
}
|
||||
pub fn set_flag(&mut self, flag: ExecFlags) {
|
||||
self.flags |= flag
|
||||
}
|
||||
pub fn unset_flag(&mut self, flag: ExecFlags) {
|
||||
self.flags &= !flag
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct SavedIo {
|
||||
pub stdin: RawFd,
|
||||
pub stdout: RawFd,
|
||||
pub stderr: RawFd
|
||||
}
|
||||
|
||||
impl SavedIo {
|
||||
pub fn save(stdin: RawFd, stdout: RawFd, stderr: RawFd) -> Self {
|
||||
Self { stdin, stdout, stderr }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct IoMask {
|
||||
default: RawFd,
|
||||
mask: Option<RawFd>
|
||||
}
|
||||
|
||||
impl IoMask {
|
||||
pub fn new(default: RawFd) -> Self {
|
||||
Self { default, mask: None }
|
||||
}
|
||||
pub fn new_mask(&mut self, mask: RawFd) {
|
||||
self.mask = Some(mask)
|
||||
}
|
||||
pub fn unmask(&mut self) {
|
||||
self.mask = None
|
||||
}
|
||||
pub fn get_fd(&self) -> RawFd {
|
||||
if let Some(fd) = self.mask {
|
||||
fd
|
||||
} else {
|
||||
self.default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
/// Necessary for when process file descriptors are permanently redirected using `exec`
|
||||
pub struct IoMasks {
|
||||
stdin: IoMask,
|
||||
stdout: IoMask,
|
||||
stderr: IoMask
|
||||
}
|
||||
|
||||
impl IoMasks {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stdin: IoMask::new(0),
|
||||
stdout: IoMask::new(1),
|
||||
stderr: IoMask::new(2),
|
||||
}
|
||||
}
|
||||
pub fn stdin(&self) -> &IoMask {
|
||||
&self.stdin
|
||||
}
|
||||
pub fn stdout(&self) -> &IoMask {
|
||||
&self.stdout
|
||||
}
|
||||
pub fn stderr(&self) -> &IoMask {
|
||||
&self.stderr
|
||||
}
|
||||
}
|
||||
573
src/shellenv/jobs.rs
Normal file
573
src/shellenv/jobs.rs
Normal file
@@ -0,0 +1,573 @@
|
||||
use std::{fmt, sync::{Arc, LazyLock, RwLock}};
|
||||
|
||||
use nix::unistd::setpgid;
|
||||
use shellenv::{disable_reaping, enable_reaping};
|
||||
use sys::SIG_EXIT_OFFSET;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct JobCmdFlags: u8 {
|
||||
const LONG = 0b0000_0001; // 0x01
|
||||
const PIDS = 0b0000_0010; // 0x02
|
||||
const NEW_ONLY = 0b0000_0100; // 0x04
|
||||
const RUNNING = 0b0000_1000; // 0x08
|
||||
const STOPPED = 0b0001_0000; // 0x10
|
||||
const INIT = 0b0010_0000; // 0x20
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DisplayWaitStatus(pub WtStat);
|
||||
|
||||
impl fmt::Display for DisplayWaitStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
WtStat::Exited(_, code) => {
|
||||
match code {
|
||||
0 => write!(f, "done"),
|
||||
_ => write!(f, "failed: {}", code),
|
||||
}
|
||||
}
|
||||
WtStat::Signaled(_, signal, _) => {
|
||||
write!(f, "signaled: {:?}", signal)
|
||||
}
|
||||
WtStat::Stopped(_, signal) => {
|
||||
write!(f, "stopped: {:?}", signal)
|
||||
}
|
||||
WtStat::PtraceEvent(_, signal, _) => {
|
||||
write!(f, "ptrace event: {:?}", signal)
|
||||
}
|
||||
WtStat::PtraceSyscall(_) => {
|
||||
write!(f, "ptrace syscall")
|
||||
}
|
||||
WtStat::Continued(_) => {
|
||||
write!(f, "continued")
|
||||
}
|
||||
WtStat::StillAlive => {
|
||||
write!(f, "running")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The job table
|
||||
pub static JOBS: LazyLock<Arc<RwLock<JobTab>>> = LazyLock::new(|| {
|
||||
Arc::new(
|
||||
RwLock::new(
|
||||
JobTab::new()
|
||||
)
|
||||
)
|
||||
});
|
||||
|
||||
pub fn write_jobs<'a,T,F: FnOnce(&mut JobTab) -> T>(operation: F) -> T {
|
||||
unsafe {
|
||||
let mut jobs = JOBS.write().unwrap();
|
||||
operation(&mut jobs)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_jobs<'a,T,F: FnOnce(&JobTab) -> T>(operation: F) -> T {
|
||||
unsafe {
|
||||
let jobs = JOBS.read().unwrap();
|
||||
operation(&jobs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum JobID {
|
||||
Pgid(Pid),
|
||||
Pid(Pid),
|
||||
TableID(usize),
|
||||
Command(String)
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct ChildProc {
|
||||
pgid: Pid,
|
||||
pid: Pid,
|
||||
command: Option<String>,
|
||||
stat: WtStat
|
||||
}
|
||||
|
||||
impl<'a> ChildProc {
|
||||
pub fn new(pid: Pid, command: Option<&str>, pgid: Option<Pid>) -> ShResult<Self> {
|
||||
let command = command.map(|str| str.to_string());
|
||||
let stat = if kill(pid,None).is_ok() {
|
||||
WtStat::StillAlive
|
||||
} else {
|
||||
WtStat::Exited(pid, 0)
|
||||
};
|
||||
let mut child = Self { pgid: pid, pid, command, stat };
|
||||
if let Some(pgid) = pgid {
|
||||
child.set_pgid(pgid)?;
|
||||
}
|
||||
Ok(child)
|
||||
}
|
||||
pub fn pid(&self) -> Pid {
|
||||
self.pid
|
||||
}
|
||||
pub fn pgid(&self) -> Pid {
|
||||
self.pgid
|
||||
}
|
||||
pub fn cmd(&self) -> Option<&str> {
|
||||
self.command.as_ref().map(|cmd| cmd.as_str())
|
||||
}
|
||||
pub fn stat(&self) -> WtStat {
|
||||
self.stat
|
||||
}
|
||||
pub fn wait(&mut self, flags: Option<WtFlag>) -> Result<WtStat,Errno> {
|
||||
let result = waitpid(self.pid, flags);
|
||||
if let Ok(stat) = result {
|
||||
self.stat = stat
|
||||
}
|
||||
result
|
||||
}
|
||||
pub fn kill<T: Into<Option<Signal>>>(&self, sig: T) -> ShResult<()> {
|
||||
Ok(kill(self.pid, sig)?)
|
||||
}
|
||||
pub fn set_pgid(&mut self, pgid: Pid) -> ShResult<()> {
|
||||
unsafe { setpgid(self.pid, pgid)? };
|
||||
self.pgid = pgid;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_stat(&mut self, stat: WtStat) {
|
||||
self.stat = stat
|
||||
}
|
||||
pub fn is_alive(&self) -> bool {
|
||||
self.stat == WtStat::StillAlive
|
||||
}
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
matches!(self.stat,WtStat::Stopped(..))
|
||||
}
|
||||
pub fn exited(&self) -> bool {
|
||||
matches!(self.stat,WtStat::Exited(..))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JobBldr {
|
||||
table_id: Option<usize>,
|
||||
pgid: Option<Pid>,
|
||||
children: Vec<ChildProc>
|
||||
}
|
||||
|
||||
impl Default for JobBldr {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl JobBldr {
|
||||
pub fn new() -> Self {
|
||||
Self { table_id: None, pgid: None, children: vec![] }
|
||||
}
|
||||
pub fn with_id(self, id: usize) -> Self {
|
||||
Self {
|
||||
table_id: Some(id),
|
||||
pgid: self.pgid,
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
pub fn with_pgid(self, pgid: Pid) -> Self {
|
||||
Self {
|
||||
table_id: self.table_id,
|
||||
pgid: Some(pgid),
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
pub fn with_children(self, children: Vec<ChildProc>) -> Self {
|
||||
Self {
|
||||
table_id: self.table_id,
|
||||
pgid: self.pgid,
|
||||
children
|
||||
}
|
||||
}
|
||||
pub fn build(self) -> Job {
|
||||
Job {
|
||||
table_id: self.table_id,
|
||||
pgid: self.pgid.unwrap_or(Pid::from_raw(0)),
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Job {
|
||||
table_id: Option<usize>,
|
||||
pgid: Pid,
|
||||
children: Vec<ChildProc>
|
||||
}
|
||||
|
||||
impl Job {
|
||||
pub fn set_tabid(&mut self, id: usize) {
|
||||
self.table_id = Some(id)
|
||||
}
|
||||
pub fn running(&self) -> bool {
|
||||
!self.children.iter().all(|chld| chld.exited())
|
||||
}
|
||||
pub fn tabid(&self) -> Option<usize> {
|
||||
self.table_id
|
||||
}
|
||||
pub fn pgid(&self) -> Pid {
|
||||
self.pgid
|
||||
}
|
||||
pub fn get_cmds(&self) -> Vec<&str> {
|
||||
let mut cmds = vec![];
|
||||
for child in &self.children {
|
||||
cmds.push(child.cmd().unwrap_or_default())
|
||||
}
|
||||
cmds
|
||||
}
|
||||
pub fn set_stats(&mut self, stat: WtStat) {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
pub fn get_stats(&self) -> Vec<WtStat> {
|
||||
self.children
|
||||
.iter()
|
||||
.map(|chld| chld.stat())
|
||||
.collect::<Vec<WtStat>>()
|
||||
}
|
||||
pub fn get_pids(&self) -> Vec<Pid> {
|
||||
self.children
|
||||
.iter()
|
||||
.map(|chld| chld.pid())
|
||||
.collect::<Vec<Pid>>()
|
||||
}
|
||||
pub fn children(&self) -> &[ChildProc] {
|
||||
&self.children
|
||||
}
|
||||
pub fn children_mut(&mut self) -> &mut Vec<ChildProc> {
|
||||
&mut self.children
|
||||
}
|
||||
pub fn killpg(&mut self, sig: Signal) -> ShResult<()> {
|
||||
let stat = match sig {
|
||||
Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP),
|
||||
Signal::SIGCONT => WtStat::Continued(self.pgid),
|
||||
Signal::SIGTERM => WtStat::Signaled(self.pgid, Signal::SIGTERM, false),
|
||||
_ => unimplemented!("{}",sig)
|
||||
};
|
||||
self.set_stats(stat);
|
||||
Ok(killpg(self.pgid, sig)?)
|
||||
}
|
||||
pub fn wait_pgrp<'a>(&mut self) -> ShResult<Vec<WtStat>> {
|
||||
let mut stats = vec![];
|
||||
for child in self.children.iter_mut() {
|
||||
let result = child.wait(Some(WtFlag::WUNTRACED));
|
||||
match result {
|
||||
Ok(stat) => {
|
||||
stats.push(stat);
|
||||
}
|
||||
Err(Errno::ECHILD) => break,
|
||||
Err(e) => return Err(e.into())
|
||||
}
|
||||
}
|
||||
Ok(stats)
|
||||
}
|
||||
pub fn update_by_id(&mut self, id: JobID, stat: WtStat) -> ShResult<()> {
|
||||
match id {
|
||||
JobID::Pid(pid) => {
|
||||
let query_result = self.children.iter_mut().find(|chld| chld.pid == pid);
|
||||
if let Some(child) = query_result {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
JobID::Command(cmd) => {
|
||||
let query_result = self.children
|
||||
.iter_mut()
|
||||
.find(|chld| chld
|
||||
.cmd()
|
||||
.is_some_and(|chld_cmd| chld_cmd.contains(&cmd))
|
||||
);
|
||||
if let Some(child) = query_result {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
JobID::TableID(tid) => {
|
||||
if self.table_id.is_some_and(|tblid| tblid == tid) {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
JobID::Pgid(pgid) => {
|
||||
if pgid == self.pgid {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn display(&self, job_order: &[usize], flags: JobCmdFlags) -> String {
|
||||
let long = flags.contains(JobCmdFlags::LONG);
|
||||
let init = flags.contains(JobCmdFlags::INIT);
|
||||
let pids = flags.contains(JobCmdFlags::PIDS);
|
||||
|
||||
let current = job_order.last();
|
||||
let prev = if job_order.len() > 2 {
|
||||
job_order.get(job_order.len() - 2)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let id = self.table_id.unwrap();
|
||||
let symbol = if current == self.table_id.as_ref() {
|
||||
"+"
|
||||
} else if prev == self.table_id.as_ref() {
|
||||
"-"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
let padding_count = symbol.len() + id.to_string().len() + 3;
|
||||
let padding = " ".repeat(padding_count);
|
||||
|
||||
let mut output = format!("[{}]{}\t", id + 1, symbol);
|
||||
for (i, cmd) in self.get_cmds().iter().enumerate() {
|
||||
let pid = if pids || init {
|
||||
let mut pid = self.get_pids().get(i).unwrap().to_string();
|
||||
pid.push(' ');
|
||||
pid
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let job_stat = *self.get_stats().get(i).unwrap();
|
||||
let fmt_stat = DisplayWaitStatus(job_stat).to_string();
|
||||
|
||||
let mut stat_line = if init {
|
||||
"".to_string()
|
||||
} else {
|
||||
fmt_stat.clone()
|
||||
};
|
||||
stat_line = format!("{}{} ",pid,stat_line);
|
||||
stat_line = format!("{} {}", stat_line, cmd);
|
||||
stat_line = match job_stat {
|
||||
WtStat::Stopped(..) | WtStat::Signaled(..) => style_text(stat_line, Style::Magenta),
|
||||
WtStat::Exited(_, code) => {
|
||||
match code {
|
||||
0 => style_text(stat_line, Style::Green),
|
||||
_ => style_text(stat_line, Style::Red),
|
||||
}
|
||||
}
|
||||
_ => style_text(stat_line, Style::Cyan)
|
||||
};
|
||||
if i != self.get_cmds().len() - 1 {
|
||||
stat_line = format!("{} |",stat_line);
|
||||
}
|
||||
|
||||
let stat_final = if long {
|
||||
format!(
|
||||
"{}{} {}",
|
||||
if i != 0 { &padding } else { "" },
|
||||
self.get_pids().get(i).unwrap(),
|
||||
stat_line
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}{}",
|
||||
if i != 0 { &padding } else { "" },
|
||||
stat_line
|
||||
)
|
||||
};
|
||||
output.push_str(&stat_final);
|
||||
output.push('\n');
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JobTab {
|
||||
fg: Option<Job>,
|
||||
order: Vec<usize>,
|
||||
new_updates: Vec<usize>,
|
||||
jobs: Vec<Option<Job>>
|
||||
}
|
||||
|
||||
impl JobTab {
|
||||
pub fn new() -> Self {
|
||||
Self { fg: None, order: vec![], new_updates: vec![], jobs: vec![] }
|
||||
}
|
||||
pub fn take_fg(&mut self) -> Option<Job> {
|
||||
self.fg.take()
|
||||
}
|
||||
fn next_open_pos(&self) -> usize {
|
||||
if let Some(position) = self.jobs.iter().position(|slot| slot.is_none()) {
|
||||
position
|
||||
} else {
|
||||
self.jobs.len()
|
||||
}
|
||||
}
|
||||
pub fn jobs(&self) -> &Vec<Option<Job>> {
|
||||
&self.jobs
|
||||
}
|
||||
pub fn jobs_mut(&mut self) -> &mut Vec<Option<Job>> {
|
||||
&mut self.jobs
|
||||
}
|
||||
pub fn curr_job(&self) -> Option<usize> {
|
||||
self.order.last().copied()
|
||||
}
|
||||
pub fn prev_job(&self) -> Option<usize> {
|
||||
self.order.last().copied()
|
||||
}
|
||||
fn prune_jobs(&mut self) {
|
||||
while let Some(job) = self.jobs.last() {
|
||||
if job.is_none() {
|
||||
self.jobs.pop();
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn insert_job(&mut self, mut job: Job, silent: bool) -> ShResult<usize> {
|
||||
self.prune_jobs();
|
||||
let tab_pos = if let Some(id) = job.tabid() { id } else { self.next_open_pos() };
|
||||
job.set_tabid(tab_pos);
|
||||
self.order.push(tab_pos);
|
||||
if !silent {
|
||||
write(borrow_fd(1),format!("{}", job.display(&self.order, JobCmdFlags::INIT)).as_bytes())?;
|
||||
}
|
||||
if tab_pos == self.jobs.len() {
|
||||
self.jobs.push(Some(job))
|
||||
} else {
|
||||
self.jobs[tab_pos] = Some(job);
|
||||
}
|
||||
Ok(tab_pos)
|
||||
}
|
||||
pub fn order(&self) -> &[usize] {
|
||||
&self.order
|
||||
}
|
||||
pub fn query(&self, identifier: JobID) -> Option<&Job> {
|
||||
match identifier {
|
||||
// Match by process group ID
|
||||
JobID::Pgid(pgid) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| j.pgid == pgid)
|
||||
})
|
||||
}
|
||||
// Match by process ID
|
||||
JobID::Pid(pid) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| j.children.iter().any(|child| child.pid == pid))
|
||||
})
|
||||
}
|
||||
// Match by table ID (index in the job table)
|
||||
JobID::TableID(id) => {
|
||||
self.jobs.get(id).and_then(|job| job.as_ref())
|
||||
}
|
||||
// Match by command name (partial match)
|
||||
JobID::Command(cmd) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| {
|
||||
j.children.iter().any(|child| {
|
||||
child.command.as_ref().is_some_and(|c| c.contains(&cmd))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn query_mut(&mut self, identifier: JobID) -> Option<&mut Job> {
|
||||
match identifier {
|
||||
// Match by process group ID
|
||||
JobID::Pgid(pgid) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| j.pgid == pgid)
|
||||
})
|
||||
}
|
||||
// Match by process ID
|
||||
JobID::Pid(pid) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| j.children.iter().any(|child| child.pid == pid))
|
||||
})
|
||||
}
|
||||
// Match by table ID (index in the job table)
|
||||
JobID::TableID(id) => {
|
||||
self.jobs.get_mut(id).and_then(|job| job.as_mut())
|
||||
}
|
||||
// Match by command name (partial match)
|
||||
JobID::Command(cmd) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| {
|
||||
j.children.iter().any(|child| {
|
||||
child.command.as_ref().is_some_and(|c| c.contains(&cmd))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_fg(&self) -> Option<&Job> {
|
||||
self.fg.as_ref()
|
||||
}
|
||||
pub fn get_fg_mut(&mut self) -> Option<&mut Job> {
|
||||
self.fg.as_mut()
|
||||
}
|
||||
pub fn new_fg<'a>(&mut self, job: Job) -> ShResult<Vec<WtStat>> {
|
||||
log!(DEBUG, "New fg job: {:?}", job);
|
||||
let pgid = job.pgid();
|
||||
self.fg = Some(job);
|
||||
attach_tty(pgid)?;
|
||||
let statuses = self.fg.as_mut().unwrap().wait_pgrp()?;
|
||||
attach_tty(getpgrp())?;
|
||||
Ok(statuses)
|
||||
}
|
||||
pub fn fg_to_bg(&mut self, stat: WtStat) -> ShResult<()> {
|
||||
if self.fg.is_none() {
|
||||
return Ok(())
|
||||
}
|
||||
take_term()?;
|
||||
let fg = std::mem::take(&mut self.fg);
|
||||
log!(DEBUG, "Moving foreground job to background");
|
||||
if let Some(mut job) = fg {
|
||||
job.set_stats(stat);
|
||||
self.insert_job(job, false)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn bg_to_fg(&mut self, shenv: &mut ShEnv, id: JobID) -> ShResult<()> {
|
||||
let job = self.remove_job(id);
|
||||
if let Some(job) = job {
|
||||
super::wait_fg(job, shenv)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn remove_job(&mut self, id: JobID) -> Option<Job> {
|
||||
let tabid = self.query(id).map(|job| job.tabid().unwrap());
|
||||
if let Some(tabid) = tabid {
|
||||
self.jobs.get_mut(tabid).and_then(Option::take)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn print_jobs(&self, flags: JobCmdFlags) -> ShResult<()> {
|
||||
let jobs = if flags.contains(JobCmdFlags::NEW_ONLY) {
|
||||
&self.jobs
|
||||
.iter()
|
||||
.filter(|job| job.as_ref().is_some_and(|job| self.new_updates.contains(&job.tabid().unwrap())))
|
||||
.map(|job| job.as_ref())
|
||||
.collect::<Vec<Option<&Job>>>()
|
||||
} else {
|
||||
&self.jobs
|
||||
.iter()
|
||||
.map(|job| job.as_ref())
|
||||
.collect::<Vec<Option<&Job>>>()
|
||||
};
|
||||
for job in jobs.iter().flatten() {
|
||||
// Skip foreground job
|
||||
let id = job.tabid().unwrap();
|
||||
// Filter jobs based on flags
|
||||
if flags.contains(JobCmdFlags::RUNNING) && !matches!(job.get_stats().get(id).unwrap(), WtStat::StillAlive | WtStat::Continued(_)) {
|
||||
continue;
|
||||
}
|
||||
if flags.contains(JobCmdFlags::STOPPED) && !matches!(job.get_stats().get(id).unwrap(), WtStat::Stopped(_,_)) {
|
||||
continue;
|
||||
}
|
||||
// Print the job in the selected format
|
||||
write(borrow_fd(1), format!("{}\n",job.display(&self.order,flags)).as_bytes())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
22
src/shellenv/logic.rs
Normal file
22
src/shellenv/logic.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct LogTab {
|
||||
aliases: HashMap<String,String>,
|
||||
functions: HashMap<String,String>
|
||||
}
|
||||
|
||||
impl LogTab {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
aliases: HashMap::new(),
|
||||
functions: HashMap::new()
|
||||
}
|
||||
}
|
||||
pub fn get_alias(&self,name: &str) -> Option<&str> {
|
||||
self.aliases.get(name).map(|a| a.as_str())
|
||||
}
|
||||
pub fn get_function(&self,name: &str) -> Option<&str> {
|
||||
self.functions.get(name).map(|a| a.as_str())
|
||||
}
|
||||
}
|
||||
18
src/shellenv/meta.rs
Normal file
18
src/shellenv/meta.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct MetaTab {
|
||||
last_status: i32
|
||||
}
|
||||
|
||||
impl MetaTab {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
last_status: 0
|
||||
}
|
||||
}
|
||||
pub fn set_status(&mut self, code: i32) {
|
||||
self.last_status = code
|
||||
}
|
||||
pub fn last_status(&self) -> i32 {
|
||||
self.last_status
|
||||
}
|
||||
}
|
||||
111
src/shellenv/mod.rs
Normal file
111
src/shellenv/mod.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::env;
|
||||
|
||||
use jobs::Job;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub mod jobs;
|
||||
pub mod logic;
|
||||
pub mod exec_ctx;
|
||||
pub mod meta;
|
||||
pub mod shenv;
|
||||
pub mod vars;
|
||||
|
||||
/// Calls attach_tty() on the shell's process group to retake control of the terminal
|
||||
pub fn take_term() -> ShResult<()> {
|
||||
attach_tty(getpgrp())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable_reaping() -> ShResult<()> {
|
||||
log!(TRACE, "Disabling reaping");
|
||||
unsafe { signal(Signal::SIGCHLD, SigHandler::Handler(crate::signal::ignore_sigchld)) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits on the current foreground job and updates the shell's last status code
|
||||
pub fn wait_fg(job: Job, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(TRACE, "Waiting on foreground job");
|
||||
let mut code = 0;
|
||||
attach_tty(job.pgid())?;
|
||||
disable_reaping()?;
|
||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||
for status in statuses {
|
||||
match status {
|
||||
WtStat::Exited(_, exit_code) => {
|
||||
code = exit_code;
|
||||
}
|
||||
WtStat::Stopped(pid, sig) => {
|
||||
write_jobs(|j| j.fg_to_bg(status))?;
|
||||
code = sys::SIG_EXIT_OFFSET + sig as i32;
|
||||
},
|
||||
WtStat::Signaled(pid, sig, _) => {
|
||||
if sig == Signal::SIGTSTP {
|
||||
write_jobs(|j| j.fg_to_bg(status))?;
|
||||
}
|
||||
code = sys::SIG_EXIT_OFFSET + sig as i32;
|
||||
},
|
||||
_ => { /* Do nothing */ }
|
||||
}
|
||||
}
|
||||
take_term()?;
|
||||
shenv.set_code(code);
|
||||
log!(TRACE, "exit code: {}", code);
|
||||
enable_reaping()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn log_level() -> crate::libsh::utils::LogLevel {
|
||||
let level = env::var("FERN_LOG_LEVEL").unwrap_or_default();
|
||||
match level.to_lowercase().as_str() {
|
||||
"error" => ERROR,
|
||||
"warn" => WARN,
|
||||
"info" => INFO,
|
||||
"debug" => DEBUG,
|
||||
"trace" => TRACE,
|
||||
_ => NULL
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_reaping() -> ShResult<()> {
|
||||
log!(TRACE, "Enabling reaping");
|
||||
unsafe { signal(Signal::SIGCHLD, SigHandler::Handler(crate::signal::handle_sigchld)) }.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
||||
if !isatty(0).unwrap_or(false) || pgid == term_ctlr() {
|
||||
return Ok(())
|
||||
}
|
||||
log!(DEBUG, "Attaching tty to pgid: {}",pgid);
|
||||
|
||||
if pgid == getpgrp() && term_ctlr() != getpgrp() {
|
||||
kill(term_ctlr(), Signal::SIGTTOU).ok();
|
||||
}
|
||||
|
||||
let mut new_mask = SigSet::empty();
|
||||
let mut mask_bkup = SigSet::empty();
|
||||
|
||||
new_mask.add(Signal::SIGTSTP);
|
||||
new_mask.add(Signal::SIGTTIN);
|
||||
new_mask.add(Signal::SIGTTOU);
|
||||
|
||||
pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(&mut new_mask), Some(&mut mask_bkup))?;
|
||||
|
||||
let result = unsafe { tcsetpgrp(borrow_fd(0), pgid) };
|
||||
|
||||
pthread_sigmask(SigmaskHow::SIG_SETMASK, Some(&mut mask_bkup), Some(&mut new_mask))?;
|
||||
|
||||
match result {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => {
|
||||
log!(ERROR, "error while switching term control: {}",e);
|
||||
unsafe { tcsetpgrp(borrow_fd(0), getpgrp())? };
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn term_ctlr() -> Pid {
|
||||
unsafe { tcgetpgrp(borrow_fd(0)).unwrap_or(getpgrp()) }
|
||||
}
|
||||
85
src/shellenv/shenv.rs
Normal file
85
src/shellenv/shenv.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ShEnv {
|
||||
vars: shellenv::vars::VarTab,
|
||||
logic: shellenv::logic::LogTab,
|
||||
meta: shellenv::meta::MetaTab,
|
||||
ctx: shellenv::exec_ctx::ExecCtx
|
||||
}
|
||||
|
||||
impl ShEnv {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vars: shellenv::vars::VarTab::new(),
|
||||
logic: shellenv::logic::LogTab::new(),
|
||||
meta: shellenv::meta::MetaTab::new(),
|
||||
ctx: shellenv::exec_ctx::ExecCtx::new(),
|
||||
}
|
||||
}
|
||||
pub fn vars(&self) -> &shellenv::vars::VarTab {
|
||||
&self.vars
|
||||
}
|
||||
pub fn vars_mut(&mut self) -> &mut shellenv::vars::VarTab {
|
||||
&mut self.vars
|
||||
}
|
||||
pub fn meta(&self) -> &shellenv::meta::MetaTab {
|
||||
&self.meta
|
||||
}
|
||||
pub fn meta_mut(&mut self) -> &mut shellenv::meta::MetaTab {
|
||||
&mut self.meta
|
||||
}
|
||||
pub fn logic(&self) -> &shellenv::logic::LogTab {
|
||||
&self.logic
|
||||
}
|
||||
pub fn logic_mut(&mut self) -> &mut shellenv::logic::LogTab {
|
||||
&mut self.logic
|
||||
}
|
||||
pub fn save_io(&mut self) -> ShResult<()> {
|
||||
let ctx = self.ctx_mut();
|
||||
let stdin = ctx.masks().stdin().get_fd();
|
||||
let stdout = ctx.masks().stdout().get_fd();
|
||||
let stderr = ctx.masks().stderr().get_fd();
|
||||
|
||||
let saved_in = dup(stdin)?;
|
||||
let saved_out = dup(stdout)?;
|
||||
let saved_err = dup(stderr)?;
|
||||
|
||||
let saved_io = shellenv::exec_ctx::SavedIo::save(saved_in, saved_out, saved_err);
|
||||
*ctx.saved_io() = Some(saved_io);
|
||||
Ok(())
|
||||
}
|
||||
pub fn reset_io(&mut self) -> ShResult<()> {
|
||||
let ctx = self.ctx_mut();
|
||||
if let Some(saved) = ctx.saved_io().take() {
|
||||
let saved_in = saved.stdin;
|
||||
let saved_out = saved.stdout;
|
||||
let saved_err = saved.stderr;
|
||||
dup2(0,saved_in)?;
|
||||
close(saved_in)?;
|
||||
dup2(1,saved_out)?;
|
||||
close(saved_out)?;
|
||||
dup2(2,saved_err)?;
|
||||
close(saved_err)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn collect_redirs(&mut self, mut redirs: Vec<Redir>) {
|
||||
let ctx = self.ctx_mut();
|
||||
while let Some(redir) = redirs.pop() {
|
||||
ctx.push_rdr(redir);
|
||||
}
|
||||
}
|
||||
pub fn set_code(&mut self, code: i32) {
|
||||
self.vars_mut().set_param("?", &code.to_string());
|
||||
}
|
||||
pub fn get_code(&self) -> i32 {
|
||||
self.vars().get_param("?").parse::<i32>().unwrap_or(0)
|
||||
}
|
||||
pub fn ctx(&self) -> &shellenv::exec_ctx::ExecCtx {
|
||||
&self.ctx
|
||||
}
|
||||
pub fn ctx_mut(&mut self) -> &mut shellenv::exec_ctx::ExecCtx {
|
||||
&mut self.ctx
|
||||
}
|
||||
}
|
||||
162
src/shellenv/vars.rs
Normal file
162
src/shellenv/vars.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use std::env;
|
||||
|
||||
use nix::unistd::{gethostname, User};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct VarTab {
|
||||
env: HashMap<String,String>,
|
||||
params: HashMap<String,String>,
|
||||
pos_params: VecDeque<String>,
|
||||
vars: HashMap<String,String>
|
||||
}
|
||||
|
||||
impl VarTab {
|
||||
pub fn new() -> Self {
|
||||
let (params,pos_params) = Self::init_params();
|
||||
Self {
|
||||
env: Self::init_env(),
|
||||
params,
|
||||
pos_params,
|
||||
vars: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn init_params() -> (HashMap<String,String>, VecDeque<String>) {
|
||||
let mut args = std::env::args().collect::<Vec<String>>();
|
||||
let mut params = HashMap::new();
|
||||
let mut pos_params = VecDeque::new();
|
||||
|
||||
params.insert("@".to_string(), args.join(" "));
|
||||
params.insert("#".to_string(), args.len().to_string());
|
||||
|
||||
while let Some(arg) = args.pop() {
|
||||
pos_params.fpush(arg);
|
||||
}
|
||||
|
||||
(params,pos_params)
|
||||
}
|
||||
pub fn init_env() -> HashMap<String,String> {
|
||||
let pathbuf_to_string = |pb: Result<PathBuf, std::io::Error>| pb.unwrap_or_default().to_string_lossy().to_string();
|
||||
// First, inherit any env vars from the parent process
|
||||
let mut env_vars = std::env::vars().collect::<HashMap<String,String>>();
|
||||
let term = {
|
||||
if isatty(1).unwrap() {
|
||||
if let Ok(term) = std::env::var("TERM") {
|
||||
term
|
||||
} else {
|
||||
"linux".to_string()
|
||||
}
|
||||
} else {
|
||||
"xterm-256color".to_string()
|
||||
}
|
||||
};
|
||||
let home;
|
||||
let username;
|
||||
let uid;
|
||||
if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() {
|
||||
home = user.dir;
|
||||
username = user.name;
|
||||
uid = user.uid;
|
||||
} else {
|
||||
home = PathBuf::new();
|
||||
username = "unknown".into();
|
||||
uid = 0.into();
|
||||
}
|
||||
let home = pathbuf_to_string(Ok(home));
|
||||
let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default();
|
||||
|
||||
env_vars.insert("IFS".into(), " \t\n".into());
|
||||
env::set_var("IFS", " \t\n");
|
||||
env_vars.insert("HOSTNAME".into(), hostname.clone());
|
||||
env::set_var("HOSTNAME", hostname);
|
||||
env_vars.insert("UID".into(), uid.to_string());
|
||||
env::set_var("UID", uid.to_string());
|
||||
env_vars.insert("PPID".into(), getppid().to_string());
|
||||
env::set_var("PPID", getppid().to_string());
|
||||
env_vars.insert("TMPDIR".into(), "/tmp".into());
|
||||
env::set_var("TMPDIR", "/tmp");
|
||||
env_vars.insert("TERM".into(), term.clone());
|
||||
env::set_var("TERM", term);
|
||||
env_vars.insert("LANG".into(), "en_US.UTF-8".into());
|
||||
env::set_var("LANG", "en_US.UTF-8");
|
||||
env_vars.insert("USER".into(), username.clone());
|
||||
env::set_var("USER", username.clone());
|
||||
env_vars.insert("LOGNAME".into(), username.clone());
|
||||
env::set_var("LOGNAME", username);
|
||||
env_vars.insert("PWD".into(), pathbuf_to_string(std::env::current_dir()));
|
||||
env::set_var("PWD", pathbuf_to_string(std::env::current_dir()));
|
||||
env_vars.insert("OLDPWD".into(), pathbuf_to_string(std::env::current_dir()));
|
||||
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
||||
env_vars.insert("HOME".into(), home.clone());
|
||||
env::set_var("HOME", home.clone());
|
||||
env_vars.insert("SHELL".into(), pathbuf_to_string(std::env::current_exe()));
|
||||
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
||||
env_vars.insert("HIST_FILE".into(),format!("{}/.fern_hist",home));
|
||||
env::set_var("HIST_FILE",format!("{}/.fern_hist",home));
|
||||
|
||||
env_vars
|
||||
}
|
||||
pub fn env(&self) -> &HashMap<String,String> {
|
||||
&self.env
|
||||
}
|
||||
pub fn env_mut(&mut self) -> &mut HashMap<String,String> {
|
||||
&mut self.env
|
||||
}
|
||||
pub fn reset_params(&mut self) {
|
||||
self.params.clear();
|
||||
}
|
||||
pub fn unset_param(&mut self, key: &str) {
|
||||
self.params.remove(key);
|
||||
}
|
||||
pub fn set_param(&mut self, key: &str, val: &str) {
|
||||
self.params.insert(key.to_string(), val.to_string());
|
||||
}
|
||||
pub fn get_param(&self, key: &str) -> &str {
|
||||
self.params.get(key).map(|s| s.as_str()).unwrap_or_default()
|
||||
}
|
||||
/// Push an arg to the back of the positional parameter deque
|
||||
pub fn bpush_arg(&mut self, arg: &str) {
|
||||
self.pos_params.bpush(arg.to_string());
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
}
|
||||
/// Pop an arg from the back of the positional parameter deque
|
||||
pub fn bpop_arg(&mut self) -> Option<String> {
|
||||
let item = self.pos_params.bpop();
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
item
|
||||
}
|
||||
/// Push an arg to the front of the positional parameter deque
|
||||
pub fn fpush_arg(&mut self, arg: &str) {
|
||||
self.pos_params.fpush(arg.to_string());
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
}
|
||||
/// Pop an arg from the front of the positional parameter deque
|
||||
pub fn fpop_arg(&mut self) -> Option<String> {
|
||||
let item = self.pos_params.fpop();
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
item
|
||||
}
|
||||
pub fn get_var(&self, var: &str) -> &str {
|
||||
if let Ok(idx) = var.parse::<usize>() {
|
||||
self.pos_params.get(idx).map(|p| p.as_str()).unwrap_or_default()
|
||||
} else if let Some(var) = self.env.get(var) {
|
||||
var.as_str()
|
||||
} else if let Some(param) = self.params.get(var) {
|
||||
param.as_str()
|
||||
} else {
|
||||
self.vars.get(var).map(|v| v.as_str()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
pub fn set_var(&mut self, var: &str, val: &str) {
|
||||
self.vars.insert(var.to_string(), val.to_string());
|
||||
}
|
||||
pub fn export(&mut self, var: &str, val: &str) {
|
||||
self.env.insert(var.to_string(),val.to_string());
|
||||
std::env::set_var(var, val);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user