use crate::global_safety::RelaxedAtomicBool; use crate::proc::JobGroupRef; use crate::signal::Signal; use crate::wchar::prelude::*; use std::cell::RefCell; use std::num::NonZeroU32; use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::{Arc, Mutex}; /// A job id, corresponding to what is printed by `jobs`. 1 is the first valid job id. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] #[repr(transparent)] pub struct JobId(NonZeroU32); #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct MaybeJobId(pub Option); impl std::ops::Deref for MaybeJobId { type Target = Option; fn deref(&self) -> &Self::Target { &self.0 } } impl MaybeJobId { pub fn as_num(&self) -> i64 { self.0.map(|j| i64::from(u32::from(j.0))).unwrap_or(-1) } } impl std::fmt::Display for MaybeJobId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.as_num().fmt(f) } } impl ToWString for MaybeJobId { fn to_wstring(&self) -> WString { self.as_num().to_wstring() } } impl<'a> printf_compat::args::ToArg<'a> for MaybeJobId { fn to_arg(self) -> printf_compat::args::Arg<'a> { self.as_num().to_arg() } } /// `JobGroup` is conceptually similar to the idea of a process group. It represents data which /// is shared among all of the "subjobs" that may be spawned by a single job. /// For example, two fish functions in a pipeline may themselves spawn multiple jobs, but all will /// share the same job group. /// There is also a notion of a "internal" job group. Internal groups are used when executing a /// foreground function or block with no pipeline. These are not jobs as the user understands them - /// they do not consume a job id, they do not show up in job lists, and they do not have a pgid /// because they contain no external procs. Note that `JobGroup` is intended to eventually be /// shared between threads, and so must be thread safe. #[derive(Debug)] pub struct JobGroup { /// If set, the saved terminal modes of this job. This needs to be saved so that we can restore /// the terminal to the same state when resuming a stopped job. pub tmodes: RefCell>, /// Whether job control is enabled in this `JobGroup` or not. /// /// If this is set, then the first process in the root job must be external, as it will become /// the process group leader. pub job_control: bool, /// Whether we should `tcsetpgrp()` the job when it runs in the foreground. Should be checked /// via [`Self::wants_terminal()`] only. wants_term: bool, /// Whether we are in the foreground, meaning the user is waiting for this job to complete. pub is_foreground: RelaxedAtomicBool, /// The pgid leading our group. This is only ever set if [`job_control`](Self::JobControl) is /// true. We ensure the value (when set) is always non-negative. pgid: RefCell>, /// The original command which produced this job tree. pub command: WString, /// Our job id, if any. `None` here should evaluate to `-1` for ffi purposes. /// "Simple block" groups like function calls do not have a job id. pub job_id: MaybeJobId, /// The signal causing the group to cancel or `0` if none. /// Not using an `Option` to be able to atomically load/store to this field. signal: AtomicI32, } // safety: all fields without interior mutabillity are only written to once unsafe impl Send for JobGroup {} unsafe impl Sync for JobGroup {} impl JobGroup { /// Whether this job wants job control. pub fn wants_job_control(&self) -> bool { self.job_control } /// If this job should own the terminal when it runs. True only if both [`Self::wants_term]` and /// [`Self::is_foreground`] are true. pub fn wants_terminal(&self) -> bool { self.wants_term && self.is_foreground() } /// Whether we are the currently the foreground group. Should never be true for more than one /// `JobGroup` at any given moment. pub fn is_foreground(&self) -> bool { self.is_foreground.load() } /// Mark whether we are in the foreground. pub fn set_is_foreground(&self, in_foreground: bool) { self.is_foreground.store(in_foreground); } /// Returns whether we have valid job id. "Simple block" groups like function calls do not. pub fn has_job_id(&self) -> bool { self.job_id.is_some() } /// Gets the cancellation signal, if any. pub fn get_cancel_signal(&self) -> Option { match self.signal.load(Ordering::Relaxed) { 0 => None, s => Some(Signal::new(s)), } } /// Mark that a process in this group got a signal and should cancel. pub fn cancel_with_signal(&self, signal: Signal) { // We only assign the signal if one hasn't yet been assigned. This means the first signal to // register wins over any that come later. self.signal .compare_exchange(0, signal.code(), Ordering::Relaxed, Ordering::Relaxed) .ok(); } /// Set the pgid for this job group, latching it to this value. This should only be called if /// job control is active for this group. The pgid should not already have been set, and should /// be different from fish's pgid. Of course this does not keep the pgid alive by itself. /// /// Note we need not be concerned about thread safety. job_groups are intended to be shared /// across threads, but any pgid should always have been set beforehand, since it's set /// immediately after the first process launches. /// /// As such, this method takes `&mut self` rather than `&self` to enforce that this operation is /// only available during initial construction/initialization. pub fn set_pgid(&self, pgid: libc::pid_t) { assert!( self.wants_job_control(), "Should not set a pgid for a group that doesn't want job control!" ); assert!(pgid >= 0, "Invalid pgid!"); assert!(self.pgid.borrow().is_none(), "JobGroup::pgid already set!"); self.pgid.replace(Some(pgid)); } /// Returns the value of [`JobGroup::pgid`]. This is never fish's own pgid! pub fn get_pgid(&self) -> Option { *self.pgid.borrow() } } /// Basic thread-safe sorted vector of job ids currently in use. /// /// In the C++ codebase, this is deliberately leaked to avoid destructor ordering issues - see /// #6539. Rust automatically "leaks" all `static` variables (does not call their `Drop` impls) /// because of the inherent difficulty in doing that correctly (i.e. what we ran into). static CONSUMED_JOB_IDS: Mutex> = Mutex::new(Vec::new()); impl JobId { pub const NONE: MaybeJobId = MaybeJobId(None); pub fn new(value: NonZeroU32) -> Self { JobId(value) } /// Return a `JobId` that is greater than all extant job ids stored in [`CONSUMED_JOB_IDS`]. /// The `JobId` should be freed with [`JobId::release()`] when it is no longer in use. fn acquire() -> JobId { let mut consumed_job_ids = CONSUMED_JOB_IDS.lock().expect("Poisoned mutex!"); // The new job id should be greater than the largest currently used id (#6053). The job ids // in CONSUMED_JOB_IDS are sorted in ascending order, so we just have to check the last. let job_id = consumed_job_ids .last() .map(JobId::next) .unwrap_or(JobId(1.try_into().unwrap())); consumed_job_ids.push(job_id); job_id } /// Remove the provided `JobId` from [`CONSUMED_JOB_IDS`]. fn release(id: JobId) { let mut consumed_job_ids = CONSUMED_JOB_IDS.lock().expect("Poisoned mutex!"); let pos = consumed_job_ids .binary_search(&id) .expect("Job id was not in use!"); consumed_job_ids.remove(pos); } /// Increments the internal id and returns it wrapped in a new `JobId`. fn next(&self) -> JobId { JobId(self.0.checked_add(1).expect("Job id overflow!")) } } impl JobGroup { pub fn new(command: WString, id: MaybeJobId, job_control: bool, wants_term: bool) -> Self { // We *can* have a job id without job control, but not the reverse. if job_control { assert!(id.is_some(), "Cannot have job control without a job id!"); } if wants_term { assert!(job_control, "Cannot take terminal without job control!"); } Self { job_id: id, job_control, wants_term, command, tmodes: RefCell::default(), signal: 0.into(), is_foreground: RelaxedAtomicBool::new(false), pgid: RefCell::default(), } } /// Return a new `JobGroup` with the provided `command`. The `JobGroup` is only assigned a /// `JobId` if `wants_job_id` is true and is created with job control disabled and /// [`JobGroup::wants_term`] set to false. pub fn create(command: WString, wants_job_id: bool) -> JobGroupRef { Arc::new(JobGroup::new( command, if wants_job_id { MaybeJobId(Some(JobId::acquire())) } else { JobId::NONE }, false, /* job_control */ false, /* wants_term */ )) } /// Return a new `JobGroup` with the provided `command` with job control enabled. A [`JobId`] is /// automatically acquired and assigned. If `wants_term` is true then [`JobGroup::wants_term`] /// is also set to `true` accordingly. pub fn create_with_job_control(command: WString, wants_term: bool) -> JobGroupRef { Arc::new(JobGroup::new( command, MaybeJobId(Some(JobId::acquire())), true, /* job_control */ wants_term, )) } } impl Drop for JobGroup { fn drop(&mut self) { if let Some(job_id) = *self.job_id { JobId::release(job_id); } } }