The Pedigree Project 0.1
static-syscall-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 "modules/Module.h"
9#include "modules/subsys/pedigree-c/pedigreecSyscallNumbers.h"
10#include "modules/subsys/posix/FileDescriptor.h"
11#include "modules/subsys/posix/PosixProcess.h"
12#include "modules/subsys/posix/PosixSubsystem.h"
13#include "modules/subsys/posix/ResolvedPath.h"
14#include "modules/subsys/posix/UnixFilesystem.h"
15#include "modules/subsys/posix/epoll-syscalls.h"
16#include "modules/subsys/posix/eventfd-syscalls.h"
17#include "modules/subsys/posix/file-syscalls.h"
18#include "modules/subsys/posix/net-syscalls.h"
19#include "modules/subsys/posix/poll-syscalls.h"
20#include "modules/subsys/posix/select-syscalls.h"
21#include "modules/subsys/posix/system-syscalls.h"
22#include "modules/system/vfs/Directory.h"
23#include "modules/system/vfs/File.h"
25#include "modules/system/vfs/MountView.h"
26#include "modules/system/vfs/Pipe.h"
27#include "modules/system/vfs/VFS.h"
28#undef PEDIGREE_INIT_SIGRET
29#undef PEDIGREE_SIGRET
30#include "pedigree/kernel/Atomic.h"
31#include "pedigree/kernel/Log.h"
32#include "pedigree/kernel/errors.h"
33#include "pedigree/kernel/linker/KernelElf.h"
34#include "pedigree/kernel/panic.h"
35#include "pedigree/kernel/process/Process.h"
36#include "pedigree/kernel/process/Scheduler.h"
37#include "pedigree/kernel/process/Semaphore.h"
38#include "pedigree/kernel/process/TerminationDeferral.h"
39#include "pedigree/kernel/process/Thread.h"
40#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
41#include "pedigree/kernel/processor/Processor.h"
42#include "pedigree/kernel/processor/ProcessorInformation.h"
43#include "pedigree/kernel/processor/SyscallManager.h"
44#include "pedigree/kernel/processor/hosted/smoke.h"
45#include "pedigree/kernel/utilities/StringView.h"
46#include "pedigree/kernel/utilities/utility.h"
47
48#include <fcntl.h>
49#include <limits.h>
50#include <sched.h>
51
52#include "modules/subsys/posix/syscalls/posixSyscallNumbers.h"
53
54extern void system_reset();
55extern bool hostedRunSyscallProfile();
56extern "C" bool posixDuplicateInitRollbackPreservesProcessForTest(Process* processIdentity);
57extern "C" void posixSetCloneBeforeStartHookForTest(void (*hook)(Thread*, size_t, void*),
58 void* context);
59extern "C" unsigned int posixSelectProjectionForTest(short revents, bool checkRead, bool checkWrite,
60 bool checkExceptional);
61extern bool runHostedAccessSyscallRegressions(Process* process);
62extern bool runHostedAdvisoryLockRegressions(Process* process);
63extern bool runHostedChildResourceRegressions(Process* process);
64extern bool runHostedCloneRoutingRegressions(Process* process);
65extern bool runHostedDup3Regressions(Process* process);
66extern bool runHostedEventFdRegressions(Process* process);
67extern bool runHostedFileContractRegressions(Process* process);
68extern bool runHostedFutexRobustRegressions(Process* process);
69extern bool runHostedSystemUsercopyRegressions(Process* process);
70extern bool runHostedVmPermissionRegressions();
71extern bool runHostedProcessQueryRegressions(Process* process);
72extern bool runHostedTermiosSyscallRegressions(Process* process);
73extern bool runHostedInotifyRegressions(Process* process);
74extern bool runHostedMmapPlacementRegressions(Process* process);
75extern bool runHostedPosixExitStatusRegressions(Process* process);
76extern bool runHostedPpollRegressions(Process* process);
77extern bool runHostedPositionalIoRegressions(Process* process);
78extern bool runHostedPositionalVectorIoRegressions(Process* process);
79extern bool runHostedPselectRegressions(Process* process);
80extern bool runHostedResourceSyscallRegressions(Process* process);
81extern bool runHostedRtSigsuspendRegressions(Process* process);
82extern bool runHostedScalarIoRegressions(Process* process);
83extern bool runHostedScmRightsRegressions(Process* process);
84extern bool runHostedScmStreamRegressions(Process* process);
85extern bool runHostedSleepClockSyscallRegressions(Process* process);
86extern bool runHostedThreadSignalSyscallRegressions(Process* process);
87extern bool runHostedTimeSyscallRegressions(Process* process);
88extern bool runHostedUnixStreamInterruptionRegressions(Process* process);
89extern bool runHostedUsercopyRegressions(Process* process);
90extern bool runHostedVectorIoRegressions(Process* process);
91
92namespace {
93constexpr size_t HostedAttempts = 10000;
94constexpr int PollCloseReuseTimeoutMilliseconds = 5000;
95constexpr uint64_t EpollInitialData = 0x1111222233334444ULL;
96constexpr uint64_t EpollLevelData = 0x3333444455556666ULL;
97constexpr uint64_t EpollOneShotData = 0x5555666677778888ULL;
98constexpr uint64_t EpollRearmedData = 0x9999AAAABBBBCCCCULL;
99constexpr uint64_t EpollAliasData = 0xDDDDEEEEFFFF0001ULL;
100constexpr uint64_t EpollEventFdData = 0x123456789ABCDEF0ULL;
101constexpr uint64_t EpollReorderedData = 0x0DDBA11C0FFEE123ULL;
102constexpr uint64_t EpollFifoData = 0xF1F0C105ED6E0001ULL;
103size_t g_RuntimePinnedLifecycleCalls = 0;
104
105struct TerminalBlockedHandlerContext {
106 TerminalBlockedHandlerContext()
107 : blocker(0, true),
108 thread(nullptr),
109 hookEntered(0),
110 exitStaged(0),
111 releasedByTermination(0),
112 unexpectedRelease(0),
113 syscallReturned(0) {}
114
115 Semaphore blocker;
116 Thread* thread;
117 Atomic<size_t> hookEntered;
118 Atomic<size_t> exitStaged;
119 Atomic<size_t> releasedByTermination;
120 Atomic<size_t> unexpectedRelease;
121 Atomic<size_t> syscallReturned;
122};
123
124TerminalBlockedHandlerContext* g_TerminalBlockedHandlerContext = nullptr;
125
126int terminalCreatedFixtureEntry(void*) {
127 FATAL("HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-created-entry-ran");
128 return 0;
129}
130
131void terminalBlockedHandlerPin(Service_t service, SyscallHandler*) {
132 TerminalBlockedHandlerContext* context = g_TerminalBlockedHandlerContext;
133 Thread* current = Processor::information().getCurrentThread();
134 if (!context || service != posix || current != context->thread) {
135 return;
136 }
137
138 context->hookEntered += 1;
139 if (SyscallManager::instance().requestProcessExit(73)) {
140 context->exitStaged += 1;
141 }
142
143 const bool acquired = context->blocker.acquire();
144 if (!acquired && current->getUnwindState() == Thread::TerminateThread) {
145 context->releasedByTermination += 1;
146 } else {
147 context->unexpectedRelease += 1;
148 }
149}
150
151int terminalBlockedHandlerEntry(void* parameter) {
152 TerminalBlockedHandlerContext* context =
153 reinterpret_cast<TerminalBlockedHandlerContext*>(parameter);
154 SyscallManager::instance().syscall(posix, POSIX_GETPID);
155 context->syscallReturned += 1;
156 return 1;
157}
158
159void runtimePinnedLifecycleProbe() {
160 ++g_RuntimePinnedLifecycleCalls;
161}
162
163class DescriptorRetirementProbe : public FileDescriptor {
164 public:
165 explicit DescriptorRetirementProbe(Atomic<size_t>& destructions)
166 : FileDescriptor(), m_Destructions(destructions) {}
167
168 ~DescriptorRetirementProbe() override {
169 m_Destructions += 1;
170 }
171
172 private:
173 Atomic<size_t>& m_Destructions;
174};
175
176class EstablishedAliasFileProbe : public File {
177 public:
178 explicit EstablishedAliasFileProbe(Atomic<size_t>& destructions)
179 : File(), m_Destructions(destructions) {}
180
181 ~EstablishedAliasFileProbe() override {
182 m_Destructions += 1;
183 }
184
185 private:
186 Atomic<size_t>& m_Destructions;
187};
188
189void repairAliasFileProbe(EstablishedAliasFileProbe* file, Atomic<size_t>& destructions) {
190 if (!destructions) {
192 }
193 if (!destructions) {
194 delete file;
195 }
196}
197
198class RetainedLookupDirectory;
199
200class RetainedLookupFilesystem final : public Filesystem {
201 public:
202 using RemoveHook = bool (*)(File*, File*, void*);
203
204 RetainedLookupFilesystem()
205 : m_Root(nullptr),
206 m_Label("retained-lookup-test"),
207 m_RemoveHook(nullptr),
208 m_RemoveHookContext(nullptr) {}
209
210 void setRoot(File* root) {
211 m_Root = root;
212 }
213
214 void setRemoveHook(RemoveHook hook, void* context) {
215 m_RemoveHook = hook;
216 m_RemoveHookContext = context;
217 }
218
219 bool initialise(Disk*) override {
220 return true;
221 }
222
223 File* getRoot() const override {
224 return m_Root;
225 }
226
227 const String& getVolumeLabel() const override {
228 return m_Label;
229 }
230
231 protected:
232 bool createFile(File*, const String&, uint32_t) override {
233 return false;
234 }
235
236 bool createDirectory(File*, const String&, uint32_t) override {
237 return false;
238 }
239
240 bool createSymlink(File*, const String&, const String&) override {
241 return false;
242 }
243
244 bool removeNode(File* parent, const String&, File* file) override {
245 return m_RemoveHook ? m_RemoveHook(parent, file, m_RemoveHookContext) : false;
246 }
247
248 private:
249 File* m_Root;
250 String m_Label;
251 RemoveHook m_RemoveHook;
252 void* m_RemoveHookContext;
253};
254
255class RetainedLookupFile final : public File {
256 public:
257 RetainedLookupFile(const String& name, Filesystem* filesystem, File* parent,
258 Atomic<size_t>& destructions, RetainedLookupDirectory* lockProbe = nullptr,
259 Atomic<size_t>* lockAvailable = nullptr)
260 : File(name, 0, 0, 0, 0, filesystem, 0, parent),
261 m_Destructions(destructions),
262 m_LockProbe(lockProbe),
263 m_LockAvailable(lockAvailable) {}
264
265 ~RetainedLookupFile() override;
266
267 private:
268 Atomic<size_t>& m_Destructions;
269 RetainedLookupDirectory* m_LockProbe;
270 Atomic<size_t>* m_LockAvailable;
271};
272
273class RetainedLookupDirectory final : public Directory {
274 public:
275 RetainedLookupDirectory(const String& name, Filesystem* filesystem,
276 Atomic<size_t>* destructions = nullptr)
277 : Directory(name, 0, 0, 0, 0, filesystem, 0, nullptr),
278 m_LazyTarget(nullptr),
279 m_Conversions(nullptr),
280 m_FirstConversionEntered(nullptr),
281 m_FirstConversionRelease(nullptr),
282 m_SecondConversionEntered(nullptr),
283 m_SecondConversionRelease(nullptr),
284 m_Destructions(destructions) {}
285
286 ~RetainedLookupDirectory() override {
287 if (m_Destructions) {
288 *m_Destructions += 1;
289 }
290 }
291
292 void publish(const String& name, File* file) {
293 addDirectoryEntry(name, file);
294 }
295
296 bool removePublished(const String& name, File* expected) {
297 return removeDirectoryEntry(HashedStringView(name), expected);
298 }
299
300 const void* namespaceLockAddress() {
301 return static_cast<const void*>(&namespaceMutationLock());
302 }
303
304 bool publishEphemeral(File* file) {
305 return addEphemeralFile(file) == AddStatus::Added;
306 }
307
308 void publishLazy(const String& name, File* file, Atomic<size_t>& conversions,
309 Atomic<size_t>& firstEntered, Semaphore& firstRelease,
310 Atomic<size_t>& secondEntered, Semaphore& secondRelease) {
311 m_LazyTarget = file;
312 m_Conversions = &conversions;
313 m_FirstConversionEntered = &firstEntered;
314 m_FirstConversionRelease = &firstRelease;
315 m_SecondConversionEntered = &secondEntered;
316 m_SecondConversionRelease = &secondRelease;
317
318 DirectoryEntryMetadata metadata;
319 metadata.pDirectory = this;
320 metadata.filename = name;
321 addDirectoryEntry(name, pedigree_std::move(metadata));
322 }
323
324 void publishFailedLazy(const String& name, Atomic<size_t>& conversions) {
325 m_LazyTarget = nullptr;
326 m_Conversions = &conversions;
327 m_FirstConversionEntered = nullptr;
328 m_FirstConversionRelease = nullptr;
329 m_SecondConversionEntered = nullptr;
330 m_SecondConversionRelease = nullptr;
331
332 DirectoryEntryMetadata metadata;
333 metadata.pDirectory = this;
334 metadata.filename = name;
335 addDirectoryEntry(name, pedigree_std::move(metadata));
336 }
337
338 protected:
339 File* convertToFile(const DirectoryEntryMetadata&) override {
340 const size_t conversion = (*m_Conversions += 1);
341 Atomic<size_t>* entered = nullptr;
342 Semaphore* release = nullptr;
343 if (conversion == static_cast<size_t>(1)) {
344 entered = m_FirstConversionEntered;
345 release = m_FirstConversionRelease;
346 } else if (conversion == static_cast<size_t>(2)) {
347 entered = m_SecondConversionEntered;
348 release = m_SecondConversionRelease;
349 }
350 if (entered && release) {
351 *entered += 1;
352 const bool released = release->acquireForCompletion();
353 (void)released;
354 }
355 return m_LazyTarget;
356 }
357
358 private:
359 File* m_LazyTarget;
360 Atomic<size_t>* m_Conversions;
361 Atomic<size_t>* m_FirstConversionEntered;
362 Semaphore* m_FirstConversionRelease;
363 Atomic<size_t>* m_SecondConversionEntered;
364 Semaphore* m_SecondConversionRelease;
365 Atomic<size_t>* m_Destructions;
366};
367
368RetainedLookupFile::~RetainedLookupFile() {
369 if (m_LockProbe && m_LockAvailable && m_LockProbe->tryCacheLockForHostedTest()) {
370 *m_LockAvailable += 1;
371 }
372 m_Destructions += 1;
373}
374
375struct RetainedLookupHookContext {
376 RetainedLookupHookContext(Directory* directory, File* file, bool pauseBefore, bool pauseAfter)
377 : directory(directory),
378 file(file),
379 pauseBefore(pauseBefore),
380 pauseAfter(pauseAfter),
381 beforeRelease(0, false),
382 afterRelease(0, false),
383 beforeClaimed(0),
384 beforeEntered(0),
385 beforeReturned(0),
386 beforeNullFile(0),
387 afterClaimed(0),
388 afterEntered(0),
389 afterReturned(0) {}
390
391 Directory* directory;
392 File* file;
393 bool pauseBefore;
394 bool pauseAfter;
395 Semaphore beforeRelease;
396 Semaphore afterRelease;
397 Atomic<size_t> beforeClaimed;
398 Atomic<size_t> beforeEntered;
399 Atomic<size_t> beforeReturned;
400 Atomic<size_t> beforeNullFile;
401 Atomic<size_t> afterClaimed;
402 Atomic<size_t> afterEntered;
403 Atomic<size_t> afterReturned;
404};
405
406struct RetainedLookupWorkerContext {
407 RetainedLookupWorkerContext(Directory* directory, const String& name)
408 : directory(directory), name(name), child(nullptr), result(false), returned(0) {}
409
410 Directory* directory;
411 String name;
412 File* child;
413 bool result;
414 Atomic<size_t> returned;
415};
416
417struct DirectoryRemoveWorkerContext {
418 DirectoryRemoveWorkerContext(Directory* directory, const String& name)
419 : directory(directory), name(name), returned(0) {}
420
421 Directory* directory;
422 String name;
423 Atomic<size_t> returned;
424};
425
426struct RetainedLookupReplacementWorkerContext {
427 RetainedLookupReplacementWorkerContext(Directory* directory, const String& oldName,
428 const String& replacementName, File* oldFile,
429 File* replacementFile)
430 : directory(directory),
431 oldName(oldName),
432 replacementName(replacementName),
433 oldFile(oldFile),
434 replacementFile(replacementFile),
435 oldRetained(false),
436 missingPreserved(false),
437 replaced(false),
438 returned(0) {}
439
440 Directory* directory;
441 String oldName;
442 String replacementName;
443 File* oldFile;
444 File* replacementFile;
445 bool oldRetained;
446 bool missingPreserved;
447 bool replaced;
448 Atomic<size_t> returned;
449};
450
451Atomic<RetainedLookupHookContext*> g_RetainedLookupHookContext(nullptr);
452
453void pauseRetainedLookup(Directory* directory, File* file, Directory::RetainedLookupPhase phase) {
454 RetainedLookupHookContext* context = g_RetainedLookupHookContext;
455 if (!context || context->directory != directory) {
456 return;
457 }
458
459 const bool before = phase == Directory::RetainedLookupPhase::BeforeLookup;
460 if ((before && file) || (!before && context->file != file)) {
461 return;
462 }
463 const bool pause = before ? context->pauseBefore : context->pauseAfter;
464 Atomic<size_t>& claimed = before ? context->beforeClaimed : context->afterClaimed;
465 if (!pause || !claimed.compareAndSwap(0, 1)) {
466 return;
467 }
468
469 Atomic<size_t>& entered = before ? context->beforeEntered : context->afterEntered;
470 Atomic<size_t>& returned = before ? context->beforeReturned : context->afterReturned;
471 Semaphore& release = before ? context->beforeRelease : context->afterRelease;
472 if (before) {
473 context->beforeNullFile += 1;
474 }
475 entered += 1;
476 if (release.acquireForCompletion()) {
477 returned += 1;
478 }
479}
480
481int retainedLookupWorker(void* parameter) {
482 RetainedLookupWorkerContext* context = reinterpret_cast<RetainedLookupWorkerContext*>(parameter);
484 context->result = context->directory->lookupRetained(HashedStringView(context->name), child);
485 context->child = child.get();
486 child.reset();
487 context->returned += 1;
488 return context->result ? 0 : 1;
489}
490
491int directoryRemoveWorker(void* parameter) {
492 DirectoryRemoveWorkerContext* context =
493 reinterpret_cast<DirectoryRemoveWorkerContext*>(parameter);
494 context->directory->remove(HashedStringView(context->name));
495 context->returned += 1;
496 return 0;
497}
498
499int retainedLookupReplacementWorker(void* parameter) {
500 RetainedLookupReplacementWorkerContext* context =
501 reinterpret_cast<RetainedLookupReplacementWorkerContext*>(parameter);
503 context->oldRetained =
504 context->directory->lookupRetained(HashedStringView(context->oldName), child) &&
505 child.get() == context->oldFile;
506 if (context->oldRetained) {
507 context->directory->remove(HashedStringView(context->oldName));
508 context->missingPreserved =
509 !context->directory->lookupRetained(HashedStringView("retained-missing"), child) &&
510 child.get() == context->oldFile;
511 }
512 if (context->missingPreserved) {
513 context->replaced =
514 context->directory->lookupRetained(HashedStringView(context->replacementName), child) &&
515 child.get() == context->replacementFile;
516 }
517 child.reset();
518 context->returned += 1;
519 return context->oldRetained && context->missingPreserved && context->replaced ? 0 : 1;
520}
521
522bool waitForDirectoryLock(Thread* thread, const Directory& directory, Atomic<size_t>& returned) {
523 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
524 if (returned) {
525 return false;
526 }
527 Thread::WaitDebugInfo info = {};
528 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
529 info.channelOwner == directory.cacheLockAddressForHostedTest() &&
530 thread->getStatus() == Thread::Sleeping) {
531 return true;
532 }
534 }
535 return false;
536}
537
538bool waitForRetainedLookupPause(Thread* thread, Atomic<size_t>& entered, Semaphore& release,
539 Atomic<size_t>& returned) {
540 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
541 if (returned) {
542 return false;
543 }
544 Thread::WaitDebugInfo info = {};
545 if (entered == static_cast<size_t>(1) && thread->getWaitDebugInfo(info) && info.queue &&
546 info.queued && info.channelOwner == &release && thread->getStatus() == Thread::Sleeping) {
547 return true;
548 }
550 }
551 return false;
552}
553
554size_t drainTrackedFileOwners(File* file, Atomic<size_t>& destructions) {
555 for (size_t release = 1; release <= 8 && !destructions; ++release) {
556 if (VFS::instance().untrackFile(file, false)) {
557 delete file;
558 return release;
559 }
560 }
561 return 0;
562}
563
564bool directoryRetainedLookupRemoval(Process* kernelProcess) {
565 RetainedLookupFilesystem filesystem;
566 RetainedLookupDirectory directory(String("retained-removal-root"), &filesystem);
567 filesystem.setRoot(&directory);
568 Atomic<size_t> destructions(0);
569 const String alias("retained-removal-alias");
570 RetainedLookupFile* child = new RetainedLookupFile(String("retained-removal-child"), &filesystem,
571 &directory, destructions);
572 directory.publish(alias, child);
573 const bool firstEmergencyRetained = VFS::instance().retainTrackedFile(child);
574 const bool secondEmergencyRetained =
575 firstEmergencyRetained && VFS::instance().retainTrackedFile(child);
576
577 RetainedLookupHookContext hook(&directory, child, true, true);
578 RetainedLookupWorkerContext lookupContext(&directory, alias);
579 DirectoryRemoveWorkerContext removeContext(&directory, alias);
580 Thread* lookup =
581 new Thread(kernelProcess, retainedLookupWorker, &lookupContext, nullptr, false, true, true);
582 lookup->setName("hosted retained directory lookup");
583 Thread* remover = nullptr;
584
585 g_RetainedLookupHookContext = &hook;
586 Directory::setRetainedLookupHookForHostedTest(pauseRetainedLookup);
587 const bool lookupStarted = secondEmergencyRetained && lookup->start();
588 const bool beforePaused =
589 lookupStarted && waitForRetainedLookupPause(lookup, hook.beforeEntered, hook.beforeRelease,
590 lookupContext.returned);
591
592 bool removeStarted = false;
593 bool removeQueuedBefore = false;
594 if (beforePaused) {
595 remover = new Thread(kernelProcess, directoryRemoveWorker, &removeContext, nullptr, false, true,
596 true);
597 remover->setName("hosted retained directory remover");
598 removeStarted = remover->start();
599 if (removeStarted) {
600 removeQueuedBefore = waitForDirectoryLock(remover, directory, removeContext.returned);
601 }
602 }
603
604 const bool removalStayedPendingBefore = removeQueuedBefore && !removeContext.returned;
605 hook.beforeRelease.release();
606
607 const bool afterPaused =
608 lookupStarted && waitForRetainedLookupPause(lookup, hook.afterEntered, hook.afterRelease,
609 lookupContext.returned);
610 const bool removeQueuedAfter = afterPaused && removeStarted &&
611 waitForDirectoryLock(remover, directory, removeContext.returned);
612 const bool removalStayedPendingAfter = removeQueuedAfter && !removeContext.returned;
613
614 // Every blocking gate gets a rescue token before completion-safe joins.
615 hook.beforeRelease.release();
616 hook.afterRelease.release();
617 const bool lookupJoined = lookupStarted && lookup->joinForCompletion();
618 const bool removeJoined = removeStarted && remover->joinForCompletion();
619 if (!lookupStarted) {
620 delete lookup;
621 }
622 if (remover && !removeStarted) {
623 delete remover;
624 }
625 Directory::setRetainedLookupHookForHostedTest(nullptr);
626 g_RetainedLookupHookContext = nullptr;
627
628 const bool lookupSucceeded = lookupContext.result && lookupContext.child == child;
629 directory.remove(HashedStringView(alias));
630 const size_t cleanupReleases = drainTrackedFileOwners(child, destructions);
631
632 return firstEmergencyRetained && secondEmergencyRetained && lookupStarted && beforePaused &&
633 removeStarted && removalStayedPendingBefore && afterPaused && removalStayedPendingAfter &&
634 lookupJoined && removeJoined && hook.beforeReturned == static_cast<size_t>(1) &&
635 hook.beforeNullFile == static_cast<size_t>(1) &&
636 hook.afterReturned == static_cast<size_t>(1) && lookupSucceeded &&
637 removeContext.returned == static_cast<size_t>(1) && cleanupReleases == 2 &&
638 destructions == static_cast<size_t>(1);
639}
640
641bool directoryRetainedLazyLookup(Process* kernelProcess) {
642 RetainedLookupFilesystem filesystem;
643 RetainedLookupDirectory directory(String("retained-lazy-root"), &filesystem);
644 filesystem.setRoot(&directory);
645 Atomic<size_t> destructions(0);
646 Atomic<size_t> conversions(0);
647 Atomic<size_t> firstConversionEntered(0);
648 Semaphore firstConversionRelease(0, false);
649 Atomic<size_t> secondConversionEntered(0);
650 Semaphore secondConversionRelease(0, false);
651 const String alias("retained-lazy-alias");
652 RetainedLookupFile* child =
653 new RetainedLookupFile(String("retained-lazy-child"), &filesystem, &directory, destructions);
654 directory.publishLazy(alias, child, conversions, firstConversionEntered, firstConversionRelease,
655 secondConversionEntered, secondConversionRelease);
656
657 RetainedLookupWorkerContext firstContext(&directory, alias);
658 RetainedLookupWorkerContext secondContext(&directory, alias);
659 Thread* first =
660 new Thread(kernelProcess, retainedLookupWorker, &firstContext, nullptr, false, true, true);
661 first->setName("hosted first lazy retained lookup");
662 Thread* second = nullptr;
663 const bool firstStarted = first->start();
664
665 bool firstPaused = false;
666 for (size_t attempt = 0; attempt < HostedAttempts && firstStarted; ++attempt) {
667 Thread::WaitDebugInfo info = {};
668 if (firstConversionEntered == static_cast<size_t>(1) && first->getWaitDebugInfo(info) &&
669 info.queue && info.queued && info.channelOwner == &firstConversionRelease &&
670 first->getStatus() == Thread::Sleeping) {
671 firstPaused = true;
672 break;
673 }
675 }
676
677 bool secondStarted = false;
678 bool secondQueued = false;
679 bool secondConverted = false;
680 if (firstStarted && firstPaused) {
681 second =
682 new Thread(kernelProcess, retainedLookupWorker, &secondContext, nullptr, false, true, true);
683 second->setName("hosted second lazy retained lookup");
684 secondStarted = second->start();
685 if (secondStarted) {
686 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
687 Thread::WaitDebugInfo info = {};
688 if (secondConversionEntered == static_cast<size_t>(1) && second->getWaitDebugInfo(info) &&
689 info.queue && info.queued && info.channelOwner == &secondConversionRelease &&
690 second->getStatus() == Thread::Sleeping) {
691 secondConverted = true;
692 break;
693 }
694 if (second->getWaitDebugInfo(info) && info.queue && info.queued &&
695 info.channelOwner == directory.cacheLockAddressForHostedTest() &&
696 second->getStatus() == Thread::Sleeping) {
697 secondQueued = true;
698 break;
699 }
700 if (secondContext.returned) {
701 break;
702 }
704 }
705 }
706 }
707
708 const bool singleConversionWhilePaused = secondQueued && !secondConverted &&
709 !secondContext.returned &&
710 conversions == static_cast<size_t>(1);
711
712 bool secondFinishedBeforeFirstRelease = false;
713 if (secondConverted) {
714 secondConversionRelease.release();
715 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
716 if (secondContext.returned) {
717 secondFinishedBeforeFirstRelease = true;
718 break;
719 }
721 }
722 }
723
724 // In an unlocked mutant, conversion two completes before conversion one is
725 // allowed to write its LazyEvaluate result.
726 firstConversionRelease.release();
727 firstConversionRelease.release();
728 secondConversionRelease.release();
729 const bool firstJoined = firstStarted && first->joinForCompletion();
730 const bool secondJoined = secondStarted && second->joinForCompletion();
731 if (!firstStarted) {
732 delete first;
733 }
734 if (second && !secondStarted) {
735 delete second;
736 }
737
738 const bool lookupsSucceeded = firstContext.result && secondContext.result &&
739 firstContext.child == child && secondContext.child == child;
740 directory.remove(HashedStringView(alias));
741 const size_t cleanupReleases = drainTrackedFileOwners(child, destructions);
742 if (!destructions) {
743 delete child;
744 }
745
746 return firstStarted && firstPaused && secondStarted && singleConversionWhilePaused &&
747 !secondFinishedBeforeFirstRelease && firstJoined && secondJoined && lookupsSucceeded &&
748 conversions == static_cast<size_t>(1) && cleanupReleases == 0 &&
749 destructions == static_cast<size_t>(1);
750}
751
752bool directoryRetainedLookupDisjoint(Process* kernelProcess) {
753 RetainedLookupFilesystem filesystem;
754 RetainedLookupDirectory firstDirectory(String("retained-disjoint-first"), &filesystem);
755 RetainedLookupDirectory secondDirectory(String("retained-disjoint-second"), &filesystem);
756 filesystem.setRoot(&firstDirectory);
757 Atomic<size_t> destructions(0);
758 const String firstAlias("retained-disjoint-first-alias");
759 const String secondAlias("retained-disjoint-second-alias");
760 RetainedLookupFile* firstChild = new RetainedLookupFile(
761 String("retained-disjoint-first-child"), &filesystem, &firstDirectory, destructions);
762 RetainedLookupFile* secondChild = new RetainedLookupFile(
763 String("retained-disjoint-second-child"), &filesystem, &secondDirectory, destructions);
764 firstDirectory.publish(firstAlias, firstChild);
765 secondDirectory.publish(secondAlias, secondChild);
766
767 RetainedLookupHookContext hook(&firstDirectory, firstChild, false, true);
768 RetainedLookupWorkerContext firstContext(&firstDirectory, firstAlias);
769 RetainedLookupWorkerContext secondContext(&secondDirectory, secondAlias);
770 Thread* first =
771 new Thread(kernelProcess, retainedLookupWorker, &firstContext, nullptr, false, true, true);
772 first->setName("hosted blocked retained lookup");
773 Thread* second = nullptr;
774
775 g_RetainedLookupHookContext = &hook;
776 Directory::setRetainedLookupHookForHostedTest(pauseRetainedLookup);
777 const bool firstStarted = first->start();
778
779 const bool firstPaused =
780 firstStarted && waitForRetainedLookupPause(first, hook.afterEntered, hook.afterRelease,
781 firstContext.returned);
782
783 bool secondStarted = false;
784 bool secondFinishedWhilePaused = false;
785 if (firstStarted && firstPaused) {
786 second =
787 new Thread(kernelProcess, retainedLookupWorker, &secondContext, nullptr, false, true, true);
788 second->setName("hosted disjoint retained lookup");
789 secondStarted = second->start();
790 for (size_t attempt = 0; attempt < HostedAttempts && secondStarted; ++attempt) {
791 if (secondContext.returned) {
792 secondFinishedWhilePaused = true;
793 break;
794 }
796 }
797 }
798
799 hook.beforeRelease.release();
800 hook.afterRelease.release();
801 const bool firstJoined = firstStarted && first->joinForCompletion();
802 const bool secondJoined = secondStarted && second->joinForCompletion();
803 if (!firstStarted) {
804 delete first;
805 }
806 if (second && !secondStarted) {
807 delete second;
808 }
809 Directory::setRetainedLookupHookForHostedTest(nullptr);
810 g_RetainedLookupHookContext = nullptr;
811
812 const bool lookupsSucceeded = firstContext.result && secondContext.result &&
813 firstContext.child == firstChild &&
814 secondContext.child == secondChild;
815 firstDirectory.remove(HashedStringView(firstAlias));
816 secondDirectory.remove(HashedStringView(secondAlias));
817
818 return firstStarted && firstPaused && secondStarted && secondFinishedWhilePaused && firstJoined &&
819 secondJoined && hook.beforeEntered == static_cast<size_t>(0) &&
820 hook.afterReturned == static_cast<size_t>(1) && lookupsSucceeded &&
821 destructions == static_cast<size_t>(2);
822}
823
824bool directoryRetainedLookupDeletion() {
825 RetainedLookupFilesystem filesystem;
826 RetainedLookupDirectory directory(String("retained-delete-root"), &filesystem);
827 filesystem.setRoot(&directory);
828 Atomic<size_t> destructions(0);
829 Atomic<size_t> lockAvailable(0);
830 const String alias("retained-delete-alias");
831 RetainedLookupFile* child =
832 new RetainedLookupFile(String("retained-delete-child"), &filesystem, &directory, destructions,
833 &directory, &lockAvailable);
834 directory.publish(alias, child);
835 directory.remove(HashedStringView(alias));
836 return destructions == static_cast<size_t>(1) && lockAvailable == static_cast<size_t>(1);
837}
838
839bool directoryRetainedLookupReplacement(Process* kernelProcess) {
840 RetainedLookupFilesystem filesystem;
841 RetainedLookupDirectory directory(String("retained-replacement-root"), &filesystem);
842 filesystem.setRoot(&directory);
843 Atomic<size_t> oldDestructions(0);
844 Atomic<size_t> oldLockAvailable(0);
845 Atomic<size_t> replacementDestructions(0);
846 const String oldAlias("retained-replacement-old-alias");
847 const String replacementAlias("retained-replacement-new-alias");
848 RetainedLookupFile* oldFile =
849 new RetainedLookupFile(String("retained-replacement-old"), &filesystem, &directory,
850 oldDestructions, &directory, &oldLockAvailable);
851 RetainedLookupFile* replacement = new RetainedLookupFile(
852 String("retained-replacement-new"), &filesystem, &directory, replacementDestructions);
853 directory.publish(oldAlias, oldFile);
854 directory.publish(replacementAlias, replacement);
855 const bool firstReplacementEmergency = VFS::instance().retainTrackedFile(replacement);
856 const bool secondReplacementEmergency =
857 firstReplacementEmergency && VFS::instance().retainTrackedFile(replacement);
858
859 RetainedLookupHookContext hook(&directory, replacement, false, true);
860 RetainedLookupReplacementWorkerContext workerContext(&directory, oldAlias, replacementAlias,
861 oldFile, replacement);
862 Thread* worker = new Thread(kernelProcess, retainedLookupReplacementWorker, &workerContext,
863 nullptr, false, true, true);
864 worker->setName("hosted retained lookup replacement");
865
866 g_RetainedLookupHookContext = &hook;
867 Directory::setRetainedLookupHookForHostedTest(pauseRetainedLookup);
868 const bool workerStarted = secondReplacementEmergency && worker->start();
869 const bool afterPaused =
870 workerStarted && waitForRetainedLookupPause(worker, hook.afterEntered, hook.afterRelease,
871 workerContext.returned);
872 const bool oldStayedAliveThroughRetain = afterPaused && !oldDestructions;
873
874 hook.afterRelease.release();
875 const bool workerJoined = workerStarted && worker->joinForCompletion();
876 if (!workerStarted) {
877 delete worker;
878 }
879 Directory::setRetainedLookupHookForHostedTest(nullptr);
880 g_RetainedLookupHookContext = nullptr;
881
882 directory.remove(HashedStringView(oldAlias));
883 directory.remove(HashedStringView(replacementAlias));
884 if (!oldDestructions) {
885 drainTrackedFileOwners(oldFile, oldDestructions);
886 }
887 const size_t replacementCleanupReleases =
888 drainTrackedFileOwners(replacement, replacementDestructions);
889
890 return firstReplacementEmergency && secondReplacementEmergency && workerStarted && afterPaused &&
891 oldStayedAliveThroughRetain && workerJoined && workerContext.oldRetained &&
892 workerContext.missingPreserved && workerContext.replaced &&
893 workerContext.returned == static_cast<size_t>(1) &&
894 hook.afterReturned == static_cast<size_t>(1) &&
895 oldDestructions == static_cast<size_t>(1) && oldLockAvailable == static_cast<size_t>(1) &&
896 replacementCleanupReleases == 2 && replacementDestructions == static_cast<size_t>(1);
897}
898
899bool directoryRetainedFailedLazyLookup() {
900 RetainedLookupFilesystem filesystem;
901 RetainedLookupDirectory directory(String("retained-failed-lazy-root"), &filesystem);
902 filesystem.setRoot(&directory);
903 Atomic<size_t> conversions(0);
904 const String alias("retained-failed-lazy-alias");
905
906 // This seed makes any attempt to track a null conversion result observable.
907 VFS::instance().trackFile(nullptr);
908 directory.publishFailedLazy(alias, conversions);
909
912 const bool firstFailed = !directory.lookupRetained(HashedStringView(alias), first);
913 const bool secondFailed = !directory.lookupRetained(HashedStringView(alias), second);
914 directory.remove(HashedStringView(alias));
915
916 const bool seedWasFinal = VFS::instance().untrackFile(nullptr, false);
917 bool mutantExtrasDrained = seedWasFinal;
918 for (size_t release = 0; release < 4 && !mutantExtrasDrained; ++release) {
919 mutantExtrasDrained = VFS::instance().untrackFile(nullptr, false);
920 }
921
922 return firstFailed && secondFailed && !first && !second &&
923 conversions == static_cast<size_t>(2) && seedWasFinal && mutantExtrasDrained;
924}
925
926struct DirectoryMutationSerializationContext {
927 DirectoryMutationSerializationContext(RetainedLookupDirectory* directory, const String& alias,
928 RetainedLookupFile* oldChild,
929 Atomic<size_t>& oldDestructions,
930 RetainedLookupFile* replacement)
931 : directory(directory),
932 alias(alias),
933 oldChild(oldChild),
934 oldDestructions(oldDestructions),
935 replacement(replacement),
936 publisher(nullptr),
937 publishStart(0),
938 publishAttempted(0),
939 publishReturned(0),
940 callbacks(0),
941 firstSawOld(false),
942 oldStayedAliveAfterAliasRemoval(false),
943 publisherBlocked(false) {}
944
945 RetainedLookupDirectory* directory;
946 String alias;
947 RetainedLookupFile* oldChild;
948 Atomic<size_t>& oldDestructions;
949 RetainedLookupFile* replacement;
950 Thread* publisher;
951 Semaphore publishStart;
952 Atomic<size_t> publishAttempted;
953 Atomic<size_t> publishReturned;
954 size_t callbacks;
955 bool firstSawOld;
956 bool oldStayedAliveAfterAliasRemoval;
957 bool publisherBlocked;
958};
959
960int publishDuringDirectoryRemoval(void* opaque) {
961 DirectoryMutationSerializationContext* context =
962 reinterpret_cast<DirectoryMutationSerializationContext*>(opaque);
963 if (!context->publishStart.acquireForCompletion()) {
964 return 1;
965 }
966 context->publishAttempted += 1;
967 context->directory->publish(context->alias, context->replacement);
968 context->publishReturned += 1;
969 return 0;
970}
971
972bool serializeDirectoryMutationRemove(File* parent, File* file, void* opaque) {
973 DirectoryMutationSerializationContext* context =
974 reinterpret_cast<DirectoryMutationSerializationContext*>(opaque);
975 ++context->callbacks;
976 context->firstSawOld =
977 context->callbacks == 1 && parent == context->directory && file == context->oldChild;
978 if (!context->firstSawOld ||
979 !context->directory->removePublished(context->alias, context->oldChild)) {
980 return false;
981 }
982
983 context->oldStayedAliveAfterAliasRemoval = context->oldDestructions == static_cast<size_t>(0);
984 context->publishStart.release();
985 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
986 Thread::WaitDebugInfo info = {};
987 if (context->publishAttempted == static_cast<size_t>(1) &&
988 context->publisher->getWaitDebugInfo(info) && info.queue && info.queued &&
989 info.channelOwner == context->directory->namespaceLockAddress() &&
990 context->publisher->getStatus() == Thread::Sleeping) {
991 context->publisherBlocked = true;
992 break;
993 }
995 }
996 return context->oldStayedAliveAfterAliasRemoval && context->publisherBlocked;
997}
998
999bool directoryMutationSerializesSameKeyReplacement(Process* kernelProcess) {
1000 RetainedLookupFilesystem filesystem;
1001 RetainedLookupDirectory directory(String("retained-mutation-root"), &filesystem);
1002 filesystem.setRoot(&directory);
1003 Atomic<size_t> oldDestructions(0);
1004 Atomic<size_t> replacementDestructions(0);
1005 const String alias("retained-mutation-alias");
1006 RetainedLookupFile* oldChild = new RetainedLookupFile(String("retained-mutation-old"),
1007 &filesystem, &directory, oldDestructions);
1008 RetainedLookupFile* replacement = new RetainedLookupFile(
1009 String("retained-mutation-replacement"), &filesystem, &directory, replacementDestructions);
1010 directory.publish(alias, oldChild);
1011
1012 DirectoryMutationSerializationContext context(&directory, alias, oldChild, oldDestructions,
1013 replacement);
1014 Thread* publisher = new Thread(kernelProcess, publishDuringDirectoryRemoval, &context, nullptr,
1015 false, true, true);
1016 publisher->setName("hosted directory mutation publisher");
1017 context.publisher = publisher;
1018 const bool publisherStarted = publisher->start();
1019
1020 filesystem.setRemoveHook(serializeDirectoryMutationRemove, &context);
1021 const bool removed = publisherStarted && filesystem.remove(&directory, oldChild);
1022 filesystem.setRemoveHook(nullptr, nullptr);
1023 if (!removed) {
1024 context.publishStart.release();
1025 }
1026 const bool publisherJoined = publisherStarted && publisher->joinForCompletion();
1027 if (!publisherStarted) {
1028 delete publisher;
1029 }
1030
1031 Directory::ChildLease retainedReplacement;
1032 const bool replacementVisible =
1033 directory.lookupRetained(HashedStringView(alias), retainedReplacement) &&
1034 retainedReplacement.get() == replacement;
1035
1036 directory.remove(HashedStringView(alias));
1037 retainedReplacement.reset();
1038 if (!oldDestructions) {
1039 drainTrackedFileOwners(oldChild, oldDestructions);
1040 }
1041 if (!replacementDestructions) {
1042 delete replacement;
1043 }
1044
1045 return publisherStarted && removed && publisherJoined && context.callbacks == 1 &&
1046 context.firstSawOld && context.oldStayedAliveAfterAliasRemoval &&
1047 context.publisherBlocked && context.publishAttempted == static_cast<size_t>(1) &&
1048 context.publishReturned == static_cast<size_t>(1) && replacementVisible &&
1049 oldDestructions == static_cast<size_t>(1) &&
1050 replacementDestructions == static_cast<size_t>(1);
1051}
1052
1053bool directoryRetainedDuplicateEphemeral() {
1054 RetainedLookupFilesystem filesystem;
1055 RetainedLookupDirectory directory(String("retained-ephemeral-root"), &filesystem);
1056 filesystem.setRoot(&directory);
1057 Atomic<size_t> originalDestructions(0);
1058 Atomic<size_t> duplicateDestructions(0);
1059 const String name("retained-ephemeral-child");
1060 RetainedLookupFile* original =
1061 new RetainedLookupFile(name, &filesystem, &directory, originalDestructions);
1062 RetainedLookupFile* duplicate =
1063 new RetainedLookupFile(name, &filesystem, &directory, duplicateDestructions);
1064 directory.publish(name, original);
1065
1066 const bool duplicateAdded = directory.publishEphemeral(duplicate);
1067 Directory::ChildLease visible;
1068 const bool originalVisible =
1069 directory.lookupRetained(HashedStringView(name), visible) && visible.get() == original;
1070 const bool duplicateWasTracked = VFS::instance().untrackFile(duplicate, false);
1071
1072 directory.remove(HashedStringView(name));
1073 visible.reset();
1074 const size_t originalCleanupReleases = drainTrackedFileOwners(original, originalDestructions);
1075 if (!duplicateDestructions) {
1076 delete duplicate;
1077 }
1078
1079 return !duplicateAdded && originalVisible && !duplicateWasTracked &&
1080 originalCleanupReleases == 0 && originalDestructions == static_cast<size_t>(1) &&
1081 duplicateDestructions == static_cast<size_t>(1);
1082}
1083
1084bool directoryRetainedLookupAtomicity(Process* kernelProcess) {
1085 const bool removal = directoryRetainedLookupRemoval(kernelProcess);
1086 const bool lazy = directoryRetainedLazyLookup(kernelProcess);
1087 const bool failedLazy = directoryRetainedFailedLazyLookup();
1088 if (!removal || !lazy || !failedLazy) {
1089 ERROR(
1090 "HOSTED-SYSCALL-TEST: FAIL directory-retained-lookup-atomicity: "
1091 "lookup did not linearise child retention with removal and lazy evaluation");
1092 return false;
1093 }
1094 NOTICE("HOSTED-SYSCALL-TEST: PASS directory-retained-lookup-atomicity");
1095 return true;
1096}
1097
1098bool directoryRetainedLookupLifecycle(Process* kernelProcess) {
1099 const bool disjoint = directoryRetainedLookupDisjoint(kernelProcess);
1100 const bool deletion = directoryRetainedLookupDeletion();
1101 const bool replacement = directoryRetainedLookupReplacement(kernelProcess);
1102 const bool mutationSerialization = directoryMutationSerializesSameKeyReplacement(kernelProcess);
1103 const bool duplicateEphemeral = directoryRetainedDuplicateEphemeral();
1104 if (!disjoint || !deletion || !replacement || !mutationSerialization || !duplicateEphemeral) {
1105 ERROR(
1106 "HOSTED-SYSCALL-TEST: FAIL directory-retained-lookup-lifecycle: "
1107 "directory locks were global or child destruction ran while locked");
1108 return false;
1109 }
1110 NOTICE("HOSTED-SYSCALL-TEST: PASS directory-retained-lookup-lifecycle");
1111 return true;
1112}
1113
1114struct TrackedFileRetainHookContext {
1115 explicit TrackedFileRetainHookContext(File* target)
1116 : target(target), release(0, false), claimed(0), entered(0), returned(0) {}
1117
1118 File* target;
1119 Semaphore release;
1120 Atomic<size_t> claimed;
1121 Atomic<size_t> entered;
1122 Atomic<size_t> returned;
1123};
1124
1125struct TrackedFileRetainWorkerContext {
1126 explicit TrackedFileRetainWorkerContext(File* file) : file(file), retained(0), returned(0) {}
1127
1128 File* file;
1129 Atomic<size_t> retained;
1130 Atomic<size_t> returned;
1131};
1132
1133Atomic<TrackedFileRetainHookContext*> g_TrackedFileRetainHookContext(nullptr);
1134
1135void pauseFirstTrackedFileRetain(File* file) {
1136 TrackedFileRetainHookContext* context = g_TrackedFileRetainHookContext;
1137 if (!context || context->target != file || !context->claimed.compareAndSwap(0, 1)) {
1138 return;
1139 }
1140
1141 context->entered += 1;
1142 if (context->release.acquireForCompletion()) {
1143 context->returned += 1;
1144 }
1145}
1146
1147int retainTrackedFileWorker(void* parameter) {
1148 TrackedFileRetainWorkerContext* context =
1149 reinterpret_cast<TrackedFileRetainWorkerContext*>(parameter);
1150 context->retained = VFS::instance().retainTrackedFile(context->file) ? 1 : 0;
1151 context->returned += 1;
1152 return context->retained ? 0 : 1;
1153}
1154
1155bool establishedAliasRetainSerialization(Process* kernelProcess) {
1156 Atomic<size_t> destructions(0);
1157 EstablishedAliasFileProbe* file = new EstablishedAliasFileProbe(destructions);
1158 VFS::instance().trackFile(file);
1159
1160 TrackedFileRetainHookContext hook(file);
1161 TrackedFileRetainWorkerContext workerAContext(file);
1162 TrackedFileRetainWorkerContext workerBContext(file);
1163 Thread* workerA = new Thread(kernelProcess, retainTrackedFileWorker, &workerAContext, nullptr,
1164 false, true, true);
1165 Thread* workerB = nullptr;
1166 workerA->setName("hosted VFS retain A");
1167
1168 g_TrackedFileRetainHookContext = &hook;
1169 VFS::setRetainTrackedFileHookForHostedTest(pauseFirstTrackedFileRetain);
1170 const bool startedA = workerA->start();
1171
1172 bool workerABlocked = false;
1173 for (size_t attempt = 0; attempt < HostedAttempts && startedA; ++attempt) {
1174 Thread::WaitDebugInfo info = {};
1175 if (hook.entered == static_cast<size_t>(1) && workerA->getWaitDebugInfo(info) && info.queue &&
1176 info.queued && info.channelOwner == &hook.release &&
1177 workerA->getStatus() == Thread::Sleeping) {
1178 workerABlocked = true;
1179 break;
1180 }
1182 }
1183
1184 bool startedB = false;
1185 if (startedA) {
1186 workerB = new Thread(kernelProcess, retainTrackedFileWorker, &workerBContext, nullptr, false,
1187 true, true);
1188 workerB->setName("hosted VFS retain B");
1189 startedB = workerB->start();
1190 }
1191 bool workerBCompleted = false;
1192 for (size_t attempt = 0; attempt < HostedAttempts && startedB; ++attempt) {
1193 if (workerBContext.returned) {
1194 workerBCompleted = workerBContext.retained == static_cast<size_t>(1);
1195 break;
1196 }
1198 }
1199
1200 bool passed = startedA && workerABlocked && startedB && workerBCompleted &&
1201 !workerAContext.returned && !hook.returned && !destructions;
1202
1203 hook.release.release();
1204 const bool joinedA = startedA ? workerA->joinForCompletion() : false;
1205 const bool joinedB = startedB ? workerB->joinForCompletion() : false;
1206 if (!startedA) {
1207 delete workerA;
1208 }
1209 if (workerB && !startedB) {
1210 delete workerB;
1211 }
1212 VFS::setRetainTrackedFileHookForHostedTest(nullptr);
1213 g_TrackedFileRetainHookContext = nullptr;
1214
1215 passed = passed && joinedA && joinedB && hook.returned == static_cast<size_t>(1) &&
1216 workerAContext.retained == static_cast<size_t>(1) &&
1217 workerBContext.retained == static_cast<size_t>(1);
1218
1219 const bool firstWasFinal = VFS::instance().untrackFile(file, false);
1220 bool secondWasFinal = false;
1221 if (!firstWasFinal) {
1222 secondWasFinal = VFS::instance().untrackFile(file, false);
1223 }
1224
1225 bool finalDestroyed = false;
1226 if (!firstWasFinal && !secondWasFinal) {
1227 finalDestroyed = VFS::instance().untrackFile(file);
1228 } else {
1229 delete file;
1230 }
1231
1232 passed = passed && !firstWasFinal && !secondWasFinal && finalDestroyed &&
1233 destructions == static_cast<size_t>(1);
1234 if (!destructions) {
1235 repairAliasFileProbe(file, destructions);
1236 }
1237
1238 if (!passed) {
1239 ERROR(
1240 "HOSTED-SYSCALL-TEST: FAIL vfs-established-alias-serialization: "
1241 "a paused established-owner retain blocked another retain or lost ownership");
1242 return false;
1243 }
1244
1245 NOTICE("HOSTED-SYSCALL-TEST: PASS vfs-established-alias-serialization");
1246 return true;
1247}
1248
1249enum DescriptorAliasConstruction {
1250 DescriptorDirect,
1251 DescriptorCopy,
1252 DescriptorPointerCopy,
1253};
1254
1255bool descriptorEstablishedAliasLifetime(DescriptorAliasConstruction construction) {
1256 Atomic<size_t> destructions(0);
1257 EstablishedAliasFileProbe* file = new EstablishedAliasFileProbe(destructions);
1258 FileDescriptor* source = nullptr;
1259
1260 if (construction != DescriptorDirect) {
1261 source = new FileDescriptor(file);
1262 }
1263
1264 VFS::instance().trackFile(file);
1265 VFS::instance().trackFile(file);
1266
1267 FileDescriptor* alias = nullptr;
1268 if (construction == DescriptorDirect) {
1269 alias = new FileDescriptor(file);
1270 } else if (construction == DescriptorCopy) {
1271 alias = new FileDescriptor(*source);
1272 } else {
1273 alias = new FileDescriptor(source);
1274 }
1275
1276 delete source;
1277 VFS::instance().untrackFile(file);
1278 const bool emergencyWasFinal = VFS::instance().untrackFile(file, false);
1279 bool passed = !emergencyWasFinal && !destructions;
1280
1281 delete alias;
1282 passed = passed && destructions == static_cast<size_t>(1);
1283 repairAliasFileProbe(file, destructions);
1284 return passed;
1285}
1286
1287bool establishedFileAliasLifetime() {
1288 const bool directPassed = descriptorEstablishedAliasLifetime(DescriptorDirect);
1289 const bool copyPassed = descriptorEstablishedAliasLifetime(DescriptorCopy);
1290 const bool pointerCopyPassed = descriptorEstablishedAliasLifetime(DescriptorPointerCopy);
1291 bool passed = directPassed && copyPassed && pointerCopyPassed;
1292
1293 Atomic<size_t> destructions(0);
1294 EstablishedAliasFileProbe* untracked = new EstablishedAliasFileProbe(destructions);
1295 FileDescriptor* descriptor = new FileDescriptor(untracked);
1296 const bool descriptorPublishedFile = VFS::instance().untrackFile(untracked, false);
1297 delete descriptor;
1298 passed = passed && !descriptorPublishedFile && !destructions;
1299 repairAliasFileProbe(untracked, destructions);
1300
1301 if (!passed) {
1302 ERROR(
1303 "HOSTED-SYSCALL-TEST: FAIL file-established-alias-lifetime: "
1304 "tracked descriptors did not retain exactly one VFS owner, or an untracked descriptor "
1305 "published a new owner");
1306 return false;
1307 }
1308
1309 NOTICE("HOSTED-SYSCALL-TEST: PASS file-established-alias-lifetime");
1310 return true;
1311}
1312
1313class ContextLifetimePath final : public FilesystemPath {
1314 public:
1315 explicit ContextLifetimePath(File* file) : m_File(file), m_Tracked(file->retainVfsReference()) {}
1316 ~ContextLifetimePath() override {
1317 if (m_Tracked)
1318 m_File->releaseVfsReference();
1319 }
1320 File* node() const override {
1321 return m_File;
1322 }
1323 const void* provider() const override {
1324 return &Provider;
1325 }
1326
1327 private:
1328 static const char Provider;
1329 File* const m_File;
1330 const bool m_Tracked;
1331};
1332const char ContextLifetimePath::Provider = 0;
1333
1334class ContextLifetimeProvider final : public FilesystemContext {
1335 public:
1336 ContextLifetimeProvider(const FilesystemPathRef& root, const FilesystemPathRef& cwd)
1337 : m_Root(root), m_Cwd(cwd) {}
1338 bool snapshot(FilesystemContextSnapshot& result) const override {
1339 if (!m_Registered)
1340 return false;
1341 result.root = m_Root;
1342 result.cwd = m_Cwd;
1343 result.contextGeneration = m_Generation;
1344 return true;
1345 }
1346 bool forkForProcess(FilesystemContextOwner& result) const override {
1347 if (!m_Registered || result)
1348 return false;
1349 auto context = FilesystemContextRef::tryAdopt(new ContextLifetimeProvider(m_Root, m_Cwd));
1350 if (!context)
1351 return false;
1352 result = FilesystemContextOwner::adopt(pedigree_std::move(context));
1353 return true;
1354 }
1355 void retireProcessOwner() override {
1356 assert(m_Registered);
1357 m_Registered = false;
1358 m_Root.reset();
1359 m_Cwd.reset();
1360 }
1361 void replace(const FilesystemPathRef& root, const FilesystemPathRef& cwd) {
1362 m_Root = root;
1363 m_Cwd = cwd;
1364 ++m_Generation;
1365 }
1366
1367 private:
1368 FilesystemPathRef m_Root, m_Cwd;
1369 uint64_t m_Generation = 1;
1370 bool m_Registered = true;
1371};
1372
1373class ContextLifetimeProcess final : public Process {
1374 public:
1375 explicit ContextLifetimeProcess(Process* parent)
1376 : Process(DeferredPublication{}, parent, true, FilesystemContextMode::Deferred) {}
1377 void publishContext() {
1378 publish();
1379 }
1380};
1381
1382bool processFilesystemContextLifetime(Process* kernelProcess) {
1383 TerminationDeferral lifetime;
1384 Atomic<size_t> cwdDestructions(0);
1385 Atomic<size_t> rootDestructions(0);
1386 Atomic<size_t> borrowedCwdDestructions(0);
1387 Atomic<size_t> borrowedRootDestructions(0);
1388 RetainedLookupFilesystem borrowedCwdFilesystem;
1389 RetainedLookupFilesystem borrowedRootFilesystem;
1390 EstablishedAliasFileProbe* cwd = new EstablishedAliasFileProbe(cwdDestructions);
1391 EstablishedAliasFileProbe* root = new EstablishedAliasFileProbe(rootDestructions);
1392 EstablishedAliasFileProbe* borrowedCwd = new EstablishedAliasFileProbe(borrowedCwdDestructions);
1393 EstablishedAliasFileProbe* borrowedRoot = new EstablishedAliasFileProbe(borrowedRootDestructions);
1394 borrowedCwd->setFilesystem(&borrowedCwdFilesystem);
1395 borrowedRoot->setFilesystem(&borrowedRootFilesystem);
1396 borrowedCwdFilesystem.setRoot(borrowedCwd);
1397 borrowedRootFilesystem.setRoot(borrowedRoot);
1398 VFS::instance().trackFile(cwd);
1399 VFS::instance().trackFile(root);
1400
1401 auto* parent = new ContextLifetimeProcess(kernelProcess);
1402 auto context = FilesystemContextRef::tryAdopt(
1403 new ContextLifetimeProvider(FilesystemPathRef::tryAdopt(new ContextLifetimePath(root)),
1404 FilesystemPathRef::tryAdopt(new ContextLifetimePath(cwd))));
1405 FilesystemContextRef retainedContext = context;
1406 const bool installed =
1407 parent->installFilesystemContext(FilesystemContextOwner::adopt(pedigree_std::move(context)));
1408 if (!installed)
1409 FATAL("Hosted context fixture could not prepare its process owner");
1410 parent->publishContext();
1412 const bool snapshotted = installed && retainedContext->snapshot(snapshot);
1413 File* retainedCwd = snapshot.cwd ? snapshot.cwd->node() : nullptr;
1414 File* retainedRoot = snapshot.root ? snapshot.root->node() : nullptr;
1415 Process* child = new Process(parent);
1416 FilesystemContextSnapshot inherited;
1417 auto childContext = child->acquireFilesystemContext();
1418 const bool childInherited = childContext && childContext.get() != retainedContext.get() &&
1419 childContext->snapshot(inherited) && inherited.cwd == snapshot.cwd &&
1420 inherited.root == snapshot.root;
1421 inherited = FilesystemContextSnapshot();
1422 childContext.reset();
1423
1424 const bool cwdNamespaceWasFinal = VFS::instance().untrackFile(cwd, false);
1425 const bool rootNamespaceWasFinal = VFS::instance().untrackFile(root, false);
1426 static_cast<ContextLifetimeProvider*>(retainedContext.get())
1427 ->replace(FilesystemPathRef::tryAdopt(new ContextLifetimePath(borrowedRoot)),
1428 FilesystemPathRef::tryAdopt(new ContextLifetimePath(borrowedCwd)));
1429 const bool borrowedCwdWasPublished = VFS::instance().untrackFile(borrowedCwd, false);
1430 const bool borrowedRootWasPublished = VFS::instance().untrackFile(borrowedRoot, false);
1431 childContext = child->acquireFilesystemContext();
1432 const bool childUnchanged = childContext && childContext->snapshot(inherited) &&
1433 inherited.cwd == snapshot.cwd && inherited.root == snapshot.root;
1434 inherited = FilesystemContextSnapshot();
1435 childContext.reset();
1436 FilesystemContextSnapshot replacement;
1437 const bool parentReplaced = retainedContext->snapshot(replacement) && replacement.cwd &&
1438 replacement.root && replacement.cwd->node() == borrowedCwd &&
1439 replacement.root->node() == borrowedRoot &&
1440 replacement.contextGeneration > snapshot.contextGeneration;
1441 replacement = FilesystemContextSnapshot();
1442
1443 delete child;
1444 const bool snapshotsHeldAcrossReplacement =
1445 retainedCwd == cwd && retainedRoot == root && !cwdDestructions && !rootDestructions;
1446 snapshot = FilesystemContextSnapshot();
1447 const bool inheritedReferencesReleased =
1448 cwdDestructions == static_cast<size_t>(1) && rootDestructions == static_cast<size_t>(1);
1449 delete parent;
1450 const bool retired = !retainedContext->snapshot(snapshot);
1451 retainedContext.reset();
1452 const bool borrowedReferencesSurvived = !borrowedCwdDestructions && !borrowedRootDestructions;
1453 delete borrowedCwd;
1454 delete borrowedRoot;
1455
1456 const bool passed =
1457 installed && snapshotted && childInherited && childUnchanged && parentReplaced && retired &&
1458 !cwdNamespaceWasFinal && !rootNamespaceWasFinal && !borrowedCwdWasPublished &&
1459 !borrowedRootWasPublished && snapshotsHeldAcrossReplacement && inheritedReferencesReleased &&
1460 borrowedReferencesSurvived && borrowedCwdDestructions == static_cast<size_t>(1) &&
1461 borrowedRootDestructions == static_cast<size_t>(1);
1462 if (!passed) {
1463 ERROR(
1464 "HOSTED-SYSCALL-TEST: FAIL process-filesystem-context-lifetime: "
1465 "fork, replacement, or destruction mismanaged a cwd/root VFS owner");
1466 return false;
1467 }
1468
1469 NOTICE("HOSTED-SYSCALL-TEST: PASS process-filesystem-context-lifetime");
1470 return true;
1471}
1472
1473enum MappingAliasConstruction {
1474 MappingDirect,
1475 MappingClone,
1476 MappingSplit,
1477};
1478
1479bool mappingEstablishedAliasLifetime(MappingAliasConstruction construction) {
1480 const size_t pageSize = PhysicalMemoryManager::getPageSize();
1481 Atomic<size_t> destructions(0);
1482 EstablishedAliasFileProbe* file = new EstablishedAliasFileProbe(destructions);
1483 MemoryMappedObject* source = nullptr;
1484
1485 if (construction != MappingDirect) {
1486 source = new MemoryMappedFile(0x100000, pageSize * 2, 0, file, false, MemoryMappedObject::Read);
1487 }
1488
1489 VFS::instance().trackFile(file);
1490 VFS::instance().trackFile(file);
1491
1492 MemoryMappedObject* alias = nullptr;
1493 if (construction == MappingDirect) {
1494 alias = new MemoryMappedFile(0x100000, pageSize, 0, file, false, MemoryMappedObject::Read);
1495 } else if (construction == MappingClone) {
1496 alias = source->clone();
1497 } else {
1498 alias = source->split(0x100000 + pageSize);
1499 }
1500
1501 delete source;
1502 VFS::instance().untrackFile(file);
1503 const bool emergencyWasFinal = VFS::instance().untrackFile(file, false);
1504 bool passed = !emergencyWasFinal && !destructions;
1505
1506 delete alias;
1507 passed = passed && destructions == static_cast<size_t>(1);
1508 repairAliasFileProbe(file, destructions);
1509 return passed;
1510}
1511
1512bool establishedMappingAliasLifetime() {
1513 const bool directPassed = mappingEstablishedAliasLifetime(MappingDirect);
1514 const bool clonePassed = mappingEstablishedAliasLifetime(MappingClone);
1515 const bool splitPassed = mappingEstablishedAliasLifetime(MappingSplit);
1516 bool passed = directPassed && clonePassed && splitPassed;
1517
1518 const size_t pageSize = PhysicalMemoryManager::getPageSize();
1519 Atomic<size_t> destructions(0);
1520 EstablishedAliasFileProbe* untracked = new EstablishedAliasFileProbe(destructions);
1521 MemoryMappedObject* mapping =
1522 new MemoryMappedFile(0x100000, pageSize, 0, untracked, false, MemoryMappedObject::Read);
1523 const bool mappingPublishedFile = VFS::instance().untrackFile(untracked, false);
1524 delete mapping;
1525 passed = passed && !mappingPublishedFile && !destructions;
1526 repairAliasFileProbe(untracked, destructions);
1527
1528 if (!passed) {
1529 ERROR(
1530 "HOSTED-SYSCALL-TEST: FAIL mmap-established-alias-lifetime: "
1531 "tracked mappings did not retain a VFS owner, or an untracked mapping published a new "
1532 "owner");
1533 return false;
1534 }
1535
1536 NOTICE("HOSTED-SYSCALL-TEST: PASS mmap-established-alias-lifetime");
1537 return true;
1538}
1539
1540bool munmapUsesTargetPageGeometry(Thread* thread) {
1541 const size_t pageSize = PhysicalMemoryManager::getPageSize();
1542
1543 thread->setErrno(0);
1544 const int misalignedResult = posix_munmap(reinterpret_cast<void*>(pageSize / 2), pageSize);
1545 const bool rejectedMisaligned =
1546 misalignedResult == -1 && thread->getErrno() == Error::InvalidArgument;
1547
1548 thread->setErrno(0);
1549 const int alignedResult = posix_munmap(reinterpret_cast<void*>(pageSize), pageSize);
1550 const bool acceptedAligned = alignedResult == 0 && !thread->getErrno();
1551 thread->setErrno(0);
1552
1553 if (!rejectedMisaligned || !acceptedAligned) {
1554 ERROR(
1555 "HOSTED-SYSCALL-TEST: FAIL munmap-target-page-geometry: "
1556 "munmap did not validate addresses against the target page size");
1557 return false;
1558 }
1559
1560 NOTICE("HOSTED-SYSCALL-TEST: PASS munmap-target-page-geometry");
1561 return true;
1562}
1563
1564bool mappingManagerSplitLifetime(Process* process, bool exactSuffix) {
1565 const size_t pageSize = PhysicalMemoryManager::getPageSize();
1566 const size_t mappingLength = pageSize * 3;
1567 uintptr_t address = 0;
1568 if (!process->allocateUserRange(Process::UserRegion::Normal, mappingLength, address)) {
1569 return false;
1570 }
1571
1572 Atomic<size_t> destructions(0);
1573 EstablishedAliasFileProbe* file = new EstablishedAliasFileProbe(destructions);
1574 VFS::instance().trackFile(file);
1575 VFS::instance().trackFile(file);
1576
1577 uintptr_t mappedAddress = address;
1579 file, mappedAddress, mappingLength, MemoryMappedObject::Read);
1580 bool passed = mapping && mappedAddress == address;
1581 if (mapping) {
1582 const size_t removedMiddleOrSuffix = MemoryMapManager::instance().remove(
1583 address + pageSize, exactSuffix ? pageSize * 2 : pageSize);
1584 const size_t removedPrefix = MemoryMapManager::instance().remove(address, pageSize);
1585 size_t removedTail = 1;
1586 if (!exactSuffix) {
1587 removedTail = MemoryMapManager::instance().remove(address + pageSize * 2, pageSize);
1588 }
1589 passed = passed && removedMiddleOrSuffix == 1 && removedPrefix == 1 && removedTail == 1;
1590 }
1591
1592 MemoryMapManager::instance().remove(address, mappingLength);
1593 process->freeUserRange(Process::UserRegion::Normal, address, mappingLength);
1594 const bool namespaceWasFinal = VFS::instance().untrackFile(file);
1595 const bool emergencyWasFinal = VFS::instance().untrackFile(file);
1596 passed =
1597 passed && !namespaceWasFinal && emergencyWasFinal && destructions == static_cast<size_t>(1);
1598 repairAliasFileProbe(file, destructions);
1599 return passed;
1600}
1601
1602bool mappingManagerSplitLifetime(Process* process) {
1603 const bool exactSuffixPassed = mappingManagerSplitLifetime(process, true);
1604 const bool middlePassed = mappingManagerSplitLifetime(process, false);
1605 const bool passed = exactSuffixPassed && middlePassed;
1606 if (!passed) {
1607 ERROR(
1608 "HOSTED-SYSCALL-TEST: FAIL mmap-split-alias-lifetime: "
1609 "an exact-suffix or middle removal leaked a file-backed mapping owner");
1610 return false;
1611 }
1612
1613 NOTICE("HOSTED-SYSCALL-TEST: PASS mmap-split-alias-lifetime");
1614 return true;
1615}
1616
1617bool posixPathLookupLifetime(Process* kernelProcess) {
1619 auto* priorView = VFS::instance().mountView();
1620 VFS::HostedRootViewScope fixture;
1621 UnixFilesystem* testFilesystem = new UnixFilesystem;
1622 if (!fixture.open(testFilesystem)) {
1623 delete testFilesystem;
1624 return false;
1625 }
1626 FilesystemPathRef rootPath;
1627 const bool rootInstalled = fixture.view()->bootRootPath(rootPath);
1628 const String name("hosted-established-alias-path-cache");
1629 const String path("/hosted-established-alias-path-cache");
1630 const bool created = rootInstalled && fixture.view()->createFile(rootPath, name, 0600);
1631
1632 Process* process =
1633 new PosixProcess(kernelProcess, true, Process::FilesystemContextMode::Deferred);
1634 PosixSubsystem* subsystem = new PosixSubsystem;
1635 process->setSubsystem(subsystem);
1636 subsystem->setAbi(PosixSubsystem::LinuxAbi);
1637 const bool contextInstalled = fixture.installContext(*process);
1638 ResolvedPath originalLease, resolvedLease;
1639 File* original = created && contextInstalled
1640 ? subsystem->findFileRetained(path, originalLease, rootPath)
1641 : nullptr;
1642 const bool originalRemoved = original && fixture.view()->remove(rootPath, name, original);
1643 const bool recreated = originalRemoved && fixture.view()->createFile(rootPath, name, 0600);
1644 Directory::ChildLease replacementLease;
1645 File* replacement = nullptr;
1646 if (recreated && Directory::fromFile(rootPath->node())
1647 ->lookupChild(HashedStringView(name), replacementLease) ==
1648 Directory::LookupStatus::Found)
1649 replacement = replacementLease.get();
1650 File* resolved = recreated ? subsystem->findFileRetained(path, resolvedLease, rootPath) : nullptr;
1651 const bool resolvedReplacement =
1652 replacement && replacement != original && resolved == replacement;
1653 const bool pathCleaned = replacement && fixture.view()->remove(rootPath, name, replacement);
1654 replacementLease.reset();
1655 resolvedLease.reset();
1656 originalLease.reset();
1657 rootPath.reset();
1658 delete process;
1659 const bool rootRestored = fixture.close();
1660 if (!rootRestored)
1661 FATAL("Hosted pathname fixture retained owners after teardown");
1662 const bool previousRestored =
1663 VFS::instance().getRootFilesystem() == priorRoot && VFS::instance().mountView() == priorView;
1664 delete testFilesystem;
1665
1666 const bool passed = rootInstalled && contextInstalled && created && original && originalRemoved &&
1667 recreated && resolvedReplacement && pathCleaned && rootRestored &&
1668 previousRestored;
1669 if (!passed) {
1670 ERROR(
1671 "HOSTED-SYSCALL-TEST: FAIL posix-path-lookup-lifetime: "
1672 "a removed pathname resolved to its retired cached File object");
1673 return false;
1674 }
1675
1676 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-path-lookup-lifetime");
1677 return true;
1678}
1679
1680class PollGenerationProbe : public NetworkSyscalls {
1681 public:
1682 PollGenerationProbe(Atomic<size_t>& queries, Atomic<size_t>& notifications,
1683 Atomic<size_t>& destructions)
1684 : NetworkSyscalls(AF_UNSPEC, SOCK_STREAM, 0),
1685 m_Queries(queries),
1686 m_Notifications(notifications),
1687 m_Destructions(destructions),
1688 m_Ready(0) {}
1689
1690 ~PollGenerationProbe() override {
1691 m_Destructions += 1;
1692 }
1693
1694 int connect(const struct sockaddr_storage*, socklen_t) override {
1695 return -1;
1696 }
1697
1698 ssize_t sendto_msg(const struct msghdr*, const SharedPointer<SocketRights>&) override {
1699 return -1;
1700 }
1701
1702 ssize_t recvfrom_msg(struct msghdr*, SharedPointer<SocketRights>*) override {
1703 return -1;
1704 }
1705
1706 int listen(int) override {
1707 return -1;
1708 }
1709
1710 int bind(const struct sockaddr_storage*, socklen_t) override {
1711 return -1;
1712 }
1713
1714 int accept(struct sockaddr_storage*, socklen_t*, int, DescriptorLease*) override {
1715 return -1;
1716 }
1717
1718 int getpeername(struct sockaddr_storage*, socklen_t*) override {
1719 return -1;
1720 }
1721
1722 int getsockname(struct sockaddr_storage*, socklen_t*) override {
1723 return -1;
1724 }
1725
1726 int setsockopt(int, int, const void*, socklen_t) override {
1727 return -1;
1728 }
1729
1730 int getsockopt(int, int, void*, socklen_t*) override {
1731 return -1;
1732 }
1733
1734 ReadyMask queryReady(bool reading, bool writing) override {
1735 m_Queries += 1;
1736 ReadyMask ready = ReadyNone;
1737 if (reading && m_Ready) {
1738 ready |= ReadyRead;
1739 }
1740 if (writing) {
1741 ready |= ReadyWrite;
1742 }
1743 return ready;
1744 }
1745
1746 void makeReadable() {
1747 m_Ready = 1;
1748 m_Notifications += 1;
1749 notifyReadiness(ReadyRead);
1750 }
1751
1752 private:
1753 Atomic<size_t>& m_Queries;
1754 Atomic<size_t>& m_Notifications;
1755 Atomic<size_t>& m_Destructions;
1756 Atomic<size_t> m_Ready;
1757};
1758
1759struct DescriptorCloseContext {
1760 DescriptorCloseContext(PosixSubsystem* subsystem, size_t fd)
1761 : subsystem(subsystem),
1762 fd(fd),
1763 release(0, false),
1764 entered(0),
1765 acquired(0),
1766 usedAfterClose(0),
1767 returned(0) {}
1768
1769 PosixSubsystem* subsystem;
1770 size_t fd;
1771 Semaphore release;
1772 Atomic<size_t> entered;
1773 Atomic<size_t> acquired;
1774 Atomic<size_t> usedAfterClose;
1775 Atomic<size_t> returned;
1776};
1777
1778int holdDescriptorAcrossBlock(void* parameter) {
1779 DescriptorCloseContext* context = reinterpret_cast<DescriptorCloseContext*>(parameter);
1780 DescriptorLease descriptor;
1781 context->acquired = context->subsystem->acquireFileDescriptor(context->fd, descriptor) ? 1 : 0;
1782 context->entered += 1;
1783
1784 if (!context->release.acquireForCompletion()) {
1785 context->returned += 1;
1786 return 1;
1787 }
1788
1789 if (descriptor && descriptor->fd == context->fd) {
1790 context->usedAfterClose += 1;
1791 }
1792 context->returned += 1;
1793 return 0;
1794}
1795
1796bool descriptorClosePinning(Process* kernelProcess) {
1797 constexpr size_t DescriptorNumber = 37;
1798 Process* process = new Process(kernelProcess);
1799 PosixSubsystem* subsystem = new PosixSubsystem;
1800 process->setSubsystem(subsystem);
1801
1802 Atomic<size_t> destructions(0);
1803 DescriptorRetirementProbe* probe = new DescriptorRetirementProbe(destructions);
1804 probe->fd = DescriptorNumber;
1805 subsystem->addFileDescriptor(DescriptorNumber, probe);
1806
1807 DescriptorCloseContext context(subsystem, DescriptorNumber);
1808 Thread* worker =
1809 new Thread(kernelProcess, holdDescriptorAcrossBlock, &context, nullptr, false, true, true);
1810 worker->setName("hosted descriptor pin holder");
1811
1812 bool passed = worker->start();
1813 bool blocked = false;
1814 for (size_t attempt = 0; attempt < HostedAttempts && passed; ++attempt) {
1815 Thread::WaitDebugInfo info = {};
1816 if (context.entered && worker->getWaitDebugInfo(info) && info.queue && info.queued &&
1817 info.channelOwner == &context.release && worker->getStatus() == Thread::Sleeping) {
1818 blocked = true;
1819 break;
1820 }
1822 }
1823
1824 passed = passed && blocked && context.acquired == 1;
1825 DescriptorLease closing;
1826 const bool closeAcquired = subsystem->acquireFileDescriptor(DescriptorNumber, closing);
1827 const bool closed = closeAcquired && subsystem->closeFileDescriptor(DescriptorNumber, closing);
1828 closing.reset();
1829 passed = passed && closed;
1830
1831 DescriptorLease unpublished;
1832 passed = passed && !subsystem->acquireFileDescriptor(DescriptorNumber, unpublished) &&
1833 destructions == 0;
1834
1835 context.release.release();
1836 passed = worker->join() && passed;
1837 passed = passed && context.returned == 1 && context.usedAfterClose == 1 && destructions == 1;
1838
1839 delete process;
1840
1841 if (!passed) {
1842 ERROR(
1843 "HOSTED-SYSCALL-TEST: FAIL descriptor-close-pinning: "
1844 "close did not unpublish immediately while retaining the active "
1845 "operation");
1846 return false;
1847 }
1848
1849 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-close-pinning");
1850 return true;
1851}
1852
1853bool descriptorCloseGeneration(Process* kernelProcess) {
1854 constexpr size_t DescriptorNumber = 38;
1855 Process* process = new Process(kernelProcess);
1856 PosixSubsystem* subsystem = new PosixSubsystem;
1857 process->setSubsystem(subsystem);
1858
1859 Atomic<size_t> oldDestructions(0);
1860 Atomic<size_t> replacementDestructions(0);
1861 DescriptorRetirementProbe* oldDescriptor = new DescriptorRetirementProbe(oldDestructions);
1862 oldDescriptor->fd = DescriptorNumber;
1863 oldDescriptor->setOffset(1);
1864 subsystem->addFileDescriptor(DescriptorNumber, oldDescriptor);
1865
1866 DescriptorLease oldLease;
1867 bool passed = subsystem->acquireFileDescriptor(DescriptorNumber, oldLease);
1868
1869 DescriptorRetirementProbe* replacement = new DescriptorRetirementProbe(replacementDestructions);
1870 replacement->fd = DescriptorNumber;
1871 replacement->setOffset(2);
1872 subsystem->addFileDescriptor(DescriptorNumber, replacement);
1873
1874 // An in-flight close of the old generation must not remove a descriptor
1875 // which has since reused the same numeric fd.
1876 passed = passed && !subsystem->closeFileDescriptor(DescriptorNumber, oldLease) &&
1877 oldDestructions == 0 && replacementDestructions == 0;
1878
1879 DescriptorLease replacementLease;
1880 passed = passed && subsystem->acquireFileDescriptor(DescriptorNumber, replacementLease) &&
1881 replacementLease->getOffset() == 2;
1882
1883 oldLease.reset();
1884 passed = passed && oldDestructions == 1 && replacementDestructions == 0;
1885
1886 const bool replacementClosed = subsystem->closeFileDescriptor(DescriptorNumber, replacementLease);
1887 DescriptorLease unpublished;
1888 passed = passed && replacementClosed &&
1889 !subsystem->acquireFileDescriptor(DescriptorNumber, unpublished) &&
1890 replacementDestructions == 0;
1891 replacementLease.reset();
1892 passed = passed && replacementDestructions == 1;
1893
1894 delete process;
1895
1896 if (!passed) {
1897 ERROR(
1898 "HOSTED-SYSCALL-TEST: FAIL descriptor-close-generation: "
1899 "an old close removed a reused descriptor generation");
1900 return false;
1901 }
1902
1903 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-close-generation");
1904 return true;
1905}
1906
1907struct DescriptorPositionContext {
1908 explicit DescriptorPositionContext(FileDescriptor* descriptor)
1909 : descriptor(descriptor), entered(0), acquired(0), observed(0) {}
1910
1911 FileDescriptor* descriptor;
1912 Atomic<size_t> entered;
1913 Atomic<size_t> acquired;
1914 Atomic<size_t> observed;
1915};
1916
1917class DescriptorPositionFile final : public File {
1918 public:
1919 explicit DescriptorPositionFile(bool seekable)
1920 : File(String("position-policy"), 0, 0, 0, 1, nullptr, 0, nullptr),
1921 m_Seekable(seekable),
1922 m_ReadOffset(~static_cast<uint64_t>(0)),
1923 m_WriteOffset(~static_cast<uint64_t>(0)),
1924 m_ReadCanBlock(true),
1925 m_WriteCanBlock(true) {}
1926
1927 bool isSeekable() const override {
1928 return m_Seekable;
1929 }
1930
1931 uint64_t readOffset() const {
1932 return m_ReadOffset;
1933 }
1934
1935 uint64_t writeOffset() const {
1936 return m_WriteOffset;
1937 }
1938
1939 bool readCanBlock() const {
1940 return m_ReadCanBlock;
1941 }
1942
1943 bool writeCanBlock() const {
1944 return m_WriteCanBlock;
1945 }
1946
1947 protected:
1948 bool isBytewise() const override {
1949 return true;
1950 }
1951
1952 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t, bool canBlock) override {
1953 m_ReadOffset = location;
1954 m_ReadCanBlock = canBlock;
1955 return size;
1956 }
1957
1958 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t, bool canBlock) override {
1959 m_WriteOffset = location;
1960 m_WriteCanBlock = canBlock;
1961 return size;
1962 }
1963
1964 private:
1965 bool m_Seekable;
1966 uint64_t m_ReadOffset;
1967 uint64_t m_WriteOffset;
1968 bool m_ReadCanBlock;
1969 bool m_WriteCanBlock;
1970};
1971
1972class DescriptorAppendFile final : public File {
1973 public:
1974 DescriptorAppendFile()
1975 : File(String("append-policy"), 0, 0, 0, 1, nullptr, 10, nullptr),
1976 m_WriteCount(0),
1977 m_WriteOffsets{0, 0, 0} {}
1978
1979 uint64_t writeOffset(size_t index) const {
1980 return m_WriteOffsets[index];
1981 }
1982
1983 size_t writeCount() const {
1984 return m_WriteCount;
1985 }
1986
1987 protected:
1988 bool isBytewise() const override {
1989 return true;
1990 }
1991
1992 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t, bool) override {
1993 if (m_WriteCount < 3) {
1994 m_WriteOffsets[m_WriteCount] = location;
1995 }
1996 ++m_WriteCount;
1997 if (location + size > getSize()) {
1998 setSize(location + size);
1999 }
2000 return size;
2001 }
2002
2003 private:
2004 size_t m_WriteCount;
2005 uint64_t m_WriteOffsets[3];
2006};
2007
2008class ConcurrentDescriptorAppendFile final : public File {
2009 public:
2010 ConcurrentDescriptorAppendFile()
2011 : File(String("concurrent-append-policy"), 0, 0, 0, 1, nullptr, 10, nullptr),
2012 m_FirstWriteEntered(0, false),
2013 m_ReleaseFirstWrite(0, false),
2014 m_WriteCount(0),
2015 m_WriteOffsets{0, 0} {}
2016
2017 bool waitForFirstWrite() {
2018 return m_FirstWriteEntered.acquireForCompletion();
2019 }
2020
2021 void releaseFirstWrite() {
2022 m_ReleaseFirstWrite.release();
2023 }
2024
2025 size_t writeCount() const {
2026 return m_WriteCount;
2027 }
2028
2029 uint64_t writeOffset(size_t index) const {
2030 return m_WriteOffsets[index];
2031 }
2032
2033 protected:
2034 bool isBytewise() const override {
2035 return true;
2036 }
2037
2038 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t, bool) override {
2039 const size_t slot = (m_WriteCount += 1) - 1;
2040 if (slot < 2) {
2041 m_WriteOffsets[slot] = location;
2042 }
2043 if (!slot) {
2044 m_FirstWriteEntered.release();
2045 if (!m_ReleaseFirstWrite.acquireForCompletion()) {
2046 return 0;
2047 }
2048 }
2049 if (location + size > getSize()) {
2050 setSize(location + size);
2051 }
2052 return size;
2053 }
2054
2055 private:
2056 Semaphore m_FirstWriteEntered;
2057 Semaphore m_ReleaseFirstWrite;
2058 Atomic<size_t> m_WriteCount;
2059 uint64_t m_WriteOffsets[2];
2060};
2061
2062struct ConcurrentDescriptorAppendContext {
2063 explicit ConcurrentDescriptorAppendContext(FileDescriptor* descriptor)
2064 : descriptor(descriptor), entered(0), returned(0), result(0) {}
2065
2066 FileDescriptor* descriptor;
2067 Atomic<size_t> entered;
2068 Atomic<size_t> returned;
2069 Atomic<uint64_t> result;
2070};
2071
2072int appendThroughDescriptor(void* parameter) {
2073 ConcurrentDescriptorAppendContext* context =
2074 reinterpret_cast<ConcurrentDescriptorAppendContext*>(parameter);
2075 char byte = 0;
2076 context->entered += 1;
2077 context->result = context->descriptor->write(1, reinterpret_cast<uintptr_t>(&byte));
2078 context->returned += 1;
2079 return context->result == 1 ? 0 : 1;
2080}
2081
2082bool concurrentDescriptorAppendSerialization(Process* kernelProcess, bool positionedSecond) {
2083 ConcurrentDescriptorAppendFile file;
2084 FileDescriptor first(&file, 0, 0, 0, O_WRONLY | O_APPEND);
2085 FileDescriptor second(&file, positionedSecond ? 20 : 0, 0, 0,
2086 positionedSecond ? O_WRONLY : O_WRONLY | O_APPEND);
2087 ConcurrentDescriptorAppendContext firstContext(&first);
2088 ConcurrentDescriptorAppendContext secondContext(&second);
2089
2090 Thread* firstWorker =
2091 new Thread(kernelProcess, appendThroughDescriptor, &firstContext, nullptr, false, true, true);
2092 Thread* secondWorker = new Thread(kernelProcess, appendThroughDescriptor, &secondContext, nullptr,
2093 false, true, true);
2094 firstWorker->setName("hosted first independent append");
2095 if (positionedSecond) {
2096 secondWorker->setName("hosted positioned append race");
2097 } else {
2098 secondWorker->setName("hosted second independent append");
2099 }
2100
2101 const bool firstStarted = firstWorker->start();
2102 const bool firstEntered = firstStarted && file.waitForFirstWrite();
2103 const bool secondStarted = firstEntered && secondWorker->start();
2104 bool secondBlocked = false;
2105 for (size_t attempt = 0; attempt < HostedAttempts && secondStarted; ++attempt) {
2106 Thread::WaitDebugInfo info = {};
2107 uintptr_t debugAddress = 0;
2108 if (secondContext.entered && !secondContext.returned && file.writeCount() == 1 &&
2109 secondWorker->getWaitDebugInfo(info) && info.queued &&
2110 secondWorker->getDebugState(debugAddress) == Thread::SemWait) {
2111 secondBlocked = true;
2112 break;
2113 }
2115 }
2116
2117 file.releaseFirstWrite();
2118 const bool firstJoined = firstStarted && firstWorker->joinForCompletion();
2119 const bool secondJoined = secondStarted && secondWorker->joinForCompletion();
2120 if (!firstStarted) {
2121 delete firstWorker;
2122 }
2123 if (!secondStarted) {
2124 delete secondWorker;
2125 }
2126
2127 const bool separateDescriptions =
2128 first.acquireOpenFileDescription().get() != second.acquireOpenFileDescription().get();
2129 const uint64_t expectedSecondOffset = positionedSecond ? 20 : 11;
2130 const uint64_t expectedSize = positionedSecond ? 21 : 12;
2131 const uint64_t expectedSecondPosition = positionedSecond ? 21 : 12;
2132 return firstStarted && firstEntered && secondStarted && secondBlocked && firstJoined &&
2133 secondJoined && firstContext.returned == 1 && secondContext.returned == 1 &&
2134 firstContext.result == 1 && secondContext.result == 1 && separateDescriptions &&
2135 file.writeCount() == 2 && file.writeOffset(0) == 10 &&
2136 file.writeOffset(1) == expectedSecondOffset && file.getSize() == expectedSize &&
2137 first.getOffset() == 11 && second.getOffset() == expectedSecondPosition;
2138}
2139
2140bool independentDescriptorAppendSerialization(Process* kernelProcess) {
2141 return concurrentDescriptorAppendSerialization(kernelProcess, false) &&
2142 concurrentDescriptorAppendSerialization(kernelProcess, true);
2143}
2144
2145bool descriptorOpenFileDescriptionState() {
2146 DescriptorPositionFile file(true);
2147 FileDescriptor source(&file, 0, 19, 0, O_RDWR | O_CLOEXEC);
2148 FileDescriptor alias(source);
2149 alias.setFlags(0);
2150 FileDescriptor::OpenFileDescriptionLease sourceDescription = source.acquireOpenFileDescription();
2151 FileDescriptor::OpenFileDescriptionLease aliasDescription = alias.acquireOpenFileDescription();
2152
2153 source.addStatusFlag(O_NONBLOCK | O_CLOEXEC);
2154 bool passed = source.getFlags() == FD_CLOEXEC && alias.getFlags() == 0 &&
2155 source.getStatusFlags() == (O_RDWR | O_NONBLOCK) &&
2156 alias.getStatusFlags() == (O_RDWR | O_NONBLOCK) &&
2157 sourceDescription == aliasDescription && sourceDescription->getFile() == &file &&
2158 !sourceDescription->getNetworkImpl() &&
2159 sourceDescription->descriptorOwnerCount() == 2;
2160
2161 {
2162 FileDescriptor third(alias);
2163 passed = passed && sourceDescription->descriptorOwnerCount() == 3;
2164 }
2165 passed = passed && sourceDescription->descriptorOwnerCount() == 2;
2166
2167 alias.setStatusFlags(O_APPEND | O_CLOEXEC);
2168 passed = passed && source.getStatusFlags() == (O_RDWR | O_APPEND) &&
2169 alias.getStatusFlags() == (O_RDWR | O_APPEND) && source.getFlags() == FD_CLOEXEC &&
2170 alias.getFlags() == 0;
2171
2172 source.removeStatusFlag(O_APPEND);
2173 passed = passed && source.getStatusFlags() == O_RDWR && alias.getStatusFlags() == O_RDWR;
2174
2175 if (!passed) {
2176 ERROR(
2177 "HOSTED-SYSCALL-TEST: FAIL descriptor-open-file-description-state: "
2178 "status flags were descriptor-local, access mode was lost, or CLOEXEC entered shared "
2179 "state");
2180 return false;
2181 }
2182
2183 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-open-file-description-state");
2184 return true;
2185}
2186
2187bool descriptorOpenFileDescriptionLifetime() {
2188 Atomic<size_t> fileDestructions(0);
2189 EstablishedAliasFileProbe* file = new EstablishedAliasFileProbe(fileDestructions);
2190 VFS::instance().trackFile(file);
2191 FileDescriptor* descriptor = new FileDescriptor(file, 0, 0, 0, O_RDWR);
2192 const bool baselineWasFinal = VFS::instance().untrackFile(file, false);
2194 descriptor->acquireOpenFileDescription();
2195
2196 delete descriptor;
2197 bool passed = !baselineWasFinal && !fileDestructions &&
2198 fileDescription->descriptorOwnerCount() == 0 && fileDescription->getFile() == file;
2199 fileDescription.reset();
2200 passed = passed && fileDestructions == 1;
2201 repairAliasFileProbe(file, fileDestructions);
2202
2203 Atomic<size_t> registrations(0);
2204 Atomic<size_t> unpolls(0);
2205 Atomic<size_t> networkDestructions(0);
2206 FileDescriptor* socketDescriptor = new FileDescriptor;
2208 new PollGenerationProbe(registrations, unpolls, networkDestructions));
2209 socketDescriptor->setNetworkImpl(network);
2210 network.reset();
2212 socketDescriptor->acquireOpenFileDescription();
2213
2214 delete socketDescriptor;
2215 passed = passed && !networkDestructions && socketDescription->descriptorOwnerCount() == 0 &&
2216 socketDescription->getNetworkImpl();
2217 socketDescription.reset();
2218 passed = passed && networkDestructions == 1;
2219
2220 if (!passed) {
2221 ERROR(
2222 "HOSTED-SYSCALL-TEST: FAIL descriptor-open-file-description-lifetime: "
2223 "an OFD lease did not retain its target independently of descriptor aliases");
2224 return false;
2225 }
2226
2227 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-open-file-description-lifetime");
2228 return true;
2229}
2230
2231bool descriptorAppendPolicy(Process* kernelProcess) {
2232 char byte = 0;
2233 DescriptorAppendFile file;
2234 FileDescriptor source(&file, 2, 0, 0, O_WRONLY | O_APPEND);
2235 FileDescriptor alias(source);
2236
2237 const bool firstWrite = source.write(3, reinterpret_cast<uintptr_t>(&byte)) == 3;
2238 source.setOffset(1);
2239 const bool secondWrite = alias.write(2, reinterpret_cast<uintptr_t>(&byte)) == 2;
2240
2241 alias.setStatusFlags(0);
2242 source.setOffset(5);
2243 const bool positionedWrite = source.write(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
2244
2245 const bool independentSerialization = independentDescriptorAppendSerialization(kernelProcess);
2246 const bool passed = firstWrite && secondWrite && positionedWrite && independentSerialization &&
2247 file.writeCount() == 3 && file.writeOffset(0) == 10 &&
2248 file.writeOffset(1) == 13 && file.writeOffset(2) == 5 &&
2249 file.getSize() == 15 && source.getOffset() == 6 && alias.getOffset() == 6 &&
2250 source.getStatusFlags() == O_WRONLY;
2251 if (!passed) {
2252 ERROR(
2253 "HOSTED-SYSCALL-TEST: FAIL descriptor-append-policy: "
2254 "append did not serialize EOF selection across open descriptions or update the shared "
2255 "offset");
2256 return false;
2257 }
2258
2259 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-append-policy");
2260 return true;
2261}
2262
2263bool descriptorNonblockingPolicy() {
2264 char byte = 0;
2265 DescriptorPositionFile sequential(false);
2266 FileDescriptor source(&sequential, 0, 0, 0, O_RDWR | O_NONBLOCK);
2267 FileDescriptor alias(source);
2268
2269 const bool nonblockingRead = source.read(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
2270 const bool nonblockingWrite = source.write(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
2271 bool passed = nonblockingRead && nonblockingWrite && !sequential.readCanBlock() &&
2272 !sequential.writeCanBlock();
2273
2274 alias.removeStatusFlag(O_NONBLOCK);
2275 const bool blockingRead = source.read(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
2276 const bool blockingWrite = source.write(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
2277 passed = passed && blockingRead && blockingWrite && sequential.readCanBlock() &&
2278 sequential.writeCanBlock() && source.getStatusFlags() == O_RDWR &&
2279 alias.getStatusFlags() == O_RDWR;
2280
2281 if (!passed) {
2282 ERROR(
2283 "HOSTED-SYSCALL-TEST: FAIL descriptor-nonblocking-policy: "
2284 "O_NONBLOCK did not reach file I/O or did not propagate across aliases");
2285 return false;
2286 }
2287
2288 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-nonblocking-policy");
2289 return true;
2290}
2291
2292bool descriptorPositionPolicy() {
2293 char byte = 0;
2294 DescriptorPositionFile seekable(true);
2295 FileDescriptor positioned(&seekable, 40, 0, 0, O_RDWR);
2296 const bool positionedPassed = positioned.read(1, reinterpret_cast<uintptr_t>(&byte)) == 1 &&
2297 positioned.write(1, reinterpret_cast<uintptr_t>(&byte)) == 1 &&
2298 seekable.readOffset() == 40 && seekable.writeOffset() == 41 &&
2299 positioned.getOffset() == 42;
2300
2301 DescriptorPositionFile sequential(false);
2302 FileDescriptor unpositioned(&sequential, 40, 0, 0, O_RDWR);
2303 const bool unpositionedPassed = unpositioned.read(1, reinterpret_cast<uintptr_t>(&byte)) == 1 &&
2304 unpositioned.write(1, reinterpret_cast<uintptr_t>(&byte)) == 1 &&
2305 sequential.readOffset() == 0 && sequential.writeOffset() == 0 &&
2306 unpositioned.getOffset() == 40;
2307
2308 if (!positionedPassed || !unpositionedPassed) {
2309 ERROR(
2310 "HOSTED-SYSCALL-TEST: FAIL descriptor-position-policy: "
2311 "non-seekable I/O consumed a file position");
2312 return false;
2313 }
2314
2315 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-position-policy");
2316 return true;
2317}
2318
2319int advanceDescriptorPosition(void* parameter) {
2320 DescriptorPositionContext* context = reinterpret_cast<DescriptorPositionContext*>(parameter);
2321 context->entered += 1;
2322 FileDescriptor::PositionGuard position = context->descriptor->lockPosition();
2323 context->observed = position.offset();
2324 position.advanceOffset(1);
2325 context->acquired += 1;
2326 return 0;
2327}
2328
2329bool descriptorPositionAliasSerialization(Process* kernelProcess) {
2330 FileDescriptor source;
2331 source.setOffset(40);
2332 FileDescriptor alias(source);
2333 DescriptorPositionContext context(&alias);
2334
2335 Thread* worker =
2336 new Thread(kernelProcess, advanceDescriptorPosition, &context, nullptr, false, true, true);
2337 worker->setName("hosted descriptor position alias");
2338
2339 bool started = false;
2340 bool queued = false;
2341 {
2342 FileDescriptor::PositionGuard position = source.lockPosition();
2343 started = worker->start();
2344 for (size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
2345 Thread::WaitDebugInfo info = {};
2346 uintptr_t debugAddress = 0;
2347 if (context.entered && !context.acquired && worker->getWaitDebugInfo(info) && info.queued &&
2348 worker->getDebugState(debugAddress) == Thread::SemWait) {
2349 queued = true;
2350 break;
2351 }
2353 }
2354 position.setOffset(41);
2355 }
2356
2357 const bool joined = started && worker->joinForCompletion();
2358 if (!started) {
2359 delete worker;
2360 }
2361 const bool passed = started && queued && joined && context.acquired == 1 &&
2362 context.observed == 41 && source.getOffset() == 42 && alias.getOffset() == 42;
2363 if (!passed) {
2364 ERROR(
2365 "HOSTED-SYSCALL-TEST: FAIL descriptor-position-alias-serialization: "
2366 "duplicated descriptors did not serialize access to their shared offset");
2367 return false;
2368 }
2369
2370 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-position-alias-serialization");
2371 return true;
2372}
2373
2374class VectorWriteFile final : public File {
2375 public:
2376 VectorWriteFile()
2377 : File(String("vector-write-policy"), 0, 0, 0, 1, nullptr, 0, nullptr),
2378 m_FirstWriteEntered(0, false),
2379 m_ReleaseFirstWrite(0, false),
2380 m_WriteCount(0),
2381 m_WriteOffsets{0, 0, 0},
2382 m_WriteValues{0, 0, 0} {}
2383
2384 bool waitForFirstWrite() {
2385 return m_FirstWriteEntered.acquireForCompletion();
2386 }
2387
2388 void releaseFirstWrite() {
2389 m_ReleaseFirstWrite.release();
2390 }
2391
2392 size_t writeCount() const {
2393 return m_WriteCount;
2394 }
2395
2396 uint64_t writeOffset(size_t index) const {
2397 return m_WriteOffsets[index];
2398 }
2399
2400 char writeValue(size_t index) const {
2401 return m_WriteValues[index];
2402 }
2403
2404 protected:
2405 bool isBytewise() const override {
2406 return true;
2407 }
2408
2409 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
2410 const size_t slot = (m_WriteCount += 1) - 1;
2411 if (slot < 3) {
2412 m_WriteOffsets[slot] = location;
2413 m_WriteValues[slot] = size ? *reinterpret_cast<const char*>(buffer) : 0;
2414 }
2415 if (!slot) {
2416 m_FirstWriteEntered.release();
2417 if (!m_ReleaseFirstWrite.acquireForCompletion()) {
2418 return 0;
2419 }
2420 }
2421 if (location + size > getSize()) {
2422 setSize(location + size);
2423 }
2424 return size;
2425 }
2426
2427 private:
2428 Semaphore m_FirstWriteEntered;
2429 Semaphore m_ReleaseFirstWrite;
2430 Atomic<size_t> m_WriteCount;
2431 uint64_t m_WriteOffsets[3];
2432 char m_WriteValues[3];
2433};
2434
2435struct VectorWriteContext {
2436 explicit VectorWriteContext(size_t descriptor)
2437 : descriptor(descriptor), result(-2), error(0), returned(0) {}
2438
2439 size_t descriptor;
2440 int result;
2441 int error;
2442 Atomic<size_t> returned;
2443};
2444
2445int writeThroughVector(void* parameter) {
2446 VectorWriteContext* context = reinterpret_cast<VectorWriteContext*>(parameter);
2447 Thread* thread = Processor::information().getCurrentThread();
2448 char bytes[2] = {'a', 'b'};
2449 struct iovec vectors[2] = {{&bytes[0], 1}, {&bytes[1], 1}};
2450 thread->setErrno(0);
2451 context->result = posix_writev(static_cast<int>(context->descriptor), vectors, 2);
2452 context->error = thread->getErrno();
2453 context->returned += 1;
2454 return 0;
2455}
2456
2457int writeThroughAlias(void* parameter) {
2458 VectorWriteContext* context = reinterpret_cast<VectorWriteContext*>(parameter);
2459 Thread* thread = Processor::information().getCurrentThread();
2460 char byte = 'c';
2461 thread->setErrno(0);
2462 context->result = posix_write(static_cast<int>(context->descriptor), &byte, 1, false);
2463 context->error = thread->getErrno();
2464 context->returned += 1;
2465 return 0;
2466}
2467
2468bool descriptorVectorIoSerialization(Process* kernelProcess) {
2469 constexpr size_t SourceDescriptor = 74;
2470 constexpr size_t AliasDescriptor = 75;
2471 Process* process = new Process(kernelProcess);
2472 PosixSubsystem* subsystem = new PosixSubsystem;
2473 process->setSubsystem(subsystem);
2474
2475 VectorWriteFile original;
2476 DescriptorPositionFile replacement(true);
2477 FileDescriptor* source = new FileDescriptor(&original, 0, SourceDescriptor, 0, O_WRONLY);
2478 FileDescriptor* alias = new FileDescriptor(*source);
2479 alias->fd = AliasDescriptor;
2480 subsystem->addFileDescriptor(SourceDescriptor, source);
2481 subsystem->addFileDescriptor(AliasDescriptor, alias);
2482
2483 VectorWriteContext vectorContext(SourceDescriptor);
2484 VectorWriteContext aliasContext(AliasDescriptor);
2485 Thread* vectorWorker =
2486 new Thread(process, writeThroughVector, &vectorContext, nullptr, false, true, true);
2487 Thread* aliasWorker =
2488 new Thread(process, writeThroughAlias, &aliasContext, nullptr, false, true, true);
2489 vectorWorker->setName("hosted vector write generation");
2490 aliasWorker->setName("hosted vector write alias");
2491
2492 const bool vectorStarted = vectorWorker->start();
2493 const bool firstEntered = vectorStarted && original.waitForFirstWrite();
2494 const bool aliasStarted = firstEntered && aliasWorker->start();
2495 bool aliasBlocked = false;
2496 for (size_t attempt = 0; attempt < HostedAttempts && aliasStarted; ++attempt) {
2497 Thread::WaitDebugInfo info = {};
2498 uintptr_t debugAddress = 0;
2499 if (!aliasContext.returned && original.writeCount() == 1 &&
2500 aliasWorker->getWaitDebugInfo(info) && info.queued &&
2501 aliasWorker->getDebugState(debugAddress) == Thread::SemWait) {
2502 aliasBlocked = true;
2503 break;
2504 }
2506 }
2507
2508 DescriptorLease closingSource;
2509 const bool sourceAcquired = subsystem->acquireFileDescriptor(SourceDescriptor, closingSource);
2510 const bool sourceClosed =
2511 sourceAcquired && subsystem->closeFileDescriptor(SourceDescriptor, closingSource);
2512 closingSource.reset();
2513 FileDescriptor* replacementDescriptor =
2514 new FileDescriptor(&replacement, 0, SourceDescriptor, 0, O_WRONLY);
2515 subsystem->addFileDescriptor(SourceDescriptor, replacementDescriptor);
2516
2517 original.releaseFirstWrite();
2518 const bool vectorJoined = vectorStarted && vectorWorker->joinForCompletion();
2519 const bool aliasJoined = aliasStarted && aliasWorker->joinForCompletion();
2520 if (!vectorStarted) {
2521 delete vectorWorker;
2522 }
2523 if (!aliasStarted) {
2524 delete aliasWorker;
2525 }
2526
2527 bool passed = vectorStarted && firstEntered && aliasStarted && aliasBlocked && sourceClosed &&
2528 vectorJoined && aliasJoined && vectorContext.returned == 1 &&
2529 vectorContext.result == 2 && vectorContext.error == 0 &&
2530 aliasContext.returned == 1 && aliasContext.result == 1 && aliasContext.error == 0 &&
2531 original.writeCount() == 2 && original.writeOffset(0) == 0 &&
2532 original.writeOffset(1) == 2 && original.writeValue(0) == 'a' &&
2533 original.writeValue(1) == 'c' && original.getSize() == 3 &&
2534 replacement.writeOffset() == ~static_cast<uint64_t>(0);
2535
2536 DescriptorLease closingAlias;
2537 const bool aliasAcquired = subsystem->acquireFileDescriptor(AliasDescriptor, closingAlias);
2538 const bool aliasClosed =
2539 aliasAcquired && subsystem->closeFileDescriptor(AliasDescriptor, closingAlias);
2540 closingAlias.reset();
2541 DescriptorLease closingReplacement;
2542 const bool replacementAcquired =
2543 subsystem->acquireFileDescriptor(SourceDescriptor, closingReplacement);
2544 const bool replacementClosed =
2545 replacementAcquired && subsystem->closeFileDescriptor(SourceDescriptor, closingReplacement);
2546 closingReplacement.reset();
2547 passed = passed && aliasClosed && replacementClosed;
2548 delete process;
2549
2550 if (!passed) {
2551 ERROR(
2552 "HOSTED-SYSCALL-TEST: FAIL descriptor-vector-io-serialization: "
2553 "writev switched descriptor generations or released the shared offset between vectors");
2554 return false;
2555 }
2556
2557 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-vector-io-serialization");
2558 return true;
2559}
2560
2561struct DescriptorDupContractContext {
2562 DescriptorDupContractContext(PosixSubsystem* subsystem, size_t sourceFd, size_t occupiedFd,
2563 size_t minimum,
2564 const FileDescriptor::OpenFileDescriptionLease& sourceDescription,
2565 const FileDescriptor::OpenFileDescriptionLease& occupiedDescription)
2566 : subsystem(subsystem),
2567 sourceFd(sourceFd),
2568 occupiedFd(occupiedFd),
2569 minimum(minimum),
2570 sourceDescription(sourceDescription),
2571 occupiedDescription(occupiedDescription),
2572 duplicateResult(-2),
2573 duplicateError(0),
2574 occupiedIntact(false),
2575 duplicateAliasesSource(false),
2576 duplicateFlags(-1),
2577 duplicateClosed(false),
2578 ordinaryAllocation(static_cast<size_t>(-1)),
2579 sameInvalidResult(-2),
2580 sameInvalidError(0),
2581 returned(0) {}
2582
2583 PosixSubsystem* subsystem;
2584 size_t sourceFd;
2585 size_t occupiedFd;
2586 size_t minimum;
2588 FileDescriptor::OpenFileDescriptionLease occupiedDescription;
2589 int duplicateResult;
2590 int duplicateError;
2591 bool occupiedIntact;
2592 bool duplicateAliasesSource;
2593 int duplicateFlags;
2594 bool duplicateClosed;
2595 size_t ordinaryAllocation;
2596 int sameInvalidResult;
2597 int sameInvalidError;
2598 Atomic<size_t> returned;
2599};
2600
2601int exerciseDescriptorDupContract(void* parameter) {
2602 DescriptorDupContractContext* context =
2603 reinterpret_cast<DescriptorDupContractContext*>(parameter);
2604 Thread* thread = Processor::information().getCurrentThread();
2605
2606 thread->setErrno(0);
2607 context->duplicateResult = posix_fcntl(static_cast<int>(context->sourceFd), F_DUPFD,
2608 reinterpret_cast<void*>(context->minimum));
2609 context->duplicateError = thread->getErrno();
2610
2611 DescriptorLease occupied;
2612 if (context->subsystem->acquireFileDescriptor(context->occupiedFd, occupied)) {
2613 context->occupiedIntact =
2614 occupied->acquireOpenFileDescription().get() == context->occupiedDescription.get();
2615 }
2616 occupied.reset();
2617
2618 DescriptorLease duplicate;
2619 if (context->duplicateResult >= 0 &&
2620 context->subsystem->acquireFileDescriptor(context->duplicateResult, duplicate)) {
2621 context->duplicateAliasesSource =
2622 duplicate->acquireOpenFileDescription().get() == context->sourceDescription.get();
2623 context->duplicateFlags = duplicate->getFlags();
2624 context->duplicateClosed = context->subsystem->closeFileDescriptor(
2625 static_cast<size_t>(context->duplicateResult), duplicate);
2626 }
2627 duplicate.reset();
2628
2629 context->ordinaryAllocation = context->subsystem->getFd();
2630
2631 constexpr int InvalidDescriptor = 80;
2632 thread->setErrno(0);
2633 context->sameInvalidResult = posix_dup2(InvalidDescriptor, InvalidDescriptor);
2634 context->sameInvalidError = thread->getErrno();
2635 context->returned += 1;
2636 return 0;
2637}
2638
2639bool descriptorDupContract(Process* kernelProcess) {
2640 constexpr size_t SourceDescriptor = 70;
2641 constexpr size_t MinimumDescriptor = 72;
2642 constexpr size_t ExpectedDescriptor = 73;
2643
2644 Process* process = new Process(kernelProcess);
2645 PosixSubsystem* subsystem = new PosixSubsystem;
2646 process->setSubsystem(subsystem);
2647 File* sourceFile = new File;
2648 File* occupiedFile = new File;
2649 FileDescriptor* source =
2650 new FileDescriptor(sourceFile, 0, SourceDescriptor, FD_CLOEXEC, O_RDONLY);
2651 FileDescriptor* occupied = new FileDescriptor(occupiedFile, 0, MinimumDescriptor, 0, O_RDONLY);
2652 subsystem->addFileDescriptor(SourceDescriptor, source);
2653 subsystem->addFileDescriptor(MinimumDescriptor, occupied);
2654
2655 DescriptorDupContractContext context(subsystem, SourceDescriptor, MinimumDescriptor,
2656 MinimumDescriptor, source->acquireOpenFileDescription(),
2657 occupied->acquireOpenFileDescription());
2658 Thread* worker =
2659 new Thread(process, exerciseDescriptorDupContract, &context, nullptr, false, true, true);
2660 worker->setName("hosted descriptor duplication contract");
2661 const bool started = worker->start();
2662 const bool joined = started && worker->joinForCompletion();
2663 if (!started) {
2664 delete worker;
2665 }
2666
2667 const bool passed =
2668 started && joined && context.returned == 1 &&
2669 context.duplicateResult == static_cast<int>(ExpectedDescriptor) &&
2670 context.duplicateError == 0 && context.occupiedIntact && context.duplicateAliasesSource &&
2671 context.duplicateFlags == 0 && context.duplicateClosed && context.ordinaryAllocation == 0 &&
2672 context.sameInvalidResult == -1 && context.sameInvalidError == Error::BadFileDescriptor;
2673
2674 delete process;
2675 context.sourceDescription.reset();
2676 context.occupiedDescription.reset();
2677 delete sourceFile;
2678 delete occupiedFile;
2679
2680 if (!passed) {
2681 ERROR(
2682 "HOSTED-SYSCALL-TEST: FAIL descriptor-dup-contract: "
2683 "F_DUPFD replaced an occupied descriptor, hid a lower hole, or dup2 accepted an invalid "
2684 "same-fd source");
2685 return false;
2686 }
2687
2688 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-dup-contract");
2689 return true;
2690}
2691
2692struct PollCloseReuseContext {
2693 explicit PollCloseReuseContext(size_t fd)
2694 : descriptor{static_cast<int>(fd), POLLIN, 0}, result(-2), entered(0), returned(0) {}
2695
2696 struct pollfd descriptor;
2697 Atomic<int> result;
2698 Atomic<size_t> entered;
2699 Atomic<size_t> returned;
2700};
2701
2702bool selectProjectionContract() {
2703 constexpr unsigned int ReadResult = 1U;
2704 constexpr unsigned int WriteResult = 1U << 1;
2705 constexpr unsigned int ExceptionalResult = 1U << 2;
2706 constexpr unsigned int OneReady = 1U << 8;
2707 constexpr unsigned int TwoReady = 2U << 8;
2708 constexpr unsigned int ThreeReady = 3U << 8;
2709
2710 const unsigned int hangup = posixSelectProjectionForTest(POLLHUP, true, true, true);
2711 const unsigned int error = posixSelectProjectionForTest(POLLERR, true, true, true);
2712 const unsigned int priority = posixSelectProjectionForTest(POLLPRI, true, true, true);
2713 const unsigned int all =
2714 posixSelectProjectionForTest(POLLIN | POLLOUT | POLLPRI, true, true, true);
2715 const unsigned int writeOnlyError = posixSelectProjectionForTest(POLLERR, false, true, false);
2716
2717 const bool projectionsPassed =
2718 hangup == (OneReady | ReadResult) && error == (TwoReady | ReadResult | WriteResult) &&
2719 priority == (OneReady | ExceptionalResult) &&
2720 all == (ThreeReady | ReadResult | WriteResult | ExceptionalResult) &&
2721 writeOnlyError == (OneReady | WriteResult);
2722
2723 if (!projectionsPassed) {
2724 ERROR(
2725 "HOSTED-SYSCALL-TEST: FAIL select-projection: "
2726 "readiness projection or return-bit counting was incorrect");
2727 return false;
2728 }
2729
2730 NOTICE("HOSTED-SYSCALL-TEST: PASS select-projection");
2731 return true;
2732}
2733
2734struct PipePollReadinessContext {
2735 PipePollReadinessContext(size_t readFd, size_t writeFd)
2736 : readFd(readFd),
2737 writeFd(writeFd),
2738 pollEntryGate(0, false),
2739 firstPhaseGate(0, false),
2740 eofGate(0, false),
2741 pollEntered(0),
2742 firstPhaseReturned(0),
2743 returned(0),
2744 emptyReadResult(-2),
2745 emptyReadError(0),
2746 firstPollResult(-2),
2747 firstPollEvents(0),
2748 firstReadResult(-2),
2749 fillWriteResult(-2),
2750 fullWriteResult(-2),
2751 fullWriteError(0),
2752 atomicSetupDrainResult(-2),
2753 atomicVectorResult(-2),
2754 atomicVectorError(0),
2755 drainResult(-2),
2756 overBoundaryWriteResult(-2),
2757 overBoundaryDrainResult(-2),
2758 eofPollResult(-2),
2759 eofPollEvents(0),
2760 eofReadResult(-2) {}
2761
2762 size_t readFd;
2763 size_t writeFd;
2764 Semaphore pollEntryGate;
2765 Semaphore firstPhaseGate;
2766 Semaphore eofGate;
2767 Atomic<size_t> pollEntered;
2768 Atomic<size_t> firstPhaseReturned;
2769 Atomic<size_t> returned;
2770 int emptyReadResult;
2771 int emptyReadError;
2772 int firstPollResult;
2773 short firstPollEvents;
2774 int firstReadResult;
2775 int fillWriteResult;
2776 int fullWriteResult;
2777 int fullWriteError;
2778 int atomicSetupDrainResult;
2779 int atomicVectorResult;
2780 int atomicVectorError;
2781 int drainResult;
2782 int overBoundaryWriteResult;
2783 int overBoundaryDrainResult;
2784 int eofPollResult;
2785 short eofPollEvents;
2786 int eofReadResult;
2787};
2788
2789struct EpollReadinessContext {
2790 EpollReadinessContext(PosixSubsystem* subsystem, const SharedPointer<EpollInstance>& instance,
2791 size_t readFd, size_t writeFd, size_t aliasFd, size_t regularFd)
2792 : subsystem(subsystem),
2793 instance(instance),
2794 readDescription(),
2795 readFd(readFd),
2796 writeFd(writeFd),
2797 aliasFd(aliasFd),
2798 regularFd(regularFd),
2799 waitEntryGate(0, false),
2800 waitEntered(0),
2801 returned(0),
2802 addResult(-2),
2803 regularAddResult(-2),
2804 regularAddError(0),
2805 createdFd(-2),
2806 createdDescriptorAcquired(false),
2807 createdStatusFlags(-1),
2808 createdDescriptorFlags(-1),
2809 createdCloseResult(false),
2810 duplicateAddResult(-2),
2811 duplicateAddError(0),
2812 exclusiveModifyResult(-2),
2813 exclusiveModifyError(0),
2814 firstWaitResult(-2),
2815 firstWaitEvents(0),
2816 firstWaitData(0),
2817 repeatedWaitResult(-2),
2818 repeatedWaitEvents(0),
2819 repeatedWaitData(0),
2820 sameReadyWriteResult(-2),
2821 sameReadyWaitResult(-2),
2822 firstDrainResult(-2),
2823 drainedWaitResult(-2),
2824 transitionWriteResult(-2),
2825 transitionWaitResult(-2),
2826 transitionWaitEvents(0),
2827 transitionWaitData(0),
2828 transitionDrainResult(-2),
2829 levelModifyResult(-2),
2830 levelWriteResult(-2),
2831 levelWaitResult(-2),
2832 levelWaitEvents(0),
2833 levelWaitData(0),
2834 levelRepeatedWaitResult(-2),
2835 levelDrainResult(-2),
2836 oneShotModifyResult(-2),
2837 oneShotWriteResult(-2),
2838 oneShotWaitResult(-2),
2839 oneShotWaitEvents(0),
2840 oneShotWaitData(0),
2841 oneShotSuppressedResult(-2),
2842 rearmResult(-2),
2843 rearmedWaitResult(-2),
2844 rearmedWaitEvents(0),
2845 rearmedWaitData(0),
2846 oneShotDrainResult(-2),
2847 deleteResult(-2),
2848 postDeleteWriteResult(-2),
2849 postDeleteWaitResult(-2),
2850 postDeleteDrainResult(-2),
2851 eventFd(-2),
2852 eventFdAddResult(-2),
2853 eventFdFirstWriteResult(-2),
2854 eventFdFirstWaitResult(-2),
2855 eventFdFirstWaitEvents(0),
2856 eventFdFirstWaitData(0),
2857 eventFdSecondWriteResult(-2),
2858 eventFdSecondWaitResult(-2),
2859 eventFdSecondWaitEvents(0),
2860 eventFdSecondWaitData(0),
2861 eventFdReadResult(-2),
2862 eventFdReadValue(0),
2863 eventFdDeleteResult(-2),
2864 eventFdCloseResult(false),
2865 aliasAddResult(-2),
2866 originalCloseResult(false),
2867 ownersAfterOriginalClose(static_cast<size_t>(-1)),
2868 aliasWriteResult(-2),
2869 aliasWaitResult(-2),
2870 aliasWaitEvents(0),
2871 aliasWaitData(0),
2872 aliasDrainResult(-2),
2873 aliasCloseResult(false),
2874 ownersAfterAliasClose(static_cast<size_t>(-1)),
2875 prunedWaitResult(-2),
2876 descriptionRefsAfterPrune(static_cast<size_t>(-1)) {}
2877
2878 PosixSubsystem* subsystem;
2881 size_t readFd;
2882 size_t writeFd;
2883 size_t aliasFd;
2884 size_t regularFd;
2885 Semaphore waitEntryGate;
2886 Atomic<size_t> waitEntered;
2887 Atomic<size_t> returned;
2888 int addResult;
2889 int regularAddResult;
2890 int regularAddError;
2891 int createdFd;
2892 bool createdDescriptorAcquired;
2893 int createdStatusFlags;
2894 int createdDescriptorFlags;
2895 bool createdCloseResult;
2896 int duplicateAddResult;
2897 int duplicateAddError;
2898 int exclusiveModifyResult;
2899 int exclusiveModifyError;
2900 int firstWaitResult;
2901 uint32_t firstWaitEvents;
2902 uint64_t firstWaitData;
2903 int repeatedWaitResult;
2904 uint32_t repeatedWaitEvents;
2905 uint64_t repeatedWaitData;
2906 int sameReadyWriteResult;
2907 int sameReadyWaitResult;
2908 int firstDrainResult;
2909 int drainedWaitResult;
2910 int transitionWriteResult;
2911 int transitionWaitResult;
2912 uint32_t transitionWaitEvents;
2913 uint64_t transitionWaitData;
2914 int transitionDrainResult;
2915 int levelModifyResult;
2916 int levelWriteResult;
2917 int levelWaitResult;
2918 uint32_t levelWaitEvents;
2919 uint64_t levelWaitData;
2920 int levelRepeatedWaitResult;
2921 int levelDrainResult;
2922 int oneShotModifyResult;
2923 int oneShotWriteResult;
2924 int oneShotWaitResult;
2925 uint32_t oneShotWaitEvents;
2926 uint64_t oneShotWaitData;
2927 int oneShotSuppressedResult;
2928 int rearmResult;
2929 int rearmedWaitResult;
2930 uint32_t rearmedWaitEvents;
2931 uint64_t rearmedWaitData;
2932 int oneShotDrainResult;
2933 int deleteResult;
2934 int postDeleteWriteResult;
2935 int postDeleteWaitResult;
2936 int postDeleteDrainResult;
2937 int eventFd;
2938 int eventFdAddResult;
2939 int eventFdFirstWriteResult;
2940 int eventFdFirstWaitResult;
2941 uint32_t eventFdFirstWaitEvents;
2942 uint64_t eventFdFirstWaitData;
2943 int eventFdSecondWriteResult;
2944 int eventFdSecondWaitResult;
2945 uint32_t eventFdSecondWaitEvents;
2946 uint64_t eventFdSecondWaitData;
2947 int eventFdReadResult;
2948 uint64_t eventFdReadValue;
2949 int eventFdDeleteResult;
2950 bool eventFdCloseResult;
2951 int aliasAddResult;
2952 bool originalCloseResult;
2953 size_t ownersAfterOriginalClose;
2954 int aliasWriteResult;
2955 int aliasWaitResult;
2956 uint32_t aliasWaitEvents;
2957 uint64_t aliasWaitData;
2958 int aliasDrainResult;
2959 bool aliasCloseResult;
2960 size_t ownersAfterAliasClose;
2961 int prunedWaitResult;
2962 size_t descriptionRefsAfterPrune;
2963};
2964
2965class ReorderedReadinessFile final : public File {
2966 public:
2967 ReorderedReadinessFile()
2968 : File(), m_ReadinessLock(), m_Readable(true), m_Writable(true), m_Generations() {
2969 m_Generations.read = 1;
2970 m_Generations.write = 1;
2971 }
2972
2973 ReadyMask queryReady(bool reading, bool writing) override {
2974 LockGuard<Mutex> guard(m_ReadinessLock);
2975 ReadyMask ready = ReadyNone;
2976 if (reading && m_Readable) {
2977 ready |= ReadyRead;
2978 }
2979 if (writing && m_Writable) {
2980 ready |= ReadyWrite;
2981 }
2982 return ready;
2983 }
2984
2986 LockGuard<Mutex> guard(m_ReadinessLock);
2987 return m_Generations;
2988 }
2989
2990 bool supportsReadinessNotifications() const override {
2991 return true;
2992 }
2993
2994 void setReady(bool readable, bool writable) {
2995 LockGuard<Mutex> guard(m_ReadinessLock);
2996 if (!m_Readable && readable) {
2997 ++m_Generations.read;
2998 }
2999 if (!m_Writable && writable) {
3000 ++m_Generations.write;
3001 }
3002 m_Readable = readable;
3003 m_Writable = writable;
3004 }
3005
3006 void publishReadiness() {
3007 dataChanged();
3008 }
3009
3010 private:
3011 Mutex m_ReadinessLock;
3012 bool m_Readable;
3013 bool m_Writable;
3014 ReadinessGenerations m_Generations;
3015};
3016
3017struct ReorderedEpollContext {
3018 ReorderedEpollContext(const SharedPointer<EpollInstance>& instance, size_t fd,
3019 ReorderedReadinessFile* file)
3020 : instance(instance),
3021 fd(fd),
3022 file(file),
3023 initialComplete(0, false),
3024 drainMutated(0, false),
3025 publishDrain(0, false),
3026 collectFinal(0, false),
3027 initialWaitResult(-2),
3028 initialWaitEvents(0),
3029 initialWaitData(0),
3030 finalWaitResult(-2),
3031 finalWaitEvents(0),
3032 finalWaitData(0),
3033 addResult(-2),
3034 deleteResult(-2),
3035 waiterReturned(0),
3036 drainReturned(0) {}
3037
3039 size_t fd;
3040 ReorderedReadinessFile* file;
3041 Semaphore initialComplete;
3042 Semaphore drainMutated;
3043 Semaphore publishDrain;
3044 Semaphore collectFinal;
3045 int initialWaitResult;
3046 uint32_t initialWaitEvents;
3047 uint64_t initialWaitData;
3048 int finalWaitResult;
3049 uint32_t finalWaitEvents;
3050 uint64_t finalWaitData;
3051 int addResult;
3052 int deleteResult;
3053 Atomic<size_t> waiterReturned;
3054 Atomic<size_t> drainReturned;
3055};
3056
3057int waitAcrossReorderedReadiness(void* parameter) {
3058 ReorderedEpollContext* context = reinterpret_cast<ReorderedEpollContext*>(parameter);
3059 LinuxEpollEvent event = {LinuxEpoll::In | LinuxEpoll::Out | LinuxEpoll::EdgeTriggered,
3060 EpollReorderedData};
3061 context->addResult =
3062 context->instance->control(LinuxEpoll::ControlAdd, static_cast<int>(context->fd), &event);
3063
3064 LinuxEpollEvent result = {};
3065 context->initialWaitResult = context->instance->wait(&result, 1, 0);
3066 context->initialWaitEvents = result.events;
3067 context->initialWaitData = result.data;
3068 context->initialComplete.release();
3069
3070 if (context->collectFinal.acquireForCompletion()) {
3071 result = {};
3072 context->finalWaitResult = context->instance->wait(&result, 1, 0);
3073 context->finalWaitEvents = result.events;
3074 context->finalWaitData = result.data;
3075 context->deleteResult = context->instance->control(LinuxEpoll::ControlDelete,
3076 static_cast<int>(context->fd), nullptr);
3077 }
3078
3079 context->waiterReturned += 1;
3080 return 0;
3081}
3082
3083int publishDelayedDrain(void* parameter) {
3084 ReorderedEpollContext* context = reinterpret_cast<ReorderedEpollContext*>(parameter);
3085 context->file->setReady(false, false);
3086 context->drainMutated.release();
3087 if (context->publishDrain.acquireForCompletion()) {
3088 context->file->publishReadiness();
3089 }
3090 context->drainReturned += 1;
3091 return 0;
3092}
3093
3094class ReorderedFifo final : public Pipe {
3095 public:
3096 ReorderedFifo() : Pipe(String("hosted-reordered-fifo"), 0, 0, 0, 0, nullptr, 0, nullptr, false) {}
3097
3098 void reopenReaderWithoutPublishing() {
3099 LockGuard<Mutex> guard(m_Lock);
3100 m_Buffer.enableReads();
3101 ++m_nReaders;
3102 m_ReaderCondition.broadcast();
3103 }
3104
3105 void publishReopen() {
3106 dataChanged();
3107 }
3108};
3109
3110struct ReorderedFifoEpollContext {
3111 ReorderedFifoEpollContext(const SharedPointer<EpollInstance>& instance, size_t fd)
3112 : instance(instance),
3113 fd(fd),
3114 initialComplete(0, false),
3115 collectFinal(0, false),
3116 addResult(-2),
3117 initialWaitResult(-2),
3118 initialWaitEvents(0),
3119 initialWaitData(0),
3120 finalWaitResult(-2),
3121 finalWaitEvents(0),
3122 finalWaitData(0),
3123 deleteResult(-2),
3124 returned(0) {}
3125
3127 size_t fd;
3128 Semaphore initialComplete;
3129 Semaphore collectFinal;
3130 int addResult;
3131 int initialWaitResult;
3132 uint32_t initialWaitEvents;
3133 uint64_t initialWaitData;
3134 int finalWaitResult;
3135 uint32_t finalWaitEvents;
3136 uint64_t finalWaitData;
3137 int deleteResult;
3138 Atomic<size_t> returned;
3139};
3140
3141int waitAcrossReorderedFifoReopen(void* parameter) {
3142 ReorderedFifoEpollContext* context = reinterpret_cast<ReorderedFifoEpollContext*>(parameter);
3143 LinuxEpollEvent interest = {LinuxEpoll::Out | LinuxEpoll::EdgeTriggered, EpollFifoData};
3144 context->addResult =
3145 context->instance->control(LinuxEpoll::ControlAdd, static_cast<int>(context->fd), &interest);
3146
3147 LinuxEpollEvent event = {};
3148 context->initialWaitResult = context->instance->wait(&event, 1, 0);
3149 context->initialWaitEvents = event.events;
3150 context->initialWaitData = event.data;
3151 context->initialComplete.release();
3152
3153 if (context->collectFinal.acquireForCompletion()) {
3154 event = {};
3155 context->finalWaitResult = context->instance->wait(&event, 1, 0);
3156 context->finalWaitEvents = event.events;
3157 context->finalWaitData = event.data;
3158 context->deleteResult = context->instance->control(LinuxEpoll::ControlDelete,
3159 static_cast<int>(context->fd), nullptr);
3160 }
3161
3162 context->returned += 1;
3163 return 0;
3164}
3165
3166int pollPipeReadiness(void* parameter) {
3167 PipePollReadinessContext* context = reinterpret_cast<PipePollReadinessContext*>(parameter);
3168 Thread* thread = Processor::information().getCurrentThread();
3169 char byte = 0;
3170
3171 thread->setErrno(0);
3172 context->emptyReadResult = posix_read(context->readFd, &byte, 1);
3173 context->emptyReadError = thread->getErrno();
3174
3175 struct pollfd readable = {static_cast<int>(context->readFd), POLLIN, 0};
3176 context->pollEntered += 1;
3177 context->pollEntryGate.release();
3178 context->firstPollResult = posix_poll_safe(&readable, 1, PollCloseReuseTimeoutMilliseconds);
3179 context->firstPollEvents = readable.revents;
3180 context->firstReadResult = posix_read(context->readFd, &byte, 1);
3181
3182 char fill[PIPE_BUF_MAX] = {};
3183 context->fillWriteResult = posix_write(context->writeFd, fill, sizeof(fill), false);
3184 thread->setErrno(0);
3185 context->fullWriteResult = posix_write(context->writeFd, &byte, 1, false);
3186 context->fullWriteError = thread->getErrno();
3187 context->atomicSetupDrainResult = posix_read(context->readFd, &byte, 1);
3188 char vectorBytes[2] = {1, 2};
3189 struct iovec vectors[2] = {{&vectorBytes[0], 1}, {&vectorBytes[1], 1}};
3190 thread->setErrno(0);
3191 context->atomicVectorResult = posix_writev(context->writeFd, vectors, 2);
3192 context->atomicVectorError = thread->getErrno();
3193 context->drainResult = posix_read(context->readFd, fill, sizeof(fill));
3194 char overBoundary[PIPE_BUF_MAX + 1] = {};
3195 context->overBoundaryWriteResult =
3196 posix_write(context->writeFd, overBoundary, sizeof(overBoundary), false);
3197 context->overBoundaryDrainResult = posix_read(context->readFd, fill, sizeof(fill));
3198 context->firstPhaseReturned += 1;
3199 context->firstPhaseGate.release();
3200
3201 if (!context->eofGate.acquireForCompletion()) {
3202 context->returned += 1;
3203 return 1;
3204 }
3205
3206 struct pollfd eof = {static_cast<int>(context->readFd), POLLIN, 0};
3207 context->eofPollResult = posix_poll_safe(&eof, 1, PollCloseReuseTimeoutMilliseconds);
3208 context->eofPollEvents = eof.revents;
3209 context->eofReadResult = posix_read(context->readFd, &byte, 1);
3210 context->returned += 1;
3211 return 0;
3212}
3213
3214bool pipePollReadiness(Process* kernelProcess) {
3215 constexpr size_t ReadDescriptor = 45;
3216 constexpr size_t WriteDescriptor = 46;
3217 Process* process = new Process(kernelProcess);
3218 PosixSubsystem* subsystem = new PosixSubsystem;
3219 process->setSubsystem(subsystem);
3220
3221 Pipe* pipe = new Pipe(String(""), 0, 0, 0, 0, nullptr, 0, nullptr, true);
3222 FileDescriptor* reader = new FileDescriptor(pipe, 0, ReadDescriptor, 0, O_RDONLY | O_NONBLOCK);
3223 FileDescriptor* writer = new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY | O_NONBLOCK);
3224 subsystem->addFileDescriptor(ReadDescriptor, reader);
3225 subsystem->addFileDescriptor(WriteDescriptor, writer);
3226
3227 PipePollReadinessContext context(ReadDescriptor, WriteDescriptor);
3228 Thread* worker = new Thread(process, pollPipeReadiness, &context, nullptr, false, true, true);
3229 worker->setName("hosted pipe poll readiness worker");
3230 const bool started = worker->start();
3231
3232 const bool pollEntered = started && context.pollEntryGate.acquireForCompletion();
3233 bool pollBlocked = false;
3234 for (size_t attempt = 0; attempt < HostedAttempts && pollEntered; ++attempt) {
3235 Thread::WaitDebugInfo info = {};
3236 if (worker->getWaitDebugInfo(info) && info.queue && info.queued &&
3237 worker->getStatus() == Thread::Sleeping) {
3238 pollBlocked = true;
3239 break;
3240 }
3242 }
3243
3244 char byte = 1;
3245 const bool initialWrite =
3246 pollBlocked && writer->write(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
3247 const bool firstPhaseCompleted = started && context.firstPhaseGate.acquireForCompletion();
3248
3249 DescriptorLease closingWriter;
3250 const bool writerAcquired = subsystem->acquireFileDescriptor(WriteDescriptor, closingWriter);
3251 const bool writerClosed =
3252 writerAcquired && subsystem->closeFileDescriptor(WriteDescriptor, closingWriter);
3253 closingWriter.reset();
3254 context.eofGate.release();
3255
3256 const bool joined = started && worker->joinForCompletion();
3257 if (!started) {
3258 delete worker;
3259 }
3260
3261 const bool nonblockingPassed =
3262 context.emptyReadResult == -1 && context.emptyReadError == Error::NoMoreProcesses &&
3263 context.fillWriteResult == PIPE_BUF_MAX && context.fullWriteResult == -1 &&
3264 context.fullWriteError == Error::NoMoreProcesses && context.atomicSetupDrainResult == 1 &&
3265 context.atomicVectorResult == -1 && context.atomicVectorError == Error::NoMoreProcesses &&
3266 context.drainResult == PIPE_BUF_MAX - 1 && context.overBoundaryWriteResult == PIPE_BUF_MAX &&
3267 context.overBoundaryDrainResult == PIPE_BUF_MAX && context.eofReadResult == 0;
3268 const bool readinessPassed =
3269 initialWrite && context.firstPollResult == 1 && (context.firstPollEvents & POLLIN) &&
3270 !(context.firstPollEvents & POLLHUP) && context.firstReadResult == 1 &&
3271 context.eofPollResult == 1 && (context.eofPollEvents & POLLHUP);
3272 bool passed = started && pollBlocked && firstPhaseCompleted && joined && writerClosed &&
3273 context.firstPhaseReturned == 1 && context.returned == 1 && nonblockingPassed &&
3274 readinessPassed;
3275
3276 FileDescriptor::OpenFileDescriptionLease readerDescription = reader->acquireOpenFileDescription();
3277 DescriptorLease closingReader;
3278 const bool readerAcquired = subsystem->acquireFileDescriptor(ReadDescriptor, closingReader);
3279 const bool readerClosed =
3280 readerAcquired && subsystem->closeFileDescriptor(ReadDescriptor, closingReader);
3281 closingReader.reset();
3282 passed = passed && readerClosed && readerDescription->descriptorOwnerCount() == 0 &&
3283 readerDescription->getFile() == pipe && pipe->getReaderCount() == 0;
3284 readerDescription.reset();
3285 delete process;
3286
3287 if (!passed) {
3288 ERROR(
3289 "HOSTED-SYSCALL-TEST: FAIL pipe-poll-readiness: "
3290 "POLLIN wakeup, EOF hangup, or atomic nonblocking pipe I/O was incorrect");
3291 return false;
3292 }
3293
3294 NOTICE("HOSTED-SYSCALL-TEST: PASS pipe-poll-readiness");
3295 return true;
3296}
3297
3298int exerciseEpollReadiness(void* parameter) {
3299 EpollReadinessContext* context = reinterpret_cast<EpollReadinessContext*>(parameter);
3300 Thread* thread = Processor::information().getCurrentThread();
3301 LinuxEpollEvent events[2] = {};
3302 char byte = 1;
3303
3304 LinuxEpollEvent interest = {LinuxEpoll::In | LinuxEpoll::ReadNormal | LinuxEpoll::EdgeTriggered,
3305 EpollInitialData};
3306 context->addResult =
3307 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &interest);
3308
3309 thread->setErrno(0);
3310 context->regularAddResult =
3311 context->instance->control(LinuxEpoll::ControlAdd, context->regularFd, &interest);
3312 context->regularAddError = thread->getErrno();
3313
3314 context->createdFd = posix_epoll_create1(LinuxEpoll::CloseOnExec);
3315 DescriptorLease createdDescriptor;
3316 if (context->createdFd >= 0) {
3317 context->createdDescriptorAcquired = context->subsystem->acquireFileDescriptor(
3318 static_cast<size_t>(context->createdFd), createdDescriptor);
3319 if (context->createdDescriptorAcquired) {
3320 context->createdStatusFlags = createdDescriptor->getStatusFlags();
3321 context->createdDescriptorFlags = createdDescriptor->getFlags();
3322 context->createdCloseResult = context->subsystem->closeFileDescriptor(
3323 static_cast<size_t>(context->createdFd), createdDescriptor);
3324 }
3325 }
3326 createdDescriptor.reset();
3327
3328 thread->setErrno(0);
3329 context->duplicateAddResult =
3330 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &interest);
3331 context->duplicateAddError = thread->getErrno();
3332
3333 LinuxEpollEvent exclusive = {LinuxEpoll::In | LinuxEpoll::Exclusive, EpollInitialData};
3334 thread->setErrno(0);
3335 context->exclusiveModifyResult =
3336 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &exclusive);
3337 context->exclusiveModifyError = thread->getErrno();
3338
3339 context->waitEntered += 1;
3340 context->waitEntryGate.release();
3341 context->firstWaitResult = context->instance->wait(events, 2, 5000);
3342 context->firstWaitEvents = events[0].events;
3343 context->firstWaitData = events[0].data;
3344
3345 events[0] = {};
3346 context->repeatedWaitResult = context->instance->wait(events, 2, 0);
3347 context->repeatedWaitEvents = events[0].events;
3348 context->repeatedWaitData = events[0].data;
3349 context->sameReadyWriteResult = posix_write(context->writeFd, &byte, 1, false);
3350 context->sameReadyWaitResult = context->instance->wait(events, 2, 0);
3351 char edgeBytes[2] = {};
3352 context->firstDrainResult = posix_read(context->readFd, edgeBytes, sizeof(edgeBytes));
3353 context->drainedWaitResult = context->instance->wait(events, 2, 0);
3354
3355 context->transitionWriteResult = posix_write(context->writeFd, &byte, 1, false);
3356 events[0] = {};
3357 context->transitionWaitResult = context->instance->wait(events, 2, 0);
3358 context->transitionWaitEvents = events[0].events;
3359 context->transitionWaitData = events[0].data;
3360 context->transitionDrainResult = posix_read(context->readFd, &byte, 1);
3361
3362 LinuxEpollEvent level = {LinuxEpoll::In, EpollLevelData};
3363 context->levelModifyResult =
3364 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &level);
3365 context->levelWriteResult = posix_write(context->writeFd, &byte, 1, false);
3366 events[0] = {};
3367 context->levelWaitResult = context->instance->wait(events, 2, 0);
3368 context->levelWaitEvents = events[0].events;
3369 context->levelWaitData = events[0].data;
3370 context->levelRepeatedWaitResult = context->instance->wait(events, 2, 0);
3371 context->levelDrainResult = posix_read(context->readFd, &byte, 1);
3372
3373 LinuxEpollEvent oneShot = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered | LinuxEpoll::OneShot,
3374 EpollOneShotData};
3375 context->oneShotModifyResult =
3376 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &oneShot);
3377 context->oneShotWriteResult = posix_write(context->writeFd, &byte, 1, false);
3378 events[0] = {};
3379 context->oneShotWaitResult = context->instance->wait(events, 2, 0);
3380 context->oneShotWaitEvents = events[0].events;
3381 context->oneShotWaitData = events[0].data;
3382 context->oneShotSuppressedResult = context->instance->wait(events, 2, 0);
3383
3384 LinuxEpollEvent rearmed = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered | LinuxEpoll::OneShot,
3385 EpollRearmedData};
3386 context->rearmResult =
3387 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &rearmed);
3388 events[0] = {};
3389 context->rearmedWaitResult = context->instance->wait(events, 2, 0);
3390 context->rearmedWaitEvents = events[0].events;
3391 context->rearmedWaitData = events[0].data;
3392 context->oneShotDrainResult = posix_read(context->readFd, &byte, 1);
3393
3394 context->deleteResult =
3395 context->instance->control(LinuxEpoll::ControlDelete, context->readFd, nullptr);
3396 context->postDeleteWriteResult = posix_write(context->writeFd, &byte, 1, false);
3397 context->postDeleteWaitResult = context->instance->wait(events, 2, 0);
3398 context->postDeleteDrainResult = posix_read(context->readFd, &byte, 1);
3399
3400 context->eventFd = posix_eventfd2(0, LinuxEventFd::NonBlock);
3401 LinuxEpollEvent eventFdInterest = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered, EpollEventFdData};
3402 if (context->eventFd >= 0) {
3403 context->eventFdAddResult =
3404 context->instance->control(LinuxEpoll::ControlAdd, context->eventFd, &eventFdInterest);
3405 uint64_t eventValue = 1;
3406 context->eventFdFirstWriteResult = posix_write(
3407 context->eventFd, reinterpret_cast<char*>(&eventValue), sizeof(eventValue), false);
3408 events[0] = {};
3409 context->eventFdFirstWaitResult = context->instance->wait(events, 2, 0);
3410 context->eventFdFirstWaitEvents = events[0].events;
3411 context->eventFdFirstWaitData = events[0].data;
3412
3413 // libuv deliberately leaves its Linux async eventfd readable. A second
3414 // producer write must therefore generate another edge without a read in
3415 // between, even though the counter's readable level never went false.
3416 context->eventFdSecondWriteResult = posix_write(
3417 context->eventFd, reinterpret_cast<char*>(&eventValue), sizeof(eventValue), false);
3418 events[0] = {};
3419 context->eventFdSecondWaitResult = context->instance->wait(events, 2, 0);
3420 context->eventFdSecondWaitEvents = events[0].events;
3421 context->eventFdSecondWaitData = events[0].data;
3422 context->eventFdReadResult =
3423 posix_read(context->eventFd, reinterpret_cast<char*>(&context->eventFdReadValue),
3424 sizeof(context->eventFdReadValue));
3425 context->eventFdDeleteResult =
3426 context->instance->control(LinuxEpoll::ControlDelete, context->eventFd, nullptr);
3427
3428 DescriptorLease closingEventFd;
3429 const bool eventFdAcquired = context->subsystem->acquireFileDescriptor(
3430 static_cast<size_t>(context->eventFd), closingEventFd);
3431 context->eventFdCloseResult =
3432 eventFdAcquired && context->subsystem->closeFileDescriptor(
3433 static_cast<size_t>(context->eventFd), closingEventFd);
3434 closingEventFd.reset();
3435 }
3436
3437 LinuxEpollEvent aliasLifetime = {LinuxEpoll::In, EpollAliasData};
3438 context->aliasAddResult =
3439 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &aliasLifetime);
3440
3441 DescriptorLease closingOriginal;
3442 const bool originalAcquired =
3443 context->subsystem->acquireFileDescriptor(context->readFd, closingOriginal);
3444 context->originalCloseResult =
3445 originalAcquired && context->subsystem->closeFileDescriptor(context->readFd, closingOriginal);
3446 closingOriginal.reset();
3447 context->ownersAfterOriginalClose = context->readDescription->descriptorOwnerCount();
3448
3449 context->aliasWriteResult = posix_write(context->writeFd, &byte, 1, false);
3450 events[0] = {};
3451 context->aliasWaitResult = context->instance->wait(events, 2, 0);
3452 context->aliasWaitEvents = events[0].events;
3453 context->aliasWaitData = events[0].data;
3454 context->aliasDrainResult = posix_read(context->aliasFd, &byte, 1);
3455
3456 DescriptorLease closingAlias;
3457 const bool aliasAcquired =
3458 context->subsystem->acquireFileDescriptor(context->aliasFd, closingAlias);
3459 context->aliasCloseResult =
3460 aliasAcquired && context->subsystem->closeFileDescriptor(context->aliasFd, closingAlias);
3461 closingAlias.reset();
3462 context->ownersAfterAliasClose = context->readDescription->descriptorOwnerCount();
3463 context->prunedWaitResult = context->instance->wait(events, 2, 0);
3464 context->descriptionRefsAfterPrune = context->readDescription.refcount();
3465
3466 context->returned += 1;
3467 return 0;
3468}
3469
3470bool epollLevelOneShotAndOfdLifetime(Process* kernelProcess) {
3471 constexpr size_t ReadDescriptor = 47;
3472 constexpr size_t WriteDescriptor = 48;
3473 constexpr size_t AliasDescriptor = 49;
3474 constexpr size_t RegularDescriptor = 50;
3475
3476 Process* process = new Process(kernelProcess);
3477 PosixSubsystem* subsystem = new PosixSubsystem;
3478 process->setSubsystem(subsystem);
3479
3480 Pipe* pipe = new Pipe(String(""), 0, 0, 0, 0, nullptr, 0, nullptr, true);
3481 FileDescriptor* reader = new FileDescriptor(pipe, 0, ReadDescriptor, 0, O_RDONLY | O_NONBLOCK);
3482 FileDescriptor* writer = new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY | O_NONBLOCK);
3483 FileDescriptor* alias = new FileDescriptor(*reader);
3484 alias->fd = AliasDescriptor;
3485 File* regularFile = new File;
3486 FileDescriptor* regular = new FileDescriptor(regularFile, 0, RegularDescriptor, 0, O_RDONLY);
3487 subsystem->addFileDescriptor(ReadDescriptor, reader);
3488 subsystem->addFileDescriptor(WriteDescriptor, writer);
3489 subsystem->addFileDescriptor(AliasDescriptor, alias);
3490 subsystem->addFileDescriptor(RegularDescriptor, regular);
3491
3493 EpollReadinessContext context(subsystem, instance, ReadDescriptor, WriteDescriptor,
3494 AliasDescriptor, RegularDescriptor);
3495 context.readDescription = reader->acquireOpenFileDescription();
3496
3497 Thread* worker =
3498 new Thread(process, exerciseEpollReadiness, &context, nullptr, false, true, true);
3499 worker->setName("hosted epoll readiness worker");
3500 const bool started = worker->start();
3501 const bool waitEntered = started && context.waitEntryGate.acquireForCompletion();
3502
3503 bool waitBlocked = false;
3504 for (size_t attempt = 0; attempt < HostedAttempts && waitEntered; ++attempt) {
3505 Thread::WaitDebugInfo info = {};
3506 if (worker->getWaitDebugInfo(info) && info.queue && info.queued &&
3507 worker->getStatus() == Thread::Sleeping) {
3508 waitBlocked = true;
3509 break;
3510 }
3512 }
3513
3514 char byte = 1;
3515 const int wakeWriteResult = writer->write(1, reinterpret_cast<uintptr_t>(&byte));
3516 const bool joined = started && worker->joinForCompletion();
3517 if (!started) {
3518 delete worker;
3519 }
3520
3521 const bool edgePassed =
3522 waitBlocked && wakeWriteResult == 1 && context.firstWaitResult == 1 &&
3523 context.firstWaitEvents == (LinuxEpoll::In | LinuxEpoll::ReadNormal) &&
3524 context.firstWaitData == EpollInitialData && context.repeatedWaitResult == 0 &&
3525 context.sameReadyWriteResult == 1 && context.sameReadyWaitResult == 0 &&
3526 context.firstDrainResult == 2 && context.drainedWaitResult == 0 &&
3527 context.transitionWriteResult == 1 && context.transitionWaitResult == 1 &&
3528 context.transitionWaitEvents == (LinuxEpoll::In | LinuxEpoll::ReadNormal) &&
3529 context.transitionWaitData == EpollInitialData && context.transitionDrainResult == 1;
3530 const bool levelPassed = context.levelModifyResult == 0 && context.levelWriteResult == 1 &&
3531 context.levelWaitResult == 1 &&
3532 context.levelWaitEvents == LinuxEpoll::In &&
3533 context.levelWaitData == EpollLevelData &&
3534 context.levelRepeatedWaitResult == 1 && context.levelDrainResult == 1;
3535 const bool controlPassed =
3536 context.addResult == 0 && context.regularAddResult == -1 &&
3537 context.regularAddError == Error::NotEnoughPermissions && context.createdFd >= 0 &&
3538 context.createdDescriptorAcquired && context.createdStatusFlags == O_RDWR &&
3539 context.createdDescriptorFlags == FD_CLOEXEC && context.createdCloseResult &&
3540 context.duplicateAddResult == -1 && context.duplicateAddError == Error::FileExists &&
3541 context.exclusiveModifyResult == -1 &&
3542 context.exclusiveModifyError == Error::OperationNotSupported &&
3543 context.oneShotModifyResult == 0 && context.oneShotWriteResult == 1 &&
3544 context.oneShotWaitResult == 1 && context.oneShotWaitEvents == LinuxEpoll::In &&
3545 context.oneShotWaitData == EpollOneShotData && context.oneShotSuppressedResult == 0 &&
3546 context.rearmResult == 0 && context.rearmedWaitResult == 1 &&
3547 context.rearmedWaitEvents == LinuxEpoll::In && context.rearmedWaitData == EpollRearmedData &&
3548 context.oneShotDrainResult == 1 && context.deleteResult == 0 &&
3549 context.postDeleteWriteResult == 1 && context.postDeleteWaitResult == 0 &&
3550 context.postDeleteDrainResult == 1;
3551 const bool lifetimePassed =
3552 context.aliasAddResult == 0 && context.originalCloseResult &&
3553 context.ownersAfterOriginalClose == 1 && context.aliasWriteResult == 1 &&
3554 context.aliasWaitResult == 1 && context.aliasWaitEvents == LinuxEpoll::In &&
3555 context.aliasWaitData == EpollAliasData && context.aliasDrainResult == 1 &&
3556 context.aliasCloseResult && context.ownersAfterAliasClose == 0 &&
3557 context.prunedWaitResult == 0 && context.descriptionRefsAfterPrune == 1;
3558 const bool eventFdPassed =
3559 context.eventFd >= 0 && context.eventFdAddResult == 0 &&
3560 context.eventFdFirstWriteResult == static_cast<int>(sizeof(uint64_t)) &&
3561 context.eventFdFirstWaitResult == 1 && context.eventFdFirstWaitEvents == LinuxEpoll::In &&
3562 context.eventFdFirstWaitData == EpollEventFdData &&
3563 context.eventFdSecondWriteResult == static_cast<int>(sizeof(uint64_t)) &&
3564 context.eventFdSecondWaitResult == 1 && context.eventFdSecondWaitEvents == LinuxEpoll::In &&
3565 context.eventFdSecondWaitData == EpollEventFdData &&
3566 context.eventFdReadResult == static_cast<int>(sizeof(uint64_t)) &&
3567 context.eventFdReadValue == 2 && context.eventFdDeleteResult == 0 &&
3568 context.eventFdCloseResult;
3569 bool passed = started && waitEntered && joined && context.waitEntered == 1 &&
3570 context.returned == 1 && edgePassed && levelPassed && controlPassed &&
3571 lifetimePassed && eventFdPassed;
3572
3573 DescriptorLease closingWriter;
3574 const bool writerAcquired = subsystem->acquireFileDescriptor(WriteDescriptor, closingWriter);
3575 const bool writerClosed =
3576 writerAcquired && subsystem->closeFileDescriptor(WriteDescriptor, closingWriter);
3577 closingWriter.reset();
3578 passed = passed && writerClosed;
3579
3580 instance.reset();
3581 context.instance.reset();
3582 context.readDescription.reset();
3583 delete process;
3584 delete regularFile;
3585
3586 if (!passed) {
3587 ERROR(
3588 "HOSTED-SYSCALL-TEST: FAIL epoll-level-oneshot-ofd-lifetime: "
3589 "target admission, descriptor flags, edge and level delivery, one-shot rearm, control "
3590 "errors, eventfd generations, or OFD retirement was incorrect");
3591 return false;
3592 }
3593
3594 NOTICE("HOSTED-SYSCALL-TEST: PASS epoll-level-oneshot-ofd-lifetime");
3595 return true;
3596}
3597
3598bool epollReorderedTransitionPublication(Process* kernelProcess) {
3599 constexpr size_t DescriptorNumber = 51;
3600 Process* process = new Process(kernelProcess);
3601 PosixSubsystem* subsystem = new PosixSubsystem;
3602 process->setSubsystem(subsystem);
3603
3604 ReorderedReadinessFile* source = new ReorderedReadinessFile;
3605 FileDescriptor* descriptor =
3606 new FileDescriptor(source, 0, DescriptorNumber, 0, O_RDWR | O_NONBLOCK);
3607 subsystem->addFileDescriptor(DescriptorNumber, descriptor);
3608
3610 ReorderedEpollContext context(instance, DescriptorNumber, source);
3611 Thread* waiter =
3612 new Thread(process, waitAcrossReorderedReadiness, &context, nullptr, false, true, true);
3613 waiter->setName("hosted reordered epoll waiter");
3614 const bool waiterStarted = waiter->start();
3615 const bool initialComplete = waiterStarted && context.initialComplete.acquireForCompletion();
3616
3617 Thread* drain = nullptr;
3618 bool drainStarted = false;
3619 bool drainMutated = false;
3620 if (initialComplete) {
3621 drain = new Thread(process, publishDelayedDrain, &context, nullptr, false, true, true);
3622 drain->setName("hosted delayed epoll drain publisher");
3623 drainStarted = drain->start();
3624 drainMutated = drainStarted && context.drainMutated.acquireForCompletion();
3625 }
3626
3627 // The refill callback deliberately overtakes the callback for the drain.
3628 // Both callbacks therefore sample the final readable level; only the
3629 // source-side generation proves that a reusable edge occurred between them.
3630 if (drainMutated) {
3631 source->setReady(true, true);
3632 source->publishReadiness();
3633 }
3634 context.publishDrain.release();
3635 const bool drainJoined = drainStarted && drain->joinForCompletion();
3636 if (drain && !drainStarted) {
3637 delete drain;
3638 }
3639
3640 context.collectFinal.release();
3641 const bool waiterJoined = waiterStarted && waiter->joinForCompletion();
3642 if (!waiterStarted) {
3643 delete waiter;
3644 }
3645
3646 const bool transitionPassed =
3647 context.addResult == 0 && context.initialWaitResult == 1 &&
3648 context.initialWaitEvents == (LinuxEpoll::In | LinuxEpoll::Out) &&
3649 context.initialWaitData == EpollReorderedData && context.finalWaitResult == 1 &&
3650 context.finalWaitEvents == (LinuxEpoll::In | LinuxEpoll::Out) &&
3651 context.finalWaitData == EpollReorderedData && context.deleteResult == 0 &&
3652 source->readinessGenerations().read == 2 && source->readinessGenerations().write == 2;
3653 bool passed = waiterStarted && initialComplete && drainStarted && drainMutated && drainJoined &&
3654 waiterJoined && context.waiterReturned == 1 && context.drainReturned == 1 &&
3655 transitionPassed;
3656
3657 DescriptorLease closing;
3658 const bool descriptorAcquired = subsystem->acquireFileDescriptor(DescriptorNumber, closing);
3659 const bool descriptorClosed =
3660 descriptorAcquired && subsystem->closeFileDescriptor(DescriptorNumber, closing);
3661 closing.reset();
3662 passed = passed && descriptorClosed;
3663
3664 instance.reset();
3665 context.instance.reset();
3666 delete process;
3667 delete source;
3668
3669 if (!passed) {
3670 ERROR(
3671 "HOSTED-SYSCALL-TEST: FAIL epoll-reordered-transition-publication: "
3672 "a refill edge was lost when its callback overtook the drain callback");
3673 return false;
3674 }
3675
3676 NOTICE("HOSTED-SYSCALL-TEST: PASS epoll-reordered-transition-publication");
3677 return true;
3678}
3679
3680bool epollPersistentFifoReopenReclose(Process* kernelProcess) {
3681 constexpr size_t DescriptorNumber = 52;
3682 Process* process = new Process(kernelProcess);
3683 PosixSubsystem* subsystem = new PosixSubsystem;
3684 process->setSubsystem(subsystem);
3685
3686 ReorderedFifo* fifo = new ReorderedFifo;
3687 FileDescriptor* writer = new FileDescriptor(fifo, 0, DescriptorNumber, 0, O_WRONLY | O_NONBLOCK);
3688 subsystem->addFileDescriptor(DescriptorNumber, writer);
3689
3690 fifo->increaseRefCount(false);
3691 char fill[PIPE_BUF_MAX] = {};
3692 const int fillResult = writer->write(sizeof(fill), reinterpret_cast<uintptr_t>(fill));
3693 fifo->decreaseRefCount(false);
3694
3696 ReorderedFifoEpollContext context(instance, DescriptorNumber);
3697 Thread* waiter =
3698 new Thread(process, waitAcrossReorderedFifoReopen, &context, nullptr, false, true, true);
3699 waiter->setName("hosted reordered FIFO epoll waiter");
3700 const bool waiterStarted = waiter->start();
3701 const bool initialComplete = waiterStarted && context.initialComplete.acquireForCompletion();
3702
3703 const ReadinessGenerations initialGenerations = fifo->readinessGenerations();
3704 bool reopenMadeNotReady = false;
3705 if (initialComplete) {
3706 // Delay the notification for the falling reader-reopen transition. The
3707 // re-close publishes first, so both callbacks sample the final OUT|ERR
3708 // level and the Pipe generation is the only evidence of the OUT rise.
3709 fifo->reopenReaderWithoutPublishing();
3710 reopenMadeNotReady = fifo->queryReady(false, true) == ReadyNone;
3711 fifo->decreaseRefCount(false);
3712 fifo->publishReopen();
3713 }
3714 const ReadinessGenerations finalGenerations = fifo->readinessGenerations();
3715
3716 context.collectFinal.release();
3717 const bool waiterJoined = waiterStarted && waiter->joinForCompletion();
3718 if (!waiterStarted) {
3719 delete waiter;
3720 }
3721
3722 const uint32_t expectedEvents = LinuxEpoll::Out | LinuxEpoll::Error;
3723 const bool edgePassed =
3724 fillResult == PIPE_BUF_MAX && context.addResult == 0 && context.initialWaitResult == 1 &&
3725 context.initialWaitEvents == expectedEvents && context.initialWaitData == EpollFifoData &&
3726 reopenMadeNotReady && context.finalWaitResult == 1 &&
3727 context.finalWaitEvents == expectedEvents && context.finalWaitData == EpollFifoData &&
3728 context.deleteResult == 0 && finalGenerations.write != initialGenerations.write &&
3729 finalGenerations.error != initialGenerations.error;
3730 bool passed =
3731 waiterStarted && initialComplete && waiterJoined && context.returned == 1 && edgePassed;
3732
3733 DescriptorLease closing;
3734 const bool descriptorAcquired = subsystem->acquireFileDescriptor(DescriptorNumber, closing);
3735 const bool descriptorClosed =
3736 descriptorAcquired && subsystem->closeFileDescriptor(DescriptorNumber, closing);
3737 closing.reset();
3738 passed = passed && descriptorClosed;
3739
3740 instance.reset();
3741 context.instance.reset();
3742 delete process;
3743 delete fifo;
3744
3745 if (!passed) {
3746 ERROR(
3747 "HOSTED-SYSCALL-TEST: FAIL epoll-persistent-fifo-reopen-reclose: "
3748 "a full FIFO lost EPOLLOUT when reader re-close overtook reopen publication");
3749 return false;
3750 }
3751
3752 NOTICE("HOSTED-SYSCALL-TEST: PASS epoll-persistent-fifo-reopen-reclose");
3753 return true;
3754}
3755
3756int pollAcrossCloseReuse(void* parameter) {
3757 PollCloseReuseContext* context = reinterpret_cast<PollCloseReuseContext*>(parameter);
3758 context->entered += 1;
3759 // A missed wakeup must report a bounded regression failure. The old
3760 // infinite poll made the outer harness timeout the only evidence.
3761 context->result = posix_poll_safe(&context->descriptor, 1, PollCloseReuseTimeoutMilliseconds);
3762 context->returned += 1;
3763 return context->result == 1 ? 0 : 1;
3764}
3765
3766bool pollCloseReuseCleanup(Process* kernelProcess) {
3767 constexpr size_t DescriptorNumber = 39;
3768 Process* process = new Process(kernelProcess);
3769 PosixSubsystem* subsystem = new PosixSubsystem;
3770 process->setSubsystem(subsystem);
3771
3772 Atomic<size_t> aQueries(0);
3773 Atomic<size_t> aNotifications(0);
3774 Atomic<size_t> aNetworkDestructions(0);
3775 Atomic<size_t> aDescriptorDestructions(0);
3776 PollGenerationProbe* aNetwork =
3777 new PollGenerationProbe(aQueries, aNotifications, aNetworkDestructions);
3778 SharedPointer<NetworkSyscalls> aNetworkKeepalive(aNetwork);
3779 DescriptorRetirementProbe* aDescriptor = new DescriptorRetirementProbe(aDescriptorDestructions);
3780 aDescriptor->fd = DescriptorNumber;
3781 aDescriptor->setOffset(1);
3782 aDescriptor->setNetworkImpl(aNetworkKeepalive);
3783 subsystem->addFileDescriptor(DescriptorNumber, aDescriptor);
3784
3785 Atomic<size_t> bQueries(0);
3786 Atomic<size_t> bNotifications(0);
3787 Atomic<size_t> bNetworkDestructions(0);
3788 Atomic<size_t> bDescriptorDestructions(0);
3789 PollGenerationProbe* bNetwork =
3790 new PollGenerationProbe(bQueries, bNotifications, bNetworkDestructions);
3791 SharedPointer<NetworkSyscalls> bNetworkKeepalive(bNetwork);
3792 DescriptorRetirementProbe* bDescriptor = new DescriptorRetirementProbe(bDescriptorDestructions);
3793 bDescriptor->fd = DescriptorNumber;
3794 bDescriptor->setOffset(2);
3795 bDescriptor->setNetworkImpl(bNetworkKeepalive);
3796
3797 PollCloseReuseContext context(DescriptorNumber);
3798 Thread* worker = new Thread(process, pollAcrossCloseReuse, &context, nullptr, false, true, true);
3799 worker->setName("hosted poll close-reuse worker");
3800 const bool started = worker->start();
3801 NOTICE(
3802 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3803 "worker-started");
3804 bool blockedOnA = false;
3805 for (size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
3806 Thread::WaitDebugInfo info = {};
3807 if (context.entered && aQueries >= 2 && worker->getWaitDebugInfo(info) && info.queue &&
3808 info.queued && worker->getStatus() == Thread::Sleeping) {
3809 blockedOnA = true;
3810 break;
3811 }
3813 }
3814
3815 bool passed = started && blockedOnA && aQueries >= 2;
3816 NOTICE(
3817 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3818 "waiter-published-a blocked="
3819 << blockedOnA << " queries=" << aQueries.value());
3820 DescriptorLease closingA;
3821 const bool acquiredA = subsystem->acquireFileDescriptor(DescriptorNumber, closingA);
3822 const bool closedA = acquiredA && subsystem->closeFileDescriptor(DescriptorNumber, closingA);
3823 closingA.reset();
3824 subsystem->addFileDescriptor(DescriptorNumber, bDescriptor);
3825 passed = passed && closedA && aDescriptorDestructions == 0 && aNetworkDestructions == 0;
3826 NOTICE(
3827 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3828 "closed-a-published-b");
3829
3830 if (aQueries >= 2) {
3831 aNetwork->makeReadable();
3832 } else {
3833 // Failure cleanup: if the worker did not pin A, allow any lookup of B
3834 // to finish rather than leaving the hosted smoke run blocked.
3835 bNetwork->makeReadable();
3836 }
3837 NOTICE(
3838 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3839 "release-published");
3840
3841 const bool joined = started && worker->joinForCompletion();
3842 NOTICE(
3843 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3844 "worker-returned joined="
3845 << joined << " returned=" << context.returned.value()
3846 << " result=" << context.result.value());
3847 passed = passed && joined && context.returned == 1 && context.result == 1 &&
3848 (context.descriptor.revents & POLLIN) && aNotifications == 1 && bNotifications == 0 &&
3849 bQueries == 0 && aDescriptorDestructions == 1 && aNetworkDestructions == 0;
3850 aNetworkKeepalive.reset();
3851 passed = passed && aNetworkDestructions == 1;
3852
3853 DescriptorLease closingB;
3854 const bool acquiredB = subsystem->acquireFileDescriptor(DescriptorNumber, closingB);
3855 const bool closedB = acquiredB && subsystem->closeFileDescriptor(DescriptorNumber, closingB);
3856 closingB.reset();
3857 passed = passed && closedB && bDescriptorDestructions == 1 && bNetworkDestructions == 0;
3858 bNetworkKeepalive.reset();
3859 passed = passed && bNetworkDestructions == 1;
3860
3861 delete process;
3862
3863 if (!passed) {
3864 ERROR(
3865 "HOSTED-SYSCALL-TEST: FAIL poll-close-reuse-cleanup: "
3866 "poll cleanup followed the reused fd instead of its registered "
3867 "descriptor generation");
3868 return false;
3869 }
3870
3871 NOTICE("HOSTED-SYSCALL-TEST: PASS poll-close-reuse-cleanup");
3872 return true;
3873}
3874
3875struct PosixTeardownContext {
3876 explicit PosixTeardownContext(Process* process)
3877 : process(process),
3878 releaseGate(0, false),
3879 holderEntered(0),
3880 holderReturned(0),
3881 reaperEntered(0),
3882 processDeleted(0) {}
3883
3884 Process* process;
3885 Semaphore releaseGate;
3886 Atomic<size_t> holderEntered;
3887 Atomic<size_t> holderReturned;
3888 Atomic<size_t> reaperEntered;
3889 Atomic<size_t> processDeleted;
3890};
3891
3892int holdMemoryMapLifecycleGate(void* parameter) {
3893 PosixTeardownContext* context = reinterpret_cast<PosixTeardownContext*>(parameter);
3894 MemoryMapManager::instance().acquireLifecycleGateForHostedTest();
3895 context->holderEntered += 1;
3896 const bool released = context->releaseGate.acquireForCompletion();
3897 MemoryMapManager::instance().releaseLifecycleGateForHostedTest();
3898 context->holderReturned += 1;
3899 return released ? 0 : 1;
3900}
3901
3902int deletePosixProcess(void* parameter) {
3903 PosixTeardownContext* context = reinterpret_cast<PosixTeardownContext*>(parameter);
3904 context->reaperEntered += 1;
3905 delete context->process;
3906 context->processDeleted += 1;
3907 return 0;
3908}
3909
3910bool posixTeardownContention(Process* kernelProcess) {
3911 Process* process = new Process(kernelProcess);
3912 process->setSubsystem(new PosixSubsystem);
3913 PosixTeardownContext context(process);
3914
3915 Thread* holder =
3916 new Thread(kernelProcess, holdMemoryMapLifecycleGate, &context, nullptr, false, true, true);
3917 holder->setName("hosted mmap lifecycle holder");
3918 bool passed = holder->start();
3919
3920 for (size_t attempt = 0; attempt < HostedAttempts && passed && !context.holderEntered;
3921 ++attempt) {
3923 }
3924 passed = passed && context.holderEntered == 1;
3925
3926 Thread* reaper = nullptr;
3927 bool blocked = false;
3928 if (passed) {
3929 reaper = new Thread(kernelProcess, deletePosixProcess, &context, nullptr, false, true, true);
3930 reaper->setName("hosted POSIX process reaper");
3931 passed = reaper->start();
3932
3933 for (size_t attempt = 0; attempt < HostedAttempts && passed; ++attempt) {
3934 Thread::WaitDebugInfo info = {};
3935 if (context.reaperEntered && reaper->getWaitDebugInfo(info) && info.queue && info.queued &&
3936 info.channelOwner == MemoryMapManager::instance().lifecycleGateAddressForHostedTest() &&
3937 reaper->getStatus() == Thread::Sleeping) {
3938 blocked = true;
3939 break;
3940 }
3942 }
3943 passed = passed && blocked && context.processDeleted == 0;
3944 }
3945
3946 context.releaseGate.release();
3947 passed = holder->join() && passed;
3948 if (reaper) {
3949 passed = reaper->join() && passed;
3950 passed = passed && context.processDeleted == 1;
3951 } else {
3952 delete process;
3953 }
3954
3955 if (!passed) {
3956 ERROR(
3957 "HOSTED-SYSCALL-TEST: FAIL posix-teardown-contention: "
3958 "real PosixSubsystem destruction did not sleep and resume on "
3959 "the memory-map lifecycle gate");
3960 return false;
3961 }
3962
3963 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-teardown-contention");
3964 return true;
3965}
3966
3967bool zeroResultWinsSignal(Thread* thread) {
3968 thread->setErrno(0);
3969 thread->setInterruptionReason(Thread::InterruptedBySignal);
3970 const bool completed = finishInterruptibleSocketCall(thread, static_cast<ssize_t>(0));
3971 const bool passed =
3972 completed && thread->getInterruptionReason() == Thread::NotInterrupted && !thread->getErrno();
3973 if (!passed) {
3974 ERROR(
3975 "HOSTED-SYSCALL-TEST: FAIL socket-zero-result-signal: "
3976 "EOF or zero-length success was replaced with EINTR");
3977 thread->clearInterruption();
3978 thread->setErrno(0);
3979 return false;
3980 }
3981
3982 NOTICE("HOSTED-SYSCALL-TEST: PASS socket-zero-result-signal");
3983 return true;
3984}
3985
3986bool cloneStateDropsParentErrnoDestination() {
3987 long error = 0;
3988 SyscallState parent = {};
3989 parent.error_ptr = reinterpret_cast<uintptr_t>(&error);
3990 parent.result = 37;
3991
3992 const SyscallState child = posix_copy_clone_state(parent);
3993 const bool passed = !child.error_ptr && child.result == parent.result &&
3994 parent.error_ptr == reinterpret_cast<uintptr_t>(&error);
3995 if (!passed) {
3996 ERROR(
3997 "HOSTED-SYSCALL-TEST: FAIL clone-errno-lifetime: "
3998 "the child retained its parent's stack-local errno destination");
3999 return false;
4000 }
4001
4002 NOTICE("HOSTED-SYSCALL-TEST: PASS clone-errno-lifetime");
4003 return true;
4004}
4005
4006bool cloneVmNullStackPreservesInterrupts() {
4007 const bool interruptsWereEnabled = Processor::getInterrupts();
4008 const uintptr_t result = SyscallManager::instance().syscall(
4009 posix, POSIX_CLONE, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD, 0, 0, 0,
4010 0);
4011 const bool interruptsStillEnabled = Processor::getInterrupts();
4012 if (interruptsStillEnabled != interruptsWereEnabled) {
4013 Processor::setInterrupts(interruptsWereEnabled);
4014 }
4015
4016 if (!interruptsWereEnabled || !interruptsStillEnabled || result != static_cast<uintptr_t>(-1)) {
4017 ERROR(
4018 "HOSTED-SYSCALL-TEST: FAIL clone-vm-null-stack-interrupts: "
4019 "the invalid clone path did not preserve its enabled IRQ state");
4020 return false;
4021 }
4022
4023 NOTICE("HOSTED-SYSCALL-TEST: PASS clone-vm-null-stack-interrupts");
4024 return true;
4025}
4026
4027enum CloneVmBeforeStartAction {
4028 CancelChildBeforeStart,
4029 WaitForProcessExit,
4030};
4031
4032struct CloneVmUserFixture {
4033 int parentTid;
4034 int childTid;
4035 uintptr_t childTls;
4036 alignas(16) uint8_t childStack[4096];
4037};
4038
4039struct CloneVmExitRaceContext {
4040 explicit CloneVmExitRaceContext(CloneVmBeforeStartAction action)
4041 : process(nullptr),
4042 caller(nullptr),
4043 terminator(nullptr),
4044 child(nullptr),
4045 action(action),
4046 beforeStart(0, true),
4047 userAddress(0),
4048 parentTid(-1),
4049 childTid(-1),
4050 observedTid(0),
4051 callerEntered(0),
4052 callerReturned(0),
4053 callerInterruptsBefore(0),
4054 callerInterruptsAfter(0),
4055 terminatorEntered(0),
4056 terminatorReturned(0),
4057 hookCalls(0),
4058 tidsReady(0),
4059 terminationElectionCalls(0),
4060 ownershipWindowReleased(0),
4061 ownershipCancellationObserved(0),
4062 ownershipStartObserved(0),
4063 controlledHookRelease(0),
4064 electionTimedOut(0),
4065 childCancellationPublished(0),
4066 cloneResult(static_cast<size_t>(-1)),
4067 childCancellationRequested(0),
4068 childCancellationReapable(0),
4069 terminatorStarted(0),
4070 terminalCancellation(0),
4071 unexpectedHookRelease(0),
4072 hookTimedOut(0),
4073 rescueCancellation(0),
4074 processDestructions(0),
4075 subsystemDestructions(0) {}
4076
4077 Process* process;
4078 Thread* caller;
4079 Thread* terminator;
4080 Atomic<Thread*> child;
4081 CloneVmBeforeStartAction action;
4082 Semaphore beforeStart;
4083 uintptr_t userAddress;
4084 int parentTid;
4085 int childTid;
4086 size_t observedTid;
4087 Atomic<size_t> callerEntered;
4088 Atomic<size_t> callerReturned;
4089 Atomic<size_t> callerInterruptsBefore;
4090 Atomic<size_t> callerInterruptsAfter;
4091 Atomic<size_t> terminatorEntered;
4092 Atomic<size_t> terminatorReturned;
4093 Atomic<size_t> hookCalls;
4094 Atomic<size_t> tidsReady;
4095 Atomic<size_t> terminationElectionCalls;
4096 Atomic<size_t> ownershipWindowReleased;
4097 Atomic<size_t> ownershipCancellationObserved;
4098 Atomic<size_t> ownershipStartObserved;
4099 Atomic<size_t> controlledHookRelease;
4100 Atomic<size_t> electionTimedOut;
4101 Atomic<size_t> childCancellationPublished;
4102 Atomic<size_t> cloneResult;
4103 Atomic<size_t> childCancellationRequested;
4104 Atomic<size_t> childCancellationReapable;
4105 Atomic<size_t> terminatorStarted;
4106 Atomic<size_t> terminalCancellation;
4107 Atomic<size_t> unexpectedHookRelease;
4108 Atomic<size_t> hookTimedOut;
4109 Atomic<size_t> rescueCancellation;
4110 Atomic<size_t> processDestructions;
4111 Atomic<size_t> subsystemDestructions;
4112};
4113
4114CloneVmExitRaceContext* g_CloneVmExitRaceContext = nullptr;
4115
4116class CloneVmExitRaceProcess final : public PosixProcess {
4117 public:
4118 CloneVmExitRaceProcess(Process* parent, Atomic<size_t>& destructions)
4119 : PosixProcess(parent), m_Destructions(destructions) {}
4120
4121 ~CloneVmExitRaceProcess() override {
4122 m_Destructions += 1;
4123 }
4124
4125 private:
4126 Atomic<size_t>& m_Destructions;
4127};
4128
4129class CloneVmExitRaceSubsystem final : public PosixSubsystem {
4130 public:
4131 explicit CloneVmExitRaceSubsystem(Atomic<size_t>& destructions)
4132 : PosixSubsystem(), m_Destructions(destructions) {}
4133
4134 ~CloneVmExitRaceSubsystem() override {
4135 m_Destructions += 1;
4136 }
4137
4138 private:
4139 Atomic<size_t>& m_Destructions;
4140};
4141
4142int terminateCloneVmProcess(void* parameter) {
4143 CloneVmExitRaceContext* context = reinterpret_cast<CloneVmExitRaceContext*>(parameter);
4144 context->terminatorEntered += 1;
4145 SyscallManager::instance().syscall(posix, POSIX_EXIT_GROUP, 0);
4146 context->terminatorReturned += 1;
4147 return 1;
4148}
4149
4150void terminateCloneVmBeforeStart(Thread* child, size_t threadId, void* parameter) {
4151 CloneVmExitRaceContext* context = reinterpret_cast<CloneVmExitRaceContext*>(parameter);
4152 if (!context || !child || child->getParent() != context->process) {
4153 return;
4154 }
4155 context->child = child;
4156 context->observedTid = threadId;
4157 auto* user = reinterpret_cast<CloneVmUserFixture*>(context->userAddress);
4158 uintptr_t tlsValue = 0;
4159 const bool copied = PosixSubsystem::copyFromUser(&context->parentTid, &user->parentTid,
4160 sizeof(context->parentTid)) &&
4161 PosixSubsystem::copyFromUser(&context->childTid, &user->childTid,
4162 sizeof(context->childTid)) &&
4163 PosixSubsystem::copyFromUser(&tlsValue, &user->childTls, sizeof(tlsValue));
4164 if (copied && child->getId() == threadId && context->parentTid == static_cast<int>(threadId) &&
4165 context->childTid == static_cast<int>(threadId) &&
4166 tlsValue == reinterpret_cast<uintptr_t>(&user->childTls)) {
4167 context->tidsReady += 1;
4168 }
4169 context->hookCalls += 1;
4170
4171 bool cancelChild = context->action == CancelChildBeforeStart || context->rescueCancellation;
4172 if (!cancelChild) {
4173 const bool released = context->beforeStart.acquire(1, 5, 0);
4174 cancelChild = context->rescueCancellation;
4175 if (!cancelChild) {
4176 if (released && context->ownershipWindowReleased) {
4177 context->controlledHookRelease += 1;
4178 return;
4179 }
4180 Thread* current = Processor::information().getCurrentThread();
4181 if (!released && current && current->getUnwindState() == Thread::TerminateThread) {
4182 context->terminalCancellation += 1;
4183 } else {
4184 context->unexpectedHookRelease += 1;
4185 if (!released) {
4186 context->hookTimedOut += 1;
4187 }
4188 }
4189 return;
4190 }
4191 }
4192
4194 context->childCancellationPublished = 1;
4195 context->childCancellationRequested += 1;
4196 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4197 if (child->isReapableForHostedTest()) {
4198 context->childCancellationReapable += 1;
4199 return;
4200 }
4202 }
4203 context->hookTimedOut += 1;
4204}
4205
4206void observeCloneVmTerminationElection(Process* process, Thread* owner) {
4207 CloneVmExitRaceContext* context = __atomic_load_n(&g_CloneVmExitRaceContext, __ATOMIC_ACQUIRE);
4208 if (!context || process != context->process || owner != context->terminator) {
4209 return;
4210 }
4211 context->terminationElectionCalls += 1;
4212 context->ownershipWindowReleased = 1;
4213 context->beforeStart.release();
4214 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4215 Thread* child = context->child.value();
4216 if (child && context->callerReturned) {
4217 if (child->getUnwindState() == Thread::TerminateThread &&
4218 !child->wasStartPublishedForHostedTest()) {
4219 context->ownershipCancellationObserved += 1;
4220 } else {
4221 context->ownershipStartObserved += 1;
4222 }
4223 context->childCancellationPublished = 1;
4224 return;
4225 }
4227 }
4228 context->electionTimedOut += 1;
4229 context->childCancellationPublished = 1;
4230}
4231
4232void clearCloneVmHooks() {
4233 posixSetCloneBeforeStartHookForTest(nullptr, nullptr);
4234 Process::setTerminationElectionHook(nullptr);
4235 __atomic_store_n(&g_CloneVmExitRaceContext, static_cast<CloneVmExitRaceContext*>(nullptr),
4236 __ATOMIC_RELEASE);
4237}
4238
4239int cloneVmWhileProcessExits(void* parameter) {
4240 CloneVmExitRaceContext* context = reinterpret_cast<CloneVmExitRaceContext*>(parameter);
4241 context->callerEntered += 1;
4242 const size_t pageSize = PhysicalMemoryManager::getPageSize();
4243 const size_t length = (sizeof(CloneVmUserFixture) + pageSize - 1) & ~(pageSize - 1);
4244 uintptr_t address = 0;
4245 if (!context->process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
4246 context->callerReturned += 1;
4247 return 1;
4248 }
4249 uintptr_t mappedAddress = address;
4251 mappedAddress, length, MemoryMappedObject::Read | MemoryMappedObject::Write);
4252 if (!mapping || mappedAddress != address) {
4253 if (mapping) {
4254 MemoryMapManager::instance().remove(mappedAddress, length);
4255 }
4256 context->process->freeUserRange(Process::UserRegion::Normal, address, length);
4257 context->callerReturned += 1;
4258 return 1;
4259 }
4260 context->userAddress = address;
4261 auto* user = reinterpret_cast<CloneVmUserFixture*>(address);
4262 const CloneVmUserFixture initial = {-1, -1, 0, {}};
4263 if (!PosixSubsystem::copyToUser(user, &initial, sizeof(initial))) {
4264 context->callerReturned += 1;
4265 return 1;
4266 }
4267 const bool interruptsWereEnabled = Processor::getInterrupts();
4268 context->callerInterruptsBefore = interruptsWereEnabled ? 1 : 0;
4269 context->cloneResult = SyscallManager::instance().syscall(
4270 posix, POSIX_CLONE,
4271 CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS |
4272 CLONE_PARENT_SETTID | CLONE_CHILD_SETTID,
4273 reinterpret_cast<uintptr_t>(user->childStack + sizeof(user->childStack)),
4274 reinterpret_cast<uintptr_t>(&user->parentTid), reinterpret_cast<uintptr_t>(&user->childTid),
4275 reinterpret_cast<uintptr_t>(&user->childTls));
4276 context->callerInterruptsAfter = Processor::getInterrupts() ? 1 : 0;
4277 if (Processor::getInterrupts() != interruptsWereEnabled) {
4278 Processor::setInterrupts(interruptsWereEnabled);
4279 }
4280 context->callerReturned += 1;
4281 return 1;
4282}
4283
4284int cleanupCloneVmUserFixture(void* parameter) {
4285 CloneVmExitRaceContext* context = reinterpret_cast<CloneVmExitRaceContext*>(parameter);
4286 if (context->userAddress) {
4287 const size_t pageSize = PhysicalMemoryManager::getPageSize();
4288 const size_t length = (sizeof(CloneVmUserFixture) + pageSize - 1) & ~(pageSize - 1);
4289 MemoryMapManager::instance().remove(context->userAddress, length);
4290 context->process->freeUserRange(Process::UserRegion::Normal, context->userAddress, length);
4291 context->userAddress = 0;
4292 }
4293 return 0;
4294}
4295
4296bool waitForCloneVmHookPause(CloneVmExitRaceContext* context) {
4297 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4298 Thread::WaitDebugInfo info = {};
4299 if (context->hookCalls == static_cast<size_t>(1) && context->caller->getWaitDebugInfo(info) &&
4300 info.queue && info.queued && info.channelOwner == &context->beforeStart &&
4301 context->caller->getStatus() == Thread::Sleeping) {
4302 return true;
4303 }
4304 if (context->unexpectedHookRelease || context->hookTimedOut || context->callerReturned) {
4305 return false;
4306 }
4308 }
4309 return false;
4310}
4311
4312bool waitForCloneVmThreadReapable(Thread* thread) {
4313 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4314 if (thread->isReapableForHostedTest()) {
4315 return true;
4316 }
4318 }
4319 return false;
4320}
4321
4322bool waitForCloneVmThreadCount(Process* process, size_t count) {
4323 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4324 if (process->getNumThreads() == count) {
4325 return true;
4326 }
4328 }
4329 return false;
4330}
4331
4332NORETURN void fatalCloneVmFixture(const char* detail) {
4333 clearCloneVmHooks();
4334 FATAL("HOSTED-SYSCALL-TEST: clone fixture could not retire safely: " << detail);
4335 panic(detail);
4336}
4337
4338bool cloneVmDetachedCancellationReturnsCachedTid(Process* kernelProcess) {
4339 CloneVmExitRaceContext* context = new CloneVmExitRaceContext(CancelChildBeforeStart);
4340 CloneVmExitRaceProcess* process =
4341 new CloneVmExitRaceProcess(kernelProcess, context->processDestructions);
4342 process->setSubsystem(new CloneVmExitRaceSubsystem(context->subsystemDestructions));
4343 process->description() = "hosted clone detached-cancellation fixture";
4344
4345 context->process = process;
4346 context->caller =
4347 new Thread(process, cloneVmWhileProcessExits, context, nullptr, false, true, true);
4348 context->caller->setName("hosted clone detached-cancellation caller");
4349 process->publish();
4350
4351 posixSetCloneBeforeStartHookForTest(terminateCloneVmBeforeStart, context);
4352 const bool callerStarted = context->caller->start();
4353 bool callerReapable = callerStarted && waitForCloneVmThreadReapable(context->caller);
4354 if (!callerReapable) {
4355 context->rescueCancellation = 1;
4356 context->caller->setUnwindState(Thread::TerminateThread);
4357 context->beforeStart.release();
4358 callerReapable = waitForCloneVmThreadReapable(context->caller);
4359 }
4360 if (!callerReapable) {
4361 fatalCloneVmFixture("detached-cancellation caller remained live after rescue");
4362 }
4363 clearCloneVmHooks();
4364
4365 const bool childDeletedBeforeReturn = waitForCloneVmThreadCount(process, 1);
4366 bool passed = callerStarted && callerReapable && childDeletedBeforeReturn &&
4367 process->getState() == Process::Active && context->callerEntered == 1 &&
4368 context->callerReturned == 1 && context->callerInterruptsBefore == 1 &&
4369 context->callerInterruptsAfter == 1 && context->hookCalls == 1 &&
4370 context->tidsReady == 1 && context->childCancellationRequested == 1 &&
4371 context->childCancellationReapable == 1 && !context->hookTimedOut &&
4372 !context->unexpectedHookRelease && context->observedTid &&
4373 context->cloneResult == context->observedTid &&
4374 context->parentTid == static_cast<int>(context->observedTid) &&
4375 context->childTid == static_cast<int>(context->observedTid);
4376
4377 if (!childDeletedBeforeReturn) {
4378 fatalCloneVmFixture("detached child remained live after its creator retired");
4379 }
4380 if (!context->caller->joinForCompletion()) {
4381 fatalCloneVmFixture("detached-cancellation caller could not be joined");
4382 }
4383 if (!waitForCloneVmThreadCount(process, 0)) {
4384 fatalCloneVmFixture("detached-cancellation process retained a live thread");
4385 }
4386 Thread* cleanup = new Thread(process, cleanupCloneVmUserFixture, context, nullptr, false, true);
4387 if (!cleanup->joinForCompletion()) {
4388 fatalCloneVmFixture("detached-cancellation user mapping cleanup could not be joined");
4389 }
4390
4391 delete process;
4392 passed = passed && context->processDestructions == 1 && context->subsystemDestructions == 1;
4393 delete context;
4394
4395 if (!passed) {
4396 ERROR(
4397 "HOSTED-SYSCALL-TEST: FAIL clone-vm-detached-cached-tid: "
4398 "POSIX clone did not return the cached ID after detached cancellation");
4399 return false;
4400 }
4401
4402 NOTICE("HOSTED-SYSCALL-TEST: PASS clone-vm-detached-cached-tid");
4403 return true;
4404}
4405
4406bool cloneVmTerminalStartCancellation(Process* kernelProcess) {
4407 CloneVmExitRaceContext* context = new CloneVmExitRaceContext(WaitForProcessExit);
4408 CloneVmExitRaceProcess* process =
4409 new CloneVmExitRaceProcess(kernelProcess, context->processDestructions);
4410 process->setSubsystem(new CloneVmExitRaceSubsystem(context->subsystemDestructions));
4411 process->description() = "hosted clone-vs-exit fixture";
4412
4413 context->process = process;
4414 context->caller =
4415 new Thread(process, cloneVmWhileProcessExits, context, nullptr, false, true, true);
4416 context->caller->setName("hosted clone-vs-exit caller");
4417 context->terminator =
4418 new Thread(process, terminateCloneVmProcess, context, nullptr, false, true, true);
4419 context->terminator->setName("hosted clone-vs-exit terminator");
4420 process->publish();
4421
4422 __atomic_store_n(&g_CloneVmExitRaceContext, context, __ATOMIC_RELEASE);
4423 Process::setTerminationElectionHook(observeCloneVmTerminationElection);
4424 posixSetCloneBeforeStartHookForTest(terminateCloneVmBeforeStart, context);
4425 const bool callerStarted = context->caller->start();
4426 const bool callerPaused = callerStarted && waitForCloneVmHookPause(context);
4427 Thread* pausedExpectedChild = context->child.value();
4428 Process::ThreadLease pausedChild;
4429 const bool pausedChildPinned = callerPaused && pausedExpectedChild &&
4430 process->acquireThread(pausedChild, pausedExpectedChild) &&
4431 pausedChild->getId() == context->observedTid;
4432 const bool terminatorStarted = context->terminator->start();
4433 if (terminatorStarted) {
4434 context->terminatorStarted += 1;
4435 } else {
4436 context->rescueCancellation = 1;
4437 Thread* rescueExpectedChild = context->child.value();
4438 bool publishedChildSafe = !rescueExpectedChild;
4439 if (pausedChildPinned) {
4441 context->childCancellationPublished = 1;
4442 publishedChildSafe = true;
4443 } else if (rescueExpectedChild) {
4444 Process::ThreadLease rescueChild;
4445 if (process->acquireThread(rescueChild, rescueExpectedChild)) {
4447 context->childCancellationPublished = 1;
4448 publishedChildSafe = true;
4449 } else {
4450 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4451 if (process->getNumThreads() == 2) {
4452 context->child = nullptr;
4453 publishedChildSafe = true;
4454 break;
4455 }
4457 }
4458 }
4459 }
4460 if (!publishedChildSafe) {
4461 pausedChild.reset();
4462 fatalCloneVmFixture("published child could not be cancelled for start-failure rescue");
4463 }
4464 pausedChild.reset();
4465 context->terminator->setUnwindState(Thread::TerminateThread);
4466 context->caller->setUnwindState(Thread::TerminateThread);
4467 context->beforeStart.release();
4468 }
4469 pausedChild.reset();
4470
4471 bool terminated = false;
4472 for (size_t attempt = 0; attempt < HostedAttempts && terminatorStarted; ++attempt) {
4473 if (process->isTerminationReapableForHostedTest()) {
4474 terminated = true;
4475 break;
4476 }
4478 }
4479 if (terminatorStarted && !terminated) {
4480 context->beforeStart.release();
4481 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4482 if (process->isTerminationReapableForHostedTest()) {
4483 terminated = true;
4484 break;
4485 }
4487 }
4488 }
4489
4490 if (!terminatorStarted) {
4491 const bool callerReapable = waitForCloneVmThreadReapable(context->caller);
4492 const bool terminatorReapable = waitForCloneVmThreadReapable(context->terminator);
4493 if (!callerReapable || !terminatorReapable) {
4494 fatalCloneVmFixture("start-failure rescue left a worker live");
4495 }
4496 clearCloneVmHooks();
4497 if (!context->caller->joinForCompletion() || !context->terminator->joinForCompletion()) {
4498 fatalCloneVmFixture("start-failure rescue could not join both workers");
4499 }
4500 if (!waitForCloneVmThreadCount(process, 0)) {
4501 fatalCloneVmFixture("start-failure rescue retained a cloned child");
4502 }
4503 Thread* cleanup = new Thread(process, cleanupCloneVmUserFixture, context, nullptr, false, true);
4504 if (!cleanup->joinForCompletion()) {
4505 fatalCloneVmFixture("start-failure user mapping cleanup could not be joined");
4506 }
4507 delete process;
4508 const bool destroyed = context->processDestructions == 1 && context->subsystemDestructions == 1;
4509 delete context;
4510 if (!destroyed) {
4511 FATAL("HOSTED-SYSCALL-TEST: clone start-failure rescue did not destroy exact owners");
4512 }
4513 ERROR(
4514 "HOSTED-SYSCALL-TEST: FAIL clone-vm-terminal-start-cancellation: "
4515 "the exit worker did not start");
4516 return false;
4517 }
4518 if (!terminated) {
4519 fatalCloneVmFixture("process-exit cancellation did not become reapable");
4520 }
4521 clearCloneVmHooks();
4522
4523 Thread* retiredChild = context->child.value();
4524 const bool retired = terminated && process->getState() == Process::Terminated &&
4525 process->getNumThreads() == 3 &&
4526 context->caller->getStatus() == Thread::AwaitingJoin &&
4527 context->terminator->getStatus() == Thread::AwaitingJoin && retiredChild &&
4528 retiredChild->getStatus() == Thread::AwaitingJoin &&
4529 !retiredChild->wasStartPublishedForHostedTest();
4530 bool passed =
4531 callerPaused && retired && context->callerEntered == 1 && context->callerReturned == 1 &&
4532 context->callerInterruptsBefore == 1 && context->callerInterruptsAfter == 1 &&
4533 context->terminatorEntered == 1 && !context->terminatorReturned && context->hookCalls == 1 &&
4534 context->tidsReady == 1 && context->terminationElectionCalls == 1 &&
4535 context->ownershipWindowReleased == 1 && context->ownershipCancellationObserved == 1 &&
4536 !context->ownershipStartObserved && context->controlledHookRelease == 1 &&
4537 !context->electionTimedOut && context->childCancellationPublished == 1 &&
4538 context->terminatorStarted == 1 && !context->terminalCancellation &&
4539 !context->unexpectedHookRelease && !context->hookTimedOut && context->observedTid &&
4540 context->cloneResult == context->observedTid &&
4541 context->parentTid == static_cast<int>(context->observedTid) &&
4542 context->childTid == static_cast<int>(context->observedTid);
4543
4544 passed = passed && pausedChildPinned;
4545 delete process;
4546 passed = passed && context->processDestructions == 1 && context->subsystemDestructions == 1;
4547 delete context;
4548
4549 if (!passed) {
4550 ERROR(
4551 "HOSTED-SYSCALL-TEST: FAIL clone-vm-terminal-start-cancellation: "
4552 "terminal cancellation did not retire the published clone exactly once");
4553 return false;
4554 }
4555
4556 NOTICE("HOSTED-SYSCALL-TEST: PASS clone-vm-terminal-start-cancellation");
4557 return true;
4558}
4559
4560bool failedPinnedModuleRejectsUnload() {
4561 Module module;
4562 module.name.assign("hosted-failed-pinned-probe");
4563 module.unloadable = false;
4564 module.status = Module::Failed;
4565
4566 if (KernelElf::claimModuleUnloadForTest(&module) != KernelElf::TestUnloadPinned ||
4567 module.status != Module::Failed || module.unloadComplete) {
4568 ERROR(
4569 "HOSTED-SYSCALL-TEST: FAIL failed-pinned-module: "
4570 "failed initialisation did not preserve its pinned module image");
4571 return false;
4572 }
4573
4574 NOTICE("HOSTED-SYSCALL-TEST: PASS failed-pinned-module");
4575 return true;
4576}
4577
4578ModuleInfo* findStaticModuleInfo(const char* name, size_t& matches) {
4579 ModuleInfo* match = nullptr;
4580 matches = 0;
4581 for (size_t i = 0; i < g_StaticDriverN; ++i) {
4582 ModuleInfo* info = g_StaticDrivers[i];
4583 if (info && info->name && !StringCompare(info->name, name)) {
4584 match = info;
4585 ++matches;
4586 }
4587 }
4588 return match;
4589}
4590
4591bool moduleInfoDependsOn(ModuleInfo* info, const char* dependency, bool optional = false) {
4592 const char** dependencies = optional ? info->opt_dependencies : info->dependencies;
4593 if (!dependencies) {
4594 return false;
4595 }
4596 for (size_t i = 0; dependencies[i]; ++i) {
4597 if (!StringCompare(dependencies[i], dependency)) {
4598 return true;
4599 }
4600 }
4601 return false;
4602}
4603
4604bool linkerModuleMetadataIsPinned() {
4605 size_t matches = 0;
4606 ModuleInfo* linker = findStaticModuleInfo("linker", matches);
4607
4608 if (matches != 1 || !linker || linker->unloadable || linker->runtimeUnloadable ||
4609 !moduleInfoDependsOn(linker, "vfs")) {
4610 ERROR(
4611 "HOSTED-SYSCALL-TEST: FAIL linker-pinned-metadata: "
4612 "the real linker ModuleInfo did not pin its dependency closure");
4613 return false;
4614 }
4615
4616 NOTICE("HOSTED-SYSCALL-TEST: PASS linker-pinned-metadata");
4617 return true;
4618}
4619
4620bool filesystemModuleUnloadPolicyIsCorrect() {
4621 size_t posixMatches = 0;
4622 size_t mountrootMatches = 0;
4623 size_t ramfsMatches = 0;
4624 size_t rawfsMatches = 0;
4625 ModuleInfo* posix = findStaticModuleInfo("posix", posixMatches);
4626 ModuleInfo* mountroot = findStaticModuleInfo("mountroot", mountrootMatches);
4627 ModuleInfo* ramfs = findStaticModuleInfo("ramfs", ramfsMatches);
4628 ModuleInfo* rawfs = findStaticModuleInfo("rawfs", rawfsMatches);
4629
4630 const bool metadataValid =
4631 posixMatches == 1 && mountrootMatches == 1 && ramfsMatches == 1 && rawfsMatches == 1 &&
4632 posix && mountroot && ramfs && rawfs && posix->unloadable && !posix->runtimeUnloadable &&
4633 mountroot->unloadable && !mountroot->runtimeUnloadable && !ramfs->unloadable &&
4634 !ramfs->runtimeUnloadable && rawfs->unloadable && !rawfs->runtimeUnloadable &&
4635 moduleInfoDependsOn(posix, "mountroot") && moduleInfoDependsOn(posix, "ramfs") &&
4636 moduleInfoDependsOn(mountroot, "vfs") && moduleInfoDependsOn(mountroot, "rawfs") &&
4637 moduleInfoDependsOn(mountroot, "ramfs") && moduleInfoDependsOn(mountroot, "fat", true) &&
4638 moduleInfoDependsOn(mountroot, "ext2", true) &&
4639 moduleInfoDependsOn(mountroot, "iso9660", true) && moduleInfoDependsOn(ramfs, "vfs") &&
4640 moduleInfoDependsOn(rawfs, "vfs");
4641 if (!metadataValid) {
4642 ERROR(
4643 "HOSTED-SYSCALL-TEST: FAIL filesystem-unload-policy-metadata: "
4644 "the real filesystem owner metadata did not encode the expected unload policy or "
4645 "dependency closure");
4646 return false;
4647 }
4648
4649 NOTICE("HOSTED-SYSCALL-TEST: PASS filesystem-unload-policy-metadata");
4650 return true;
4651}
4652
4653bool runtimePinnedModuleAllowsLifecycleCleanup() {
4654 g_RuntimePinnedLifecycleCalls = 0;
4655
4656 Module active;
4657 active.name.assign("hosted-runtime-pinned-active-probe");
4658 active.exit = runtimePinnedLifecycleProbe;
4659 active.runtimeUnloadable = false;
4660 active.status = Module::Active;
4661
4662 bool runLifecycle = true;
4663 const KernelElf::TestModuleUnloadClaim explicitActive =
4664 KernelElf::claimModuleUnloadForTest(&active, false, &runLifecycle);
4665 const bool explicitActiveValid = explicitActive == KernelElf::TestUnloadRuntimePinned &&
4666 !runLifecycle && active.status == Module::Active &&
4667 !active.unloadComplete && !g_RuntimePinnedLifecycleCalls;
4668 if (explicitActive == KernelElf::TestUnloadClaimed) {
4669 KernelElf::completeModuleUnloadForTest(&active);
4670 }
4671 if (!explicitActiveValid) {
4672 ERROR(
4673 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4674 "explicit unload escaped the runtime-only lifetime boundary");
4675 return false;
4676 }
4677
4678 const KernelElf::TestModuleUnloadClaim shutdownActive =
4679 KernelElf::claimModuleUnloadForTest(&active, true, &runLifecycle);
4680 const bool shutdownActiveValid = shutdownActive == KernelElf::TestUnloadClaimed && runLifecycle &&
4681 active.status == Module::Unloading;
4682 if (shutdownActive == KernelElf::TestUnloadClaimed) {
4683 KernelElf::completeModuleUnloadForTest(&active, false, runLifecycle);
4684 }
4685 if (!shutdownActiveValid) {
4686 ERROR(
4687 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4688 "shutdown could not claim an active runtime-pinned module");
4689 return false;
4690 }
4691
4692 Module failed;
4693 failed.name.assign("hosted-runtime-pinned-failed-probe");
4694 failed.exit = runtimePinnedLifecycleProbe;
4695 failed.runtimeUnloadable = false;
4696 failed.status = Module::Failed;
4697 runLifecycle = true;
4698 const KernelElf::TestModuleUnloadClaim explicitFailed =
4699 KernelElf::claimModuleUnloadForTest(&failed, false, &runLifecycle);
4700 const bool explicitFailedValid = explicitFailed == KernelElf::TestUnloadRuntimePinned &&
4701 !runLifecycle && failed.status == Module::Failed &&
4702 !failed.unloadComplete;
4703 if (explicitFailed == KernelElf::TestUnloadClaimed) {
4704 KernelElf::completeModuleUnloadForTest(&failed, true);
4705 }
4706 if (!explicitFailedValid) {
4707 ERROR(
4708 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4709 "explicit unload escaped a failed runtime-pinned module");
4710 return false;
4711 }
4712
4713 const KernelElf::TestModuleUnloadClaim failureCleanup =
4714 KernelElf::claimModuleUnloadForTest(&failed, true, &runLifecycle);
4715 const bool failureCleanupValid = failureCleanup == KernelElf::TestUnloadClaimed && runLifecycle &&
4716 failed.status == Module::Unloading;
4717 if (failureCleanup == KernelElf::TestUnloadClaimed) {
4718 KernelElf::completeModuleUnloadForTest(&failed, true, runLifecycle);
4719 }
4720 if (!failureCleanupValid) {
4721 ERROR(
4722 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4723 "failed initialisation could not claim lifecycle cleanup");
4724 return false;
4725 }
4726
4727 if (!active.isUnloaded() || !active.unloadComplete || failed.status != Module::Failed ||
4728 !failed.unloadComplete || g_RuntimePinnedLifecycleCalls != 2) {
4729 ERROR(
4730 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4731 "shutdown or failure cleanup did not publish completion");
4732 return false;
4733 }
4734
4735 NOTICE("HOSTED-SYSCALL-TEST: PASS runtime-pinned-cleanup");
4736 return true;
4737}
4738
4739bool moduleUnloadOwnershipIsRetryable() {
4740 Module module;
4741 module.name.assign("hosted-unload-owner-probe");
4742 module.status = Module::Active;
4743 Module* fixtures[] = {&module};
4744
4745 const KernelElf::TestModuleUnloadClaim first =
4746 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1, "hosted-unload-owner-probe");
4747 const KernelElf::TestModuleUnloadClaim concurrent =
4748 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1, "hosted-unload-owner-probe");
4749 KernelElf::completeModuleUnloadForTest(&module);
4750 const KernelElf::TestModuleUnloadClaim repeat =
4751 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1, "hosted-unload-owner-probe");
4752 const KernelElf::TestModuleUnloadClaim missing =
4753 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1, "hosted-unload-missing-probe");
4754
4755 if (first != KernelElf::TestUnloadClaimed || concurrent != KernelElf::TestUnloadBusy ||
4756 repeat != KernelElf::TestUnloadComplete || missing != KernelElf::TestUnloadUnknown ||
4757 !module.isUnloaded() || !module.unloadComplete ||
4758 !KernelElf::moduleExecutionWaitsForUnloadForTest()) {
4759 ERROR(
4760 "HOSTED-SYSCALL-TEST: FAIL module-unload-ownership: "
4761 "the first owner, concurrent retry, or completed tombstone was lost");
4762 return false;
4763 }
4764
4765 NOTICE("HOSTED-SYSCALL-TEST: PASS module-unload-ownership");
4766 return true;
4767}
4768
4769bool moduleShutdownOrderIsDependencySafe() {
4770 const char* nicsOptional[] = {"ne2k", nullptr};
4771 const char* ne2kDependencies[] = {"network-stack", nullptr};
4772
4773 Module networkStack;
4774 networkStack.name.assign("network-stack");
4775 networkStack.status = Module::Active;
4776
4777 Module nics;
4778 nics.name.assign("nics");
4779 nics.depends_opt = nicsOptional;
4780 nics.runtimeUnloadable = false;
4781 nics.status = Module::Active;
4782
4783 Module ne2k;
4784 ne2k.name.assign("ne2k");
4785 ne2k.depends = ne2kDependencies;
4786 ne2k.status = Module::Active;
4787
4788 Module* modules[] = {&networkStack, &nics, &ne2k};
4789 Module* order[3] = {};
4790 const size_t planned = KernelElf::planModuleUnloadOrderForTest(modules, 3, order, 3);
4791 const size_t repeated = KernelElf::planModuleUnloadOrderForTest(modules, 3, order, 3);
4792
4793 const char* cycleADependencies[] = {"cycle-b", nullptr};
4794 const char* cycleBDependencies[] = {"cycle-a", nullptr};
4795 Module cycleA;
4796 cycleA.name.assign("cycle-a");
4797 cycleA.depends = cycleADependencies;
4798 cycleA.status = Module::Active;
4799 Module cycleB;
4800 cycleB.name.assign("cycle-b");
4801 cycleB.depends = cycleBDependencies;
4802 cycleB.status = Module::Active;
4803 Module* cycle[] = {&cycleA, &cycleB};
4804 Module* cycleOrder[2] = {};
4805 const size_t cyclicPlanned = KernelElf::planModuleUnloadOrderForTest(cycle, 2, cycleOrder, 2);
4806
4807 if (planned != 3 || order[0] != &nics || order[1] != &ne2k || order[2] != &networkStack ||
4808 repeated != 0 || cyclicPlanned != 0 || cycleA.unloadComplete || cycleB.unloadComplete) {
4809 ERROR(
4810 "HOSTED-SYSCALL-TEST: FAIL module-shutdown-order: "
4811 "optional/mandatory dependents were not retired first or a cycle was torn down");
4812 return false;
4813 }
4814
4815 Module permanent;
4816 permanent.name.assign("permanent-shutdown-probe");
4817 permanent.unloadable = false;
4818 permanent.runtimeUnloadable = false;
4819 permanent.status = Module::Active;
4820 Module runtimePinned;
4821 runtimePinned.name.assign("runtime-pinned-shutdown-probe");
4822 runtimePinned.runtimeUnloadable = false;
4823 runtimePinned.status = Module::Active;
4824 Module* retentionModules[] = {&permanent, &runtimePinned};
4825 Module* retentionOrder[2] = {};
4826 const size_t retentionPlanned =
4827 KernelElf::planModuleUnloadOrderForTest(retentionModules, 2, retentionOrder, 2);
4828
4829 if (retentionPlanned != 1 || retentionOrder[0] != &runtimePinned ||
4830 !runtimePinned.unloadComplete || permanent.unloadComplete) {
4831 ERROR(
4832 "HOSTED-SYSCALL-TEST: FAIL module-shutdown-retention-policy: "
4833 "a runtime-only module was retained or a permanent pin was retired");
4834 return false;
4835 }
4836
4837 NOTICE("HOSTED-SYSCALL-TEST: PASS module-shutdown-order");
4838 NOTICE("HOSTED-SYSCALL-TEST: PASS module-shutdown-retention-policy");
4839 return true;
4840}
4841
4842bool publishTerminalBlockedHandlerFixture(Process* kernelProcess) {
4843 TerminalBlockedHandlerContext* context = new TerminalBlockedHandlerContext;
4844 PosixProcess* process = new PosixProcess(kernelProcess);
4845 process->setSubsystem(new PosixSubsystem);
4846 process->description() = "hosted blocked POSIX handler shutdown fixture";
4847 Thread* thread =
4848 new Thread(process, terminalBlockedHandlerEntry, context, nullptr, false, true, true);
4849 thread->setName("hosted blocked POSIX handler fixture");
4850 context->thread = thread;
4851 process->publish();
4852
4853 g_TerminalBlockedHandlerContext = context;
4854 SyscallManager::instance().setHandlerPinHook(terminalBlockedHandlerPin);
4855 const bool started = thread->start();
4856
4857 bool blocked = false;
4858 for (size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
4859 Thread::WaitDebugInfo info = {};
4860 if (context->hookEntered == static_cast<size_t>(1) && thread->getWaitDebugInfo(info) &&
4861 info.queue && info.queued && info.channelOwner == &context->blocker &&
4862 thread->getStatus() == Thread::Sleeping) {
4863 blocked = true;
4864 break;
4865 }
4867 }
4868
4869 SyscallManager::instance().setHandlerPinHook(nullptr);
4870
4871 if (!started || !blocked || context->exitStaged != static_cast<size_t>(1) ||
4872 context->releasedByTermination || context->unexpectedRelease || context->syscallReturned) {
4873 ERROR(
4874 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-blocked-handler-fixture: "
4875 "the POSIX handler was not admitted and blocked with a staged process exit");
4876 return false;
4877 }
4878
4879 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-terminal-blocked-handler-fixture-published");
4880 return true;
4881}
4882
4883bool runRegressions() {
4884 NOTICE("HOSTED-SYSCALL-TEST: BEGIN real-event-boundaries");
4885 Thread* thread = Processor::information().getCurrentThread();
4886 if (!thread || thread->getStateLevel()) {
4887 ERROR(
4888 "HOSTED-SYSCALL-TEST: FAIL real-event-boundaries: "
4889 "module initialisation was not at base state");
4890 return false;
4891 }
4892
4893 Process* kernelProcess = Scheduler::instance().getKernelProcess();
4894 if (!kernelProcess) {
4895 return false;
4896 }
4897
4898 NOTICE("HOSTED-SYSCALL-TEST: BEGIN usercopy");
4899 if (!runHostedUsercopyRegressions(kernelProcess)) {
4900 return false;
4901 }
4902
4903 NOTICE("HOSTED-SYSCALL-TEST: BEGIN mmap-placement");
4904 if (!runHostedMmapPlacementRegressions(kernelProcess)) {
4905 return false;
4906 }
4907
4908 NOTICE("HOSTED-SYSCALL-TEST: BEGIN time-syscall-usercopy");
4909 if (!runHostedTimeSyscallRegressions(kernelProcess)) {
4910 return false;
4911 }
4912
4913 NOTICE("HOSTED-SYSCALL-TEST: BEGIN resource-syscall-semantics");
4914 if (!runHostedResourceSyscallRegressions(kernelProcess)) {
4915 return false;
4916 }
4917
4918 NOTICE("HOSTED-SYSCALL-TEST: BEGIN child-resource-accounting");
4919 if (!runHostedChildResourceRegressions(kernelProcess)) {
4920 return false;
4921 }
4922
4923 NOTICE("HOSTED-SYSCALL-TEST: BEGIN faccessat2-semantics");
4924 if (!runHostedAccessSyscallRegressions(kernelProcess)) {
4925 return false;
4926 }
4927
4928 if (!runHostedVmPermissionRegressions() || !runHostedSystemUsercopyRegressions(kernelProcess) ||
4929 !runHostedFutexRobustRegressions(kernelProcess) ||
4930 !runHostedFileContractRegressions(kernelProcess) ||
4931 !runHostedProcessQueryRegressions(kernelProcess) ||
4932 !runHostedTermiosSyscallRegressions(kernelProcess)) {
4933 return false;
4934 }
4935
4936 NOTICE("HOSTED-SYSCALL-TEST: BEGIN sleep-clock-usercopy");
4937 if (!runHostedSleepClockSyscallRegressions(kernelProcess)) {
4938 return false;
4939 }
4940
4941 NOTICE("HOSTED-SYSCALL-TEST: BEGIN posix-exit-status");
4942 if (!runHostedPosixExitStatusRegressions(kernelProcess)) {
4943 return false;
4944 }
4945
4946 NOTICE("HOSTED-SYSCALL-TEST: BEGIN thread-signal-syscalls");
4947 if (!runHostedThreadSignalSyscallRegressions(kernelProcess)) {
4948 return false;
4949 }
4950
4951 NOTICE("HOSTED-SYSCALL-TEST: BEGIN rt-sigsuspend");
4952 if (!runHostedRtSigsuspendRegressions(kernelProcess)) {
4953 return false;
4954 }
4955
4956 bool establishedAliasPassed = true;
4957 NOTICE("HOSTED-SYSCALL-TEST: BEGIN directory-retained-lookup-atomicity");
4958 establishedAliasPassed &= directoryRetainedLookupAtomicity(kernelProcess);
4959
4960 NOTICE("HOSTED-SYSCALL-TEST: BEGIN directory-retained-lookup-lifecycle");
4961 establishedAliasPassed &= directoryRetainedLookupLifecycle(kernelProcess);
4962
4963 NOTICE("HOSTED-SYSCALL-TEST: BEGIN vfs-established-alias-serialization");
4964 establishedAliasPassed &= establishedAliasRetainSerialization(kernelProcess);
4965
4966 NOTICE("HOSTED-SYSCALL-TEST: BEGIN file-established-alias-lifetime");
4967 establishedAliasPassed &= establishedFileAliasLifetime();
4968
4969 NOTICE("HOSTED-SYSCALL-TEST: BEGIN process-filesystem-context-lifetime");
4970 establishedAliasPassed &= processFilesystemContextLifetime(kernelProcess);
4971
4972 NOTICE("HOSTED-SYSCALL-TEST: BEGIN mmap-established-alias-lifetime");
4973 establishedAliasPassed &= establishedMappingAliasLifetime();
4974
4975 NOTICE("HOSTED-SYSCALL-TEST: BEGIN munmap-target-page-geometry");
4976 establishedAliasPassed &= munmapUsesTargetPageGeometry(thread);
4977
4978 NOTICE("HOSTED-SYSCALL-TEST: BEGIN mmap-split-alias-lifetime");
4979 establishedAliasPassed &= mappingManagerSplitLifetime(kernelProcess);
4980
4981 NOTICE("HOSTED-SYSCALL-TEST: BEGIN posix-path-lookup-lifetime");
4982 establishedAliasPassed &= posixPathLookupLifetime(kernelProcess);
4983 if (!establishedAliasPassed) {
4984 return false;
4985 }
4986
4987 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-close-pinning");
4988 if (!descriptorClosePinning(kernelProcess)) {
4989 return false;
4990 }
4991
4992 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-close-generation");
4993 if (!descriptorCloseGeneration(kernelProcess)) {
4994 return false;
4995 }
4996
4997 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-open-file-description-state");
4998 if (!descriptorOpenFileDescriptionState()) {
4999 return false;
5000 }
5001
5002 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-open-file-description-lifetime");
5003 if (!descriptorOpenFileDescriptionLifetime()) {
5004 return false;
5005 }
5006
5007 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-append-policy");
5008 if (!descriptorAppendPolicy(kernelProcess)) {
5009 return false;
5010 }
5011
5012 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-nonblocking-policy");
5013 if (!descriptorNonblockingPolicy()) {
5014 return false;
5015 }
5016
5017 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-position-alias-serialization");
5018 if (!descriptorPositionAliasSerialization(kernelProcess)) {
5019 return false;
5020 }
5021
5022 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-vector-io-serialization");
5023 if (!descriptorVectorIoSerialization(kernelProcess)) {
5024 return false;
5025 }
5026
5027 NOTICE("HOSTED-SYSCALL-TEST: BEGIN advisory-lock-fail-closed");
5028 if (!runHostedAdvisoryLockRegressions(kernelProcess)) {
5029 return false;
5030 }
5031
5032 NOTICE("HOSTED-SYSCALL-TEST: BEGIN scalar-io-user-buffer-lifetime");
5033 if (!runHostedScalarIoRegressions(kernelProcess)) {
5034 return false;
5035 }
5036
5037 NOTICE("HOSTED-SYSCALL-TEST: BEGIN positional-io-semantics");
5038 if (!runHostedPositionalIoRegressions(kernelProcess)) {
5039 return false;
5040 }
5041
5042 NOTICE("HOSTED-SYSCALL-TEST: BEGIN positional-vector-io-semantics");
5043 if (!runHostedPositionalVectorIoRegressions(kernelProcess)) {
5044 return false;
5045 }
5046
5047 NOTICE("HOSTED-SYSCALL-TEST: BEGIN vector-io-user-buffer-lifetime");
5048 if (!runHostedVectorIoRegressions(kernelProcess)) {
5049 return false;
5050 }
5051
5052 NOTICE("HOSTED-SYSCALL-TEST: BEGIN ppoll-linux-abi");
5053 if (!runHostedPpollRegressions(kernelProcess)) {
5054 return false;
5055 }
5056
5057 NOTICE("HOSTED-SYSCALL-TEST: BEGIN pselect-linux-abi");
5058 if (!runHostedPselectRegressions(kernelProcess)) {
5059 return false;
5060 }
5061
5062 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-dup-contract");
5063 if (!descriptorDupContract(kernelProcess)) {
5064 return false;
5065 }
5066
5067 NOTICE("HOSTED-SYSCALL-TEST: BEGIN dup3-atomic-replacement");
5068 if (!runHostedDup3Regressions(kernelProcess)) {
5069 return false;
5070 }
5071
5072 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-position-policy");
5073 if (!descriptorPositionPolicy()) {
5074 return false;
5075 }
5076
5077 NOTICE("HOSTED-SYSCALL-TEST: BEGIN select-projection");
5078 if (!selectProjectionContract()) {
5079 return false;
5080 }
5081
5082 NOTICE("HOSTED-SYSCALL-TEST: BEGIN pipe-poll-readiness");
5083 if (!pipePollReadiness(kernelProcess)) {
5084 return false;
5085 }
5086
5087 NOTICE("HOSTED-SYSCALL-TEST: BEGIN epoll-level-oneshot-ofd-lifetime");
5088 if (!epollLevelOneShotAndOfdLifetime(kernelProcess)) {
5089 return false;
5090 }
5091
5092 NOTICE("HOSTED-SYSCALL-TEST: BEGIN epoll-reordered-transition-publication");
5093 if (!epollReorderedTransitionPublication(kernelProcess)) {
5094 return false;
5095 }
5096
5097 NOTICE("HOSTED-SYSCALL-TEST: BEGIN epoll-persistent-fifo-reopen-reclose");
5098 if (!epollPersistentFifoReopenReclose(kernelProcess)) {
5099 return false;
5100 }
5101
5102 NOTICE("HOSTED-SYSCALL-TEST: BEGIN eventfd-counter-readiness-lifetime");
5103 if (!runHostedEventFdRegressions(kernelProcess)) {
5104 return false;
5105 }
5106
5107 NOTICE("HOSTED-SYSCALL-TEST: BEGIN inotify-vfs-epoll-lifetime");
5108 if (!runHostedInotifyRegressions(kernelProcess)) {
5109 return false;
5110 }
5111
5112 NOTICE("HOSTED-SYSCALL-TEST: BEGIN poll-close-reuse-cleanup");
5113 if (!pollCloseReuseCleanup(kernelProcess)) {
5114 return false;
5115 }
5116
5117 NOTICE("HOSTED-SYSCALL-TEST: BEGIN posix-teardown-contention");
5118 if (!posixTeardownContention(kernelProcess)) {
5119 return false;
5120 }
5121
5122 NOTICE("HOSTED-SYSCALL-TEST: BEGIN unix-bind-replacement-lifetime");
5123 if (!runHostedUnixEndpointLifetimeRegression(kernelProcess)) {
5124 return false;
5125 }
5126
5127 NOTICE("HOSTED-SYSCALL-TEST: BEGIN scm-rights-datagram");
5128 if (!runHostedScmRightsRegressions(kernelProcess)) {
5129 return false;
5130 }
5131
5132 NOTICE("HOSTED-SYSCALL-TEST: BEGIN scm-rights-stream");
5133 if (!runHostedScmStreamRegressions(kernelProcess)) {
5134 return false;
5135 }
5136
5137 NOTICE("HOSTED-SYSCALL-TEST: BEGIN unix-stream-interruption");
5138 if (!runHostedUnixStreamInterruptionRegressions(kernelProcess)) {
5139 return false;
5140 }
5141
5142 NOTICE("HOSTED-SYSCALL-TEST: BEGIN socket-zero-result-signal");
5143 if (!zeroResultWinsSignal(thread)) {
5144 return false;
5145 }
5146
5147 NOTICE("HOSTED-SYSCALL-TEST: BEGIN clone-errno-lifetime");
5148 if (!cloneStateDropsParentErrnoDestination()) {
5149 return false;
5150 }
5151
5152 NOTICE("HOSTED-SYSCALL-TEST: BEGIN clone-process-routing");
5153 if (!runHostedCloneRoutingRegressions(kernelProcess)) {
5154 return false;
5155 }
5156
5157 NOTICE("HOSTED-SYSCALL-TEST: BEGIN clone-vm-null-stack-interrupts");
5158 if (!cloneVmNullStackPreservesInterrupts()) {
5159 return false;
5160 }
5161
5162 NOTICE("HOSTED-SYSCALL-TEST: BEGIN clone-vm-detached-cached-tid");
5163 if (!cloneVmDetachedCancellationReturnsCachedTid(kernelProcess)) {
5164 return false;
5165 }
5166
5167 NOTICE("HOSTED-SYSCALL-TEST: BEGIN clone-vm-terminal-start-cancellation");
5168 if (!cloneVmTerminalStartCancellation(kernelProcess)) {
5169 return false;
5170 }
5171
5172 NOTICE("HOSTED-SYSCALL-TEST: BEGIN failed-pinned-module");
5173 if (!failedPinnedModuleRejectsUnload()) {
5174 return false;
5175 }
5176
5177 NOTICE("HOSTED-SYSCALL-TEST: BEGIN linker-pinned-metadata");
5178 if (!linkerModuleMetadataIsPinned()) {
5179 return false;
5180 }
5181
5182 NOTICE("HOSTED-SYSCALL-TEST: BEGIN filesystem-unload-policy-metadata");
5183 if (!filesystemModuleUnloadPolicyIsCorrect()) {
5184 return false;
5185 }
5186
5187 NOTICE("HOSTED-SYSCALL-TEST: BEGIN runtime-pinned-cleanup");
5188 if (!runtimePinnedModuleAllowsLifecycleCleanup()) {
5189 return false;
5190 }
5191
5192 NOTICE("HOSTED-SYSCALL-TEST: BEGIN module-unload-ownership");
5193 if (!moduleUnloadOwnershipIsRetryable()) {
5194 return false;
5195 }
5196
5197 NOTICE("HOSTED-SYSCALL-TEST: BEGIN module-shutdown-order");
5198 if (!moduleShutdownOrderIsDependencySafe()) {
5199 return false;
5200 }
5201
5203 static char pedigreeCModule[] = "pedigree-c";
5204 static char lwipModule[] = "lwip";
5205 static char networkStackModule[] = "network-stack";
5206 const uintptr_t sigretResult = manager.syscall(posix, PEDIGREE_SIGRET);
5207 const uintptr_t unwindResult = manager.syscall(posix, PEDIGREE_UNWIND_SIGNAL);
5208 const uintptr_t eventReturnResult = manager.syscall(pedigree_c, PEDIGREE_EVENT_RETURN);
5209 const uintptr_t selfUnloadResult = manager.syscall(pedigree_c, PEDIGREE_MODULE_UNLOAD,
5210 reinterpret_cast<uintptr_t>(pedigreeCModule));
5211 const uintptr_t stillLoadedResult = manager.syscall(pedigree_c, PEDIGREE_MODULE_IS_LOADED,
5212 reinterpret_cast<uintptr_t>(pedigreeCModule));
5213 const uintptr_t lwipUnloadResult =
5214 manager.syscall(pedigree_c, PEDIGREE_MODULE_UNLOAD, reinterpret_cast<uintptr_t>(lwipModule));
5215 const uintptr_t lwipStillLoadedResult = manager.syscall(pedigree_c, PEDIGREE_MODULE_IS_LOADED,
5216 reinterpret_cast<uintptr_t>(lwipModule));
5217 const uintptr_t networkStackUnloadResult = manager.syscall(
5218 pedigree_c, PEDIGREE_MODULE_UNLOAD, reinterpret_cast<uintptr_t>(networkStackModule));
5219 const uintptr_t networkStackStillLoadedResult = manager.syscall(
5220 pedigree_c, PEDIGREE_MODULE_IS_LOADED, reinterpret_cast<uintptr_t>(networkStackModule));
5221
5222 if (sigretResult != static_cast<uintptr_t>(-1) || unwindResult != static_cast<uintptr_t>(-1) ||
5223 eventReturnResult != static_cast<uintptr_t>(-1) ||
5224 selfUnloadResult != static_cast<uintptr_t>(-1) || stillLoadedResult != 1 ||
5225 lwipUnloadResult != static_cast<uintptr_t>(-1) || lwipStillLoadedResult != 1 ||
5226 networkStackUnloadResult != static_cast<uintptr_t>(-1) ||
5227 networkStackStillLoadedResult != 1 || thread->getStateLevel() || thread->getErrno()) {
5228 ERROR(
5229 "HOSTED-SYSCALL-TEST: FAIL real-event-boundaries: "
5230 "a public misuse path escaped its lifetime boundary");
5231 return false;
5232 }
5233
5234 NOTICE("HOSTED-SYSCALL-TEST: PASS real-event-boundaries");
5235
5236 PosixProcess* terminalFixture = new PosixProcess(kernelProcess);
5237 terminalFixture->setSubsystem(new PosixSubsystem);
5238 terminalFixture->description() = "hosted zero-thread POSIX shutdown fixture";
5239 terminalFixture->publish();
5240 if (terminalFixture->getNumThreads() != 0 || terminalFixture->getType() != Process::Posix) {
5241 ERROR(
5242 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-fixture: "
5243 "fixture was not published as an ownerless POSIX process");
5244 return false;
5245 }
5246 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-terminal-drain-fixture-published");
5247
5248 if (!posixDuplicateInitRollbackPreservesProcessForTest(terminalFixture)) {
5249 ERROR(
5250 "HOSTED-SYSCALL-TEST: FAIL posix-duplicate-init-rollback: "
5251 "an unowned duplicate initialisation retired an existing POSIX process");
5252 return false;
5253 }
5254 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-duplicate-init-rollback-preserved-process");
5255
5256 PosixProcess* createdFixture = new PosixProcess(kernelProcess);
5257 createdFixture->setSubsystem(new PosixSubsystem);
5258 createdFixture->description() = "hosted Created-thread POSIX shutdown fixture";
5259 Thread* createdThread =
5260 new Thread(createdFixture, terminalCreatedFixtureEntry, nullptr, nullptr, false, true, true);
5261 createdThread->setName("hosted terminal Created-thread fixture");
5262 createdFixture->publish();
5263 if (createdFixture->getNumThreads() != 1 || createdThread->getStatus() != Thread::Created ||
5264 createdFixture->getType() != Process::Posix) {
5265 ERROR(
5266 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-created-fixture: "
5267 "fixture did not retain its unstarted ordinary entry");
5268 return false;
5269 }
5270 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-terminal-drain-created-fixture-published");
5271
5272 if (!publishTerminalBlockedHandlerFixture(kernelProcess)) {
5273 return false;
5274 }
5275 return true;
5276}
5277
5278bool entry() {
5279 const bool passed =
5280 hostedSyscallProfileRequested() ? hostedRunSyscallProfile() : runRegressions();
5281 system_reset();
5282 return passed;
5283}
5284
5285void exit() {
5286 TerminalBlockedHandlerContext* context = g_TerminalBlockedHandlerContext;
5287 g_TerminalBlockedHandlerContext = nullptr;
5288 if (!context) {
5289 return;
5290 }
5291
5292 if (context->hookEntered != static_cast<size_t>(1) ||
5293 context->exitStaged != static_cast<size_t>(1) ||
5294 context->releasedByTermination != static_cast<size_t>(1) || context->unexpectedRelease ||
5295 context->syscallReturned) {
5296 ERROR(
5297 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-blocked-handler-release: "
5298 "terminal process teardown did not release the admitted handler before module exit");
5299 } else {
5300 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-terminal-blocked-handler-released-by-process-exit");
5301 }
5302 delete context;
5303}
5304} // namespace
5305
5306MODULE_INFO("hosted-syscall-smoke", &entry, &exit, "posix", "pedigree-c");
Memory-mapped file interface.
static Directory * fromFile(File *pF)
Definition Directory.h:151
bool addDirectoryEntry(const String &name, File *pTarget)
Definition Directory.cc:835
Mutex & namespaceMutationLock()
Definition Directory.h:416
AddStatus addEphemeralFile(File *pFile)
Add an ephemeral file to the directory.
Definition Directory.cc:924
void remove(const HashedStringView &s)
Definition Directory.cc:597
bool removeDirectoryEntry(const HashedStringView &name, File *expected)
Definition Directory.cc:894
virtual File * convertToFile(const DirectoryEntryMetadata &meta)
MUST_USE_RESULT bool lookupRetained(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:351
MUST_USE_RESULT LookupStatus lookupChild(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:355
Definition Disk.h:35
void setNetworkImpl(const SharedPointer< NetworkSyscalls > &implementation)
OpenFileDescriptionLease acquireOpenFileDescription() const
void setOffset(uint64_t offset)
PositionGuard lockPosition() const
int getFlags() const
Get current descriptor flags.
int getStatusFlags() const
Get current status flags.
size_t fd
Descriptor number.
uint64_t write(uint64_t size, uintptr_t buffer, bool canBlock=true)
uint64_t getOffset() const
Definition File.h:75
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1369
virtual bool isSeekable() const
Definition File.cc:820
virtual uint64_t read(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true) final
Definition File.cc:239
virtual bool isBytewise() const
Definition File.cc:1365
virtual ReadyMask queryReady(bool reading, bool writing)
Definition File.cc:1054
void dataChanged()
Definition File.cc:1452
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1376
virtual bool supportsReadinessNotifications() const
Definition File.cc:1065
virtual bool removeNode(File *parent, const String &filename, File *file)=0
virtual bool initialise(Disk *pDisk)=0
virtual const String & getVolumeLabel() const =0
bool createSymlink(const StringView &path, const String &value, File *pStartNode=0)
bool createDirectory(const StringView &path, uint32_t mask, File *pStartNode=0)
virtual File * getRoot() const =0
bool createFile(const StringView &path, uint32_t mask, File *pStartNode=0)
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
MemoryMappedObject * mapFile(File *pFile, uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms, size_t offset=0, bool bCopyOnWrite=true)
virtual MemoryMappedObject * split(uintptr_t at)=0
virtual MemoryMappedObject * clone()=0
Definition Mutex.h:56
virtual ReadyMask queryReady(bool reading, bool writing)
Definition Pipe.h:36
size_t getReaderCount()
Definition Pipe.cc:310
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
void setAbi(Abi which)
bool closeFileDescriptor(size_t fd, const DescriptorLease &descriptor)
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
void addFileDescriptor(size_t fd, FileDescriptor *pFd)
size_t getNumThreads()
Definition Process.cc:1380
void publish()
Definition Process.cc:944
LargeStaticString & description()
Definition Process.h:525
MUST_USE_RESULT bool acquireThread(ThreadLease &lease, size_t n)
Definition Process.cc:1385
static bool getInterrupts()
static ProcessorInformation & information()
static void setInterrupts(bool bEnable)
void notifyReadiness(ReadyMask mask)
Definition Readiness.cc:201
virtual ReadinessGenerations readinessGenerations()
Definition Readiness.cc:177
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:236
static SharedPointer< FilesystemContext > tryAdopt(FilesystemContext *ptr)
T * get() const
virtual uintptr_t syscall(Service_t service, uintptr_t function, uintptr_t p1=0, uintptr_t p2=0, uintptr_t p3=0, uintptr_t p4=0, uintptr_t p5=0)=0
static EXPORTED_PUBLIC SyscallManager & instance()
void setErrno(size_t err)
Definition Thread.h:482
void setUnwindState(UnwindType ut)
Definition Thread.cc:3580
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:519
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3163
size_t getErrno()
Definition Thread.h:477
bool joinForCompletion()
Definition Thread.cc:2750
bool join()
Definition Thread.cc:2746
Status getStatus() const
Definition Thread.h:433
UnwindType getUnwindState()
Definition Thread.h:535
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:574
Process * getParent() const
Definition Thread.h:340
size_t getId()
Definition Thread.h:465
bool start()
Definition Thread.cc:751
size_t getStateLevel() const
Definition Thread.h:316
MUST_USE_RESULT bool retainTrackedFile(File *pFile)
Definition VFS.cc:1640
bool untrackFile(File *pFile, bool destroy=true)
Definition VFS.cc:1670
Filesystem * getRootFilesystem() const
Definition VFS.cc:742
static VFS & instance()
Definition VFS.cc:311
void trackFile(File *pFile)
Track a File object that exists. It is necessary to keep track of File objects, or at least those tha...
Definition VFS.cc:1625
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118
Definition waits.c:9