re-imported job/signal code from old implementation
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use crate::{libsh::error::{ShErr, ShErrKind}, parse::lex::{is_hard_sep, LexFlags, LexStream, Tk, Span, TkErr, TkFlags, TkState, TkRule}, prelude::*, state::read_vars};
|
||||
use crate::{parse::lex::{is_hard_sep, LexFlags, LexStream, Tk, Span, TkErr, TkFlags, TkRule}, state::read_vars};
|
||||
|
||||
/// Variable substitution marker
|
||||
pub const VAR_SUB: char = '\u{fdd0}';
|
||||
|
||||
31
src/fern.rs
31
src/fern.rs
@@ -7,12 +7,43 @@ pub mod expand;
|
||||
pub mod state;
|
||||
pub mod builtin;
|
||||
pub mod jobs;
|
||||
pub mod signal;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream};
|
||||
use termios::{LocalFlags, Termios};
|
||||
use crate::prelude::*;
|
||||
|
||||
pub static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
||||
|
||||
pub fn save_termios() {
|
||||
unsafe {
|
||||
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
||||
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
||||
termios.local_flags &= !LocalFlags::ECHOCTL;
|
||||
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
|
||||
Some(termios)
|
||||
} else {
|
||||
None
|
||||
});
|
||||
}
|
||||
}
|
||||
pub fn get_saved_termios() -> Option<Termios> {
|
||||
unsafe {
|
||||
// This is only used when the shell exits so it's fine
|
||||
// SAVED_TERMIOS is only mutated once at the start as well
|
||||
SAVED_TERMIOS.clone().flatten()
|
||||
}
|
||||
}
|
||||
fn set_termios() {
|
||||
if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
||||
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
||||
termios.local_flags &= !LocalFlags::ECHOCTL;
|
||||
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
'main: loop {
|
||||
let input = prompt::read_line().unwrap();
|
||||
|
||||
65
src/jobs.rs
65
src/jobs.rs
@@ -1,4 +1,6 @@
|
||||
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, procio::borrow_fd};
|
||||
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, procio::borrow_fd, state::{set_status, write_jobs}};
|
||||
|
||||
pub const SIG_EXIT_OFFSET: i32 = 128;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -354,6 +356,67 @@ pub fn term_ctlr() -> Pid {
|
||||
tcgetpgrp(borrow_fd(0)).unwrap_or(getpgrp())
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
flog!(TRACE, "Disabling reaping");
|
||||
unsafe { signal(Signal::SIGCHLD, SigHandler::Handler(crate::signal::ignore_sigchld)) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enable_reaping() -> ShResult<()> {
|
||||
flog!(TRACE, "Enabling reaping");
|
||||
unsafe { signal(Signal::SIGCHLD, SigHandler::Handler(crate::signal::handle_sigchld)) }.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits on the current foreground job and updates the shell's last status code
|
||||
pub fn wait_fg(job: Job) -> ShResult<()> {
|
||||
flog!(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(_, sig) => {
|
||||
write_jobs(|j| j.fg_to_bg(status))?;
|
||||
code = SIG_EXIT_OFFSET + sig as i32;
|
||||
},
|
||||
WtStat::Signaled(_, sig, _) => {
|
||||
if sig == Signal::SIGTSTP {
|
||||
write_jobs(|j| j.fg_to_bg(status))?;
|
||||
}
|
||||
code = SIG_EXIT_OFFSET + sig as i32;
|
||||
},
|
||||
_ => { /* Do nothing */ }
|
||||
}
|
||||
}
|
||||
take_term()?;
|
||||
set_status(code);
|
||||
flog!(TRACE, "exit code: {}", code);
|
||||
enable_reaping()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn dispatch_job(job: Job, is_bg: bool) -> ShResult<()> {
|
||||
if is_bg {
|
||||
write_jobs(|j| {
|
||||
j.insert_job(job, false)
|
||||
})?;
|
||||
} else {
|
||||
wait_fg(job)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
||||
// If we aren't attached to a terminal, the pgid already controls it, or the process group does not exist
|
||||
// Then return ok
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{fmt::Display, ops::Range, str::FromStr};
|
||||
use std::{fmt::Display, ops::Range};
|
||||
|
||||
use crate::{parse::lex::Span, prelude::*};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod error;
|
||||
pub mod term;
|
||||
pub mod flog;
|
||||
pub mod sys;
|
||||
|
||||
18
src/libsh/sys.rs
Normal file
18
src/libsh/sys.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use crate::{prelude::*, state::write_jobs};
|
||||
|
||||
pub fn sh_quit(code: i32) -> ! {
|
||||
write_jobs(|j| {
|
||||
for job in j.jobs_mut().iter_mut().flatten() {
|
||||
job.killpg(Signal::SIGTERM).ok();
|
||||
}
|
||||
});
|
||||
if let Some(termios) = crate::get_saved_termios() {
|
||||
termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap();
|
||||
}
|
||||
if code == 0 {
|
||||
eprintln!("exit");
|
||||
} else {
|
||||
eprintln!("exit {code}");
|
||||
}
|
||||
exit(code);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use nix::sys::wait::WaitPidFlag;
|
||||
|
||||
use crate::{builtin::echo::echo, libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state::{self, write_vars}};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{fmt::Display, ops::{Bound, Deref, Range, RangeBounds}};
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
use crate::{builtin::BUILTINS, libsh::error::{ShErr, ShErrKind}, prelude::*};
|
||||
use crate::{builtin::BUILTINS, prelude::*};
|
||||
|
||||
pub const KEYWORDS: [&'static str;14] = [
|
||||
"if",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use lex::{is_hard_sep, Span, Tk, TkFlags, TkRule};
|
||||
use lex::{Span, Tk, TkFlags, TkRule};
|
||||
|
||||
use crate::{prelude::*, libsh::error::{ShErr, ShErrKind, ShResult}, procio::{IoFd, IoFile, IoInfo}};
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Standard Library Common IO and FS Abstractions
|
||||
pub use std::io::{
|
||||
self,
|
||||
BufRead,
|
||||
BufReader,
|
||||
BufWriter,
|
||||
Error,
|
||||
ErrorKind,
|
||||
Read,
|
||||
Seek,
|
||||
SeekFrom,
|
||||
Write,
|
||||
self,
|
||||
BufRead,
|
||||
BufReader,
|
||||
BufWriter,
|
||||
Error,
|
||||
ErrorKind,
|
||||
Read,
|
||||
Seek,
|
||||
SeekFrom,
|
||||
Write,
|
||||
};
|
||||
pub use std::fs::{ self, File, OpenOptions };
|
||||
pub use std::path::{ Path, PathBuf };
|
||||
@@ -25,18 +25,19 @@ pub use std::os::unix::io::{ AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd,
|
||||
|
||||
// Nix crate for POSIX APIs
|
||||
pub use nix::{
|
||||
errno::Errno,
|
||||
fcntl::{ open, OFlag },
|
||||
sys::{
|
||||
signal::{ self, kill, killpg, pthread_sigmask, SigSet, SigmaskHow, SigHandler, Signal },
|
||||
stat::Mode,
|
||||
wait::{ waitpid, WaitPidFlag as WtFlag, WaitStatus as WtStat },
|
||||
},
|
||||
libc::{ STDIN_FILENO, STDERR_FILENO, STDOUT_FILENO },
|
||||
unistd::{
|
||||
dup, read, isatty, write, close, setpgid, dup2, getpgrp,
|
||||
execvpe, tcgetpgrp, tcsetpgrp, fork, pipe, Pid, ForkResult
|
||||
},
|
||||
errno::Errno,
|
||||
fcntl::{ open, OFlag },
|
||||
sys::{
|
||||
termios::{ self },
|
||||
signal::{ self, signal, kill, killpg, pthread_sigmask, SigSet, SigmaskHow, SigHandler, Signal },
|
||||
stat::Mode,
|
||||
wait::{ waitpid, WaitPidFlag as WtFlag, WaitStatus as WtStat },
|
||||
},
|
||||
libc::{ self, STDIN_FILENO, STDERR_FILENO, STDOUT_FILENO },
|
||||
unistd::{
|
||||
dup, read, isatty, write, close, setpgid, dup2, getpgrp, getpgid,
|
||||
execvpe, tcgetpgrp, tcsetpgrp, fork, pipe, Pid, ForkResult
|
||||
},
|
||||
};
|
||||
pub use bitflags::bitflags;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{fs::{File, OpenOptions}, ops::{Deref, DerefMut}, path::PathBuf};
|
||||
use std::{fs::File, ops::{Deref, DerefMut}, path::PathBuf};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use rustyline::history::{History, SearchResult};
|
||||
|
||||
@@ -3,9 +3,8 @@ pub mod readline;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use history::FernHist;
|
||||
use readline::FernReadline;
|
||||
use rustyline::{error::ReadlineError, history::{FileHistory, History}, Config, Editor};
|
||||
use rustyline::{error::ReadlineError, history::FileHistory, Editor};
|
||||
|
||||
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::borrow::Cow;
|
||||
|
||||
use rustyline::{completion::Completer, highlight::Highlighter, hint::{Hint, Hinter}, validate::{ValidationResult, Validator}, Helper};
|
||||
|
||||
use crate::{libsh::term::{Style, Styled}, prelude::*};
|
||||
use crate::libsh::term::{Style, Styled};
|
||||
|
||||
pub struct FernReadline {
|
||||
}
|
||||
|
||||
159
src/signal.rs
Normal file
159
src/signal.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use crate::{jobs::{take_term, JobCmdFlags, JobID}, libsh::{error::ShResult, sys::sh_quit}, prelude::*, state::{read_jobs, write_jobs}};
|
||||
|
||||
pub fn sig_setup() {
|
||||
unsafe {
|
||||
signal(Signal::SIGCHLD, SigHandler::Handler(handle_sigchld)).unwrap();
|
||||
signal(Signal::SIGQUIT, SigHandler::Handler(handle_sigquit)).unwrap();
|
||||
signal(Signal::SIGTSTP, SigHandler::Handler(handle_sigtstp)).unwrap();
|
||||
signal(Signal::SIGHUP, SigHandler::Handler(handle_sighup)).unwrap();
|
||||
signal(Signal::SIGINT, SigHandler::Handler(handle_sigint)).unwrap();
|
||||
signal(Signal::SIGTTIN, SigHandler::SigIgn).unwrap();
|
||||
signal(Signal::SIGTTOU, SigHandler::SigIgn).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern "C" fn handle_sighup(_: libc::c_int) {
|
||||
write_jobs(|j| {
|
||||
for job in j.jobs_mut().iter_mut().flatten() {
|
||||
job.killpg(Signal::SIGTERM).ok();
|
||||
}
|
||||
});
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigtstp(_: libc::c_int) {
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.get_fg_mut() {
|
||||
job.killpg(Signal::SIGTSTP).ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigint(_: libc::c_int) {
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.get_fg_mut() {
|
||||
job.killpg(Signal::SIGINT).ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub extern "C" fn ignore_sigchld(_: libc::c_int) {
|
||||
/*
|
||||
Do nothing
|
||||
|
||||
This function exists because using SIGIGN to ignore SIGCHLD
|
||||
will cause the kernel to automatically reap the child process, which is not what we want.
|
||||
This handler will leave the signaling process as a zombie, allowing us
|
||||
to handle it somewhere else.
|
||||
|
||||
This handler is used when we want to handle SIGCHLD explicitly,
|
||||
like in the case of handling foreground jobs
|
||||
*/
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigquit(_: libc::c_int) {
|
||||
sh_quit(0)
|
||||
}
|
||||
|
||||
pub extern "C" fn handle_sigchld(_: libc::c_int) {
|
||||
let flags = WtFlag::WNOHANG | WtFlag::WSTOPPED;
|
||||
while let Ok(status) = waitpid(None, Some(flags)) {
|
||||
if let Err(e) = match status {
|
||||
WtStat::Exited(pid, _) => child_exited(pid, status),
|
||||
WtStat::Signaled(pid, signal, _) => child_signaled(pid, signal),
|
||||
WtStat::Stopped(pid, signal) => child_stopped(pid, signal),
|
||||
WtStat::Continued(pid) => child_continued(pid),
|
||||
WtStat::StillAlive => break,
|
||||
_ => unimplemented!()
|
||||
} {
|
||||
eprintln!("{}",e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn child_signaled(pid: Pid, sig: Signal) -> ShResult<()> {
|
||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||
let child = job.children_mut().iter_mut().find(|chld| pid == chld.pid()).unwrap();
|
||||
let stat = WtStat::Signaled(pid, sig, false);
|
||||
child.set_stat(stat);
|
||||
}
|
||||
});
|
||||
if sig == Signal::SIGINT {
|
||||
take_term().unwrap()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn child_stopped(pid: Pid, sig: Signal) -> ShResult<()> {
|
||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||
let child = job.children_mut().iter_mut().find(|chld| pid == chld.pid()).unwrap();
|
||||
let status = WtStat::Stopped(pid, sig);
|
||||
child.set_stat(status);
|
||||
} else if j.get_fg_mut().is_some_and(|fg| fg.pgid() == pgid) {
|
||||
j.fg_to_bg(WtStat::Stopped(pid, sig)).unwrap();
|
||||
}
|
||||
});
|
||||
take_term()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn child_continued(pid: Pid) -> ShResult<()> {
|
||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||
job.killpg(Signal::SIGCONT).ok();
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
|
||||
/*
|
||||
* Here we are going to get metadata on the exited process by querying the job table with the pid.
|
||||
* Then if the discovered job is the fg task, return terminal control to rsh
|
||||
* If it is not the fg task, print the display info for the job in the job table
|
||||
* We can reasonably assume that if it is not a foreground job, then it exists in the job table
|
||||
* If this assumption is incorrect, the code has gone wrong somewhere.
|
||||
*/
|
||||
if let Some((
|
||||
pgid,
|
||||
is_fg,
|
||||
is_finished
|
||||
)) = write_jobs(|j| {
|
||||
let fg_pgid = j.get_fg().map(|job| job.pgid());
|
||||
if let Some(job) = j.query_mut(JobID::Pid(pid)) {
|
||||
let pgid = job.pgid();
|
||||
let is_fg = fg_pgid.is_some_and(|fg| fg == pgid);
|
||||
job.update_by_id(JobID::Pid(pid), status).unwrap();
|
||||
let is_finished = !job.running();
|
||||
|
||||
if let Some(child) = job.children_mut().iter_mut().find(|chld| pid == chld.pid()) {
|
||||
child.set_stat(status);
|
||||
}
|
||||
|
||||
Some((pgid, is_fg, is_finished))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
|
||||
if is_finished {
|
||||
if is_fg {
|
||||
take_term()?;
|
||||
} else {
|
||||
println!();
|
||||
let job_order = read_jobs(|j| j.order().to_vec());
|
||||
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
|
||||
if let Some(job) = result {
|
||||
println!("{}",job.display(&job_order,JobCmdFlags::PIDS))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
194
src/state.rs
194
src/state.rs
@@ -1,18 +1,206 @@
|
||||
use std::{collections::HashMap, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{jobs::{wait_fg, attach_tty, take_term, Job, JobCmdFlags, JobID}, libsh::error::ShResult, prelude::*, procio::borrow_fd};
|
||||
|
||||
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
|
||||
|
||||
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
|
||||
|
||||
pub struct JobTab {
|
||||
|
||||
fg: Option<Job>,
|
||||
order: Vec<usize>,
|
||||
new_updates: Vec<usize>,
|
||||
jobs: Vec<Option<Job>>
|
||||
}
|
||||
|
||||
impl JobTab {
|
||||
pub fn new() -> Self {
|
||||
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.cmd().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.cmd().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>> {
|
||||
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);
|
||||
if let Some(mut job) = fg {
|
||||
job.set_stats(stat);
|
||||
self.insert_job(job, false)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn bg_to_fg(&mut self, id: JobID) -> ShResult<()> {
|
||||
let job = self.remove_job(id);
|
||||
if let Some(job) = job {
|
||||
wait_fg(job)?;
|
||||
}
|
||||
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(&mut 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>>>()
|
||||
};
|
||||
let mut jobs_to_remove = vec![];
|
||||
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())?;
|
||||
if job.get_stats().iter().all(|stat| matches!(stat,WtStat::Exited(_, _))) {
|
||||
jobs_to_remove.push(JobID::TableID(id));
|
||||
}
|
||||
}
|
||||
for id in jobs_to_remove {
|
||||
self.remove_job(id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user