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/Processor.h"
19#include "modules/subsys/posix/FileDescriptor.h"
20#include "modules/subsys/posix/PosixSubsystem.h"
21#include "modules/subsys/posix/eventfd-syscalls.h"
22#include "modules/subsys/posix/file-syscalls.h"
25constexpr size_t Attempts = 10000;
26constexpr size_t ReplacementIterations = 256;
27constexpr size_t EventFdRaceIterations = 32;
28constexpr int PreservedErrno = 173;
30struct PublicationStressContext {
31 PublicationStressContext(
int source,
int target)
50 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
51 if (value.value() == expected) {
59int replaceWithCloexec(
void* parameter) {
60 PublicationStressContext* context =
reinterpret_cast<PublicationStressContext*
>(parameter);
62 if (!waitFor(context->start, 1)) {
63 context->failures += 1;
64 context->finished += 1;
68 for (
size_t iteration = 0; iteration < ReplacementIterations; ++iteration) {
69 if (posix_dup3(context->source, context->target, O_CLOEXEC) != context->target) {
70 context->failures += 1;
75 context->finished += 1;
79int observeCloexecPublication(
void* parameter) {
80 PublicationStressContext* context =
reinterpret_cast<PublicationStressContext*
>(parameter);
82 if (!waitFor(context->start, 1)) {
83 context->failures += 1;
88 if (posix_fcntl(context->target, F_GETFD,
nullptr) != FD_CLOEXEC) {
89 context->failures += 1;
92 context->observations += 1;
94 }
while (!context->finished.value());
98bool cloexecPublicationIsAtomic(
Process* process,
int source,
int target) {
99 PublicationStressContext context(source, target);
100 Thread* replacer =
new Thread(process, replaceWithCloexec, &context,
nullptr,
false,
true,
true);
102 new Thread(process, observeCloexecPublication, &context,
nullptr,
false,
true,
true);
103 replacer->setName(
"hosted dup3 CLOEXEC replacer");
104 observer->setName(
"hosted dup3 CLOEXEC observer");
105 const bool replacerStarted = replacer->
start();
106 const bool observerStarted = observer->
start();
107 const bool ready = replacerStarted && observerStarted && waitFor(context.ready, 2);
108 if (!replacerStarted) {
109 context.finished += 1;
114 if (!replacerStarted) {
117 if (!observerStarted) {
121 return ready && replacerJoined && observerJoined && context.finished == 1 &&
122 context.observations.value() && !context.failures.value();
125struct EventFdCloseRaceContext {
126 EventFdCloseRaceContext(
int source,
int target)
146int duplicateAgainstFinalClose(
void* parameter) {
147 EventFdCloseRaceContext* context =
reinterpret_cast<EventFdCloseRaceContext*
>(parameter);
150 if (!waitFor(context->start, 1)) {
151 context->failures += 1;
155 context->duplicateResult = posix_dup3(context->source, context->target, O_CLOEXEC);
156 context->duplicateError = thread->
getErrno();
160int closeEventFdSource(
void* parameter) {
161 EventFdCloseRaceContext* context =
reinterpret_cast<EventFdCloseRaceContext*
>(parameter);
163 if (!waitFor(context->start, 1)) {
164 context->failures += 1;
167 context->closeResult = posix_close(context->source);
172 for (
size_t iteration = 0; iteration < EventFdRaceIterations; ++iteration) {
173 const int source = posix_eventfd2(1, LinuxEventFd::NonBlock);
174 const int target = posix_eventfd2(2, LinuxEventFd::NonBlock);
177 if (source < 0 || target < 0 || !subsystem->acquireFileDescriptor(source, sourceDescriptor) ||
185 sourceDescriptor.reset();
186 targetDescriptor.reset();
188 EventFdCloseRaceContext context(source, target);
190 new Thread(process, duplicateAgainstFinalClose, &context,
nullptr,
false,
true,
true);
191 Thread* closer =
new Thread(process, closeEventFdSource, &context,
nullptr,
false,
true,
true);
192 duplicator->setName(
"hosted dup3 eventfd duplicator");
193 closer->setName(
"hosted dup3 eventfd closer");
194 const bool duplicatorStarted = duplicator->
start();
195 const bool closerStarted = closer->
start();
196 const bool ready = duplicatorStarted && closerStarted && waitFor(context.ready, 2);
198 const bool duplicatorJoined = duplicatorStarted && duplicator->
joinForCompletion();
200 if (!duplicatorStarted) {
203 if (!closerStarted) {
209 const bool duplicateWon = context.duplicateResult == target && context.duplicateError == 0;
210 const bool closeWon =
211 context.duplicateResult == -1 && context.duplicateError == Error::BadFileDescriptor;
212 const bool expectedDescription =
214 (duplicateWon ? sourceDescription.
get() : targetDescription.
get());
215 publishedTarget.reset();
216 const bool targetClosed = posix_close(target) == 0;
218 if (!ready || !duplicatorJoined || !closerJoined || context.closeResult ||
219 context.failures.value() || (!duplicateWon && !closeWon) || !expectedDescription ||
220 !targetClosed || sourceDescription->descriptorOwnerCount() ||
221 targetDescription->descriptorOwnerCount()) {
229 explicit Dup3Context(
Process* process)
230 : process(process), completed(false), passed(false), returned(0) {}
238int exerciseDup3(
void* parameter) {
239 Dup3Context* context =
reinterpret_cast<Dup3Context*
>(parameter);
244 const int source = posix_eventfd2(2, LinuxEventFd::NonBlock | LinuxEventFd::CloseOnExec);
245 const int target = posix_eventfd2(7, LinuxEventFd::NonBlock);
248 passed &= source >= 0 && target >= 0 &&
253 if (sourceDescriptor && oldTargetDescriptor) {
257 sourceDescriptor.reset();
258 oldTargetDescriptor.reset();
259 if (!sourceDescription || !oldTargetDescription) {
266 context->completed =
true;
267 context->returned += 1;
272 passed &= posix_dup3(source, target, 0) == target && thread->
getErrno() == PreservedErrno &&
273 sourceDescription->descriptorOwnerCount() == 2 &&
274 oldTargetDescription->descriptorOwnerCount() == 0;
283 passed &= posix_read(target,
reinterpret_cast<char*
>(&value),
sizeof(value)) == 8 && value == 2;
285 passed &= posix_write(source,
reinterpret_cast<char*
>(&value),
sizeof(value),
false) == 8;
287 passed &= posix_read(target,
reinterpret_cast<char*
>(&value),
sizeof(value)) == 8 && value == 3;
289 passed &= posix_dup3(source, target, O_CLOEXEC) == target &&
290 posix_fcntl(target, F_GETFD,
nullptr) == FD_CLOEXEC;
294 if (retainedTarget) {
297 retainedTarget.reset();
300 passed &= posix_dup3(source, source, 0) == -1 && thread->
getErrno() == Error::InvalidArgument;
302 passed &= posix_dup3(-1, -1, 0) == -1 && thread->
getErrno() == Error::InvalidArgument;
305 posix_dup3(source, target, O_NONBLOCK) == -1 && thread->
getErrno() == Error::InvalidArgument;
307 passed &= posix_dup3(-1, target, 0) == -1 && thread->
getErrno() == Error::BadFileDescriptor;
309 passed &= posix_dup3(source, -1, 0) == -1 && thread->
getErrno() == Error::BadFileDescriptor;
311 passed &= posix_dup3(source, 16384, 0) == -1 && thread->
getErrno() == Error::BadFileDescriptor;
316 unchangedTarget->
getFlags() == FD_CLOEXEC;
317 unchangedTarget.reset();
319 const size_t reserved = subsystem->
getFd(100);
321 passed &= reserved == 100 && posix_dup3(-1,
static_cast<int>(reserved), 0) == -1 &&
322 thread->
getErrno() == Error::BadFileDescriptor;
324 passed &= posix_dup3(source,
static_cast<int>(reserved), O_CLOEXEC) == -1 &&
325 thread->
getErrno() == Error::DeviceBusy;
328 subsystem->
freeFd(reserved);
330 posix_dup3(source,
static_cast<int>(reserved), O_CLOEXEC) ==
static_cast<int>(reserved) &&
331 posix_fcntl(
static_cast<int>(reserved), F_GETFD,
nullptr) == FD_CLOEXEC &&
332 posix_close(
static_cast<int>(reserved)) == 0;
334 passed &= cloexecPublicationIsAtomic(context->process, source, target);
335 passed &= posix_close(source) == 0;
337 passed &= posix_write(target,
reinterpret_cast<char*
>(&value),
sizeof(value),
false) == 8;
339 passed &= posix_read(target,
reinterpret_cast<char*
>(&value),
sizeof(value)) == 8 && value == 5 &&
340 posix_close(target) == 0 && sourceDescription->descriptorOwnerCount() == 0;
342 passed &= eventFdFinalCloseRace(context->process, subsystem);
343 context->passed = passed;
344 context->completed =
true;
345 context->returned += 1;
346 return passed ? 0 : 1;
350bool runHostedDup3Regressions(
Process* kernelProcess) {
353 Dup3Context context(process);
354 Thread*
worker =
new Thread(process, exerciseDup3, &context,
nullptr,
false,
true,
true);
355 worker->setName(
"hosted dup3 regression worker");
356 const bool started =
worker->start();
357 const bool joined = started &&
worker->joinForCompletion();
363 started && joined && context.returned == 1 && context.completed && context.passed;
367 "HOSTED-SYSCALL-TEST: FAIL dup3-atomic-replacement: "
368 "validation, OFD sharing, CLOEXEC publication, replacement, or eventfd close race "
373 NOTICE(
"HOSTED-SYSCALL-TEST: PASS dup3-atomic-replacement");
OpenFileDescriptionLease acquireOpenFileDescription() const
int getFlags() const
Get current descriptor flags.
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
size_t getFd(size_t minimum=0)
void freeFd(size_t fdNum)
static ProcessorInformation & information()
static Scheduler & instance()
void setErrno(size_t err)
Process * getParent() const