The Pedigree Project 0.1
ProcFs-namespaces.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/process/Scheduler.h"
3#include "pedigree/kernel/process/Thread.h"
4#include "pedigree/kernel/processor/Processor.h"
5#include "pedigree/kernel/syscallError.h"
6#include "pedigree/kernel/utilities/StaticString.h"
7#include "pedigree/kernel/utilities/utility.h"
8
9#include "PosixProcess.h"
10#include "PosixSubsystem.h"
11#include "ProcFs.h"
12#include "descriptor-path.h"
13#include "modules/system/vfs/MountView.h"
14#include "modules/system/vfs/Symlink.h"
15#include "modules/system/vfs/VFS.h"
16#include "namespace-file.h"
17#include "uts-namespace.h"
18
19namespace {
20constexpr uint32_t DirectoryPermissions = FILE_UR | FILE_UX | FILE_GR | FILE_GX | FILE_OR | FILE_OX;
21
22bool taskNumber(const StringView& name, size_t& result) {
23 if (!name.length() || name[0] == '0')
24 return false;
25 size_t value = 0;
26 for (size_t i = 0; i < name.length(); ++i) {
27 if (name[i] < '0' || name[i] > '9')
28 return false;
29 const size_t digit = name[i] - '0';
30 if (value > (~size_t(0) - digit) / 10)
31 return false;
32 value = value * 10 + digit;
33 }
34 result = value;
35 return true;
36}
37
38class NamespaceLink final : public Symlink {
39 public:
40 NamespaceLink(ProcFs& filesystem, uintptr_t inode, File* parent, const PosixUtsTarget& target)
41 : Symlink(String("uts"), 0, 0, 0, inode, &filesystem, 0, parent), m_Target(target) {
42 setPermissions(DirectoryPermissions | FILE_UW | FILE_GW | FILE_OW);
43 }
44
45 bool isPathLink() const override {
46 return true;
47 }
48
49 bool followPath(FilesystemPathRef& result) override {
50 UtsRef space;
51 UtsStatus status = posix_uts_acquire_target(m_Target, space);
52 if (status != UtsStatus::Success) {
53 posix_uts_error(status);
54 return false;
55 }
56 RetainedFile file;
57 status = posix_uts_make_file(space, file);
58 if (status != UtsStatus::Success) {
59 posix_uts_error(status);
60 return false;
61 }
62 auto* view = VFS::instance().mountView();
63 if (!view) {
64 SYSCALL_ERROR(NoSuchDevice);
65 return false;
66 }
67 return view->anonymousPath(file.get(), result);
68 }
69
70 int followLink(char* buffer, size_t length) override {
71 UtsRef space;
72 const UtsStatus status = posix_uts_acquire_target(m_Target, space);
73 if (status != UtsStatus::Success)
74 return posix_uts_error(status);
75 NormalStaticString name("uts:[");
76 name.append(space->identity());
77 name.append(']');
78 const size_t copied = length < name.length() ? length : name.length();
79 MemoryCopy(buffer, static_cast<const char*>(name), copied);
80 return static_cast<int>(copied);
81 }
82
83 private:
84 PosixUtsTarget m_Target;
85};
86
87class NamespaceDirectory final : public ProcFsDirectory {
88 public:
89 NamespaceDirectory(ProcFs& filesystem, File* parent, const PosixUtsTarget& target)
90 : ProcFsDirectory(String("ns"), 0, 0, 0, filesystem.getNextInode(), &filesystem, 0, parent),
91 m_Target(target),
92 m_LinkInode(filesystem.getNextInode()) {
93 setPermissions(DirectoryPermissions);
94 }
95
96 protected:
97 LookupStatus resolveChild(const StringView& name, File*& child) override {
98 child = nullptr;
99 if (!(name == "uts"))
100 return LookupStatus::NotFound;
101 child = new NamespaceLink(*static_cast<ProcFs*>(getFilesystem()), m_LinkInode, this, m_Target);
102 return child ? LookupStatus::Found : LookupStatus::IoError;
103 }
104
105 ReadStatus readDirectory(uint64_t& cookie, DirectoryEntryEmitter emitter,
106 void* context) override {
107 if (!cookie) {
108 DirectoryEntryView entry{StringView("uts"), m_LinkInode, EntryType::Symlink, 0, 1};
109 if (!emitter(context, entry))
110 return ReadStatus::Stopped;
111 cookie = 1;
112 }
113 return ReadStatus::Complete;
114 }
115
116 private:
117 PosixUtsTarget m_Target;
118 const uintptr_t m_LinkInode;
119};
120
121class TaskDirectory final : public ProcFsDirectory {
122 public:
123 TaskDirectory(ProcFs& filesystem, File* parent, size_t pid,
125 : ProcFsDirectory(String("task"), 0, 0, 0, filesystem.getNextInode(), &filesystem, 0, parent),
126 m_Context(context),
127 m_Pid(pid) {
128 setPermissions(DirectoryPermissions);
129 }
130
131 void invalidate(size_t taskId) {
133 name.append(taskId);
134 ChildLease previous;
135 if (lookupChild(HashedStringView(name), previous) == LookupStatus::Found) {
136 // A retained task directory can outlive this cache and its process.
137 // Detachment retains its parent before dropping the publication ref.
138 static_cast<ProcFsDirectory*>(previous.get())->markDetached();
139 }
141 }
142
143 protected:
144 bool cacheResolvedChildren() const override {
145 return false;
146 }
147
148 LookupStatus resolveChild(const StringView& name, File*& child) override {
149 child = nullptr;
150 size_t taskId = 0;
151 PosixUtsTarget target;
152 auto& filesystem = *static_cast<ProcFs*>(getFilesystem());
155 if (!taskNumber(name, taskId) ||
156 !Scheduler::instance().acquireProcessByUserspaceId(process, m_Pid,
157 filesystem.pidNamespace().get()) ||
158 !process->acquireThreadByUserspaceId(thread, taskId, filesystem.pidNamespace().get()) ||
159 !m_Context->taskTarget(thread->getTaskId(), target)) {
160 return LookupStatus::NotFound;
161 }
162 auto* directory =
163 new ProcFsDirectory(String(name), 0, 0, 0, filesystem.getNextInode(), &filesystem, 0, this);
164 if (!directory)
165 return LookupStatus::IoError;
166 directory->setPermissions(DirectoryPermissions);
167 auto* namespaces = new NamespaceDirectory(filesystem, directory, target);
168 if (!namespaces) {
169 delete directory;
170 return LookupStatus::IoError;
171 }
172 directory->addEntry(String("ns"), namespaces);
173 child = directory;
174 return LookupStatus::Found;
175 }
176
177 ReadStatus readDirectory(uint64_t& cookie, DirectoryEntryEmitter emitter,
178 void* context) override {
179 auto& filesystem = *static_cast<ProcFs*>(getFilesystem());
181 if (!Scheduler::instance().acquireProcessByUserspaceId(process, m_Pid,
182 filesystem.pidNamespace().get())) {
183 return ReadStatus::Complete;
184 }
185 while (true) {
186 size_t selected = 0;
187 for (size_t i = 0; i < process->getNumThreads(); ++i) {
189 if (!process->acquireThread(thread, i)) {
190 continue;
191 }
192 const size_t id = thread->getUserspaceTaskId(filesystem.pidNamespace().get());
193 if (id > cookie && (!selected || id < selected)) {
194 selected = id;
195 }
196 }
197 if (!selected) {
198 return ReadStatus::Complete;
199 }
201 name.append(selected);
202 ChildLease child;
203 const LookupStatus status = lookupChild(HashedStringView(name), child);
204 if (status == LookupStatus::IoError)
205 return ReadStatus::IoError;
206 if (status == LookupStatus::Found) {
207 DirectoryEntryView entry{StringView(name, name.length()), child.get()->getInode(),
208 EntryType::Directory, cookie, selected};
209 if (!emitter(context, entry))
210 return ReadStatus::Stopped;
211 }
212 cookie = selected;
213 }
214 }
215
216 private:
218 size_t m_Pid;
219};
220
221class ProcessPathLink final : public Symlink {
222 public:
223 enum class Kind { Executable, Cwd, Root };
224
225 ProcessPathLink(ProcFs& filesystem, File* parent, size_t pid, Kind kind)
226 : Symlink(String(kind == Kind::Executable ? "exe"
227 : kind == Kind::Cwd ? "cwd"
228 : "root"),
229 0, 0, 0, filesystem.getNextInode(), &filesystem, 0, parent),
230 m_Pid(pid),
231 m_Kind(kind) {
232 setPermissions(DirectoryPermissions | FILE_UW | FILE_GW | FILE_OW);
233 }
234
235 bool isPathLink() const override {
236 return true;
237 }
238
239 bool followPath(FilesystemPathRef& result) override {
241 if (!Scheduler::instance().acquireProcessByUserspaceId(
242 process, m_Pid, static_cast<ProcFs*>(getFilesystem())->pidNamespace().get()) ||
243 process->getType() != Process::Posix) {
244 SYSCALL_ERROR(DoesNotExist);
245 return false;
246 }
247 if (m_Kind == Kind::Executable) {
248 auto* subsystem = static_cast<PosixSubsystem*>(process->getSubsystem());
249 return subsystem && subsystem->executablePath(result);
250 }
251 auto context = process->acquireFilesystemContext();
253 if (!context || !context->snapshot(snapshot)) {
254 SYSCALL_ERROR(DoesNotExist);
255 return false;
256 }
257 result = m_Kind == Kind::Cwd ? snapshot.cwd : snapshot.root;
258 return static_cast<bool>(result);
259 }
260
261 int followLink(char* buffer, size_t length) override {
263 if (!followPath(path))
264 return -1;
266 if (!Scheduler::instance().acquireProcessByUserspaceId(
267 process, m_Pid, static_cast<ProcFs*>(getFilesystem())->pidNamespace().get())) {
268 SYSCALL_ERROR(DoesNotExist);
269 return -1;
270 }
271 auto context = process->acquireFilesystemContext();
273 auto* view = VFS::instance().mountView();
274 String target;
275 if (!context || !context->snapshot(snapshot) || !view ||
276 !view->formatPath(snapshot, path, target)) {
277 SYSCALL_ERROR(DoesNotExist);
278 return -1;
279 }
280 const size_t copied = length < target.length() ? length : target.length();
281 MemoryCopy(buffer, target.cstr(), copied);
282 return static_cast<int>(copied);
283 }
284
285 private:
286 size_t m_Pid;
287 Kind m_Kind;
288};
289
290class ProcessDirectory final : public ProcFsDirectory {
291 public:
292 ProcessDirectory(ProcFs& filesystem, const String& name,
294 : ProcFsDirectory(name, 0, 0, 0, filesystem.getNextInode(), &filesystem, 0,
295 filesystem.getRoot()),
296 m_Context(context) {}
297
298 bool initialise(ProcFs& filesystem, size_t pid) {
299 if (!m_Context)
300 return true;
301 PosixUtsTarget leader;
302 if (!m_Context->leaderTarget(leader))
303 return false;
304 auto* namespaces = new NamespaceDirectory(filesystem, this, leader);
305 if (!namespaces)
306 return false;
307 addEntry(String("ns"), namespaces);
308 m_Tasks = new TaskDirectory(filesystem, this, pid, m_Context);
309 if (!m_Tasks)
310 return false;
311 addEntry(String("task"), m_Tasks);
312 auto* descriptors = posix_make_descriptor_directory(filesystem, this, pid, m_Context);
313 if (!descriptors)
314 return false;
315 addEntry(String("fd"), descriptors);
316 auto* executable =
317 new ProcessPathLink(filesystem, this, pid, ProcessPathLink::Kind::Executable);
318 auto* cwd = new ProcessPathLink(filesystem, this, pid, ProcessPathLink::Kind::Cwd);
319 auto* root = new ProcessPathLink(filesystem, this, pid, ProcessPathLink::Kind::Root);
320 if (!executable || !cwd || !root) {
321 delete executable;
322 delete cwd;
323 delete root;
324 return false;
325 }
326 addEntry(executable->getName(), executable);
327 addEntry(cwd->getName(), cwd);
328 addEntry(root->getName(), root);
329 return procfsAddProcessStatusFiles(filesystem, *this, pid);
330 }
331
332 void invalidate(const SharedPointer<PosixNamespaceContext>& context, size_t taskId) {
333 if (context.get() != m_Context.get() || !m_Tasks)
334 return;
335 m_Tasks->invalidate(taskId);
336 }
337
338 private:
340 TaskDirectory* m_Tasks = nullptr;
341};
342
343class SelfLink final : public Symlink {
344 public:
345 SelfLink(ProcFs& filesystem, bool thread)
346 : Symlink(String(thread ? "thread-self" : "self"), 0, 0, 0, filesystem.getNextInode(),
347 &filesystem, 0, filesystem.getRoot()),
348 m_Thread(thread) {
349 setPermissions(DirectoryPermissions | FILE_UW | FILE_GW | FILE_OW);
350 }
351
352 int followLink(char* buffer, size_t length) override {
353 NormalStaticString target;
354 if (!name(target)) {
355 return -1;
356 }
357 const size_t copied = length < target.length() ? length : target.length();
358 MemoryCopy(buffer, static_cast<const char*>(target), copied);
359 return static_cast<int>(copied);
360 }
361
362 private:
363 bool name(NormalStaticString& result) const {
364 auto* thread = Processor::information().getCurrentThread();
365 auto* process = thread ? thread->getParent() : nullptr;
366 if (!process || process->getType() != Process::Posix) {
367 SYSCALL_ERROR(DoesNotExist);
368 return false;
369 }
370 result.append(
371 process->getUserspaceId(static_cast<ProcFs*>(getFilesystem())->pidNamespace().get()));
372 if (m_Thread) {
373 result.append("/task/");
374 result.append(
375 thread->getUserspaceTaskId(static_cast<ProcFs*>(getFilesystem())->pidNamespace().get()));
376 }
377 return true;
378 }
379 const bool m_Thread;
380};
381} // namespace
382
383bool ProcFs::initialiseNamespaceLinks() {
384 auto* self = new SelfLink(*this, false);
385 auto* threadSelf = new SelfLink(*this, true);
386 if (!self || !threadSelf) {
387 delete self;
388 delete threadSelf;
389 return false;
390 }
391 m_pRoot->addEntry(self->getName(), self);
392 m_pRoot->addEntry(threadSelf->getName(), threadSelf);
393 return true;
394}
395
396ProcFsDirectory* ProcFs::createProcessDirectory(PosixProcess* process) {
397 auto* subsystem = static_cast<PosixSubsystem*>(process->getSubsystem());
398 auto context = subsystem ? subsystem->namespaceContext() : SharedPointer<PosixNamespaceContext>();
400 name.append(process->getUserspaceId(m_PidNamespace.get()));
401 auto* directory = new ProcessDirectory(*this, String(name, name.length()), context);
402 if (directory && !directory->initialise(*this, process->getUserspaceId(m_PidNamespace.get()))) {
403 delete directory;
404 directory = nullptr;
405 }
406 if (directory && !procfsAddUserMaps(*this, *directory, *process)) {
407 delete directory;
408 directory = nullptr;
409 }
410 return directory;
411}
412
413void ProcFs::invalidateNamespaceTask(const SharedPointer<PosixNamespaceContext>& context,
414 size_t pid, size_t taskId) {
415 if (!context || !pid || !taskId)
416 return;
418 name.append(pid);
419 Directory::ChildLease directory;
420 if (m_pRoot->lookupChild(HashedStringView(name), directory) != Directory::LookupStatus::Found)
421 return;
422 // Numeric root entries are created only by createProcessDirectory; retaining
423 // that entry also retains its resident task directory during invalidation.
424 static_cast<ProcessDirectory*>(directory.get())->invalidate(context, taskId);
425}
426
427void procfsInvalidateNamespaceTask(const SharedPointer<PosixNamespaceContext>& context, size_t pid,
428 size_t taskId) {
429 auto& vfs = VFS::instance();
430 Filesystem* key = vfs.getFilesystemAt(String("/media/proc"));
432 if (!vfs.acquireMount(key, mount))
433 return;
434 static_cast<ProcFs*>(mount.filesystem())->invalidateNamespaceTask(context, pid, taskId);
435}
virtual bool cacheResolvedChildren() const
Definition Directory.h:299
void remove(const HashedStringView &s)
Definition Directory.cc:597
virtual ReadStatus readDirectory(uint64_t &cookie, DirectoryEntryEmitter emitter, void *context)
virtual LookupStatus resolveChild(const StringView &name, File *&child)
MUST_USE_RESULT LookupStatus lookupChild(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:355
Definition File.h:75
size_t getUserspaceId() const
Definition Process.h:504
size_t getNumThreads()
Definition Process.cc:1380
MUST_USE_RESULT bool acquireThread(ThreadLease &lease, size_t n)
Definition Process.cc:1385
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
T * get() const
Process * getParent() const
Definition Thread.h:340
size_t getUserspaceTaskId(const UserspacePidNamespace *space=nullptr) const
Definition Thread.cc:3743
static VFS & instance()
Definition VFS.cc:311