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