use ariadne::Fmt; use crate::{ jobs::{JobCmdFlags, JobID, wait_bg}, libsh::error::{ShErr, ShErrKind, ShResult, next_color}, parse::{NdRule, Node, execute::prepare_argv, lex::Span}, prelude::*, procio::borrow_fd, state::{self, read_jobs, write_jobs}, }; pub enum JobBehavior { Foregound, Background, } pub fn continue_job(node: Node, behavior: JobBehavior) -> ShResult<()> { let blame = node.get_span().clone(); let cmd_tk = node.get_command(); let cmd_span = cmd_tk.unwrap().span.clone(); let cmd = match behavior { JobBehavior::Foregound => "fg", JobBehavior::Background => "bg", }; let NdRule::Command { assignments: _, argv, } = node.class else { unreachable!() }; let mut argv = prepare_argv(argv)?; if !argv.is_empty() { argv.remove(0); } let mut argv = argv.into_iter(); if read_jobs(|j| j.get_fg().is_some()) { return Err(ShErr::at( ShErrKind::InternalErr, cmd_span, format!("Somehow called '{}' with an existing foreground job", cmd), )); } let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) { id } else { return Err(ShErr::at(ShErrKind::ExecFail, cmd_span, "No jobs found")); }; let tabid = match argv.next() { Some((arg, blame)) => parse_job_id(&arg, blame)?, None => curr_job_id, }; let mut job = write_jobs(|j| { let id = JobID::TableID(tabid); let query_result = j.query(id.clone()); if query_result.is_some() { Ok(j.remove_job(id.clone()).unwrap()) } else { Err(ShErr::at( ShErrKind::ExecFail, blame.clone(), format!("Job id `{}' not found", tabid), )) } })?; job.killpg(Signal::SIGCONT)?; match behavior { JobBehavior::Foregound => { write_jobs(|j| j.new_fg(job))?; } JobBehavior::Background => { let job_order = read_jobs(|j| j.order().to_vec()); write( borrow_fd(1), job.display(&job_order, JobCmdFlags::PIDS).as_bytes(), )?; write_jobs(|j| j.insert_job(job, true))?; } } state::set_status(0); Ok(()) } fn parse_job_id(arg: &str, blame: Span) -> ShResult { if arg.starts_with('%') { let arg = arg.strip_prefix('%').unwrap(); if arg.chars().all(|ch| ch.is_ascii_digit()) { let num = arg.parse::().unwrap_or_default(); if num == 0 { Err(ShErr::at( ShErrKind::SyntaxErr, blame, format!("Invalid job id: {}", arg.fg(next_color())), )) } else { Ok(num.saturating_sub(1)) } } else { let result = write_jobs(|j| { let query_result = j.query(JobID::Command(arg.into())); query_result.map(|job| job.tabid().unwrap()) }); match result { Some(id) => Ok(id), None => Err(ShErr::at( ShErrKind::InternalErr, blame, "Found a job but no table id in parse_job_id()", )), } } } else if arg.chars().all(|ch| ch.is_ascii_digit()) { let result = write_jobs(|j| { let pgid_query_result = j.query(JobID::Pgid(Pid::from_raw(arg.parse::().unwrap()))); if let Some(job) = pgid_query_result { return Some(job.tabid().unwrap()); } if arg.parse::().unwrap() > 0 { let table_id_query_result = j.query(JobID::TableID(arg.parse::().unwrap())); return table_id_query_result.map(|job| job.tabid().unwrap()); } None }); match result { Some(id) => Ok(id), None => Err(ShErr::at( ShErrKind::InternalErr, blame, "Found a job but no table id in parse_job_id()", )), } } else { Err(ShErr::at( ShErrKind::SyntaxErr, blame, format!("Invalid arg: {}", arg.fg(next_color())), )) } } pub fn jobs(node: Node) -> ShResult<()> { let NdRule::Command { assignments: _, argv, } = node.class else { unreachable!() }; let mut argv = prepare_argv(argv)?; if !argv.is_empty() { argv.remove(0); } let mut flags = JobCmdFlags::empty(); for (arg, span) in argv { let mut chars = arg.chars().peekable(); if chars.peek().is_none_or(|ch| *ch != '-') { return Err(ShErr::at( ShErrKind::SyntaxErr, span, "Invalid flag in jobs call", )); } chars.next(); for ch in chars { let flag = match ch { 'l' => JobCmdFlags::LONG, 'p' => JobCmdFlags::PIDS, 'n' => JobCmdFlags::NEW_ONLY, 'r' => JobCmdFlags::RUNNING, 's' => JobCmdFlags::STOPPED, _ => { return Err(ShErr::at( ShErrKind::SyntaxErr, span, "Invalid flag in jobs call", )); } }; flags |= flag } } write_jobs(|j| j.print_jobs(flags))?; state::set_status(0); Ok(()) } pub fn wait(node: Node) -> ShResult<()> { let blame = node.get_span().clone(); let NdRule::Command { assignments: _, argv, } = node.class else { unreachable!() }; let mut argv = prepare_argv(argv)?; if !argv.is_empty() { argv.remove(0); } if read_jobs(|j| j.curr_job().is_none()) { state::set_status(0); return Err(ShErr::at(ShErrKind::ExecFail, blame, "wait: No jobs found")); } let argv = argv .into_iter() .map(|arg| { if arg.0.as_str().chars().all(|ch| ch.is_ascii_digit()) { Ok(JobID::Pid(Pid::from_raw(arg.0.parse::().unwrap()))) } else { Ok(JobID::TableID(parse_job_id(&arg.0, arg.1)?)) } }) .collect::>>()?; if argv.is_empty() { write_jobs(|j| j.wait_all_bg())?; } else { for arg in argv { wait_bg(arg)?; } } // don't set status here, the status of the waited-on job should be the status of the wait builtin Ok(()) } pub fn disown(node: Node) -> ShResult<()> { let blame = node.get_span().clone(); let NdRule::Command { assignments: _, argv, } = node.class else { unreachable!() }; let mut argv = prepare_argv(argv)?; if !argv.is_empty() { argv.remove(0); } let mut argv = argv.into_iter(); let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) { id } else { return Err(ShErr::at( ShErrKind::ExecFail, blame, "disown: No jobs to disown", )); }; let mut tabid = curr_job_id; let mut nohup = false; let mut disown_all = false; while let Some((arg, span)) = argv.next() { match arg.as_str() { "-h" => nohup = true, "-a" => disown_all = true, _ => { tabid = parse_job_id(&arg, span.clone())?; } } } if disown_all { write_jobs(|j| j.disown_all(nohup))?; } else { write_jobs(|j| j.disown(JobID::TableID(tabid), nohup))?; } state::set_status(0); Ok(()) }