use alloc::vec::Vec;
use core::{future::poll_fn, task::Poll};
use ax_errno::{AxError, AxResult, LinuxError};
use ax_task::{
current,
future::{block_on, interruptible},
};
use bitflags::bitflags;
use linux_raw_sys::general::{__WALL, __WCLONE, __WNOTHREAD, WCONTINUED, WNOHANG, WUNTRACED};
use starry_process::{Pid, Process};
use starry_vm::{VmMutPtr, VmPtr};
use crate::task::{AsThread, get_task, remove_process, unregister_zombie};
bitflags! {
#[derive(Debug)]
struct WaitOptions: u32 {
const WNOHANG = WNOHANG;
const WUNTRACED = WUNTRACED;
const WCONTINUED = WCONTINUED;
const WNOTHREAD = __WNOTHREAD;
const WALL = __WALL;
const WCLONE = __WCLONE;
}
}
#[derive(Debug, Clone, Copy)]
enum WaitPid {
Any,
Pid(Pid),
Pgid(Pid),
}
impl WaitPid {
fn apply(&self, child: &Process) -> bool {
match self {
WaitPid::Any => true,
WaitPid::Pid(pid) => child.pid() == *pid,
WaitPid::Pgid(pgid) => child.group().pgid() == *pgid,
}
}
}
pub fn sys_waitpid(pid: i32, exit_code: *mut i32, options: u32) -> AxResult<isize> {
let options = WaitOptions::from_bits(options).ok_or(AxError::InvalidInput)?;
info!("sys_waitpid <= pid: {pid:?}, options: {options:?}");
let curr = current();
let proc = &curr.as_thread().proc_data.proc;
let pid = if pid == -1 {
WaitPid::Any
} else if pid == 0 {
WaitPid::Pgid(proc.group().pgid())
} else if pid > 0 {
WaitPid::Pid(pid as _)
} else {
WaitPid::Pgid(-pid as _)
};
let children = proc
.children()
.into_iter()
.filter(|child| pid.apply(child))
.collect::<Vec<_>>();
if children.is_empty() {
return Err(AxError::from(LinuxError::ECHILD));
}
let proc_data = curr.as_thread().proc_data.clone();
let check_children = || {
if let Some(child) = children.iter().find(|child| child.is_zombie()) {
for tid in child.threads() {
if let Ok(task) = get_task(tid) {
let thr = task.as_thread();
let (utime, stime) = thr.time.borrow().output();
proc_data.add_child_cpu_time(utime, stime);
}
}
child.free();
remove_process(child.pid());
unregister_zombie(child.pid());
if let Some(exit_code) = exit_code.nullable() {
exit_code.vm_write(child.exit_code())?;
}
Ok(Some(child.pid() as _))
} else if options.contains(WaitOptions::WNOHANG) {
Ok(Some(0))
} else {
Ok(None)
}
};
block_on(interruptible(poll_fn(|cx| {
match check_children().transpose() {
Some(res) => Poll::Ready(res),
None => {
proc_data.child_exit_event.register(cx.waker());
match check_children().transpose() {
Some(res) => Poll::Ready(res),
None => Poll::Pending,
}
}
}
})))?
}