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
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"
52#include "modules/subsys/posix/syscalls/posixSyscallNumbers.h"
54extern void system_reset();
55extern bool hostedRunSyscallProfile();
56extern "C" bool posixDuplicateInitRollbackPreservesProcessForTest(
Process* processIdentity);
57extern "C" void posixSetCloneBeforeStartHookForTest(
void (*hook)(
Thread*,
size_t,
void*),
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);
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;
105struct TerminalBlockedHandlerContext {
106 TerminalBlockedHandlerContext()
111 releasedByTermination(0),
112 unexpectedRelease(0),
113 syscallReturned(0) {}
124TerminalBlockedHandlerContext* g_TerminalBlockedHandlerContext =
nullptr;
126int terminalCreatedFixtureEntry(
void*) {
127 FATAL(
"HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-created-entry-ran");
131void terminalBlockedHandlerPin(Service_t service,
SyscallHandler*) {
132 TerminalBlockedHandlerContext* context = g_TerminalBlockedHandlerContext;
134 if (!context || service != posix || current != context->thread) {
138 context->hookEntered += 1;
140 context->exitStaged += 1;
143 const bool acquired = context->blocker.acquire();
145 context->releasedByTermination += 1;
147 context->unexpectedRelease += 1;
151int terminalBlockedHandlerEntry(
void* parameter) {
152 TerminalBlockedHandlerContext* context =
153 reinterpret_cast<TerminalBlockedHandlerContext*
>(parameter);
155 context->syscallReturned += 1;
159void runtimePinnedLifecycleProbe() {
160 ++g_RuntimePinnedLifecycleCalls;
168 ~DescriptorRetirementProbe()
override {
176class EstablishedAliasFileProbe :
public File {
179 :
File(), m_Destructions(destructions) {}
181 ~EstablishedAliasFileProbe()
override {
189void repairAliasFileProbe(EstablishedAliasFileProbe* file,
Atomic<size_t>& destructions) {
198class RetainedLookupDirectory;
200class RetainedLookupFilesystem final :
public Filesystem {
202 using RemoveHook = bool (*)(
File*,
File*,
void*);
204 RetainedLookupFilesystem()
206 m_Label(
"retained-lookup-test"),
207 m_RemoveHook(nullptr),
208 m_RemoveHookContext(nullptr) {}
210 void setRoot(
File* root) {
214 void setRemoveHook(RemoveHook hook,
void* context) {
216 m_RemoveHookContext = context;
245 return m_RemoveHook ? m_RemoveHook(parent, file, m_RemoveHookContext) : false;
251 RemoveHook m_RemoveHook;
252 void* m_RemoveHookContext;
255class RetainedLookupFile final :
public File {
258 Atomic<size_t>& destructions, RetainedLookupDirectory* lockProbe =
nullptr,
260 :
File(name, 0, 0, 0, 0, filesystem, 0, parent),
261 m_Destructions(destructions),
262 m_LockProbe(lockProbe),
263 m_LockAvailable(lockAvailable) {}
265 ~RetainedLookupFile()
override;
269 RetainedLookupDirectory* m_LockProbe;
273class RetainedLookupDirectory final :
public Directory {
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) {}
286 ~RetainedLookupDirectory()
override {
287 if (m_Destructions) {
288 *m_Destructions += 1;
292 void publish(
const String& name,
File* file) {
296 bool removePublished(
const String& name,
File* expected) {
300 const void* namespaceLockAddress() {
304 bool publishEphemeral(
File* file) {
312 m_Conversions = &conversions;
313 m_FirstConversionEntered = &firstEntered;
314 m_FirstConversionRelease = &firstRelease;
315 m_SecondConversionEntered = &secondEntered;
316 m_SecondConversionRelease = &secondRelease;
318 DirectoryEntryMetadata metadata;
319 metadata.pDirectory =
this;
320 metadata.filename = name;
325 m_LazyTarget =
nullptr;
326 m_Conversions = &conversions;
327 m_FirstConversionEntered =
nullptr;
328 m_FirstConversionRelease =
nullptr;
329 m_SecondConversionEntered =
nullptr;
330 m_SecondConversionRelease =
nullptr;
332 DirectoryEntryMetadata metadata;
333 metadata.pDirectory =
this;
334 metadata.filename = name;
340 const size_t conversion = (*m_Conversions += 1);
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;
350 if (entered && release) {
368RetainedLookupFile::~RetainedLookupFile() {
369 if (m_LockProbe && m_LockAvailable && m_LockProbe->tryCacheLockForHostedTest()) {
370 *m_LockAvailable += 1;
375struct RetainedLookupHookContext {
376 RetainedLookupHookContext(
Directory* directory,
File* file,
bool pauseBefore,
bool pauseAfter)
377 : directory(directory),
379 pauseBefore(pauseBefore),
380 pauseAfter(pauseAfter),
381 beforeRelease(0, false),
382 afterRelease(0, false),
406struct RetainedLookupWorkerContext {
408 : directory(directory), name(name), child(nullptr), result(false), returned(0) {}
417struct DirectoryRemoveWorkerContext {
419 : directory(directory), name(name), returned(0) {}
426struct RetainedLookupReplacementWorkerContext {
427 RetainedLookupReplacementWorkerContext(
Directory* directory,
const String& oldName,
429 File* replacementFile)
430 : directory(directory),
432 replacementName(replacementName),
434 replacementFile(replacementFile),
436 missingPreserved(false),
444 File* replacementFile;
446 bool missingPreserved;
453void pauseRetainedLookup(
Directory* directory,
File* file, Directory::RetainedLookupPhase phase) {
454 RetainedLookupHookContext* context = g_RetainedLookupHookContext;
455 if (!context || context->directory != directory) {
459 const bool before = phase == Directory::RetainedLookupPhase::BeforeLookup;
460 if ((before && file) || (!before && context->file != file)) {
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)) {
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;
473 context->beforeNullFile += 1;
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();
487 context->returned += 1;
488 return context->result ? 0 : 1;
491int directoryRemoveWorker(
void* parameter) {
492 DirectoryRemoveWorkerContext* context =
493 reinterpret_cast<DirectoryRemoveWorkerContext*
>(parameter);
495 context->returned += 1;
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) {
508 context->missingPreserved =
509 !context->directory->lookupRetained(
HashedStringView(
"retained-missing"), child) &&
510 child.get() == context->oldFile;
512 if (context->missingPreserved) {
514 context->directory->lookupRetained(
HashedStringView(context->replacementName), child) &&
515 child.get() == context->replacementFile;
518 context->returned += 1;
519 return context->oldRetained && context->missingPreserved && context->replaced ? 0 : 1;
523 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
529 info.channelOwner == directory.cacheLockAddressForHostedTest() &&
530 thread->
getStatus() == Thread::Sleeping) {
540 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
545 if (entered ==
static_cast<size_t>(1) && thread->
getWaitDebugInfo(info) && info.queue &&
546 info.queued && info.channelOwner == &release && thread->
getStatus() == Thread::Sleeping) {
555 for (
size_t release = 1; release <= 8 && !destructions; ++release) {
564bool directoryRetainedLookupRemoval(
Process* kernelProcess) {
565 RetainedLookupFilesystem filesystem;
566 RetainedLookupDirectory directory(
String(
"retained-removal-root"), &filesystem);
567 filesystem.setRoot(&directory);
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);
574 const bool secondEmergencyRetained =
577 RetainedLookupHookContext hook(&directory, child,
true,
true);
578 RetainedLookupWorkerContext lookupContext(&directory, alias);
579 DirectoryRemoveWorkerContext removeContext(&directory, alias);
581 new Thread(kernelProcess, retainedLookupWorker, &lookupContext,
nullptr,
false,
true,
true);
582 lookup->setName(
"hosted retained directory lookup");
583 Thread* remover =
nullptr;
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);
592 bool removeStarted =
false;
593 bool removeQueuedBefore =
false;
595 remover =
new Thread(kernelProcess, directoryRemoveWorker, &removeContext,
nullptr,
false,
true,
597 remover->setName(
"hosted retained directory remover");
598 removeStarted = remover->
start();
600 removeQueuedBefore = waitForDirectoryLock(remover, directory, removeContext.returned);
604 const bool removalStayedPendingBefore = removeQueuedBefore && !removeContext.returned;
605 hook.beforeRelease.release();
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;
615 hook.beforeRelease.release();
616 hook.afterRelease.release();
619 if (!lookupStarted) {
622 if (remover && !removeStarted) {
625 Directory::setRetainedLookupHookForHostedTest(
nullptr);
626 g_RetainedLookupHookContext =
nullptr;
628 const bool lookupSucceeded = lookupContext.result && lookupContext.child == child;
630 const size_t cleanupReleases = drainTrackedFileOwners(child, destructions);
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);
641bool directoryRetainedLazyLookup(
Process* kernelProcess) {
642 RetainedLookupFilesystem filesystem;
643 RetainedLookupDirectory directory(
String(
"retained-lazy-root"), &filesystem);
644 filesystem.setRoot(&directory);
648 Semaphore firstConversionRelease(0,
false);
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);
657 RetainedLookupWorkerContext firstContext(&directory, alias);
658 RetainedLookupWorkerContext secondContext(&directory, alias);
660 new Thread(kernelProcess, retainedLookupWorker, &firstContext,
nullptr,
false,
true,
true);
661 first->setName(
"hosted first lazy retained lookup");
663 const bool firstStarted = first->
start();
665 bool firstPaused =
false;
666 for (
size_t attempt = 0; attempt < HostedAttempts && firstStarted; ++attempt) {
668 if (firstConversionEntered ==
static_cast<size_t>(1) && first->
getWaitDebugInfo(info) &&
669 info.queue && info.queued && info.channelOwner == &firstConversionRelease &&
670 first->
getStatus() == Thread::Sleeping) {
677 bool secondStarted =
false;
678 bool secondQueued =
false;
679 bool secondConverted =
false;
680 if (firstStarted && firstPaused) {
682 new Thread(kernelProcess, retainedLookupWorker, &secondContext,
nullptr,
false,
true,
true);
683 second->setName(
"hosted second lazy retained lookup");
684 secondStarted = second->
start();
686 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
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;
695 info.channelOwner == directory.cacheLockAddressForHostedTest() &&
696 second->
getStatus() == Thread::Sleeping) {
700 if (secondContext.returned) {
708 const bool singleConversionWhilePaused = secondQueued && !secondConverted &&
709 !secondContext.returned &&
710 conversions ==
static_cast<size_t>(1);
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;
726 firstConversionRelease.release();
727 firstConversionRelease.release();
728 secondConversionRelease.release();
734 if (second && !secondStarted) {
738 const bool lookupsSucceeded = firstContext.result && secondContext.result &&
739 firstContext.child == child && secondContext.child == child;
741 const size_t cleanupReleases = drainTrackedFileOwners(child, destructions);
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);
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);
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);
767 RetainedLookupHookContext hook(&firstDirectory, firstChild,
false,
true);
768 RetainedLookupWorkerContext firstContext(&firstDirectory, firstAlias);
769 RetainedLookupWorkerContext secondContext(&secondDirectory, secondAlias);
771 new Thread(kernelProcess, retainedLookupWorker, &firstContext,
nullptr,
false,
true,
true);
772 first->setName(
"hosted blocked retained lookup");
775 g_RetainedLookupHookContext = &hook;
776 Directory::setRetainedLookupHookForHostedTest(pauseRetainedLookup);
777 const bool firstStarted = first->
start();
779 const bool firstPaused =
780 firstStarted && waitForRetainedLookupPause(first, hook.afterEntered, hook.afterRelease,
781 firstContext.returned);
783 bool secondStarted =
false;
784 bool secondFinishedWhilePaused =
false;
785 if (firstStarted && firstPaused) {
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;
799 hook.beforeRelease.release();
800 hook.afterRelease.release();
806 if (second && !secondStarted) {
809 Directory::setRetainedLookupHookForHostedTest(
nullptr);
810 g_RetainedLookupHookContext =
nullptr;
812 const bool lookupsSucceeded = firstContext.result && secondContext.result &&
813 firstContext.child == firstChild &&
814 secondContext.child == secondChild;
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);
824bool directoryRetainedLookupDeletion() {
825 RetainedLookupFilesystem filesystem;
826 RetainedLookupDirectory directory(
String(
"retained-delete-root"), &filesystem);
827 filesystem.setRoot(&directory);
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);
836 return destructions ==
static_cast<size_t>(1) && lockAvailable ==
static_cast<size_t>(1);
839bool directoryRetainedLookupReplacement(
Process* kernelProcess) {
840 RetainedLookupFilesystem filesystem;
841 RetainedLookupDirectory directory(
String(
"retained-replacement-root"), &filesystem);
842 filesystem.setRoot(&directory);
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);
856 const bool secondReplacementEmergency =
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");
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;
874 hook.afterRelease.release();
875 const bool workerJoined = workerStarted &&
worker->joinForCompletion();
876 if (!workerStarted) {
879 Directory::setRetainedLookupHookForHostedTest(
nullptr);
880 g_RetainedLookupHookContext =
nullptr;
884 if (!oldDestructions) {
885 drainTrackedFileOwners(oldFile, oldDestructions);
887 const size_t replacementCleanupReleases =
888 drainTrackedFileOwners(replacement, replacementDestructions);
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);
899bool directoryRetainedFailedLazyLookup() {
900 RetainedLookupFilesystem filesystem;
901 RetainedLookupDirectory directory(
String(
"retained-failed-lazy-root"), &filesystem);
902 filesystem.setRoot(&directory);
904 const String alias(
"retained-failed-lazy-alias");
908 directory.publishFailedLazy(alias, conversions);
917 bool mutantExtrasDrained = seedWasFinal;
918 for (
size_t release = 0; release < 4 && !mutantExtrasDrained; ++release) {
922 return firstFailed && secondFailed && !first && !second &&
923 conversions ==
static_cast<size_t>(2) && seedWasFinal && mutantExtrasDrained;
926struct DirectoryMutationSerializationContext {
927 DirectoryMutationSerializationContext(RetainedLookupDirectory* directory,
const String& alias,
928 RetainedLookupFile* oldChild,
930 RetainedLookupFile* replacement)
931 : directory(directory),
934 oldDestructions(oldDestructions),
935 replacement(replacement),
942 oldStayedAliveAfterAliasRemoval(false),
943 publisherBlocked(false) {}
945 RetainedLookupDirectory* directory;
947 RetainedLookupFile* oldChild;
949 RetainedLookupFile* replacement;
956 bool oldStayedAliveAfterAliasRemoval;
957 bool publisherBlocked;
960int publishDuringDirectoryRemoval(
void* opaque) {
961 DirectoryMutationSerializationContext* context =
962 reinterpret_cast<DirectoryMutationSerializationContext*
>(opaque);
963 if (!context->publishStart.acquireForCompletion()) {
966 context->publishAttempted += 1;
967 context->directory->publish(context->alias, context->replacement);
968 context->publishReturned += 1;
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)) {
983 context->oldStayedAliveAfterAliasRemoval = context->oldDestructions ==
static_cast<size_t>(0);
984 context->publishStart.release();
985 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
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;
996 return context->oldStayedAliveAfterAliasRemoval && context->publisherBlocked;
999bool directoryMutationSerializesSameKeyReplacement(
Process* kernelProcess) {
1000 RetainedLookupFilesystem filesystem;
1001 RetainedLookupDirectory directory(
String(
"retained-mutation-root"), &filesystem);
1002 filesystem.setRoot(&directory);
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);
1012 DirectoryMutationSerializationContext context(&directory, alias, oldChild, oldDestructions,
1014 Thread* publisher =
new Thread(kernelProcess, publishDuringDirectoryRemoval, &context,
nullptr,
1016 publisher->setName(
"hosted directory mutation publisher");
1017 context.publisher = publisher;
1018 const bool publisherStarted = publisher->
start();
1020 filesystem.setRemoveHook(serializeDirectoryMutationRemove, &context);
1021 const bool removed = publisherStarted && filesystem.remove(&directory, oldChild);
1022 filesystem.setRemoveHook(
nullptr,
nullptr);
1024 context.publishStart.release();
1026 const bool publisherJoined = publisherStarted && publisher->
joinForCompletion();
1027 if (!publisherStarted) {
1032 const bool replacementVisible =
1034 retainedReplacement.get() == replacement;
1037 retainedReplacement.reset();
1038 if (!oldDestructions) {
1039 drainTrackedFileOwners(oldChild, oldDestructions);
1041 if (!replacementDestructions) {
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);
1053bool directoryRetainedDuplicateEphemeral() {
1054 RetainedLookupFilesystem filesystem;
1055 RetainedLookupDirectory directory(
String(
"retained-ephemeral-root"), &filesystem);
1056 filesystem.setRoot(&directory);
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);
1066 const bool duplicateAdded = directory.publishEphemeral(duplicate);
1068 const bool originalVisible =
1074 const size_t originalCleanupReleases = drainTrackedFileOwners(original, originalDestructions);
1075 if (!duplicateDestructions) {
1079 return !duplicateAdded && originalVisible && !duplicateWasTracked &&
1080 originalCleanupReleases == 0 && originalDestructions ==
static_cast<size_t>(1) &&
1081 duplicateDestructions ==
static_cast<size_t>(1);
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) {
1090 "HOSTED-SYSCALL-TEST: FAIL directory-retained-lookup-atomicity: "
1091 "lookup did not linearise child retention with removal and lazy evaluation");
1094 NOTICE(
"HOSTED-SYSCALL-TEST: PASS directory-retained-lookup-atomicity");
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) {
1106 "HOSTED-SYSCALL-TEST: FAIL directory-retained-lookup-lifecycle: "
1107 "directory locks were global or child destruction ran while locked");
1110 NOTICE(
"HOSTED-SYSCALL-TEST: PASS directory-retained-lookup-lifecycle");
1114struct TrackedFileRetainHookContext {
1115 explicit TrackedFileRetainHookContext(
File* target)
1116 : target(target), release(0, false), claimed(0), entered(0), returned(0) {}
1125struct TrackedFileRetainWorkerContext {
1126 explicit TrackedFileRetainWorkerContext(
File* file) : file(file), retained(0), returned(0) {}
1135void pauseFirstTrackedFileRetain(
File* file) {
1136 TrackedFileRetainHookContext* context = g_TrackedFileRetainHookContext;
1137 if (!context || context->target != file || !context->claimed.compareAndSwap(0, 1)) {
1141 context->entered += 1;
1142 if (context->release.acquireForCompletion()) {
1143 context->returned += 1;
1147int retainTrackedFileWorker(
void* parameter) {
1148 TrackedFileRetainWorkerContext* context =
1149 reinterpret_cast<TrackedFileRetainWorkerContext*
>(parameter);
1151 context->returned += 1;
1152 return context->retained ? 0 : 1;
1155bool establishedAliasRetainSerialization(
Process* kernelProcess) {
1157 EstablishedAliasFileProbe* file =
new EstablishedAliasFileProbe(destructions);
1160 TrackedFileRetainHookContext hook(file);
1161 TrackedFileRetainWorkerContext workerAContext(file);
1162 TrackedFileRetainWorkerContext workerBContext(file);
1163 Thread* workerA =
new Thread(kernelProcess, retainTrackedFileWorker, &workerAContext,
nullptr,
1165 Thread* workerB =
nullptr;
1166 workerA->setName(
"hosted VFS retain serializer A");
1168 g_TrackedFileRetainHookContext = &hook;
1169 VFS::setRetainTrackedFileHookForHostedTest(pauseFirstTrackedFileRetain);
1170 const bool startedA = workerA->
start();
1172 bool workerABlocked =
false;
1173 for (
size_t attempt = 0; attempt < HostedAttempts && startedA; ++attempt) {
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;
1184 bool startedB =
false;
1186 workerB =
new Thread(kernelProcess, retainTrackedFileWorker, &workerBContext,
nullptr,
false,
1188 workerB->setName(
"hosted VFS retain serializer B");
1189 startedB = workerB->
start();
1191 bool workerBQueued =
false;
1192 for (
size_t attempt = 0; attempt < HostedAttempts && startedB; ++attempt) {
1195 info.channelOwner ==
VFS::instance().trackedFilesLockAddressForHostedTest() &&
1196 workerB->
getStatus() == Thread::Sleeping) {
1197 workerBQueued =
true;
1200 if (workerBContext.returned) {
1206 bool passed = startedA && workerABlocked && startedB && workerBQueued && !workerBContext.returned;
1208 hook.release.release();
1214 if (workerB && !startedB) {
1217 VFS::setRetainTrackedFileHookForHostedTest(
nullptr);
1218 g_TrackedFileRetainHookContext =
nullptr;
1220 passed = passed && joinedA && joinedB && hook.returned ==
static_cast<size_t>(1) &&
1221 workerAContext.retained ==
static_cast<size_t>(1) &&
1222 workerBContext.retained ==
static_cast<size_t>(1);
1225 bool secondWasFinal =
false;
1226 if (!firstWasFinal) {
1230 bool finalDestroyed =
false;
1231 if (!firstWasFinal && !secondWasFinal) {
1237 passed = passed && !firstWasFinal && !secondWasFinal && finalDestroyed &&
1238 destructions ==
static_cast<size_t>(1);
1239 if (!destructions) {
1240 repairAliasFileProbe(file, destructions);
1245 "HOSTED-SYSCALL-TEST: FAIL vfs-established-alias-serialization: "
1246 "concurrent established-owner retains were not serialized by the tracker lock");
1250 NOTICE(
"HOSTED-SYSCALL-TEST: PASS vfs-established-alias-serialization");
1254enum DescriptorAliasConstruction {
1257 DescriptorPointerCopy,
1260bool descriptorEstablishedAliasLifetime(DescriptorAliasConstruction construction) {
1262 EstablishedAliasFileProbe* file =
new EstablishedAliasFileProbe(destructions);
1265 if (construction != DescriptorDirect) {
1273 if (construction == DescriptorDirect) {
1275 }
else if (construction == DescriptorCopy) {
1284 bool passed = !emergencyWasFinal && !destructions;
1287 passed = passed && destructions ==
static_cast<size_t>(1);
1288 repairAliasFileProbe(file, destructions);
1292bool establishedFileAliasLifetime() {
1293 const bool directPassed = descriptorEstablishedAliasLifetime(DescriptorDirect);
1294 const bool copyPassed = descriptorEstablishedAliasLifetime(DescriptorCopy);
1295 const bool pointerCopyPassed = descriptorEstablishedAliasLifetime(DescriptorPointerCopy);
1296 bool passed = directPassed && copyPassed && pointerCopyPassed;
1299 EstablishedAliasFileProbe* untracked =
new EstablishedAliasFileProbe(destructions);
1303 passed = passed && !descriptorPublishedFile && !destructions;
1304 repairAliasFileProbe(untracked, destructions);
1308 "HOSTED-SYSCALL-TEST: FAIL file-established-alias-lifetime: "
1309 "tracked descriptors did not retain exactly one VFS owner, or an untracked descriptor "
1310 "published a new owner");
1314 NOTICE(
"HOSTED-SYSCALL-TEST: PASS file-established-alias-lifetime");
1320 explicit ContextLifetimePath(
File* file) : m_File(file), m_Tracked(file->retainVfsReference()) {}
1321 ~ContextLifetimePath()
override {
1323 m_File->releaseVfsReference();
1325 File* node()
const override {
1328 const void* provider()
const override {
1333 static const char Provider;
1335 const bool m_Tracked;
1337const char ContextLifetimePath::Provider = 0;
1342 : m_Root(root), m_Cwd(cwd) {}
1346 result.root = m_Root;
1348 result.contextGeneration = m_Generation;
1352 if (!m_Registered || result)
1357 result = FilesystemContextOwner::adopt(pedigree_std::move(context));
1360 void retireProcessOwner()
override {
1361 assert(m_Registered);
1362 m_Registered =
false;
1374 uint64_t m_Generation = 1;
1375 bool m_Registered =
true;
1378class ContextLifetimeProcess final :
public Process {
1380 explicit ContextLifetimeProcess(
Process* parent)
1381 :
Process(DeferredPublication{}, parent, true, FilesystemContextMode::Deferred) {}
1382 void publishContext() {
1387bool processFilesystemContextLifetime(
Process* kernelProcess) {
1393 RetainedLookupFilesystem borrowedCwdFilesystem;
1394 RetainedLookupFilesystem borrowedRootFilesystem;
1395 EstablishedAliasFileProbe* cwd =
new EstablishedAliasFileProbe(cwdDestructions);
1396 EstablishedAliasFileProbe* root =
new EstablishedAliasFileProbe(rootDestructions);
1397 EstablishedAliasFileProbe* borrowedCwd =
new EstablishedAliasFileProbe(borrowedCwdDestructions);
1398 EstablishedAliasFileProbe* borrowedRoot =
new EstablishedAliasFileProbe(borrowedRootDestructions);
1399 borrowedCwd->setFilesystem(&borrowedCwdFilesystem);
1400 borrowedRoot->setFilesystem(&borrowedRootFilesystem);
1401 borrowedCwdFilesystem.setRoot(borrowedCwd);
1402 borrowedRootFilesystem.setRoot(borrowedRoot);
1406 auto* parent =
new ContextLifetimeProcess(kernelProcess);
1411 const bool installed =
1412 parent->installFilesystemContext(FilesystemContextOwner::adopt(pedigree_std::move(context)));
1414 FATAL(
"Hosted context fixture could not prepare its process owner");
1415 parent->publishContext();
1417 const bool snapshotted = installed && retainedContext->snapshot(snapshot);
1418 File* retainedCwd = snapshot.cwd ? snapshot.cwd->node() :
nullptr;
1419 File* retainedRoot = snapshot.root ? snapshot.root->node() :
nullptr;
1422 auto childContext = child->acquireFilesystemContext();
1423 const bool childInherited = childContext && childContext.
get() != retainedContext.
get() &&
1424 childContext->snapshot(inherited) && inherited.cwd == snapshot.cwd &&
1425 inherited.root == snapshot.root;
1427 childContext.reset();
1431 static_cast<ContextLifetimeProvider*
>(retainedContext.
get())
1436 childContext = child->acquireFilesystemContext();
1437 const bool childUnchanged = childContext && childContext->snapshot(inherited) &&
1438 inherited.cwd == snapshot.cwd && inherited.root == snapshot.root;
1440 childContext.reset();
1442 const bool parentReplaced = retainedContext->snapshot(replacement) && replacement.cwd &&
1443 replacement.root && replacement.cwd->node() == borrowedCwd &&
1444 replacement.root->node() == borrowedRoot &&
1445 replacement.contextGeneration > snapshot.contextGeneration;
1449 const bool snapshotsHeldAcrossReplacement =
1450 retainedCwd == cwd && retainedRoot == root && !cwdDestructions && !rootDestructions;
1452 const bool inheritedReferencesReleased =
1453 cwdDestructions ==
static_cast<size_t>(1) && rootDestructions ==
static_cast<size_t>(1);
1455 const bool retired = !retainedContext->snapshot(snapshot);
1456 retainedContext.
reset();
1457 const bool borrowedReferencesSurvived = !borrowedCwdDestructions && !borrowedRootDestructions;
1459 delete borrowedRoot;
1462 installed && snapshotted && childInherited && childUnchanged && parentReplaced && retired &&
1463 !cwdNamespaceWasFinal && !rootNamespaceWasFinal && !borrowedCwdWasPublished &&
1464 !borrowedRootWasPublished && snapshotsHeldAcrossReplacement && inheritedReferencesReleased &&
1465 borrowedReferencesSurvived && borrowedCwdDestructions ==
static_cast<size_t>(1) &&
1466 borrowedRootDestructions ==
static_cast<size_t>(1);
1469 "HOSTED-SYSCALL-TEST: FAIL process-filesystem-context-lifetime: "
1470 "fork, replacement, or destruction mismanaged a cwd/root VFS owner");
1474 NOTICE(
"HOSTED-SYSCALL-TEST: PASS process-filesystem-context-lifetime");
1478enum MappingAliasConstruction {
1484bool mappingEstablishedAliasLifetime(MappingAliasConstruction construction) {
1487 EstablishedAliasFileProbe* file =
new EstablishedAliasFileProbe(destructions);
1490 if (construction != MappingDirect) {
1491 source =
new MemoryMappedFile(0x100000, pageSize * 2, 0, file,
false, MemoryMappedObject::Read);
1498 if (construction == MappingDirect) {
1499 alias =
new MemoryMappedFile(0x100000, pageSize, 0, file,
false, MemoryMappedObject::Read);
1500 }
else if (construction == MappingClone) {
1501 alias = source->
clone();
1503 alias = source->
split(0x100000 + pageSize);
1509 bool passed = !emergencyWasFinal && !destructions;
1512 passed = passed && destructions ==
static_cast<size_t>(1);
1513 repairAliasFileProbe(file, destructions);
1517bool establishedMappingAliasLifetime() {
1518 const bool directPassed = mappingEstablishedAliasLifetime(MappingDirect);
1519 const bool clonePassed = mappingEstablishedAliasLifetime(MappingClone);
1520 const bool splitPassed = mappingEstablishedAliasLifetime(MappingSplit);
1521 bool passed = directPassed && clonePassed && splitPassed;
1525 EstablishedAliasFileProbe* untracked =
new EstablishedAliasFileProbe(destructions);
1527 new MemoryMappedFile(0x100000, pageSize, 0, untracked,
false, MemoryMappedObject::Read);
1530 passed = passed && !mappingPublishedFile && !destructions;
1531 repairAliasFileProbe(untracked, destructions);
1535 "HOSTED-SYSCALL-TEST: FAIL mmap-established-alias-lifetime: "
1536 "tracked mappings did not retain a VFS owner, or an untracked mapping published a new "
1541 NOTICE(
"HOSTED-SYSCALL-TEST: PASS mmap-established-alias-lifetime");
1545bool munmapUsesTargetPageGeometry(
Thread* thread) {
1549 const int misalignedResult = posix_munmap(
reinterpret_cast<void*
>(pageSize / 2), pageSize);
1550 const bool rejectedMisaligned =
1551 misalignedResult == -1 && thread->
getErrno() == Error::InvalidArgument;
1554 const int alignedResult = posix_munmap(
reinterpret_cast<void*
>(pageSize), pageSize);
1555 const bool acceptedAligned = alignedResult == 0 && !thread->
getErrno();
1558 if (!rejectedMisaligned || !acceptedAligned) {
1560 "HOSTED-SYSCALL-TEST: FAIL munmap-target-page-geometry: "
1561 "munmap did not validate addresses against the target page size");
1565 NOTICE(
"HOSTED-SYSCALL-TEST: PASS munmap-target-page-geometry");
1569bool mappingManagerSplitLifetime(
Process* process,
bool exactSuffix) {
1571 const size_t mappingLength = pageSize * 3;
1572 uintptr_t address = 0;
1573 if (!process->allocateUserRange(Process::UserRegion::Normal, mappingLength, address)) {
1578 EstablishedAliasFileProbe* file =
new EstablishedAliasFileProbe(destructions);
1582 uintptr_t mappedAddress = address;
1584 file, mappedAddress, mappingLength, MemoryMappedObject::Read);
1585 bool passed = mapping && mappedAddress == address;
1588 address + pageSize, exactSuffix ? pageSize * 2 : pageSize);
1590 size_t removedTail = 1;
1594 passed = passed && removedMiddleOrSuffix == 1 && removedPrefix == 1 && removedTail == 1;
1598 process->freeUserRange(Process::UserRegion::Normal, address, mappingLength);
1602 passed && !namespaceWasFinal && emergencyWasFinal && destructions ==
static_cast<size_t>(1);
1603 repairAliasFileProbe(file, destructions);
1607bool mappingManagerSplitLifetime(
Process* process) {
1608 const bool exactSuffixPassed = mappingManagerSplitLifetime(process,
true);
1609 const bool middlePassed = mappingManagerSplitLifetime(process,
false);
1610 const bool passed = exactSuffixPassed && middlePassed;
1613 "HOSTED-SYSCALL-TEST: FAIL mmap-split-alias-lifetime: "
1614 "an exact-suffix or middle removal leaked a file-backed mapping owner");
1618 NOTICE(
"HOSTED-SYSCALL-TEST: PASS mmap-split-alias-lifetime");
1622bool posixPathLookupLifetime(
Process* kernelProcess) {
1625 VFS::HostedRootViewScope fixture;
1627 if (!fixture.open(testFilesystem)) {
1628 delete testFilesystem;
1632 const bool rootInstalled = fixture.view()->bootRootPath(rootPath);
1633 const String name(
"hosted-established-alias-path-cache");
1634 const String path(
"/hosted-established-alias-path-cache");
1635 const bool created = rootInstalled && fixture.view()->createFile(rootPath, name, 0600);
1638 new PosixProcess(kernelProcess,
true, Process::FilesystemContextMode::Deferred);
1640 process->setSubsystem(subsystem);
1641 subsystem->
setAbi(PosixSubsystem::LinuxAbi);
1642 const bool contextInstalled = fixture.installContext(*process);
1644 File* original = created && contextInstalled
1645 ? subsystem->findFileRetained(path, originalLease, rootPath)
1647 const bool originalRemoved = original && fixture.view()->remove(rootPath, name, original);
1648 const bool recreated = originalRemoved && fixture.view()->createFile(rootPath, name, 0600);
1650 File* replacement =
nullptr;
1653 Directory::LookupStatus::Found)
1654 replacement = replacementLease.get();
1655 File* resolved = recreated ? subsystem->findFileRetained(path, resolvedLease, rootPath) :
nullptr;
1656 const bool resolvedReplacement =
1657 replacement && replacement != original && resolved == replacement;
1658 const bool pathCleaned = replacement && fixture.view()->remove(rootPath, name, replacement);
1659 replacementLease.reset();
1660 resolvedLease.reset();
1661 originalLease.reset();
1664 const bool rootRestored = fixture.close();
1666 FATAL(
"Hosted pathname fixture retained owners after teardown");
1667 const bool previousRestored =
1669 delete testFilesystem;
1671 const bool passed = rootInstalled && contextInstalled && created && original && originalRemoved &&
1672 recreated && resolvedReplacement && pathCleaned && rootRestored &&
1676 "HOSTED-SYSCALL-TEST: FAIL posix-path-lookup-lifetime: "
1677 "a removed pathname resolved to its retired cached File object");
1681 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-path-lookup-lifetime");
1691 m_Notifications(notifications),
1692 m_Destructions(destructions),
1695 ~PollGenerationProbe()
override {
1696 m_Destructions += 1;
1699 int connect(
const struct sockaddr_storage*, socklen_t)
override {
1711 int listen(
int)
override {
1715 int bind(
const struct sockaddr_storage*, socklen_t)
override {
1719 int accept(
struct sockaddr_storage*, socklen_t*,
int,
DescriptorLease*)
override {
1723 int getpeername(
struct sockaddr_storage*, socklen_t*)
override {
1727 int getsockname(
struct sockaddr_storage*, socklen_t*)
override {
1731 int setsockopt(
int,
int,
const void*, socklen_t)
override {
1735 int getsockopt(
int,
int,
void*, socklen_t*)
override {
1739 ReadyMask
queryReady(
bool reading,
bool writing)
override {
1741 ReadyMask ready = ReadyNone;
1742 if (reading && m_Ready) {
1746 ready |= ReadyWrite;
1751 void makeReadable() {
1753 m_Notifications += 1;
1764struct DescriptorCloseContext {
1766 : subsystem(subsystem),
1783int holdDescriptorAcrossBlock(
void* parameter) {
1784 DescriptorCloseContext* context =
reinterpret_cast<DescriptorCloseContext*
>(parameter);
1786 context->acquired = context->subsystem->acquireFileDescriptor(context->fd, descriptor) ? 1 : 0;
1787 context->entered += 1;
1789 if (!context->release.acquireForCompletion()) {
1790 context->returned += 1;
1794 if (descriptor && descriptor->
fd == context->fd) {
1795 context->usedAfterClose += 1;
1797 context->returned += 1;
1801bool descriptorClosePinning(
Process* kernelProcess) {
1802 constexpr size_t DescriptorNumber = 37;
1805 process->setSubsystem(subsystem);
1808 DescriptorRetirementProbe* probe =
new DescriptorRetirementProbe(destructions);
1809 probe->fd = DescriptorNumber;
1812 DescriptorCloseContext context(subsystem, DescriptorNumber);
1814 new Thread(kernelProcess, holdDescriptorAcrossBlock, &context,
nullptr,
false,
true,
true);
1815 worker->setName(
"hosted descriptor pin holder");
1817 bool passed =
worker->start();
1818 bool blocked =
false;
1819 for (
size_t attempt = 0; attempt < HostedAttempts && passed; ++attempt) {
1821 if (context.entered &&
worker->getWaitDebugInfo(info) && info.queue && info.queued &&
1822 info.channelOwner == &context.release &&
worker->getStatus() == Thread::Sleeping) {
1829 passed = passed && blocked && context.acquired == 1;
1832 const bool closed = closeAcquired && subsystem->
closeFileDescriptor(DescriptorNumber, closing);
1834 passed = passed && closed;
1840 context.release.release();
1841 passed =
worker->join() && passed;
1842 passed = passed && context.returned == 1 && context.usedAfterClose == 1 && destructions == 1;
1848 "HOSTED-SYSCALL-TEST: FAIL descriptor-close-pinning: "
1849 "close did not unpublish immediately while retaining the active "
1854 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-close-pinning");
1858bool descriptorCloseGeneration(
Process* kernelProcess) {
1859 constexpr size_t DescriptorNumber = 38;
1862 process->setSubsystem(subsystem);
1866 DescriptorRetirementProbe* oldDescriptor =
new DescriptorRetirementProbe(oldDestructions);
1867 oldDescriptor->fd = DescriptorNumber;
1868 oldDescriptor->setOffset(1);
1874 DescriptorRetirementProbe* replacement =
new DescriptorRetirementProbe(replacementDestructions);
1875 replacement->fd = DescriptorNumber;
1876 replacement->setOffset(2);
1882 oldDestructions == 0 && replacementDestructions == 0;
1889 passed = passed && oldDestructions == 1 && replacementDestructions == 0;
1891 const bool replacementClosed = subsystem->
closeFileDescriptor(DescriptorNumber, replacementLease);
1893 passed = passed && replacementClosed &&
1895 replacementDestructions == 0;
1896 replacementLease.reset();
1897 passed = passed && replacementDestructions == 1;
1903 "HOSTED-SYSCALL-TEST: FAIL descriptor-close-generation: "
1904 "an old close removed a reused descriptor generation");
1908 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-close-generation");
1912struct DescriptorPositionContext {
1914 : descriptor(descriptor), entered(0), acquired(0), observed(0) {}
1922class DescriptorPositionFile final :
public File {
1924 explicit DescriptorPositionFile(
bool seekable)
1925 :
File(
String(
"position-policy"), 0, 0, 0, 1, nullptr, 0, nullptr),
1926 m_Seekable(seekable),
1927 m_ReadOffset(~static_cast<uint64_t>(0)),
1928 m_WriteOffset(~static_cast<uint64_t>(0)),
1929 m_ReadCanBlock(true),
1930 m_WriteCanBlock(true) {}
1936 uint64_t readOffset()
const {
1937 return m_ReadOffset;
1940 uint64_t writeOffset()
const {
1941 return m_WriteOffset;
1944 bool readCanBlock()
const {
1945 return m_ReadCanBlock;
1948 bool writeCanBlock()
const {
1949 return m_WriteCanBlock;
1957 uint64_t
readBytewise(uint64_t location, uint64_t size, uintptr_t,
bool canBlock)
override {
1958 m_ReadOffset = location;
1959 m_ReadCanBlock = canBlock;
1963 uint64_t
writeBytewise(uint64_t location, uint64_t size, uintptr_t,
bool canBlock)
override {
1964 m_WriteOffset = location;
1965 m_WriteCanBlock = canBlock;
1971 uint64_t m_ReadOffset;
1972 uint64_t m_WriteOffset;
1973 bool m_ReadCanBlock;
1974 bool m_WriteCanBlock;
1977class DescriptorAppendFile final :
public File {
1979 DescriptorAppendFile()
1980 :
File(
String(
"append-policy"), 0, 0, 0, 1, nullptr, 10, nullptr),
1982 m_WriteOffsets{0, 0, 0} {}
1984 uint64_t writeOffset(
size_t index)
const {
1985 return m_WriteOffsets[index];
1988 size_t writeCount()
const {
1989 return m_WriteCount;
1997 uint64_t
writeBytewise(uint64_t location, uint64_t size, uintptr_t,
bool)
override {
1998 if (m_WriteCount < 3) {
1999 m_WriteOffsets[m_WriteCount] = location;
2002 if (location + size > getSize()) {
2003 setSize(location + size);
2009 size_t m_WriteCount;
2010 uint64_t m_WriteOffsets[3];
2013class ConcurrentDescriptorAppendFile final :
public File {
2015 ConcurrentDescriptorAppendFile()
2016 :
File(
String(
"concurrent-append-policy"), 0, 0, 0, 1, nullptr, 10, nullptr),
2017 m_FirstWriteEntered(0, false),
2018 m_ReleaseFirstWrite(0, false),
2020 m_WriteOffsets{0, 0} {}
2022 bool waitForFirstWrite() {
2023 return m_FirstWriteEntered.acquireForCompletion();
2026 void releaseFirstWrite() {
2027 m_ReleaseFirstWrite.release();
2030 size_t writeCount()
const {
2031 return m_WriteCount;
2034 uint64_t writeOffset(
size_t index)
const {
2035 return m_WriteOffsets[index];
2043 uint64_t
writeBytewise(uint64_t location, uint64_t size, uintptr_t,
bool)
override {
2044 const size_t slot = (m_WriteCount += 1) - 1;
2046 m_WriteOffsets[slot] = location;
2049 m_FirstWriteEntered.release();
2050 if (!m_ReleaseFirstWrite.acquireForCompletion()) {
2054 if (location + size > getSize()) {
2055 setSize(location + size);
2064 uint64_t m_WriteOffsets[2];
2067struct ConcurrentDescriptorAppendContext {
2068 explicit ConcurrentDescriptorAppendContext(
FileDescriptor* descriptor)
2069 : descriptor(descriptor), entered(0), returned(0), result(0) {}
2077int appendThroughDescriptor(
void* parameter) {
2078 ConcurrentDescriptorAppendContext* context =
2079 reinterpret_cast<ConcurrentDescriptorAppendContext*
>(parameter);
2081 context->entered += 1;
2082 context->result = context->descriptor->write(1,
reinterpret_cast<uintptr_t
>(&
byte));
2083 context->returned += 1;
2084 return context->result == 1 ? 0 : 1;
2087bool concurrentDescriptorAppendSerialization(
Process* kernelProcess,
bool positionedSecond) {
2088 ConcurrentDescriptorAppendFile file;
2091 positionedSecond ? O_WRONLY : O_WRONLY | O_APPEND);
2092 ConcurrentDescriptorAppendContext firstContext(&first);
2093 ConcurrentDescriptorAppendContext secondContext(&second);
2096 new Thread(kernelProcess, appendThroughDescriptor, &firstContext,
nullptr,
false,
true,
true);
2097 Thread* secondWorker =
new Thread(kernelProcess, appendThroughDescriptor, &secondContext,
nullptr,
2099 firstWorker->setName(
"hosted first independent append");
2100 if (positionedSecond) {
2101 secondWorker->setName(
"hosted positioned append race");
2103 secondWorker->setName(
"hosted second independent append");
2106 const bool firstStarted = firstWorker->
start();
2107 const bool firstEntered = firstStarted && file.waitForFirstWrite();
2108 const bool secondStarted = firstEntered && secondWorker->
start();
2109 bool secondBlocked =
false;
2110 for (
size_t attempt = 0; attempt < HostedAttempts && secondStarted; ++attempt) {
2112 uintptr_t debugAddress = 0;
2113 if (secondContext.entered && !secondContext.returned && file.writeCount() == 1 &&
2115 secondWorker->
getDebugState(debugAddress) == Thread::SemWait) {
2116 secondBlocked =
true;
2122 file.releaseFirstWrite();
2125 if (!firstStarted) {
2128 if (!secondStarted) {
2129 delete secondWorker;
2132 const bool separateDescriptions =
2134 const uint64_t expectedSecondOffset = positionedSecond ? 20 : 11;
2135 const uint64_t expectedSize = positionedSecond ? 21 : 12;
2136 const uint64_t expectedSecondPosition = positionedSecond ? 21 : 12;
2137 return firstStarted && firstEntered && secondStarted && secondBlocked && firstJoined &&
2138 secondJoined && firstContext.returned == 1 && secondContext.returned == 1 &&
2139 firstContext.result == 1 && secondContext.result == 1 && separateDescriptions &&
2140 file.writeCount() == 2 && file.writeOffset(0) == 10 &&
2141 file.writeOffset(1) == expectedSecondOffset && file.getSize() == expectedSize &&
2142 first.getOffset() == 11 && second.
getOffset() == expectedSecondPosition;
2145bool independentDescriptorAppendSerialization(
Process* kernelProcess) {
2146 return concurrentDescriptorAppendSerialization(kernelProcess,
false) &&
2147 concurrentDescriptorAppendSerialization(kernelProcess,
true);
2150bool descriptorOpenFileDescriptionState() {
2151 DescriptorPositionFile file(
true);
2158 source.addStatusFlag(O_NONBLOCK | O_CLOEXEC);
2159 bool passed = source.getFlags() == FD_CLOEXEC && alias.getFlags() == 0 &&
2160 source.getStatusFlags() == (O_RDWR | O_NONBLOCK) &&
2161 alias.getStatusFlags() == (O_RDWR | O_NONBLOCK) &&
2162 sourceDescription == aliasDescription && sourceDescription->getFile() == &file &&
2163 !sourceDescription->getNetworkImpl() &&
2164 sourceDescription->descriptorOwnerCount() == 2;
2168 passed = passed && sourceDescription->descriptorOwnerCount() == 3;
2170 passed = passed && sourceDescription->descriptorOwnerCount() == 2;
2172 alias.setStatusFlags(O_APPEND | O_CLOEXEC);
2173 passed = passed && source.getStatusFlags() == (O_RDWR | O_APPEND) &&
2174 alias.getStatusFlags() == (O_RDWR | O_APPEND) && source.getFlags() == FD_CLOEXEC &&
2175 alias.getFlags() == 0;
2177 source.removeStatusFlag(O_APPEND);
2178 passed = passed && source.getStatusFlags() == O_RDWR && alias.getStatusFlags() == O_RDWR;
2182 "HOSTED-SYSCALL-TEST: FAIL descriptor-open-file-description-state: "
2183 "status flags were descriptor-local, access mode was lost, or CLOEXEC entered shared "
2188 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-open-file-description-state");
2192bool descriptorOpenFileDescriptionLifetime() {
2194 EstablishedAliasFileProbe* file =
new EstablishedAliasFileProbe(fileDestructions);
2202 bool passed = !baselineWasFinal && !fileDestructions &&
2203 fileDescription->descriptorOwnerCount() == 0 && fileDescription->getFile() == file;
2204 fileDescription.
reset();
2205 passed = passed && fileDestructions == 1;
2206 repairAliasFileProbe(file, fileDestructions);
2213 new PollGenerationProbe(registrations, unpolls, networkDestructions));
2219 delete socketDescriptor;
2220 passed = passed && !networkDestructions && socketDescription->descriptorOwnerCount() == 0 &&
2221 socketDescription->getNetworkImpl();
2222 socketDescription.
reset();
2223 passed = passed && networkDestructions == 1;
2227 "HOSTED-SYSCALL-TEST: FAIL descriptor-open-file-description-lifetime: "
2228 "an OFD lease did not retain its target independently of descriptor aliases");
2232 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-open-file-description-lifetime");
2236bool descriptorAppendPolicy(
Process* kernelProcess) {
2238 DescriptorAppendFile file;
2242 const bool firstWrite = source.write(3,
reinterpret_cast<uintptr_t
>(&
byte)) == 3;
2243 source.setOffset(1);
2244 const bool secondWrite = alias.write(2,
reinterpret_cast<uintptr_t
>(&
byte)) == 2;
2246 alias.setStatusFlags(0);
2247 source.setOffset(5);
2248 const bool positionedWrite = source.write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
2250 const bool independentSerialization = independentDescriptorAppendSerialization(kernelProcess);
2251 const bool passed = firstWrite && secondWrite && positionedWrite && independentSerialization &&
2252 file.writeCount() == 3 && file.writeOffset(0) == 10 &&
2253 file.writeOffset(1) == 13 && file.writeOffset(2) == 5 &&
2254 file.getSize() == 15 && source.getOffset() == 6 && alias.getOffset() == 6 &&
2255 source.getStatusFlags() == O_WRONLY;
2258 "HOSTED-SYSCALL-TEST: FAIL descriptor-append-policy: "
2259 "append did not serialize EOF selection across open descriptions or update the shared "
2264 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-append-policy");
2268bool descriptorNonblockingPolicy() {
2270 DescriptorPositionFile sequential(
false);
2271 FileDescriptor source(&sequential, 0, 0, 0, O_RDWR | O_NONBLOCK);
2274 const bool nonblockingRead = source.read(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
2275 const bool nonblockingWrite = source.write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
2276 bool passed = nonblockingRead && nonblockingWrite && !sequential.readCanBlock() &&
2277 !sequential.writeCanBlock();
2279 alias.removeStatusFlag(O_NONBLOCK);
2280 const bool blockingRead = source.read(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
2281 const bool blockingWrite = source.write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
2282 passed = passed && blockingRead && blockingWrite && sequential.readCanBlock() &&
2283 sequential.writeCanBlock() && source.getStatusFlags() == O_RDWR &&
2284 alias.getStatusFlags() == O_RDWR;
2288 "HOSTED-SYSCALL-TEST: FAIL descriptor-nonblocking-policy: "
2289 "O_NONBLOCK did not reach file I/O or did not propagate across aliases");
2293 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-nonblocking-policy");
2297bool descriptorPositionPolicy() {
2299 DescriptorPositionFile seekable(
true);
2301 const bool positionedPassed = positioned.read(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1 &&
2302 positioned.write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1 &&
2303 seekable.readOffset() == 40 && seekable.writeOffset() == 41 &&
2304 positioned.getOffset() == 42;
2306 DescriptorPositionFile sequential(
false);
2308 const bool unpositionedPassed = unpositioned.read(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1 &&
2309 unpositioned.write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1 &&
2310 sequential.readOffset() == 0 && sequential.writeOffset() == 0 &&
2311 unpositioned.getOffset() == 40;
2313 if (!positionedPassed || !unpositionedPassed) {
2315 "HOSTED-SYSCALL-TEST: FAIL descriptor-position-policy: "
2316 "non-seekable I/O consumed a file position");
2320 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-position-policy");
2324int advanceDescriptorPosition(
void* parameter) {
2325 DescriptorPositionContext* context =
reinterpret_cast<DescriptorPositionContext*
>(parameter);
2326 context->entered += 1;
2328 context->observed = position.offset();
2329 position.advanceOffset(1);
2330 context->acquired += 1;
2334bool descriptorPositionAliasSerialization(
Process* kernelProcess) {
2338 DescriptorPositionContext context(&alias);
2341 new Thread(kernelProcess, advanceDescriptorPosition, &context,
nullptr,
false,
true,
true);
2342 worker->setName(
"hosted descriptor position alias");
2344 bool started =
false;
2345 bool queued =
false;
2348 started =
worker->start();
2349 for (
size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
2351 uintptr_t debugAddress = 0;
2352 if (context.entered && !context.acquired &&
worker->getWaitDebugInfo(info) && info.queued &&
2353 worker->getDebugState(debugAddress) == Thread::SemWait) {
2359 position.setOffset(41);
2362 const bool joined = started &&
worker->joinForCompletion();
2366 const bool passed = started && queued && joined && context.acquired == 1 &&
2367 context.observed == 41 && source.
getOffset() == 42 && alias.getOffset() == 42;
2370 "HOSTED-SYSCALL-TEST: FAIL descriptor-position-alias-serialization: "
2371 "duplicated descriptors did not serialize access to their shared offset");
2375 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-position-alias-serialization");
2379class VectorWriteFile final :
public File {
2382 :
File(
String(
"vector-write-policy"), 0, 0, 0, 1, nullptr, 0, nullptr),
2383 m_FirstWriteEntered(0, false),
2384 m_ReleaseFirstWrite(0, false),
2386 m_WriteOffsets{0, 0, 0},
2387 m_WriteValues{0, 0, 0} {}
2389 bool waitForFirstWrite() {
2390 return m_FirstWriteEntered.acquireForCompletion();
2393 void releaseFirstWrite() {
2394 m_ReleaseFirstWrite.release();
2397 size_t writeCount()
const {
2398 return m_WriteCount;
2401 uint64_t writeOffset(
size_t index)
const {
2402 return m_WriteOffsets[index];
2405 char writeValue(
size_t index)
const {
2406 return m_WriteValues[index];
2414 uint64_t
writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
bool)
override {
2415 const size_t slot = (m_WriteCount += 1) - 1;
2417 m_WriteOffsets[slot] = location;
2418 m_WriteValues[slot] = size ? *
reinterpret_cast<const char*
>(buffer) : 0;
2421 m_FirstWriteEntered.release();
2422 if (!m_ReleaseFirstWrite.acquireForCompletion()) {
2426 if (location + size > getSize()) {
2427 setSize(location + size);
2436 uint64_t m_WriteOffsets[3];
2437 char m_WriteValues[3];
2440struct VectorWriteContext {
2441 explicit VectorWriteContext(
size_t descriptor)
2442 : descriptor(descriptor), result(-2), error(0), returned(0) {}
2450int writeThroughVector(
void* parameter) {
2451 VectorWriteContext* context =
reinterpret_cast<VectorWriteContext*
>(parameter);
2453 char bytes[2] = {
'a',
'b'};
2454 struct iovec vectors[2] = {{&bytes[0], 1}, {&bytes[1], 1}};
2456 context->result = posix_writev(
static_cast<int>(context->descriptor), vectors, 2);
2457 context->error = thread->
getErrno();
2458 context->returned += 1;
2462int writeThroughAlias(
void* parameter) {
2463 VectorWriteContext* context =
reinterpret_cast<VectorWriteContext*
>(parameter);
2467 context->result = posix_write(
static_cast<int>(context->descriptor), &
byte, 1,
false);
2468 context->error = thread->
getErrno();
2469 context->returned += 1;
2473bool descriptorVectorIoSerialization(
Process* kernelProcess) {
2474 constexpr size_t SourceDescriptor = 74;
2475 constexpr size_t AliasDescriptor = 75;
2478 process->setSubsystem(subsystem);
2480 VectorWriteFile original;
2481 DescriptorPositionFile replacement(
true);
2484 alias->
fd = AliasDescriptor;
2488 VectorWriteContext vectorContext(SourceDescriptor);
2489 VectorWriteContext aliasContext(AliasDescriptor);
2491 new Thread(process, writeThroughVector, &vectorContext,
nullptr,
false,
true,
true);
2493 new Thread(process, writeThroughAlias, &aliasContext,
nullptr,
false,
true,
true);
2494 vectorWorker->setName(
"hosted vector write generation");
2495 aliasWorker->setName(
"hosted vector write alias");
2497 const bool vectorStarted = vectorWorker->
start();
2498 const bool firstEntered = vectorStarted && original.waitForFirstWrite();
2499 const bool aliasStarted = firstEntered && aliasWorker->
start();
2500 bool aliasBlocked =
false;
2501 for (
size_t attempt = 0; attempt < HostedAttempts && aliasStarted; ++attempt) {
2503 uintptr_t debugAddress = 0;
2504 if (!aliasContext.returned && original.writeCount() == 1 &&
2506 aliasWorker->
getDebugState(debugAddress) == Thread::SemWait) {
2507 aliasBlocked =
true;
2515 const bool sourceClosed =
2517 closingSource.reset();
2519 new FileDescriptor(&replacement, 0, SourceDescriptor, 0, O_WRONLY);
2522 original.releaseFirstWrite();
2525 if (!vectorStarted) {
2526 delete vectorWorker;
2528 if (!aliasStarted) {
2532 bool passed = vectorStarted && firstEntered && aliasStarted && aliasBlocked && sourceClosed &&
2533 vectorJoined && aliasJoined && vectorContext.returned == 1 &&
2534 vectorContext.result == 2 && vectorContext.error == 0 &&
2535 aliasContext.returned == 1 && aliasContext.result == 1 && aliasContext.error == 0 &&
2536 original.writeCount() == 2 && original.writeOffset(0) == 0 &&
2537 original.writeOffset(1) == 2 && original.writeValue(0) ==
'a' &&
2538 original.writeValue(1) ==
'c' && original.getSize() == 3 &&
2539 replacement.writeOffset() == ~static_cast<uint64_t>(0);
2543 const bool aliasClosed =
2545 closingAlias.reset();
2547 const bool replacementAcquired =
2549 const bool replacementClosed =
2550 replacementAcquired && subsystem->
closeFileDescriptor(SourceDescriptor, closingReplacement);
2551 closingReplacement.reset();
2552 passed = passed && aliasClosed && replacementClosed;
2557 "HOSTED-SYSCALL-TEST: FAIL descriptor-vector-io-serialization: "
2558 "writev switched descriptor generations or released the shared offset between vectors");
2562 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-vector-io-serialization");
2566struct DescriptorDupContractContext {
2567 DescriptorDupContractContext(
PosixSubsystem* subsystem,
size_t sourceFd,
size_t occupiedFd,
2571 : subsystem(subsystem),
2573 occupiedFd(occupiedFd),
2575 sourceDescription(sourceDescription),
2576 occupiedDescription(occupiedDescription),
2577 duplicateResult(-2),
2579 occupiedIntact(false),
2580 duplicateAliasesSource(false),
2582 duplicateClosed(false),
2583 ordinaryAllocation(static_cast<size_t>(-1)),
2584 sameInvalidResult(-2),
2585 sameInvalidError(0),
2594 int duplicateResult;
2596 bool occupiedIntact;
2597 bool duplicateAliasesSource;
2599 bool duplicateClosed;
2600 size_t ordinaryAllocation;
2601 int sameInvalidResult;
2602 int sameInvalidError;
2606int exerciseDescriptorDupContract(
void* parameter) {
2607 DescriptorDupContractContext* context =
2608 reinterpret_cast<DescriptorDupContractContext*
>(parameter);
2612 context->duplicateResult = posix_fcntl(
static_cast<int>(context->sourceFd), F_DUPFD,
2613 reinterpret_cast<void*
>(context->minimum));
2614 context->duplicateError = thread->
getErrno();
2617 if (context->subsystem->acquireFileDescriptor(context->occupiedFd, occupied)) {
2618 context->occupiedIntact =
2624 if (context->duplicateResult >= 0 &&
2625 context->subsystem->acquireFileDescriptor(context->duplicateResult, duplicate)) {
2626 context->duplicateAliasesSource =
2628 context->duplicateFlags = duplicate->
getFlags();
2629 context->duplicateClosed = context->subsystem->closeFileDescriptor(
2630 static_cast<size_t>(context->duplicateResult), duplicate);
2634 context->ordinaryAllocation = context->subsystem->getFd();
2636 constexpr int InvalidDescriptor = 80;
2638 context->sameInvalidResult = posix_dup2(InvalidDescriptor, InvalidDescriptor);
2639 context->sameInvalidError = thread->
getErrno();
2640 context->returned += 1;
2644bool descriptorDupContract(
Process* kernelProcess) {
2645 constexpr size_t SourceDescriptor = 70;
2646 constexpr size_t MinimumDescriptor = 72;
2647 constexpr size_t ExpectedDescriptor = 73;
2651 process->setSubsystem(subsystem);
2655 new FileDescriptor(sourceFile, 0, SourceDescriptor, FD_CLOEXEC, O_RDONLY);
2660 DescriptorDupContractContext context(subsystem, SourceDescriptor, MinimumDescriptor,
2664 new Thread(process, exerciseDescriptorDupContract, &context,
nullptr,
false,
true,
true);
2665 worker->setName(
"hosted descriptor duplication contract");
2666 const bool started =
worker->start();
2667 const bool joined = started &&
worker->joinForCompletion();
2673 started && joined && context.returned == 1 &&
2674 context.duplicateResult ==
static_cast<int>(ExpectedDescriptor) &&
2675 context.duplicateError == 0 && context.occupiedIntact && context.duplicateAliasesSource &&
2676 context.duplicateFlags == 0 && context.duplicateClosed && context.ordinaryAllocation == 0 &&
2677 context.sameInvalidResult == -1 && context.sameInvalidError == Error::BadFileDescriptor;
2680 context.sourceDescription.reset();
2681 context.occupiedDescription.reset();
2683 delete occupiedFile;
2687 "HOSTED-SYSCALL-TEST: FAIL descriptor-dup-contract: "
2688 "F_DUPFD replaced an occupied descriptor, hid a lower hole, or dup2 accepted an invalid "
2693 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-dup-contract");
2697struct PollCloseReuseContext {
2698 explicit PollCloseReuseContext(
size_t fd)
2699 : descriptor{static_cast<int>(fd), POLLIN, 0}, result(-2), entered(0), returned(0) {}
2701 struct pollfd descriptor;
2707bool selectProjectionContract() {
2708 constexpr unsigned int ReadResult = 1U;
2709 constexpr unsigned int WriteResult = 1U << 1;
2710 constexpr unsigned int ExceptionalResult = 1U << 2;
2711 constexpr unsigned int OneReady = 1U << 8;
2712 constexpr unsigned int TwoReady = 2U << 8;
2713 constexpr unsigned int ThreeReady = 3U << 8;
2715 const unsigned int hangup = posixSelectProjectionForTest(POLLHUP,
true,
true,
true);
2716 const unsigned int error = posixSelectProjectionForTest(POLLERR,
true,
true,
true);
2717 const unsigned int priority = posixSelectProjectionForTest(POLLPRI,
true,
true,
true);
2718 const unsigned int all =
2719 posixSelectProjectionForTest(POLLIN | POLLOUT | POLLPRI,
true,
true,
true);
2720 const unsigned int writeOnlyError = posixSelectProjectionForTest(POLLERR,
false,
true,
false);
2722 const bool projectionsPassed =
2723 hangup == (OneReady | ReadResult) && error == (TwoReady | ReadResult | WriteResult) &&
2724 priority == (OneReady | ExceptionalResult) &&
2725 all == (ThreeReady | ReadResult | WriteResult | ExceptionalResult) &&
2726 writeOnlyError == (OneReady | WriteResult);
2728 if (!projectionsPassed) {
2730 "HOSTED-SYSCALL-TEST: FAIL select-projection: "
2731 "readiness projection or return-bit counting was incorrect");
2735 NOTICE(
"HOSTED-SYSCALL-TEST: PASS select-projection");
2739struct PipePollReadinessContext {
2740 PipePollReadinessContext(
size_t readFd,
size_t writeFd)
2743 pollEntryGate(0, false),
2744 firstPhaseGate(0, false),
2747 firstPhaseReturned(0),
2749 emptyReadResult(-2),
2751 firstPollResult(-2),
2753 firstReadResult(-2),
2754 fillWriteResult(-2),
2755 fullWriteResult(-2),
2757 atomicSetupDrainResult(-2),
2758 atomicVectorResult(-2),
2759 atomicVectorError(0),
2761 overBoundaryWriteResult(-2),
2762 overBoundaryDrainResult(-2),
2765 eofReadResult(-2) {}
2775 int emptyReadResult;
2777 int firstPollResult;
2778 short firstPollEvents;
2779 int firstReadResult;
2780 int fillWriteResult;
2781 int fullWriteResult;
2783 int atomicSetupDrainResult;
2784 int atomicVectorResult;
2785 int atomicVectorError;
2787 int overBoundaryWriteResult;
2788 int overBoundaryDrainResult;
2790 short eofPollEvents;
2794struct EpollReadinessContext {
2796 size_t readFd,
size_t writeFd,
size_t aliasFd,
size_t regularFd)
2797 : subsystem(subsystem),
2803 regularFd(regularFd),
2804 waitEntryGate(0, false),
2808 regularAddResult(-2),
2811 createdDescriptorAcquired(false),
2812 createdStatusFlags(-1),
2813 createdDescriptorFlags(-1),
2814 createdCloseResult(false),
2815 duplicateAddResult(-2),
2816 duplicateAddError(0),
2817 exclusiveModifyResult(-2),
2818 exclusiveModifyError(0),
2819 firstWaitResult(-2),
2822 repeatedWaitResult(-2),
2823 repeatedWaitEvents(0),
2824 repeatedWaitData(0),
2825 sameReadyWriteResult(-2),
2826 sameReadyWaitResult(-2),
2827 firstDrainResult(-2),
2828 drainedWaitResult(-2),
2829 transitionWriteResult(-2),
2830 transitionWaitResult(-2),
2831 transitionWaitEvents(0),
2832 transitionWaitData(0),
2833 transitionDrainResult(-2),
2834 levelModifyResult(-2),
2835 levelWriteResult(-2),
2836 levelWaitResult(-2),
2839 levelRepeatedWaitResult(-2),
2840 levelDrainResult(-2),
2841 oneShotModifyResult(-2),
2842 oneShotWriteResult(-2),
2843 oneShotWaitResult(-2),
2844 oneShotWaitEvents(0),
2846 oneShotSuppressedResult(-2),
2848 rearmedWaitResult(-2),
2849 rearmedWaitEvents(0),
2851 oneShotDrainResult(-2),
2853 postDeleteWriteResult(-2),
2854 postDeleteWaitResult(-2),
2855 postDeleteDrainResult(-2),
2857 eventFdAddResult(-2),
2858 eventFdFirstWriteResult(-2),
2859 eventFdFirstWaitResult(-2),
2860 eventFdFirstWaitEvents(0),
2861 eventFdFirstWaitData(0),
2862 eventFdSecondWriteResult(-2),
2863 eventFdSecondWaitResult(-2),
2864 eventFdSecondWaitEvents(0),
2865 eventFdSecondWaitData(0),
2866 eventFdReadResult(-2),
2867 eventFdReadValue(0),
2868 eventFdDeleteResult(-2),
2869 eventFdCloseResult(false),
2871 originalCloseResult(false),
2872 ownersAfterOriginalClose(static_cast<size_t>(-1)),
2873 aliasWriteResult(-2),
2874 aliasWaitResult(-2),
2877 aliasDrainResult(-2),
2878 aliasCloseResult(false),
2879 ownersAfterAliasClose(static_cast<size_t>(-1)),
2880 prunedWaitResult(-2),
2881 descriptionRefsAfterPrune(static_cast<size_t>(-1)) {}
2894 int regularAddResult;
2895 int regularAddError;
2897 bool createdDescriptorAcquired;
2898 int createdStatusFlags;
2899 int createdDescriptorFlags;
2900 bool createdCloseResult;
2901 int duplicateAddResult;
2902 int duplicateAddError;
2903 int exclusiveModifyResult;
2904 int exclusiveModifyError;
2905 int firstWaitResult;
2906 uint32_t firstWaitEvents;
2907 uint64_t firstWaitData;
2908 int repeatedWaitResult;
2909 uint32_t repeatedWaitEvents;
2910 uint64_t repeatedWaitData;
2911 int sameReadyWriteResult;
2912 int sameReadyWaitResult;
2913 int firstDrainResult;
2914 int drainedWaitResult;
2915 int transitionWriteResult;
2916 int transitionWaitResult;
2917 uint32_t transitionWaitEvents;
2918 uint64_t transitionWaitData;
2919 int transitionDrainResult;
2920 int levelModifyResult;
2921 int levelWriteResult;
2922 int levelWaitResult;
2923 uint32_t levelWaitEvents;
2924 uint64_t levelWaitData;
2925 int levelRepeatedWaitResult;
2926 int levelDrainResult;
2927 int oneShotModifyResult;
2928 int oneShotWriteResult;
2929 int oneShotWaitResult;
2930 uint32_t oneShotWaitEvents;
2931 uint64_t oneShotWaitData;
2932 int oneShotSuppressedResult;
2934 int rearmedWaitResult;
2935 uint32_t rearmedWaitEvents;
2936 uint64_t rearmedWaitData;
2937 int oneShotDrainResult;
2939 int postDeleteWriteResult;
2940 int postDeleteWaitResult;
2941 int postDeleteDrainResult;
2943 int eventFdAddResult;
2944 int eventFdFirstWriteResult;
2945 int eventFdFirstWaitResult;
2946 uint32_t eventFdFirstWaitEvents;
2947 uint64_t eventFdFirstWaitData;
2948 int eventFdSecondWriteResult;
2949 int eventFdSecondWaitResult;
2950 uint32_t eventFdSecondWaitEvents;
2951 uint64_t eventFdSecondWaitData;
2952 int eventFdReadResult;
2953 uint64_t eventFdReadValue;
2954 int eventFdDeleteResult;
2955 bool eventFdCloseResult;
2957 bool originalCloseResult;
2958 size_t ownersAfterOriginalClose;
2959 int aliasWriteResult;
2960 int aliasWaitResult;
2961 uint32_t aliasWaitEvents;
2962 uint64_t aliasWaitData;
2963 int aliasDrainResult;
2964 bool aliasCloseResult;
2965 size_t ownersAfterAliasClose;
2966 int prunedWaitResult;
2967 size_t descriptionRefsAfterPrune;
2970class ReorderedReadinessFile final :
public File {
2972 ReorderedReadinessFile()
2973 :
File(), m_ReadinessLock(), m_Readable(true), m_Writable(true), m_Generations() {
2974 m_Generations.
read = 1;
2975 m_Generations.write = 1;
2978 ReadyMask
queryReady(
bool reading,
bool writing)
override {
2980 ReadyMask ready = ReadyNone;
2981 if (reading && m_Readable) {
2984 if (writing && m_Writable) {
2985 ready |= ReadyWrite;
2992 return m_Generations;
2999 void setReady(
bool readable,
bool writable) {
3001 if (!m_Readable && readable) {
3002 ++m_Generations.read;
3004 if (!m_Writable && writable) {
3005 ++m_Generations.write;
3007 m_Readable = readable;
3008 m_Writable = writable;
3011 void publishReadiness() {
3016 Mutex m_ReadinessLock;
3022struct ReorderedEpollContext {
3024 ReorderedReadinessFile* file)
3025 : instance(instance),
3028 initialComplete(0, false),
3029 drainMutated(0, false),
3030 publishDrain(0, false),
3031 collectFinal(0, false),
3032 initialWaitResult(-2),
3033 initialWaitEvents(0),
3035 finalWaitResult(-2),
3045 ReorderedReadinessFile* file;
3050 int initialWaitResult;
3051 uint32_t initialWaitEvents;
3052 uint64_t initialWaitData;
3053 int finalWaitResult;
3054 uint32_t finalWaitEvents;
3055 uint64_t finalWaitData;
3062int waitAcrossReorderedReadiness(
void* parameter) {
3063 ReorderedEpollContext* context =
reinterpret_cast<ReorderedEpollContext*
>(parameter);
3064 LinuxEpollEvent event = {LinuxEpoll::In | LinuxEpoll::Out | LinuxEpoll::EdgeTriggered,
3065 EpollReorderedData};
3066 context->addResult =
3067 context->instance->control(LinuxEpoll::ControlAdd,
static_cast<int>(context->fd), &event);
3070 context->initialWaitResult = context->instance->wait(&result, 1, 0);
3071 context->initialWaitEvents = result.events;
3072 context->initialWaitData = result.data;
3073 context->initialComplete.release();
3075 if (context->collectFinal.acquireForCompletion()) {
3077 context->finalWaitResult = context->instance->wait(&result, 1, 0);
3078 context->finalWaitEvents = result.events;
3079 context->finalWaitData = result.data;
3080 context->deleteResult = context->instance->control(LinuxEpoll::ControlDelete,
3081 static_cast<int>(context->fd),
nullptr);
3084 context->waiterReturned += 1;
3088int publishDelayedDrain(
void* parameter) {
3089 ReorderedEpollContext* context =
reinterpret_cast<ReorderedEpollContext*
>(parameter);
3090 context->file->setReady(
false,
false);
3091 context->drainMutated.release();
3092 if (context->publishDrain.acquireForCompletion()) {
3093 context->file->publishReadiness();
3095 context->drainReturned += 1;
3099class ReorderedFifo final :
public Pipe {
3101 ReorderedFifo() :
Pipe(
String(
"hosted-reordered-fifo"), 0, 0, 0, 0, nullptr, 0, nullptr, false) {}
3103 void reopenReaderWithoutPublishing() {
3105 m_Buffer.enableReads();
3107 m_ReaderCondition.broadcast();
3110 void publishReopen() {
3115struct ReorderedFifoEpollContext {
3117 : instance(instance),
3119 initialComplete(0, false),
3120 collectFinal(0, false),
3122 initialWaitResult(-2),
3123 initialWaitEvents(0),
3125 finalWaitResult(-2),
3136 int initialWaitResult;
3137 uint32_t initialWaitEvents;
3138 uint64_t initialWaitData;
3139 int finalWaitResult;
3140 uint32_t finalWaitEvents;
3141 uint64_t finalWaitData;
3146int waitAcrossReorderedFifoReopen(
void* parameter) {
3147 ReorderedFifoEpollContext* context =
reinterpret_cast<ReorderedFifoEpollContext*
>(parameter);
3148 LinuxEpollEvent interest = {LinuxEpoll::Out | LinuxEpoll::EdgeTriggered, EpollFifoData};
3149 context->addResult =
3150 context->instance->control(LinuxEpoll::ControlAdd,
static_cast<int>(context->fd), &interest);
3153 context->initialWaitResult = context->instance->wait(&event, 1, 0);
3154 context->initialWaitEvents =
event.events;
3155 context->initialWaitData =
event.data;
3156 context->initialComplete.release();
3158 if (context->collectFinal.acquireForCompletion()) {
3160 context->finalWaitResult = context->instance->wait(&event, 1, 0);
3161 context->finalWaitEvents =
event.events;
3162 context->finalWaitData =
event.data;
3163 context->deleteResult = context->instance->control(LinuxEpoll::ControlDelete,
3164 static_cast<int>(context->fd),
nullptr);
3167 context->returned += 1;
3171int pollPipeReadiness(
void* parameter) {
3172 PipePollReadinessContext* context =
reinterpret_cast<PipePollReadinessContext*
>(parameter);
3177 context->emptyReadResult = posix_read(context->readFd, &
byte, 1);
3178 context->emptyReadError = thread->
getErrno();
3180 struct pollfd readable = {
static_cast<int>(context->readFd), POLLIN, 0};
3181 context->pollEntered += 1;
3182 context->pollEntryGate.release();
3183 context->firstPollResult = posix_poll_safe(&readable, 1, PollCloseReuseTimeoutMilliseconds);
3184 context->firstPollEvents = readable.revents;
3185 context->firstReadResult = posix_read(context->readFd, &
byte, 1);
3187 char fill[PIPE_BUF_MAX] = {};
3188 context->fillWriteResult = posix_write(context->writeFd, fill,
sizeof(fill),
false);
3190 context->fullWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3191 context->fullWriteError = thread->
getErrno();
3192 context->atomicSetupDrainResult = posix_read(context->readFd, &
byte, 1);
3193 char vectorBytes[2] = {1, 2};
3194 struct iovec vectors[2] = {{&vectorBytes[0], 1}, {&vectorBytes[1], 1}};
3196 context->atomicVectorResult = posix_writev(context->writeFd, vectors, 2);
3197 context->atomicVectorError = thread->
getErrno();
3198 context->drainResult = posix_read(context->readFd, fill,
sizeof(fill));
3199 char overBoundary[PIPE_BUF_MAX + 1] = {};
3200 context->overBoundaryWriteResult =
3201 posix_write(context->writeFd, overBoundary,
sizeof(overBoundary),
false);
3202 context->overBoundaryDrainResult = posix_read(context->readFd, fill,
sizeof(fill));
3203 context->firstPhaseReturned += 1;
3204 context->firstPhaseGate.release();
3206 if (!context->eofGate.acquireForCompletion()) {
3207 context->returned += 1;
3211 struct pollfd eof = {
static_cast<int>(context->readFd), POLLIN, 0};
3212 context->eofPollResult = posix_poll_safe(&eof, 1, PollCloseReuseTimeoutMilliseconds);
3213 context->eofPollEvents = eof.revents;
3214 context->eofReadResult = posix_read(context->readFd, &
byte, 1);
3215 context->returned += 1;
3219bool pipePollReadiness(
Process* kernelProcess) {
3220 constexpr size_t ReadDescriptor = 45;
3221 constexpr size_t WriteDescriptor = 46;
3224 process->setSubsystem(subsystem);
3226 Pipe* pipe =
new Pipe(
String(
""), 0, 0, 0, 0,
nullptr, 0,
nullptr,
true);
3232 PipePollReadinessContext context(ReadDescriptor, WriteDescriptor);
3233 Thread*
worker =
new Thread(process, pollPipeReadiness, &context,
nullptr,
false,
true,
true);
3234 worker->setName(
"hosted pipe poll readiness worker");
3235 const bool started =
worker->start();
3237 const bool pollEntered = started && context.pollEntryGate.acquireForCompletion();
3238 bool pollBlocked =
false;
3239 for (
size_t attempt = 0; attempt < HostedAttempts && pollEntered; ++attempt) {
3241 if (
worker->getWaitDebugInfo(info) && info.queue && info.queued &&
3242 worker->getStatus() == Thread::Sleeping) {
3250 const bool initialWrite =
3251 pollBlocked && writer->
write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
3252 const bool firstPhaseCompleted = started && context.firstPhaseGate.acquireForCompletion();
3256 const bool writerClosed =
3258 closingWriter.reset();
3259 context.eofGate.release();
3261 const bool joined = started &&
worker->joinForCompletion();
3266 const bool nonblockingPassed =
3267 context.emptyReadResult == -1 && context.emptyReadError == Error::NoMoreProcesses &&
3268 context.fillWriteResult == PIPE_BUF_MAX && context.fullWriteResult == -1 &&
3269 context.fullWriteError == Error::NoMoreProcesses && context.atomicSetupDrainResult == 1 &&
3270 context.atomicVectorResult == -1 && context.atomicVectorError == Error::NoMoreProcesses &&
3271 context.drainResult == PIPE_BUF_MAX - 1 && context.overBoundaryWriteResult == PIPE_BUF_MAX &&
3272 context.overBoundaryDrainResult == PIPE_BUF_MAX && context.eofReadResult == 0;
3273 const bool readinessPassed =
3274 initialWrite && context.firstPollResult == 1 && (context.firstPollEvents & POLLIN) &&
3275 !(context.firstPollEvents & POLLHUP) && context.firstReadResult == 1 &&
3276 context.eofPollResult == 1 && (context.eofPollEvents & POLLHUP);
3277 bool passed = started && pollBlocked && firstPhaseCompleted && joined && writerClosed &&
3278 context.firstPhaseReturned == 1 && context.returned == 1 && nonblockingPassed &&
3284 const bool readerClosed =
3286 closingReader.reset();
3287 passed = passed && readerClosed && readerDescription->descriptorOwnerCount() == 0 &&
3288 readerDescription->getFile() == pipe && pipe->
getReaderCount() == 0;
3289 readerDescription.
reset();
3294 "HOSTED-SYSCALL-TEST: FAIL pipe-poll-readiness: "
3295 "POLLIN wakeup, EOF hangup, or atomic nonblocking pipe I/O was incorrect");
3299 NOTICE(
"HOSTED-SYSCALL-TEST: PASS pipe-poll-readiness");
3303int exerciseEpollReadiness(
void* parameter) {
3304 EpollReadinessContext* context =
reinterpret_cast<EpollReadinessContext*
>(parameter);
3309 LinuxEpollEvent interest = {LinuxEpoll::In | LinuxEpoll::ReadNormal | LinuxEpoll::EdgeTriggered,
3311 context->addResult =
3312 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &interest);
3315 context->regularAddResult =
3316 context->instance->control(LinuxEpoll::ControlAdd, context->regularFd, &interest);
3317 context->regularAddError = thread->
getErrno();
3319 context->createdFd = posix_epoll_create1(LinuxEpoll::CloseOnExec);
3321 if (context->createdFd >= 0) {
3322 context->createdDescriptorAcquired = context->subsystem->acquireFileDescriptor(
3323 static_cast<size_t>(context->createdFd), createdDescriptor);
3324 if (context->createdDescriptorAcquired) {
3325 context->createdStatusFlags = createdDescriptor->
getStatusFlags();
3326 context->createdDescriptorFlags = createdDescriptor->
getFlags();
3327 context->createdCloseResult = context->subsystem->closeFileDescriptor(
3328 static_cast<size_t>(context->createdFd), createdDescriptor);
3331 createdDescriptor.reset();
3334 context->duplicateAddResult =
3335 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &interest);
3336 context->duplicateAddError = thread->
getErrno();
3338 LinuxEpollEvent exclusive = {LinuxEpoll::In | LinuxEpoll::Exclusive, EpollInitialData};
3340 context->exclusiveModifyResult =
3341 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &exclusive);
3342 context->exclusiveModifyError = thread->
getErrno();
3344 context->waitEntered += 1;
3345 context->waitEntryGate.release();
3346 context->firstWaitResult = context->instance->wait(events, 2, 5000);
3347 context->firstWaitEvents = events[0].events;
3348 context->firstWaitData = events[0].data;
3351 context->repeatedWaitResult = context->instance->wait(events, 2, 0);
3352 context->repeatedWaitEvents = events[0].events;
3353 context->repeatedWaitData = events[0].data;
3354 context->sameReadyWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3355 context->sameReadyWaitResult = context->instance->wait(events, 2, 0);
3356 char edgeBytes[2] = {};
3357 context->firstDrainResult = posix_read(context->readFd, edgeBytes,
sizeof(edgeBytes));
3358 context->drainedWaitResult = context->instance->wait(events, 2, 0);
3360 context->transitionWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3362 context->transitionWaitResult = context->instance->wait(events, 2, 0);
3363 context->transitionWaitEvents = events[0].events;
3364 context->transitionWaitData = events[0].data;
3365 context->transitionDrainResult = posix_read(context->readFd, &
byte, 1);
3368 context->levelModifyResult =
3369 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &level);
3370 context->levelWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3372 context->levelWaitResult = context->instance->wait(events, 2, 0);
3373 context->levelWaitEvents = events[0].events;
3374 context->levelWaitData = events[0].data;
3375 context->levelRepeatedWaitResult = context->instance->wait(events, 2, 0);
3376 context->levelDrainResult = posix_read(context->readFd, &
byte, 1);
3378 LinuxEpollEvent oneShot = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered | LinuxEpoll::OneShot,
3380 context->oneShotModifyResult =
3381 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &oneShot);
3382 context->oneShotWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3384 context->oneShotWaitResult = context->instance->wait(events, 2, 0);
3385 context->oneShotWaitEvents = events[0].events;
3386 context->oneShotWaitData = events[0].data;
3387 context->oneShotSuppressedResult = context->instance->wait(events, 2, 0);
3389 LinuxEpollEvent rearmed = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered | LinuxEpoll::OneShot,
3391 context->rearmResult =
3392 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &rearmed);
3394 context->rearmedWaitResult = context->instance->wait(events, 2, 0);
3395 context->rearmedWaitEvents = events[0].events;
3396 context->rearmedWaitData = events[0].data;
3397 context->oneShotDrainResult = posix_read(context->readFd, &
byte, 1);
3399 context->deleteResult =
3400 context->instance->control(LinuxEpoll::ControlDelete, context->readFd,
nullptr);
3401 context->postDeleteWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3402 context->postDeleteWaitResult = context->instance->wait(events, 2, 0);
3403 context->postDeleteDrainResult = posix_read(context->readFd, &
byte, 1);
3405 context->eventFd = posix_eventfd2(0, LinuxEventFd::NonBlock);
3406 LinuxEpollEvent eventFdInterest = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered, EpollEventFdData};
3407 if (context->eventFd >= 0) {
3408 context->eventFdAddResult =
3409 context->instance->control(LinuxEpoll::ControlAdd, context->eventFd, &eventFdInterest);
3410 uint64_t eventValue = 1;
3411 context->eventFdFirstWriteResult = posix_write(
3412 context->eventFd,
reinterpret_cast<char*
>(&eventValue),
sizeof(eventValue),
false);
3414 context->eventFdFirstWaitResult = context->instance->wait(events, 2, 0);
3415 context->eventFdFirstWaitEvents = events[0].events;
3416 context->eventFdFirstWaitData = events[0].data;
3421 context->eventFdSecondWriteResult = posix_write(
3422 context->eventFd,
reinterpret_cast<char*
>(&eventValue),
sizeof(eventValue),
false);
3424 context->eventFdSecondWaitResult = context->instance->wait(events, 2, 0);
3425 context->eventFdSecondWaitEvents = events[0].events;
3426 context->eventFdSecondWaitData = events[0].data;
3427 context->eventFdReadResult =
3428 posix_read(context->eventFd,
reinterpret_cast<char*
>(&context->eventFdReadValue),
3429 sizeof(context->eventFdReadValue));
3430 context->eventFdDeleteResult =
3431 context->instance->control(LinuxEpoll::ControlDelete, context->eventFd,
nullptr);
3434 const bool eventFdAcquired = context->subsystem->acquireFileDescriptor(
3435 static_cast<size_t>(context->eventFd), closingEventFd);
3436 context->eventFdCloseResult =
3437 eventFdAcquired && context->subsystem->closeFileDescriptor(
3438 static_cast<size_t>(context->eventFd), closingEventFd);
3439 closingEventFd.reset();
3443 context->aliasAddResult =
3444 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &aliasLifetime);
3447 const bool originalAcquired =
3448 context->subsystem->acquireFileDescriptor(context->readFd, closingOriginal);
3449 context->originalCloseResult =
3450 originalAcquired && context->subsystem->closeFileDescriptor(context->readFd, closingOriginal);
3451 closingOriginal.reset();
3452 context->ownersAfterOriginalClose = context->readDescription->descriptorOwnerCount();
3454 context->aliasWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3456 context->aliasWaitResult = context->instance->wait(events, 2, 0);
3457 context->aliasWaitEvents = events[0].events;
3458 context->aliasWaitData = events[0].data;
3459 context->aliasDrainResult = posix_read(context->aliasFd, &
byte, 1);
3462 const bool aliasAcquired =
3463 context->subsystem->acquireFileDescriptor(context->aliasFd, closingAlias);
3464 context->aliasCloseResult =
3465 aliasAcquired && context->subsystem->closeFileDescriptor(context->aliasFd, closingAlias);
3466 closingAlias.reset();
3467 context->ownersAfterAliasClose = context->readDescription->descriptorOwnerCount();
3468 context->prunedWaitResult = context->instance->wait(events, 2, 0);
3469 context->descriptionRefsAfterPrune = context->readDescription.refcount();
3471 context->returned += 1;
3475bool epollLevelOneShotAndOfdLifetime(
Process* kernelProcess) {
3476 constexpr size_t ReadDescriptor = 47;
3477 constexpr size_t WriteDescriptor = 48;
3478 constexpr size_t AliasDescriptor = 49;
3479 constexpr size_t RegularDescriptor = 50;
3483 process->setSubsystem(subsystem);
3485 Pipe* pipe =
new Pipe(
String(
""), 0, 0, 0, 0,
nullptr, 0,
nullptr,
true);
3489 alias->
fd = AliasDescriptor;
3498 EpollReadinessContext context(subsystem, instance, ReadDescriptor, WriteDescriptor,
3499 AliasDescriptor, RegularDescriptor);
3500 context.readDescription =
reader->acquireOpenFileDescription();
3503 new Thread(process, exerciseEpollReadiness, &context,
nullptr,
false,
true,
true);
3504 worker->setName(
"hosted epoll readiness worker");
3505 const bool started =
worker->start();
3506 const bool waitEntered = started && context.waitEntryGate.acquireForCompletion();
3508 bool waitBlocked =
false;
3509 for (
size_t attempt = 0; attempt < HostedAttempts && waitEntered; ++attempt) {
3511 if (
worker->getWaitDebugInfo(info) && info.queue && info.queued &&
3512 worker->getStatus() == Thread::Sleeping) {
3520 const int wakeWriteResult = writer->
write(1,
reinterpret_cast<uintptr_t
>(&
byte));
3521 const bool joined = started &&
worker->joinForCompletion();
3526 const bool edgePassed =
3527 waitBlocked && wakeWriteResult == 1 && context.firstWaitResult == 1 &&
3528 context.firstWaitEvents == (LinuxEpoll::In | LinuxEpoll::ReadNormal) &&
3529 context.firstWaitData == EpollInitialData && context.repeatedWaitResult == 0 &&
3530 context.sameReadyWriteResult == 1 && context.sameReadyWaitResult == 0 &&
3531 context.firstDrainResult == 2 && context.drainedWaitResult == 0 &&
3532 context.transitionWriteResult == 1 && context.transitionWaitResult == 1 &&
3533 context.transitionWaitEvents == (LinuxEpoll::In | LinuxEpoll::ReadNormal) &&
3534 context.transitionWaitData == EpollInitialData && context.transitionDrainResult == 1;
3535 const bool levelPassed = context.levelModifyResult == 0 && context.levelWriteResult == 1 &&
3536 context.levelWaitResult == 1 &&
3537 context.levelWaitEvents == LinuxEpoll::In &&
3538 context.levelWaitData == EpollLevelData &&
3539 context.levelRepeatedWaitResult == 1 && context.levelDrainResult == 1;
3540 const bool controlPassed =
3541 context.addResult == 0 && context.regularAddResult == -1 &&
3542 context.regularAddError == Error::NotEnoughPermissions && context.createdFd >= 0 &&
3543 context.createdDescriptorAcquired && context.createdStatusFlags == O_RDWR &&
3544 context.createdDescriptorFlags == FD_CLOEXEC && context.createdCloseResult &&
3545 context.duplicateAddResult == -1 && context.duplicateAddError == Error::FileExists &&
3546 context.exclusiveModifyResult == -1 &&
3547 context.exclusiveModifyError == Error::OperationNotSupported &&
3548 context.oneShotModifyResult == 0 && context.oneShotWriteResult == 1 &&
3549 context.oneShotWaitResult == 1 && context.oneShotWaitEvents == LinuxEpoll::In &&
3550 context.oneShotWaitData == EpollOneShotData && context.oneShotSuppressedResult == 0 &&
3551 context.rearmResult == 0 && context.rearmedWaitResult == 1 &&
3552 context.rearmedWaitEvents == LinuxEpoll::In && context.rearmedWaitData == EpollRearmedData &&
3553 context.oneShotDrainResult == 1 && context.deleteResult == 0 &&
3554 context.postDeleteWriteResult == 1 && context.postDeleteWaitResult == 0 &&
3555 context.postDeleteDrainResult == 1;
3556 const bool lifetimePassed =
3557 context.aliasAddResult == 0 && context.originalCloseResult &&
3558 context.ownersAfterOriginalClose == 1 && context.aliasWriteResult == 1 &&
3559 context.aliasWaitResult == 1 && context.aliasWaitEvents == LinuxEpoll::In &&
3560 context.aliasWaitData == EpollAliasData && context.aliasDrainResult == 1 &&
3561 context.aliasCloseResult && context.ownersAfterAliasClose == 0 &&
3562 context.prunedWaitResult == 0 && context.descriptionRefsAfterPrune == 1;
3563 const bool eventFdPassed =
3564 context.eventFd >= 0 && context.eventFdAddResult == 0 &&
3565 context.eventFdFirstWriteResult ==
static_cast<int>(
sizeof(uint64_t)) &&
3566 context.eventFdFirstWaitResult == 1 && context.eventFdFirstWaitEvents == LinuxEpoll::In &&
3567 context.eventFdFirstWaitData == EpollEventFdData &&
3568 context.eventFdSecondWriteResult ==
static_cast<int>(
sizeof(uint64_t)) &&
3569 context.eventFdSecondWaitResult == 1 && context.eventFdSecondWaitEvents == LinuxEpoll::In &&
3570 context.eventFdSecondWaitData == EpollEventFdData &&
3571 context.eventFdReadResult ==
static_cast<int>(
sizeof(uint64_t)) &&
3572 context.eventFdReadValue == 2 && context.eventFdDeleteResult == 0 &&
3573 context.eventFdCloseResult;
3574 bool passed = started && waitEntered && joined && context.waitEntered == 1 &&
3575 context.returned == 1 && edgePassed && levelPassed && controlPassed &&
3576 lifetimePassed && eventFdPassed;
3580 const bool writerClosed =
3582 closingWriter.reset();
3583 passed = passed && writerClosed;
3586 context.instance.reset();
3587 context.readDescription.reset();
3593 "HOSTED-SYSCALL-TEST: FAIL epoll-level-oneshot-ofd-lifetime: "
3594 "target admission, descriptor flags, edge and level delivery, one-shot rearm, control "
3595 "errors, eventfd generations, or OFD retirement was incorrect");
3599 NOTICE(
"HOSTED-SYSCALL-TEST: PASS epoll-level-oneshot-ofd-lifetime");
3603bool epollReorderedTransitionPublication(
Process* kernelProcess) {
3604 constexpr size_t DescriptorNumber = 51;
3607 process->setSubsystem(subsystem);
3609 ReorderedReadinessFile* source =
new ReorderedReadinessFile;
3611 new FileDescriptor(source, 0, DescriptorNumber, 0, O_RDWR | O_NONBLOCK);
3615 ReorderedEpollContext context(instance, DescriptorNumber, source);
3617 new Thread(process, waitAcrossReorderedReadiness, &context,
nullptr,
false,
true,
true);
3618 waiter->setName(
"hosted reordered epoll waiter");
3619 const bool waiterStarted =
waiter->start();
3620 const bool initialComplete = waiterStarted && context.initialComplete.acquireForCompletion();
3623 bool drainStarted =
false;
3624 bool drainMutated =
false;
3625 if (initialComplete) {
3626 drain =
new Thread(process, publishDelayedDrain, &context,
nullptr,
false,
true,
true);
3627 drain->setName(
"hosted delayed epoll drain publisher");
3628 drainStarted = drain->
start();
3629 drainMutated = drainStarted && context.drainMutated.acquireForCompletion();
3636 source->setReady(
true,
true);
3637 source->publishReadiness();
3639 context.publishDrain.release();
3641 if (drain && !drainStarted) {
3645 context.collectFinal.release();
3646 const bool waiterJoined = waiterStarted &&
waiter->joinForCompletion();
3647 if (!waiterStarted) {
3651 const bool transitionPassed =
3652 context.addResult == 0 && context.initialWaitResult == 1 &&
3653 context.initialWaitEvents == (LinuxEpoll::In | LinuxEpoll::Out) &&
3654 context.initialWaitData == EpollReorderedData && context.finalWaitResult == 1 &&
3655 context.finalWaitEvents == (LinuxEpoll::In | LinuxEpoll::Out) &&
3656 context.finalWaitData == EpollReorderedData && context.deleteResult == 0 &&
3657 source->readinessGenerations().read == 2 && source->readinessGenerations().write == 2;
3658 bool passed = waiterStarted && initialComplete && drainStarted && drainMutated && drainJoined &&
3659 waiterJoined && context.waiterReturned == 1 && context.drainReturned == 1 &&
3664 const bool descriptorClosed =
3667 passed = passed && descriptorClosed;
3670 context.instance.reset();
3676 "HOSTED-SYSCALL-TEST: FAIL epoll-reordered-transition-publication: "
3677 "a refill edge was lost when its callback overtook the drain callback");
3681 NOTICE(
"HOSTED-SYSCALL-TEST: PASS epoll-reordered-transition-publication");
3685bool epollPersistentFifoReopenReclose(
Process* kernelProcess) {
3686 constexpr size_t DescriptorNumber = 52;
3689 process->setSubsystem(subsystem);
3691 ReorderedFifo* fifo =
new ReorderedFifo;
3695 fifo->increaseRefCount(
false);
3696 char fill[PIPE_BUF_MAX] = {};
3697 const int fillResult = writer->
write(
sizeof(fill),
reinterpret_cast<uintptr_t
>(fill));
3698 fifo->decreaseRefCount(
false);
3701 ReorderedFifoEpollContext context(instance, DescriptorNumber);
3703 new Thread(process, waitAcrossReorderedFifoReopen, &context,
nullptr,
false,
true,
true);
3704 waiter->setName(
"hosted reordered FIFO epoll waiter");
3705 const bool waiterStarted =
waiter->start();
3706 const bool initialComplete = waiterStarted && context.initialComplete.acquireForCompletion();
3709 bool reopenMadeNotReady =
false;
3710 if (initialComplete) {
3714 fifo->reopenReaderWithoutPublishing();
3715 reopenMadeNotReady = fifo->queryReady(
false,
true) == ReadyNone;
3716 fifo->decreaseRefCount(
false);
3717 fifo->publishReopen();
3721 context.collectFinal.release();
3722 const bool waiterJoined = waiterStarted &&
waiter->joinForCompletion();
3723 if (!waiterStarted) {
3727 const uint32_t expectedEvents = LinuxEpoll::Out | LinuxEpoll::Error;
3728 const bool edgePassed =
3729 fillResult == PIPE_BUF_MAX && context.addResult == 0 && context.initialWaitResult == 1 &&
3730 context.initialWaitEvents == expectedEvents && context.initialWaitData == EpollFifoData &&
3731 reopenMadeNotReady && context.finalWaitResult == 1 &&
3732 context.finalWaitEvents == expectedEvents && context.finalWaitData == EpollFifoData &&
3733 context.deleteResult == 0 && finalGenerations.write != initialGenerations.write &&
3734 finalGenerations.error != initialGenerations.error;
3736 waiterStarted && initialComplete && waiterJoined && context.returned == 1 && edgePassed;
3740 const bool descriptorClosed =
3743 passed = passed && descriptorClosed;
3746 context.instance.reset();
3752 "HOSTED-SYSCALL-TEST: FAIL epoll-persistent-fifo-reopen-reclose: "
3753 "a full FIFO lost EPOLLOUT when reader re-close overtook reopen publication");
3757 NOTICE(
"HOSTED-SYSCALL-TEST: PASS epoll-persistent-fifo-reopen-reclose");
3761int pollAcrossCloseReuse(
void* parameter) {
3762 PollCloseReuseContext* context =
reinterpret_cast<PollCloseReuseContext*
>(parameter);
3763 context->entered += 1;
3766 context->result = posix_poll_safe(&context->descriptor, 1, PollCloseReuseTimeoutMilliseconds);
3767 context->returned += 1;
3768 return context->result == 1 ? 0 : 1;
3771bool pollCloseReuseCleanup(
Process* kernelProcess) {
3772 constexpr size_t DescriptorNumber = 39;
3775 process->setSubsystem(subsystem);
3781 PollGenerationProbe* aNetwork =
3782 new PollGenerationProbe(aQueries, aNotifications, aNetworkDestructions);
3784 DescriptorRetirementProbe* aDescriptor =
new DescriptorRetirementProbe(aDescriptorDestructions);
3785 aDescriptor->fd = DescriptorNumber;
3786 aDescriptor->setOffset(1);
3787 aDescriptor->setNetworkImpl(aNetworkKeepalive);
3794 PollGenerationProbe* bNetwork =
3795 new PollGenerationProbe(bQueries, bNotifications, bNetworkDestructions);
3797 DescriptorRetirementProbe* bDescriptor =
new DescriptorRetirementProbe(bDescriptorDestructions);
3798 bDescriptor->fd = DescriptorNumber;
3799 bDescriptor->setOffset(2);
3800 bDescriptor->setNetworkImpl(bNetworkKeepalive);
3802 PollCloseReuseContext context(DescriptorNumber);
3803 Thread*
worker =
new Thread(process, pollAcrossCloseReuse, &context,
nullptr,
false,
true,
true);
3804 worker->setName(
"hosted poll close-reuse worker");
3805 const bool started =
worker->start();
3807 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3809 bool blockedOnA =
false;
3810 for (
size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
3812 if (context.entered && aQueries >= 2 &&
worker->getWaitDebugInfo(info) && info.queue &&
3813 info.queued &&
worker->getStatus() == Thread::Sleeping) {
3820 bool passed = started && blockedOnA && aQueries >= 2;
3822 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3823 "waiter-published-a blocked="
3824 << blockedOnA <<
" queries=" << aQueries.value());
3827 const bool closedA = acquiredA && subsystem->
closeFileDescriptor(DescriptorNumber, closingA);
3830 passed = passed && closedA && aDescriptorDestructions == 0 && aNetworkDestructions == 0;
3832 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3833 "closed-a-published-b");
3835 if (aQueries >= 2) {
3836 aNetwork->makeReadable();
3840 bNetwork->makeReadable();
3843 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3844 "release-published");
3846 const bool joined = started &&
worker->joinForCompletion();
3848 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3849 "worker-returned joined="
3850 << joined <<
" returned=" << context.returned.value()
3851 <<
" result=" << context.result.value());
3852 passed = passed && joined && context.returned == 1 && context.result == 1 &&
3853 (context.descriptor.revents & POLLIN) && aNotifications == 1 && bNotifications == 0 &&
3854 bQueries == 0 && aDescriptorDestructions == 1 && aNetworkDestructions == 0;
3855 aNetworkKeepalive.reset();
3856 passed = passed && aNetworkDestructions == 1;
3860 const bool closedB = acquiredB && subsystem->
closeFileDescriptor(DescriptorNumber, closingB);
3862 passed = passed && closedB && bDescriptorDestructions == 1 && bNetworkDestructions == 0;
3863 bNetworkKeepalive.reset();
3864 passed = passed && bNetworkDestructions == 1;
3870 "HOSTED-SYSCALL-TEST: FAIL poll-close-reuse-cleanup: "
3871 "poll cleanup followed the reused fd instead of its registered "
3872 "descriptor generation");
3876 NOTICE(
"HOSTED-SYSCALL-TEST: PASS poll-close-reuse-cleanup");
3880struct PosixTeardownContext {
3881 explicit PosixTeardownContext(
Process* process)
3883 releaseGate(0, false),
3887 processDeleted(0) {}
3897int holdMemoryMapLifecycleGate(
void* parameter) {
3898 PosixTeardownContext* context =
reinterpret_cast<PosixTeardownContext*
>(parameter);
3900 context->holderEntered += 1;
3901 const bool released = context->releaseGate.acquireForCompletion();
3903 context->holderReturned += 1;
3904 return released ? 0 : 1;
3907int deletePosixProcess(
void* parameter) {
3908 PosixTeardownContext* context =
reinterpret_cast<PosixTeardownContext*
>(parameter);
3909 context->reaperEntered += 1;
3910 delete context->process;
3911 context->processDeleted += 1;
3915bool posixTeardownContention(
Process* kernelProcess) {
3918 PosixTeardownContext context(process);
3921 new Thread(kernelProcess, holdMemoryMapLifecycleGate, &context,
nullptr,
false,
true,
true);
3922 holder->setName(
"hosted mmap lifecycle holder");
3923 bool passed = holder->
start();
3925 for (
size_t attempt = 0; attempt < HostedAttempts && passed && !context.holderEntered;
3929 passed = passed && context.holderEntered == 1;
3931 Thread* reaper =
nullptr;
3932 bool blocked =
false;
3934 reaper =
new Thread(kernelProcess, deletePosixProcess, &context,
nullptr,
false,
true,
true);
3935 reaper->setName(
"hosted POSIX process reaper");
3936 passed = reaper->
start();
3938 for (
size_t attempt = 0; attempt < HostedAttempts && passed; ++attempt) {
3940 if (context.reaperEntered && reaper->
getWaitDebugInfo(info) && info.queue && info.queued &&
3942 reaper->
getStatus() == Thread::Sleeping) {
3948 passed = passed && blocked && context.processDeleted == 0;
3951 context.releaseGate.release();
3952 passed = holder->
join() && passed;
3954 passed = reaper->
join() && passed;
3955 passed = passed && context.processDeleted == 1;
3962 "HOSTED-SYSCALL-TEST: FAIL posix-teardown-contention: "
3963 "real PosixSubsystem destruction did not sleep and resume on "
3964 "the memory-map lifecycle gate");
3968 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-teardown-contention");
3972bool zeroResultWinsSignal(
Thread* thread) {
3974 thread->setInterruptionReason(Thread::InterruptedBySignal);
3975 const bool completed = finishInterruptibleSocketCall(thread,
static_cast<ssize_t
>(0));
3977 completed && thread->getInterruptionReason() == Thread::NotInterrupted && !thread->
getErrno();
3980 "HOSTED-SYSCALL-TEST: FAIL socket-zero-result-signal: "
3981 "EOF or zero-length success was replaced with EINTR");
3982 thread->clearInterruption();
3987 NOTICE(
"HOSTED-SYSCALL-TEST: PASS socket-zero-result-signal");
3991bool cloneStateDropsParentErrnoDestination() {
3993 SyscallState parent = {};
3994 parent.error_ptr =
reinterpret_cast<uintptr_t
>(&error);
3997 const SyscallState child = posix_copy_clone_state(parent);
3998 const bool passed = !child.error_ptr && child.result == parent.result &&
3999 parent.error_ptr ==
reinterpret_cast<uintptr_t
>(&error);
4002 "HOSTED-SYSCALL-TEST: FAIL clone-errno-lifetime: "
4003 "the child retained its parent's stack-local errno destination");
4007 NOTICE(
"HOSTED-SYSCALL-TEST: PASS clone-errno-lifetime");
4011bool cloneVmNullStackPreservesInterrupts() {
4014 posix, POSIX_CLONE, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD, 0, 0, 0,
4017 if (interruptsStillEnabled != interruptsWereEnabled) {
4021 if (!interruptsWereEnabled || !interruptsStillEnabled || result !=
static_cast<uintptr_t
>(-1)) {
4023 "HOSTED-SYSCALL-TEST: FAIL clone-vm-null-stack-interrupts: "
4024 "the invalid clone path did not preserve its enabled IRQ state");
4028 NOTICE(
"HOSTED-SYSCALL-TEST: PASS clone-vm-null-stack-interrupts");
4032enum CloneVmBeforeStartAction {
4033 CancelChildBeforeStart,
4037struct CloneVmUserFixture {
4041 alignas(16) uint8_t childStack[4096];
4044struct CloneVmExitRaceContext {
4045 explicit CloneVmExitRaceContext(CloneVmBeforeStartAction action)
4048 terminator(nullptr),
4051 beforeStart(0, true),
4058 callerInterruptsBefore(0),
4059 callerInterruptsAfter(0),
4060 terminatorEntered(0),
4061 terminatorReturned(0),
4064 terminationElectionCalls(0),
4065 ownershipWindowReleased(0),
4066 ownershipCancellationObserved(0),
4067 ownershipStartObserved(0),
4068 controlledHookRelease(0),
4069 electionTimedOut(0),
4070 childCancellationPublished(0),
4071 cloneResult(static_cast<size_t>(-1)),
4072 childCancellationRequested(0),
4073 childCancellationReapable(0),
4074 terminatorStarted(0),
4075 terminalCancellation(0),
4076 unexpectedHookRelease(0),
4078 rescueCancellation(0),
4079 processDestructions(0),
4080 subsystemDestructions(0) {}
4086 CloneVmBeforeStartAction action;
4088 uintptr_t userAddress;
4119CloneVmExitRaceContext* g_CloneVmExitRaceContext =
nullptr;
4121class CloneVmExitRaceProcess final :
public PosixProcess {
4124 :
PosixProcess(parent), m_Destructions(destructions) {}
4126 ~CloneVmExitRaceProcess()
override {
4127 m_Destructions += 1;
4139 ~CloneVmExitRaceSubsystem()
override {
4140 m_Destructions += 1;
4147int terminateCloneVmProcess(
void* parameter) {
4148 CloneVmExitRaceContext* context =
reinterpret_cast<CloneVmExitRaceContext*
>(parameter);
4149 context->terminatorEntered += 1;
4151 context->terminatorReturned += 1;
4155void terminateCloneVmBeforeStart(
Thread* child,
size_t threadId,
void* parameter) {
4156 CloneVmExitRaceContext* context =
reinterpret_cast<CloneVmExitRaceContext*
>(parameter);
4157 if (!context || !child || child->
getParent() != context->process) {
4160 context->child = child;
4161 context->observedTid = threadId;
4162 auto* user =
reinterpret_cast<CloneVmUserFixture*
>(context->userAddress);
4163 uintptr_t tlsValue = 0;
4165 sizeof(context->parentTid)) &&
4167 sizeof(context->childTid)) &&
4169 if (copied && child->
getId() == threadId && context->parentTid ==
static_cast<int>(threadId) &&
4170 context->childTid ==
static_cast<int>(threadId) &&
4171 tlsValue ==
reinterpret_cast<uintptr_t
>(&user->childTls)) {
4172 context->tidsReady += 1;
4174 context->hookCalls += 1;
4176 bool cancelChild = context->action == CancelChildBeforeStart || context->rescueCancellation;
4178 const bool released = context->beforeStart.acquire(1, 5, 0);
4179 cancelChild = context->rescueCancellation;
4181 if (released && context->ownershipWindowReleased) {
4182 context->controlledHookRelease += 1;
4187 context->terminalCancellation += 1;
4189 context->unexpectedHookRelease += 1;
4191 context->hookTimedOut += 1;
4199 context->childCancellationPublished = 1;
4200 context->childCancellationRequested += 1;
4201 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4202 if (child->isReapableForHostedTest()) {
4203 context->childCancellationReapable += 1;
4208 context->hookTimedOut += 1;
4211void observeCloneVmTerminationElection(
Process* process,
Thread* owner) {
4212 CloneVmExitRaceContext* context = __atomic_load_n(&g_CloneVmExitRaceContext, __ATOMIC_ACQUIRE);
4213 if (!context || process != context->process || owner != context->terminator) {
4216 context->terminationElectionCalls += 1;
4217 context->ownershipWindowReleased = 1;
4218 context->beforeStart.release();
4219 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4220 Thread* child = context->child.value();
4221 if (child && context->callerReturned) {
4223 !child->wasStartPublishedForHostedTest()) {
4224 context->ownershipCancellationObserved += 1;
4226 context->ownershipStartObserved += 1;
4228 context->childCancellationPublished = 1;
4233 context->electionTimedOut += 1;
4234 context->childCancellationPublished = 1;
4237void clearCloneVmHooks() {
4238 posixSetCloneBeforeStartHookForTest(
nullptr,
nullptr);
4239 Process::setTerminationElectionHook(
nullptr);
4240 __atomic_store_n(&g_CloneVmExitRaceContext,
static_cast<CloneVmExitRaceContext*
>(
nullptr),
4244int cloneVmWhileProcessExits(
void* parameter) {
4245 CloneVmExitRaceContext* context =
reinterpret_cast<CloneVmExitRaceContext*
>(parameter);
4246 context->callerEntered += 1;
4248 const size_t length = (
sizeof(CloneVmUserFixture) + pageSize - 1) & ~(pageSize - 1);
4249 uintptr_t address = 0;
4250 if (!context->process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
4251 context->callerReturned += 1;
4254 uintptr_t mappedAddress = address;
4256 mappedAddress, length, MemoryMappedObject::Read | MemoryMappedObject::Write);
4257 if (!mapping || mappedAddress != address) {
4261 context->process->freeUserRange(Process::UserRegion::Normal, address, length);
4262 context->callerReturned += 1;
4265 context->userAddress = address;
4266 auto* user =
reinterpret_cast<CloneVmUserFixture*
>(address);
4267 const CloneVmUserFixture initial = {-1, -1, 0, {}};
4269 context->callerReturned += 1;
4273 context->callerInterruptsBefore = interruptsWereEnabled ? 1 : 0;
4276 CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS |
4277 CLONE_PARENT_SETTID | CLONE_CHILD_SETTID,
4278 reinterpret_cast<uintptr_t
>(user->childStack +
sizeof(user->childStack)),
4279 reinterpret_cast<uintptr_t
>(&user->parentTid),
reinterpret_cast<uintptr_t
>(&user->childTid),
4280 reinterpret_cast<uintptr_t
>(&user->childTls));
4285 context->callerReturned += 1;
4289int cleanupCloneVmUserFixture(
void* parameter) {
4290 CloneVmExitRaceContext* context =
reinterpret_cast<CloneVmExitRaceContext*
>(parameter);
4291 if (context->userAddress) {
4293 const size_t length = (
sizeof(CloneVmUserFixture) + pageSize - 1) & ~(pageSize - 1);
4295 context->process->freeUserRange(Process::UserRegion::Normal, context->userAddress, length);
4296 context->userAddress = 0;
4301bool waitForCloneVmHookPause(CloneVmExitRaceContext* context) {
4302 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4304 if (context->hookCalls ==
static_cast<size_t>(1) && context->caller->getWaitDebugInfo(info) &&
4305 info.queue && info.queued && info.channelOwner == &context->beforeStart &&
4306 context->caller->getStatus() == Thread::Sleeping) {
4309 if (context->unexpectedHookRelease || context->hookTimedOut || context->callerReturned) {
4317bool waitForCloneVmThreadReapable(
Thread* thread) {
4318 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4319 if (thread->isReapableForHostedTest()) {
4327bool waitForCloneVmThreadCount(
Process* process,
size_t count) {
4328 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4337NORETURN void fatalCloneVmFixture(
const char* detail) {
4338 clearCloneVmHooks();
4339 FATAL(
"HOSTED-SYSCALL-TEST: clone fixture could not retire safely: " << detail);
4343bool cloneVmDetachedCancellationReturnsCachedTid(
Process* kernelProcess) {
4344 CloneVmExitRaceContext* context =
new CloneVmExitRaceContext(CancelChildBeforeStart);
4345 CloneVmExitRaceProcess* process =
4346 new CloneVmExitRaceProcess(kernelProcess, context->processDestructions);
4347 process->setSubsystem(
new CloneVmExitRaceSubsystem(context->subsystemDestructions));
4348 process->
description() =
"hosted clone detached-cancellation fixture";
4350 context->process = process;
4352 new Thread(process, cloneVmWhileProcessExits, context,
nullptr,
false,
true,
true);
4353 context->caller->setName(
"hosted clone detached-cancellation caller");
4356 posixSetCloneBeforeStartHookForTest(terminateCloneVmBeforeStart, context);
4357 const bool callerStarted = context->caller->start();
4358 bool callerReapable = callerStarted && waitForCloneVmThreadReapable(context->caller);
4359 if (!callerReapable) {
4360 context->rescueCancellation = 1;
4362 context->beforeStart.release();
4363 callerReapable = waitForCloneVmThreadReapable(context->caller);
4365 if (!callerReapable) {
4366 fatalCloneVmFixture(
"detached-cancellation caller remained live after rescue");
4368 clearCloneVmHooks();
4370 const bool childDeletedBeforeReturn = waitForCloneVmThreadCount(process, 1);
4371 bool passed = callerStarted && callerReapable && childDeletedBeforeReturn &&
4372 process->getState() == Process::Active && context->callerEntered == 1 &&
4373 context->callerReturned == 1 && context->callerInterruptsBefore == 1 &&
4374 context->callerInterruptsAfter == 1 && context->hookCalls == 1 &&
4375 context->tidsReady == 1 && context->childCancellationRequested == 1 &&
4376 context->childCancellationReapable == 1 && !context->hookTimedOut &&
4377 !context->unexpectedHookRelease && context->observedTid &&
4378 context->cloneResult == context->observedTid &&
4379 context->parentTid ==
static_cast<int>(context->observedTid) &&
4380 context->childTid ==
static_cast<int>(context->observedTid);
4382 if (!childDeletedBeforeReturn) {
4383 fatalCloneVmFixture(
"detached child remained live after its creator retired");
4385 if (!context->caller->joinForCompletion()) {
4386 fatalCloneVmFixture(
"detached-cancellation caller could not be joined");
4388 if (!waitForCloneVmThreadCount(process, 0)) {
4389 fatalCloneVmFixture(
"detached-cancellation process retained a live thread");
4391 Thread* cleanup =
new Thread(process, cleanupCloneVmUserFixture, context,
nullptr,
false,
true);
4393 fatalCloneVmFixture(
"detached-cancellation user mapping cleanup could not be joined");
4397 passed = passed && context->processDestructions == 1 && context->subsystemDestructions == 1;
4402 "HOSTED-SYSCALL-TEST: FAIL clone-vm-detached-cached-tid: "
4403 "POSIX clone did not return the cached ID after detached cancellation");
4407 NOTICE(
"HOSTED-SYSCALL-TEST: PASS clone-vm-detached-cached-tid");
4411bool cloneVmTerminalStartCancellation(
Process* kernelProcess) {
4412 CloneVmExitRaceContext* context =
new CloneVmExitRaceContext(WaitForProcessExit);
4413 CloneVmExitRaceProcess* process =
4414 new CloneVmExitRaceProcess(kernelProcess, context->processDestructions);
4415 process->setSubsystem(
new CloneVmExitRaceSubsystem(context->subsystemDestructions));
4416 process->
description() =
"hosted clone-vs-exit fixture";
4418 context->process = process;
4420 new Thread(process, cloneVmWhileProcessExits, context,
nullptr,
false,
true,
true);
4421 context->caller->setName(
"hosted clone-vs-exit caller");
4422 context->terminator =
4423 new Thread(process, terminateCloneVmProcess, context,
nullptr,
false,
true,
true);
4424 context->terminator->setName(
"hosted clone-vs-exit terminator");
4427 __atomic_store_n(&g_CloneVmExitRaceContext, context, __ATOMIC_RELEASE);
4428 Process::setTerminationElectionHook(observeCloneVmTerminationElection);
4429 posixSetCloneBeforeStartHookForTest(terminateCloneVmBeforeStart, context);
4430 const bool callerStarted = context->caller->start();
4431 const bool callerPaused = callerStarted && waitForCloneVmHookPause(context);
4432 Thread* pausedExpectedChild = context->child.value();
4434 const bool pausedChildPinned = callerPaused && pausedExpectedChild &&
4436 pausedChild->
getId() == context->observedTid;
4437 const bool terminatorStarted = context->terminator->start();
4438 if (terminatorStarted) {
4439 context->terminatorStarted += 1;
4441 context->rescueCancellation = 1;
4442 Thread* rescueExpectedChild = context->child.value();
4443 bool publishedChildSafe = !rescueExpectedChild;
4444 if (pausedChildPinned) {
4446 context->childCancellationPublished = 1;
4447 publishedChildSafe =
true;
4448 }
else if (rescueExpectedChild) {
4450 if (process->
acquireThread(rescueChild, rescueExpectedChild)) {
4452 context->childCancellationPublished = 1;
4453 publishedChildSafe =
true;
4455 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4457 context->child =
nullptr;
4458 publishedChildSafe =
true;
4465 if (!publishedChildSafe) {
4466 pausedChild.reset();
4467 fatalCloneVmFixture(
"published child could not be cancelled for start-failure rescue");
4469 pausedChild.reset();
4472 context->beforeStart.release();
4474 pausedChild.reset();
4476 bool terminated =
false;
4477 for (
size_t attempt = 0; attempt < HostedAttempts && terminatorStarted; ++attempt) {
4478 if (process->isTerminationReapableForHostedTest()) {
4484 if (terminatorStarted && !terminated) {
4485 context->beforeStart.release();
4486 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4487 if (process->isTerminationReapableForHostedTest()) {
4495 if (!terminatorStarted) {
4496 const bool callerReapable = waitForCloneVmThreadReapable(context->caller);
4497 const bool terminatorReapable = waitForCloneVmThreadReapable(context->terminator);
4498 if (!callerReapable || !terminatorReapable) {
4499 fatalCloneVmFixture(
"start-failure rescue left a worker live");
4501 clearCloneVmHooks();
4502 if (!context->caller->joinForCompletion() || !context->terminator->joinForCompletion()) {
4503 fatalCloneVmFixture(
"start-failure rescue could not join both workers");
4505 if (!waitForCloneVmThreadCount(process, 0)) {
4506 fatalCloneVmFixture(
"start-failure rescue retained a cloned child");
4508 Thread* cleanup =
new Thread(process, cleanupCloneVmUserFixture, context,
nullptr,
false,
true);
4510 fatalCloneVmFixture(
"start-failure user mapping cleanup could not be joined");
4513 const bool destroyed = context->processDestructions == 1 && context->subsystemDestructions == 1;
4516 FATAL(
"HOSTED-SYSCALL-TEST: clone start-failure rescue did not destroy exact owners");
4519 "HOSTED-SYSCALL-TEST: FAIL clone-vm-terminal-start-cancellation: "
4520 "the exit worker did not start");
4524 fatalCloneVmFixture(
"process-exit cancellation did not become reapable");
4526 clearCloneVmHooks();
4528 Thread* retiredChild = context->child.value();
4529 const bool retired = terminated && process->getState() == Process::Terminated &&
4531 context->caller->getStatus() == Thread::AwaitingJoin &&
4532 context->terminator->getStatus() == Thread::AwaitingJoin && retiredChild &&
4533 retiredChild->
getStatus() == Thread::AwaitingJoin &&
4534 !retiredChild->wasStartPublishedForHostedTest();
4536 callerPaused && retired && context->callerEntered == 1 && context->callerReturned == 1 &&
4537 context->callerInterruptsBefore == 1 && context->callerInterruptsAfter == 1 &&
4538 context->terminatorEntered == 1 && !context->terminatorReturned && context->hookCalls == 1 &&
4539 context->tidsReady == 1 && context->terminationElectionCalls == 1 &&
4540 context->ownershipWindowReleased == 1 && context->ownershipCancellationObserved == 1 &&
4541 !context->ownershipStartObserved && context->controlledHookRelease == 1 &&
4542 !context->electionTimedOut && context->childCancellationPublished == 1 &&
4543 context->terminatorStarted == 1 && !context->terminalCancellation &&
4544 !context->unexpectedHookRelease && !context->hookTimedOut && context->observedTid &&
4545 context->cloneResult == context->observedTid &&
4546 context->parentTid ==
static_cast<int>(context->observedTid) &&
4547 context->childTid ==
static_cast<int>(context->observedTid);
4549 passed = passed && pausedChildPinned;
4551 passed = passed && context->processDestructions == 1 && context->subsystemDestructions == 1;
4556 "HOSTED-SYSCALL-TEST: FAIL clone-vm-terminal-start-cancellation: "
4557 "terminal cancellation did not retire the published clone exactly once");
4561 NOTICE(
"HOSTED-SYSCALL-TEST: PASS clone-vm-terminal-start-cancellation");
4565bool failedPinnedModuleRejectsUnload() {
4567 module.name.assign("hosted-failed-pinned-probe");
4568 module.unloadable = false;
4569 module.status = Module::Failed;
4571 if (KernelElf::claimModuleUnloadForTest(&module) != KernelElf::TestUnloadPinned ||
4572 module.status != Module::Failed || module.unloadComplete) {
4574 "HOSTED-SYSCALL-TEST: FAIL failed-pinned-module: "
4575 "failed initialisation did not preserve its pinned module image");
4579 NOTICE(
"HOSTED-SYSCALL-TEST: PASS failed-pinned-module");
4583ModuleInfo* findStaticModuleInfo(
const char* name,
size_t& matches) {
4586 for (
size_t i = 0; i < g_StaticDriverN; ++i) {
4588 if (info && info->name && !StringCompare(info->name, name)) {
4596bool moduleInfoDependsOn(
ModuleInfo* info,
const char* dependency,
bool optional =
false) {
4597 const char** dependencies = optional ? info->opt_dependencies : info->dependencies;
4598 if (!dependencies) {
4601 for (
size_t i = 0; dependencies[i]; ++i) {
4602 if (!StringCompare(dependencies[i], dependency)) {
4609bool linkerModuleMetadataIsPinned() {
4611 ModuleInfo* linker = findStaticModuleInfo(
"linker", matches);
4613 if (matches != 1 || !linker || linker->unloadable || linker->runtimeUnloadable ||
4614 !moduleInfoDependsOn(linker,
"vfs")) {
4616 "HOSTED-SYSCALL-TEST: FAIL linker-pinned-metadata: "
4617 "the real linker ModuleInfo did not pin its dependency closure");
4621 NOTICE(
"HOSTED-SYSCALL-TEST: PASS linker-pinned-metadata");
4625bool filesystemModuleUnloadPolicyIsCorrect() {
4626 size_t posixMatches = 0;
4627 size_t mountrootMatches = 0;
4628 size_t ramfsMatches = 0;
4629 size_t rawfsMatches = 0;
4630 ModuleInfo* posix = findStaticModuleInfo(
"posix", posixMatches);
4631 ModuleInfo* mountroot = findStaticModuleInfo(
"mountroot", mountrootMatches);
4632 ModuleInfo* ramfs = findStaticModuleInfo(
"ramfs", ramfsMatches);
4633 ModuleInfo* rawfs = findStaticModuleInfo(
"rawfs", rawfsMatches);
4635 const bool metadataValid =
4636 posixMatches == 1 && mountrootMatches == 1 && ramfsMatches == 1 && rawfsMatches == 1 &&
4637 posix && mountroot && ramfs && rawfs && posix->unloadable && !posix->runtimeUnloadable &&
4638 mountroot->unloadable && !mountroot->runtimeUnloadable && !ramfs->unloadable &&
4639 !ramfs->runtimeUnloadable && rawfs->unloadable && !rawfs->runtimeUnloadable &&
4640 moduleInfoDependsOn(posix,
"mountroot") && moduleInfoDependsOn(posix,
"ramfs") &&
4641 moduleInfoDependsOn(mountroot,
"vfs") && moduleInfoDependsOn(mountroot,
"rawfs") &&
4642 moduleInfoDependsOn(mountroot,
"ramfs") && moduleInfoDependsOn(mountroot,
"fat",
true) &&
4643 moduleInfoDependsOn(mountroot,
"ext2",
true) &&
4644 moduleInfoDependsOn(mountroot,
"iso9660",
true) && moduleInfoDependsOn(ramfs,
"vfs") &&
4645 moduleInfoDependsOn(rawfs,
"vfs");
4646 if (!metadataValid) {
4648 "HOSTED-SYSCALL-TEST: FAIL filesystem-unload-policy-metadata: "
4649 "the real filesystem owner metadata did not encode the expected unload policy or "
4650 "dependency closure");
4654 NOTICE(
"HOSTED-SYSCALL-TEST: PASS filesystem-unload-policy-metadata");
4658bool runtimePinnedModuleAllowsLifecycleCleanup() {
4659 g_RuntimePinnedLifecycleCalls = 0;
4662 active.name.assign(
"hosted-runtime-pinned-active-probe");
4663 active.exit = runtimePinnedLifecycleProbe;
4664 active.runtimeUnloadable =
false;
4665 active.status = Module::Active;
4667 bool runLifecycle =
true;
4668 const KernelElf::TestModuleUnloadClaim explicitActive =
4669 KernelElf::claimModuleUnloadForTest(&active,
false, &runLifecycle);
4670 const bool explicitActiveValid = explicitActive == KernelElf::TestUnloadRuntimePinned &&
4671 !runLifecycle && active.status == Module::Active &&
4672 !active.unloadComplete && !g_RuntimePinnedLifecycleCalls;
4673 if (explicitActive == KernelElf::TestUnloadClaimed) {
4674 KernelElf::completeModuleUnloadForTest(&active);
4676 if (!explicitActiveValid) {
4678 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4679 "explicit unload escaped the runtime-only lifetime boundary");
4683 const KernelElf::TestModuleUnloadClaim shutdownActive =
4684 KernelElf::claimModuleUnloadForTest(&active,
true, &runLifecycle);
4685 const bool shutdownActiveValid = shutdownActive == KernelElf::TestUnloadClaimed && runLifecycle &&
4686 active.status == Module::Unloading;
4687 if (shutdownActive == KernelElf::TestUnloadClaimed) {
4688 KernelElf::completeModuleUnloadForTest(&active,
false, runLifecycle);
4690 if (!shutdownActiveValid) {
4692 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4693 "shutdown could not claim an active runtime-pinned module");
4698 failed.name.assign(
"hosted-runtime-pinned-failed-probe");
4699 failed.exit = runtimePinnedLifecycleProbe;
4700 failed.runtimeUnloadable =
false;
4701 failed.status = Module::Failed;
4702 runLifecycle =
true;
4703 const KernelElf::TestModuleUnloadClaim explicitFailed =
4704 KernelElf::claimModuleUnloadForTest(&failed,
false, &runLifecycle);
4705 const bool explicitFailedValid = explicitFailed == KernelElf::TestUnloadRuntimePinned &&
4706 !runLifecycle && failed.status == Module::Failed &&
4707 !failed.unloadComplete;
4708 if (explicitFailed == KernelElf::TestUnloadClaimed) {
4709 KernelElf::completeModuleUnloadForTest(&failed,
true);
4711 if (!explicitFailedValid) {
4713 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4714 "explicit unload escaped a failed runtime-pinned module");
4718 const KernelElf::TestModuleUnloadClaim failureCleanup =
4719 KernelElf::claimModuleUnloadForTest(&failed,
true, &runLifecycle);
4720 const bool failureCleanupValid = failureCleanup == KernelElf::TestUnloadClaimed && runLifecycle &&
4721 failed.status == Module::Unloading;
4722 if (failureCleanup == KernelElf::TestUnloadClaimed) {
4723 KernelElf::completeModuleUnloadForTest(&failed,
true, runLifecycle);
4725 if (!failureCleanupValid) {
4727 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4728 "failed initialisation could not claim lifecycle cleanup");
4732 if (!active.isUnloaded() || !active.unloadComplete || failed.status != Module::Failed ||
4733 !failed.unloadComplete || g_RuntimePinnedLifecycleCalls != 2) {
4735 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4736 "shutdown or failure cleanup did not publish completion");
4740 NOTICE(
"HOSTED-SYSCALL-TEST: PASS runtime-pinned-cleanup");
4744bool moduleUnloadOwnershipIsRetryable() {
4746 module.name.assign("hosted-unload-owner-probe");
4747 module.status = Module::Active;
4748 Module* fixtures[] = {&
module};
4750 const KernelElf::TestModuleUnloadClaim first =
4751 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1,
"hosted-unload-owner-probe");
4752 const KernelElf::TestModuleUnloadClaim concurrent =
4753 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1,
"hosted-unload-owner-probe");
4754 KernelElf::completeModuleUnloadForTest(&module);
4755 const KernelElf::TestModuleUnloadClaim repeat =
4756 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1,
"hosted-unload-owner-probe");
4757 const KernelElf::TestModuleUnloadClaim missing =
4758 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1,
"hosted-unload-missing-probe");
4760 if (first != KernelElf::TestUnloadClaimed || concurrent != KernelElf::TestUnloadBusy ||
4761 repeat != KernelElf::TestUnloadComplete || missing != KernelElf::TestUnloadUnknown ||
4762 !module.isUnloaded() || !module.unloadComplete ||
4763 !KernelElf::moduleExecutionWaitsForUnloadForTest()) {
4765 "HOSTED-SYSCALL-TEST: FAIL module-unload-ownership: "
4766 "the first owner, concurrent retry, or completed tombstone was lost");
4770 NOTICE(
"HOSTED-SYSCALL-TEST: PASS module-unload-ownership");
4774bool moduleShutdownOrderIsDependencySafe() {
4775 const char* nicsOptional[] = {
"ne2k",
nullptr};
4776 const char* ne2kDependencies[] = {
"network-stack",
nullptr};
4779 networkStack.name.assign(
"network-stack");
4780 networkStack.status = Module::Active;
4783 nics.name.assign(
"nics");
4784 nics.depends_opt = nicsOptional;
4785 nics.runtimeUnloadable =
false;
4786 nics.status = Module::Active;
4789 ne2k.name.assign(
"ne2k");
4790 ne2k.depends = ne2kDependencies;
4791 ne2k.status = Module::Active;
4793 Module* modules[] = {&networkStack, &nics, &ne2k};
4795 const size_t planned = KernelElf::planModuleUnloadOrderForTest(modules, 3, order, 3);
4796 const size_t repeated = KernelElf::planModuleUnloadOrderForTest(modules, 3, order, 3);
4798 const char* cycleADependencies[] = {
"cycle-b",
nullptr};
4799 const char* cycleBDependencies[] = {
"cycle-a",
nullptr};
4801 cycleA.name.assign(
"cycle-a");
4802 cycleA.depends = cycleADependencies;
4803 cycleA.status = Module::Active;
4805 cycleB.name.assign(
"cycle-b");
4806 cycleB.depends = cycleBDependencies;
4807 cycleB.status = Module::Active;
4808 Module* cycle[] = {&cycleA, &cycleB};
4809 Module* cycleOrder[2] = {};
4810 const size_t cyclicPlanned = KernelElf::planModuleUnloadOrderForTest(cycle, 2, cycleOrder, 2);
4812 if (planned != 3 || order[0] != &nics || order[1] != &ne2k || order[2] != &networkStack ||
4813 repeated != 0 || cyclicPlanned != 0 || cycleA.unloadComplete || cycleB.unloadComplete) {
4815 "HOSTED-SYSCALL-TEST: FAIL module-shutdown-order: "
4816 "optional/mandatory dependents were not retired first or a cycle was torn down");
4821 permanent.name.assign(
"permanent-shutdown-probe");
4822 permanent.unloadable =
false;
4823 permanent.runtimeUnloadable =
false;
4824 permanent.status = Module::Active;
4826 runtimePinned.name.assign(
"runtime-pinned-shutdown-probe");
4827 runtimePinned.runtimeUnloadable =
false;
4828 runtimePinned.status = Module::Active;
4829 Module* retentionModules[] = {&permanent, &runtimePinned};
4830 Module* retentionOrder[2] = {};
4831 const size_t retentionPlanned =
4832 KernelElf::planModuleUnloadOrderForTest(retentionModules, 2, retentionOrder, 2);
4834 if (retentionPlanned != 1 || retentionOrder[0] != &runtimePinned ||
4835 !runtimePinned.unloadComplete || permanent.unloadComplete) {
4837 "HOSTED-SYSCALL-TEST: FAIL module-shutdown-retention-policy: "
4838 "a runtime-only module was retained or a permanent pin was retired");
4842 NOTICE(
"HOSTED-SYSCALL-TEST: PASS module-shutdown-order");
4843 NOTICE(
"HOSTED-SYSCALL-TEST: PASS module-shutdown-retention-policy");
4847bool publishTerminalBlockedHandlerFixture(
Process* kernelProcess) {
4848 TerminalBlockedHandlerContext* context =
new TerminalBlockedHandlerContext;
4851 process->
description() =
"hosted blocked POSIX handler shutdown fixture";
4853 new Thread(process, terminalBlockedHandlerEntry, context,
nullptr,
false,
true,
true);
4854 thread->setName(
"hosted blocked POSIX handler fixture");
4855 context->thread = thread;
4858 g_TerminalBlockedHandlerContext = context;
4860 const bool started = thread->
start();
4862 bool blocked =
false;
4863 for (
size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
4865 if (context->hookEntered ==
static_cast<size_t>(1) && thread->
getWaitDebugInfo(info) &&
4866 info.queue && info.queued && info.channelOwner == &context->blocker &&
4867 thread->
getStatus() == Thread::Sleeping) {
4876 if (!started || !blocked || context->exitStaged !=
static_cast<size_t>(1) ||
4877 context->releasedByTermination || context->unexpectedRelease || context->syscallReturned) {
4879 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-blocked-handler-fixture: "
4880 "the POSIX handler was not admitted and blocked with a staged process exit");
4884 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-terminal-blocked-handler-fixture-published");
4888bool runRegressions() {
4889 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN real-event-boundaries");
4893 "HOSTED-SYSCALL-TEST: FAIL real-event-boundaries: "
4894 "module initialisation was not at base state");
4899 if (!kernelProcess) {
4903 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN usercopy");
4904 if (!runHostedUsercopyRegressions(kernelProcess)) {
4908 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN mmap-placement");
4909 if (!runHostedMmapPlacementRegressions(kernelProcess)) {
4913 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN time-syscall-usercopy");
4914 if (!runHostedTimeSyscallRegressions(kernelProcess)) {
4918 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN resource-syscall-semantics");
4919 if (!runHostedResourceSyscallRegressions(kernelProcess)) {
4923 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN child-resource-accounting");
4924 if (!runHostedChildResourceRegressions(kernelProcess)) {
4928 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN faccessat2-semantics");
4929 if (!runHostedAccessSyscallRegressions(kernelProcess)) {
4933 if (!runHostedVmPermissionRegressions() || !runHostedSystemUsercopyRegressions(kernelProcess) ||
4934 !runHostedFutexRobustRegressions(kernelProcess) ||
4935 !runHostedFileContractRegressions(kernelProcess) ||
4936 !runHostedProcessQueryRegressions(kernelProcess) ||
4937 !runHostedTermiosSyscallRegressions(kernelProcess)) {
4941 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN sleep-clock-usercopy");
4942 if (!runHostedSleepClockSyscallRegressions(kernelProcess)) {
4946 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN posix-exit-status");
4947 if (!runHostedPosixExitStatusRegressions(kernelProcess)) {
4951 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN thread-signal-syscalls");
4952 if (!runHostedThreadSignalSyscallRegressions(kernelProcess)) {
4956 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN rt-sigsuspend");
4957 if (!runHostedRtSigsuspendRegressions(kernelProcess)) {
4961 bool establishedAliasPassed =
true;
4962 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN directory-retained-lookup-atomicity");
4963 establishedAliasPassed &= directoryRetainedLookupAtomicity(kernelProcess);
4965 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN directory-retained-lookup-lifecycle");
4966 establishedAliasPassed &= directoryRetainedLookupLifecycle(kernelProcess);
4968 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN vfs-established-alias-serialization");
4969 establishedAliasPassed &= establishedAliasRetainSerialization(kernelProcess);
4971 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN file-established-alias-lifetime");
4972 establishedAliasPassed &= establishedFileAliasLifetime();
4974 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN process-filesystem-context-lifetime");
4975 establishedAliasPassed &= processFilesystemContextLifetime(kernelProcess);
4977 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN mmap-established-alias-lifetime");
4978 establishedAliasPassed &= establishedMappingAliasLifetime();
4980 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN munmap-target-page-geometry");
4981 establishedAliasPassed &= munmapUsesTargetPageGeometry(thread);
4983 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN mmap-split-alias-lifetime");
4984 establishedAliasPassed &= mappingManagerSplitLifetime(kernelProcess);
4986 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN posix-path-lookup-lifetime");
4987 establishedAliasPassed &= posixPathLookupLifetime(kernelProcess);
4988 if (!establishedAliasPassed) {
4992 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-close-pinning");
4993 if (!descriptorClosePinning(kernelProcess)) {
4997 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-close-generation");
4998 if (!descriptorCloseGeneration(kernelProcess)) {
5002 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-open-file-description-state");
5003 if (!descriptorOpenFileDescriptionState()) {
5007 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-open-file-description-lifetime");
5008 if (!descriptorOpenFileDescriptionLifetime()) {
5012 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-append-policy");
5013 if (!descriptorAppendPolicy(kernelProcess)) {
5017 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-nonblocking-policy");
5018 if (!descriptorNonblockingPolicy()) {
5022 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-position-alias-serialization");
5023 if (!descriptorPositionAliasSerialization(kernelProcess)) {
5027 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-vector-io-serialization");
5028 if (!descriptorVectorIoSerialization(kernelProcess)) {
5032 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN advisory-lock-fail-closed");
5033 if (!runHostedAdvisoryLockRegressions(kernelProcess)) {
5037 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN scalar-io-user-buffer-lifetime");
5038 if (!runHostedScalarIoRegressions(kernelProcess)) {
5042 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN positional-io-semantics");
5043 if (!runHostedPositionalIoRegressions(kernelProcess)) {
5047 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN positional-vector-io-semantics");
5048 if (!runHostedPositionalVectorIoRegressions(kernelProcess)) {
5052 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN vector-io-user-buffer-lifetime");
5053 if (!runHostedVectorIoRegressions(kernelProcess)) {
5057 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN ppoll-linux-abi");
5058 if (!runHostedPpollRegressions(kernelProcess)) {
5062 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN pselect-linux-abi");
5063 if (!runHostedPselectRegressions(kernelProcess)) {
5067 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-dup-contract");
5068 if (!descriptorDupContract(kernelProcess)) {
5072 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN dup3-atomic-replacement");
5073 if (!runHostedDup3Regressions(kernelProcess)) {
5077 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-position-policy");
5078 if (!descriptorPositionPolicy()) {
5082 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN select-projection");
5083 if (!selectProjectionContract()) {
5087 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN pipe-poll-readiness");
5088 if (!pipePollReadiness(kernelProcess)) {
5092 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN epoll-level-oneshot-ofd-lifetime");
5093 if (!epollLevelOneShotAndOfdLifetime(kernelProcess)) {
5097 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN epoll-reordered-transition-publication");
5098 if (!epollReorderedTransitionPublication(kernelProcess)) {
5102 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN epoll-persistent-fifo-reopen-reclose");
5103 if (!epollPersistentFifoReopenReclose(kernelProcess)) {
5107 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN eventfd-counter-readiness-lifetime");
5108 if (!runHostedEventFdRegressions(kernelProcess)) {
5112 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN inotify-vfs-epoll-lifetime");
5113 if (!runHostedInotifyRegressions(kernelProcess)) {
5117 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN poll-close-reuse-cleanup");
5118 if (!pollCloseReuseCleanup(kernelProcess)) {
5122 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN posix-teardown-contention");
5123 if (!posixTeardownContention(kernelProcess)) {
5127 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN unix-bind-replacement-lifetime");
5128 if (!runHostedUnixEndpointLifetimeRegression(kernelProcess)) {
5132 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN scm-rights-datagram");
5133 if (!runHostedScmRightsRegressions(kernelProcess)) {
5137 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN scm-rights-stream");
5138 if (!runHostedScmStreamRegressions(kernelProcess)) {
5142 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN unix-stream-interruption");
5143 if (!runHostedUnixStreamInterruptionRegressions(kernelProcess)) {
5147 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN socket-zero-result-signal");
5148 if (!zeroResultWinsSignal(thread)) {
5152 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN clone-errno-lifetime");
5153 if (!cloneStateDropsParentErrnoDestination()) {
5157 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN clone-process-routing");
5158 if (!runHostedCloneRoutingRegressions(kernelProcess)) {
5162 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN clone-vm-null-stack-interrupts");
5163 if (!cloneVmNullStackPreservesInterrupts()) {
5167 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN clone-vm-detached-cached-tid");
5168 if (!cloneVmDetachedCancellationReturnsCachedTid(kernelProcess)) {
5172 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN clone-vm-terminal-start-cancellation");
5173 if (!cloneVmTerminalStartCancellation(kernelProcess)) {
5177 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN failed-pinned-module");
5178 if (!failedPinnedModuleRejectsUnload()) {
5182 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN linker-pinned-metadata");
5183 if (!linkerModuleMetadataIsPinned()) {
5187 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN filesystem-unload-policy-metadata");
5188 if (!filesystemModuleUnloadPolicyIsCorrect()) {
5192 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN runtime-pinned-cleanup");
5193 if (!runtimePinnedModuleAllowsLifecycleCleanup()) {
5197 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN module-unload-ownership");
5198 if (!moduleUnloadOwnershipIsRetryable()) {
5202 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN module-shutdown-order");
5203 if (!moduleShutdownOrderIsDependencySafe()) {
5208 static char pedigreeCModule[] =
"pedigree-c";
5209 static char lwipModule[] =
"lwip";
5210 static char networkStackModule[] =
"network-stack";
5211 const uintptr_t sigretResult = manager.
syscall(posix, PEDIGREE_SIGRET);
5212 const uintptr_t unwindResult = manager.
syscall(posix, PEDIGREE_UNWIND_SIGNAL);
5213 const uintptr_t eventReturnResult = manager.
syscall(pedigree_c, PEDIGREE_EVENT_RETURN);
5214 const uintptr_t selfUnloadResult = manager.
syscall(pedigree_c, PEDIGREE_MODULE_UNLOAD,
5215 reinterpret_cast<uintptr_t
>(pedigreeCModule));
5216 const uintptr_t stillLoadedResult = manager.
syscall(pedigree_c, PEDIGREE_MODULE_IS_LOADED,
5217 reinterpret_cast<uintptr_t
>(pedigreeCModule));
5218 const uintptr_t lwipUnloadResult =
5219 manager.
syscall(pedigree_c, PEDIGREE_MODULE_UNLOAD,
reinterpret_cast<uintptr_t
>(lwipModule));
5220 const uintptr_t lwipStillLoadedResult = manager.
syscall(pedigree_c, PEDIGREE_MODULE_IS_LOADED,
5221 reinterpret_cast<uintptr_t
>(lwipModule));
5222 const uintptr_t networkStackUnloadResult = manager.
syscall(
5223 pedigree_c, PEDIGREE_MODULE_UNLOAD,
reinterpret_cast<uintptr_t
>(networkStackModule));
5224 const uintptr_t networkStackStillLoadedResult = manager.
syscall(
5225 pedigree_c, PEDIGREE_MODULE_IS_LOADED,
reinterpret_cast<uintptr_t
>(networkStackModule));
5227 if (sigretResult !=
static_cast<uintptr_t
>(-1) || unwindResult !=
static_cast<uintptr_t
>(-1) ||
5228 eventReturnResult !=
static_cast<uintptr_t
>(-1) ||
5229 selfUnloadResult !=
static_cast<uintptr_t
>(-1) || stillLoadedResult != 1 ||
5230 lwipUnloadResult !=
static_cast<uintptr_t
>(-1) || lwipStillLoadedResult != 1 ||
5231 networkStackUnloadResult !=
static_cast<uintptr_t
>(-1) ||
5234 "HOSTED-SYSCALL-TEST: FAIL real-event-boundaries: "
5235 "a public misuse path escaped its lifetime boundary");
5239 NOTICE(
"HOSTED-SYSCALL-TEST: PASS real-event-boundaries");
5243 terminalFixture->
description() =
"hosted zero-thread POSIX shutdown fixture";
5245 if (terminalFixture->
getNumThreads() != 0 || terminalFixture->getType() != Process::Posix) {
5247 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-fixture: "
5248 "fixture was not published as an ownerless POSIX process");
5251 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-terminal-drain-fixture-published");
5253 if (!posixDuplicateInitRollbackPreservesProcessForTest(terminalFixture)) {
5255 "HOSTED-SYSCALL-TEST: FAIL posix-duplicate-init-rollback: "
5256 "an unowned duplicate initialisation retired an existing POSIX process");
5259 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-duplicate-init-rollback-preserved-process");
5263 createdFixture->
description() =
"hosted Created-thread POSIX shutdown fixture";
5265 new Thread(createdFixture, terminalCreatedFixtureEntry,
nullptr,
nullptr,
false,
true,
true);
5266 createdThread->setName(
"hosted terminal Created-thread fixture");
5269 createdFixture->getType() != Process::Posix) {
5271 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-created-fixture: "
5272 "fixture did not retain its unstarted ordinary entry");
5275 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-terminal-drain-created-fixture-published");
5277 if (!publishTerminalBlockedHandlerFixture(kernelProcess)) {
5285 hostedSyscallProfileRequested() ? hostedRunSyscallProfile() : runRegressions();
5291 TerminalBlockedHandlerContext* context = g_TerminalBlockedHandlerContext;
5292 g_TerminalBlockedHandlerContext =
nullptr;
5297 if (context->hookEntered !=
static_cast<size_t>(1) ||
5298 context->exitStaged !=
static_cast<size_t>(1) ||
5299 context->releasedByTermination !=
static_cast<size_t>(1) || context->unexpectedRelease ||
5300 context->syscallReturned) {
5302 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-blocked-handler-release: "
5303 "terminal process teardown did not release the admitted handler before module exit");
5305 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-terminal-blocked-handler-released-by-process-exit");
5311MODULE_INFO(
"hosted-syscall-smoke", &entry, &exit,
"posix",
"pedigree-c");
Memory-mapped file interface.
static Directory * fromFile(File *pF)
bool addDirectoryEntry(const String &name, File *pTarget)
Mutex & namespaceMutationLock()
AddStatus addEphemeralFile(File *pFile)
Add an ephemeral file to the directory.
void remove(const HashedStringView &s)
bool removeDirectoryEntry(const HashedStringView &name, File *expected)
virtual File * convertToFile(const DirectoryEntryMetadata &meta)
MUST_USE_RESULT bool lookupRetained(const HashedStringView &s, ChildLease &child) const
MUST_USE_RESULT LookupStatus lookupChild(const HashedStringView &s, ChildLease &child) const
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
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
virtual bool isSeekable() const
virtual uint64_t read(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true) final
virtual bool isBytewise() const
virtual ReadyMask queryReady(bool reading, bool writing)
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
virtual bool supportsReadinessNotifications() const
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
virtual ReadyMask queryReady(bool reading, bool writing)
static constexpr size_t getPageSize() PURE
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
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)
LargeStaticString & description()
MUST_USE_RESULT bool acquireThread(ThreadLease &lease, size_t n)
static bool getInterrupts()
static ProcessorInformation & information()
static void setInterrupts(bool bEnable)
void notifyReadiness(ReadyMask mask)
virtual ReadinessGenerations readinessGenerations()
static Scheduler & instance()
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
static SharedPointer< FilesystemContext > tryAdopt(FilesystemContext *ptr)
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)
void setUnwindState(UnwindType ut)
@ TerminateThread
Exit only this thread during Process exit.
bool getWaitDebugInfo(WaitDebugInfo &info)
UnwindType getUnwindState()
DebugState getDebugState(uintptr_t &address)
Process * getParent() const
size_t getStateLevel() const
MUST_USE_RESULT bool retainTrackedFile(File *pFile)
bool untrackFile(File *pFile, bool destroy=true)
Filesystem * getRootFilesystem() const
void trackFile(File *pFile)
Track a File object that exists. It is necessary to keep track of File objects, or at least those tha...
void EXPORTED_PUBLIC panic(const char *msg) NORETURN