The Pedigree Project 0.1
process-query-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/SignalEvent.h"
14#include "pedigree/kernel/process/Thread.h"
15#include "pedigree/kernel/processor/Processor.h"
16#include "pedigree/kernel/time/Time.h"
17
18#include <limits.h>
19#include <signal.h>
20
21#include "modules/subsys/posix/PosixProcess.h"
22#include "modules/subsys/posix/PosixSubsystem.h"
23#include "modules/subsys/posix/system-syscalls.h"
24#include <sys/resource.h>
25#include <sys/wait.h>
26
27namespace {
28constexpr int PreservedErrno = 173;
29constexpr int TestUid = 913;
30constexpr size_t TestSignal = 10;
31
32struct PriorityContext {
33 PosixProcess* process;
34 int foreignPid;
35 bool passed = false;
36 Atomic<size_t> returned{0};
37};
38
39int priorityWorker(void* parameter) {
40 PriorityContext* context = reinterpret_cast<PriorityContext*>(parameter);
41 Thread* current = Processor::information().getCurrentThread();
42 const int processId = static_cast<int>(context->process->getId());
43 bool passed = true;
44
45 constexpr bool Abis[] = {false, true};
46 constexpr int Selectors[] = {PRIO_PROCESS, PRIO_PGRP, PRIO_USER};
47 for (bool linuxAbi : Abis) {
48 const int expected = linuxAbi ? 20 : 0;
49 for (int selector : Selectors) {
50 const int explicitTarget = selector == PRIO_USER ? TestUid : processId;
51 current->setErrno(PreservedErrno);
52 passed &= posix_getpriority(selector, 0, linuxAbi) == expected &&
53 current->getErrno() == PreservedErrno;
54 current->setErrno(PreservedErrno);
55 passed &= posix_getpriority(selector, explicitTarget, linuxAbi) == expected &&
56 current->getErrno() == PreservedErrno;
57 current->setErrno(0);
58 passed &= posix_getpriority(selector, INT_MAX, linuxAbi) == -1 &&
59 current->getErrno() ==
60 (selector == PRIO_PROCESS ? Error::NoSuchProcess : Error::Unimplemented);
61 current->setErrno(0);
62 passed &= posix_getpriority(selector, -1, linuxAbi) == -1 &&
63 current->getErrno() == Error::NoSuchProcess;
64 }
65
66 current->setErrno(PreservedErrno);
67 passed &= posix_getpriority(PRIO_PROCESS, context->foreignPid, linuxAbi) == expected &&
68 current->getErrno() == PreservedErrno;
69 current->setErrno(0);
70 passed &= posix_getpriority(PRIO_PGRP, context->foreignPid, linuxAbi) == -1 &&
71 current->getErrno() == Error::Unimplemented;
72 current->setErrno(0);
73 passed &= posix_getpriority(PRIO_USER, TestUid + 1, linuxAbi) == -1 &&
74 current->getErrno() == Error::Unimplemented;
75 current->setErrno(0);
76 passed &=
77 posix_getpriority(-1, 0, linuxAbi) == -1 && current->getErrno() == Error::InvalidArgument;
78 current->setErrno(0);
79 passed &=
80 posix_getpriority(3, 0, linuxAbi) == -1 && current->getErrno() == Error::InvalidArgument;
81 }
82
83 context->passed = passed;
84 context->returned += 1;
85 return passed ? 0 : 1;
86}
87
88bool priorityQueries(Process* kernelProcess) {
89 PosixProcess* process = new PosixProcess(kernelProcess);
90 process->setSubsystem(new PosixSubsystem);
91 process->setUserId(TestUid);
92 process->publish();
93
94 PosixProcess* foreign = new PosixProcess(kernelProcess);
95 foreign->setSubsystem(new PosixSubsystem);
96 foreign->setUserId(TestUid + 1);
97 foreign->publish();
98 PriorityContext context{process, static_cast<int>(foreign->getId())};
99 Thread* worker = new Thread(process, priorityWorker, &context, nullptr, false, true, true);
100 worker->setName("hosted getpriority query contracts");
101 const bool started = worker->start();
102 const bool joined = started && worker->joinForCompletion();
103 if (!started) {
104 delete worker;
105 }
106 const bool passed = started && joined && context.returned == 1 && context.passed;
107 delete foreign;
108 delete process;
109 if (passed) {
110 NOTICE("HOSTED-SYSCALL-TEST: PASS getpriority-query-contracts");
111 } else {
112 ERROR("HOSTED-SYSCALL-TEST: FAIL getpriority-query-contracts");
113 }
114 return passed;
115}
116
117void unusedSignalHandler(size_t) {}
118
119class DeferredWaitSignal : public SignalEvent {
120 public:
121 DeferredWaitSignal()
122 : SignalEvent(reinterpret_cast<uintptr_t>(&unusedSignalHandler), TestSignal, ~0UL, 0, true,
123 false, Event::HandlerPrivilege::User) {}
124
125 bool requiresExactUserReturnState() const override {
126 return true;
127 }
128};
129
130struct WaitContext {
131 PosixProcess* child;
132 Event* signal;
133 bool passed = false;
134 bool collisionQueued = false;
135 Atomic<size_t> returned{0};
136};
137
138WaitContext* g_WaitCollision = nullptr;
139
140void childStatusSignalCollision(WaitQueue*, Thread* thread, const WaitQueue::Channel&,
141 size_t debugState) {
142 WaitContext* context = g_WaitCollision;
143 if (!context || debugState != Thread::ProcessWait ||
144 thread->getParent() != context->child->getParent()) {
145 return;
146 }
147 WaitQueue::setBeforeBlockHook(nullptr);
148 context->collisionQueued = thread->sendEvent(context->signal);
149 context->child->suspend(SIGSTOP);
150}
151
152int interruptedWaitWorker(void* parameter) {
153 WaitContext* context = reinterpret_cast<WaitContext*>(parameter);
154 Thread* current = Processor::information().getCurrentThread();
155 const int childPid = static_cast<int>(context->child->getId());
156 current->setInterruptionReason(Thread::InterruptedBySignal);
157 current->setErrno(PreservedErrno);
158 const int result = posix_waitpid(childPid, nullptr, WUNTRACED, nullptr);
159 bool passed = result == -1 && current->getErrno() == Error::Interrupted &&
160 current->getInterruptionReason() == Thread::NotInterrupted &&
161 current->hasEvent(context->signal);
162 current->cullEvent(context->signal);
163 current->clearInterruption();
164
165 if (passed) {
166 current->setErrno(PreservedErrno);
167 passed &= posix_waitpid(childPid, nullptr, WNOHANG | WUNTRACED, nullptr) == 0 &&
168 current->getErrno() == PreservedErrno;
169
170 g_WaitCollision = context;
171 WaitQueue::setBeforeBlockHook(&childStatusSignalCollision);
172 current->setErrno(PreservedErrno);
173 const int childStatus = posix_waitpid(childPid, nullptr, WUNTRACED, nullptr);
174 WaitQueue::setBeforeBlockHook(nullptr);
175 g_WaitCollision = nullptr;
176 passed &= childStatus == childPid && current->getErrno() == PreservedErrno &&
177 context->collisionQueued && current->hasEvent(context->signal) &&
178 current->getInterruptionReason() == Thread::NotInterrupted;
179 current->cullEvent(context->signal);
180 passed &= posix_waitpid(childPid, nullptr, WNOHANG | WUNTRACED, nullptr) == 0 &&
181 current->getInterruptionReason() == Thread::NotInterrupted;
182 }
183
184 context->passed = passed;
185 context->returned += 1;
186 return passed ? 0 : 1;
187}
188
189bool waitUntilBlocked(Thread* thread) {
190 const Time::Timestamp deadline = Time::getTicks() + 2 * Time::Multiplier::Second;
191 while (Time::getTicks() < deadline) {
192 Thread::WaitDebugInfo wait = {};
193 uintptr_t debugAddress = 0;
194 if (thread->getWaitDebugInfo(wait) && wait.queue && wait.queued &&
195 thread->getDebugState(debugAddress) == Thread::ProcessWait) {
196 return true;
197 }
199 }
200 return false;
201}
202
203bool waitForReturn(const Atomic<size_t>& returned) {
204 const Time::Timestamp deadline = Time::getTicks() + 2 * Time::Multiplier::Second;
205 while (!returned && Time::getTicks() < deadline) {
207 }
208 return returned == 1;
209}
210
211bool caughtSignalWait(Process* kernelProcess) {
212 PosixProcess* process = new PosixProcess(kernelProcess);
213 process->setSubsystem(new PosixSubsystem);
214 process->publish();
215 PosixProcess* child = new PosixProcess(process);
216 child->setSubsystem(new PosixSubsystem);
217 child->publish();
218
219 DeferredWaitSignal signal;
220 WaitContext context{child, &signal};
221 Thread* worker = new Thread(process, interruptedWaitWorker, &context, nullptr, false, true, true);
222 worker->setName("hosted caught-signal waitpid interruption");
223 const bool started = worker->start();
224 const bool queued = started && waitUntilBlocked(worker) && worker->sendEvent(&signal);
225 const bool returnedBeforeChildStatus = queued && waitForReturn(context.returned);
226 if (started && !returnedBeforeChildStatus) {
227 // Let the old retry behavior return a status so a regression fails cleanly.
228 child->suspend(SIGSTOP);
229 }
230 const bool joined = started && worker->joinForCompletion();
231 if (!started) {
232 delete worker;
233 }
234
235 const bool passed = queued && returnedBeforeChildStatus && joined && context.passed;
236 delete child;
237 delete process;
238 if (passed) {
239 NOTICE("HOSTED-SYSCALL-TEST: PASS waitpid-caught-signal-interruption");
240 } else {
241 ERROR("HOSTED-SYSCALL-TEST: FAIL waitpid-caught-signal-interruption");
242 }
243 return passed;
244}
245} // namespace
246
247bool runHostedProcessQueryRegressions(Process* kernelProcess) {
248 const bool priorityPassed = priorityQueries(kernelProcess);
249 const bool waitPassed = caughtSignalWait(kernelProcess);
250 return priorityPassed && waitPassed;
251}
Definition Event.h:49
virtual bool requiresExactUserReturnState() const
Definition Event.h:254
size_t getId()
Definition Process.h:463
void publish()
Definition Process.cc:832
void suspend(int stopSignal=0)
Definition Process.cc:1864
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
void setErrno(size_t err)
Definition Thread.h:478
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool hasEvent(Event *pEvent)
Definition Thread.cc:2639
size_t getErrno()
Definition Thread.h:473
void cullEvent(Event *pEvent)
Definition Thread.cc:2193
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
Process * getParent() const
Definition Thread.h:338
bool sendEvent(Event *pEvent)
Definition Thread.cc:1158
Definition User.h:32