8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/PerProcessorScheduler.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Scheduler.h"
13#include "pedigree/kernel/process/SignalEvent.h"
14#include "pedigree/kernel/process/Thread.h"
15#include "pedigree/kernel/processor/Processor.h"
16#include "pedigree/kernel/utilities/ZombieQueue.h"
20#include "modules/subsys/posix/PosixProcess.h"
21#include "modules/subsys/posix/PosixSubsystem.h"
22#include "modules/subsys/posix/signal-syscalls.h"
23#include "modules/subsys/posix/system-syscalls.h"
27constexpr size_t HostedAttempts = 10000;
28constexpr int NormalExitCode = 0xA5;
30struct ExitStatusContext {
31 ExitStatusContext(
int code,
Subsystem::ExitCause cause) : code(code), cause(cause), entered(0) {}
38struct TransitionSeederContext {
39 explicit TransitionSeederContext(
Process* process)
40 : process(process), ready(0), go(0), done(0), resumes(0) {}
49struct SigchldVisibilityContext {
50 SigchldVisibilityContext()
57 handlerSawTerminated(0),
58 handlerSawOwnerOnStack(0),
74SigchldVisibilityContext* g_SigchldVisibilityContext =
nullptr;
76void hostedSigchldHandler(
size_t argument) {
77 SigchldVisibilityContext* context =
78 __atomic_load_n(&g_SigchldVisibilityContext, __ATOMIC_ACQUIRE);
83 const uint8_t* serializedEvent =
reinterpret_cast<const uint8_t*
>(argument);
84 if (!serializedEvent || serializedEvent[0] != SIGCHLD || !context->child) {
85 context->handlerFailures += 1;
86 context->handlerCalls += 1;
90 context->handlerSawTerminated = context->child->getState() == Process::Terminated ? 1 : 0;
91 context->handlerSawOwnerOnStack = context->child->isTerminationReapableForHostedTest() ? 0 : 1;
93 const int status = context->child->getExitStatus();
94 const int result = posix_waitpid(
static_cast<int>(context->childId),
nullptr, WNOHANG,
nullptr);
95 context->waitResult =
static_cast<size_t>(result);
96 context->waitStatus =
static_cast<size_t>(status);
97 context->handlerCalls += 1;
100int sigchldParentTarget(
void* parameter) {
101 SigchldVisibilityContext* context =
reinterpret_cast<SigchldVisibilityContext*
>(parameter);
102 context->parentReady += 1;
103 while (!context->handlerCalls && !context->releaseParent) {
109int resumeSuspendedProcess(
void* parameter) {
110 TransitionSeederContext* context =
reinterpret_cast<TransitionSeederContext*
>(parameter);
112 while (!context->go) {
116 while (!context->done) {
117 if (context->process->isSuspended()) {
118 context->process->resume();
119 context->resumes += 1;
127int deferredPosixExit(
void* parameter) {
128 ExitStatusContext* context =
reinterpret_cast<ExitStatusContext*
>(parameter);
129 context->entered += 1;
132 if (context->cause == Subsystem::ExitCause::Signal) {
133 pedigree_init_sigret();
135 subsystem->
sendSignal(current, context->code,
false);
146bool waitForTermination(
Process* process) {
147 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
148 if (process->isTerminationReapableForHostedTest()) {
157 int previousStatus,
bool seedTransientStatus) {
158 ExitStatusContext* context =
new ExitStatusContext(code, cause);
164 bool transientSeeded = !seedTransientStatus;
165 if (seedTransientStatus) {
166 TransitionSeederContext transitionContext(process);
167 Thread* resumer =
new Thread(kernelProcess, resumeSuspendedProcess, &transitionContext,
nullptr,
169 resumer->setName(
"hosted POSIX exit-status transition seeder");
170 if (resumer->
start()) {
171 for (
size_t attempt = 0; attempt < HostedAttempts && !transitionContext.ready; ++attempt) {
174 if (transitionContext.ready) {
175 transitionContext.go += 1;
178 transitionContext.done += 1;
179 transitionContext.go += 1;
180 transientSeeded = resumer->
joinForCompletion() && transitionContext.ready == 1 &&
181 transitionContext.resumes == 1;
187 Thread* thread =
new Thread(process, deferredPosixExit, context,
nullptr,
false,
true,
true);
188 thread->setName(
"hosted POSIX exit-status fixture");
190 const bool started = thread->
start();
197 const bool reapable = waitForTermination(process);
199 const bool normal = cause == Subsystem::ExitCause::Normal;
200 const int expectedStatus = normal ? ((code & 0xFF) << 8) : (code & 0x7F);
201 const bool statusValid =
202 status == expectedStatus && (normal ? (WIFEXITED(status) && WEXITSTATUS(status) == code)
203 : (WIFSIGNALED(status) && WTERMSIG(status) == code));
205 bool transientCleared =
false;
211 reapable && context->entered == 1 && transientSeeded && transientCleared && statusValid;
220bool sigchldWaitStatusVisible(
Process* kernelProcess) {
221 constexpr int ExitCode = 37;
222 SigchldVisibilityContext context;
226 parent->setSubsystem(parentSubsystem);
230 handler->
pEvent =
new SignalEvent(
reinterpret_cast<uintptr_t
>(&hostedSigchldHandler), SIGCHLD);
234 new Thread(parent, sigchldParentTarget, &context,
nullptr,
false,
true,
true);
235 parentThread->setName(
"hosted SIGCHLD wait-status target");
238 __atomic_store_n(&g_SigchldVisibilityContext, &context, __ATOMIC_RELEASE);
239 bool parentStarted = parentThread->
start();
240 for (
size_t attempt = 0; parentStarted && attempt < HostedAttempts && !context.parentReady;
244 if (!parentStarted || context.parentReady != 1) {
245 context.releaseParent += 1;
251 __atomic_store_n(&g_SigchldVisibilityContext,
static_cast<SigchldVisibilityContext*
>(
nullptr),
257 ExitStatusContext childExit(ExitCode, Subsystem::ExitCause::Normal);
261 new Thread(child, deferredPosixExit, &childExit,
nullptr,
false,
true,
true);
262 childThread->setName(
"hosted SIGCHLD wait-status child");
264 context.child = child;
265 context.childId = child->
getId();
269 const bool childStarted = childLeased && childThread->
start();
274 context.child =
nullptr;
275 context.releaseParent += 1;
277 __atomic_store_n(&g_SigchldVisibilityContext,
static_cast<SigchldVisibilityContext*
>(
nullptr),
283 for (
size_t attempt = 0; childStarted && attempt < HostedAttempts && !context.handlerCalls;
288 bool reapable =
false;
289 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
290 if (child->isTerminationReapableForHostedTest()) {
298 reapable = child->isTerminationReapableForHostedTest();
301 FATAL(
"SIGCHLD wait-status fixture retained an on-stack child");
304 const int waitStatus =
static_cast<int>(
static_cast<size_t>(context.waitStatus));
305 bool passed = context.handlerCalls == 1 && context.handlerFailures == 0 &&
306 context.handlerSawTerminated == 1 && context.handlerSawOwnerOnStack == 1 &&
307 context.waitResult == context.childId && WIFEXITED(waitStatus) &&
308 WEXITSTATUS(waitStatus) == ExitCode && reapable &&
313 if (!reaperPublished && child->getState() == Process::Terminated) {
315 if (child->getState() == Process::Terminated) {
321 rescueReaper.publish();
322 reaperPublished =
true;
324 if (!reaperPublished) {
325 FATAL(
"SIGCHLD wait-status fixture could not publish child destruction");
328 context.releaseParent += 1;
330 __atomic_store_n(&g_SigchldVisibilityContext,
static_cast<SigchldVisibilityContext*
>(
nullptr),
333 Process* childIdentity = child;
336 passed &= ZombieQueue::instance().
drain();
337 passed &= parentJoined;
341 NOTICE(
"HOSTED-SYSCALL-TEST: PASS sigchld-wait-status-visible");
347bool runHostedPosixExitStatusRegressions(
Process* kernelProcess) {
348 const bool normalPassed =
349 runExitStatusFixture(kernelProcess, NormalExitCode, Subsystem::ExitCause::Normal, 0x7F,
true);
350 const bool signalPassed =
351 runExitStatusFixture(kernelProcess, SIGUSR1, Subsystem::ExitCause::Signal, 0xFF,
false);
352 const bool sigchldPassed = sigchldWaitStatusVisible(kernelProcess);
354 if (!normalPassed || !signalPassed || !sigchldPassed) {
356 "HOSTED-SYSCALL-TEST: FAIL posix-exit-status: "
357 "termination status or SIGCHLD publication was not wait-compatible");
361 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-normal-exit-status");
362 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-signal-exit-status");
void checkEventState(uintptr_t userStack)
virtual void sendSignal(Thread *pThread, int signal, bool yield=true, bool processDirected=false)
void setSignalHandler(size_t sig, SignalHandler *handler)
void setExitStatus(int code)
@ Reaped
Terminal wait status is visible; the owner may still be on-stack.
WaitQueue::Guard acquireChildStateWait()
bool takePendingChildTransition(bool includeStopped, bool includeContinued, ChildTransition &transition)
void suspend(int stopSignal=0)
ReaperClaim tryClaimReaper()
static ProcessorInformation & information()
static Scheduler & instance()
MUST_USE_RESULT bool acquireProcess(ProcessLease &lease, size_t n)
void waitUntilProcessRemoved(Process *expected)
@ Exit
Exit the owning process at the next safe boundary.
static void threadExited() NORETURN
UnwindType getUnwindState()
Process * getParent() const
class PerProcessorScheduler * getScheduler() const
void deferProcessExit(int code)
int type
Type - 0 = normal, 1 = SIG_DFL, 2 = SIG_IGN.
SignalEvent * pEvent
Event for the signal handler.