Use Option<Pid> instead of Option<pid_t>

Statically assert that the interior value is both positive and non-zero.
This commit is contained in:
Mahmoud Al-Qudsi 2024-11-11 13:31:24 -06:00
parent 3307672998
commit 95ac51101e
6 changed files with 80 additions and 50 deletions

View file

@ -24,7 +24,7 @@ fn disown_job(cmd: &wstr, streams: &mut IoStreams, j: &Job) {
if j.is_stopped() { if j.is_stopped() {
if let Some(pgid) = pgid { if let Some(pgid) = pgid {
unsafe { unsafe {
libc::killpg(pgid, SIGCONT); libc::killpg(pgid.as_pid_t(), SIGCONT);
} }
} }
streams.err.append(wgettext_fmt!( streams.err.append(wgettext_fmt!(

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 pid = execute_fork(); let mut pid = execute_fork();
if pid < 0 { if pid < 0 {
return Err(()); return Err(());
} }
@ -709,7 +709,8 @@ fn fork_child_for_process(
p.set_pid(if is_parent { p.set_pid(if is_parent {
pid pid
} else { } else {
unsafe { libc::getpid() } pid = unsafe { libc::getpid() };
pid
}); });
if p.leads_pgrp { if p.leads_pgrp {
job.group().set_pgid(pid); job.group().set_pgid(pid);
@ -895,7 +896,7 @@ fn exec_external_command(
// In glibc, posix_spawn uses fork() and the pgid group is set on the child side; // In glibc, posix_spawn uses fork() and the pgid group is set on the child side;
// therefore the parent may not have seen it be set yet. // therefore the parent may not have seen it be set yet.
// Ensure it gets set. See #4715, also https://github.com/Microsoft/WSL/issues/2997. // Ensure it gets set. See #4715, also https://github.com/Microsoft/WSL/issues/2997.
execute_setpgid(pid.as_pid_t(), pid.as_pid_t(), true /* is parent */); execute_setpgid(pid, pid, true /* is parent */);
} }
return Ok(()); return Ok(());
} }
@ -1327,7 +1328,9 @@ 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.mut_wait_handles().remove_by_pid(p.pid().unwrap()); parser
.mut_wait_handles()
.remove_by_pid(p.pid().unwrap().as_pid_t());
Ok(()) Ok(())
} }
ProcessType::exec => { ProcessType::exec => {

View file

@ -3,6 +3,7 @@
// That means no locking, no allocating, no freeing memory, etc! // That means no locking, no allocating, no freeing memory, etc!
use super::flog_safe::FLOG_SAFE; use super::flog_safe::FLOG_SAFE;
use crate::nix::getpid; use crate::nix::getpid;
use crate::proc::Pid;
use crate::redirection::Dup2List; use crate::redirection::Dup2List;
use crate::signal::signal_reset_handlers; use crate::signal::signal_reset_handlers;
use crate::{common::exit_without_destructors, wutil::fstat}; use crate::{common::exit_without_destructors, wutil::fstat};
@ -52,20 +53,20 @@ fn strlen_safe(s: *const libc::c_char) -> usize {
pub(crate) fn report_setpgid_error( pub(crate) fn report_setpgid_error(
err: i32, err: i32,
is_parent: bool, is_parent: bool,
pid: pid_t, pid: Pid,
desired_pgid: pid_t, desired_pgid: Pid,
job_id: i64, job_id: i64,
command: &CStr, command: &CStr,
argv0: &CStr, argv0: &CStr,
) { ) {
let cur_group = unsafe { libc::getpgid(pid) }; let cur_group = unsafe { libc::getpgid(pid.as_pid_t()) };
FLOG_SAFE!( FLOG_SAFE!(
warning, warning,
"Could not send ", "Could not send ",
if is_parent { "child" } else { "self" }, if is_parent { "child" } else { "self" },
" ", " ",
pid, pid.get(),
", '", ", '",
argv0, argv0,
"' in job ", "' in job ",
@ -75,35 +76,35 @@ pub(crate) fn report_setpgid_error(
"' from group ", "' from group ",
cur_group, cur_group,
" to group ", " to group ",
desired_pgid, desired_pgid.get(),
); );
match err { match err {
libc::EACCES => FLOG_SAFE!(error, "setpgid: Process ", pid, " has already exec'd"), libc::EACCES => FLOG_SAFE!(error, "setpgid: Process ", pid.get(), " has already exec'd"),
libc::EINVAL => FLOG_SAFE!(error, "setpgid: pgid ", cur_group, " unsupported"), libc::EINVAL => FLOG_SAFE!(error, "setpgid: pgid ", cur_group, " unsupported"),
libc::EPERM => { libc::EPERM => {
FLOG_SAFE!( FLOG_SAFE!(
error, error,
"setpgid: Process ", "setpgid: Process ",
pid, pid.get(),
" is a session leader or pgid ", " is a session leader or pgid ",
cur_group, cur_group,
" does not match" " does not match"
); );
} }
libc::ESRCH => FLOG_SAFE!(error, "setpgid: Process ID ", pid, " does not match"), libc::ESRCH => FLOG_SAFE!(error, "setpgid: Process ID ", pid.get(), " does not match"),
_ => FLOG_SAFE!(error, "setpgid: Unknown error number ", err), _ => FLOG_SAFE!(error, "setpgid: Unknown error number ", err),
} }
} }
/// Execute setpgid, moving pid into the given pgroup. /// Execute setpgid, moving pid into the given pgroup.
/// Return the result of setpgid. /// Return the result of setpgid.
pub fn execute_setpgid(pid: pid_t, pgroup: pid_t, is_parent: bool) -> i32 { pub fn execute_setpgid(pid: Pid, pgroup: Pid, is_parent: bool) -> i32 {
// There is a comment "Historically we have looped here to support WSL." // There is a comment "Historically we have looped here to support WSL."
// TODO: stop looping. // TODO: stop looping.
let mut eperm_count = 0; let mut eperm_count = 0;
loop { loop {
if unsafe { libc::setpgid(pid, pgroup) } == 0 { if unsafe { libc::setpgid(pid.as_pid_t(), pgroup.as_pid_t()) } == 0 {
return 0; return 0;
} }
let err = errno::errno().0; let err = errno::errno().0;

View file

@ -105,7 +105,7 @@ impl PosixSpawner {
// desired_pgid tracks the pgroup for the process. If it is none, the pgroup is left unchanged. // desired_pgid tracks the pgroup for the process. If it is none, the pgroup is left unchanged.
// If it is zero, create a new pgroup from the pid. If it is >0, join that pgroup. // If it is zero, create a new pgroup from the pid. If it is >0, join that pgroup.
let desired_pgid = if let Some(pgid) = j.get_pgid() { let desired_pgid = if let Some(pgid) = j.get_pgid() {
Some(pgid) Some(pgid.get())
} else if j.processes()[0].leads_pgrp { } else if j.processes()[0].leads_pgrp {
Some(0) Some(0)
} else { } else {

View file

@ -1,5 +1,5 @@
use crate::global_safety::RelaxedAtomicBool; use crate::global_safety::RelaxedAtomicBool;
use crate::proc::JobGroupRef; use crate::proc::{AtomicPid, JobGroupRef, Pid};
use crate::signal::Signal; use crate::signal::Signal;
use crate::wchar::prelude::*; use crate::wchar::prelude::*;
use std::cell::RefCell; use std::cell::RefCell;
@ -72,8 +72,8 @@ pub struct JobGroup {
/// Whether we are in the foreground, meaning the user is waiting for this job to complete. /// Whether we are in the foreground, meaning the user is waiting for this job to complete.
pub is_foreground: RelaxedAtomicBool, pub is_foreground: RelaxedAtomicBool,
/// The pgid leading our group. This is only ever set if [`job_control`](Self::JobControl) is /// 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. /// true. We ensure the value (when set) is always non-negative and non-zero.
pgid: RefCell<Option<libc::pid_t>>, pgid: AtomicPid,
/// The original command which produced this job tree. /// The original command which produced this job tree.
pub command: WString, pub command: WString,
/// Our job id, if any. `None` here should evaluate to `-1` for ffi purposes. /// Our job id, if any. `None` here should evaluate to `-1` for ffi purposes.
@ -149,14 +149,14 @@ impl JobGroup {
"Should not set a pgid for a group that doesn't want job control!" "Should not set a pgid for a group that doesn't want job control!"
); );
assert!(pgid >= 0, "Invalid pgid!"); assert!(pgid >= 0, "Invalid pgid!");
assert!(self.pgid.borrow().is_none(), "JobGroup::pgid already set!");
self.pgid.replace(Some(pgid)); let old_pgid = self.pgid.swap(Pid::new(pgid).unwrap());
assert!(old_pgid.is_none(), "JobGroup::pgid already set!");
} }
/// Returns the value of [`JobGroup::pgid`]. This is never fish's own pgid! /// Returns the value of [`JobGroup::pgid`]. This is never fish's own pgid!
pub fn get_pgid(&self) -> Option<libc::pid_t> { pub fn get_pgid(&self) -> Option<Pid> {
*self.pgid.borrow() self.pgid.load()
} }
} }
@ -223,7 +223,7 @@ impl JobGroup {
tmodes: RefCell::default(), tmodes: RefCell::default(),
signal: 0.into(), signal: 0.into(),
is_foreground: RelaxedAtomicBool::new(false), is_foreground: RelaxedAtomicBool::new(false),
pgid: RefCell::default(), pgid: AtomicPid::default(),
} }
} }

View file

@ -361,11 +361,13 @@ impl TtyTransfer {
// Get the pgid; we must have one if we want the terminal. // Get the pgid; we must have one if we want the terminal.
let pgid = jg.get_pgid().unwrap(); let pgid = jg.get_pgid().unwrap();
assert!(pgid >= 0, "Invalid pgid");
// It should never be fish's pgroup. // It should never be fish's pgroup.
let fish_pgrp = unsafe { libc::getpgrp() }; let fish_pgrp = unsafe { libc::getpgrp() };
assert!(pgid != fish_pgrp, "Job should not have fish's pgroup"); assert!(
pgid.as_pid_t() != fish_pgrp,
"Job should not have fish's pgroup"
);
// Ok, we want to transfer to the child. // Ok, we want to transfer to the child.
// Note it is important to be very careful about calling tcsetpgrp()! // Note it is important to be very careful about calling tcsetpgrp()!
@ -385,10 +387,10 @@ impl TtyTransfer {
if current_owner < 0 { if current_owner < 0 {
// Case 1. // Case 1.
return false; return false;
} else if current_owner == pgid { } else if current_owner == pgid.get() {
// Case 2. // Case 2.
return true; return true;
} else if current_owner != pgid && current_owner != fish_pgrp { } else if current_owner != pgid.get() && current_owner != fish_pgrp {
// Case 3. // Case 3.
return false; return false;
} }
@ -403,7 +405,7 @@ impl TtyTransfer {
// 4.4.0), EPERM does indeed disappear on retry. The important thing is that we can // 4.4.0), EPERM does indeed disappear on retry. The important thing is that we can
// guarantee the process isn't going to exit while we wait (which would cause us to possibly // guarantee the process isn't going to exit while we wait (which would cause us to possibly
// block indefinitely). // block indefinitely).
while unsafe { libc::tcsetpgrp(STDIN_FILENO, pgid) } != 0 { while unsafe { libc::tcsetpgrp(STDIN_FILENO, pgid.as_pid_t()) } != 0 {
FLOGF!(proc_termowner, "tcsetpgrp failed: %d", errno::errno().0); FLOGF!(proc_termowner, "tcsetpgrp failed: %d", errno::errno().0);
// Before anything else, make sure that it's even necessary to call tcsetpgrp. // Before anything else, make sure that it's even necessary to call tcsetpgrp.
@ -429,7 +431,7 @@ impl TtyTransfer {
} }
} }
} }
if getpgrp_res == pgid { if getpgrp_res == pgid.get() {
FLOGF!( FLOGF!(
proc_termowner, proc_termowner,
"Process group %d already has control of terminal", "Process group %d already has control of terminal",
@ -447,7 +449,7 @@ impl TtyTransfer {
} else if errno::errno().0 == EPERM { } else if errno::errno().0 == EPERM {
// Retry so long as this isn't because the process group is dead. // Retry so long as this isn't because the process group is dead.
let mut result: libc::c_int = 0; let mut result: libc::c_int = 0;
let wait_result = unsafe { libc::waitpid(-pgid, &mut result, WNOHANG) }; let wait_result = unsafe { libc::waitpid(-pgid.as_pid_t(), &mut result, WNOHANG) };
if wait_result == -1 { if wait_result == -1 {
// Note that -1 is technically an "error" for waitpid in the sense that an // Note that -1 is technically an "error" for waitpid in the sense that an
// invalid argument was specified because no such process group exists any // invalid argument was specified because no such process group exists any
@ -508,6 +510,7 @@ impl Drop for TtyTransfer {
} }
} }
/// A type-safe equivalent to [`libc::pid_t`].
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct Pid(NonZeroU32); pub struct Pid(NonZeroU32);
@ -540,6 +543,24 @@ impl Into<Pid> for u32 {
} }
} }
impl std::fmt::Display for Pid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.get(), f)
}
}
impl ToWString for Pid {
fn to_wstring(&self) -> WString {
self.get().to_wstring()
}
}
impl fish_printf::ToArg<'static> for Pid {
fn to_arg(self) -> fish_printf::Arg<'static> {
fish_printf::Arg::UInt(self.get() as u64)
}
}
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct AtomicPid(AtomicU32); pub struct AtomicPid(AtomicU32);
@ -666,13 +687,13 @@ impl Process {
/// Retrieves the associated [`libc::pid_t`], `None` if unset. /// Retrieves the associated [`libc::pid_t`], `None` if unset.
#[inline(always)] #[inline(always)]
pub fn pid(&self) -> Option<libc::pid_t> { pub fn pid(&self) -> Option<Pid> {
self.pid.load().map(|pid| pid.as_pid_t()) self.pid.load()
} }
#[inline(always)] #[inline(always)]
pub fn has_pid(&self) -> bool { pub fn has_pid(&self) -> bool {
self.pid.load().is_some() self.pid().is_some()
} }
/// Sets the process' pid. Panics if a pid has already been set. /// Sets the process' pid. Panics if a pid has already been set.
@ -759,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(), self.pid().unwrap().get(),
jid, jid,
wbasename(&self.actual_cmd.clone()).to_owned(), wbasename(&self.actual_cmd.clone()).to_owned(),
))); )));
@ -903,17 +924,17 @@ impl Job {
/// Return our pgid, or none if we don't have one, or are internal to fish /// Return our pgid, or none if we don't have one, or are internal to fish
/// This never returns fish's own pgroup. /// This never returns fish's own pgroup.
// TODO: Return a type-safe result. pub fn get_pgid(&self) -> Option<Pid> {
pub fn get_pgid(&self) -> Option<libc::pid_t> {
self.group().get_pgid() self.group().get_pgid()
} }
/// Return the pid of the last external process in the job. /// Return the pid of the last external process in the job.
/// This may be none if the job consists of just internal fish functions or builtins. /// This may be none if the job consists of just internal fish functions or builtins.
/// This will never be fish's own pid. /// This will never be fish's own pid.
// TODO: Return a type-safe result. pub fn get_last_pid(&self) -> Option<Pid> {
pub fn get_last_pid(&self) -> Option<libc::pid_t> { self.external_procs()
self.processes().iter().filter_map(|proc| proc.pid()).last() .last()
.and_then(|proc| proc.pid.load())
} }
/// The id of this job. /// The id of this job.
@ -1082,20 +1103,22 @@ impl Job {
/// Return true on success, false on failure. /// Return true on success, false on failure.
pub fn signal(&self, signal: i32) -> bool { pub fn signal(&self, signal: i32) -> bool {
if let Some(pgid) = self.group().get_pgid() { if let Some(pgid) = self.group().get_pgid() {
if unsafe { libc::killpg(pgid, signal) } == -1 { if unsafe { libc::killpg(pgid.as_pid_t(), signal) } == -1 {
let strsignal = unsafe { libc::strsignal(signal) }; let strsignal = unsafe { libc::strsignal(signal) };
let strsignal = if strsignal.is_null() { let strsignal = if strsignal.is_null() {
L!("(nil)").to_owned() L!("(nil)").to_owned()
} else { } else {
charptr2wcstring(strsignal) charptr2wcstring(strsignal)
}; };
wperror(&sprintf!("killpg(%d, %s)", pgid, strsignal)); wperror(&sprintf!("killpg(%d, %s)", pgid.get(), strsignal));
return false; return false;
} }
} else { } else {
// This job lives in fish's pgroup and we need to signal procs individually. // This job lives in fish's pgroup and we need to signal procs individually.
for p in self.external_procs() { for p in self.external_procs() {
if !p.is_completed() && unsafe { libc::kill(p.pid().unwrap(), signal) } == -1 { if !p.is_completed()
&& unsafe { libc::kill(p.pid().unwrap().as_pid_t(), signal) } == -1
{
return false; return false;
} }
} }
@ -1235,7 +1258,7 @@ pub fn print_exit_warning_for_jobs(jobs: &JobList) {
// external processes always have a pid set. // external processes always have a pid set.
printf!( printf!(
"%6d %ls\n", "%6d %ls\n",
j.external_procs().next().unwrap().pid().unwrap(), j.external_procs().next().and_then(|p| p.pid()).unwrap(),
j.command() j.command()
); );
} }
@ -1354,7 +1377,7 @@ pub fn hup_jobs(jobs: &JobList) {
let mut kill_list = Vec::new(); let mut kill_list = Vec::new();
for j in jobs { for j in jobs {
let Some(pgid) = j.get_pgid() else { continue }; let Some(pgid) = j.get_pgid() else { continue };
if pgid != fish_pgrp && !j.is_completed() { if pgid.as_pid_t() != fish_pgrp && !j.is_completed() {
j.signal(SIGHUP); j.signal(SIGHUP);
if j.is_stopped() { if j.is_stopped() {
j.signal(SIGCONT); j.signal(SIGCONT);
@ -1391,7 +1414,7 @@ pub fn hup_jobs(jobs: &JobList) {
pub fn add_disowned_job(j: &Job) { pub fn add_disowned_job(j: &Job) {
let mut disowned_pids = DISOWNED_PIDS.get().borrow_mut(); let mut disowned_pids = DISOWNED_PIDS.get().borrow_mut();
for process in j.external_procs() { for process in j.external_procs() {
disowned_pids.push(process.pid().unwrap()); disowned_pids.push(process.pid().unwrap().as_pid_t());
} }
} }
@ -1475,7 +1498,7 @@ fn process_mark_finished_children(parser: &Parser, block_ok: bool) {
let mut statusv: libc::c_int = -1; let mut statusv: libc::c_int = -1;
let pid = unsafe { let pid = unsafe {
libc::waitpid( libc::waitpid(
proc.pid().unwrap(), proc.pid().unwrap().as_pid_t(),
&mut statusv, &mut statusv,
WNOHANG | WUNTRACED | WCONTINUED, WNOHANG | WUNTRACED | WCONTINUED,
) )
@ -1483,7 +1506,10 @@ fn process_mark_finished_children(parser: &Parser, block_ok: bool) {
if pid <= 0 { if pid <= 0 {
continue; continue;
} }
assert!(pid == proc.pid().unwrap(), "Unexpected waitpid() return"); assert!(
pid == proc.pid().unwrap().get(),
"Unexpected waitpid() return"
);
// The process has stopped or exited! Update its status. // The process has stopped or exited! Update its status.
let status = ProcStatus::from_waitpid(statusv); let status = ProcStatus::from_waitpid(statusv);
@ -1572,7 +1598,7 @@ fn generate_process_exit_events(j: &Job, out_evts: &mut Vec<Event>) {
if p.is_completed() && !p.posted_proc_exit.load() { if p.is_completed() && !p.posted_proc_exit.load() {
p.posted_proc_exit.store(true); p.posted_proc_exit.store(true);
out_evts.push(Event::process_exit( out_evts.push(Event::process_exit(
p.pid().unwrap(), p.pid().unwrap().as_pid_t(),
p.status.status_value(), p.status.status_value(),
)); ));
} }
@ -1587,7 +1613,7 @@ fn generate_job_exit_events(j: &Job, out_evts: &mut Vec<Event>) {
// job_exit events. // job_exit events.
if j.posts_job_exit_events() { if j.posts_job_exit_events() {
if let Some(last_pid) = j.get_last_pid() { if let Some(last_pid) = j.get_last_pid() {
out_evts.push(Event::job_exit(last_pid, j.internal_job_id)); out_evts.push(Event::job_exit(last_pid.as_pid_t(), j.internal_job_id));
} }
} }
} }