Files
nt-lite/code/user/personalities/posix/posix_signal.c
T

478 lines
18 KiB
C

// POSIX signal policy and user-mode dispatch over generic thread APC delivery.
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <ntlite/exception.h>
#include <ntlite/syscall.h>
#include "posix_internal.h"
// Returns one signal bit after validating its public signal number.
static uint64_t PosixSignalBit(int signalNumber) {
return NtlPosixSignalValid(signalNumber) ? NTL_POSIX_SIGNAL_BIT(signalNumber) : 0;
}
// Converts the process-local action snapshot into the server's routing view.
static uint32_t PosixSignalDisposition(const POSIX_SIGNAL_ACTION_STATE* action) {
uintptr_t handler = action ? action->Handler : 0;
if (handler == (uintptr_t)SIG_IGN) return NTL_POSIX_SIGNAL_DISPOSITION_IGNORE;
if (handler == (uintptr_t)SIG_DFL) return NTL_POSIX_SIGNAL_DISPOSITION_DEFAULT;
return NTL_POSIX_SIGNAL_DISPOSITION_CAUGHT;
}
// Sends one signal-control request through the authoritative POSIX server.
static KSTATUS PosixSignalCallServer(NTL_POSIX_REQUEST* request, NTL_POSIX_REPLY* reply) {
if (!request || !reply) return NTLSTATUS_INVALID_PARAMETER;
request->Size = sizeof(*request);
request->Version = NTL_POSIX_PROTOCOL_VERSION;
request->Flags = NTL_POSIX_REQUEST_FLAG_NONE;
KSTATUS status = PosixRuntimeCallServer(request, reply);
if (!KSUCCESS(status)) return status;
return reply->Status;
}
// Applies one caught/default/ignored signal on the current POSIX thread.
static bool PosixSignalDeliver(POSIX_THREAD_STATE* thread, int signalNumber) {
if (!thread || !thread->NativeProcess || !NtlPosixSignalValid(signalNumber)) return false;
uint64_t bit = PosixSignalBit(signalNumber);
uint64_t effectiveMask = thread->SignalBlocked | thread->SignalHandlerBlocked;
if (signalNumber != SIGKILL && (effectiveMask & bit) != 0) {
thread->SignalPending |= bit;
return false;
}
thread->SignalPending &= ~bit;
POSIX_SIGNAL_ACTION_STATE* state = &thread->LogicalProcess->SignalActions[signalNumber];
uintptr_t handlerValue = state->Handler;
if (signalNumber == SIGKILL) handlerValue = (uintptr_t)SIG_DFL;
if (handlerValue == (uintptr_t)SIG_IGN ||
(handlerValue == (uintptr_t)SIG_DFL && signalNumber == SIGCHLD))
return false;
if (handlerValue == (uintptr_t)SIG_DFL) {
_exit(128 + signalNumber);
return true;
}
uint64_t previousHandlerMask = thread->SignalHandlerBlocked;
thread->SignalHandlerBlocked = (previousHandlerMask | state->Mask | bit) &
NTL_POSIX_SIGNAL_VALID_MASK &
~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK;
++thread->SignalDeliveryCount;
if (thread->SignalWaitEvent != SYS_HANDLE_INVALID)
(void)SysSignalEvent(thread->SignalWaitEvent);
((sighandler_t)handlerValue)(signalNumber);
thread->SignalHandlerBlocked = previousHandlerMask;
PosixSignalDispatchLocalPending(thread);
return true;
}
// Maps one personality-neutral native exception class to a POSIX signal.
static int PosixSignalForNativeException(uint32_t code) {
switch (code) {
case SYS_EXCEPTION_CODE_ACCESS_VIOLATION:
case SYS_EXCEPTION_CODE_DATATYPE_MISALIGNMENT:
case SYS_EXCEPTION_CODE_STACK_OVERFLOW:
case SYS_EXCEPTION_CODE_GUARD_PAGE_VIOLATION:
return SIGSEGV;
case SYS_EXCEPTION_CODE_ILLEGAL_INSTRUCTION:
case SYS_EXCEPTION_CODE_PRIVILEGED_INSTRUCTION:
return SIGILL;
case SYS_EXCEPTION_CODE_INT_DIVIDE_BY_ZERO:
case SYS_EXCEPTION_CODE_INT_OVERFLOW:
case SYS_EXCEPTION_CODE_FLT_DENORMAL_OPERAND:
case SYS_EXCEPTION_CODE_FLT_DIVIDE_BY_ZERO:
case SYS_EXCEPTION_CODE_FLT_INEXACT_RESULT:
case SYS_EXCEPTION_CODE_FLT_INVALID_OPERATION:
case SYS_EXCEPTION_CODE_FLT_OVERFLOW:
case SYS_EXCEPTION_CODE_FLT_STACK_CHECK:
case SYS_EXCEPTION_CODE_FLT_UNDERFLOW:
return SIGFPE;
default:
return 0;
}
}
// Translates selected native faults at the POSIX personality boundary.
static uint32_t PosixSignalNativeException(SYS_EXCEPTION_RECORD* record,
SYS_USER_CONTEXT* context,
void* userContext) {
(void)context;
(void)userContext;
if (!record) return NTL_EXCEPTION_CONTINUE_SEARCH;
int signalNumber = PosixSignalForNativeException(record->Code);
if (!signalNumber) return NTL_EXCEPTION_CONTINUE_SEARCH;
POSIX_THREAD_STATE* thread = PosixRuntimeThreadState();
if (!thread) return NTL_EXCEPTION_CONTINUE_SEARCH;
uint64_t bit = PosixSignalBit(signalNumber);
if ((thread->SignalBlocked | thread->SignalHandlerBlocked) & bit)
return NTL_EXCEPTION_CONTINUE_SEARCH;
POSIX_SIGNAL_ACTION_STATE* state = &thread->LogicalProcess->SignalActions[signalNumber];
if (state->Handler == (uintptr_t)SIG_IGN)
return NTL_EXCEPTION_CONTINUE_SEARCH;
return PosixSignalDeliver(thread, signalNumber)
? NTL_EXCEPTION_CONTINUE_EXECUTION
: NTL_EXCEPTION_CONTINUE_SEARCH;
}
// Installs the native-exception adapter exactly once for one POSIX image.
KSTATUS PosixSignalInitializeExceptionTranslation(POSIX_PROCESS_STATE* process) {
if (!process) return NTLSTATUS_INVALID_PARAMETER;
if (process->ExceptionHandlerHandle) return NTLSTATUS_SUCCESS;
void* handle = NtlAddExceptionHandler(1, PosixSignalNativeException, (void*)0);
if (!handle) return NTLSTATUS_LIMIT_REACHED;
process->ExceptionHandlerHandle = handle;
return NTLSTATUS_SUCCESS;
}
// Delivers one POSIX signal from the generic user-APC substrate.
void PosixSignalApc(void* signalValue, void* routeValue, void* reserved) {
(void)routeValue;
(void)reserved;
int signalNumber = (int)(uintptr_t)signalValue;
POSIX_THREAD_STATE* thread = PosixRuntimeThreadState();
if (!thread || !NtlPosixSignalValid(signalNumber)) return;
PosixSignalDeliver(thread, signalNumber);
}
// Dispatches thread-pending signals made deliverable by a mask change.
void PosixSignalDispatchLocalPending(POSIX_THREAD_STATE* thread) {
if (!thread) return;
for (;;) {
uint64_t deliverable = thread->SignalPending &
~(thread->SignalBlocked | thread->SignalHandlerBlocked) &
NTL_POSIX_SIGNAL_VALID_MASK;
deliverable |= thread->SignalPending & NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK;
if (deliverable == 0) return;
int signalNumber = NTL_POSIX_SIGNAL_HUP;
while (signalNumber <= NTL_POSIX_SIGNAL_MAX &&
(deliverable & PosixSignalBit(signalNumber)) == 0)
++signalNumber;
if (signalNumber > NTL_POSIX_SIGNAL_MAX) return;
PosixSignalDeliver(thread, signalNumber);
}
}
// Initializes an empty signal set.
__declspec(dllexport) int sigemptyset(sigset_t* set) {
if (!set) { errno = EINVAL; return -1; }
*set = 0;
return 0;
}
// Initializes a signal set containing every supported signal.
__declspec(dllexport) int sigfillset(sigset_t* set) {
if (!set) { errno = EINVAL; return -1; }
*set = NTL_POSIX_SIGNAL_VALID_MASK;
return 0;
}
// Adds one signal to a signal set.
__declspec(dllexport) int sigaddset(sigset_t* set, int signalNumber) {
uint64_t bit = PosixSignalBit(signalNumber);
if (!set || bit == 0) { errno = EINVAL; return -1; }
*set |= bit;
return 0;
}
// Removes one signal from a signal set.
__declspec(dllexport) int sigdelset(sigset_t* set, int signalNumber) {
uint64_t bit = PosixSignalBit(signalNumber);
if (!set || bit == 0) { errno = EINVAL; return -1; }
*set &= ~bit;
return 0;
}
// Tests whether one signal belongs to a signal set.
__declspec(dllexport) int sigismember(const sigset_t* set, int signalNumber) {
uint64_t bit = PosixSignalBit(signalNumber);
if (!set || bit == 0) { errno = EINVAL; return -1; }
return (*set & bit) != 0 ? 1 : 0;
}
// Installs or queries one logical-process-wide POSIX signal action.
__declspec(dllexport) int sigaction(int signalNumber,
const struct sigaction* action,
struct sigaction* oldAction) {
if (!NtlPosixSignalValid(signalNumber)) { errno = EINVAL; return -1; }
POSIX_LOGICAL_PROCESS_STATE* process = PosixRuntimeLogicalProcessState();
if (!process) { errno = EIO; return -1; }
POSIX_SIGNAL_ACTION_STATE* state = &process->SignalActions[signalNumber];
if (oldAction) {
oldAction->sa_handler = (sighandler_t)state->Handler;
oldAction->sa_mask = state->Mask;
oldAction->sa_flags = (int)state->Flags;
}
if (!action) return 0;
if (action->sa_flags != 0 || (action->sa_mask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0 ||
action->sa_handler == SIG_ERR ||
(signalNumber == SIGKILL && action->sa_handler != SIG_DFL)) {
errno = EINVAL;
return -1;
}
POSIX_SIGNAL_ACTION_STATE previous = *state;
state->Handler = (uintptr_t)action->sa_handler;
state->Mask = action->sa_mask & ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK;
state->Flags = (uint32_t)action->sa_flags;
NTL_POSIX_REQUEST request;
memset(&request, 0, sizeof(request));
request.Operation = NTL_POSIX_OP_SET_SIGNAL_DISPOSITION;
request.SignalNumber = signalNumber;
request.SignalDisposition = PosixSignalDisposition(state);
NTL_POSIX_REPLY reply;
KSTATUS status = PosixSignalCallServer(&request, &reply);
if (!KSUCCESS(status)) {
*state = previous;
errno = PosixStatusToErrno(status);
return -1;
}
return 0;
}
// Changes the calling thread's persistent POSIX signal mask.
__declspec(dllexport) int sigprocmask(int how, const sigset_t* set, sigset_t* oldSet) {
POSIX_THREAD_STATE* thread = PosixRuntimeThreadState();
if (!thread) { errno = EIO; return -1; }
uint64_t previous = thread->SignalBlocked;
if (oldSet) *oldSet = previous;
if (!set) return 0;
if ((*set & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0 ||
(how != SIG_BLOCK && how != SIG_UNBLOCK && how != SIG_SETMASK)) {
errno = EINVAL;
return -1;
}
uint64_t next = previous;
if (how == SIG_BLOCK) next |= *set;
else if (how == SIG_UNBLOCK) next &= ~*set;
else next = *set;
next &= NTL_POSIX_SIGNAL_VALID_MASK & ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK;
thread->SignalBlocked = next;
NTL_POSIX_REQUEST request;
memset(&request, 0, sizeof(request));
request.Operation = NTL_POSIX_OP_UPDATE_SIGNAL_MASK;
request.SignalMask = next;
NTL_POSIX_REPLY reply;
KSTATUS status = PosixSignalCallServer(&request, &reply);
if (!KSUCCESS(status)) {
thread->SignalBlocked = previous;
errno = PosixStatusToErrno(status);
return -1;
}
PosixSignalDispatchLocalPending(thread);
return 0;
}
// Returns the union of calling-thread and process-pending signals.
__declspec(dllexport) int sigpending(sigset_t* set) {
if (!set) { errno = EINVAL; return -1; }
POSIX_THREAD_STATE* thread = PosixRuntimeThreadState();
if (!thread) { errno = EIO; return -1; }
NTL_POSIX_REQUEST request;
memset(&request, 0, sizeof(request));
request.Operation = NTL_POSIX_OP_GET_PENDING_SIGNALS;
NTL_POSIX_REPLY reply;
KSTATUS status = PosixSignalCallServer(&request, &reply);
if (!KSUCCESS(status)) { errno = PosixStatusToErrno(status); return -1; }
*set = (thread->SignalPending | reply.PendingSignals) & NTL_POSIX_SIGNAL_VALID_MASK;
return 0;
}
// Atomically substitutes a temporary mask for the duration of one signal wait.
__declspec(dllexport) int sigsuspend(const sigset_t* set) {
if (!set || (*set & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) {
errno = EINVAL;
return -1;
}
POSIX_THREAD_STATE* thread = PosixRuntimeThreadState();
if (!thread) { errno = EIO; return -1; }
SYS_HANDLE event = SYS_HANDLE_INVALID;
KSTATUS status = SysCreateEvent(0u, 0u, &event);
if (!KSUCCESS(status)) { errno = PosixStatusToErrno(status); return -1; }
SYS_HANDLE previousEvent = thread->SignalWaitEvent;
uint64_t deliveryCount = thread->SignalDeliveryCount;
thread->SignalWaitEvent = event;
sigset_t previousMask = 0;
if (sigprocmask(SIG_SETMASK, set, &previousMask) != 0) {
thread->SignalWaitEvent = previousEvent;
(void)SysCloseHandle(event);
return -1;
}
for (;;) {
if (thread->SignalDeliveryCount != deliveryCount) {
status = NTLSTATUS_SUCCESS;
break;
}
status = SysWait(event, SYS_WAIT_FLAG_ALERTABLE);
if (status == NTLSTATUS_SUCCESS && thread->SignalDeliveryCount != deliveryCount)
break;
if (status == NTLSTATUS_USER_APC || status == NTLSTATUS_KERNEL_APC ||
status == NTLSTATUS_ALERTED)
continue;
if (!KSUCCESS(status)) break;
}
thread->SignalWaitEvent = previousEvent;
int restore = sigprocmask(SIG_SETMASK, &previousMask, (sigset_t*)0);
(void)SysCloseHandle(event);
if (restore != 0) return -1;
if (!KSUCCESS(status)) {
errno = PosixStatusToErrno(status);
return -1;
}
errno = EINTR;
return -1;
}
// Sends one signal through stable POSIX process or process-group identity.
__declspec(dllexport) int kill(pid_t pid, int signalNumber) {
if (signalNumber < 0 || signalNumber > NTL_POSIX_SIGNAL_MAX) {
errno = EINVAL;
return -1;
}
NTL_POSIX_REQUEST request;
memset(&request, 0, sizeof(request));
request.Operation = NTL_POSIX_OP_SEND_SIGNAL;
request.SignalTargetPid = (int32_t)pid;
request.SignalNumber = signalNumber;
NTL_POSIX_REPLY reply;
KSTATUS status = PosixSignalCallServer(&request, &reply);
if (!KSUCCESS(status)) {
if (status == NTLSTATUS_NOT_FOUND) errno = ESRCH;
else if (status == NTLSTATUS_ACCESS_DENIED) errno = EPERM;
else errno = PosixStatusToErrno(status);
return -1;
}
return 0;
}
// Replaces the process alarm and returns the rounded-up previous remaining interval.
__declspec(dllexport) unsigned int alarm(unsigned int seconds) {
NTL_POSIX_REQUEST request;
memset(&request, 0, sizeof(request));
request.Operation = NTL_POSIX_OP_SET_ALARM;
request.AlarmSeconds = seconds;
NTL_POSIX_REPLY reply;
KSTATUS status = PosixSignalCallServer(&request, &reply);
if (!KSUCCESS(status)) {
errno = PosixStatusToErrno(status);
return 0;
}
return reply.AlarmRemainingSeconds;
}
// Waits until one caught signal runs.
__declspec(dllexport) int pause(void) {
sigset_t mask = 0;
if (sigprocmask(SIG_SETMASK, (const sigset_t*)0, &mask) != 0) return -1;
return sigsuspend(&mask);
}
// Sleeps on a native waitable timer and reports whole seconds left after a caught signal.
__declspec(dllexport) unsigned int sleep(unsigned int seconds) {
if (seconds == 0) {
(void)SysYield();
return 0;
}
POSIX_THREAD_STATE* thread = PosixRuntimeThreadState();
if (!thread) return seconds;
SYS_HANDLE timer = SYS_HANDLE_INVALID;
SYS_HANDLE signalEvent = SYS_HANDLE_INVALID;
KSTATUS status = SysCreateTimer(&timer);
if (!KSUCCESS(status)) return seconds;
status = SysCreateEvent(0u, 0u, &signalEvent);
if (!KSUCCESS(status)) { (void)SysCloseHandle(timer); return seconds; }
uint64_t frequency = 0;
status = SysGetTickFrequency(&frequency);
if (!KSUCCESS(status) || frequency == 0) {
(void)SysCloseHandle(signalEvent);
(void)SysCloseHandle(timer);
return seconds;
}
uint64_t start = SysGetTick();
SYS_SET_TIMER_REQUEST setTimer;
memset(&setTimer, 0, sizeof(setTimer));
setTimer.Size = sizeof(setTimer);
setTimer.TimerHandle = timer;
setTimer.Flags = SYS_TIMER_FLAG_NONE;
setTimer.DueTimeMilliseconds = (uint64_t)seconds * 1000u;
status = SysSetTimer(&setTimer);
if (!KSUCCESS(status)) {
(void)SysCloseHandle(signalEvent);
(void)SysCloseHandle(timer);
return seconds;
}
SYS_HANDLE previousEvent = thread->SignalWaitEvent;
uint64_t deliveryCount = thread->SignalDeliveryCount;
thread->SignalWaitEvent = signalEvent;
SYS_HANDLE waits[2] = { timer, signalEvent };
uint32_t selected = UINT32_MAX;
SYS_WAIT_MANY_REQUEST wait;
memset(&wait, 0, sizeof(wait));
wait.Size = sizeof(wait);
wait.Flags = SYS_WAIT_FLAG_ALERTABLE;
wait.Handles = waits;
wait.Count = 2u;
wait.SignaledIndexOut = &selected;
wait.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE;
for (;;) {
selected = UINT32_MAX;
status = SysWaitMany(&wait);
if (status == NTLSTATUS_SUCCESS && selected == 0u) break;
if (thread->SignalDeliveryCount != deliveryCount) break;
if (status == NTLSTATUS_SUCCESS || status == NTLSTATUS_USER_APC ||
status == NTLSTATUS_KERNEL_APC || status == NTLSTATUS_ALERTED)
continue;
break;
}
thread->SignalWaitEvent = previousEvent;
uint64_t elapsedTicks = SysGetTick() - start;
uint64_t elapsedMs = SysTicksToMilliseconds(elapsedTicks, frequency);
uint64_t totalMs = (uint64_t)seconds * 1000u;
uint64_t remainingMs = elapsedMs < totalMs ? totalMs - elapsedMs : 0;
unsigned int remaining = (unsigned int)((remainingMs + 999u) / 1000u);
(void)SysCancelTimer(timer);
(void)SysCloseHandle(signalEvent);
(void)SysCloseHandle(timer);
if (status == NTLSTATUS_SUCCESS && selected == 0u) return 0;
if (thread->SignalDeliveryCount != deliveryCount) return remaining;
return remaining ? remaining : seconds;
}
// Installs the historical one-argument signal disposition API.
__declspec(dllexport) sighandler_t signal(int signalNumber, sighandler_t handler) {
struct sigaction action;
struct sigaction previous;
action.sa_handler = handler;
action.sa_mask = 0;
action.sa_flags = 0;
if (sigaction(signalNumber, &action, &previous) != 0) return SIG_ERR;
return previous.sa_handler;
}
// Raises one signal on the calling POSIX thread.
__declspec(dllexport) int raise(int signalNumber) {
if (!NtlPosixSignalValid(signalNumber)) { errno = EINVAL; return -1; }
POSIX_THREAD_STATE* thread = PosixRuntimeThreadState();
if (!thread) { errno = EIO; return -1; }
PosixSignalDeliver(thread, signalNumber);
return 0;
}