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) {
352 const bool released = release->acquireForCompletion();
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;
476 if (release.acquireForCompletion()) {
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 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 B");
1189 startedB = workerB->
start();
1191 bool workerBCompleted =
false;
1192 for (
size_t attempt = 0; attempt < HostedAttempts && startedB; ++attempt) {
1193 if (workerBContext.returned) {
1194 workerBCompleted = workerBContext.retained ==
static_cast<size_t>(1);
1200 bool passed = startedA && workerABlocked && startedB && workerBCompleted &&
1201 !workerAContext.returned && !hook.returned && !destructions;
1203 hook.release.release();
1209 if (workerB && !startedB) {
1212 VFS::setRetainTrackedFileHookForHostedTest(
nullptr);
1213 g_TrackedFileRetainHookContext =
nullptr;
1215 passed = passed && joinedA && joinedB && hook.returned ==
static_cast<size_t>(1) &&
1216 workerAContext.retained ==
static_cast<size_t>(1) &&
1217 workerBContext.retained ==
static_cast<size_t>(1);
1220 bool secondWasFinal =
false;
1221 if (!firstWasFinal) {
1225 bool finalDestroyed =
false;
1226 if (!firstWasFinal && !secondWasFinal) {
1232 passed = passed && !firstWasFinal && !secondWasFinal && finalDestroyed &&
1233 destructions ==
static_cast<size_t>(1);
1234 if (!destructions) {
1235 repairAliasFileProbe(file, destructions);
1240 "HOSTED-SYSCALL-TEST: FAIL vfs-established-alias-serialization: "
1241 "a paused established-owner retain blocked another retain or lost ownership");
1245 NOTICE(
"HOSTED-SYSCALL-TEST: PASS vfs-established-alias-serialization");
1249enum DescriptorAliasConstruction {
1252 DescriptorPointerCopy,
1255bool descriptorEstablishedAliasLifetime(DescriptorAliasConstruction construction) {
1257 EstablishedAliasFileProbe* file =
new EstablishedAliasFileProbe(destructions);
1260 if (construction != DescriptorDirect) {
1268 if (construction == DescriptorDirect) {
1270 }
else if (construction == DescriptorCopy) {
1279 bool passed = !emergencyWasFinal && !destructions;
1282 passed = passed && destructions ==
static_cast<size_t>(1);
1283 repairAliasFileProbe(file, destructions);
1287bool establishedFileAliasLifetime() {
1288 const bool directPassed = descriptorEstablishedAliasLifetime(DescriptorDirect);
1289 const bool copyPassed = descriptorEstablishedAliasLifetime(DescriptorCopy);
1290 const bool pointerCopyPassed = descriptorEstablishedAliasLifetime(DescriptorPointerCopy);
1291 bool passed = directPassed && copyPassed && pointerCopyPassed;
1294 EstablishedAliasFileProbe* untracked =
new EstablishedAliasFileProbe(destructions);
1298 passed = passed && !descriptorPublishedFile && !destructions;
1299 repairAliasFileProbe(untracked, destructions);
1303 "HOSTED-SYSCALL-TEST: FAIL file-established-alias-lifetime: "
1304 "tracked descriptors did not retain exactly one VFS owner, or an untracked descriptor "
1305 "published a new owner");
1309 NOTICE(
"HOSTED-SYSCALL-TEST: PASS file-established-alias-lifetime");
1315 explicit ContextLifetimePath(
File* file) : m_File(file), m_Tracked(file->retainVfsReference()) {}
1316 ~ContextLifetimePath()
override {
1318 m_File->releaseVfsReference();
1320 File* node()
const override {
1323 const void* provider()
const override {
1328 static const char Provider;
1330 const bool m_Tracked;
1332const char ContextLifetimePath::Provider = 0;
1337 : m_Root(root), m_Cwd(cwd) {}
1341 result.root = m_Root;
1343 result.contextGeneration = m_Generation;
1347 if (!m_Registered || result)
1352 result = FilesystemContextOwner::adopt(pedigree_std::move(context));
1355 void retireProcessOwner()
override {
1356 assert(m_Registered);
1357 m_Registered =
false;
1369 uint64_t m_Generation = 1;
1370 bool m_Registered =
true;
1373class ContextLifetimeProcess final :
public Process {
1375 explicit ContextLifetimeProcess(
Process* parent)
1376 :
Process(DeferredPublication{}, parent, true, FilesystemContextMode::Deferred) {}
1377 void publishContext() {
1382bool processFilesystemContextLifetime(
Process* kernelProcess) {
1388 RetainedLookupFilesystem borrowedCwdFilesystem;
1389 RetainedLookupFilesystem borrowedRootFilesystem;
1390 EstablishedAliasFileProbe* cwd =
new EstablishedAliasFileProbe(cwdDestructions);
1391 EstablishedAliasFileProbe* root =
new EstablishedAliasFileProbe(rootDestructions);
1392 EstablishedAliasFileProbe* borrowedCwd =
new EstablishedAliasFileProbe(borrowedCwdDestructions);
1393 EstablishedAliasFileProbe* borrowedRoot =
new EstablishedAliasFileProbe(borrowedRootDestructions);
1394 borrowedCwd->setFilesystem(&borrowedCwdFilesystem);
1395 borrowedRoot->setFilesystem(&borrowedRootFilesystem);
1396 borrowedCwdFilesystem.setRoot(borrowedCwd);
1397 borrowedRootFilesystem.setRoot(borrowedRoot);
1401 auto* parent =
new ContextLifetimeProcess(kernelProcess);
1406 const bool installed =
1407 parent->installFilesystemContext(FilesystemContextOwner::adopt(pedigree_std::move(context)));
1409 FATAL(
"Hosted context fixture could not prepare its process owner");
1410 parent->publishContext();
1412 const bool snapshotted = installed && retainedContext->snapshot(snapshot);
1413 File* retainedCwd = snapshot.cwd ? snapshot.cwd->node() :
nullptr;
1414 File* retainedRoot = snapshot.root ? snapshot.root->node() :
nullptr;
1417 auto childContext = child->acquireFilesystemContext();
1418 const bool childInherited = childContext && childContext.
get() != retainedContext.
get() &&
1419 childContext->snapshot(inherited) && inherited.cwd == snapshot.cwd &&
1420 inherited.root == snapshot.root;
1422 childContext.reset();
1426 static_cast<ContextLifetimeProvider*
>(retainedContext.
get())
1431 childContext = child->acquireFilesystemContext();
1432 const bool childUnchanged = childContext && childContext->snapshot(inherited) &&
1433 inherited.cwd == snapshot.cwd && inherited.root == snapshot.root;
1435 childContext.reset();
1437 const bool parentReplaced = retainedContext->snapshot(replacement) && replacement.cwd &&
1438 replacement.root && replacement.cwd->node() == borrowedCwd &&
1439 replacement.root->node() == borrowedRoot &&
1440 replacement.contextGeneration > snapshot.contextGeneration;
1444 const bool snapshotsHeldAcrossReplacement =
1445 retainedCwd == cwd && retainedRoot == root && !cwdDestructions && !rootDestructions;
1447 const bool inheritedReferencesReleased =
1448 cwdDestructions ==
static_cast<size_t>(1) && rootDestructions ==
static_cast<size_t>(1);
1450 const bool retired = !retainedContext->snapshot(snapshot);
1451 retainedContext.
reset();
1452 const bool borrowedReferencesSurvived = !borrowedCwdDestructions && !borrowedRootDestructions;
1454 delete borrowedRoot;
1457 installed && snapshotted && childInherited && childUnchanged && parentReplaced && retired &&
1458 !cwdNamespaceWasFinal && !rootNamespaceWasFinal && !borrowedCwdWasPublished &&
1459 !borrowedRootWasPublished && snapshotsHeldAcrossReplacement && inheritedReferencesReleased &&
1460 borrowedReferencesSurvived && borrowedCwdDestructions ==
static_cast<size_t>(1) &&
1461 borrowedRootDestructions ==
static_cast<size_t>(1);
1464 "HOSTED-SYSCALL-TEST: FAIL process-filesystem-context-lifetime: "
1465 "fork, replacement, or destruction mismanaged a cwd/root VFS owner");
1469 NOTICE(
"HOSTED-SYSCALL-TEST: PASS process-filesystem-context-lifetime");
1473enum MappingAliasConstruction {
1479bool mappingEstablishedAliasLifetime(MappingAliasConstruction construction) {
1482 EstablishedAliasFileProbe* file =
new EstablishedAliasFileProbe(destructions);
1485 if (construction != MappingDirect) {
1486 source =
new MemoryMappedFile(0x100000, pageSize * 2, 0, file,
false, MemoryMappedObject::Read);
1493 if (construction == MappingDirect) {
1494 alias =
new MemoryMappedFile(0x100000, pageSize, 0, file,
false, MemoryMappedObject::Read);
1495 }
else if (construction == MappingClone) {
1496 alias = source->
clone();
1498 alias = source->
split(0x100000 + pageSize);
1504 bool passed = !emergencyWasFinal && !destructions;
1507 passed = passed && destructions ==
static_cast<size_t>(1);
1508 repairAliasFileProbe(file, destructions);
1512bool establishedMappingAliasLifetime() {
1513 const bool directPassed = mappingEstablishedAliasLifetime(MappingDirect);
1514 const bool clonePassed = mappingEstablishedAliasLifetime(MappingClone);
1515 const bool splitPassed = mappingEstablishedAliasLifetime(MappingSplit);
1516 bool passed = directPassed && clonePassed && splitPassed;
1520 EstablishedAliasFileProbe* untracked =
new EstablishedAliasFileProbe(destructions);
1522 new MemoryMappedFile(0x100000, pageSize, 0, untracked,
false, MemoryMappedObject::Read);
1525 passed = passed && !mappingPublishedFile && !destructions;
1526 repairAliasFileProbe(untracked, destructions);
1530 "HOSTED-SYSCALL-TEST: FAIL mmap-established-alias-lifetime: "
1531 "tracked mappings did not retain a VFS owner, or an untracked mapping published a new "
1536 NOTICE(
"HOSTED-SYSCALL-TEST: PASS mmap-established-alias-lifetime");
1540bool munmapUsesTargetPageGeometry(
Thread* thread) {
1544 const int misalignedResult = posix_munmap(
reinterpret_cast<void*
>(pageSize / 2), pageSize);
1545 const bool rejectedMisaligned =
1546 misalignedResult == -1 && thread->
getErrno() == Error::InvalidArgument;
1549 const int alignedResult = posix_munmap(
reinterpret_cast<void*
>(pageSize), pageSize);
1550 const bool acceptedAligned = alignedResult == 0 && !thread->
getErrno();
1553 if (!rejectedMisaligned || !acceptedAligned) {
1555 "HOSTED-SYSCALL-TEST: FAIL munmap-target-page-geometry: "
1556 "munmap did not validate addresses against the target page size");
1560 NOTICE(
"HOSTED-SYSCALL-TEST: PASS munmap-target-page-geometry");
1564bool mappingManagerSplitLifetime(
Process* process,
bool exactSuffix) {
1566 const size_t mappingLength = pageSize * 3;
1567 uintptr_t address = 0;
1568 if (!process->allocateUserRange(Process::UserRegion::Normal, mappingLength, address)) {
1573 EstablishedAliasFileProbe* file =
new EstablishedAliasFileProbe(destructions);
1577 uintptr_t mappedAddress = address;
1579 file, mappedAddress, mappingLength, MemoryMappedObject::Read);
1580 bool passed = mapping && mappedAddress == address;
1583 address + pageSize, exactSuffix ? pageSize * 2 : pageSize);
1585 size_t removedTail = 1;
1589 passed = passed && removedMiddleOrSuffix == 1 && removedPrefix == 1 && removedTail == 1;
1593 process->freeUserRange(Process::UserRegion::Normal, address, mappingLength);
1597 passed && !namespaceWasFinal && emergencyWasFinal && destructions ==
static_cast<size_t>(1);
1598 repairAliasFileProbe(file, destructions);
1602bool mappingManagerSplitLifetime(
Process* process) {
1603 const bool exactSuffixPassed = mappingManagerSplitLifetime(process,
true);
1604 const bool middlePassed = mappingManagerSplitLifetime(process,
false);
1605 const bool passed = exactSuffixPassed && middlePassed;
1608 "HOSTED-SYSCALL-TEST: FAIL mmap-split-alias-lifetime: "
1609 "an exact-suffix or middle removal leaked a file-backed mapping owner");
1613 NOTICE(
"HOSTED-SYSCALL-TEST: PASS mmap-split-alias-lifetime");
1617bool posixPathLookupLifetime(
Process* kernelProcess) {
1620 VFS::HostedRootViewScope
fixture;
1622 if (!
fixture.open(testFilesystem)) {
1623 delete testFilesystem;
1627 const bool rootInstalled =
fixture.view()->bootRootPath(rootPath);
1628 const String name(
"hosted-established-alias-path-cache");
1629 const String path(
"/hosted-established-alias-path-cache");
1630 const bool created = rootInstalled &&
fixture.view()->createFile(rootPath, name, 0600);
1633 new PosixProcess(kernelProcess,
true, Process::FilesystemContextMode::Deferred);
1635 process->setSubsystem(subsystem);
1636 subsystem->
setAbi(PosixSubsystem::LinuxAbi);
1637 const bool contextInstalled =
fixture.installContext(*process);
1639 File* original = created && contextInstalled
1640 ? subsystem->findFileRetained(path, originalLease, rootPath)
1642 const bool originalRemoved = original &&
fixture.view()->remove(rootPath, name, original);
1643 const bool recreated = originalRemoved &&
fixture.view()->createFile(rootPath, name, 0600);
1645 File* replacement =
nullptr;
1648 Directory::LookupStatus::Found)
1649 replacement = replacementLease.get();
1650 File* resolved = recreated ? subsystem->findFileRetained(path, resolvedLease, rootPath) :
nullptr;
1651 const bool resolvedReplacement =
1652 replacement && replacement != original && resolved == replacement;
1653 const bool pathCleaned = replacement &&
fixture.view()->remove(rootPath, name, replacement);
1654 replacementLease.reset();
1655 resolvedLease.reset();
1656 originalLease.reset();
1659 const bool rootRestored =
fixture.close();
1661 FATAL(
"Hosted pathname fixture retained owners after teardown");
1662 const bool previousRestored =
1664 delete testFilesystem;
1666 const bool passed = rootInstalled && contextInstalled && created && original && originalRemoved &&
1667 recreated && resolvedReplacement && pathCleaned && rootRestored &&
1671 "HOSTED-SYSCALL-TEST: FAIL posix-path-lookup-lifetime: "
1672 "a removed pathname resolved to its retired cached File object");
1676 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-path-lookup-lifetime");
1686 m_Notifications(notifications),
1687 m_Destructions(destructions),
1690 ~PollGenerationProbe()
override {
1691 m_Destructions += 1;
1694 int connect(
const struct sockaddr_storage*, socklen_t)
override {
1706 int listen(
int)
override {
1710 int bind(
const struct sockaddr_storage*, socklen_t)
override {
1714 int accept(
struct sockaddr_storage*, socklen_t*,
int,
DescriptorLease*)
override {
1718 int getpeername(
struct sockaddr_storage*, socklen_t*)
override {
1722 int getsockname(
struct sockaddr_storage*, socklen_t*)
override {
1726 int setsockopt(
int,
int,
const void*, socklen_t)
override {
1730 int getsockopt(
int,
int,
void*, socklen_t*)
override {
1734 ReadyMask
queryReady(
bool reading,
bool writing)
override {
1736 ReadyMask ready = ReadyNone;
1737 if (reading && m_Ready) {
1741 ready |= ReadyWrite;
1746 void makeReadable() {
1748 m_Notifications += 1;
1759struct DescriptorCloseContext {
1761 : subsystem(subsystem),
1778int holdDescriptorAcrossBlock(
void* parameter) {
1779 DescriptorCloseContext* context =
reinterpret_cast<DescriptorCloseContext*
>(parameter);
1781 context->acquired = context->subsystem->acquireFileDescriptor(context->fd, descriptor) ? 1 : 0;
1782 context->entered += 1;
1784 if (!context->release.acquireForCompletion()) {
1785 context->returned += 1;
1789 if (descriptor && descriptor->
fd == context->fd) {
1790 context->usedAfterClose += 1;
1792 context->returned += 1;
1796bool descriptorClosePinning(
Process* kernelProcess) {
1797 constexpr size_t DescriptorNumber = 37;
1800 process->setSubsystem(subsystem);
1803 DescriptorRetirementProbe* probe =
new DescriptorRetirementProbe(destructions);
1804 probe->fd = DescriptorNumber;
1807 DescriptorCloseContext context(subsystem, DescriptorNumber);
1809 new Thread(kernelProcess, holdDescriptorAcrossBlock, &context,
nullptr,
false,
true,
true);
1810 worker->setName(
"hosted descriptor pin holder");
1812 bool passed =
worker->start();
1813 bool blocked =
false;
1814 for (
size_t attempt = 0; attempt < HostedAttempts && passed; ++attempt) {
1816 if (context.entered &&
worker->getWaitDebugInfo(info) && info.queue && info.queued &&
1817 info.channelOwner == &context.release &&
worker->getStatus() == Thread::Sleeping) {
1824 passed = passed && blocked && context.acquired == 1;
1827 const bool closed = closeAcquired && subsystem->
closeFileDescriptor(DescriptorNumber, closing);
1829 passed = passed && closed;
1835 context.release.release();
1836 passed =
worker->join() && passed;
1837 passed = passed && context.returned == 1 && context.usedAfterClose == 1 && destructions == 1;
1843 "HOSTED-SYSCALL-TEST: FAIL descriptor-close-pinning: "
1844 "close did not unpublish immediately while retaining the active "
1849 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-close-pinning");
1853bool descriptorCloseGeneration(
Process* kernelProcess) {
1854 constexpr size_t DescriptorNumber = 38;
1857 process->setSubsystem(subsystem);
1861 DescriptorRetirementProbe* oldDescriptor =
new DescriptorRetirementProbe(oldDestructions);
1862 oldDescriptor->fd = DescriptorNumber;
1863 oldDescriptor->setOffset(1);
1869 DescriptorRetirementProbe* replacement =
new DescriptorRetirementProbe(replacementDestructions);
1870 replacement->fd = DescriptorNumber;
1871 replacement->setOffset(2);
1877 oldDestructions == 0 && replacementDestructions == 0;
1884 passed = passed && oldDestructions == 1 && replacementDestructions == 0;
1886 const bool replacementClosed = subsystem->
closeFileDescriptor(DescriptorNumber, replacementLease);
1888 passed = passed && replacementClosed &&
1890 replacementDestructions == 0;
1891 replacementLease.reset();
1892 passed = passed && replacementDestructions == 1;
1898 "HOSTED-SYSCALL-TEST: FAIL descriptor-close-generation: "
1899 "an old close removed a reused descriptor generation");
1903 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-close-generation");
1907struct DescriptorPositionContext {
1909 : descriptor(descriptor), entered(0), acquired(0), observed(0) {}
1917class DescriptorPositionFile final :
public File {
1919 explicit DescriptorPositionFile(
bool seekable)
1920 :
File(
String(
"position-policy"), 0, 0, 0, 1, nullptr, 0, nullptr),
1921 m_Seekable(seekable),
1922 m_ReadOffset(~static_cast<uint64_t>(0)),
1923 m_WriteOffset(~static_cast<uint64_t>(0)),
1924 m_ReadCanBlock(true),
1925 m_WriteCanBlock(true) {}
1931 uint64_t readOffset()
const {
1932 return m_ReadOffset;
1935 uint64_t writeOffset()
const {
1936 return m_WriteOffset;
1939 bool readCanBlock()
const {
1940 return m_ReadCanBlock;
1943 bool writeCanBlock()
const {
1944 return m_WriteCanBlock;
1952 uint64_t
readBytewise(uint64_t location, uint64_t size, uintptr_t,
bool canBlock)
override {
1953 m_ReadOffset = location;
1954 m_ReadCanBlock = canBlock;
1958 uint64_t
writeBytewise(uint64_t location, uint64_t size, uintptr_t,
bool canBlock)
override {
1959 m_WriteOffset = location;
1960 m_WriteCanBlock = canBlock;
1966 uint64_t m_ReadOffset;
1967 uint64_t m_WriteOffset;
1968 bool m_ReadCanBlock;
1969 bool m_WriteCanBlock;
1972class DescriptorAppendFile final :
public File {
1974 DescriptorAppendFile()
1975 :
File(
String(
"append-policy"), 0, 0, 0, 1, nullptr, 10, nullptr),
1977 m_WriteOffsets{0, 0, 0} {}
1979 uint64_t writeOffset(
size_t index)
const {
1980 return m_WriteOffsets[index];
1983 size_t writeCount()
const {
1984 return m_WriteCount;
1992 uint64_t
writeBytewise(uint64_t location, uint64_t size, uintptr_t,
bool)
override {
1993 if (m_WriteCount < 3) {
1994 m_WriteOffsets[m_WriteCount] = location;
1997 if (location + size > getSize()) {
1998 setSize(location + size);
2004 size_t m_WriteCount;
2005 uint64_t m_WriteOffsets[3];
2008class ConcurrentDescriptorAppendFile final :
public File {
2010 ConcurrentDescriptorAppendFile()
2011 :
File(
String(
"concurrent-append-policy"), 0, 0, 0, 1, nullptr, 10, nullptr),
2012 m_FirstWriteEntered(0, false),
2013 m_ReleaseFirstWrite(0, false),
2015 m_WriteOffsets{0, 0} {}
2017 bool waitForFirstWrite() {
2018 return m_FirstWriteEntered.acquireForCompletion();
2021 void releaseFirstWrite() {
2022 m_ReleaseFirstWrite.release();
2025 size_t writeCount()
const {
2026 return m_WriteCount;
2029 uint64_t writeOffset(
size_t index)
const {
2030 return m_WriteOffsets[index];
2038 uint64_t
writeBytewise(uint64_t location, uint64_t size, uintptr_t,
bool)
override {
2039 const size_t slot = (m_WriteCount += 1) - 1;
2041 m_WriteOffsets[slot] = location;
2044 m_FirstWriteEntered.release();
2045 if (!m_ReleaseFirstWrite.acquireForCompletion()) {
2049 if (location + size > getSize()) {
2050 setSize(location + size);
2059 uint64_t m_WriteOffsets[2];
2062struct ConcurrentDescriptorAppendContext {
2063 explicit ConcurrentDescriptorAppendContext(
FileDescriptor* descriptor)
2064 : descriptor(descriptor), entered(0), returned(0), result(0) {}
2072int appendThroughDescriptor(
void* parameter) {
2073 ConcurrentDescriptorAppendContext* context =
2074 reinterpret_cast<ConcurrentDescriptorAppendContext*
>(parameter);
2076 context->entered += 1;
2077 context->result = context->descriptor->write(1,
reinterpret_cast<uintptr_t
>(&
byte));
2078 context->returned += 1;
2079 return context->result == 1 ? 0 : 1;
2082bool concurrentDescriptorAppendSerialization(
Process* kernelProcess,
bool positionedSecond) {
2083 ConcurrentDescriptorAppendFile file;
2086 positionedSecond ? O_WRONLY : O_WRONLY | O_APPEND);
2087 ConcurrentDescriptorAppendContext firstContext(&first);
2088 ConcurrentDescriptorAppendContext secondContext(&second);
2091 new Thread(kernelProcess, appendThroughDescriptor, &firstContext,
nullptr,
false,
true,
true);
2092 Thread* secondWorker =
new Thread(kernelProcess, appendThroughDescriptor, &secondContext,
nullptr,
2094 firstWorker->setName(
"hosted first independent append");
2095 if (positionedSecond) {
2096 secondWorker->setName(
"hosted positioned append race");
2098 secondWorker->setName(
"hosted second independent append");
2101 const bool firstStarted = firstWorker->
start();
2102 const bool firstEntered = firstStarted && file.waitForFirstWrite();
2103 const bool secondStarted = firstEntered && secondWorker->
start();
2104 bool secondBlocked =
false;
2105 for (
size_t attempt = 0; attempt < HostedAttempts && secondStarted; ++attempt) {
2107 uintptr_t debugAddress = 0;
2108 if (secondContext.entered && !secondContext.returned && file.writeCount() == 1 &&
2110 secondWorker->
getDebugState(debugAddress) == Thread::SemWait) {
2111 secondBlocked =
true;
2117 file.releaseFirstWrite();
2120 if (!firstStarted) {
2123 if (!secondStarted) {
2124 delete secondWorker;
2127 const bool separateDescriptions =
2129 const uint64_t expectedSecondOffset = positionedSecond ? 20 : 11;
2130 const uint64_t expectedSize = positionedSecond ? 21 : 12;
2131 const uint64_t expectedSecondPosition = positionedSecond ? 21 : 12;
2132 return firstStarted && firstEntered && secondStarted && secondBlocked && firstJoined &&
2133 secondJoined && firstContext.returned == 1 && secondContext.returned == 1 &&
2134 firstContext.result == 1 && secondContext.result == 1 && separateDescriptions &&
2135 file.writeCount() == 2 && file.writeOffset(0) == 10 &&
2136 file.writeOffset(1) == expectedSecondOffset && file.getSize() == expectedSize &&
2137 first.getOffset() == 11 && second.
getOffset() == expectedSecondPosition;
2140bool independentDescriptorAppendSerialization(
Process* kernelProcess) {
2141 return concurrentDescriptorAppendSerialization(kernelProcess,
false) &&
2142 concurrentDescriptorAppendSerialization(kernelProcess,
true);
2145bool descriptorOpenFileDescriptionState() {
2146 DescriptorPositionFile file(
true);
2153 source.addStatusFlag(O_NONBLOCK | O_CLOEXEC);
2154 bool passed = source.getFlags() == FD_CLOEXEC && alias.getFlags() == 0 &&
2155 source.getStatusFlags() == (O_RDWR | O_NONBLOCK) &&
2156 alias.getStatusFlags() == (O_RDWR | O_NONBLOCK) &&
2157 sourceDescription == aliasDescription && sourceDescription->getFile() == &file &&
2158 !sourceDescription->getNetworkImpl() &&
2159 sourceDescription->descriptorOwnerCount() == 2;
2163 passed = passed && sourceDescription->descriptorOwnerCount() == 3;
2165 passed = passed && sourceDescription->descriptorOwnerCount() == 2;
2167 alias.setStatusFlags(O_APPEND | O_CLOEXEC);
2168 passed = passed && source.getStatusFlags() == (O_RDWR | O_APPEND) &&
2169 alias.getStatusFlags() == (O_RDWR | O_APPEND) && source.getFlags() == FD_CLOEXEC &&
2170 alias.getFlags() == 0;
2172 source.removeStatusFlag(O_APPEND);
2173 passed = passed && source.getStatusFlags() == O_RDWR && alias.getStatusFlags() == O_RDWR;
2177 "HOSTED-SYSCALL-TEST: FAIL descriptor-open-file-description-state: "
2178 "status flags were descriptor-local, access mode was lost, or CLOEXEC entered shared "
2183 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-open-file-description-state");
2187bool descriptorOpenFileDescriptionLifetime() {
2189 EstablishedAliasFileProbe* file =
new EstablishedAliasFileProbe(fileDestructions);
2197 bool passed = !baselineWasFinal && !fileDestructions &&
2198 fileDescription->descriptorOwnerCount() == 0 && fileDescription->getFile() == file;
2199 fileDescription.
reset();
2200 passed = passed && fileDestructions == 1;
2201 repairAliasFileProbe(file, fileDestructions);
2208 new PollGenerationProbe(registrations, unpolls, networkDestructions));
2214 delete socketDescriptor;
2215 passed = passed && !networkDestructions && socketDescription->descriptorOwnerCount() == 0 &&
2216 socketDescription->getNetworkImpl();
2217 socketDescription.
reset();
2218 passed = passed && networkDestructions == 1;
2222 "HOSTED-SYSCALL-TEST: FAIL descriptor-open-file-description-lifetime: "
2223 "an OFD lease did not retain its target independently of descriptor aliases");
2227 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-open-file-description-lifetime");
2231bool descriptorAppendPolicy(
Process* kernelProcess) {
2233 DescriptorAppendFile file;
2237 const bool firstWrite = source.write(3,
reinterpret_cast<uintptr_t
>(&
byte)) == 3;
2238 source.setOffset(1);
2239 const bool secondWrite = alias.write(2,
reinterpret_cast<uintptr_t
>(&
byte)) == 2;
2241 alias.setStatusFlags(0);
2242 source.setOffset(5);
2243 const bool positionedWrite = source.write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
2245 const bool independentSerialization = independentDescriptorAppendSerialization(kernelProcess);
2246 const bool passed = firstWrite && secondWrite && positionedWrite && independentSerialization &&
2247 file.writeCount() == 3 && file.writeOffset(0) == 10 &&
2248 file.writeOffset(1) == 13 && file.writeOffset(2) == 5 &&
2249 file.getSize() == 15 && source.getOffset() == 6 && alias.getOffset() == 6 &&
2250 source.getStatusFlags() == O_WRONLY;
2253 "HOSTED-SYSCALL-TEST: FAIL descriptor-append-policy: "
2254 "append did not serialize EOF selection across open descriptions or update the shared "
2259 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-append-policy");
2263bool descriptorNonblockingPolicy() {
2265 DescriptorPositionFile sequential(
false);
2266 FileDescriptor source(&sequential, 0, 0, 0, O_RDWR | O_NONBLOCK);
2269 const bool nonblockingRead = source.read(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
2270 const bool nonblockingWrite = source.write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
2271 bool passed = nonblockingRead && nonblockingWrite && !sequential.readCanBlock() &&
2272 !sequential.writeCanBlock();
2274 alias.removeStatusFlag(O_NONBLOCK);
2275 const bool blockingRead = source.read(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
2276 const bool blockingWrite = source.write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
2277 passed = passed && blockingRead && blockingWrite && sequential.readCanBlock() &&
2278 sequential.writeCanBlock() && source.getStatusFlags() == O_RDWR &&
2279 alias.getStatusFlags() == O_RDWR;
2283 "HOSTED-SYSCALL-TEST: FAIL descriptor-nonblocking-policy: "
2284 "O_NONBLOCK did not reach file I/O or did not propagate across aliases");
2288 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-nonblocking-policy");
2292bool descriptorPositionPolicy() {
2294 DescriptorPositionFile seekable(
true);
2296 const bool positionedPassed = positioned.read(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1 &&
2297 positioned.write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1 &&
2298 seekable.readOffset() == 40 && seekable.writeOffset() == 41 &&
2299 positioned.getOffset() == 42;
2301 DescriptorPositionFile sequential(
false);
2303 const bool unpositionedPassed = unpositioned.read(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1 &&
2304 unpositioned.write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1 &&
2305 sequential.readOffset() == 0 && sequential.writeOffset() == 0 &&
2306 unpositioned.getOffset() == 40;
2308 if (!positionedPassed || !unpositionedPassed) {
2310 "HOSTED-SYSCALL-TEST: FAIL descriptor-position-policy: "
2311 "non-seekable I/O consumed a file position");
2315 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-position-policy");
2319int advanceDescriptorPosition(
void* parameter) {
2320 DescriptorPositionContext* context =
reinterpret_cast<DescriptorPositionContext*
>(parameter);
2321 context->entered += 1;
2323 context->observed = position.offset();
2324 position.advanceOffset(1);
2325 context->acquired += 1;
2329bool descriptorPositionAliasSerialization(
Process* kernelProcess) {
2333 DescriptorPositionContext context(&alias);
2336 new Thread(kernelProcess, advanceDescriptorPosition, &context,
nullptr,
false,
true,
true);
2337 worker->setName(
"hosted descriptor position alias");
2339 bool started =
false;
2340 bool queued =
false;
2343 started =
worker->start();
2344 for (
size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
2346 uintptr_t debugAddress = 0;
2347 if (context.entered && !context.acquired &&
worker->getWaitDebugInfo(info) && info.queued &&
2348 worker->getDebugState(debugAddress) == Thread::SemWait) {
2354 position.setOffset(41);
2357 const bool joined = started &&
worker->joinForCompletion();
2361 const bool passed = started && queued && joined && context.acquired == 1 &&
2362 context.observed == 41 && source.
getOffset() == 42 && alias.getOffset() == 42;
2365 "HOSTED-SYSCALL-TEST: FAIL descriptor-position-alias-serialization: "
2366 "duplicated descriptors did not serialize access to their shared offset");
2370 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-position-alias-serialization");
2374class VectorWriteFile final :
public File {
2377 :
File(
String(
"vector-write-policy"), 0, 0, 0, 1, nullptr, 0, nullptr),
2378 m_FirstWriteEntered(0, false),
2379 m_ReleaseFirstWrite(0, false),
2381 m_WriteOffsets{0, 0, 0},
2382 m_WriteValues{0, 0, 0} {}
2384 bool waitForFirstWrite() {
2385 return m_FirstWriteEntered.acquireForCompletion();
2388 void releaseFirstWrite() {
2389 m_ReleaseFirstWrite.release();
2392 size_t writeCount()
const {
2393 return m_WriteCount;
2396 uint64_t writeOffset(
size_t index)
const {
2397 return m_WriteOffsets[index];
2400 char writeValue(
size_t index)
const {
2401 return m_WriteValues[index];
2409 uint64_t
writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
bool)
override {
2410 const size_t slot = (m_WriteCount += 1) - 1;
2412 m_WriteOffsets[slot] = location;
2413 m_WriteValues[slot] = size ? *
reinterpret_cast<const char*
>(buffer) : 0;
2416 m_FirstWriteEntered.release();
2417 if (!m_ReleaseFirstWrite.acquireForCompletion()) {
2421 if (location + size > getSize()) {
2422 setSize(location + size);
2431 uint64_t m_WriteOffsets[3];
2432 char m_WriteValues[3];
2435struct VectorWriteContext {
2436 explicit VectorWriteContext(
size_t descriptor)
2437 : descriptor(descriptor), result(-2), error(0), returned(0) {}
2445int writeThroughVector(
void* parameter) {
2446 VectorWriteContext* context =
reinterpret_cast<VectorWriteContext*
>(parameter);
2448 char bytes[2] = {
'a',
'b'};
2449 struct iovec vectors[2] = {{&bytes[0], 1}, {&bytes[1], 1}};
2451 context->result = posix_writev(
static_cast<int>(context->descriptor), vectors, 2);
2452 context->error = thread->
getErrno();
2453 context->returned += 1;
2457int writeThroughAlias(
void* parameter) {
2458 VectorWriteContext* context =
reinterpret_cast<VectorWriteContext*
>(parameter);
2462 context->result = posix_write(
static_cast<int>(context->descriptor), &
byte, 1,
false);
2463 context->error = thread->
getErrno();
2464 context->returned += 1;
2468bool descriptorVectorIoSerialization(
Process* kernelProcess) {
2469 constexpr size_t SourceDescriptor = 74;
2470 constexpr size_t AliasDescriptor = 75;
2473 process->setSubsystem(subsystem);
2475 VectorWriteFile original;
2476 DescriptorPositionFile replacement(
true);
2479 alias->
fd = AliasDescriptor;
2483 VectorWriteContext vectorContext(SourceDescriptor);
2484 VectorWriteContext aliasContext(AliasDescriptor);
2486 new Thread(process, writeThroughVector, &vectorContext,
nullptr,
false,
true,
true);
2488 new Thread(process, writeThroughAlias, &aliasContext,
nullptr,
false,
true,
true);
2489 vectorWorker->setName(
"hosted vector write generation");
2490 aliasWorker->setName(
"hosted vector write alias");
2492 const bool vectorStarted = vectorWorker->
start();
2493 const bool firstEntered = vectorStarted && original.waitForFirstWrite();
2494 const bool aliasStarted = firstEntered && aliasWorker->
start();
2495 bool aliasBlocked =
false;
2496 for (
size_t attempt = 0; attempt < HostedAttempts && aliasStarted; ++attempt) {
2498 uintptr_t debugAddress = 0;
2499 if (!aliasContext.returned && original.writeCount() == 1 &&
2501 aliasWorker->
getDebugState(debugAddress) == Thread::SemWait) {
2502 aliasBlocked =
true;
2510 const bool sourceClosed =
2512 closingSource.reset();
2514 new FileDescriptor(&replacement, 0, SourceDescriptor, 0, O_WRONLY);
2517 original.releaseFirstWrite();
2520 if (!vectorStarted) {
2521 delete vectorWorker;
2523 if (!aliasStarted) {
2527 bool passed = vectorStarted && firstEntered && aliasStarted && aliasBlocked && sourceClosed &&
2528 vectorJoined && aliasJoined && vectorContext.returned == 1 &&
2529 vectorContext.result == 2 && vectorContext.error == 0 &&
2530 aliasContext.returned == 1 && aliasContext.result == 1 && aliasContext.error == 0 &&
2531 original.writeCount() == 2 && original.writeOffset(0) == 0 &&
2532 original.writeOffset(1) == 2 && original.writeValue(0) ==
'a' &&
2533 original.writeValue(1) ==
'c' && original.getSize() == 3 &&
2534 replacement.writeOffset() == ~static_cast<uint64_t>(0);
2538 const bool aliasClosed =
2540 closingAlias.reset();
2542 const bool replacementAcquired =
2544 const bool replacementClosed =
2545 replacementAcquired && subsystem->
closeFileDescriptor(SourceDescriptor, closingReplacement);
2546 closingReplacement.reset();
2547 passed = passed && aliasClosed && replacementClosed;
2552 "HOSTED-SYSCALL-TEST: FAIL descriptor-vector-io-serialization: "
2553 "writev switched descriptor generations or released the shared offset between vectors");
2557 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-vector-io-serialization");
2561struct DescriptorDupContractContext {
2562 DescriptorDupContractContext(
PosixSubsystem* subsystem,
size_t sourceFd,
size_t occupiedFd,
2566 : subsystem(subsystem),
2568 occupiedFd(occupiedFd),
2570 sourceDescription(sourceDescription),
2571 occupiedDescription(occupiedDescription),
2572 duplicateResult(-2),
2574 occupiedIntact(false),
2575 duplicateAliasesSource(false),
2577 duplicateClosed(false),
2578 ordinaryAllocation(static_cast<size_t>(-1)),
2579 sameInvalidResult(-2),
2580 sameInvalidError(0),
2589 int duplicateResult;
2591 bool occupiedIntact;
2592 bool duplicateAliasesSource;
2594 bool duplicateClosed;
2595 size_t ordinaryAllocation;
2596 int sameInvalidResult;
2597 int sameInvalidError;
2601int exerciseDescriptorDupContract(
void* parameter) {
2602 DescriptorDupContractContext* context =
2603 reinterpret_cast<DescriptorDupContractContext*
>(parameter);
2607 context->duplicateResult = posix_fcntl(
static_cast<int>(context->sourceFd), F_DUPFD,
2608 reinterpret_cast<void*
>(context->minimum));
2609 context->duplicateError = thread->
getErrno();
2612 if (context->subsystem->acquireFileDescriptor(context->occupiedFd, occupied)) {
2613 context->occupiedIntact =
2619 if (context->duplicateResult >= 0 &&
2620 context->subsystem->acquireFileDescriptor(context->duplicateResult, duplicate)) {
2621 context->duplicateAliasesSource =
2623 context->duplicateFlags = duplicate->
getFlags();
2624 context->duplicateClosed = context->subsystem->closeFileDescriptor(
2625 static_cast<size_t>(context->duplicateResult), duplicate);
2629 context->ordinaryAllocation = context->subsystem->getFd();
2631 constexpr int InvalidDescriptor = 80;
2633 context->sameInvalidResult = posix_dup2(InvalidDescriptor, InvalidDescriptor);
2634 context->sameInvalidError = thread->
getErrno();
2635 context->returned += 1;
2639bool descriptorDupContract(
Process* kernelProcess) {
2640 constexpr size_t SourceDescriptor = 70;
2641 constexpr size_t MinimumDescriptor = 72;
2642 constexpr size_t ExpectedDescriptor = 73;
2646 process->setSubsystem(subsystem);
2650 new FileDescriptor(sourceFile, 0, SourceDescriptor, FD_CLOEXEC, O_RDONLY);
2655 DescriptorDupContractContext context(subsystem, SourceDescriptor, MinimumDescriptor,
2659 new Thread(process, exerciseDescriptorDupContract, &context,
nullptr,
false,
true,
true);
2660 worker->setName(
"hosted descriptor duplication contract");
2661 const bool started =
worker->start();
2662 const bool joined = started &&
worker->joinForCompletion();
2668 started && joined && context.returned == 1 &&
2669 context.duplicateResult ==
static_cast<int>(ExpectedDescriptor) &&
2670 context.duplicateError == 0 && context.occupiedIntact && context.duplicateAliasesSource &&
2671 context.duplicateFlags == 0 && context.duplicateClosed && context.ordinaryAllocation == 0 &&
2672 context.sameInvalidResult == -1 && context.sameInvalidError == Error::BadFileDescriptor;
2675 context.sourceDescription.reset();
2676 context.occupiedDescription.reset();
2678 delete occupiedFile;
2682 "HOSTED-SYSCALL-TEST: FAIL descriptor-dup-contract: "
2683 "F_DUPFD replaced an occupied descriptor, hid a lower hole, or dup2 accepted an invalid "
2688 NOTICE(
"HOSTED-SYSCALL-TEST: PASS descriptor-dup-contract");
2692struct PollCloseReuseContext {
2693 explicit PollCloseReuseContext(
size_t fd)
2694 : descriptor{static_cast<int>(fd), POLLIN, 0}, result(-2), entered(0), returned(0) {}
2696 struct pollfd descriptor;
2702bool selectProjectionContract() {
2703 constexpr unsigned int ReadResult = 1U;
2704 constexpr unsigned int WriteResult = 1U << 1;
2705 constexpr unsigned int ExceptionalResult = 1U << 2;
2706 constexpr unsigned int OneReady = 1U << 8;
2707 constexpr unsigned int TwoReady = 2U << 8;
2708 constexpr unsigned int ThreeReady = 3U << 8;
2710 const unsigned int hangup = posixSelectProjectionForTest(POLLHUP,
true,
true,
true);
2711 const unsigned int error = posixSelectProjectionForTest(POLLERR,
true,
true,
true);
2712 const unsigned int priority = posixSelectProjectionForTest(POLLPRI,
true,
true,
true);
2713 const unsigned int all =
2714 posixSelectProjectionForTest(POLLIN | POLLOUT | POLLPRI,
true,
true,
true);
2715 const unsigned int writeOnlyError = posixSelectProjectionForTest(POLLERR,
false,
true,
false);
2717 const bool projectionsPassed =
2718 hangup == (OneReady | ReadResult) && error == (TwoReady | ReadResult | WriteResult) &&
2719 priority == (OneReady | ExceptionalResult) &&
2720 all == (ThreeReady | ReadResult | WriteResult | ExceptionalResult) &&
2721 writeOnlyError == (OneReady | WriteResult);
2723 if (!projectionsPassed) {
2725 "HOSTED-SYSCALL-TEST: FAIL select-projection: "
2726 "readiness projection or return-bit counting was incorrect");
2730 NOTICE(
"HOSTED-SYSCALL-TEST: PASS select-projection");
2734struct PipePollReadinessContext {
2735 PipePollReadinessContext(
size_t readFd,
size_t writeFd)
2738 pollEntryGate(0, false),
2739 firstPhaseGate(0, false),
2742 firstPhaseReturned(0),
2744 emptyReadResult(-2),
2746 firstPollResult(-2),
2748 firstReadResult(-2),
2749 fillWriteResult(-2),
2750 fullWriteResult(-2),
2752 atomicSetupDrainResult(-2),
2753 atomicVectorResult(-2),
2754 atomicVectorError(0),
2756 overBoundaryWriteResult(-2),
2757 overBoundaryDrainResult(-2),
2760 eofReadResult(-2) {}
2770 int emptyReadResult;
2772 int firstPollResult;
2773 short firstPollEvents;
2774 int firstReadResult;
2775 int fillWriteResult;
2776 int fullWriteResult;
2778 int atomicSetupDrainResult;
2779 int atomicVectorResult;
2780 int atomicVectorError;
2782 int overBoundaryWriteResult;
2783 int overBoundaryDrainResult;
2785 short eofPollEvents;
2789struct EpollReadinessContext {
2791 size_t readFd,
size_t writeFd,
size_t aliasFd,
size_t regularFd)
2792 : subsystem(subsystem),
2798 regularFd(regularFd),
2799 waitEntryGate(0, false),
2803 regularAddResult(-2),
2806 createdDescriptorAcquired(false),
2807 createdStatusFlags(-1),
2808 createdDescriptorFlags(-1),
2809 createdCloseResult(false),
2810 duplicateAddResult(-2),
2811 duplicateAddError(0),
2812 exclusiveModifyResult(-2),
2813 exclusiveModifyError(0),
2814 firstWaitResult(-2),
2817 repeatedWaitResult(-2),
2818 repeatedWaitEvents(0),
2819 repeatedWaitData(0),
2820 sameReadyWriteResult(-2),
2821 sameReadyWaitResult(-2),
2822 firstDrainResult(-2),
2823 drainedWaitResult(-2),
2824 transitionWriteResult(-2),
2825 transitionWaitResult(-2),
2826 transitionWaitEvents(0),
2827 transitionWaitData(0),
2828 transitionDrainResult(-2),
2829 levelModifyResult(-2),
2830 levelWriteResult(-2),
2831 levelWaitResult(-2),
2834 levelRepeatedWaitResult(-2),
2835 levelDrainResult(-2),
2836 oneShotModifyResult(-2),
2837 oneShotWriteResult(-2),
2838 oneShotWaitResult(-2),
2839 oneShotWaitEvents(0),
2841 oneShotSuppressedResult(-2),
2843 rearmedWaitResult(-2),
2844 rearmedWaitEvents(0),
2846 oneShotDrainResult(-2),
2848 postDeleteWriteResult(-2),
2849 postDeleteWaitResult(-2),
2850 postDeleteDrainResult(-2),
2852 eventFdAddResult(-2),
2853 eventFdFirstWriteResult(-2),
2854 eventFdFirstWaitResult(-2),
2855 eventFdFirstWaitEvents(0),
2856 eventFdFirstWaitData(0),
2857 eventFdSecondWriteResult(-2),
2858 eventFdSecondWaitResult(-2),
2859 eventFdSecondWaitEvents(0),
2860 eventFdSecondWaitData(0),
2861 eventFdReadResult(-2),
2862 eventFdReadValue(0),
2863 eventFdDeleteResult(-2),
2864 eventFdCloseResult(false),
2866 originalCloseResult(false),
2867 ownersAfterOriginalClose(static_cast<size_t>(-1)),
2868 aliasWriteResult(-2),
2869 aliasWaitResult(-2),
2872 aliasDrainResult(-2),
2873 aliasCloseResult(false),
2874 ownersAfterAliasClose(static_cast<size_t>(-1)),
2875 prunedWaitResult(-2),
2876 descriptionRefsAfterPrune(static_cast<size_t>(-1)) {}
2889 int regularAddResult;
2890 int regularAddError;
2892 bool createdDescriptorAcquired;
2893 int createdStatusFlags;
2894 int createdDescriptorFlags;
2895 bool createdCloseResult;
2896 int duplicateAddResult;
2897 int duplicateAddError;
2898 int exclusiveModifyResult;
2899 int exclusiveModifyError;
2900 int firstWaitResult;
2901 uint32_t firstWaitEvents;
2902 uint64_t firstWaitData;
2903 int repeatedWaitResult;
2904 uint32_t repeatedWaitEvents;
2905 uint64_t repeatedWaitData;
2906 int sameReadyWriteResult;
2907 int sameReadyWaitResult;
2908 int firstDrainResult;
2909 int drainedWaitResult;
2910 int transitionWriteResult;
2911 int transitionWaitResult;
2912 uint32_t transitionWaitEvents;
2913 uint64_t transitionWaitData;
2914 int transitionDrainResult;
2915 int levelModifyResult;
2916 int levelWriteResult;
2917 int levelWaitResult;
2918 uint32_t levelWaitEvents;
2919 uint64_t levelWaitData;
2920 int levelRepeatedWaitResult;
2921 int levelDrainResult;
2922 int oneShotModifyResult;
2923 int oneShotWriteResult;
2924 int oneShotWaitResult;
2925 uint32_t oneShotWaitEvents;
2926 uint64_t oneShotWaitData;
2927 int oneShotSuppressedResult;
2929 int rearmedWaitResult;
2930 uint32_t rearmedWaitEvents;
2931 uint64_t rearmedWaitData;
2932 int oneShotDrainResult;
2934 int postDeleteWriteResult;
2935 int postDeleteWaitResult;
2936 int postDeleteDrainResult;
2938 int eventFdAddResult;
2939 int eventFdFirstWriteResult;
2940 int eventFdFirstWaitResult;
2941 uint32_t eventFdFirstWaitEvents;
2942 uint64_t eventFdFirstWaitData;
2943 int eventFdSecondWriteResult;
2944 int eventFdSecondWaitResult;
2945 uint32_t eventFdSecondWaitEvents;
2946 uint64_t eventFdSecondWaitData;
2947 int eventFdReadResult;
2948 uint64_t eventFdReadValue;
2949 int eventFdDeleteResult;
2950 bool eventFdCloseResult;
2952 bool originalCloseResult;
2953 size_t ownersAfterOriginalClose;
2954 int aliasWriteResult;
2955 int aliasWaitResult;
2956 uint32_t aliasWaitEvents;
2957 uint64_t aliasWaitData;
2958 int aliasDrainResult;
2959 bool aliasCloseResult;
2960 size_t ownersAfterAliasClose;
2961 int prunedWaitResult;
2962 size_t descriptionRefsAfterPrune;
2965class ReorderedReadinessFile final :
public File {
2967 ReorderedReadinessFile()
2968 :
File(), m_ReadinessLock(), m_Readable(true), m_Writable(true), m_Generations() {
2969 m_Generations.
read = 1;
2970 m_Generations.write = 1;
2973 ReadyMask
queryReady(
bool reading,
bool writing)
override {
2975 ReadyMask ready = ReadyNone;
2976 if (reading && m_Readable) {
2979 if (writing && m_Writable) {
2980 ready |= ReadyWrite;
2987 return m_Generations;
2994 void setReady(
bool readable,
bool writable) {
2996 if (!m_Readable && readable) {
2997 ++m_Generations.read;
2999 if (!m_Writable && writable) {
3000 ++m_Generations.write;
3002 m_Readable = readable;
3003 m_Writable = writable;
3006 void publishReadiness() {
3011 Mutex m_ReadinessLock;
3017struct ReorderedEpollContext {
3019 ReorderedReadinessFile* file)
3020 : instance(instance),
3023 initialComplete(0, false),
3024 drainMutated(0, false),
3025 publishDrain(0, false),
3026 collectFinal(0, false),
3027 initialWaitResult(-2),
3028 initialWaitEvents(0),
3030 finalWaitResult(-2),
3040 ReorderedReadinessFile* file;
3045 int initialWaitResult;
3046 uint32_t initialWaitEvents;
3047 uint64_t initialWaitData;
3048 int finalWaitResult;
3049 uint32_t finalWaitEvents;
3050 uint64_t finalWaitData;
3057int waitAcrossReorderedReadiness(
void* parameter) {
3058 ReorderedEpollContext* context =
reinterpret_cast<ReorderedEpollContext*
>(parameter);
3059 LinuxEpollEvent event = {LinuxEpoll::In | LinuxEpoll::Out | LinuxEpoll::EdgeTriggered,
3060 EpollReorderedData};
3061 context->addResult =
3062 context->instance->control(LinuxEpoll::ControlAdd,
static_cast<int>(context->fd), &event);
3065 context->initialWaitResult = context->instance->wait(&result, 1, 0);
3066 context->initialWaitEvents = result.events;
3067 context->initialWaitData = result.data;
3068 context->initialComplete.release();
3070 if (context->collectFinal.acquireForCompletion()) {
3072 context->finalWaitResult = context->instance->wait(&result, 1, 0);
3073 context->finalWaitEvents = result.events;
3074 context->finalWaitData = result.data;
3075 context->deleteResult = context->instance->control(LinuxEpoll::ControlDelete,
3076 static_cast<int>(context->fd),
nullptr);
3079 context->waiterReturned += 1;
3083int publishDelayedDrain(
void* parameter) {
3084 ReorderedEpollContext* context =
reinterpret_cast<ReorderedEpollContext*
>(parameter);
3085 context->file->setReady(
false,
false);
3086 context->drainMutated.release();
3087 if (context->publishDrain.acquireForCompletion()) {
3088 context->file->publishReadiness();
3090 context->drainReturned += 1;
3094class ReorderedFifo final :
public Pipe {
3096 ReorderedFifo() :
Pipe(
String(
"hosted-reordered-fifo"), 0, 0, 0, 0, nullptr, 0, nullptr, false) {}
3098 void reopenReaderWithoutPublishing() {
3100 m_Buffer.enableReads();
3102 m_ReaderCondition.broadcast();
3105 void publishReopen() {
3110struct ReorderedFifoEpollContext {
3112 : instance(instance),
3114 initialComplete(0, false),
3115 collectFinal(0, false),
3117 initialWaitResult(-2),
3118 initialWaitEvents(0),
3120 finalWaitResult(-2),
3131 int initialWaitResult;
3132 uint32_t initialWaitEvents;
3133 uint64_t initialWaitData;
3134 int finalWaitResult;
3135 uint32_t finalWaitEvents;
3136 uint64_t finalWaitData;
3141int waitAcrossReorderedFifoReopen(
void* parameter) {
3142 ReorderedFifoEpollContext* context =
reinterpret_cast<ReorderedFifoEpollContext*
>(parameter);
3143 LinuxEpollEvent interest = {LinuxEpoll::Out | LinuxEpoll::EdgeTriggered, EpollFifoData};
3144 context->addResult =
3145 context->instance->control(LinuxEpoll::ControlAdd,
static_cast<int>(context->fd), &interest);
3148 context->initialWaitResult = context->instance->wait(&event, 1, 0);
3149 context->initialWaitEvents =
event.events;
3150 context->initialWaitData =
event.data;
3151 context->initialComplete.release();
3153 if (context->collectFinal.acquireForCompletion()) {
3155 context->finalWaitResult = context->instance->wait(&event, 1, 0);
3156 context->finalWaitEvents =
event.events;
3157 context->finalWaitData =
event.data;
3158 context->deleteResult = context->instance->control(LinuxEpoll::ControlDelete,
3159 static_cast<int>(context->fd),
nullptr);
3162 context->returned += 1;
3166int pollPipeReadiness(
void* parameter) {
3167 PipePollReadinessContext* context =
reinterpret_cast<PipePollReadinessContext*
>(parameter);
3172 context->emptyReadResult = posix_read(context->readFd, &
byte, 1);
3173 context->emptyReadError = thread->
getErrno();
3175 struct pollfd readable = {
static_cast<int>(context->readFd), POLLIN, 0};
3176 context->pollEntered += 1;
3177 context->pollEntryGate.release();
3178 context->firstPollResult = posix_poll_safe(&readable, 1, PollCloseReuseTimeoutMilliseconds);
3179 context->firstPollEvents = readable.revents;
3180 context->firstReadResult = posix_read(context->readFd, &
byte, 1);
3182 char fill[PIPE_BUF_MAX] = {};
3183 context->fillWriteResult = posix_write(context->writeFd, fill,
sizeof(fill),
false);
3185 context->fullWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3186 context->fullWriteError = thread->
getErrno();
3187 context->atomicSetupDrainResult = posix_read(context->readFd, &
byte, 1);
3188 char vectorBytes[2] = {1, 2};
3189 struct iovec vectors[2] = {{&vectorBytes[0], 1}, {&vectorBytes[1], 1}};
3191 context->atomicVectorResult = posix_writev(context->writeFd, vectors, 2);
3192 context->atomicVectorError = thread->
getErrno();
3193 context->drainResult = posix_read(context->readFd, fill,
sizeof(fill));
3194 char overBoundary[PIPE_BUF_MAX + 1] = {};
3195 context->overBoundaryWriteResult =
3196 posix_write(context->writeFd, overBoundary,
sizeof(overBoundary),
false);
3197 context->overBoundaryDrainResult = posix_read(context->readFd, fill,
sizeof(fill));
3198 context->firstPhaseReturned += 1;
3199 context->firstPhaseGate.release();
3201 if (!context->eofGate.acquireForCompletion()) {
3202 context->returned += 1;
3206 struct pollfd eof = {
static_cast<int>(context->readFd), POLLIN, 0};
3207 context->eofPollResult = posix_poll_safe(&eof, 1, PollCloseReuseTimeoutMilliseconds);
3208 context->eofPollEvents = eof.revents;
3209 context->eofReadResult = posix_read(context->readFd, &
byte, 1);
3210 context->returned += 1;
3214bool pipePollReadiness(
Process* kernelProcess) {
3215 constexpr size_t ReadDescriptor = 45;
3216 constexpr size_t WriteDescriptor = 46;
3219 process->setSubsystem(subsystem);
3221 Pipe* pipe =
new Pipe(
String(
""), 0, 0, 0, 0,
nullptr, 0,
nullptr,
true);
3227 PipePollReadinessContext context(ReadDescriptor, WriteDescriptor);
3228 Thread*
worker =
new Thread(process, pollPipeReadiness, &context,
nullptr,
false,
true,
true);
3229 worker->setName(
"hosted pipe poll readiness worker");
3230 const bool started =
worker->start();
3232 const bool pollEntered = started && context.pollEntryGate.acquireForCompletion();
3233 bool pollBlocked =
false;
3234 for (
size_t attempt = 0; attempt < HostedAttempts && pollEntered; ++attempt) {
3236 if (
worker->getWaitDebugInfo(info) && info.queue && info.queued &&
3237 worker->getStatus() == Thread::Sleeping) {
3245 const bool initialWrite =
3246 pollBlocked && writer->
write(1,
reinterpret_cast<uintptr_t
>(&
byte)) == 1;
3247 const bool firstPhaseCompleted = started && context.firstPhaseGate.acquireForCompletion();
3251 const bool writerClosed =
3253 closingWriter.reset();
3254 context.eofGate.release();
3256 const bool joined = started &&
worker->joinForCompletion();
3261 const bool nonblockingPassed =
3262 context.emptyReadResult == -1 && context.emptyReadError == Error::NoMoreProcesses &&
3263 context.fillWriteResult == PIPE_BUF_MAX && context.fullWriteResult == -1 &&
3264 context.fullWriteError == Error::NoMoreProcesses && context.atomicSetupDrainResult == 1 &&
3265 context.atomicVectorResult == -1 && context.atomicVectorError == Error::NoMoreProcesses &&
3266 context.drainResult == PIPE_BUF_MAX - 1 && context.overBoundaryWriteResult == PIPE_BUF_MAX &&
3267 context.overBoundaryDrainResult == PIPE_BUF_MAX && context.eofReadResult == 0;
3268 const bool readinessPassed =
3269 initialWrite && context.firstPollResult == 1 && (context.firstPollEvents & POLLIN) &&
3270 !(context.firstPollEvents & POLLHUP) && context.firstReadResult == 1 &&
3271 context.eofPollResult == 1 && (context.eofPollEvents & POLLHUP);
3272 bool passed = started && pollBlocked && firstPhaseCompleted && joined && writerClosed &&
3273 context.firstPhaseReturned == 1 && context.returned == 1 && nonblockingPassed &&
3279 const bool readerClosed =
3281 closingReader.reset();
3282 passed = passed && readerClosed && readerDescription->descriptorOwnerCount() == 0 &&
3283 readerDescription->getFile() == pipe && pipe->
getReaderCount() == 0;
3284 readerDescription.
reset();
3289 "HOSTED-SYSCALL-TEST: FAIL pipe-poll-readiness: "
3290 "POLLIN wakeup, EOF hangup, or atomic nonblocking pipe I/O was incorrect");
3294 NOTICE(
"HOSTED-SYSCALL-TEST: PASS pipe-poll-readiness");
3298int exerciseEpollReadiness(
void* parameter) {
3299 EpollReadinessContext* context =
reinterpret_cast<EpollReadinessContext*
>(parameter);
3304 LinuxEpollEvent interest = {LinuxEpoll::In | LinuxEpoll::ReadNormal | LinuxEpoll::EdgeTriggered,
3306 context->addResult =
3307 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &interest);
3310 context->regularAddResult =
3311 context->instance->control(LinuxEpoll::ControlAdd, context->regularFd, &interest);
3312 context->regularAddError = thread->
getErrno();
3314 context->createdFd = posix_epoll_create1(LinuxEpoll::CloseOnExec);
3316 if (context->createdFd >= 0) {
3317 context->createdDescriptorAcquired = context->subsystem->acquireFileDescriptor(
3318 static_cast<size_t>(context->createdFd), createdDescriptor);
3319 if (context->createdDescriptorAcquired) {
3320 context->createdStatusFlags = createdDescriptor->
getStatusFlags();
3321 context->createdDescriptorFlags = createdDescriptor->
getFlags();
3322 context->createdCloseResult = context->subsystem->closeFileDescriptor(
3323 static_cast<size_t>(context->createdFd), createdDescriptor);
3326 createdDescriptor.reset();
3329 context->duplicateAddResult =
3330 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &interest);
3331 context->duplicateAddError = thread->
getErrno();
3333 LinuxEpollEvent exclusive = {LinuxEpoll::In | LinuxEpoll::Exclusive, EpollInitialData};
3335 context->exclusiveModifyResult =
3336 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &exclusive);
3337 context->exclusiveModifyError = thread->
getErrno();
3339 context->waitEntered += 1;
3340 context->waitEntryGate.release();
3341 context->firstWaitResult = context->instance->wait(events, 2, 5000);
3342 context->firstWaitEvents = events[0].events;
3343 context->firstWaitData = events[0].data;
3346 context->repeatedWaitResult = context->instance->wait(events, 2, 0);
3347 context->repeatedWaitEvents = events[0].events;
3348 context->repeatedWaitData = events[0].data;
3349 context->sameReadyWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3350 context->sameReadyWaitResult = context->instance->wait(events, 2, 0);
3351 char edgeBytes[2] = {};
3352 context->firstDrainResult = posix_read(context->readFd, edgeBytes,
sizeof(edgeBytes));
3353 context->drainedWaitResult = context->instance->wait(events, 2, 0);
3355 context->transitionWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3357 context->transitionWaitResult = context->instance->wait(events, 2, 0);
3358 context->transitionWaitEvents = events[0].events;
3359 context->transitionWaitData = events[0].data;
3360 context->transitionDrainResult = posix_read(context->readFd, &
byte, 1);
3363 context->levelModifyResult =
3364 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &level);
3365 context->levelWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3367 context->levelWaitResult = context->instance->wait(events, 2, 0);
3368 context->levelWaitEvents = events[0].events;
3369 context->levelWaitData = events[0].data;
3370 context->levelRepeatedWaitResult = context->instance->wait(events, 2, 0);
3371 context->levelDrainResult = posix_read(context->readFd, &
byte, 1);
3373 LinuxEpollEvent oneShot = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered | LinuxEpoll::OneShot,
3375 context->oneShotModifyResult =
3376 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &oneShot);
3377 context->oneShotWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3379 context->oneShotWaitResult = context->instance->wait(events, 2, 0);
3380 context->oneShotWaitEvents = events[0].events;
3381 context->oneShotWaitData = events[0].data;
3382 context->oneShotSuppressedResult = context->instance->wait(events, 2, 0);
3384 LinuxEpollEvent rearmed = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered | LinuxEpoll::OneShot,
3386 context->rearmResult =
3387 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &rearmed);
3389 context->rearmedWaitResult = context->instance->wait(events, 2, 0);
3390 context->rearmedWaitEvents = events[0].events;
3391 context->rearmedWaitData = events[0].data;
3392 context->oneShotDrainResult = posix_read(context->readFd, &
byte, 1);
3394 context->deleteResult =
3395 context->instance->control(LinuxEpoll::ControlDelete, context->readFd,
nullptr);
3396 context->postDeleteWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3397 context->postDeleteWaitResult = context->instance->wait(events, 2, 0);
3398 context->postDeleteDrainResult = posix_read(context->readFd, &
byte, 1);
3400 context->eventFd = posix_eventfd2(0, LinuxEventFd::NonBlock);
3401 LinuxEpollEvent eventFdInterest = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered, EpollEventFdData};
3402 if (context->eventFd >= 0) {
3403 context->eventFdAddResult =
3404 context->instance->control(LinuxEpoll::ControlAdd, context->eventFd, &eventFdInterest);
3405 uint64_t eventValue = 1;
3406 context->eventFdFirstWriteResult = posix_write(
3407 context->eventFd,
reinterpret_cast<char*
>(&eventValue),
sizeof(eventValue),
false);
3409 context->eventFdFirstWaitResult = context->instance->wait(events, 2, 0);
3410 context->eventFdFirstWaitEvents = events[0].events;
3411 context->eventFdFirstWaitData = events[0].data;
3416 context->eventFdSecondWriteResult = posix_write(
3417 context->eventFd,
reinterpret_cast<char*
>(&eventValue),
sizeof(eventValue),
false);
3419 context->eventFdSecondWaitResult = context->instance->wait(events, 2, 0);
3420 context->eventFdSecondWaitEvents = events[0].events;
3421 context->eventFdSecondWaitData = events[0].data;
3422 context->eventFdReadResult =
3423 posix_read(context->eventFd,
reinterpret_cast<char*
>(&context->eventFdReadValue),
3424 sizeof(context->eventFdReadValue));
3425 context->eventFdDeleteResult =
3426 context->instance->control(LinuxEpoll::ControlDelete, context->eventFd,
nullptr);
3429 const bool eventFdAcquired = context->subsystem->acquireFileDescriptor(
3430 static_cast<size_t>(context->eventFd), closingEventFd);
3431 context->eventFdCloseResult =
3432 eventFdAcquired && context->subsystem->closeFileDescriptor(
3433 static_cast<size_t>(context->eventFd), closingEventFd);
3434 closingEventFd.reset();
3438 context->aliasAddResult =
3439 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &aliasLifetime);
3442 const bool originalAcquired =
3443 context->subsystem->acquireFileDescriptor(context->readFd, closingOriginal);
3444 context->originalCloseResult =
3445 originalAcquired && context->subsystem->closeFileDescriptor(context->readFd, closingOriginal);
3446 closingOriginal.reset();
3447 context->ownersAfterOriginalClose = context->readDescription->descriptorOwnerCount();
3449 context->aliasWriteResult = posix_write(context->writeFd, &
byte, 1,
false);
3451 context->aliasWaitResult = context->instance->wait(events, 2, 0);
3452 context->aliasWaitEvents = events[0].events;
3453 context->aliasWaitData = events[0].data;
3454 context->aliasDrainResult = posix_read(context->aliasFd, &
byte, 1);
3457 const bool aliasAcquired =
3458 context->subsystem->acquireFileDescriptor(context->aliasFd, closingAlias);
3459 context->aliasCloseResult =
3460 aliasAcquired && context->subsystem->closeFileDescriptor(context->aliasFd, closingAlias);
3461 closingAlias.reset();
3462 context->ownersAfterAliasClose = context->readDescription->descriptorOwnerCount();
3463 context->prunedWaitResult = context->instance->wait(events, 2, 0);
3464 context->descriptionRefsAfterPrune = context->readDescription.refcount();
3466 context->returned += 1;
3470bool epollLevelOneShotAndOfdLifetime(
Process* kernelProcess) {
3471 constexpr size_t ReadDescriptor = 47;
3472 constexpr size_t WriteDescriptor = 48;
3473 constexpr size_t AliasDescriptor = 49;
3474 constexpr size_t RegularDescriptor = 50;
3478 process->setSubsystem(subsystem);
3480 Pipe* pipe =
new Pipe(
String(
""), 0, 0, 0, 0,
nullptr, 0,
nullptr,
true);
3484 alias->
fd = AliasDescriptor;
3493 EpollReadinessContext context(subsystem, instance, ReadDescriptor, WriteDescriptor,
3494 AliasDescriptor, RegularDescriptor);
3495 context.readDescription =
reader->acquireOpenFileDescription();
3498 new Thread(process, exerciseEpollReadiness, &context,
nullptr,
false,
true,
true);
3499 worker->setName(
"hosted epoll readiness worker");
3500 const bool started =
worker->start();
3501 const bool waitEntered = started && context.waitEntryGate.acquireForCompletion();
3503 bool waitBlocked =
false;
3504 for (
size_t attempt = 0; attempt < HostedAttempts && waitEntered; ++attempt) {
3506 if (
worker->getWaitDebugInfo(info) && info.queue && info.queued &&
3507 worker->getStatus() == Thread::Sleeping) {
3515 const int wakeWriteResult = writer->
write(1,
reinterpret_cast<uintptr_t
>(&
byte));
3516 const bool joined = started &&
worker->joinForCompletion();
3521 const bool edgePassed =
3522 waitBlocked && wakeWriteResult == 1 && context.firstWaitResult == 1 &&
3523 context.firstWaitEvents == (LinuxEpoll::In | LinuxEpoll::ReadNormal) &&
3524 context.firstWaitData == EpollInitialData && context.repeatedWaitResult == 0 &&
3525 context.sameReadyWriteResult == 1 && context.sameReadyWaitResult == 0 &&
3526 context.firstDrainResult == 2 && context.drainedWaitResult == 0 &&
3527 context.transitionWriteResult == 1 && context.transitionWaitResult == 1 &&
3528 context.transitionWaitEvents == (LinuxEpoll::In | LinuxEpoll::ReadNormal) &&
3529 context.transitionWaitData == EpollInitialData && context.transitionDrainResult == 1;
3530 const bool levelPassed = context.levelModifyResult == 0 && context.levelWriteResult == 1 &&
3531 context.levelWaitResult == 1 &&
3532 context.levelWaitEvents == LinuxEpoll::In &&
3533 context.levelWaitData == EpollLevelData &&
3534 context.levelRepeatedWaitResult == 1 && context.levelDrainResult == 1;
3535 const bool controlPassed =
3536 context.addResult == 0 && context.regularAddResult == -1 &&
3537 context.regularAddError == Error::NotEnoughPermissions && context.createdFd >= 0 &&
3538 context.createdDescriptorAcquired && context.createdStatusFlags == O_RDWR &&
3539 context.createdDescriptorFlags == FD_CLOEXEC && context.createdCloseResult &&
3540 context.duplicateAddResult == -1 && context.duplicateAddError == Error::FileExists &&
3541 context.exclusiveModifyResult == -1 &&
3542 context.exclusiveModifyError == Error::OperationNotSupported &&
3543 context.oneShotModifyResult == 0 && context.oneShotWriteResult == 1 &&
3544 context.oneShotWaitResult == 1 && context.oneShotWaitEvents == LinuxEpoll::In &&
3545 context.oneShotWaitData == EpollOneShotData && context.oneShotSuppressedResult == 0 &&
3546 context.rearmResult == 0 && context.rearmedWaitResult == 1 &&
3547 context.rearmedWaitEvents == LinuxEpoll::In && context.rearmedWaitData == EpollRearmedData &&
3548 context.oneShotDrainResult == 1 && context.deleteResult == 0 &&
3549 context.postDeleteWriteResult == 1 && context.postDeleteWaitResult == 0 &&
3550 context.postDeleteDrainResult == 1;
3551 const bool lifetimePassed =
3552 context.aliasAddResult == 0 && context.originalCloseResult &&
3553 context.ownersAfterOriginalClose == 1 && context.aliasWriteResult == 1 &&
3554 context.aliasWaitResult == 1 && context.aliasWaitEvents == LinuxEpoll::In &&
3555 context.aliasWaitData == EpollAliasData && context.aliasDrainResult == 1 &&
3556 context.aliasCloseResult && context.ownersAfterAliasClose == 0 &&
3557 context.prunedWaitResult == 0 && context.descriptionRefsAfterPrune == 1;
3558 const bool eventFdPassed =
3559 context.eventFd >= 0 && context.eventFdAddResult == 0 &&
3560 context.eventFdFirstWriteResult ==
static_cast<int>(
sizeof(uint64_t)) &&
3561 context.eventFdFirstWaitResult == 1 && context.eventFdFirstWaitEvents == LinuxEpoll::In &&
3562 context.eventFdFirstWaitData == EpollEventFdData &&
3563 context.eventFdSecondWriteResult ==
static_cast<int>(
sizeof(uint64_t)) &&
3564 context.eventFdSecondWaitResult == 1 && context.eventFdSecondWaitEvents == LinuxEpoll::In &&
3565 context.eventFdSecondWaitData == EpollEventFdData &&
3566 context.eventFdReadResult ==
static_cast<int>(
sizeof(uint64_t)) &&
3567 context.eventFdReadValue == 2 && context.eventFdDeleteResult == 0 &&
3568 context.eventFdCloseResult;
3569 bool passed = started && waitEntered && joined && context.waitEntered == 1 &&
3570 context.returned == 1 && edgePassed && levelPassed && controlPassed &&
3571 lifetimePassed && eventFdPassed;
3575 const bool writerClosed =
3577 closingWriter.reset();
3578 passed = passed && writerClosed;
3581 context.instance.reset();
3582 context.readDescription.reset();
3588 "HOSTED-SYSCALL-TEST: FAIL epoll-level-oneshot-ofd-lifetime: "
3589 "target admission, descriptor flags, edge and level delivery, one-shot rearm, control "
3590 "errors, eventfd generations, or OFD retirement was incorrect");
3594 NOTICE(
"HOSTED-SYSCALL-TEST: PASS epoll-level-oneshot-ofd-lifetime");
3598bool epollReorderedTransitionPublication(
Process* kernelProcess) {
3599 constexpr size_t DescriptorNumber = 51;
3602 process->setSubsystem(subsystem);
3604 ReorderedReadinessFile* source =
new ReorderedReadinessFile;
3606 new FileDescriptor(source, 0, DescriptorNumber, 0, O_RDWR | O_NONBLOCK);
3610 ReorderedEpollContext context(instance, DescriptorNumber, source);
3612 new Thread(process, waitAcrossReorderedReadiness, &context,
nullptr,
false,
true,
true);
3613 waiter->setName(
"hosted reordered epoll waiter");
3614 const bool waiterStarted =
waiter->start();
3615 const bool initialComplete = waiterStarted && context.initialComplete.acquireForCompletion();
3618 bool drainStarted =
false;
3619 bool drainMutated =
false;
3620 if (initialComplete) {
3621 drain =
new Thread(process, publishDelayedDrain, &context,
nullptr,
false,
true,
true);
3622 drain->setName(
"hosted delayed epoll drain publisher");
3623 drainStarted = drain->
start();
3624 drainMutated = drainStarted && context.drainMutated.acquireForCompletion();
3631 source->setReady(
true,
true);
3632 source->publishReadiness();
3634 context.publishDrain.release();
3636 if (drain && !drainStarted) {
3640 context.collectFinal.release();
3641 const bool waiterJoined = waiterStarted &&
waiter->joinForCompletion();
3642 if (!waiterStarted) {
3646 const bool transitionPassed =
3647 context.addResult == 0 && context.initialWaitResult == 1 &&
3648 context.initialWaitEvents == (LinuxEpoll::In | LinuxEpoll::Out) &&
3649 context.initialWaitData == EpollReorderedData && context.finalWaitResult == 1 &&
3650 context.finalWaitEvents == (LinuxEpoll::In | LinuxEpoll::Out) &&
3651 context.finalWaitData == EpollReorderedData && context.deleteResult == 0 &&
3652 source->readinessGenerations().read == 2 && source->readinessGenerations().write == 2;
3653 bool passed = waiterStarted && initialComplete && drainStarted && drainMutated && drainJoined &&
3654 waiterJoined && context.waiterReturned == 1 && context.drainReturned == 1 &&
3659 const bool descriptorClosed =
3662 passed = passed && descriptorClosed;
3665 context.instance.reset();
3671 "HOSTED-SYSCALL-TEST: FAIL epoll-reordered-transition-publication: "
3672 "a refill edge was lost when its callback overtook the drain callback");
3676 NOTICE(
"HOSTED-SYSCALL-TEST: PASS epoll-reordered-transition-publication");
3680bool epollPersistentFifoReopenReclose(
Process* kernelProcess) {
3681 constexpr size_t DescriptorNumber = 52;
3684 process->setSubsystem(subsystem);
3686 ReorderedFifo* fifo =
new ReorderedFifo;
3690 fifo->increaseRefCount(
false);
3691 char fill[PIPE_BUF_MAX] = {};
3692 const int fillResult = writer->
write(
sizeof(fill),
reinterpret_cast<uintptr_t
>(fill));
3693 fifo->decreaseRefCount(
false);
3696 ReorderedFifoEpollContext context(instance, DescriptorNumber);
3698 new Thread(process, waitAcrossReorderedFifoReopen, &context,
nullptr,
false,
true,
true);
3699 waiter->setName(
"hosted reordered FIFO epoll waiter");
3700 const bool waiterStarted =
waiter->start();
3701 const bool initialComplete = waiterStarted && context.initialComplete.acquireForCompletion();
3704 bool reopenMadeNotReady =
false;
3705 if (initialComplete) {
3709 fifo->reopenReaderWithoutPublishing();
3710 reopenMadeNotReady = fifo->queryReady(
false,
true) == ReadyNone;
3711 fifo->decreaseRefCount(
false);
3712 fifo->publishReopen();
3716 context.collectFinal.release();
3717 const bool waiterJoined = waiterStarted &&
waiter->joinForCompletion();
3718 if (!waiterStarted) {
3722 const uint32_t expectedEvents = LinuxEpoll::Out | LinuxEpoll::Error;
3723 const bool edgePassed =
3724 fillResult == PIPE_BUF_MAX && context.addResult == 0 && context.initialWaitResult == 1 &&
3725 context.initialWaitEvents == expectedEvents && context.initialWaitData == EpollFifoData &&
3726 reopenMadeNotReady && context.finalWaitResult == 1 &&
3727 context.finalWaitEvents == expectedEvents && context.finalWaitData == EpollFifoData &&
3728 context.deleteResult == 0 && finalGenerations.write != initialGenerations.write &&
3729 finalGenerations.error != initialGenerations.error;
3731 waiterStarted && initialComplete && waiterJoined && context.returned == 1 && edgePassed;
3735 const bool descriptorClosed =
3738 passed = passed && descriptorClosed;
3741 context.instance.reset();
3747 "HOSTED-SYSCALL-TEST: FAIL epoll-persistent-fifo-reopen-reclose: "
3748 "a full FIFO lost EPOLLOUT when reader re-close overtook reopen publication");
3752 NOTICE(
"HOSTED-SYSCALL-TEST: PASS epoll-persistent-fifo-reopen-reclose");
3756int pollAcrossCloseReuse(
void* parameter) {
3757 PollCloseReuseContext* context =
reinterpret_cast<PollCloseReuseContext*
>(parameter);
3758 context->entered += 1;
3761 context->result = posix_poll_safe(&context->descriptor, 1, PollCloseReuseTimeoutMilliseconds);
3762 context->returned += 1;
3763 return context->result == 1 ? 0 : 1;
3766bool pollCloseReuseCleanup(
Process* kernelProcess) {
3767 constexpr size_t DescriptorNumber = 39;
3770 process->setSubsystem(subsystem);
3776 PollGenerationProbe* aNetwork =
3777 new PollGenerationProbe(aQueries, aNotifications, aNetworkDestructions);
3779 DescriptorRetirementProbe* aDescriptor =
new DescriptorRetirementProbe(aDescriptorDestructions);
3780 aDescriptor->fd = DescriptorNumber;
3781 aDescriptor->setOffset(1);
3782 aDescriptor->setNetworkImpl(aNetworkKeepalive);
3789 PollGenerationProbe* bNetwork =
3790 new PollGenerationProbe(bQueries, bNotifications, bNetworkDestructions);
3792 DescriptorRetirementProbe* bDescriptor =
new DescriptorRetirementProbe(bDescriptorDestructions);
3793 bDescriptor->fd = DescriptorNumber;
3794 bDescriptor->setOffset(2);
3795 bDescriptor->setNetworkImpl(bNetworkKeepalive);
3797 PollCloseReuseContext context(DescriptorNumber);
3798 Thread*
worker =
new Thread(process, pollAcrossCloseReuse, &context,
nullptr,
false,
true,
true);
3799 worker->setName(
"hosted poll close-reuse worker");
3800 const bool started =
worker->start();
3802 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3804 bool blockedOnA =
false;
3805 for (
size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
3807 if (context.entered && aQueries >= 2 &&
worker->getWaitDebugInfo(info) && info.queue &&
3808 info.queued &&
worker->getStatus() == Thread::Sleeping) {
3815 bool passed = started && blockedOnA && aQueries >= 2;
3817 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3818 "waiter-published-a blocked="
3819 << blockedOnA <<
" queries=" << aQueries.value());
3822 const bool closedA = acquiredA && subsystem->
closeFileDescriptor(DescriptorNumber, closingA);
3825 passed = passed && closedA && aDescriptorDestructions == 0 && aNetworkDestructions == 0;
3827 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3828 "closed-a-published-b");
3830 if (aQueries >= 2) {
3831 aNetwork->makeReadable();
3835 bNetwork->makeReadable();
3838 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3839 "release-published");
3841 const bool joined = started &&
worker->joinForCompletion();
3843 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3844 "worker-returned joined="
3845 << joined <<
" returned=" << context.returned.value()
3846 <<
" result=" << context.result.value());
3847 passed = passed && joined && context.returned == 1 && context.result == 1 &&
3848 (context.descriptor.revents & POLLIN) && aNotifications == 1 && bNotifications == 0 &&
3849 bQueries == 0 && aDescriptorDestructions == 1 && aNetworkDestructions == 0;
3850 aNetworkKeepalive.reset();
3851 passed = passed && aNetworkDestructions == 1;
3855 const bool closedB = acquiredB && subsystem->
closeFileDescriptor(DescriptorNumber, closingB);
3857 passed = passed && closedB && bDescriptorDestructions == 1 && bNetworkDestructions == 0;
3858 bNetworkKeepalive.reset();
3859 passed = passed && bNetworkDestructions == 1;
3865 "HOSTED-SYSCALL-TEST: FAIL poll-close-reuse-cleanup: "
3866 "poll cleanup followed the reused fd instead of its registered "
3867 "descriptor generation");
3871 NOTICE(
"HOSTED-SYSCALL-TEST: PASS poll-close-reuse-cleanup");
3875struct PosixTeardownContext {
3876 explicit PosixTeardownContext(
Process* process)
3878 releaseGate(0, false),
3882 processDeleted(0) {}
3892int holdMemoryMapLifecycleGate(
void* parameter) {
3893 PosixTeardownContext* context =
reinterpret_cast<PosixTeardownContext*
>(parameter);
3895 context->holderEntered += 1;
3896 const bool released = context->releaseGate.acquireForCompletion();
3898 context->holderReturned += 1;
3899 return released ? 0 : 1;
3902int deletePosixProcess(
void* parameter) {
3903 PosixTeardownContext* context =
reinterpret_cast<PosixTeardownContext*
>(parameter);
3904 context->reaperEntered += 1;
3905 delete context->process;
3906 context->processDeleted += 1;
3910bool posixTeardownContention(
Process* kernelProcess) {
3913 PosixTeardownContext context(process);
3916 new Thread(kernelProcess, holdMemoryMapLifecycleGate, &context,
nullptr,
false,
true,
true);
3917 holder->setName(
"hosted mmap lifecycle holder");
3918 bool passed = holder->
start();
3920 for (
size_t attempt = 0; attempt < HostedAttempts && passed && !context.holderEntered;
3924 passed = passed && context.holderEntered == 1;
3926 Thread* reaper =
nullptr;
3927 bool blocked =
false;
3929 reaper =
new Thread(kernelProcess, deletePosixProcess, &context,
nullptr,
false,
true,
true);
3930 reaper->setName(
"hosted POSIX process reaper");
3931 passed = reaper->
start();
3933 for (
size_t attempt = 0; attempt < HostedAttempts && passed; ++attempt) {
3935 if (context.reaperEntered && reaper->
getWaitDebugInfo(info) && info.queue && info.queued &&
3937 reaper->
getStatus() == Thread::Sleeping) {
3943 passed = passed && blocked && context.processDeleted == 0;
3946 context.releaseGate.release();
3947 passed = holder->
join() && passed;
3949 passed = reaper->
join() && passed;
3950 passed = passed && context.processDeleted == 1;
3957 "HOSTED-SYSCALL-TEST: FAIL posix-teardown-contention: "
3958 "real PosixSubsystem destruction did not sleep and resume on "
3959 "the memory-map lifecycle gate");
3963 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-teardown-contention");
3967bool zeroResultWinsSignal(
Thread* thread) {
3969 thread->setInterruptionReason(Thread::InterruptedBySignal);
3970 const bool completed = finishInterruptibleSocketCall(thread,
static_cast<ssize_t
>(0));
3972 completed && thread->getInterruptionReason() == Thread::NotInterrupted && !thread->
getErrno();
3975 "HOSTED-SYSCALL-TEST: FAIL socket-zero-result-signal: "
3976 "EOF or zero-length success was replaced with EINTR");
3977 thread->clearInterruption();
3982 NOTICE(
"HOSTED-SYSCALL-TEST: PASS socket-zero-result-signal");
3986bool cloneStateDropsParentErrnoDestination() {
3988 SyscallState parent = {};
3989 parent.error_ptr =
reinterpret_cast<uintptr_t
>(&error);
3992 const SyscallState child = posix_copy_clone_state(parent);
3993 const bool passed = !child.error_ptr && child.result == parent.result &&
3994 parent.error_ptr ==
reinterpret_cast<uintptr_t
>(&error);
3997 "HOSTED-SYSCALL-TEST: FAIL clone-errno-lifetime: "
3998 "the child retained its parent's stack-local errno destination");
4002 NOTICE(
"HOSTED-SYSCALL-TEST: PASS clone-errno-lifetime");
4006bool cloneVmNullStackPreservesInterrupts() {
4009 posix, POSIX_CLONE, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD, 0, 0, 0,
4012 if (interruptsStillEnabled != interruptsWereEnabled) {
4016 if (!interruptsWereEnabled || !interruptsStillEnabled || result !=
static_cast<uintptr_t
>(-1)) {
4018 "HOSTED-SYSCALL-TEST: FAIL clone-vm-null-stack-interrupts: "
4019 "the invalid clone path did not preserve its enabled IRQ state");
4023 NOTICE(
"HOSTED-SYSCALL-TEST: PASS clone-vm-null-stack-interrupts");
4027enum CloneVmBeforeStartAction {
4028 CancelChildBeforeStart,
4032struct CloneVmUserFixture {
4036 alignas(16) uint8_t childStack[4096];
4039struct CloneVmExitRaceContext {
4040 explicit CloneVmExitRaceContext(CloneVmBeforeStartAction action)
4043 terminator(nullptr),
4046 beforeStart(0, true),
4053 callerInterruptsBefore(0),
4054 callerInterruptsAfter(0),
4055 terminatorEntered(0),
4056 terminatorReturned(0),
4059 terminationElectionCalls(0),
4060 ownershipWindowReleased(0),
4061 ownershipCancellationObserved(0),
4062 ownershipStartObserved(0),
4063 controlledHookRelease(0),
4064 electionTimedOut(0),
4065 childCancellationPublished(0),
4066 cloneResult(static_cast<size_t>(-1)),
4067 childCancellationRequested(0),
4068 childCancellationReapable(0),
4069 terminatorStarted(0),
4070 terminalCancellation(0),
4071 unexpectedHookRelease(0),
4073 rescueCancellation(0),
4074 processDestructions(0),
4075 subsystemDestructions(0) {}
4081 CloneVmBeforeStartAction action;
4083 uintptr_t userAddress;
4114CloneVmExitRaceContext* g_CloneVmExitRaceContext =
nullptr;
4116class CloneVmExitRaceProcess final :
public PosixProcess {
4119 :
PosixProcess(parent), m_Destructions(destructions) {}
4121 ~CloneVmExitRaceProcess()
override {
4122 m_Destructions += 1;
4134 ~CloneVmExitRaceSubsystem()
override {
4135 m_Destructions += 1;
4142int terminateCloneVmProcess(
void* parameter) {
4143 CloneVmExitRaceContext* context =
reinterpret_cast<CloneVmExitRaceContext*
>(parameter);
4144 context->terminatorEntered += 1;
4146 context->terminatorReturned += 1;
4150void terminateCloneVmBeforeStart(
Thread* child,
size_t threadId,
void* parameter) {
4151 CloneVmExitRaceContext* context =
reinterpret_cast<CloneVmExitRaceContext*
>(parameter);
4152 if (!context || !child || child->
getParent() != context->process) {
4155 context->child = child;
4156 context->observedTid = threadId;
4157 auto* user =
reinterpret_cast<CloneVmUserFixture*
>(context->userAddress);
4158 uintptr_t tlsValue = 0;
4160 sizeof(context->parentTid)) &&
4162 sizeof(context->childTid)) &&
4164 if (copied && child->
getId() == threadId && context->parentTid ==
static_cast<int>(threadId) &&
4165 context->childTid ==
static_cast<int>(threadId) &&
4166 tlsValue ==
reinterpret_cast<uintptr_t
>(&user->childTls)) {
4167 context->tidsReady += 1;
4169 context->hookCalls += 1;
4171 bool cancelChild = context->action == CancelChildBeforeStart || context->rescueCancellation;
4173 const bool released = context->beforeStart.acquire(1, 5, 0);
4174 cancelChild = context->rescueCancellation;
4176 if (released && context->ownershipWindowReleased) {
4177 context->controlledHookRelease += 1;
4182 context->terminalCancellation += 1;
4184 context->unexpectedHookRelease += 1;
4186 context->hookTimedOut += 1;
4194 context->childCancellationPublished = 1;
4195 context->childCancellationRequested += 1;
4196 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4197 if (child->isReapableForHostedTest()) {
4198 context->childCancellationReapable += 1;
4203 context->hookTimedOut += 1;
4206void observeCloneVmTerminationElection(
Process* process,
Thread* owner) {
4207 CloneVmExitRaceContext* context = __atomic_load_n(&g_CloneVmExitRaceContext, __ATOMIC_ACQUIRE);
4208 if (!context || process != context->process || owner != context->terminator) {
4211 context->terminationElectionCalls += 1;
4212 context->ownershipWindowReleased = 1;
4213 context->beforeStart.release();
4214 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4215 Thread* child = context->child.value();
4216 if (child && context->callerReturned) {
4218 !child->wasStartPublishedForHostedTest()) {
4219 context->ownershipCancellationObserved += 1;
4221 context->ownershipStartObserved += 1;
4223 context->childCancellationPublished = 1;
4228 context->electionTimedOut += 1;
4229 context->childCancellationPublished = 1;
4232void clearCloneVmHooks() {
4233 posixSetCloneBeforeStartHookForTest(
nullptr,
nullptr);
4234 Process::setTerminationElectionHook(
nullptr);
4235 __atomic_store_n(&g_CloneVmExitRaceContext,
static_cast<CloneVmExitRaceContext*
>(
nullptr),
4239int cloneVmWhileProcessExits(
void* parameter) {
4240 CloneVmExitRaceContext* context =
reinterpret_cast<CloneVmExitRaceContext*
>(parameter);
4241 context->callerEntered += 1;
4243 const size_t length = (
sizeof(CloneVmUserFixture) + pageSize - 1) & ~(pageSize - 1);
4244 uintptr_t address = 0;
4245 if (!context->process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
4246 context->callerReturned += 1;
4249 uintptr_t mappedAddress = address;
4251 mappedAddress, length, MemoryMappedObject::Read | MemoryMappedObject::Write);
4252 if (!mapping || mappedAddress != address) {
4256 context->process->freeUserRange(Process::UserRegion::Normal, address, length);
4257 context->callerReturned += 1;
4260 context->userAddress = address;
4261 auto* user =
reinterpret_cast<CloneVmUserFixture*
>(address);
4262 const CloneVmUserFixture initial = {-1, -1, 0, {}};
4264 context->callerReturned += 1;
4268 context->callerInterruptsBefore = interruptsWereEnabled ? 1 : 0;
4271 CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS |
4272 CLONE_PARENT_SETTID | CLONE_CHILD_SETTID,
4273 reinterpret_cast<uintptr_t
>(user->childStack +
sizeof(user->childStack)),
4274 reinterpret_cast<uintptr_t
>(&user->parentTid),
reinterpret_cast<uintptr_t
>(&user->childTid),
4275 reinterpret_cast<uintptr_t
>(&user->childTls));
4280 context->callerReturned += 1;
4284int cleanupCloneVmUserFixture(
void* parameter) {
4285 CloneVmExitRaceContext* context =
reinterpret_cast<CloneVmExitRaceContext*
>(parameter);
4286 if (context->userAddress) {
4288 const size_t length = (
sizeof(CloneVmUserFixture) + pageSize - 1) & ~(pageSize - 1);
4290 context->process->freeUserRange(Process::UserRegion::Normal, context->userAddress, length);
4291 context->userAddress = 0;
4296bool waitForCloneVmHookPause(CloneVmExitRaceContext* context) {
4297 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4299 if (context->hookCalls ==
static_cast<size_t>(1) && context->caller->getWaitDebugInfo(info) &&
4300 info.queue && info.queued && info.channelOwner == &context->beforeStart &&
4301 context->caller->getStatus() == Thread::Sleeping) {
4304 if (context->unexpectedHookRelease || context->hookTimedOut || context->callerReturned) {
4312bool waitForCloneVmThreadReapable(
Thread* thread) {
4313 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4314 if (thread->isReapableForHostedTest()) {
4322bool waitForCloneVmThreadCount(
Process* process,
size_t count) {
4323 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4332NORETURN void fatalCloneVmFixture(
const char* detail) {
4333 clearCloneVmHooks();
4334 FATAL(
"HOSTED-SYSCALL-TEST: clone fixture could not retire safely: " << detail);
4338bool cloneVmDetachedCancellationReturnsCachedTid(
Process* kernelProcess) {
4339 CloneVmExitRaceContext* context =
new CloneVmExitRaceContext(CancelChildBeforeStart);
4340 CloneVmExitRaceProcess* process =
4341 new CloneVmExitRaceProcess(kernelProcess, context->processDestructions);
4342 process->setSubsystem(
new CloneVmExitRaceSubsystem(context->subsystemDestructions));
4343 process->
description() =
"hosted clone detached-cancellation fixture";
4345 context->process = process;
4347 new Thread(process, cloneVmWhileProcessExits, context,
nullptr,
false,
true,
true);
4348 context->caller->setName(
"hosted clone detached-cancellation caller");
4351 posixSetCloneBeforeStartHookForTest(terminateCloneVmBeforeStart, context);
4352 const bool callerStarted = context->caller->start();
4353 bool callerReapable = callerStarted && waitForCloneVmThreadReapable(context->caller);
4354 if (!callerReapable) {
4355 context->rescueCancellation = 1;
4357 context->beforeStart.release();
4358 callerReapable = waitForCloneVmThreadReapable(context->caller);
4360 if (!callerReapable) {
4361 fatalCloneVmFixture(
"detached-cancellation caller remained live after rescue");
4363 clearCloneVmHooks();
4365 const bool childDeletedBeforeReturn = waitForCloneVmThreadCount(process, 1);
4366 bool passed = callerStarted && callerReapable && childDeletedBeforeReturn &&
4367 process->getState() == Process::Active && context->callerEntered == 1 &&
4368 context->callerReturned == 1 && context->callerInterruptsBefore == 1 &&
4369 context->callerInterruptsAfter == 1 && context->hookCalls == 1 &&
4370 context->tidsReady == 1 && context->childCancellationRequested == 1 &&
4371 context->childCancellationReapable == 1 && !context->hookTimedOut &&
4372 !context->unexpectedHookRelease && context->observedTid &&
4373 context->cloneResult == context->observedTid &&
4374 context->parentTid ==
static_cast<int>(context->observedTid) &&
4375 context->childTid ==
static_cast<int>(context->observedTid);
4377 if (!childDeletedBeforeReturn) {
4378 fatalCloneVmFixture(
"detached child remained live after its creator retired");
4380 if (!context->caller->joinForCompletion()) {
4381 fatalCloneVmFixture(
"detached-cancellation caller could not be joined");
4383 if (!waitForCloneVmThreadCount(process, 0)) {
4384 fatalCloneVmFixture(
"detached-cancellation process retained a live thread");
4386 Thread* cleanup =
new Thread(process, cleanupCloneVmUserFixture, context,
nullptr,
false,
true);
4388 fatalCloneVmFixture(
"detached-cancellation user mapping cleanup could not be joined");
4392 passed = passed && context->processDestructions == 1 && context->subsystemDestructions == 1;
4397 "HOSTED-SYSCALL-TEST: FAIL clone-vm-detached-cached-tid: "
4398 "POSIX clone did not return the cached ID after detached cancellation");
4402 NOTICE(
"HOSTED-SYSCALL-TEST: PASS clone-vm-detached-cached-tid");
4406bool cloneVmTerminalStartCancellation(
Process* kernelProcess) {
4407 CloneVmExitRaceContext* context =
new CloneVmExitRaceContext(WaitForProcessExit);
4408 CloneVmExitRaceProcess* process =
4409 new CloneVmExitRaceProcess(kernelProcess, context->processDestructions);
4410 process->setSubsystem(
new CloneVmExitRaceSubsystem(context->subsystemDestructions));
4411 process->
description() =
"hosted clone-vs-exit fixture";
4413 context->process = process;
4415 new Thread(process, cloneVmWhileProcessExits, context,
nullptr,
false,
true,
true);
4416 context->caller->setName(
"hosted clone-vs-exit caller");
4417 context->terminator =
4418 new Thread(process, terminateCloneVmProcess, context,
nullptr,
false,
true,
true);
4419 context->terminator->setName(
"hosted clone-vs-exit terminator");
4422 __atomic_store_n(&g_CloneVmExitRaceContext, context, __ATOMIC_RELEASE);
4423 Process::setTerminationElectionHook(observeCloneVmTerminationElection);
4424 posixSetCloneBeforeStartHookForTest(terminateCloneVmBeforeStart, context);
4425 const bool callerStarted = context->caller->start();
4426 const bool callerPaused = callerStarted && waitForCloneVmHookPause(context);
4427 Thread* pausedExpectedChild = context->child.value();
4429 const bool pausedChildPinned = callerPaused && pausedExpectedChild &&
4431 pausedChild->
getId() == context->observedTid;
4432 const bool terminatorStarted = context->terminator->start();
4433 if (terminatorStarted) {
4434 context->terminatorStarted += 1;
4436 context->rescueCancellation = 1;
4437 Thread* rescueExpectedChild = context->child.value();
4438 bool publishedChildSafe = !rescueExpectedChild;
4439 if (pausedChildPinned) {
4441 context->childCancellationPublished = 1;
4442 publishedChildSafe =
true;
4443 }
else if (rescueExpectedChild) {
4445 if (process->
acquireThread(rescueChild, rescueExpectedChild)) {
4447 context->childCancellationPublished = 1;
4448 publishedChildSafe =
true;
4450 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4452 context->child =
nullptr;
4453 publishedChildSafe =
true;
4460 if (!publishedChildSafe) {
4461 pausedChild.reset();
4462 fatalCloneVmFixture(
"published child could not be cancelled for start-failure rescue");
4464 pausedChild.reset();
4467 context->beforeStart.release();
4469 pausedChild.reset();
4471 bool terminated =
false;
4472 for (
size_t attempt = 0; attempt < HostedAttempts && terminatorStarted; ++attempt) {
4473 if (process->isTerminationReapableForHostedTest()) {
4479 if (terminatorStarted && !terminated) {
4480 context->beforeStart.release();
4481 for (
size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4482 if (process->isTerminationReapableForHostedTest()) {
4490 if (!terminatorStarted) {
4491 const bool callerReapable = waitForCloneVmThreadReapable(context->caller);
4492 const bool terminatorReapable = waitForCloneVmThreadReapable(context->terminator);
4493 if (!callerReapable || !terminatorReapable) {
4494 fatalCloneVmFixture(
"start-failure rescue left a worker live");
4496 clearCloneVmHooks();
4497 if (!context->caller->joinForCompletion() || !context->terminator->joinForCompletion()) {
4498 fatalCloneVmFixture(
"start-failure rescue could not join both workers");
4500 if (!waitForCloneVmThreadCount(process, 0)) {
4501 fatalCloneVmFixture(
"start-failure rescue retained a cloned child");
4503 Thread* cleanup =
new Thread(process, cleanupCloneVmUserFixture, context,
nullptr,
false,
true);
4505 fatalCloneVmFixture(
"start-failure user mapping cleanup could not be joined");
4508 const bool destroyed = context->processDestructions == 1 && context->subsystemDestructions == 1;
4511 FATAL(
"HOSTED-SYSCALL-TEST: clone start-failure rescue did not destroy exact owners");
4514 "HOSTED-SYSCALL-TEST: FAIL clone-vm-terminal-start-cancellation: "
4515 "the exit worker did not start");
4519 fatalCloneVmFixture(
"process-exit cancellation did not become reapable");
4521 clearCloneVmHooks();
4523 Thread* retiredChild = context->child.value();
4524 const bool retired = terminated && process->getState() == Process::Terminated &&
4526 context->caller->getStatus() == Thread::AwaitingJoin &&
4527 context->terminator->getStatus() == Thread::AwaitingJoin && retiredChild &&
4528 retiredChild->
getStatus() == Thread::AwaitingJoin &&
4529 !retiredChild->wasStartPublishedForHostedTest();
4531 callerPaused && retired && context->callerEntered == 1 && context->callerReturned == 1 &&
4532 context->callerInterruptsBefore == 1 && context->callerInterruptsAfter == 1 &&
4533 context->terminatorEntered == 1 && !context->terminatorReturned && context->hookCalls == 1 &&
4534 context->tidsReady == 1 && context->terminationElectionCalls == 1 &&
4535 context->ownershipWindowReleased == 1 && context->ownershipCancellationObserved == 1 &&
4536 !context->ownershipStartObserved && context->controlledHookRelease == 1 &&
4537 !context->electionTimedOut && context->childCancellationPublished == 1 &&
4538 context->terminatorStarted == 1 && !context->terminalCancellation &&
4539 !context->unexpectedHookRelease && !context->hookTimedOut && context->observedTid &&
4540 context->cloneResult == context->observedTid &&
4541 context->parentTid ==
static_cast<int>(context->observedTid) &&
4542 context->childTid ==
static_cast<int>(context->observedTid);
4544 passed = passed && pausedChildPinned;
4546 passed = passed && context->processDestructions == 1 && context->subsystemDestructions == 1;
4551 "HOSTED-SYSCALL-TEST: FAIL clone-vm-terminal-start-cancellation: "
4552 "terminal cancellation did not retire the published clone exactly once");
4556 NOTICE(
"HOSTED-SYSCALL-TEST: PASS clone-vm-terminal-start-cancellation");
4560bool failedPinnedModuleRejectsUnload() {
4562 module.name.assign("hosted-failed-pinned-probe");
4563 module.unloadable = false;
4564 module.status = Module::Failed;
4566 if (KernelElf::claimModuleUnloadForTest(&module) != KernelElf::TestUnloadPinned ||
4567 module.status != Module::Failed || module.unloadComplete) {
4569 "HOSTED-SYSCALL-TEST: FAIL failed-pinned-module: "
4570 "failed initialisation did not preserve its pinned module image");
4574 NOTICE(
"HOSTED-SYSCALL-TEST: PASS failed-pinned-module");
4578ModuleInfo* findStaticModuleInfo(
const char* name,
size_t& matches) {
4581 for (
size_t i = 0; i < g_StaticDriverN; ++i) {
4583 if (info && info->name && !StringCompare(info->name, name)) {
4591bool moduleInfoDependsOn(
ModuleInfo* info,
const char* dependency,
bool optional =
false) {
4592 const char** dependencies = optional ? info->opt_dependencies : info->dependencies;
4593 if (!dependencies) {
4596 for (
size_t i = 0; dependencies[i]; ++i) {
4597 if (!StringCompare(dependencies[i], dependency)) {
4604bool linkerModuleMetadataIsPinned() {
4606 ModuleInfo* linker = findStaticModuleInfo(
"linker", matches);
4608 if (matches != 1 || !linker || linker->unloadable || linker->runtimeUnloadable ||
4609 !moduleInfoDependsOn(linker,
"vfs")) {
4611 "HOSTED-SYSCALL-TEST: FAIL linker-pinned-metadata: "
4612 "the real linker ModuleInfo did not pin its dependency closure");
4616 NOTICE(
"HOSTED-SYSCALL-TEST: PASS linker-pinned-metadata");
4620bool filesystemModuleUnloadPolicyIsCorrect() {
4621 size_t posixMatches = 0;
4622 size_t mountrootMatches = 0;
4623 size_t ramfsMatches = 0;
4624 size_t rawfsMatches = 0;
4625 ModuleInfo* posix = findStaticModuleInfo(
"posix", posixMatches);
4626 ModuleInfo* mountroot = findStaticModuleInfo(
"mountroot", mountrootMatches);
4627 ModuleInfo* ramfs = findStaticModuleInfo(
"ramfs", ramfsMatches);
4628 ModuleInfo* rawfs = findStaticModuleInfo(
"rawfs", rawfsMatches);
4630 const bool metadataValid =
4631 posixMatches == 1 && mountrootMatches == 1 && ramfsMatches == 1 && rawfsMatches == 1 &&
4632 posix && mountroot && ramfs && rawfs && posix->unloadable && !posix->runtimeUnloadable &&
4633 mountroot->unloadable && !mountroot->runtimeUnloadable && !ramfs->unloadable &&
4634 !ramfs->runtimeUnloadable && rawfs->unloadable && !rawfs->runtimeUnloadable &&
4635 moduleInfoDependsOn(posix,
"mountroot") && moduleInfoDependsOn(posix,
"ramfs") &&
4636 moduleInfoDependsOn(mountroot,
"vfs") && moduleInfoDependsOn(mountroot,
"rawfs") &&
4637 moduleInfoDependsOn(mountroot,
"ramfs") && moduleInfoDependsOn(mountroot,
"fat",
true) &&
4638 moduleInfoDependsOn(mountroot,
"ext2",
true) &&
4639 moduleInfoDependsOn(mountroot,
"iso9660",
true) && moduleInfoDependsOn(ramfs,
"vfs") &&
4640 moduleInfoDependsOn(rawfs,
"vfs");
4641 if (!metadataValid) {
4643 "HOSTED-SYSCALL-TEST: FAIL filesystem-unload-policy-metadata: "
4644 "the real filesystem owner metadata did not encode the expected unload policy or "
4645 "dependency closure");
4649 NOTICE(
"HOSTED-SYSCALL-TEST: PASS filesystem-unload-policy-metadata");
4653bool runtimePinnedModuleAllowsLifecycleCleanup() {
4654 g_RuntimePinnedLifecycleCalls = 0;
4657 active.name.assign(
"hosted-runtime-pinned-active-probe");
4658 active.exit = runtimePinnedLifecycleProbe;
4659 active.runtimeUnloadable =
false;
4660 active.status = Module::Active;
4662 bool runLifecycle =
true;
4663 const KernelElf::TestModuleUnloadClaim explicitActive =
4664 KernelElf::claimModuleUnloadForTest(&active,
false, &runLifecycle);
4665 const bool explicitActiveValid = explicitActive == KernelElf::TestUnloadRuntimePinned &&
4666 !runLifecycle && active.status == Module::Active &&
4667 !active.unloadComplete && !g_RuntimePinnedLifecycleCalls;
4668 if (explicitActive == KernelElf::TestUnloadClaimed) {
4669 KernelElf::completeModuleUnloadForTest(&active);
4671 if (!explicitActiveValid) {
4673 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4674 "explicit unload escaped the runtime-only lifetime boundary");
4678 const KernelElf::TestModuleUnloadClaim shutdownActive =
4679 KernelElf::claimModuleUnloadForTest(&active,
true, &runLifecycle);
4680 const bool shutdownActiveValid = shutdownActive == KernelElf::TestUnloadClaimed && runLifecycle &&
4681 active.status == Module::Unloading;
4682 if (shutdownActive == KernelElf::TestUnloadClaimed) {
4683 KernelElf::completeModuleUnloadForTest(&active,
false, runLifecycle);
4685 if (!shutdownActiveValid) {
4687 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4688 "shutdown could not claim an active runtime-pinned module");
4693 failed.name.assign(
"hosted-runtime-pinned-failed-probe");
4694 failed.exit = runtimePinnedLifecycleProbe;
4695 failed.runtimeUnloadable =
false;
4696 failed.status = Module::Failed;
4697 runLifecycle =
true;
4698 const KernelElf::TestModuleUnloadClaim explicitFailed =
4699 KernelElf::claimModuleUnloadForTest(&failed,
false, &runLifecycle);
4700 const bool explicitFailedValid = explicitFailed == KernelElf::TestUnloadRuntimePinned &&
4701 !runLifecycle && failed.status == Module::Failed &&
4702 !failed.unloadComplete;
4703 if (explicitFailed == KernelElf::TestUnloadClaimed) {
4704 KernelElf::completeModuleUnloadForTest(&failed,
true);
4706 if (!explicitFailedValid) {
4708 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4709 "explicit unload escaped a failed runtime-pinned module");
4713 const KernelElf::TestModuleUnloadClaim failureCleanup =
4714 KernelElf::claimModuleUnloadForTest(&failed,
true, &runLifecycle);
4715 const bool failureCleanupValid = failureCleanup == KernelElf::TestUnloadClaimed && runLifecycle &&
4716 failed.status == Module::Unloading;
4717 if (failureCleanup == KernelElf::TestUnloadClaimed) {
4718 KernelElf::completeModuleUnloadForTest(&failed,
true, runLifecycle);
4720 if (!failureCleanupValid) {
4722 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4723 "failed initialisation could not claim lifecycle cleanup");
4727 if (!active.isUnloaded() || !active.unloadComplete || failed.status != Module::Failed ||
4728 !failed.unloadComplete || g_RuntimePinnedLifecycleCalls != 2) {
4730 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4731 "shutdown or failure cleanup did not publish completion");
4735 NOTICE(
"HOSTED-SYSCALL-TEST: PASS runtime-pinned-cleanup");
4739bool moduleUnloadOwnershipIsRetryable() {
4741 module.name.assign("hosted-unload-owner-probe");
4742 module.status = Module::Active;
4743 Module* fixtures[] = {&
module};
4745 const KernelElf::TestModuleUnloadClaim first =
4746 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1,
"hosted-unload-owner-probe");
4747 const KernelElf::TestModuleUnloadClaim concurrent =
4748 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1,
"hosted-unload-owner-probe");
4749 KernelElf::completeModuleUnloadForTest(&module);
4750 const KernelElf::TestModuleUnloadClaim repeat =
4751 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1,
"hosted-unload-owner-probe");
4752 const KernelElf::TestModuleUnloadClaim missing =
4753 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1,
"hosted-unload-missing-probe");
4755 if (first != KernelElf::TestUnloadClaimed || concurrent != KernelElf::TestUnloadBusy ||
4756 repeat != KernelElf::TestUnloadComplete || missing != KernelElf::TestUnloadUnknown ||
4757 !module.isUnloaded() || !module.unloadComplete ||
4758 !KernelElf::moduleExecutionWaitsForUnloadForTest()) {
4760 "HOSTED-SYSCALL-TEST: FAIL module-unload-ownership: "
4761 "the first owner, concurrent retry, or completed tombstone was lost");
4765 NOTICE(
"HOSTED-SYSCALL-TEST: PASS module-unload-ownership");
4769bool moduleShutdownOrderIsDependencySafe() {
4770 const char* nicsOptional[] = {
"ne2k",
nullptr};
4771 const char* ne2kDependencies[] = {
"network-stack",
nullptr};
4774 networkStack.name.assign(
"network-stack");
4775 networkStack.status = Module::Active;
4778 nics.name.assign(
"nics");
4779 nics.depends_opt = nicsOptional;
4780 nics.runtimeUnloadable =
false;
4781 nics.status = Module::Active;
4784 ne2k.name.assign(
"ne2k");
4785 ne2k.depends = ne2kDependencies;
4786 ne2k.status = Module::Active;
4788 Module* modules[] = {&networkStack, &nics, &ne2k};
4790 const size_t planned = KernelElf::planModuleUnloadOrderForTest(modules, 3, order, 3);
4791 const size_t repeated = KernelElf::planModuleUnloadOrderForTest(modules, 3, order, 3);
4793 const char* cycleADependencies[] = {
"cycle-b",
nullptr};
4794 const char* cycleBDependencies[] = {
"cycle-a",
nullptr};
4796 cycleA.name.assign(
"cycle-a");
4797 cycleA.depends = cycleADependencies;
4798 cycleA.status = Module::Active;
4800 cycleB.name.assign(
"cycle-b");
4801 cycleB.depends = cycleBDependencies;
4802 cycleB.status = Module::Active;
4803 Module* cycle[] = {&cycleA, &cycleB};
4804 Module* cycleOrder[2] = {};
4805 const size_t cyclicPlanned = KernelElf::planModuleUnloadOrderForTest(cycle, 2, cycleOrder, 2);
4807 if (planned != 3 || order[0] != &nics || order[1] != &ne2k || order[2] != &networkStack ||
4808 repeated != 0 || cyclicPlanned != 0 || cycleA.unloadComplete || cycleB.unloadComplete) {
4810 "HOSTED-SYSCALL-TEST: FAIL module-shutdown-order: "
4811 "optional/mandatory dependents were not retired first or a cycle was torn down");
4816 permanent.name.assign(
"permanent-shutdown-probe");
4817 permanent.unloadable =
false;
4818 permanent.runtimeUnloadable =
false;
4819 permanent.status = Module::Active;
4821 runtimePinned.name.assign(
"runtime-pinned-shutdown-probe");
4822 runtimePinned.runtimeUnloadable =
false;
4823 runtimePinned.status = Module::Active;
4824 Module* retentionModules[] = {&permanent, &runtimePinned};
4825 Module* retentionOrder[2] = {};
4826 const size_t retentionPlanned =
4827 KernelElf::planModuleUnloadOrderForTest(retentionModules, 2, retentionOrder, 2);
4829 if (retentionPlanned != 1 || retentionOrder[0] != &runtimePinned ||
4830 !runtimePinned.unloadComplete || permanent.unloadComplete) {
4832 "HOSTED-SYSCALL-TEST: FAIL module-shutdown-retention-policy: "
4833 "a runtime-only module was retained or a permanent pin was retired");
4837 NOTICE(
"HOSTED-SYSCALL-TEST: PASS module-shutdown-order");
4838 NOTICE(
"HOSTED-SYSCALL-TEST: PASS module-shutdown-retention-policy");
4842bool publishTerminalBlockedHandlerFixture(
Process* kernelProcess) {
4843 TerminalBlockedHandlerContext* context =
new TerminalBlockedHandlerContext;
4846 process->
description() =
"hosted blocked POSIX handler shutdown fixture";
4848 new Thread(process, terminalBlockedHandlerEntry, context,
nullptr,
false,
true,
true);
4849 thread->setName(
"hosted blocked POSIX handler fixture");
4850 context->thread = thread;
4853 g_TerminalBlockedHandlerContext = context;
4855 const bool started = thread->
start();
4857 bool blocked =
false;
4858 for (
size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
4860 if (context->hookEntered ==
static_cast<size_t>(1) && thread->
getWaitDebugInfo(info) &&
4861 info.queue && info.queued && info.channelOwner == &context->blocker &&
4862 thread->
getStatus() == Thread::Sleeping) {
4871 if (!started || !blocked || context->exitStaged !=
static_cast<size_t>(1) ||
4872 context->releasedByTermination || context->unexpectedRelease || context->syscallReturned) {
4874 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-blocked-handler-fixture: "
4875 "the POSIX handler was not admitted and blocked with a staged process exit");
4879 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-terminal-blocked-handler-fixture-published");
4883bool runRegressions() {
4884 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN real-event-boundaries");
4888 "HOSTED-SYSCALL-TEST: FAIL real-event-boundaries: "
4889 "module initialisation was not at base state");
4894 if (!kernelProcess) {
4898 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN usercopy");
4899 if (!runHostedUsercopyRegressions(kernelProcess)) {
4903 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN mmap-placement");
4904 if (!runHostedMmapPlacementRegressions(kernelProcess)) {
4908 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN time-syscall-usercopy");
4909 if (!runHostedTimeSyscallRegressions(kernelProcess)) {
4913 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN resource-syscall-semantics");
4914 if (!runHostedResourceSyscallRegressions(kernelProcess)) {
4918 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN child-resource-accounting");
4919 if (!runHostedChildResourceRegressions(kernelProcess)) {
4923 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN faccessat2-semantics");
4924 if (!runHostedAccessSyscallRegressions(kernelProcess)) {
4928 if (!runHostedVmPermissionRegressions() || !runHostedSystemUsercopyRegressions(kernelProcess) ||
4929 !runHostedFutexRobustRegressions(kernelProcess) ||
4930 !runHostedFileContractRegressions(kernelProcess) ||
4931 !runHostedProcessQueryRegressions(kernelProcess) ||
4932 !runHostedTermiosSyscallRegressions(kernelProcess)) {
4936 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN sleep-clock-usercopy");
4937 if (!runHostedSleepClockSyscallRegressions(kernelProcess)) {
4941 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN posix-exit-status");
4942 if (!runHostedPosixExitStatusRegressions(kernelProcess)) {
4946 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN thread-signal-syscalls");
4947 if (!runHostedThreadSignalSyscallRegressions(kernelProcess)) {
4951 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN rt-sigsuspend");
4952 if (!runHostedRtSigsuspendRegressions(kernelProcess)) {
4956 bool establishedAliasPassed =
true;
4957 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN directory-retained-lookup-atomicity");
4958 establishedAliasPassed &= directoryRetainedLookupAtomicity(kernelProcess);
4960 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN directory-retained-lookup-lifecycle");
4961 establishedAliasPassed &= directoryRetainedLookupLifecycle(kernelProcess);
4963 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN vfs-established-alias-serialization");
4964 establishedAliasPassed &= establishedAliasRetainSerialization(kernelProcess);
4966 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN file-established-alias-lifetime");
4967 establishedAliasPassed &= establishedFileAliasLifetime();
4969 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN process-filesystem-context-lifetime");
4970 establishedAliasPassed &= processFilesystemContextLifetime(kernelProcess);
4972 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN mmap-established-alias-lifetime");
4973 establishedAliasPassed &= establishedMappingAliasLifetime();
4975 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN munmap-target-page-geometry");
4976 establishedAliasPassed &= munmapUsesTargetPageGeometry(thread);
4978 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN mmap-split-alias-lifetime");
4979 establishedAliasPassed &= mappingManagerSplitLifetime(kernelProcess);
4981 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN posix-path-lookup-lifetime");
4982 establishedAliasPassed &= posixPathLookupLifetime(kernelProcess);
4983 if (!establishedAliasPassed) {
4987 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-close-pinning");
4988 if (!descriptorClosePinning(kernelProcess)) {
4992 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-close-generation");
4993 if (!descriptorCloseGeneration(kernelProcess)) {
4997 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-open-file-description-state");
4998 if (!descriptorOpenFileDescriptionState()) {
5002 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-open-file-description-lifetime");
5003 if (!descriptorOpenFileDescriptionLifetime()) {
5007 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-append-policy");
5008 if (!descriptorAppendPolicy(kernelProcess)) {
5012 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-nonblocking-policy");
5013 if (!descriptorNonblockingPolicy()) {
5017 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-position-alias-serialization");
5018 if (!descriptorPositionAliasSerialization(kernelProcess)) {
5022 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-vector-io-serialization");
5023 if (!descriptorVectorIoSerialization(kernelProcess)) {
5027 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN advisory-lock-fail-closed");
5028 if (!runHostedAdvisoryLockRegressions(kernelProcess)) {
5032 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN scalar-io-user-buffer-lifetime");
5033 if (!runHostedScalarIoRegressions(kernelProcess)) {
5037 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN positional-io-semantics");
5038 if (!runHostedPositionalIoRegressions(kernelProcess)) {
5042 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN positional-vector-io-semantics");
5043 if (!runHostedPositionalVectorIoRegressions(kernelProcess)) {
5047 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN vector-io-user-buffer-lifetime");
5048 if (!runHostedVectorIoRegressions(kernelProcess)) {
5052 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN ppoll-linux-abi");
5053 if (!runHostedPpollRegressions(kernelProcess)) {
5057 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN pselect-linux-abi");
5058 if (!runHostedPselectRegressions(kernelProcess)) {
5062 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-dup-contract");
5063 if (!descriptorDupContract(kernelProcess)) {
5067 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN dup3-atomic-replacement");
5068 if (!runHostedDup3Regressions(kernelProcess)) {
5072 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN descriptor-position-policy");
5073 if (!descriptorPositionPolicy()) {
5077 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN select-projection");
5078 if (!selectProjectionContract()) {
5082 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN pipe-poll-readiness");
5083 if (!pipePollReadiness(kernelProcess)) {
5087 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN epoll-level-oneshot-ofd-lifetime");
5088 if (!epollLevelOneShotAndOfdLifetime(kernelProcess)) {
5092 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN epoll-reordered-transition-publication");
5093 if (!epollReorderedTransitionPublication(kernelProcess)) {
5097 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN epoll-persistent-fifo-reopen-reclose");
5098 if (!epollPersistentFifoReopenReclose(kernelProcess)) {
5102 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN eventfd-counter-readiness-lifetime");
5103 if (!runHostedEventFdRegressions(kernelProcess)) {
5107 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN inotify-vfs-epoll-lifetime");
5108 if (!runHostedInotifyRegressions(kernelProcess)) {
5112 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN poll-close-reuse-cleanup");
5113 if (!pollCloseReuseCleanup(kernelProcess)) {
5117 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN posix-teardown-contention");
5118 if (!posixTeardownContention(kernelProcess)) {
5122 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN unix-bind-replacement-lifetime");
5123 if (!runHostedUnixEndpointLifetimeRegression(kernelProcess)) {
5127 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN scm-rights-datagram");
5128 if (!runHostedScmRightsRegressions(kernelProcess)) {
5132 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN scm-rights-stream");
5133 if (!runHostedScmStreamRegressions(kernelProcess)) {
5137 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN unix-stream-interruption");
5138 if (!runHostedUnixStreamInterruptionRegressions(kernelProcess)) {
5142 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN socket-zero-result-signal");
5143 if (!zeroResultWinsSignal(thread)) {
5147 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN clone-errno-lifetime");
5148 if (!cloneStateDropsParentErrnoDestination()) {
5152 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN clone-process-routing");
5153 if (!runHostedCloneRoutingRegressions(kernelProcess)) {
5157 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN clone-vm-null-stack-interrupts");
5158 if (!cloneVmNullStackPreservesInterrupts()) {
5162 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN clone-vm-detached-cached-tid");
5163 if (!cloneVmDetachedCancellationReturnsCachedTid(kernelProcess)) {
5167 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN clone-vm-terminal-start-cancellation");
5168 if (!cloneVmTerminalStartCancellation(kernelProcess)) {
5172 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN failed-pinned-module");
5173 if (!failedPinnedModuleRejectsUnload()) {
5177 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN linker-pinned-metadata");
5178 if (!linkerModuleMetadataIsPinned()) {
5182 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN filesystem-unload-policy-metadata");
5183 if (!filesystemModuleUnloadPolicyIsCorrect()) {
5187 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN runtime-pinned-cleanup");
5188 if (!runtimePinnedModuleAllowsLifecycleCleanup()) {
5192 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN module-unload-ownership");
5193 if (!moduleUnloadOwnershipIsRetryable()) {
5197 NOTICE(
"HOSTED-SYSCALL-TEST: BEGIN module-shutdown-order");
5198 if (!moduleShutdownOrderIsDependencySafe()) {
5203 static char pedigreeCModule[] =
"pedigree-c";
5204 static char lwipModule[] =
"lwip";
5205 static char networkStackModule[] =
"network-stack";
5206 const uintptr_t sigretResult = manager.
syscall(posix, PEDIGREE_SIGRET);
5207 const uintptr_t unwindResult = manager.
syscall(posix, PEDIGREE_UNWIND_SIGNAL);
5208 const uintptr_t eventReturnResult = manager.
syscall(pedigree_c, PEDIGREE_EVENT_RETURN);
5209 const uintptr_t selfUnloadResult = manager.
syscall(pedigree_c, PEDIGREE_MODULE_UNLOAD,
5210 reinterpret_cast<uintptr_t
>(pedigreeCModule));
5211 const uintptr_t stillLoadedResult = manager.
syscall(pedigree_c, PEDIGREE_MODULE_IS_LOADED,
5212 reinterpret_cast<uintptr_t
>(pedigreeCModule));
5213 const uintptr_t lwipUnloadResult =
5214 manager.
syscall(pedigree_c, PEDIGREE_MODULE_UNLOAD,
reinterpret_cast<uintptr_t
>(lwipModule));
5215 const uintptr_t lwipStillLoadedResult = manager.
syscall(pedigree_c, PEDIGREE_MODULE_IS_LOADED,
5216 reinterpret_cast<uintptr_t
>(lwipModule));
5217 const uintptr_t networkStackUnloadResult = manager.
syscall(
5218 pedigree_c, PEDIGREE_MODULE_UNLOAD,
reinterpret_cast<uintptr_t
>(networkStackModule));
5219 const uintptr_t networkStackStillLoadedResult = manager.
syscall(
5220 pedigree_c, PEDIGREE_MODULE_IS_LOADED,
reinterpret_cast<uintptr_t
>(networkStackModule));
5222 if (sigretResult !=
static_cast<uintptr_t
>(-1) || unwindResult !=
static_cast<uintptr_t
>(-1) ||
5223 eventReturnResult !=
static_cast<uintptr_t
>(-1) ||
5224 selfUnloadResult !=
static_cast<uintptr_t
>(-1) || stillLoadedResult != 1 ||
5225 lwipUnloadResult !=
static_cast<uintptr_t
>(-1) || lwipStillLoadedResult != 1 ||
5226 networkStackUnloadResult !=
static_cast<uintptr_t
>(-1) ||
5229 "HOSTED-SYSCALL-TEST: FAIL real-event-boundaries: "
5230 "a public misuse path escaped its lifetime boundary");
5234 NOTICE(
"HOSTED-SYSCALL-TEST: PASS real-event-boundaries");
5238 terminalFixture->
description() =
"hosted zero-thread POSIX shutdown fixture";
5240 if (terminalFixture->
getNumThreads() != 0 || terminalFixture->getType() != Process::Posix) {
5242 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-fixture: "
5243 "fixture was not published as an ownerless POSIX process");
5246 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-terminal-drain-fixture-published");
5248 if (!posixDuplicateInitRollbackPreservesProcessForTest(terminalFixture)) {
5250 "HOSTED-SYSCALL-TEST: FAIL posix-duplicate-init-rollback: "
5251 "an unowned duplicate initialisation retired an existing POSIX process");
5254 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-duplicate-init-rollback-preserved-process");
5258 createdFixture->
description() =
"hosted Created-thread POSIX shutdown fixture";
5260 new Thread(createdFixture, terminalCreatedFixtureEntry,
nullptr,
nullptr,
false,
true,
true);
5261 createdThread->setName(
"hosted terminal Created-thread fixture");
5264 createdFixture->getType() != Process::Posix) {
5266 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-created-fixture: "
5267 "fixture did not retain its unstarted ordinary entry");
5270 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-terminal-drain-created-fixture-published");
5272 if (!publishTerminalBlockedHandlerFixture(kernelProcess)) {
5280 hostedSyscallProfileRequested() ? hostedRunSyscallProfile() : runRegressions();
5286 TerminalBlockedHandlerContext* context = g_TerminalBlockedHandlerContext;
5287 g_TerminalBlockedHandlerContext =
nullptr;
5292 if (context->hookEntered !=
static_cast<size_t>(1) ||
5293 context->exitStaged !=
static_cast<size_t>(1) ||
5294 context->releasedByTermination !=
static_cast<size_t>(1) || context->unexpectedRelease ||
5295 context->syscallReturned) {
5297 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-blocked-handler-release: "
5298 "terminal process teardown did not release the admitted handler before module exit");
5300 NOTICE(
"HOSTED-SYSCALL-TEST: PASS posix-terminal-blocked-handler-released-by-process-exit");
5306MODULE_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()
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