The Pedigree Project 0.1
MountView.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/Log.h"
3#include "pedigree/kernel/syscallError.h"
4
5#include "MountView-internal.h"
6#if THREADS && !defined(STANDALONE_MUTEXES)
7#include "pedigree/kernel/process/Thread.h"
8#include "pedigree/kernel/processor/Processor.h"
9#include "pedigree/kernel/processor/ProcessorInformation.h"
10#endif
11
12VfsNodeReference::VfsNodeReference(VfsNodeReference&& other) noexcept
13 : m_Node(other.m_Node), m_Tracked(other.m_Tracked) {
14 other.m_Node = nullptr;
15 other.m_Tracked = false;
16}
17VfsNodeReference& VfsNodeReference::operator=(VfsNodeReference&& other) noexcept {
18 if (this != &other) {
19 reset();
20 m_Node = other.m_Node;
21 m_Tracked = other.m_Tracked;
22 other.m_Node = nullptr;
23 other.m_Tracked = false;
24 }
25 return *this;
26}
27VfsNodeReference::~VfsNodeReference() {
28 reset();
29}
30void VfsNodeReference::reset() {
31#if THREADS && !defined(STANDALONE_MUTEXES)
32 TerminationDeferral lifetime;
33#endif
34 File* node = m_Node;
35 const bool tracked = m_Tracked;
36 m_Node = nullptr;
37 m_Tracked = false;
38 if (tracked)
39 node->releaseVfsReference();
40}
41bool VfsNodeReference::retain(File* node, Filesystem* backing) {
42 if (m_Node || !node || !backing || node->getFilesystem() != backing)
43 return false;
44 m_Tracked = node->retainVfsReference();
45 if (!m_Tracked && node != backing->getRoot())
46 return false;
47 m_Node = node;
48 return true;
49}
50
51bool VfsNodeReference::retainAnonymous(File* node) {
52 if (m_Node || !node || node->isDirectory() || !node->retainVfsReference())
53 return false;
54 m_Node = node;
55 m_Tracked = true;
56 return true;
57}
58
59VfsAttachment::~VfsAttachment() {
60#if THREADS && !defined(STANDALONE_MUTEXES)
61 TerminationDeferral lifetime;
62#endif
63 // Keep our pin until admission closes, so retirement cannot delete storage
64 // while this attachment still owns it. Graph rows retire outside graph/writer locks.
65 if (owningRegistry && !owningRegistry->retireOwnedFilesystem(backing.filesystem()))
66 FATAL("Owned attachment lost its backing registration");
67 backing.reset();
68}
69
70VfsPath::VfsPath(VfsMountView& owner, const VfsAttachmentRef& mounted, VfsNodeReference&& retained)
71 : kind(Kind::Mounted), view(owner), attachment(mounted), file(pedigree_std::move(retained)) {
72 attachment->paths += 1;
73}
74VfsPath::VfsPath(VfsMountView& owner, VfsNodeReference&& retained)
75 : kind(Kind::Anonymous), view(owner), attachment(), file(pedigree_std::move(retained)) {
76 view.m_State->anonymousPaths += 1;
77}
78VfsPath::~VfsPath() {
79#if THREADS && !defined(STANDALONE_MUTEXES)
80 TerminationDeferral lifetime;
81 Thread* thread = Processor::information().getCurrentThread();
82 const size_t error = thread ? thread->getErrno() : 0;
83#endif
84 file.reset();
85 if (kind == Kind::Mounted) {
86 if ((attachment->paths -= 1) == 0)
87 view.m_State->reapDetached();
88 } else {
89 view.m_State->anonymousPaths -= 1;
90 }
91 attachment.reset();
92#if THREADS && !defined(STANDALONE_MUTEXES)
93 if (thread)
94 thread->setErrno(error);
95#endif
96}
97
98VfsMountView::VfsMountView(VFS& vfs) : m_State(new State(*this)), m_Vfs(vfs) {}
99VfsMountView::~VfsMountView() {
100 delete m_State;
101}
102VfsMountView::State::~State() {
103 if (contexts || anonymousPaths)
104 FATAL("Mount view destroyed with retained filesystem owners");
105 auto* row = attachments;
106 attachments = nullptr;
107 while (row) {
108 if (row->attachment->paths)
109 FATAL("Mount view destroyed with retained paths");
110 auto* next = row->next;
111 delete row;
112 row = next;
113 }
114}
115
116VfsAttachmentRow* VfsMountView::State::find(uint64_t id) const {
117 for (auto* row = attachments; row; row = row->next)
118 if (row->attachment->id == id)
119 return row;
120 return nullptr;
121}
122VfsAttachmentRow* VfsMountView::State::at(const VfsPath& path) const {
123 for (auto* row = attachments; row; row = row->next)
124 if (row->parent.get() == path.attachment.get() && row->covered &&
125 row->covered->get() == path.node())
126 return row;
127 return nullptr;
128}
129VfsPath* VfsMountView::State::nodePath(const FilesystemPathRef& reference) const {
130 auto* result = reference && reference->provider() == &view
131 ? static_cast<VfsPath*>(reference.get())
132 : nullptr;
133 return result && &result->view == &view ? result : nullptr;
134}
135VfsPath* VfsMountView::State::path(const FilesystemPathRef& reference) const {
136 auto* selected = nodePath(reference);
137 return selected && selected->kind == VfsPath::Kind::Mounted ? selected : nullptr;
138}
139bool VfsMountView::State::contains(const FilesystemPathRef& reference) const {
140 auto* p = path(reference);
141 if (!p)
142 return false;
143 auto* row = find(p->attachment->id);
144 size_t remaining = 1;
145 for (auto* entry = attachments; entry; entry = entry->next)
146 ++remaining;
147 while (row && remaining--) {
148 if (row->attachment->id == rootId)
149 return true;
150 row = row->parent ? find(row->parent->id) : nullptr;
151 }
152 return false;
153}
154bool VfsMountView::State::context(const FilesystemContextRef& reference,
155 VfsFilesystemContext*& result) const {
156 for (auto* row = contexts; row; row = row->next) {
157 if (row->context.get() == reference.get()) {
158 result = static_cast<VfsFilesystemContext*>(reference.get());
159 return true;
160 }
161 }
162 return false;
163}
164bool VfsMountView::State::makePath(const VfsAttachmentRef& attachment, File* node,
165 FilesystemPathRef& result) {
166 VfsNodeReference retained;
167 if (!attachment || !retained.retain(node, attachment->backing.filesystem())) {
168 SYSCALL_ERROR(DoesNotExist);
169 return false;
170 }
171 auto path =
172 FilesystemPathRef::tryAdopt(new VfsPath(view, attachment, pedigree_std::move(retained)));
173 if (!path) {
174 SYSCALL_ERROR(OutOfMemory);
175 return false;
176 }
177 result = pedigree_std::move(path);
178 return true;
179}
180
181bool VfsMountView::initialise(Filesystem* bootRoot) {
182 if (!m_State || !bootRoot) {
183 SYSCALL_ERROR(OutOfMemory);
184 return false;
185 }
187 if (!m_Vfs.pinFilesystem(bootRoot, pin)) {
188 SYSCALL_ERROR(DoesNotExist);
189 return false;
190 }
191 auto attachment = VfsAttachmentRef::tryAdopt(new VfsAttachment(pedigree_std::move(pin), 1));
193 if (!attachment || !row) {
194 SYSCALL_ERROR(OutOfMemory);
195 return false;
196 }
197 row.get()->attachment = attachment;
198 VFS::NamespaceMutation writer(m_Vfs);
199 LockGuard<Mutex> guard(m_State->graph);
200 if (m_State->rootId) {
201 SYSCALL_ERROR(DeviceBusy);
202 return false;
203 }
204 m_State->rootId = 1;
205 m_State->nextId = 2;
206 m_State->attachments = row.releaseOwnership();
207 return true;
208}
209
210#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
211bool VfsMountView::quiescentForHostedTest() const {
212 if (!m_State)
213 return true;
214 LockGuard<Mutex> guard(m_State->graph);
215 if (m_State->contexts || m_State->anonymousPaths)
216 return false;
217 for (auto* row = m_State->attachments; row; row = row->next)
218 if (row->attachment->paths)
219 return false;
220 return true;
221}
222#endif
223
224bool VfsMountView::bootRootPath(FilesystemPathRef& result) {
225 VfsAttachmentRef attachment;
226 if (!m_State)
227 return false;
228 {
229 LockGuard<Mutex> guard(m_State->graph);
230 auto* row = m_State->find(m_State->rootId);
231 if (!row)
232 return false;
233 attachment = row->attachment;
234 }
235 return m_State->makePath(attachment, attachment->root, result);
236}
237bool VfsMountView::createBootContext(FilesystemContextOwner& result) {
238 return m_State && m_State->createContext(nullptr, result);
239}
240bool VfsMountView::State::createContext(const VfsFilesystemContext* parent,
241 FilesystemContextOwner& result) {
242#if THREADS && !defined(STANDALONE_MUTEXES)
243 TerminationDeferral lifetime;
244#endif
245 if (result) {
246 SYSCALL_ERROR(InvalidArgument);
247 return false;
248 }
249 auto reference = FilesystemContextRef::tryAdopt(new VfsFilesystemContext(view));
251 if (!reference || !row) {
252 SYSCALL_ERROR(OutOfMemory);
253 return false;
254 }
255 auto* created = static_cast<VfsFilesystemContext*>(reference.get());
256 row.get()->context = reference;
257 FilesystemPathRef initial;
258 VFS::NamespaceMutation writer(view.m_Vfs);
259 if (!parent) {
260 // Boot enrollment snapshots the current view root under the same writer
261 // admission as publication, so it cannot resurrect a root moved by pivot.
262 VfsAttachmentRef attachment;
263 {
264 LockGuard<Mutex> guard(graph);
265 auto* root = find(rootId);
266 if (!root) {
267 SYSCALL_ERROR(DoesNotExist);
268 return false;
269 }
270 attachment = root->attachment;
271 }
272 if (!makePath(attachment, attachment->root, initial))
273 return false;
274 }
275 {
276 LockGuard<Mutex> guard(graph);
277 if (parent && !parent->registration) {
278 SYSCALL_ERROR(DoesNotExist);
279 return false;
280 }
281 created->root = parent ? parent->root : initial;
282 created->cwd = parent ? parent->cwd : initial;
283 created->registration = row.get();
284 row.get()->next = contexts;
285 contexts = row.releaseOwnership();
286 ++contextCount;
287 }
288 result = FilesystemContextOwner::adopt(pedigree_std::move(reference));
289 return true;
290}
291
292VfsFilesystemContext::~VfsFilesystemContext() {
293 if (registration)
294 FATAL("Filesystem context destroyed before owner retirement");
295}
296bool VfsFilesystemContext::snapshot(FilesystemContextSnapshot& result) const {
297 FilesystemContextSnapshot replacement;
298 {
299 LockGuard<Mutex> guard(view.m_State->graph);
300 if (!registration)
301 return false;
302 replacement.root = root;
303 replacement.cwd = cwd;
304 replacement.contextGeneration = generation;
305 replacement.topologyGeneration = view.m_State->topology;
306 }
307 result = pedigree_std::move(replacement);
308 return true;
309}
310bool VfsFilesystemContext::forkForProcess(FilesystemContextOwner& result) const {
311 return view.m_State->createContext(this, result);
312}
313void VfsFilesystemContext::retireProcessOwner() {
314#if THREADS && !defined(STANDALONE_MUTEXES)
315 TerminationDeferral lifetime;
316#endif
317 VfsContextRow* retired = nullptr;
318 FilesystemPathRef retiredRoot, retiredCwd;
319 {
320 VFS::NamespaceMutation writer(view.m_Vfs);
321 LockGuard<Mutex> guard(view.m_State->graph);
322 if (!registration)
323 return;
324 auto** link = &view.m_State->contexts;
325 while (*link && *link != registration)
326 link = &(*link)->next;
327 if (!*link)
328 FATAL("Missing filesystem context enrollment");
329 retired = *link;
330 *link = retired->next;
331 registration = nullptr;
332 --view.m_State->contextCount;
333 retiredRoot = pedigree_std::move(root);
334 retiredCwd = pedigree_std::move(cwd);
335 }
336 delete retired;
337}
338
339bool VfsMountView::changeCwd(const FilesystemContextRef& context, const FilesystemPathRef& path) {
340 if (!m_State->path(path) || !path->node()->isDirectory()) {
341 SYSCALL_ERROR(NotADirectory);
342 return false;
343 }
344 if (!VFS::checkAccess(path->node(), false, false, true))
345 return false;
346 FilesystemPathRef retired;
347 {
348 VFS::NamespaceMutation writer(m_Vfs);
349 LockGuard<Mutex> guard(m_State->graph);
350 VfsFilesystemContext* target = nullptr;
351 if (!m_State->context(context, target)) {
352 SYSCALL_ERROR(DoesNotExist);
353 return false;
354 }
355 retired = pedigree_std::move(target->cwd);
356 target->cwd = path;
357 ++target->generation;
358 }
359 return true;
360}
361bool VfsMountView::changeRoot(const FilesystemContextRef& context, const FilesystemPathRef& path) {
362 if (!m_State->path(path) || !path->node()->isDirectory()) {
363 SYSCALL_ERROR(NotADirectory);
364 return false;
365 }
366 if (!VFS::checkAccess(path->node(), false, false, true))
367 return false;
368 FilesystemPathRef retired;
369 {
370 VFS::NamespaceMutation writer(m_Vfs);
371 LockGuard<Mutex> guard(m_State->graph);
372 VfsFilesystemContext* target = nullptr;
373 if (!m_State->context(context, target)) {
374 SYSCALL_ERROR(DoesNotExist);
375 return false;
376 }
377 retired = pedigree_std::move(target->root);
378 target->root = path;
379 ++target->generation;
380 }
381 return true;
382}
383uint64_t VfsMountView::attachmentId(const FilesystemPathRef& path) const {
384 auto* concrete = m_State ? m_State->path(path) : nullptr;
385 return concrete ? concrete->attachment->id : 0;
386}
387bool VfsMountView::samePath(const FilesystemPathRef& a, const FilesystemPathRef& b) const {
388 auto* first = m_State ? m_State->nodePath(a) : nullptr;
389 auto* second = m_State ? m_State->nodePath(b) : nullptr;
390 return first && second && first->kind == second->kind &&
391 first->attachment.get() == second->attachment.get() && first->node() == second->node();
392}
393bool VfsMountView::anonymousPath(File* node, FilesystemPathRef& result) {
394 VfsNodeReference retained;
395 if (!m_State || !retained.retainAnonymous(node)) {
396 SYSCALL_ERROR(NotADirectory);
397 return false;
398 }
399 auto path = FilesystemPathRef::tryAdopt(new VfsPath(*this, pedigree_std::move(retained)));
400 if (!path) {
401 SYSCALL_ERROR(OutOfMemory);
402 return false;
403 }
404 result = pedigree_std::move(path);
405 return true;
406}
407bool VfsMountView::pathForNode(const FilesystemPathRef& sameAttachment, File* node,
408 FilesystemPathRef& result) {
409 auto* path = m_State ? m_State->path(sameAttachment) : nullptr;
410 if (!path) {
411 SYSCALL_ERROR(InvalidArgument);
412 return false;
413 }
414 return m_State->makePath(path->attachment, node, result);
415}
Definition File.h:74
virtual bool isDirectory()
Definition File.cc:721
virtual File * getRoot() const =0
static ProcessorInformation & information()
static SharedPointer< FilesystemPath > tryAdopt(FilesystemPath *ptr)
T * get() const
void setErrno(size_t err)
Definition Thread.h:465
size_t getErrno()
Definition Thread.h:460
static UniquePointer< T > adopt(T *pointer)
Definition Pointers.h:101
Definition VFS.h:58
bool retireOwnedFilesystem(Filesystem *filesystem)
Definition VFS.cc:613
static bool checkAccess(File *pFile, bool bRead, bool bWrite, bool bExecute)
Definition VFS.cc:1392