The Pedigree Project 0.1
descriptor-path.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "descriptor-path.h"
3#include "pedigree/kernel/process/Thread.h"
4#include "pedigree/kernel/processor/Processor.h"
5#include "pedigree/kernel/processor/ProcessorInformation.h"
6#include "pedigree/kernel/syscallError.h"
7#include "pedigree/kernel/utilities/StaticString.h"
8#include "pedigree/kernel/utilities/utility.h"
9
10#include "DevFs.h"
11#include "PosixSubsystem.h"
12#include "ProcFs.h"
13#include "fanotify-syscalls.h"
14#include "modules/system/vfs/MountView.h"
15#include "modules/system/vfs/Symlink.h"
16#include "signalfd-syscalls.h"
17#include "timerfd-syscalls.h"
18
19namespace {
20constexpr uint32_t DirectoryPermissions = FILE_UR | FILE_UX | FILE_GR | FILE_GX | FILE_OR | FILE_OX;
21constexpr uint32_t LinkPermissions =
22 FILE_UR | FILE_UW | FILE_UX | FILE_GR | FILE_GW | FILE_GX | FILE_OR | FILE_OW | FILE_OX;
23
24class DescriptorOwner {
25 public:
26 DescriptorOwner(size_t pid, const SharedPointer<PosixNamespaceContext>& identity)
27 : m_Pid(pid), m_Identity(identity) {}
28
29 PosixSubsystem* acquire() const {
30 auto* thread = Processor::information().getCurrentThread();
31 auto* process = thread ? thread->getParent() : nullptr;
32 if (!process || process->getType() != Process::Posix) {
33 // Cross-process fd access needs a ptrace access policy; self access
34 // remains safe without exposing another process's retained objects.
35 SYSCALL_ERROR(PermissionDenied);
36 return nullptr;
37 }
38 auto* subsystem = static_cast<PosixSubsystem*>(process->getSubsystem());
39 if (!subsystem || subsystem->namespaceContext().get() != m_Identity.get()) {
40 SYSCALL_ERROR(DoesNotExist);
41 return nullptr;
42 }
43 return subsystem;
44 }
45
46 private:
47 size_t m_Pid;
49};
50
51bool descriptorNumber(const StringView& name, size_t& number) {
52 if (!name.length() || (name.length() > 1 && name[0] == '0'))
53 return false;
54 number = 0;
55 for (size_t i = 0; i < name.length(); ++i) {
56 const char ch = name[i];
57 if (ch < '0' || ch > '9' || number > (~size_t(0) - (ch - '0')) / 10)
58 return false;
59 number = number * 10 + ch - '0';
60 }
61 return true;
62}
63
64class DescriptorLink final : public Symlink {
65 public:
66 DescriptorLink(ProcFs& filesystem, File* parent, const String& name, const DescriptorOwner& owner,
67 size_t fd)
68 : Symlink(name, 0, 0, 0, filesystem.getNextInode(), &filesystem, 0, parent),
69 m_Owner(owner),
70 m_Fd(fd) {
71 setPermissions(LinkPermissions);
72 }
73
74 bool isPathLink() const override {
75 return true;
76 }
77
78 bool followPath(FilesystemPathRef& result) override {
79 DescriptorLease descriptor;
80 if (!acquire(descriptor))
81 return false;
82 auto path = descriptor->openingPath();
83 if (path) {
84 result = path;
85 return true;
86 }
87 auto* view = VFS::instance().mountView();
88 File* file = descriptor->getFile();
89 if (!view || !file) {
90 SYSCALL_ERROR(NoSuchDevice);
91 return false;
92 }
93 return view->anonymousPath(file, result);
94 }
95
97 // File-only traversal cannot preserve the descriptor's opening attachment.
98 SYSCALL_ERROR(OperationNotSupported);
99 return nullptr;
100 }
101
102 int followLink(char* buffer, size_t length) override {
103 DescriptorLease descriptor;
104 if (!acquire(descriptor))
105 return -1;
106 String target;
107 if (descriptor->getTimerFdImpl())
108 target.assign("anon_inode:[timerfd]");
109 else if (descriptor->getSignalFdImpl())
110 target.assign("anon_inode:[signalfd]");
111 else if (descriptor->getFanotifyImpl())
112 target.assign("anon_inode:[fanotify]");
113 else if (File* file = descriptor->getFile()) {
114 auto path = descriptor->openingPath();
115 auto* view = VFS::instance().mountView();
116 if (path && view && view->attachmentId(path)) {
117 auto context =
118 Processor::information().getCurrentThread()->getParent()->acquireFilesystemContext();
120 if (!context || !context->snapshot(snapshot) || !view->formatPath(snapshot, path, target))
121 return -1;
122 } else {
123 target = String("/");
124 target += file->getName();
125 }
126 if (!file->getAttributes().links)
127 target += " (deleted)";
128 } else {
129 SYSCALL_ERROR(NoSuchDevice);
130 return -1;
131 }
132 const size_t copied = length < target.length() ? length : target.length();
133 MemoryCopy(buffer, target.cstr(), copied);
134 return static_cast<int>(copied);
135 }
136
137 private:
138 bool acquire(DescriptorLease& descriptor) const {
139 auto* subsystem = m_Owner.acquire();
140 if (!subsystem)
141 return false;
142 if (!subsystem->acquireFileDescriptor(m_Fd, descriptor)) {
143 SYSCALL_ERROR(DoesNotExist);
144 return false;
145 }
146 return true;
147 }
148 DescriptorOwner m_Owner;
149 size_t m_Fd;
150};
151
152class DescriptorDirectory final : public ProcFsDirectory {
153 public:
154 DescriptorDirectory(ProcFs& filesystem, File* parent, size_t pid,
156 : ProcFsDirectory(String("fd"), 0, 0, 0, filesystem.getNextInode(), &filesystem, 0, parent),
157 m_Owner(pid, identity) {
158 setPermissions(DirectoryPermissions);
159 }
160
161 protected:
162 bool cacheResolvedChildren() const override {
163 return false;
164 }
165
166 LookupStatus resolveChild(const StringView& name, File*& child) override {
167 child = nullptr;
168 size_t fd;
169 if (!descriptorNumber(name, fd))
170 return LookupStatus::NotFound;
171 auto* subsystem = m_Owner.acquire();
172 if (!subsystem)
173 return LookupStatus::NotFound;
174 DescriptorLease descriptor;
175 if (!subsystem->acquireFileDescriptor(fd, descriptor))
176 return LookupStatus::NotFound;
177 child =
178 new DescriptorLink(*static_cast<ProcFs*>(getFilesystem()), this, String(name), m_Owner, fd);
179 return child ? LookupStatus::Found : LookupStatus::IoError;
180 }
181
182 ReadStatus readDirectory(uint64_t& cookie, DirectoryEntryEmitter emitter,
183 void* context) override {
184 auto* subsystem = m_Owner.acquire();
185 if (!subsystem)
186 return ReadStatus::IoError;
187 size_t fd;
188 DescriptorLease descriptor;
189 while (subsystem->acquireNextFileDescriptor(cookie, fd, descriptor)) {
191 name.append(fd);
192 DirectoryEntryView entry{StringView(name, name.length()),
193 static_cast<ProcFs*>(getFilesystem())->getNextInode(),
194 EntryType::Symlink, cookie, fd + 1};
195 if (!emitter(context, entry))
196 return ReadStatus::Stopped;
197 cookie = fd + 1;
198 }
199 return ReadStatus::Complete;
200 }
201
202 private:
203 DescriptorOwner m_Owner;
204};
205
206class DevFdLink final : public Symlink {
207 public:
208 DevFdLink(DevFs& filesystem, File* parent)
209 : Symlink(String("fd"), 0, 0, 0, filesystem.getNextInode(), &filesystem, 12, parent) {
210 m_sTarget.assign("/proc/self/fd");
211 setPermissions(LinkPermissions);
212 }
213};
214} // namespace
215
216File* posix_make_descriptor_directory(ProcFs& filesystem, File* parent, size_t pid,
218 return new DescriptorDirectory(filesystem, parent, pid, identity);
219}
220
221File* posix_make_dev_fd_link(DevFs& filesystem, File* parent) {
222 return new DevFdLink(filesystem, parent);
223}
Definition DevFs.h:269
virtual bool cacheResolvedChildren() const
Definition Directory.h:299
virtual ReadStatus readDirectory(uint64_t &cookie, DirectoryEntryEmitter emitter, void *context)
virtual LookupStatus resolveChild(const StringView &name, File *&child)
Definition File.h:75
String getName() const
Definition File.cc:782
Process * getParent()
Definition Process.h:620
static ProcessorInformation & information()
static VFS & instance()
Definition VFS.cc:311