The Pedigree Project 0.1
wait-state.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/Log.h"
3#include "pedigree/kernel/process/Scheduler.h"
4#include "pedigree/kernel/process/Thread.h"
5#include "pedigree/kernel/processor/Processor.h"
6#include "pedigree/kernel/processor/ProcessorInformation.h"
7#include "pedigree/kernel/syscallError.h"
8
9#include "PosixProcess.h"
10#include "PosixSubsystem.h"
11#include "wait-state.h"
12
13namespace PosixWait {
14namespace {
15bool eligible(PosixProcess* parent, Process* child, const Request& request) {
16 if (!child || child == parent || child->getType() != Process::Posix ||
17 child->getParent() != parent || child->getState() == Process::Reaped) {
18 return false;
19 }
20 if (request.selector == Selector::All)
21 return true;
22 if (request.selector == Selector::Pid)
23 return child->getUserspaceId(parent->pidNamespace().get()) == static_cast<size_t>(request.id);
24 size_t group = 0;
25 return request.id >= 0 &&
26 static_cast<PosixProcess*>(child)->getProcessGroupId(group,
27 parent->pidNamespace().get()) &&
28 group == static_cast<size_t>(request.id);
29}
30
31void terminalStatus(int encoded, Report& report) {
32 if (!(encoded & 0x7F)) {
33 report.cause = Exit;
34 report.status = (encoded >> 8) & 0xFF;
35 } else {
36 report.cause = (encoded & 0x80) ? Dumped : Killed;
37 report.status = encoded & 0x7F;
38 }
39}
40
41void snapshotTimes(Process* child, Report& report) {
42 report.userNanoseconds = child->getUserTime() + child->getReapedChildrenUserTime();
43 report.kernelNanoseconds = child->getKernelTime() + child->getReapedChildrenKernelTime();
44}
45} // namespace
46
47int collect(const Request& request, Report& report) {
48 report = Report();
49 Thread* thread = Processor::information().getCurrentThread();
51 struct InterruptionScope {
52 Thread* thread;
53 ~InterruptionScope() {
54 // Preserve an enclosing temporary-mask wait's interruption ownership.
56 }
57 } interruptionScope{thread};
58 auto* parent = static_cast<PosixProcess*>(thread->getParent());
59 WaitQueue::WakeReason previousWake = WaitQueue::WakeReason::Signalled;
60
61 while (true) {
64 Process* terminalChild = nullptr;
65 Report selected;
66 bool hasResult = false;
67 {
68 // This guard serializes competing reapers and joins the predicate to
69 // sleep. No child pointer survives a sleep on this parent queue.
70 auto guard = parent->acquireChildStateWait();
71 bool hasEligibleChild = false;
72 for (size_t i = 0;; ++i) {
73 Process* child = Scheduler::instance().getChildProcess(parent, i);
74 if (!child)
75 break;
76 if (!eligible(parent, child, request))
77 continue;
78
79 if (child->getState() == Process::Terminated) {
80 // An exited child cannot produce a later stop or continue event.
81 if (!(request.events & Exited))
82 continue;
83 if (request.noWait) {
84 // Acquire into an empty lease while enumeration is protected;
85 // dropping the last lease can wake a destructor and must occur
86 // outside the parent guard.
87 if (!Scheduler::instance().acquireProcess(observer, child))
88 continue;
89 } else {
90 child->reap();
91 reaper = child->tryClaimReaper();
92 if (!reaper) {
93 FATAL("child wait lost sole reaper ownership for pid " << Dec << child->getId());
94 }
95 }
96 terminalStatus(child->getExitStatus(), selected);
97 terminalChild = child;
98 hasResult = true;
99 } else {
100 hasEligibleChild = true;
101 auto* childSubsystem = static_cast<PosixSubsystem*>(child->getSubsystem());
102 if (childSubsystem &&
103 childSubsystem->traceContext().selectStop(
104 parent->getId(), request.traceStops || (request.events & Stopped),
105 request.events & Continued, !request.noWait, selected)) {
106 selected.pid =
107 static_cast<int32_t>(child->getUserspaceId(parent->pidNamespace().get()));
108 hasResult = true;
109 break;
110 }
111 Process::ChildTransition transition;
112 if (!child->selectPendingChildTransition(request.events & Stopped,
113 request.events & Continued, !request.noWait,
114 transition)) {
115 continue;
116 }
117 if (transition.kind == Process::ChildTransitionKind::Stopped) {
118 selected.cause = Stop;
119 selected.status = transition.stopSignal;
120 } else {
121 selected.cause = Continue;
122 selected.status = 18; // Linux SIGCONT, independent of hosted libc.
123 }
124 snapshotTimes(child, selected);
125 hasResult = true;
126 }
127 selected.pid = static_cast<int32_t>(child->getUserspaceId(parent->pidNamespace().get()));
128 selected.uid = static_cast<PosixProcess*>(child)->snapshotCredentials().ruid;
129 break;
130 }
131
132 if (!hasResult) {
133 if (!hasEligibleChild) {
134 SYSCALL_ERROR(NoChildren);
135 return -1;
136 }
137 if (request.noHang) {
138 thread->setErrno(0);
139 return 0;
140 }
141 // A reportable child status wins over a caught signal at this scan.
142 if (thread->getInterruptionReason() == Thread::InterruptedBySignal ||
143 previousWake == WaitQueue::WakeReason::Unwinding ||
144 previousWake == WaitQueue::WakeReason::Terminating) {
145 SYSCALL_ERROR(Interrupted);
146 return -1;
147 }
148 previousWake = guard.wait(WaitQueue::Channel(), Thread::ProcessWait,
149 reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
150 }
151 }
152
153 if (!hasResult)
154 continue;
155 if (terminalChild) {
156 // Terminated precedes the exiting thread's final scheduler accounting.
157 // Either ownership form pins the Process through this off-stack barrier.
158 if (!terminalChild->waitUntilTerminationReapable()) {
159 FATAL("child wait attempted to observe its own terminating process");
160 }
161 if (reaper) {
162 parent->accountReapedChild(terminalChild, selected.userNanoseconds,
163 selected.kernelNanoseconds);
164 reaper.publish();
165 } else {
166 snapshotTimes(terminalChild, selected);
167 observer.reset();
168 }
169 }
170 report = selected;
171 thread->setErrno(0);
172 return 1;
173 }
174}
175} // namespace PosixWait
size_t getUserspaceId() const
Definition Process.h:504
@ Reaped
Terminal wait status is visible; the owner may still be on-stack.
Definition Process.h:308
size_t getId()
Definition Process.h:499
int getExitStatus()
Definition Process.h:560
Process * getParent()
Definition Process.h:620
void accountReapedChild(const Process *child, Time::Timestamp &user, Time::Timestamp &kernel)
Definition Process.cc:857
WaitQueue::Guard acquireChildStateWait()
Definition Process.h:736
void reap()
Definition Process.cc:1642
bool selectPendingChildTransition(bool includeStopped, bool includeContinued, bool consume, ChildTransition &transition)
Definition Process.cc:2215
bool waitUntilTerminationReapable()
Definition Process.cc:2270
Time::Timestamp getUserTime() const
Definition Process.h:829
ReaperClaim tryClaimReaper()
Definition Process.cc:2001
static ProcessorInformation & information()
Process * getChildProcess(Process *pParent, size_t n)
Definition Scheduler.cc:485
static Scheduler & instance()
Definition Scheduler.h:96
T * get() const
void setErrno(size_t err)
Definition Thread.h:482
bool retainTemporarySignalWaitInterruptionOrClear()
Definition Thread.cc:2156
Process * getParent() const
Definition Thread.h:340
@ Dec
Definition Log.h:126