// Functions for executing the jobs builtin. use super::shared::{ builtin_missing_argument, builtin_print_help, builtin_unknown_option, STATUS_CMD_ERROR, STATUS_INVALID_ARGS, }; use crate::common::{escape_string, timef, EscapeFlags, EscapeStringStyle}; use crate::io::IoStreams; use crate::job_group::{JobId, MaybeJobId}; use crate::parser::Parser; use crate::proc::{clock_ticks_to_seconds, have_proc_stat, proc_get_jiffies, Job, INVALID_PID}; use crate::wchar_ext::WExt; use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t}; use crate::wutil::wgettext; use crate::{ builtins::shared::STATUS_CMD_OK, wchar::{wstr, WString, L}, wutil::{fish_wcstoi, wgettext_fmt}, }; use libc::c_int; use printf_compat::sprintf; use std::num::NonZeroU32; use std::sync::atomic::Ordering; /// Print modes for the jobs builtin. #[derive(Clone, Copy, Eq, PartialEq)] enum JobsPrintMode { Default, // print lots of general info PrintPid, // print pid of each process in job PrintCommand, // print command name of each process in job PrintGroup, // print group id of job PrintNothing, // print nothing (exit status only) } /// Calculates the cpu usage (as a fraction of 1) of the specified job. /// This may exceed 1 if there are multiple CPUs! fn cpu_use(j: &Job) -> f64 { let mut u = 0.0; for p in j.processes() { let now = timef(); let jiffies = proc_get_jiffies(p.pid.load(Ordering::Relaxed)); let last_jiffies = p.last_times.get().jiffies; let since = now - last_jiffies as f64; if since > 0.0 && jiffies > last_jiffies { u += clock_ticks_to_seconds(jiffies - last_jiffies) / since; } } u } /// Print information about the specified job. fn builtin_jobs_print(j: &Job, mode: JobsPrintMode, header: bool, streams: &mut IoStreams) { let mut pgid = INVALID_PID; { if let Some(job_pgid) = j.get_pgid() { pgid = job_pgid; } } let mut out = WString::new(); match mode { JobsPrintMode::PrintNothing => (), JobsPrintMode::Default => { if header { // Print table header before first job. out += wgettext!("Job\tGroup\t"); if have_proc_stat() { out += wgettext!("CPU\t"); } out += wgettext!("State\tCommand\n"); } sprintf!(=> &mut out, "%d\t%d\t", j.job_id(), pgid); if have_proc_stat() { sprintf!(=> &mut out, "%.0f%%\t", 100.0 * cpu_use(j)); } out += if j.is_stopped() { wgettext!("stopped") } else { wgettext!("running") }; out += "\t"; let cmd = escape_string( j.command(), EscapeStringStyle::Script(EscapeFlags::NO_PRINTABLES), ); out += &cmd[..]; out += "\n"; streams.out.append(out); } JobsPrintMode::PrintGroup => { if header { // Print table header before first job. out += &wgettext!("Group\n")[..]; } out += &sprintf!("%d\n", pgid)[..]; streams.out.append(out); } JobsPrintMode::PrintPid => { if header { // Print table header before first job. out += &wgettext!("Process\n")[..]; } for p in j.processes() { out += &sprintf!("%d\n", p.pid.load(Ordering::Relaxed))[..]; } streams.out.append(out); } JobsPrintMode::PrintCommand => { if header { // Print table header before first job. out += &wgettext!("Command\n")[..]; } for p in j.processes() { out += &sprintf!("%ls\n", p.argv0().unwrap())[..]; } streams.out.append(out); } }; } const SHORT_OPTIONS: &wstr = L!(":cghlpq"); const LONG_OPTIONS: &[woption] = &[ wopt(L!("command"), woption_argument_t::no_argument, 'c'), wopt(L!("group"), woption_argument_t::no_argument, 'g'), wopt(L!("help"), woption_argument_t::no_argument, 'h'), wopt(L!("last"), woption_argument_t::no_argument, 'l'), wopt(L!("pid"), woption_argument_t::no_argument, 'p'), wopt(L!("quiet"), woption_argument_t::no_argument, 'q'), wopt(L!("query"), woption_argument_t::no_argument, 'q'), ]; /// The jobs builtin. Used for printing running jobs. Defined in builtin_jobs.c. pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Option { let cmd = argv[0]; let argc = argv.len(); let mut found = false; let mut mode = JobsPrintMode::Default; let mut print_last = false; let mut w = wgetopter_t::new(SHORT_OPTIONS, LONG_OPTIONS, argv); while let Some(c) = w.wgetopt_long() { match c { 'p' => { mode = JobsPrintMode::PrintPid; } 'q' => { mode = JobsPrintMode::PrintNothing; } 'c' => { mode = JobsPrintMode::PrintCommand; } 'g' => { mode = JobsPrintMode::PrintGroup; } 'l' => { print_last = true; } 'h' => { builtin_print_help(parser, streams, cmd); return STATUS_CMD_OK; } ':' => { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1], true); return STATUS_INVALID_ARGS; } '?' => { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], true); return STATUS_INVALID_ARGS; } _ => panic!("unexpected retval from wgetopt_long"), } } if print_last { // Ignore unconstructed jobs, i.e. ourself. for j in &parser.jobs()[..] { if j.is_visible() { builtin_jobs_print(j, mode, !streams.out_is_redirected, streams); return STATUS_CMD_OK; } } return STATUS_CMD_ERROR; } if w.woptind < argc { for arg in &w.argv[w.woptind..] { let j; if arg.char_at(0) == '%' { match fish_wcstoi(&arg[1..]).ok().filter(|&job_id| job_id >= 0) { None => { streams.err.append(wgettext_fmt!( "%ls: '%ls' is not a valid job id\n", cmd, arg )); return STATUS_INVALID_ARGS; } Some(job_id) => { let job_id = if job_id == 0 { JobId::NONE } else { let job_id = u32::try_from(job_id).unwrap(); let job_id = NonZeroU32::try_from(job_id).unwrap(); MaybeJobId(Some(JobId::new(job_id))) }; j = parser.job_with_id(job_id); } } } else { match fish_wcstoi(arg).ok().filter(|&pid| pid >= 0) { None => { streams.err.append(wgettext_fmt!( "%ls: '%ls' is not a valid process id\n", cmd, arg )); return STATUS_INVALID_ARGS; } Some(job_id) => { j = parser.job_get_from_pid(job_id); } } } if let Some(j) = j.filter(|j| !j.is_completed() && j.is_constructed()) { builtin_jobs_print(&j, mode, false, streams); found = true; } else { if mode != JobsPrintMode::PrintNothing { streams .err .append(wgettext_fmt!("%ls: No suitable job: %ls\n", cmd, arg)); } return STATUS_CMD_ERROR; } } } else { for j in &parser.jobs()[..] { // Ignore unconstructed jobs, i.e. ourself. if j.is_visible() { builtin_jobs_print(j, mode, !found && !streams.out_is_redirected, streams); found = true; } } } if !found { // Do not babble if not interactive. if !streams.out_is_redirected && mode != JobsPrintMode::PrintNothing { streams .out .append(wgettext_fmt!("%ls: There are no jobs\n", argv[0])); } return STATUS_CMD_ERROR; } STATUS_CMD_OK }