The Pedigree Project 0.1
uts-namespace-regressions.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include <config.h>
3#if PEDIGREE_UTS_NAMESPACE_TESTS && THREADS
4#include "pedigree/kernel/Log.h"
5#include "pedigree/kernel/process/Process.h"
6#include "pedigree/kernel/process/Scheduler.h"
7#include "pedigree/kernel/process/Semaphore.h"
8#include "pedigree/kernel/process/Thread.h"
9#include "pedigree/kernel/processor/Processor.h"
10#include "pedigree/kernel/processor/ProcessorInformation.h"
11
12#include "modules/subsys/posix/PosixProcess.h"
13#include "modules/subsys/posix/PosixSubsystem.h"
14#include "modules/subsys/posix/uts-namespace.h"
15
16namespace {
17bool check(bool condition, const char* detail) {
18 if (!condition)
19 ERROR("UTS-NAMESPACE-TEST: FAIL " << detail);
20 return condition;
21}
22
23int forbiddenEntry(void* parameter) {
24 *static_cast<bool*>(parameter) = true;
25 return 0;
26}
27
28bool terminalPublication(PosixNamespaceContext& context, Process& process, const UtsRef& source) {
29 const size_t references = source.refcount();
31 if (!check(posix_uts_prepare_thread(source, false, prepared) == UtsStatus::Success,
32 "terminal preparation"))
33 return false;
34 bool entered = false;
35 Thread* peer = new Thread(&process, forbiddenEntry, &entered, nullptr, false, true, true);
36 if (!check(peer != nullptr, "terminal thread allocation"))
37 return false;
38 const size_t taskId = peer->getTaskId();
40 // The real unstarted shutdown may already have run the subsystem hook.
41 // Either ordering must leave this prepared binding outside the live context.
42 context.publishThread(prepared, *peer, false);
43 UtsRef absent;
44 PosixUtsTarget target;
45 bool passed = check(bool(prepared) && !context.acquireThread(*peer, absent) &&
46 !context.taskTarget(taskId, target),
47 "terminal publication consumed or exposed a binding");
48 prepared.reset();
49 passed &= check(source.refcount() == references, "rejected preparation retained namespace");
50 if (!peer->joinForCompletion())
51 FATAL("UTS-NAMESPACE-TEST: terminal thread could not be joined safely");
52 passed &= check(!entered, "terminal thread executed its entry point");
53 if (passed)
54 NOTICE("UTS-NAMESPACE-TEST: PASS terminal publication rollback");
55 return passed;
56}
57
58struct PeerState {
59 Semaphore ready{0}, release{0};
60 bool released = false;
61};
62
63int controlledEntry(void* parameter) {
64 auto& state = *static_cast<PeerState*>(parameter);
65 state.ready.release();
66 state.released = state.release.acquire(1, 10);
67 return 0;
68}
69
70bool targetRetirement(PosixNamespaceContext& context, Process& process, const UtsRef& source) {
72 if (!check(posix_uts_prepare_thread(source, false, prepared) == UtsStatus::Success,
73 "live preparation"))
74 return false;
75 PeerState state;
76 Thread* peer = new Thread(&process, controlledEntry, &state, nullptr, false, true, true);
77 if (!check(peer != nullptr, "live thread allocation"))
78 return false;
79 const size_t taskId = peer->getTaskId();
80 context.publishThread(prepared, *peer, false);
81 const bool started = peer->start();
82 const bool ready = started && state.ready.acquire(1, 10);
83 PosixUtsTarget retained;
84 UtsRef captured;
85 bool passed = check(ready && !prepared && context.taskTarget(taskId, retained) &&
86 posix_uts_acquire_target(retained, captured) == UtsStatus::Success &&
87 captured->identity() == source->identity(),
88 "live task namespace capture");
89 state.release.release();
90 if (!started)
92 if (!peer->joinForCompletion())
93 FATAL("UTS-NAMESPACE-TEST: controlled thread could not be joined safely");
94 UtsRef missing;
95 PosixUtsTarget absent;
96 passed &= check(state.released && !context.taskTarget(taskId, absent) &&
97 posix_uts_acquire_target(retained, missing) == UtsStatus::Missing &&
98 !missing && captured && captured->identity() == source->identity(),
99 "retained target survived task retirement or lost namespace ownership");
100 if (passed)
101 NOTICE("UTS-NAMESPACE-TEST: PASS retained task retirement");
102 return passed;
103}
104
105int coordinator(void* parameter) {
106 bool& passed = *static_cast<bool*>(parameter);
107 Thread& current = *Processor::information().getCurrentThread();
108 auto* process = current.getParent();
109 auto context = static_cast<PosixSubsystem*>(process->getSubsystem())->namespaceContext();
110 UtsRef source;
111 if (!check(context && context->acquireThread(current, source), "coordinator binding"))
112 return 0;
113 passed = terminalPublication(*context, *process, source) &&
114 targetRetirement(*context, *process, source);
115 PosixUtsTarget leader;
116 passed &= check(context->leaderTarget(leader), "leader capture before close");
117 context->close();
118 context->close();
119 UtsRef absent;
120 PosixUtsTarget noLeader;
121 passed &= check(!context->acquireThread(current, absent) && !context->leaderTarget(noLeader) &&
122 posix_uts_acquire_target(leader, absent) == UtsStatus::Missing && !absent,
123 "closed context retained a live binding");
124 if (passed)
125 NOTICE("UTS-NAMESPACE-TEST: PASS closed context");
126 return 0;
127}
128} // namespace
129
130bool utsNamespaceRegression() {
131 NOTICE("UTS-NAMESPACE-TEST: BEGIN");
132 auto* process = new PosixProcess(Scheduler::instance().getKernelProcess(), true);
133 if (!check(process != nullptr, "isolated process allocation"))
134 return false;
135 auto* subsystem = new PosixSubsystem;
136 if (!subsystem) {
137 delete process;
138 return check(false, "isolated subsystem allocation");
139 }
140 process->setSubsystem(subsystem);
141 auto context = subsystem->namespaceContext();
142 UtsRef initial, source;
144 if (!check(context && context->valid() && posix_uts_initial(initial) == UtsStatus::Success &&
145 posix_uts_copy(initial, source) == UtsStatus::Success &&
146 posix_uts_prepare_thread(source, false, prepared) == UtsStatus::Success,
147 "isolated namespace preparation")) {
148 delete process;
149 return false;
150 }
151 bool passed = false;
152 Thread* worker = new Thread(process, coordinator, &passed, nullptr, false, true, true);
153 if (!worker) {
154 delete process;
155 return check(false, "coordinator allocation");
156 }
157 context->publishThread(prepared, *worker, true);
158 process->publish();
159 const bool started = worker->start();
160 if (!started)
161 worker->setUnwindState(Thread::TerminateThread);
162 const bool joined = worker->joinForCompletion();
163 if (!joined)
164 FATAL("UTS-NAMESPACE-TEST: coordinator could not be joined safely");
165 delete process;
166 passed = check(started && joined && passed, "isolated coordinator") && passed;
167 if (passed)
168 NOTICE("UTS-NAMESPACE-TEST: END PASS");
169 return passed;
170}
171#endif
void publish()
Definition Process.cc:832
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
void release(size_t n=1)
Definition Semaphore.cc:546
size_t refcount() const
void setUnwindState(UnwindType ut)
Definition Thread.cc:3628
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:515
bool joinForCompletion()
Definition Thread.cc:2771
Process * getParent() const
Definition Thread.h:338
bool start()
Definition Thread.cc:794
size_t getTaskId() const
Definition Thread.h:468