Use strongly typed Pid for job control

This commit is contained in:
Mahmoud Al-Qudsi 2024-11-11 14:53:25 -06:00
parent 2cf4b12d41
commit fc47d9fa1d
12 changed files with 67 additions and 63 deletions

View file

@ -1,6 +1,6 @@
// Implementation of the bg builtin. // Implementation of the bg builtin.
use libc::pid_t; use crate::proc::Pid;
use super::prelude::*; use super::prelude::*;
@ -81,18 +81,19 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Optio
// If one argument is not a valid pid (i.e. integer >= 0), fail without backgrounding anything, // If one argument is not a valid pid (i.e. integer >= 0), fail without backgrounding anything,
// but still print errors for all of them. // but still print errors for all of them.
let mut retval: Option<i32> = STATUS_CMD_OK; let mut retval: Option<i32> = STATUS_CMD_OK;
let pids: Vec<pid_t> = args[opts.optind..] let pids: Vec<Pid> = args[opts.optind..]
.iter() .iter()
.map(|arg| { .filter_map(|arg| match fish_wcstoi(arg).map(Pid::new) {
fish_wcstoi(arg).unwrap_or_else(|_| { Ok(Some(pid)) => Some(pid),
_ => {
streams.err.append(wgettext_fmt!( streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid job specifier\n", "%ls: '%ls' is not a valid job specifier\n",
cmd, cmd,
arg arg
)); ));
retval = STATUS_INVALID_ARGS; retval = STATUS_INVALID_ARGS;
0 None
}) }
}) })
.collect(); .collect();

View file

@ -3,7 +3,7 @@
use super::shared::{builtin_print_help, STATUS_CMD_ERROR, STATUS_INVALID_ARGS}; use super::shared::{builtin_print_help, STATUS_CMD_ERROR, STATUS_INVALID_ARGS};
use crate::io::IoStreams; use crate::io::IoStreams;
use crate::parser::Parser; use crate::parser::Parser;
use crate::proc::{add_disowned_job, Job}; use crate::proc::{add_disowned_job, Job, Pid};
use crate::{ use crate::{
builtins::shared::{HelpOnlyCmdOpts, STATUS_CMD_OK}, builtins::shared::{HelpOnlyCmdOpts, STATUS_CMD_OK},
wchar::wstr, wchar::wstr,
@ -80,6 +80,7 @@ pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> O
retval = STATUS_CMD_ERROR; retval = STATUS_CMD_ERROR;
} }
} else { } else {
// TODO: This is supposed to be deduplicated or a hash set per comments below!
let mut jobs = vec![]; let mut jobs = vec![];
retval = STATUS_CMD_OK; retval = STATUS_CMD_OK;
@ -89,10 +90,7 @@ pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> O
// Non-existent jobs aren't an error, but information about them is useful. // 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. // Multiple PIDs may refer to the same job; include the job only once by using a set.
for arg in &args[1..] { for arg in &args[1..] {
match fish_wcstoi(arg) match fish_wcstoi(arg).ok().and_then(Pid::new) {
.ok()
.and_then(|pid| if pid < 0 { None } else { Some(pid) })
{
None => { None => {
streams.err.append(wgettext_fmt!( streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid job specifier\n", "%ls: '%ls' is not a valid job specifier\n",

View file

@ -1,6 +1,7 @@
//! Implementation of the fg builtin. //! Implementation of the fg builtin.
use crate::fds::make_fd_blocking; use crate::fds::make_fd_blocking;
use crate::proc::Pid;
use crate::reader::reader_write_title; use crate::reader::reader_write_title;
use crate::tokenizer::tok_command; use crate::tokenizer::tok_command;
use crate::wutil::perror; use crate::wutil::perror;
@ -51,8 +52,8 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Optio
// try to locate the job $argv[1], since we need to determine which error message to // try to locate the job $argv[1], since we need to determine which error message to
// emit (ambigous job specification vs malformed job id). // emit (ambigous job specification vs malformed job id).
let mut found_job = false; let mut found_job = false;
match fish_wcstoi(argv[optind]) { match fish_wcstoi(argv[optind]).map(Pid::new) {
Ok(pid) if pid > 0 => found_job = parser.job_get_from_pid(pid).is_some(), Ok(Some(pid)) => found_job = parser.job_get_from_pid(pid).is_some(),
_ => (), _ => (),
}; };
@ -82,14 +83,15 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Optio
builtin_print_error_trailer(parser, streams.err, cmd); builtin_print_error_trailer(parser, streams.err, cmd);
} }
Ok(pid) => { Ok(pid) => {
let pid = pid.abs(); let raw_pid = pid;
let j = parser.job_get_with_index_from_pid(pid); let pid = Pid::new(pid.abs());
let j = pid.and_then(|pid| parser.job_get_with_index_from_pid(pid));
if j.as_ref() if j.as_ref()
.map_or(true, |(_pos, j)| !j.is_constructed() || j.is_completed()) .map_or(true, |(_pos, j)| !j.is_constructed() || j.is_completed())
{ {
streams streams
.err .err
.append(wgettext_fmt!("%ls: No suitable job: %d\n", cmd, pid)); .append(wgettext_fmt!("%ls: No suitable job: %d\n", cmd, raw_pid));
job_pos = None; job_pos = None;
job = None job = None
} else { } else {
@ -102,7 +104,7 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Optio
"it is not under job control\n" "it is not under job control\n"
), ),
cmd, cmd,
pid, raw_pid,
j.command() j.command()
)); ));
None None

View file

@ -58,7 +58,7 @@ const LONG_OPTIONS: &[WOption] = &[
/// Return the internal_job_id for a pid, or None if none. /// Return the internal_job_id for a pid, or None if none.
/// This looks through both active and finished jobs. /// This looks through both active and finished jobs.
fn job_id_for_pid(pid: i32, parser: &Parser) -> Option<u64> { fn job_id_for_pid(pid: Pid, parser: &Parser) -> Option<u64> {
if let Some(job) = parser.job_get_from_pid(pid) { if let Some(job) = parser.job_get_from_pid(pid) {
Some(job.internal_job_id) Some(job.internal_job_id)
} else { } else {
@ -164,7 +164,10 @@ fn parse_cmd_opts(
e = EventDescription::ProcessExit { pid: Pid::new(pid) }; e = EventDescription::ProcessExit { pid: Pid::new(pid) };
} else { } else {
// TODO: rationalize why a default of 0 is sensible. // TODO: rationalize why a default of 0 is sensible.
let internal_job_id = job_id_for_pid(pid, parser).unwrap_or(0); let internal_job_id = match Pid::new(pid) {
Some(pid) => job_id_for_pid(pid, parser).unwrap_or(0),
None => 0,
};
e = EventDescription::JobExit { e = EventDescription::JobExit {
pid: Pid::new(pid), pid: Pid::new(pid),
internal_job_id, internal_job_id,
@ -372,13 +375,13 @@ pub fn function(
for ed in &opts.events { for ed in &opts.events {
match *ed { match *ed {
EventDescription::ProcessExit { pid: Some(pid) } => { EventDescription::ProcessExit { pid: Some(pid) } => {
let wh = parser.get_wait_handles().get_by_pid(pid.as_pid_t()); let wh = parser.get_wait_handles().get_by_pid(pid);
if let Some(status) = wh.and_then(|wh| wh.status()) { if let Some(status) = wh.and_then(|wh| wh.status()) {
event::fire(parser, event::Event::process_exit(pid, status)); event::fire(parser, event::Event::process_exit(pid, status));
} }
} }
EventDescription::JobExit { pid: Some(pid), .. } => { EventDescription::JobExit { pid: Some(pid), .. } => {
let wh = parser.get_wait_handles().get_by_pid(pid.as_pid_t()); let wh = parser.get_wait_handles().get_by_pid(pid);
if let Some(wh) = wh { if let Some(wh) = wh {
if wh.is_completed() { if wh.is_completed() {
event::fire(parser, event::Event::job_exit(pid, wh.internal_job_id)); event::fire(parser, event::Event::job_exit(pid, wh.internal_job_id));

View file

@ -8,7 +8,7 @@ use crate::common::{escape_string, timef, EscapeFlags, EscapeStringStyle};
use crate::io::IoStreams; use crate::io::IoStreams;
use crate::job_group::{JobId, MaybeJobId}; use crate::job_group::{JobId, MaybeJobId};
use crate::parser::Parser; use crate::parser::Parser;
use crate::proc::{clock_ticks_to_seconds, have_proc_stat, proc_get_jiffies, Job}; use crate::proc::{clock_ticks_to_seconds, have_proc_stat, proc_get_jiffies, Job, Pid};
use crate::wchar_ext::WExt; use crate::wchar_ext::WExt;
use crate::wgetopt::{wopt, ArgType, WGetopter, WOption}; use crate::wgetopt::{wopt, ArgType, WGetopter, WOption};
use crate::wutil::wgettext; use crate::wutil::wgettext;
@ -211,7 +211,7 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Opt
} }
} }
} else { } else {
match fish_wcstoi(arg).ok().filter(|&pid| pid >= 0) { match fish_wcstoi(arg).ok().and_then(Pid::new) {
None => { None => {
streams.err.append(wgettext_fmt!( streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid process id\n", "%ls: '%ls' is not a valid process id\n",

View file

@ -1,7 +1,5 @@
use libc::pid_t;
use super::prelude::*; use super::prelude::*;
use crate::proc::{proc_wait_any, Job}; use crate::proc::{proc_wait_any, Job, Pid};
use crate::signal::SigChecker; use crate::signal::SigChecker;
use crate::wait_handle::{WaitHandleRef, WaitHandleStore}; use crate::wait_handle::{WaitHandleRef, WaitHandleStore};
@ -25,7 +23,7 @@ fn iswnumeric(s: &wstr) -> bool {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
enum WaitHandleQuery<'a> { enum WaitHandleQuery<'a> {
Pid(pid_t), Pid(Pid),
ProcName(&'a wstr), ProcName(&'a wstr),
} }
@ -181,15 +179,15 @@ pub fn wait(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Opt
for item in &argv[optind..argc] { for item in &argv[optind..argc] {
if iswnumeric(item) { if iswnumeric(item) {
// argument is pid // argument is pid
let mpid: pid_t = fish_wcstoi(item).unwrap_or(-1); let mpid: i32 = fish_wcstoi(item).unwrap_or(-1);
if mpid <= 0 { let Some(mpid) = Pid::new(mpid) else {
streams.err.append(wgettext_fmt!( streams.err.append(wgettext_fmt!(
"%ls: '%ls' is not a valid process id\n", "%ls: '%ls' is not a valid process id\n",
cmd, cmd,
item, item,
)); ));
continue; continue;
} };
if !find_wait_handles(WaitHandleQuery::Pid(mpid), parser, &mut wait_handles) { if !find_wait_handles(WaitHandleQuery::Pid(mpid), parser, &mut wait_handles) {
streams.err.append(wgettext_fmt!( streams.err.append(wgettext_fmt!(
"%ls: Could not find a job with process id '%d'\n", "%ls: Could not find a job with process id '%d'\n",

View file

@ -386,7 +386,7 @@ pub fn get_desc(parser: &Parser, evt: &Event) -> WString {
} }
EventDescription::JobExit { pid, .. } => { EventDescription::JobExit { pid, .. } => {
if let Some(pid) = pid { if let Some(pid) = pid {
if let Some(job) = parser.job_get_from_pid(pid.as_pid_t()) { if let Some(job) = parser.job_get_from_pid(*pid) {
format!("exit handler for job {}, '{}'", job.job_id(), job.command()) format!("exit handler for job {}, '{}'", job.job_id(), job.command())
} else { } else {
format!("exit handler for job with pid {pid}") format!("exit handler for job with pid {pid}")

View file

@ -698,7 +698,7 @@ fn fork_child_for_process(
let narrow_cmd = wcs2zstring(job.command()); let narrow_cmd = wcs2zstring(job.command());
let narrow_argv0 = wcs2zstring(p.argv0().unwrap_or_default()); let narrow_argv0 = wcs2zstring(p.argv0().unwrap_or_default());
let mut pid = execute_fork(); let pid = execute_fork();
if pid < 0 { if pid < 0 {
return Err(()); return Err(());
} }
@ -706,12 +706,12 @@ fn fork_child_for_process(
// Record the pgroup if this is the leader. // Record the pgroup if this is the leader.
// Both parent and child attempt to send the process to its new group, to resolve the race. // Both parent and child attempt to send the process to its new group, to resolve the race.
p.set_pid(if is_parent { let pid = if is_parent {
pid pid
} else { } else {
pid = unsafe { libc::getpid() }; unsafe { libc::getpid() }
pid };
}); p.set_pid(pid);
if p.leads_pgrp { if p.leads_pgrp {
job.group().set_pgid(pid); job.group().set_pgid(pid);
} }
@ -1328,9 +1328,7 @@ fn exec_process_in_job(
exec_external_command(parser, j, p, &process_net_io_chain)?; exec_external_command(parser, j, p, &process_net_io_chain)?;
// It's possible (though unlikely) that this is a background process which recycled a // It's possible (though unlikely) that this is a background process which recycled a
// pid from another, previous background process. Forget any such old process. // pid from another, previous background process. Forget any such old process.
parser parser.mut_wait_handles().remove_by_pid(p.pid().unwrap());
.mut_wait_handles()
.remove_by_pid(p.pid().unwrap().as_pid_t());
Ok(()) Ok(())
} }
ProcessType::exec => { ProcessType::exec => {

View file

@ -23,7 +23,7 @@ use crate::parse_constants::{
}; };
use crate::parse_execution::{EndExecutionReason, ExecutionContext}; use crate::parse_execution::{EndExecutionReason, ExecutionContext};
use crate::parse_tree::{parse_source, LineCounter, ParsedSourceRef}; use crate::parse_tree::{parse_source, LineCounter, ParsedSourceRef};
use crate::proc::{job_reap, JobGroupRef, JobList, JobRef, ProcStatus}; use crate::proc::{job_reap, JobGroupRef, JobList, JobRef, Pid, ProcStatus};
use crate::signal::{signal_check_cancel, signal_clear_cancel, Signal}; use crate::signal::{signal_check_cancel, signal_clear_cancel, Signal};
use crate::threads::assert_is_main_thread; use crate::threads::assert_is_main_thread;
use crate::util::get_time; use crate::util::get_time;
@ -942,15 +942,15 @@ impl Parser {
} }
/// Returns the job with the given pid. /// Returns the job with the given pid.
pub fn job_get_from_pid(&self, pid: libc::pid_t) -> Option<JobRef> { pub fn job_get_from_pid(&self, pid: Pid) -> Option<JobRef> {
self.job_get_with_index_from_pid(pid).map(|t| t.1) self.job_get_with_index_from_pid(pid).map(|t| t.1)
} }
/// Returns the job and job index with the given pid. /// Returns the job and job index with the given pid.
pub fn job_get_with_index_from_pid(&self, pid: libc::pid_t) -> Option<(usize, JobRef)> { pub fn job_get_with_index_from_pid(&self, pid: Pid) -> Option<(usize, JobRef)> {
for (i, job) in self.jobs().iter().enumerate() { for (i, job) in self.jobs().iter().enumerate() {
for p in job.external_procs() { for p in job.external_procs() {
if p.pid.load().unwrap().get() == pid { if p.pid.load().unwrap() == pid {
return Some((i, job.clone())); return Some((i, job.clone()));
} }
} }

View file

@ -512,7 +512,7 @@ impl Drop for TtyTransfer {
/// A type-safe equivalent to [`libc::pid_t`]. /// A type-safe equivalent to [`libc::pid_t`].
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Pid(NonZeroU32); pub struct Pid(NonZeroU32);
impl Pid { impl Pid {
@ -780,7 +780,7 @@ impl Process {
} else { } else {
if self.wait_handle.borrow().is_none() { if self.wait_handle.borrow().is_none() {
self.wait_handle.replace(Some(WaitHandle::new( self.wait_handle.replace(Some(WaitHandle::new(
self.pid().unwrap().get(), self.pid().unwrap(),
jid, jid,
wbasename(&self.actual_cmd.clone()).to_owned(), wbasename(&self.actual_cmd.clone()).to_owned(),
))); )));

View file

@ -1,5 +1,5 @@
use crate::proc::Pid;
use crate::wchar::prelude::*; use crate::wchar::prelude::*;
use libc::pid_t;
use std::cell::Cell; use std::cell::Cell;
use std::rc::Rc; use std::rc::Rc;
@ -11,7 +11,7 @@ pub type InternalJobId = u64;
/// This may outlive the job. /// This may outlive the job.
pub struct WaitHandle { pub struct WaitHandle {
/// The pid of this process. /// The pid of this process.
pub pid: pid_t, pub pid: Pid,
/// The internal job id of the job which contained this process. /// The internal job id of the job which contained this process.
pub internal_job_id: InternalJobId, pub internal_job_id: InternalJobId,
@ -42,7 +42,7 @@ impl WaitHandle {
impl WaitHandle { impl WaitHandle {
/// Construct from a pid, job id, and base name. /// Construct from a pid, job id, and base name.
pub fn new(pid: pid_t, internal_job_id: InternalJobId, base_name: WString) -> WaitHandleRef { pub fn new(pid: Pid, internal_job_id: InternalJobId, base_name: WString) -> WaitHandleRef {
Rc::new(WaitHandle { Rc::new(WaitHandle {
pid, pid,
internal_job_id, internal_job_id,
@ -60,7 +60,7 @@ const WAIT_HANDLE_STORE_DEFAULT_LIMIT: usize = 1024;
/// Note this class is not safe for concurrent access. /// Note this class is not safe for concurrent access.
pub struct WaitHandleStore { pub struct WaitHandleStore {
// Map from pid to wait handles. // Map from pid to wait handles.
cache: lru::LruCache<pid_t, WaitHandleRef>, cache: lru::LruCache<Pid, WaitHandleRef>,
} }
impl WaitHandleStore { impl WaitHandleStore {
@ -84,7 +84,7 @@ impl WaitHandleStore {
/// Return the wait handle for a pid, or None if there is none. /// Return the wait handle for a pid, or None if there is none.
/// This is a fast lookup. /// This is a fast lookup.
pub fn get_by_pid(&self, pid: pid_t) -> Option<WaitHandleRef> { pub fn get_by_pid(&self, pid: Pid) -> Option<WaitHandleRef> {
self.cache.peek(&pid).cloned() self.cache.peek(&pid).cloned()
} }
@ -99,7 +99,7 @@ impl WaitHandleStore {
} }
/// Remove the wait handle for a pid, if present in this store. /// Remove the wait handle for a pid, if present in this store.
pub fn remove_by_pid(&mut self, pid: pid_t) { pub fn remove_by_pid(&mut self, pid: Pid) {
self.cache.pop(&pid); self.cache.pop(&pid);
} }
@ -125,25 +125,29 @@ fn test_wait_handles() {
let mut whs = WaitHandleStore::new_with_capacity(limit); let mut whs = WaitHandleStore::new_with_capacity(limit);
assert_eq!(whs.size(), 0); assert_eq!(whs.size(), 0);
assert!(whs.get_by_pid(5).is_none()); fn p(pid: i32) -> Pid {
Pid::new(pid).unwrap()
}
assert!(whs.get_by_pid(p(5)).is_none());
// Duplicate pids drop oldest. // Duplicate pids drop oldest.
whs.add(WaitHandle::new(5, 0, L!("first").to_owned())); whs.add(WaitHandle::new(p(5), 0, L!("first").to_owned()));
whs.add(WaitHandle::new(5, 0, L!("second").to_owned())); whs.add(WaitHandle::new(p(5), 0, L!("second").to_owned()));
assert_eq!(whs.size(), 1); assert_eq!(whs.size(), 1);
assert_eq!(whs.get_by_pid(5).unwrap().base_name, "second"); assert_eq!(whs.get_by_pid(p(5)).unwrap().base_name, "second");
whs.remove_by_pid(123); whs.remove_by_pid(p(123));
assert_eq!(whs.size(), 1); assert_eq!(whs.size(), 1);
whs.remove_by_pid(5); whs.remove_by_pid(p(5));
assert_eq!(whs.size(), 0); assert_eq!(whs.size(), 0);
// Test evicting oldest. // Test evicting oldest.
whs.add(WaitHandle::new(1, 0, L!("1").to_owned())); whs.add(WaitHandle::new(p(1), 0, L!("1").to_owned()));
whs.add(WaitHandle::new(2, 0, L!("2").to_owned())); whs.add(WaitHandle::new(p(2), 0, L!("2").to_owned()));
whs.add(WaitHandle::new(3, 0, L!("3").to_owned())); whs.add(WaitHandle::new(p(3), 0, L!("3").to_owned()));
whs.add(WaitHandle::new(4, 0, L!("4").to_owned())); whs.add(WaitHandle::new(p(4), 0, L!("4").to_owned()));
whs.add(WaitHandle::new(5, 0, L!("5").to_owned())); whs.add(WaitHandle::new(p(5), 0, L!("5").to_owned()));
assert_eq!(whs.size(), 4); assert_eq!(whs.size(), 4);
let entries = whs.get_list(); let entries = whs.get_list();

View file

@ -503,7 +503,7 @@ type --query cp
echo $status echo $status
#CHECK: 0 #CHECK: 0
jobs --query 0 jobs --query 1
echo $status echo $status
#CHECK: 1 #CHECK: 1