The Pedigree Project 0.1
child-resource-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/errors.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Scheduler.h"
13#include "pedigree/kernel/process/Thread.h"
14#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
15#include "pedigree/kernel/processor/Processor.h"
16#include "pedigree/kernel/utilities/ZombieQueue.h"
17#include "pedigree/kernel/utilities/lib.h"
18
19#include <signal.h>
20
21#include "modules/subsys/posix/PosixProcess.h"
22#include "modules/subsys/posix/PosixSubsystem.h"
23#include "modules/subsys/posix/linux-resource-abi.h"
24#include "modules/subsys/posix/system-syscalls.h"
26#include <sys/resource.h>
27#include <sys/times.h>
28#include <sys/wait.h>
29
30namespace {
31constexpr size_t HostedAttempts = 10000;
32constexpr int PreservedErrno = 173;
33
34struct SeededExitContext {
35 SeededExitContext(Time::Timestamp user, Time::Timestamp kernel, bool gated)
36 : user(user), kernel(kernel), ready(0), release(gated ? 0 : 1) {}
37
38 Time::Timestamp user;
39 Time::Timestamp kernel;
40 Atomic<size_t> ready;
41 Atomic<size_t> release;
42};
43
44struct DescendantExitContext {
45 DescendantExitContext(Time::Timestamp childUser, Time::Timestamp childKernel,
46 Time::Timestamp descendantUser, Time::Timestamp descendantKernel)
47 : childUser(childUser),
48 childKernel(childKernel),
49 descendant(descendantUser, descendantKernel, false),
50 ready(0),
51 release(0),
52 descendantReaped(false) {}
53
54 Time::Timestamp childUser;
55 Time::Timestamp childKernel;
56 SeededExitContext descendant;
57 Atomic<size_t> ready;
58 Atomic<size_t> release;
59 bool descendantReaped;
60};
61
62struct ConcurrentWaitContext {
63 explicit ConcurrentWaitContext(int pid)
64 : pid(pid), ready(0), go(0), returned(0), result(0), error(0) {}
65
66 int pid;
67 Atomic<size_t> ready;
69 Atomic<size_t> returned;
70 int result;
71 int error;
72};
73
74struct ChildResourceContext {
75 explicit ChildResourceContext(PosixProcess* process)
76 : process(process), passed(false), returned(0) {}
77
78 PosixProcess* process;
79 bool passed;
80 Atomic<size_t> returned;
81};
82
83bool waitForValue(const Atomic<size_t>& value) {
84 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
85 if (value) {
86 return true;
87 }
89 }
90 return false;
91}
92
93bool waitForTermination(Process* process) {
94 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
95 if (process->isTerminationReapableForHostedTest()) {
96 return true;
97 }
99 }
100 return false;
101}
102
103int seededExit(void* parameter) {
104 SeededExitContext* context = reinterpret_cast<SeededExitContext*>(parameter);
105 context->ready += 1;
106 while (!context->release) {
108 }
109
110 Thread* current = Processor::information().getCurrentThread();
111 current->publishTimeAccountingForHostedTest(context->user, context->kernel);
112 current->deferProcessExit(0);
114}
115
116int reapDescendantAndExit(void* parameter) {
117 DescendantExitContext* context = reinterpret_cast<DescendantExitContext*>(parameter);
118 context->ready += 1;
119 while (!context->release) {
121 }
122
123 Process* process = Processor::information().getCurrentThread()->getParent();
124 PosixProcess* descendant = new PosixProcess(process);
125 descendant->setSubsystem(new PosixSubsystem);
126 Thread* descendantThread =
127 new Thread(descendant, seededExit, &context->descendant, nullptr, false, true, true);
128 descendantThread->setName("hosted resource descendant");
129 descendant->publish();
130 const int descendantPid = static_cast<int>(descendant->getId());
131 if (descendantThread->start()) {
132 context->descendantReaped = posix_waitpid(descendantPid, nullptr, 0, nullptr) == descendantPid;
133 } else {
134 delete descendantThread;
135 delete descendant;
136 }
137
138 Thread* current = Processor::information().getCurrentThread();
139 current->publishTimeAccountingForHostedTest(context->childUser, context->childKernel);
140 current->deferProcessExit(context->descendantReaped ? 0 : 1);
142}
143
144int concurrentWait(void* parameter) {
145 ConcurrentWaitContext* context = reinterpret_cast<ConcurrentWaitContext*>(parameter);
146 Thread* current = Processor::information().getCurrentThread();
147 context->ready += 1;
148 while (!context->go) {
150 }
151
152 current->setErrno(PreservedErrno);
153 context->result = posix_waitpid(context->pid, nullptr, 0, nullptr);
154 context->error = current->getErrno();
155 context->returned += 1;
156 return 0;
157}
158
159Time::Timestamp linuxUserTime(const LinuxRusage64& usage) {
160 return static_cast<Time::Timestamp>(usage.userSeconds) * Time::Multiplier::Second +
161 static_cast<Time::Timestamp>(usage.userMicroseconds) * Time::Multiplier::Microsecond;
162}
163
164Time::Timestamp linuxKernelTime(const LinuxRusage64& usage) {
165 return static_cast<Time::Timestamp>(usage.systemSeconds) * Time::Multiplier::Second +
166 static_cast<Time::Timestamp>(usage.systemMicroseconds) * Time::Multiplier::Microsecond;
167}
168
169bool transitiveChildAccounting(PosixProcess* parent, int* status, struct tms* processTimes,
170 struct rusage* nativeUsage, LinuxRusage64* linuxUsage,
171 uint8_t* linuxCanary) {
172 constexpr Time::Timestamp clockTick = Time::Multiplier::Second / 100;
173 constexpr Time::Timestamp childUser = 29 * clockTick;
174 constexpr Time::Timestamp childKernel = 17 * clockTick;
175 constexpr Time::Timestamp descendantUser = 43 * clockTick;
176 constexpr Time::Timestamp descendantKernel = 23 * clockTick;
177
178 const Time::Timestamp userBefore = parent->getReapedChildrenUserTime();
179 const Time::Timestamp kernelBefore = parent->getReapedChildrenKernelTime();
180 DescendantExitContext context(childUser, childKernel, descendantUser, descendantKernel);
181 PosixProcess* child = new PosixProcess(parent);
182 child->setSubsystem(new PosixSubsystem);
183 Thread* childThread =
184 new Thread(child, reapDescendantAndExit, &context, nullptr, false, true, true);
185 childThread->setName("hosted transitive resource child");
186 child->publish();
187 const int childPid = static_cast<int>(child->getId());
188 if (!childThread->start()) {
189 delete childThread;
190 delete child;
191 return false;
192 }
193
194 bool passed = waitForValue(context.ready);
195 uint8_t untouched[sizeof(LinuxRusage64) + 32];
196 *status = 0x5A5A5A5A;
197 ByteSet(linuxUsage, 0xA5, sizeof(LinuxRusage64) + 32);
198 MemoryCopy(untouched, linuxUsage, sizeof(untouched));
199 Thread* current = Processor::information().getCurrentThread();
200 current->setErrno(PreservedErrno);
201 passed &= posix_waitpid(childPid, status, WNOHANG, linuxUsage) == 0 && *status == 0x5A5A5A5A &&
202 !MemoryCompare(linuxUsage, untouched, sizeof(untouched)) &&
203 current->getErrno() == PreservedErrno;
204
205 context.release += 1;
206 ByteSet(linuxUsage, 0xA5, sizeof(LinuxRusage64) + 32);
207 current->setErrno(PreservedErrno);
208 const int waited = posix_waitpid(childPid, status, 0, linuxUsage);
209 passed &= waited == childPid && WIFEXITED(*status) && !WEXITSTATUS(*status) &&
210 context.descendantReaped && current->getErrno() == PreservedErrno;
211
212 for (size_t i = offsetof(LinuxRusage64, maximumResidentSetSize); i < sizeof(*linuxUsage); ++i) {
213 passed &= !reinterpret_cast<uint8_t*>(linuxUsage)[i];
214 }
215 for (size_t i = 0; i < 32; ++i) {
216 passed &= linuxCanary[i] == 0xA5;
217 }
218
219 const Time::Timestamp userAfter = parent->getReapedChildrenUserTime();
220 const Time::Timestamp kernelAfter = parent->getReapedChildrenKernelTime();
221 const Time::Timestamp addedUser = userAfter - userBefore;
222 const Time::Timestamp addedKernel = kernelAfter - kernelBefore;
223 passed &=
224 addedUser >= childUser + descendantUser && addedKernel >= childKernel + descendantKernel &&
225 linuxUserTime(*linuxUsage) == addedUser - (addedUser % Time::Multiplier::Microsecond) &&
226 linuxKernelTime(*linuxUsage) == addedKernel - (addedKernel % Time::Multiplier::Microsecond);
227
228 ByteSet(nativeUsage, 0xA5, sizeof(*nativeUsage));
229 passed &= posix_getrusage(RUSAGE_CHILDREN, nativeUsage) == 0;
230 const Time::Timestamp nativeUser =
231 static_cast<Time::Timestamp>(nativeUsage->ru_utime.tv_sec) * Time::Multiplier::Second +
232 static_cast<Time::Timestamp>(nativeUsage->ru_utime.tv_usec) * Time::Multiplier::Microsecond;
233 const Time::Timestamp nativeKernel =
234 static_cast<Time::Timestamp>(nativeUsage->ru_stime.tv_sec) * Time::Multiplier::Second +
235 static_cast<Time::Timestamp>(nativeUsage->ru_stime.tv_usec) * Time::Multiplier::Microsecond;
236 passed &= nativeUser == userAfter - (userAfter % Time::Multiplier::Microsecond) &&
237 nativeKernel == kernelAfter - (kernelAfter % Time::Multiplier::Microsecond);
238 for (size_t i = offsetof(struct rusage, ru_maxrss); i < sizeof(*nativeUsage); ++i) {
239 passed &= !reinterpret_cast<uint8_t*>(nativeUsage)[i];
240 }
241
242 ByteSet(linuxUsage, 0xA5, sizeof(LinuxRusage64) + 32);
243 passed &=
244 posix_linux_getrusage(RUSAGE_CHILDREN, linuxUsage) == 0 &&
245 linuxUserTime(*linuxUsage) == userAfter - (userAfter % Time::Multiplier::Microsecond) &&
246 linuxKernelTime(*linuxUsage) == kernelAfter - (kernelAfter % Time::Multiplier::Microsecond);
247 for (size_t i = offsetof(LinuxRusage64, maximumResidentSetSize); i < sizeof(*linuxUsage); ++i) {
248 passed &= !reinterpret_cast<uint8_t*>(linuxUsage)[i];
249 }
250 for (size_t i = 0; i < 32; ++i) {
251 passed &= linuxCanary[i] == 0xA5;
252 }
253
254 passed &= posix_times(processTimes) >= 0 &&
255 processTimes->tms_cutime == static_cast<clock_t>(userAfter / clockTick) &&
256 processTimes->tms_cstime == static_cast<clock_t>(kernelAfter / clockTick);
257
258 *status = 0x5A5A5A5A;
259 ByteSet(linuxUsage, 0xA5, sizeof(LinuxRusage64) + 32);
260 MemoryCopy(untouched, linuxUsage, sizeof(untouched));
261 current->setErrno(0);
262 passed &= posix_waitpid(childPid, status, WNOHANG, linuxUsage) == -1 &&
263 current->getErrno() == Error::NoChildren && *status == 0x5A5A5A5A &&
264 !MemoryCompare(linuxUsage, untouched, sizeof(untouched));
265
266 passed &= ZombieQueue::instance().drain();
267 return passed;
268}
269
270bool transitionAccounting(PosixProcess* parent, int* status, LinuxRusage64* linuxUsage) {
271 constexpr Time::Timestamp clockTick = Time::Multiplier::Second / 100;
272 SeededExitContext context(13 * clockTick, 7 * clockTick, true);
273 PosixProcess* child = new PosixProcess(parent);
274 child->setSubsystem(new PosixSubsystem);
275 Thread* childThread = new Thread(child, seededExit, &context, nullptr, false, true, true);
276 childThread->setName("hosted transition resource child");
277 child->publish();
278 const int childPid = static_cast<int>(child->getId());
279 if (!childThread->start()) {
280 delete childThread;
281 delete child;
282 return false;
283 }
284
285 bool passed = waitForValue(context.ready);
286 const Time::Timestamp userBefore = parent->getReapedChildrenUserTime();
287 const Time::Timestamp kernelBefore = parent->getReapedChildrenKernelTime();
288 child->suspend(SIGSTOP);
289 ByteSet(linuxUsage, 0xA5, sizeof(*linuxUsage));
290 passed &= posix_waitpid(childPid, status, WUNTRACED, linuxUsage) == childPid &&
291 WIFSTOPPED(*status) && WSTOPSIG(*status) == SIGSTOP &&
292 parent->getReapedChildrenUserTime() == userBefore &&
293 parent->getReapedChildrenKernelTime() == kernelBefore;
294 for (size_t i = offsetof(LinuxRusage64, maximumResidentSetSize); i < sizeof(*linuxUsage); ++i) {
295 passed &= !reinterpret_cast<uint8_t*>(linuxUsage)[i];
296 }
297
298 child->resume();
299 ByteSet(linuxUsage, 0xA5, sizeof(*linuxUsage));
300 passed &= posix_waitpid(childPid, status, WCONTINUED, linuxUsage) == childPid &&
301 WIFCONTINUED(*status) && parent->getReapedChildrenUserTime() == userBefore &&
302 parent->getReapedChildrenKernelTime() == kernelBefore;
303 for (size_t i = offsetof(LinuxRusage64, maximumResidentSetSize); i < sizeof(*linuxUsage); ++i) {
304 passed &= !reinterpret_cast<uint8_t*>(linuxUsage)[i];
305 }
306
307 context.release += 1;
308 passed &= posix_waitpid(childPid, status, 0, nullptr) == childPid && WIFEXITED(*status) &&
309 !WEXITSTATUS(*status) &&
310 parent->getReapedChildrenUserTime() >= userBefore + context.user &&
311 parent->getReapedChildrenKernelTime() >= kernelBefore + context.kernel;
312 passed &= ZombieQueue::instance().drain();
313 return passed;
314}
315
316bool concurrentReaperAccounting(PosixProcess* parent) {
317 constexpr Time::Timestamp clockTick = Time::Multiplier::Second / 100;
318 SeededExitContext exitContext(19 * clockTick, 11 * clockTick, true);
319 PosixProcess* child = new PosixProcess(parent);
320 child->setSubsystem(new PosixSubsystem);
321 Thread* childThread = new Thread(child, seededExit, &exitContext, nullptr, false, true, true);
322 childThread->setName("hosted concurrent resource child");
323 child->publish();
324 const int childPid = static_cast<int>(child->getId());
325
326 Scheduler::ProcessLease childLease;
327 const bool leased = Scheduler::instance().acquireProcess(childLease, child);
328 const bool childStarted = leased && childThread->start();
329 if (!childStarted) {
330 delete childThread;
331 childLease.reset();
332 delete child;
333 return false;
334 }
335
336 ConcurrentWaitContext firstContext(childPid);
337 ConcurrentWaitContext secondContext(childPid);
338 Thread* first = new Thread(parent, concurrentWait, &firstContext, nullptr, false, true, true);
339 Thread* second = new Thread(parent, concurrentWait, &secondContext, nullptr, false, true, true);
340 first->setName("hosted first child resource waiter");
341 second->setName("hosted second child resource waiter");
342 const bool firstStarted = first->start();
343 const bool secondStarted = second->start();
344 if (!firstStarted) {
345 delete first;
346 }
347 if (!secondStarted) {
348 delete second;
349 }
350
351 bool passed = firstStarted && secondStarted && waitForValue(firstContext.ready) &&
352 waitForValue(secondContext.ready) && waitForValue(exitContext.ready);
353 const Time::Timestamp userBefore = parent->getReapedChildrenUserTime();
354 const Time::Timestamp kernelBefore = parent->getReapedChildrenKernelTime();
355 firstContext.go += 1;
356 secondContext.go += 1;
357 exitContext.release += 1;
358
359 const bool reapable = waitForTermination(child);
360 const Time::Timestamp childUser = child->getUserTime();
361 const Time::Timestamp childKernel = child->getKernelTime();
362 const bool firstJoined = firstStarted && first->joinForCompletion();
363 const bool secondJoined = secondStarted && second->joinForCompletion();
364 const bool firstWon = firstContext.result == childPid;
365 const bool secondWon = secondContext.result == childPid;
366
367 Process::ReaperClaim rescueReaper;
368 if (!firstWon && !secondWon && child->getState() == Process::Terminated) {
369 auto guard = parent->acquireChildStateWait();
370 if (child->getState() == Process::Terminated) {
371 child->reap();
372 rescueReaper = child->tryClaimReaper();
373 }
374 }
375 if (rescueReaper) {
376 Time::Timestamp ignoredUser = 0;
377 Time::Timestamp ignoredKernel = 0;
379 parent->accountReapedChild(child, ignoredUser, ignoredKernel);
380 rescueReaper.publish();
381 }
382
383 passed &= reapable && firstJoined && secondJoined && firstContext.returned == 1 &&
384 secondContext.returned == 1 && firstWon != secondWon &&
385 (firstWon ? firstContext.error == PreservedErrno
386 : firstContext.result == -1 && firstContext.error == Error::NoChildren) &&
387 (secondWon ? secondContext.error == PreservedErrno
388 : secondContext.result == -1 && secondContext.error == Error::NoChildren) &&
389 parent->getReapedChildrenUserTime() == userBefore + childUser &&
390 parent->getReapedChildrenKernelTime() == kernelBefore + childKernel &&
391 child->getState() == Process::Reaped;
392
393 Process* childIdentity = child;
394 childLease.reset();
395 passed &= ZombieQueue::instance().drain();
397 return passed;
398}
399
400int childResourceWorker(void* parameter) {
401 ChildResourceContext* context = reinterpret_cast<ChildResourceContext*>(parameter);
402 const size_t pageSize = PhysicalMemoryManager::getPageSize();
403 uintptr_t address = 0;
404 if (!context->process->allocateUserRange(Process::UserRegion::Normal, pageSize, address)) {
405 context->returned += 1;
406 return 1;
407 }
408
409 uintptr_t mappedAddress = address;
411 mappedAddress, pageSize, MemoryMappedObject::Read | MemoryMappedObject::Write);
412 if (!mapping || mappedAddress != address) {
413 if (mapping) {
414 MemoryMapManager::instance().remove(mappedAddress, pageSize);
415 }
416 context->process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
417 context->returned += 1;
418 return 1;
419 }
420
421 int* status = reinterpret_cast<int*>(address + 64);
422 struct tms* processTimes = reinterpret_cast<struct tms*>(address + 128);
423 struct rusage* nativeUsage = reinterpret_cast<struct rusage*>(address + 256);
424 LinuxRusage64* linuxUsage = reinterpret_cast<LinuxRusage64*>(address + 1024);
425 uint8_t* linuxCanary = reinterpret_cast<uint8_t*>(linuxUsage + 1);
426 const bool passed = transitiveChildAccounting(context->process, status, processTimes, nativeUsage,
427 linuxUsage, linuxCanary) &&
428 transitionAccounting(context->process, status, linuxUsage) &&
429 concurrentReaperAccounting(context->process);
430
431 MemoryMapManager::instance().remove(address, pageSize);
432 context->process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
433 context->passed = passed;
434 context->returned += 1;
435 return passed ? 0 : 1;
436}
437} // namespace
438
439bool runHostedChildResourceRegressions(Process* kernelProcess) {
440 PosixProcess* process = new PosixProcess(kernelProcess);
441 process->setSubsystem(new PosixSubsystem);
442 ChildResourceContext context(process);
443 Thread* worker = new Thread(process, childResourceWorker, &context, nullptr, false, true, true);
444 worker->setName("hosted child resource semantics");
445 process->publish();
446
447 const bool started = worker->start();
448 const bool joined = started && worker->joinForCompletion();
449 if (!started) {
450 delete worker;
451 }
452 const bool passed = started && joined && context.returned == 1 && context.passed;
453 delete process;
454
455 if (!passed) {
456 ERROR(
457 "HOSTED-SYSCALL-TEST: FAIL child-resource-accounting: "
458 "transitive, transition, concurrent reaper, or wait4 ABI semantics regressed");
459 return false;
460 }
461
462 NOTICE("HOSTED-SYSCALL-TEST: PASS child-resource-accounting");
463 return true;
464}
Memory-mapped file interface.
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
size_t getId()
Definition Process.h:463
Process * getParent()
Definition Process.h:568
void accountReapedChild(const Process *child, Time::Timestamp &user, Time::Timestamp &kernel)
Definition Process.cc:758
WaitQueue::Guard acquireChildStateWait()
Definition Process.h:682
void reap()
Definition Process.cc:1477
void publish()
Definition Process.cc:832
void suspend(int stopSignal=0)
Definition Process.cc:1864
void resume()
Definition Process.cc:1968
bool waitUntilTerminationReapable()
Definition Process.cc:2083
Time::Timestamp getUserTime() const
Definition Process.h:775
ReaperClaim tryClaimReaper()
Definition Process.cc:1814
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
MUST_USE_RESULT bool acquireProcess(ProcessLease &lease, size_t n)
Definition Scheduler.cc:264
void waitUntilProcessRemoved(Process *expected)
Definition Scheduler.cc:401
void yield()
Definition Scheduler.cc:226
void setErrno(size_t err)
Definition Thread.h:478
static void threadExited() NORETURN
Definition Thread.cc:1073
size_t getErrno()
Definition Thread.h:473
bool joinForCompletion()
Definition Thread.cc:2771
bool start()
Definition Thread.cc:794
void deferProcessExit(int code)
Definition Thread.cc:3653