diff --git a/src/signal.c b/src/signal.c index a5f144c..d598463 100644 --- a/src/signal.c +++ b/src/signal.c @@ -154,3 +154,164 @@ bool sigRaiseInfo(uint32 targetId, int signum, int siCode, int siPid, int siStat target->sigPendingInfo[signum].siPid = siPid; target->sigPendingInfo[signum].siStatus = siStatus; + /* Real Unix's interruptible sleep/EINTR (deliberately out of scope + until now, see the file comment's history): a Sleeping/Blocked + target whose sleep timer or wait-channel condition would otherwise + never naturally fire again (e.g. user/eintrtarget.c's read that no + writer will ever satisfy) must still actually receive a signal that + would genuinely be delivered, not sit on it forever. Skipped + entirely for a blocked or ignored signal -- see sigWouldDeliver -- + so a masked/ignored signal never disturbs a thread's sleep/wait at + all, matching real Unix. threadWakeForSignal itself refuses anything + other than Sleeping/Blocked (Stopped is excluded on purpose -- only + threadContinue may move it), so this is safe to call unconditionally + whenever sigWouldDeliver agrees. */ + if (sigWouldDeliver(target, signum)) + threadWakeForSignal(targetId); + + return true; +} + +bool sigRaise(uint32 targetId, int signum) +{ + return sigRaiseInfo(targetId, signum, siCodeKernel, 0, 0); +} + +bool sigRaiseGroup(uint32 pgid, int signum, int siCode, int siPid) +{ + bool anyTarget = false; + for (uint32 id = 1; id < threadCapacity(); id++) { + Thread* candidate = threadAt(id); + if (candidate->state == threadDead || candidate->state == threadUnused) + continue; + if (candidate->pgid != pgid) + continue; + + anyTarget = true; + sigRaiseInfo(id, signum, siCode, siPid, 0); + } + return anyTarget; +} + +uint64 sigSetHandler(Thread* thread, int signum, uint64 handlerAddr, uint64 mask, uint64 flags) +{ + if (signum <= 0 || signum >= sigMaxSignals) + return sigSetFailed; + if (signum == sigKill || signum == sigStop) + return sigSetFailed; + + uint64 previous = thread->sigHandlers[signum]; + thread->sigHandlers[signum] = handlerAddr; + + /* Same stripping sigProcMask already does to a mask it is handed: a + mask cannot make sigKill/sigStop blockable just because it asked. */ + mask &= ~(1ull << sigKill); + mask &= ~(1ull << sigStop); + thread->sigHandlerMasks[signum] = mask; + thread->sigHandlerFlags[signum] = flags; + + return previous; +} + +uint64 sigProcMask(Thread* thread, int how, uint64 mask) +{ + uint64 previous = thread->sigBlocked; + mask &= ~(1ull << sigKill); + mask &= ~(1ull << sigStop); + + switch (how) { + case sigMaskBlock: + thread->sigBlocked |= mask; + break; + case sigMaskUnblock: + thread->sigBlocked &= ~mask; + break; + case sigMaskSetMask: + thread->sigBlocked = mask; + break; + default: + break; + } + + return previous; +} + +SigOutcome sigConsumePending(Thread* thread, Registers* regs, int* outSignum) +{ + if (thread->sigHandlerDepth >= sigMaxNestDepth) + return sigOutcomeNone; + + for (int signum = 1; signum < sigMaxSignals; signum++) { + uint64 bit = 1ull << (uint32)signum; + if (!(thread->sigPending & bit) || (thread->sigBlocked & bit)) + continue; + + thread->sigPending &= ~bit; + uint64 handler = thread->sigHandlers[signum]; + + if (handler == sigHandlerIgnore || + (handler == sigHandlerDefault && sigDefaultIsIgnore(signum))) + continue; + + if (outSignum) + *outSignum = signum; + + if (handler == sigHandlerDefault) { + if (sigDefaultIsStop(signum)) + return sigOutcomeStop; + return sigOutcomeTerminate; + } + + /* SA_RESTART / EINTR (see file comment): consumed here, exactly + once, regardless of which way the decision goes -- a signal + nested INSIDE the handler this delivery is about to enter is + interrupting that handler's own code, not a retryable syscall + retry point, so it must never see this as still true. Only + rewrites `regs` (the context about to be saved below) when this + thread was genuinely mid one of sysRead/sysWrite/sysWait's own + retry-on-block pattern AND this particular signal was not + registered with sigFlagRestart -- anything else (plain running + code, a sleep, an already-nested handler, or sigFlagRestart + itself) leaves `regs` completely untouched, preserving the + unconditional-retry behavior this kernel had before the flag + existed. */ + bool interruptedRetry = thread->sigInRetryableSyscall; + thread->sigInRetryableSyscall = false; + if (interruptedRetry && !(thread->sigHandlerFlags[signum] & sigFlagRestart)) { + regs->rip += 2; + regs->rax = (uint64)(int64)errInterrupted; + } + + /* Push the whole interrupted trapframe (now possibly rewritten + above) onto this thread's small context stack (the only copy of + it that exists anywhere -- unlike a real kernel this never pushes + one onto the user stack) so sigOnSigReturn can restore it byte + for byte, even if a DIFFERENT, unmasked signal nests another + handler on top before this one ever returns. */ + uint32 depth = thread->sigHandlerDepth; + thread->sigSavedRegs[depth] = *regs; + thread->sigSavedBlocked[depth] = thread->sigBlocked; + thread->sigHandlerDepth = depth + 1; + + /* sa_mask (sigHandlerMasks[signum], set by sigSetHandler) plus the + signal's own bit (self-blocked, like real Unix's implicit + default) are ADDED to whatever was already blocked -- a union, + not a replacement, so an outer handler's own blocked set stays + blocked for this nested one too. sigFlagNoDefer (signalnum.h) + opts out of exactly the self-block half of that union: a signal + registered with it does not gain its own bit here, so a second + raise of the SAME signum while this handler is still active + finds sigBlocked genuinely clear for it and nests, instead of + merely staying pending until this handler returns. Stripped of + sigKill/sigStop for the same reason every other mask operation in + this file is: neither ever consults sigBlocked at all, so this is + purely cosmetic, but leaving either bit set here would visibly + contradict "can never be blocked" for anything that inspects + sigBlocked. */ + uint64 newBlocked = thread->sigBlocked | thread->sigHandlerMasks[signum]; + if (!(thread->sigHandlerFlags[signum] & sigFlagNoDefer)) + newBlocked |= bit; + newBlocked &= ~(1ull << sigKill); + newBlocked &= ~(1ull << sigStop); + thread->sigBlocked = newBlocked; +