use alloc::sync::Arc;
use core::{future::poll_fn, task::Poll};
use ax_runtime::hal::cpu::user::UserContext;
use linux_raw_sys::general::{
MINSIGSTKSZ, SI_TKILL, SI_USER, SIG_BLOCK, SIG_SETMASK, SIG_UNBLOCK, SS_DISABLE, SS_FLAG_BITS,
SS_ONSTACK, kernel_sigaction, siginfo, timespec,
};
use starry_signal::{SignalInfo, SignalSet, SignalStack, Signo};
use crate::{
Errno, StarryError, StarryResult,
mm::{UserMemoryProvider, VmMutPtr, VmPtr},
task::{
PgidNumber, PidIdentity, TgidNumber, TidNumber, block_next_signal, check_signals,
current_pid_view,
future::{UserWaitOutcome, block_on_user, block_on_user_timeout},
get_user_task_by_number, processes, send_signal_to_process_data, send_signal_to_task,
},
time::TimeValueLike,
};
pub(crate) fn check_sigset_size(size: usize) -> StarryResult<()> {
if size != size_of::<SignalSet>() {
return Err(StarryError::InvalidInput);
}
Ok(())
}
fn parse_signo(signo: u32) -> StarryResult<Signo> {
Signo::from_repr(signo as u8).ok_or(StarryError::InvalidInput)
}
pub fn sys_rt_sigprocmask(
current: &crate::task::UserTaskRef,
how: i32,
set: *const SignalSet,
oldset: *mut SignalSet,
sigsetsize: usize,
) -> StarryResult<isize> {
check_sigset_size(sigsetsize)?;
let curr = current;
let sig = curr.as_thread().signal();
let old = sig.blocked();
if let Some(oldset) = oldset.nullable() {
oldset.vm_write(current, old)?;
}
if let Some(set) = set.nullable() {
let set = unsafe { set.vm_read_uninit(current)?.assume_init() };
let set = match how as u32 {
SIG_BLOCK => old | set,
SIG_UNBLOCK => old & !set,
SIG_SETMASK => set,
_ => return Err(StarryError::InvalidInput),
};
debug!("sys_rt_sigprocmask <= {set:?}");
sig.set_blocked(set);
}
Ok(0)
}
pub fn sys_rt_sigaction(
current: &crate::task::UserTaskRef,
signo: u32,
act: *const kernel_sigaction,
oldact: *mut kernel_sigaction,
sigsetsize: usize,
) -> StarryResult<isize> {
check_sigset_size(sigsetsize)?;
let signo = parse_signo(signo)?;
if matches!(signo, Signo::SIGKILL | Signo::SIGSTOP) {
return Err(StarryError::InvalidInput);
}
let mut user_memory = UserMemoryProvider::new(current);
Ok(current
.as_thread()
.proc_data
.signal
.set_action(&mut user_memory, signo, act, oldact)?)
}
pub fn sys_rt_sigpending(
current: &crate::task::UserTaskRef,
set: *mut SignalSet,
sigsetsize: usize,
) -> crate::StarryResult<isize> {
check_sigset_size(sigsetsize)?;
set.vm_write(current, current.as_thread().signal().pending())?;
Ok(0)
}
pub(crate) fn make_siginfo(
current: &crate::task::UserTaskRef,
signo: u32,
code: i32,
) -> crate::StarryResult<Option<SignalInfo>> {
if signo == 0 {
return Ok(None);
}
let signo = parse_signo(signo)?;
let curr = current;
let thread = curr.as_thread();
Ok(Some(SignalInfo::new_user(
signo,
code,
current_pid_view()
.visible_number(&thread.proc_data.identity())
.expect("current process is visible in its active PID namespace")
.get(),
thread.cred().uid,
)))
}
pub(crate) fn check_kill_permission_identity(
current: &crate::task::UserTaskRef,
target: &PidIdentity,
) -> StarryResult<()> {
let sender = current.as_thread().cred();
if sender.euid == 0 {
return Ok(());
}
let self_identity = current.as_thread().proc_data.identity();
if core::ptr::eq(target, Arc::as_ref(&self_identity)) {
return Ok(());
}
let target_cred = if let Some(task) = target.live_task() {
task.as_thread().cred()
} else {
target
.zombie_snapshot(|zombie| zombie.cred.clone())
.ok_or(StarryError::NoSuchProcess)?
};
if sender.euid == target_cred.uid
|| sender.euid == target_cred.euid
|| sender.euid == target_cred.suid
|| sender.uid == target_cred.uid
|| sender.uid == target_cred.euid
|| sender.uid == target_cred.suid
{
Ok(())
} else {
Err(StarryError::OperationNotPermitted)
}
}
fn signal_user_process(identity: &PidIdentity, sig: Option<SignalInfo>) -> StarryResult<()> {
if let Some(proc_data) = identity.live_data() {
send_signal_to_process_data(&proc_data, sig)
} else if identity.is_zombie() {
Ok(())
} else {
Err(StarryError::NoSuchProcess)
}
}
fn kill_process_group_checked(
current: &crate::task::UserTaskRef,
pgid: PgidNumber,
sig: Option<SignalInfo>,
) -> StarryResult<()> {
let view = current_pid_view();
let pg = view.resolve_group(pgid)?;
let mut visible_members = 0;
let mut permitted_members = 0;
for proc in pg.processes() {
let identity = proc.identity();
if view.visible_number(&identity).is_none() {
continue;
}
visible_members += 1;
if check_kill_permission_identity(current, &identity).is_ok() {
permitted_members += 1;
if let Some(sig) = sig.as_ref()
&& let Some(proc_data) = identity.live_data()
{
let _ = send_signal_to_process_data(&proc_data, Some(*sig));
}
}
}
if visible_members == 0 {
Err(crate::StarryError::NoSuchProcess)
} else if permitted_members == 0 {
Err(crate::StarryError::OperationNotPermitted)
} else {
Ok(())
}
}
enum KillTarget {
Process(TgidNumber),
CurrentProcessGroup,
AllPermittedProcesses,
ProcessGroup(PgidNumber),
}
impl TryFrom<i32> for KillTarget {
type Error = StarryError;
fn try_from(pid: i32) -> Result<Self, Self::Error> {
match pid {
1.. => Ok(Self::Process(TgidNumber::try_from(pid as u32)?)),
0 => Ok(Self::CurrentProcessGroup),
-1 => Ok(Self::AllPermittedProcesses),
..-1 => Ok(Self::ProcessGroup(PgidNumber::try_from(
pid.checked_neg().ok_or(StarryError::InvalidInput)? as u32,
)?)),
}
}
}
pub fn sys_kill(current: &crate::task::UserTaskRef, pid: i32, signo: u32) -> StarryResult<isize> {
debug!("sys_kill: pid = {pid}, signo = {signo}");
let sig = make_siginfo(current, signo, SI_USER as _)?;
match KillTarget::try_from(pid)? {
KillTarget::Process(tgid) => {
let identity = current_pid_view().resolve_process(tgid)?;
check_kill_permission_identity(current, &identity)?;
if let Some(sig) = sig {
let curr = current;
let thread = curr.as_thread();
let signo = sig.signo();
if Arc::ptr_eq(&identity, &thread.proc_data.identity())
&& !thread.signal().signal_blocked(signo)
{
send_signal_to_task(curr, None, Some(sig))?;
} else {
signal_user_process(&identity, Some(sig))?;
}
} else {
signal_user_process(&identity, None)?;
}
}
KillTarget::CurrentProcessGroup => {
let pgid = current.as_thread().proc_data.proc.group().pgid_number();
kill_process_group_checked(current, pgid, sig)?;
}
KillTarget::AllPermittedProcesses => {
let curr_pid = current.as_thread().proc_data.proc.pid();
if let Some(sig) = sig {
for proc_data in processes() {
let Some(visible_pid) =
current_pid_view().visible_number(&proc_data.identity())
else {
continue;
};
if visible_pid.get() <= 1 || proc_data.proc.pid() == curr_pid {
continue;
}
if check_kill_permission_identity(current, &proc_data.identity()).is_ok() {
let _ = send_signal_to_process_data(&proc_data, Some(sig));
}
}
}
}
KillTarget::ProcessGroup(pgid) => {
kill_process_group_checked(current, pgid, sig)?;
}
}
Ok(0)
}
pub fn sys_tkill(
current: &crate::task::UserTaskRef,
tid: i32,
signo: u32,
) -> crate::StarryResult<isize> {
if tid <= 0 {
return Err(StarryError::InvalidInput);
}
let tid = TidNumber::try_from(tid as u32)?;
let task = get_user_task_by_number(tid)?;
check_kill_permission_identity(current, &task.as_thread().proc_data.identity())?;
let sig = make_siginfo(current, signo, SI_TKILL)?;
send_signal_to_task(&task, None, sig)?;
Ok(0)
}
pub fn sys_tgkill(
current: &crate::task::UserTaskRef,
tgid: i32,
tid: i32,
signo: u32,
) -> StarryResult<isize> {
if tgid <= 0 || tid <= 0 {
return Err(StarryError::InvalidInput);
}
let process = current_pid_view().resolve_process(TgidNumber::try_from(tgid as u32)?)?;
check_kill_permission_identity(current, &process)?;
let task = get_user_task_by_number(TidNumber::try_from(tid as u32)?)?;
let sig = make_siginfo(current, signo, SI_TKILL)?;
send_signal_to_task(&task, Some(process), sig)?;
Ok(0)
}
pub(crate) fn make_queue_signal_info(
current: &crate::task::UserTaskRef,
tgid: TgidNumber,
signo: u32,
sig: *const SignalInfo,
) -> StarryResult<Option<SignalInfo>> {
if signo == 0 {
return Ok(None);
}
let signo = parse_signo(signo)?;
let mut sig = unsafe { sig.vm_read_uninit(current)?.assume_init() };
sig.set_signo(signo);
if !Arc::ptr_eq(
¤t.as_thread().proc_data.identity(),
¤t_pid_view().resolve_process(tgid)?,
) && (sig.code() >= 0 || sig.code() == SI_TKILL)
{
return Err(StarryError::OperationNotPermitted);
}
Ok(Some(sig))
}
pub fn sys_rt_sigqueueinfo(
current: &crate::task::UserTaskRef,
tgid: u32,
signo: u32,
sig: *const SignalInfo,
) -> StarryResult<isize> {
let tgid = TgidNumber::try_from(tgid)?;
let sig = make_queue_signal_info(current, tgid, signo, sig)?;
let process = current_pid_view().resolve_process(tgid)?;
signal_user_process(&process, sig)?;
Ok(0)
}
pub fn sys_rt_tgsigqueueinfo(
current: &crate::task::UserTaskRef,
tgid: u32,
tid: u32,
signo: u32,
sig: *const SignalInfo,
) -> StarryResult<isize> {
let tgid = TgidNumber::try_from(tgid)?;
let sig = make_queue_signal_info(current, tgid, signo, sig)?;
let process = current_pid_view().resolve_process(tgid)?;
let task = get_user_task_by_number(TidNumber::try_from(tid)?)?;
send_signal_to_task(&task, Some(process), sig)?;
Ok(0)
}
pub fn sys_rt_sigreturn(
current: &crate::task::UserTaskRef,
uctx: &mut UserContext,
) -> crate::StarryResult<isize> {
block_next_signal();
let mut user_memory = UserMemoryProvider::new(current);
#[cfg(target_arch = "x86_64")]
{
let restored = current.as_thread().signal().restore(&mut user_memory, uctx);
if restored.is_err() {
ax_runtime::thread::reset_current_user_fp_state()
.expect("invalid sigreturn frame must reset current task FPU state");
}
match restored? {
Some(state) => ax_runtime::thread::replace_current_user_fp_state(state)?,
None => ax_runtime::thread::reset_current_user_fp_state()?,
}
}
#[cfg(not(target_arch = "x86_64"))]
current
.as_thread()
.signal()
.restore(&mut user_memory, uctx)?;
Ok(uctx.retval() as isize)
}
pub fn sys_rt_sigtimedwait(
current: &crate::task::UserTaskRef,
uctx: &mut UserContext,
set: *const SignalSet,
info: *mut siginfo,
timeout: *const timespec,
sigsetsize: usize,
) -> StarryResult<isize> {
check_sigset_size(sigsetsize)?;
let set = unsafe { set.vm_read_uninit(current)?.assume_init() };
let timeout = if let Some(ts) = timeout.nullable() {
let ts = unsafe { ts.vm_read_uninit(current)?.assume_init() };
Some(ts.try_into_time_value()?)
} else {
None
};
debug!("sys_rt_sigtimedwait => set = {set:?}, timeout = {timeout:?}");
let curr = current;
let thr = curr.as_thread();
let signal = thr.signal();
let old_blocked = signal.blocked();
signal.begin_sigwait(set);
uctx.set_retval(-Errno::EINTR.into_raw() as usize);
let fut = poll_fn(|cx| {
if let Some(sig) = signal.dequeue_signal(&set) {
Poll::Ready(Some(sig))
} else if check_signals(current, uctx, Some(old_blocked), None) {
Poll::Ready(None)
} else {
signal.register_sigwait_waker(cx.waker());
if let Some(sig) = signal.dequeue_signal(&set) {
Poll::Ready(Some(sig))
} else if check_signals(current, uctx, Some(old_blocked), None) {
Poll::Ready(None)
} else {
Poll::Pending
}
}
});
let sig = match block_on_user_timeout(curr, timeout, fut) {
UserWaitOutcome::Ready(sig) => sig,
UserWaitOutcome::Interrupted => None,
UserWaitOutcome::TimedOut => {
signal.finish_sigwait();
return Err(crate::StarryError::WouldBlock);
}
};
let Some(sig) = sig else {
signal.finish_sigwait();
return Ok(0);
};
signal.finish_sigwait();
if let Some(info) = info.nullable() {
info.cast::<SignalInfo>().vm_write(current, sig)?;
}
Ok(sig.signo() as _)
}
pub fn sys_rt_sigsuspend(
current: &crate::task::UserTaskRef,
uctx: &mut UserContext,
set: *const SignalSet,
sigsetsize: usize,
) -> StarryResult<isize> {
check_sigset_size(sigsetsize)?;
let curr = current;
let thr = curr.as_thread();
let set = unsafe { set.vm_read_uninit(current)?.assume_init() };
let old_blocked = thr.signal().set_blocked(set);
uctx.set_retval(-Errno::EINTR.into_raw() as usize);
let _outcome = block_on_user(
curr,
poll_fn(|_cx| {
if check_signals(current, uctx, Some(old_blocked), None) {
return Poll::Ready(());
}
Poll::Pending
}),
);
Err(StarryError::Interrupted)
}
pub fn sys_sigaltstack(
current: &crate::task::UserTaskRef,
ss: *const SignalStack,
old_ss: *mut SignalStack,
) -> crate::StarryResult<isize> {
let curr = current;
let sig = curr.as_thread().signal();
if let Some(old_ss) = old_ss.nullable() {
old_ss.vm_write(current, sig.stack())?;
}
if let Some(ss) = ss.nullable() {
let ss = unsafe { ss.vm_read_uninit(current)?.assume_init() };
if sig.stack_active() {
return Err(StarryError::OperationNotPermitted);
}
if ss.flags & !(SS_DISABLE | SS_ONSTACK | SS_FLAG_BITS) != 0 {
return Err(StarryError::InvalidInput);
}
if ss.flags & SS_DISABLE == 0 && ss.size < MINSIGSTKSZ as usize {
return Err(StarryError::NoMemory);
}
sig.set_stack(ss);
}
Ok(0)
}
#[cfg(all(test, not(axtest)))]
fn signal_sigset_size_and_signo_validation_rules_hold_for_test() -> bool {
use core::mem::size_of;
use starry_signal::SignalSet;
let correct_size = size_of::<SignalSet>();
let ok = check_sigset_size(correct_size).is_ok();
let too_small = check_sigset_size(correct_size - 1).is_err();
let too_big = check_sigset_size(correct_size + 1).is_err();
let zero = check_sigset_size(0).is_err();
let valid_signo = parse_signo(9).is_ok(); let valid_signo2 = parse_signo(19).is_ok(); let zero_signo = parse_signo(0).is_err(); let overflow = parse_signo(256).is_err();
ok && too_small && too_big && zero && valid_signo && valid_signo2 && zero_signo && overflow
}
#[cfg(all(test, not(axtest)))]
fn signal_sigset_and_signo_validation_rules_hold_for_test() -> bool {
use core::mem::size_of;
use starry_signal::SignalSet;
let correct_size = size_of::<SignalSet>();
assert!(check_sigset_size(correct_size).is_ok());
assert!(check_sigset_size(correct_size - 1).is_err());
assert!(check_sigset_size(correct_size + 1).is_err());
assert!(check_sigset_size(0).is_err());
assert!(parse_signo(1).is_ok()); assert!(parse_signo(9).is_ok()); assert!(parse_signo(0).is_err()); assert!(parse_signo(255).is_err());
true
}
#[cfg(all(test, not(axtest)))]
mod tests {
#[test]
fn signal_sigset_size_and_signo_validation_rules_hold() {
assert!(super::signal_sigset_size_and_signo_validation_rules_hold_for_test());
}
#[test]
fn signal_sigset_and_signo_validation_rules_hold() {
assert!(super::signal_sigset_and_signo_validation_rules_hold_for_test());
}
}