The Pedigree Project 0.1
uts-namespace.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/process/Process.h"
4#include "pedigree/kernel/process/Scheduler.h"
5#include "pedigree/kernel/process/Thread.h"
6#include "pedigree/kernel/processor/Processor.h"
7#include "pedigree/kernel/processor/ProcessorInformation.h"
8#include "pedigree/kernel/syscallError.h"
9#include "pedigree/kernel/utilities/assert.h"
10#include "pedigree/kernel/utilities/utility.h"
11
12#include "PosixProcess.h"
13#include "PosixSubsystem.h"
15#include "uts-namespace.h"
16
17namespace {
18Mutex creationLock;
19UtsRef initialNamespace;
20size_t namespaceCount = 1;
21uint64_t nextIdentity = 1;
22constexpr size_t MaximumNamespaces = 256;
23size_t localId(const Thread& thread) {
24 return const_cast<Thread&>(thread).getId();
25}
26} // namespace
27
29 public:
30 explicit PosixUtsTaskBinding(const UtsRef& value) : space(value) {}
31 UtsRef space;
32 size_t localId = 0, taskId = 0;
33 bool live = false;
34};
35
37 public:
38 Mutex lock;
39 size_t processId = 0;
40 bool attached = false, closed = false;
41 PreparedUtsThread* first = nullptr;
43};
44
45PosixUtsNamespace::PosixUtsNamespace(uint64_t identity, const Snapshot& names, bool charged)
46 : m_Identity(identity), m_Charged(charged), m_Names(names) {}
47PosixUtsNamespace::~PosixUtsNamespace() {
48 if (m_Charged)
49 __atomic_sub_fetch(&namespaceCount, size_t(1), __ATOMIC_RELEASE);
50}
51PosixUtsNamespace::Snapshot PosixUtsNamespace::snapshot() const {
52 LockGuard<Mutex> guard(m_Lock);
53 return m_Names;
54}
55void PosixUtsNamespace::setName(bool domain, const char* bytes, size_t length) {
56 assert(length <= 64);
57 LockGuard<Mutex> guard(m_Lock);
58 char* output = domain ? m_Names.domain : m_Names.node;
59 if (length)
60 MemoryCopy(output, bytes, length);
61 ByteSet(output + length, 0, 65 - length);
62}
63
64UtsStatus posix_uts_initial(UtsRef& result) {
65 UtsRef acquired;
66 {
67 LockGuard<Mutex> guard(creationLock);
68 if (!initialNamespace) {
70 MemoryCopy(names.node, "pedigree", 8);
71 MemoryCopy(names.domain, "(none)", 6);
72 initialNamespace = SharedPointer<PosixUtsNamespace>::tryAllocate(uint64_t(1), names, false);
73 if (!initialNamespace)
74 return UtsStatus::NoMemory;
75 }
76 acquired = initialNamespace;
77 }
78 result = pedigree_std::move(acquired);
79 return UtsStatus::Success;
80}
81
82UtsStatus posix_uts_copy(const UtsRef& source, UtsRef& result) {
83 if (!source)
84 return UtsStatus::Missing;
85 const auto names = source->snapshot();
86 uint64_t identity;
87 {
88 LockGuard<Mutex> guard(creationLock);
89 if (__atomic_load_n(&namespaceCount, __ATOMIC_ACQUIRE) >= MaximumNamespaces ||
90 nextIdentity == ~uint64_t(0))
91 return UtsStatus::NoSpace;
92 __atomic_add_fetch(&namespaceCount, size_t(1), __ATOMIC_ACQ_REL);
93 identity = ++nextIdentity;
94 }
96 if (!copy) {
97 __atomic_sub_fetch(&namespaceCount, size_t(1), __ATOMIC_RELEASE);
98 return UtsStatus::NoMemory;
99 }
100 result = pedigree_std::move(copy);
101 return UtsStatus::Success;
102}
103
104PreparedUtsThread::PreparedUtsThread() = default;
105PreparedUtsThread::~PreparedUtsThread() = default;
106UtsStatus posix_uts_prepare_thread(const UtsRef& source, bool copy,
108 if (!source)
109 return UtsStatus::Missing;
110 UtsRef space = source;
111 if (copy) {
112 const auto status = posix_uts_copy(source, space);
113 if (status != UtsStatus::Success)
114 return status;
115 }
117 if (!prepared)
118 return UtsStatus::NoMemory;
119 prepared.get()->m_Binding = SharedPointer<PosixUtsTaskBinding>::tryAllocate(space);
120 if (!prepared.get()->m_Binding)
121 return UtsStatus::NoMemory;
122 result = pedigree_std::move(prepared);
123 return UtsStatus::Success;
124}
125
126PosixUtsTarget::PosixUtsTarget() = default;
127PosixUtsTarget::PosixUtsTarget(const PosixUtsTarget&) = default;
128PosixUtsTarget& PosixUtsTarget::operator=(const PosixUtsTarget&) = default;
129PosixUtsTarget::~PosixUtsTarget() = default;
130PosixUtsTarget::operator bool() const {
131 return bool(m_View);
132}
133
134PosixNamespaceContext::PosixNamespaceContext()
135 : m_View(SharedPointer<PosixUtsProcessView>::tryAllocate()) {}
136PosixNamespaceContext::~PosixNamespaceContext() {
137 close();
138}
139bool PosixNamespaceContext::valid() const {
140 return bool(m_View);
141}
142void PosixNamespaceContext::attach(Process& process) {
143 if (!m_View)
144 return;
145 LockGuard<Mutex> guard(m_View->lock);
146 assert(!m_View->closed && (!m_View->attached || m_View->processId == process.getId()));
147 m_View->processId = process.getId();
148 m_View->attached = true;
149}
150
151bool PosixNamespaceContext::acquireThread(const Thread& thread, UtsRef& result) const {
152 if (!m_View)
153 return false;
154 UtsRef acquired;
155 {
156 LockGuard<Mutex> guard(m_View->lock);
157 if (m_View->closed || !m_View->attached || thread.getParent()->getId() != m_View->processId)
158 return false;
159 for (auto* node = m_View->first; node; node = node->m_Next) {
160 if (node->m_Binding->localId == localId(thread) && node->m_Binding->live) {
161 acquired = node->m_Binding->space;
162 break;
163 }
164 }
165 }
166 if (!acquired)
167 return false;
168 result = pedigree_std::move(acquired);
169 return true;
170}
171
172void PosixNamespaceContext::publishThread(UniquePointer<PreparedUtsThread>& prepared,
173 Thread& thread, bool leader) {
174 assert(m_View && prepared && prepared.get()->m_Binding);
176 {
177 LockGuard<Mutex> guard(m_View->lock);
178 assert(m_View->attached && !m_View->closed && thread.getParent()->getId() == m_View->processId);
179 // An unstarted task can finish its exit hook before this publication.
180 // Its terminal marker precedes that hook and prevents binding resurrection.
182 return;
183 for (auto* node = m_View->first; node; node = node->m_Next)
184 assert(node->m_Binding->localId != localId(thread));
185 prepared.get()->m_Binding->localId = localId(thread);
186 prepared.get()->m_Binding->taskId = thread.getTaskId();
187 prepared.get()->m_Binding->live = true;
188 prepared.get()->m_Next = m_View->first;
189 if (leader) {
190 oldLeader = pedigree_std::move(m_View->leader);
191 m_View->leader = prepared.get()->m_Binding;
192 }
193 m_View->first = prepared.releaseOwnership();
194 }
195}
196
197void PosixNamespaceContext::promoteExec(const Thread& thread) {
198 assert(bool(m_View));
200 LockGuard<Mutex> guard(m_View->lock);
201 for (auto* node = m_View->first; node; node = node->m_Next) {
202 if (node->m_Binding->localId == localId(thread) && node->m_Binding->live) {
203 node->m_Binding->taskId = thread.getTaskId();
204 oldLeader = pedigree_std::move(m_View->leader);
205 m_View->leader = node->m_Binding;
206 return;
207 }
208 }
209 assert(false);
210}
211
212UtsStatus PosixNamespaceContext::replaceThread(const Thread& thread, const UtsRef& replacement) {
213 if (!m_View || !replacement)
214 return UtsStatus::Missing;
215 UtsRef previous;
216 LockGuard<Mutex> guard(m_View->lock);
217 if (m_View->closed)
218 return UtsStatus::Missing;
219 for (auto* node = m_View->first; node; node = node->m_Next) {
220 if (node->m_Binding->localId == localId(thread) && node->m_Binding->live) {
221 previous = pedigree_std::move(node->m_Binding->space);
222 node->m_Binding->space = replacement;
223 return UtsStatus::Success;
224 }
225 }
226 return UtsStatus::Missing;
227}
228
229void PosixNamespaceContext::retireThread(const Thread& thread) {
230 if (!m_View || __atomic_load_n(&m_View->closed, __ATOMIC_ACQUIRE))
231 return;
232 PreparedUtsThread* retired = nullptr;
233 UtsRef previous;
235 {
236 LockGuard<Mutex> guard(m_View->lock);
237 auto** link = &m_View->first;
238 while (*link) {
239 if ((*link)->m_Binding->localId == localId(thread)) {
240 retired = *link;
241 *link = retired->m_Next;
242 retired->m_Next = nullptr;
243 retired->m_Binding->live = false;
244 previous = pedigree_std::move(retired->m_Binding->space);
245 if (m_View->leader == retired->m_Binding)
246 oldLeader = pedigree_std::move(m_View->leader);
247 break;
248 }
249 link = &(*link)->m_Next;
250 }
251 }
252 delete retired;
253}
254
255void PosixNamespaceContext::close() {
256 if (!m_View || __atomic_load_n(&m_View->closed, __ATOMIC_ACQUIRE))
257 return;
258 PreparedUtsThread* retired;
260 {
261 LockGuard<Mutex> guard(m_View->lock);
262 __atomic_store_n(&m_View->closed, true, __ATOMIC_RELEASE);
263 retired = m_View->first;
264 m_View->first = nullptr;
265 oldLeader = pedigree_std::move(m_View->leader);
266 for (auto* node = retired; node; node = node->m_Next)
267 node->m_Binding->live = false;
268 }
269 while (retired) {
270 auto* next = retired->m_Next;
271 retired->m_Binding->space.reset();
272 delete retired;
273 retired = next;
274 }
275}
276
277bool PosixNamespaceContext::leaderTarget(PosixUtsTarget& result) const {
278 PosixUtsTarget target;
279 if (!m_View)
280 return false;
281 {
282 LockGuard<Mutex> guard(m_View->lock);
283 if (m_View->closed || !m_View->attached)
284 return false;
285 target.m_View = m_View;
286 }
287 result = target;
288 return true;
289}
290
291bool PosixNamespaceContext::taskTarget(size_t taskId, PosixUtsTarget& result) const {
292 PosixUtsTarget target;
293 if (!m_View)
294 return false;
295 {
296 LockGuard<Mutex> guard(m_View->lock);
297 if (m_View->closed || !m_View->attached)
298 return false;
299 for (auto* node = m_View->first; node; node = node->m_Next) {
300 if (node->m_Binding->live && node->m_Binding->taskId == taskId) {
301 target.m_View = m_View;
302 target.m_Task = node->m_Binding;
303 target.m_ExpectedTaskId = taskId;
304 break;
305 }
306 }
307 }
308 if (!target)
309 return false;
310 result = target;
311 return true;
312}
313
314bool PosixNamespaceContext::threadTarget(const Thread& thread, PosixUtsTarget& result) const {
315 return taskTarget(thread.getTaskId(), result);
316}
317
318bool PosixNamespaceContext::nextTaskTarget(size_t afterTaskId, size_t& taskId,
319 PosixUtsTarget& result) const {
320 PosixUtsTarget target;
321 if (!m_View)
322 return false;
323 {
324 LockGuard<Mutex> guard(m_View->lock);
325 if (m_View->closed || !m_View->attached)
326 return false;
327 for (auto* node = m_View->first; node; node = node->m_Next) {
328 const size_t id = node->m_Binding->taskId;
329 if (node->m_Binding->live && id > afterTaskId &&
330 (!target.m_Task || id < target.m_ExpectedTaskId)) {
331 target.m_View = m_View;
332 target.m_Task = node->m_Binding;
333 target.m_ExpectedTaskId = id;
334 }
335 }
336 }
337 if (!target)
338 return false;
339 taskId = target.m_ExpectedTaskId;
340 result = target;
341 return true;
342}
343
344UtsStatus posix_uts_acquire_target(const PosixUtsTarget& target, UtsRef& result) {
345 if (!target.m_View)
346 return UtsStatus::Missing;
347 Thread* current = Processor::information().getCurrentThread();
349 if (!Scheduler::instance().acquireProcessById(lease, target.m_View->processId))
350 return UtsStatus::Missing;
351 if (lease->getType() != Process::Posix || current->getParent()->getType() != Process::Posix)
352 return UtsStatus::Denied;
353 UtsRef acquired;
354 {
355 // Credential mutation and exec share this gate; no target address-space
356 // switch or image ownership is needed to read its namespace metadata.
358 auto* subsystem = static_cast<PosixSubsystem*>(lease->getSubsystem());
359 auto context =
360 subsystem ? subsystem->namespaceContext() : SharedPointer<PosixNamespaceContext>();
361 if (!context || context->m_View != target.m_View)
362 return UtsStatus::Missing;
363 if (current->getParent() != lease.get()) {
365 const auto destination = static_cast<PosixProcess*>(lease.get())->snapshotCredentials();
366 if (!Process::currentFilesystemCredentials(source) || !destination.dumpable ||
367 source.uid != destination.ruid || source.uid != destination.euid ||
368 source.uid != destination.suid || source.gid != destination.rgid ||
369 source.gid != destination.egid || source.gid != destination.sgid)
370 return UtsStatus::Denied;
371 }
372 LockGuard<Mutex> guard(target.m_View->lock);
373 auto binding = target.m_Task ? target.m_Task : target.m_View->leader;
374 if (target.m_View->closed || !binding || !binding->live ||
375 (target.m_Task && binding->taskId != target.m_ExpectedTaskId))
376 return UtsStatus::Missing;
377 acquired = binding->space;
378 }
379 if (!acquired)
380 return UtsStatus::Missing;
381 result = pedigree_std::move(acquired);
382 return UtsStatus::Success;
383}
384
385int posix_uts_error(UtsStatus status) {
386 switch (status) {
387 case UtsStatus::Success:
388 Processor::information().getCurrentThread()->setErrno(0);
389 return 0;
390 case UtsStatus::NoMemory:
391 SYSCALL_ERROR(OutOfMemory);
392 break;
393 case UtsStatus::NoSpace:
394 SYSCALL_ERROR(NoSpaceLeftOnDevice);
395 break;
396 case UtsStatus::Missing:
397 SYSCALL_ERROR(DoesNotExist);
398 break;
399 case UtsStatus::Denied:
400 SYSCALL_ERROR(PermissionDenied);
401 break;
402 case UtsStatus::Invalid:
403 SYSCALL_ERROR(InvalidArgument);
404 break;
405 }
406 return -1;
407}
Memory-mapped file interface.
static MemoryMapManager & instance()
Definition Mutex.h:56
size_t getId()
Definition Process.h:463
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
static SharedPointer< T > tryAllocate(Args...)
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:515
UnwindType getUnwindState()
Definition Thread.h:531
Process * getParent() const
Definition Thread.h:338
size_t getTaskId() const
Definition Thread.h:468
T * releaseOwnership() noexcept
Definition Pointers.h:106