fish-shell/src/builtins/disown.rs
2024-01-13 03:58:33 +01:00

129 lines
4.2 KiB
Rust

// Implementation of the disown builtin.
use super::shared::{builtin_print_help, STATUS_CMD_ERROR, STATUS_INVALID_ARGS};
use crate::io::IoStreams;
use crate::parser::Parser;
use crate::proc::{add_disowned_job, Job};
use crate::{
builtins::shared::{HelpOnlyCmdOpts, STATUS_CMD_OK},
wchar::wstr,
wutil::{fish_wcstoi, wgettext_fmt},
};
use libc::c_int;
use libc::{self, SIGCONT};
/// Helper for builtin_disown.
fn disown_job(cmd: &wstr, streams: &mut IoStreams, j: &Job) {
// Nothing to do if already disowned.
if j.flags().disown_requested {
return;
}
// Stopped disowned jobs must be manually signaled; explain how to do so.
let pgid = j.get_pgid();
if j.is_stopped() {
if let Some(pgid) = pgid {
unsafe {
libc::killpg(pgid, SIGCONT);
}
}
streams.err.append(wgettext_fmt!(
"%ls: job %d ('%ls') was stopped and has been signalled to continue.\n",
cmd,
j.job_id(),
j.command()
));
}
// We cannot directly remove the job from the jobs() list as `disown` might be called
// within the context of a subjob which will cause the parent job to crash in exec_job().
// Instead, we set a flag and the parser removes the job from the jobs list later.
j.mut_flags().disown_requested = true;
add_disowned_job(j);
}
/// Builtin for removing jobs from the job list.
pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Option<c_int> {
let opts = match HelpOnlyCmdOpts::parse(args, parser, streams) {
Ok(opts) => opts,
Err(err @ Some(_)) if err != STATUS_CMD_OK => return err,
Err(err) => panic!("Illogical exit code from parse_options(): {err:?}"),
};
let cmd = args[0];
if opts.print_help {
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
let mut retval;
if opts.optind == args.len() {
// Select last constructed job (ie first job in the job queue) that is possible to disown.
// Stopped jobs can be disowned (they will be continued).
// Foreground jobs can be disowned.
// Even jobs that aren't under job control can be disowned!
let mut job = None;
for j in &parser.jobs()[..] {
if j.is_constructed() && !j.is_completed() {
job = Some(j.clone());
break;
}
}
if let Some(job) = job {
disown_job(cmd, streams, &job);
retval = STATUS_CMD_OK;
} else {
streams
.err
.append(wgettext_fmt!("%ls: There are no suitable jobs\n", cmd));
retval = STATUS_CMD_ERROR;
}
} else {
let mut jobs = vec![];
retval = STATUS_CMD_OK;
// If one argument is not a valid pid (i.e. integer >= 0), fail without disowning anything,
// but still print errors for all of them.
// Non-existent jobs aren't an error, but information about them is useful.
// Multiple PIDs may refer to the same job; include the job only once by using a set.
for arg in &args[1..] {
match fish_wcstoi(arg)
.ok()
.and_then(|pid| if pid < 0 { None } else { Some(pid) })
{
None => {
streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid job specifier\n",
cmd,
arg
));
retval = STATUS_INVALID_ARGS;
}
Some(pid) => {
if let Some(j) = parser.job_get_from_pid(pid) {
jobs.push(j);
} else {
streams.err.append(wgettext_fmt!(
"%ls: Could not find job '%d'\n",
cmd,
pid
));
}
}
}
}
if retval != STATUS_CMD_OK {
return retval;
}
// Disown all target jobs.
for j in jobs {
disown_job(cmd, streams, &j);
}
}
retval
}