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(Directory::ChildLease& child)
13 : m_Node(child.releaseOwnership()),
14 m_Ownership(m_Node ? Ownership::Tracked : Ownership::Borrowed) {}
15VfsNodeReference::VfsNodeReference(VfsNodeReference&& other) noexcept
16 : m_Node(other.m_Node), m_Ownership(other.m_Ownership) {
17 other.m_Node = nullptr;
18 other.m_Ownership = Ownership::Borrowed;
19}
20VfsNodeReference& VfsNodeReference::operator=(VfsNodeReference&& other) noexcept {
21 if (this != &other) {
22 reset();
23 m_Node = other.m_Node;
24 m_Ownership = other.m_Ownership;
25 other.m_Node = nullptr;
26 other.m_Ownership = Ownership::Borrowed;
27 }
28 return *this;
29}
30VfsNodeReference::~VfsNodeReference() {
31 reset();
32}
33void VfsNodeReference::reset() {
34 if (m_Ownership == Ownership::Borrowed) {
35 m_Node = nullptr;
36 return;
37 }
38#if THREADS && !defined(STANDALONE_MUTEXES)
39 TerminationDeferral lifetime;
40#endif
41 File* node = m_Node;
42 const auto ownership = m_Ownership;
43 m_Node = nullptr;
44 m_Ownership = Ownership::Borrowed;
45 if (ownership == Ownership::Tracked) {
47 } else {
48 node->releaseVfsReference();
49 }
50}
51bool VfsNodeReference::retain(File* node, Filesystem* backing) {
52 if (m_Node || !node || !backing || node->getFilesystem() != backing)
53 return false;
54 const bool retained = node->retainVfsReference();
55 if (!retained && node != backing->getRoot()) {
56 return false;
57 }
58 m_Node = node;
59 m_Ownership = retained ? Ownership::Virtual : Ownership::Borrowed;
60 return true;
61}
62
63bool VfsNodeReference::retainAnonymous(File* node) {
64 if (m_Node || !node || node->isDirectory() || !node->retainVfsReference())
65 return false;
66 m_Node = node;
67 m_Ownership = Ownership::Virtual;
68 return true;
69}
70
71VfsBacking::~VfsBacking() {
72#if THREADS && !defined(STANDALONE_MUTEXES)
73 TerminationDeferral lifetime;
74#endif
75 // Keep our pin until admission closes, so retirement cannot delete storage
76 // while this attachment still owns it. Graph rows retire outside graph/writer locks.
77 if (owningRegistry && !owningRegistry->retireOwnedFilesystem(pin.filesystem()))
78 FATAL("Owned attachment lost its backing registration");
79 pin.reset();
80}
81
82VfsPath::VfsPath(VfsMountView& owner, const VfsAttachmentRef& mounted, VfsNodeReference&& retained)
83 : kind(Kind::Mounted), view(owner), attachment(mounted), file(pedigree_std::move(retained)) {
84 view.retain();
85 attachment->paths += 1;
86}
87VfsPath::VfsPath(VfsMountView& owner, VfsNodeReference&& retained)
88 : kind(Kind::Anonymous), view(owner), attachment(), file(pedigree_std::move(retained)) {
89 view.retain();
90 view.m_State->anonymousPaths += 1;
91}
92VfsPath::~VfsPath() {
93#if THREADS && !defined(STANDALONE_MUTEXES)
94 TerminationDeferral lifetime;
95 Thread* thread = Processor::information().getCurrentThread();
96 const size_t error = thread ? thread->getErrno() : 0;
97#endif
98 file.reset();
99 if (kind == Kind::Mounted) {
100 if ((attachment->paths -= 1) == 0)
101 view.m_State->reapDetached();
102 } else {
103 view.m_State->anonymousPaths -= 1;
104 }
105 attachment.reset();
106 lookupParent.reset();
107 view.release();
108#if THREADS && !defined(STANDALONE_MUTEXES)
109 if (thread)
110 thread->setErrno(error);
111#endif
112}
113
114VfsMountView::VfsMountView(VFS& vfs) : m_State(new State(*this)), m_Vfs(vfs) {}
115void VfsMountView::retain() {
116 __atomic_add_fetch(&m_References, 1, __ATOMIC_RELAXED);
117}
118void VfsMountView::release() {
119 if (__atomic_sub_fetch(&m_References, 1, __ATOMIC_ACQ_REL) == 0 && m_Automatic) {
120 delete this;
121 }
122}
123VfsMountView* VfsMountView::fromContext(const FilesystemContextRef& context) {
124 return context ? static_cast<VfsMountView*>(const_cast<void*>(context->provider())) : nullptr;
125}
126VfsMountView* VfsMountView::fromPath(const FilesystemPathRef& path) {
127 return path ? static_cast<VfsMountView*>(const_cast<void*>(path->provider())) : nullptr;
128}
129VfsMountView::~VfsMountView() {
130 delete m_State;
131}
132VfsMountView::State::~State() {
133 if (contexts || anonymousPaths)
134 FATAL("Mount view destroyed with retained filesystem owners");
135 auto* row = attachments;
136 attachments = nullptr;
137 while (row) {
138 if (row->attachment->paths)
139 FATAL("Mount view destroyed with retained paths");
140 auto* next = row->next;
141 delete row;
142 row = next;
143 }
144}
145
146VfsAttachmentRow* VfsMountView::State::find(uint64_t id) const {
147 for (auto* row = attachments; row; row = row->next)
148 if (row->attachment->id == id)
149 return row;
150 return nullptr;
151}
152VfsAttachmentRow* VfsMountView::State::at(const VfsPath& path) const {
153 for (auto* row = attachments; row; row = row->next)
154 if (row->parent.get() == path.attachment.get() && row->covered &&
155 row->covered->get() == path.node())
156 return row;
157 return nullptr;
158}
159bool VfsMountView::State::snapshotRows(Vector<VfsAttachmentRow>& result) {
160 LockGuard<Mutex> guard(graph);
161 size_t count = 0;
162 for (auto* row = attachments; row; row = row->next) {
163 ++count;
164 }
165 if (!result.tryReserve(count)) {
166 SYSCALL_ERROR(OutOfMemory);
167 return false;
168 }
169 for (auto* row = attachments; row; row = row->next) {
170 result.pushBack(*row);
171 }
172 return true;
173}
174VfsPath* VfsMountView::State::nodePath(const FilesystemPathRef& reference) const {
175 auto* result = reference && reference->provider() == &view
176 ? static_cast<VfsPath*>(reference.get())
177 : nullptr;
178 return result && &result->view == &view ? result : nullptr;
179}
180VfsPath* VfsMountView::State::path(const FilesystemPathRef& reference) const {
181 auto* selected = nodePath(reference);
182 return selected && selected->kind == VfsPath::Kind::Mounted ? selected : nullptr;
183}
184bool VfsMountView::State::contains(const FilesystemPathRef& reference) const {
185 auto* p = path(reference);
186 if (!p)
187 return false;
188 auto* row = find(p->attachment->id);
189 size_t remaining = 1;
190 for (auto* entry = attachments; entry; entry = entry->next)
191 ++remaining;
192 while (row && remaining--) {
193 if (row->attachment->id == rootId)
194 return true;
195 row = row->parent ? find(row->parent->id) : nullptr;
196 }
197 return false;
198}
199bool VfsMountView::State::context(const FilesystemContextRef& reference,
200 VfsFilesystemContext*& result) const {
201 for (auto* row = contexts; row; row = row->next) {
202 if (row->context.get() == reference.get()) {
203 result = static_cast<VfsFilesystemContext*>(reference.get());
204 return true;
205 }
206 }
207 return false;
208}
209bool VfsMountView::State::makePath(const VfsAttachmentRef& attachment, File* node,
210 FilesystemPathRef& result) {
211 VfsNodeReference retained;
212 if (!attachment || !retained.retain(node, attachment->backing->pin.filesystem())) {
213 SYSCALL_ERROR(DoesNotExist);
214 return false;
215 }
216 return makePath(attachment, pedigree_std::move(retained), result);
217}
218
219bool VfsMountView::State::makeAttachment(const VfsBackingRef& backing, File* root, uint64_t flags,
220 VfsAttachmentRef& result) {
221 VfsNodeReference retained;
222 if (!backing || !retained.retain(root, backing->pin.filesystem())) {
223 SYSCALL_ERROR(DoesNotExist);
224 return false;
225 }
226 if (nextId > 0x7fffffffU) {
227 SYSCALL_ERROR(OutOfMemory);
228 return false;
229 }
230 auto attachment = VfsAttachmentRef::tryAdopt(
231 new VfsAttachment(backing, nextId++, pedigree_std::move(retained), flags));
232 if (!attachment) {
233 SYSCALL_ERROR(OutOfMemory);
234 return false;
235 }
236 result = pedigree_std::move(attachment);
237 return true;
238}
239
240bool VfsMountView::forkNamespace(const FilesystemContextRef& context,
241 FilesystemContextOwner& result, uint64_t ownerNamespace) {
242 if (fromContext(context) != this || result) {
243 SYSCALL_ERROR(InvalidArgument);
244 return false;
245 }
246 auto clone = UniquePointer<VfsMountView>::adopt(new VfsMountView(m_Vfs));
247 if (!clone || !clone.get()->m_State) {
248 SYSCALL_ERROR(OutOfMemory);
249 return false;
250 }
251 auto* destination = clone.get()->m_State;
252 clone.get()->m_OwnerNamespace = ownerNamespace;
253 auto reference = FilesystemContextRef::tryAdopt(new VfsFilesystemContext(*clone.get()));
257 if (!reference || !enrolled) {
258 SYSCALL_ERROR(OutOfMemory);
259 return false;
260 }
261 {
262 VFS::NamespaceMutation writer(m_Vfs);
263 if (!context->snapshot(snapshot)) {
264 SYSCALL_ERROR(DoesNotExist);
265 return false;
266 }
267 if (!m_State->snapshotRows(rows)) {
268 return false;
269 }
270 // Preserve IDs within the clone so every edge and retained context can be
271 // translated without a second independently maintained topology index.
272 for (auto& item : rows) {
273 auto* source = &item;
274 VfsNodeReference root;
275 if (!root.retain(source->attachment->root, source->attachment->backing->pin.filesystem())) {
276 SYSCALL_ERROR(DoesNotExist);
277 return false;
278 }
279 auto attachment = VfsAttachmentRef::tryAdopt(
280 new VfsAttachment(source->attachment->backing, source->attachment->id,
281 pedigree_std::move(root), source->attachment->flags));
283 if (!attachment || !row) {
284 SYSCALL_ERROR(OutOfMemory);
285 return false;
286 }
287 attachment->lockedFlags = source->attachment->lockedFlags;
288 attachment->lockedMount = source->attachment->lockedMount;
289 if (ownerNamespace != m_OwnerNamespace) {
290 attachment->lockedFlags |= source->attachment->flags;
291 // A less privileged owner may detach the inherited tree as a whole,
292 // but cannot remove an interior mount to expose what it conceals.
293 attachment->lockedMount = source->attachment->id != m_State->rootId;
294 }
295 row.get()->attachment = attachment;
296 row.get()->covered = source->covered;
297 row.get()->next = destination->attachments;
298 destination->attachments = row.releaseOwnership();
299 }
300 for (auto& item : rows) {
301 auto* source = &item;
302 if (source->parent) {
303 destination->find(source->attachment->id)->parent =
304 destination->find(source->parent->id)->attachment;
305 }
306 }
307 destination->rootId = m_State->rootId;
308 destination->nextId = m_State->nextId;
309 auto* created = static_cast<VfsFilesystemContext*>(reference.get());
310 auto* root = m_State->path(snapshot.root);
311 auto* cwd = m_State->path(snapshot.cwd);
312 if (!root || !destination->makePath(destination->find(root->attachment->id)->attachment,
313 root->node(), created->root)) {
314 return false;
315 }
316 if (cwd) {
317 if (!destination->makePath(destination->find(cwd->attachment->id)->attachment, cwd->node(),
318 created->cwd)) {
319 return false;
320 }
321 } else {
322 created->cwd = snapshot.cwd;
323 }
324 enrolled.get()->context = reference;
325 created->registration = enrolled.get();
326 destination->contexts = enrolled.releaseOwnership();
327 destination->contextCount = 1;
328 }
329 auto* published = clone.releaseOwnership();
330 published->m_Automatic = true;
331 result = FilesystemContextOwner::adopt(pedigree_std::move(reference));
332 published->m_State->reapDetached();
333 published->release();
334 return true;
335}
336bool VfsMountView::State::makePath(const VfsAttachmentRef& attachment, VfsNodeReference&& retained,
337 FilesystemPathRef& result) {
338 if (!attachment || !retained.get() ||
339 retained.get()->getFilesystem() != attachment->backing->pin.filesystem()) {
340 SYSCALL_ERROR(DoesNotExist);
341 return false;
342 }
343 auto path =
344 FilesystemPathRef::tryAdopt(new VfsPath(view, attachment, pedigree_std::move(retained)));
345 if (!path) {
346 SYSCALL_ERROR(OutOfMemory);
347 return false;
348 }
349 result = pedigree_std::move(path);
350 return true;
351}
352
353bool VfsMountView::initialise(Filesystem* bootRoot) {
354 if (!m_State || !bootRoot) {
355 SYSCALL_ERROR(OutOfMemory);
356 return false;
357 }
359 if (!m_Vfs.pinFilesystem(bootRoot, pin)) {
360 SYSCALL_ERROR(DoesNotExist);
361 return false;
362 }
363 auto backing = VfsBackingRef::tryAdopt(new VfsBacking(pedigree_std::move(pin)));
364 VfsAttachmentRef attachment;
365 if (!backing || !m_State->makeAttachment(backing, bootRoot->getRoot(), 0, attachment)) {
366 return false;
367 }
369 if (!attachment || !row) {
370 SYSCALL_ERROR(OutOfMemory);
371 return false;
372 }
373 row.get()->attachment = attachment;
374 VFS::NamespaceMutation writer(m_Vfs);
375 LockGuard<Mutex> guard(m_State->graph);
376 if (m_State->rootId) {
377 SYSCALL_ERROR(DeviceBusy);
378 return false;
379 }
380 m_State->rootId = 1;
381 m_State->nextId = 2;
382 m_State->attachments = row.releaseOwnership();
383 return true;
384}
385
386#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
387bool VfsMountView::quiescentForHostedTest() const {
388 if (!m_State)
389 return true;
390 LockGuard<Mutex> guard(m_State->graph);
391 if (m_State->contexts || m_State->anonymousPaths)
392 return false;
393 for (auto* row = m_State->attachments; row; row = row->next)
394 if (row->attachment->paths)
395 return false;
396 return true;
397}
398#endif
399
400bool VfsMountView::bootRootPath(FilesystemPathRef& result) {
401 VfsAttachmentRef attachment;
402 if (!m_State)
403 return false;
404 {
405 LockGuard<Mutex> guard(m_State->graph);
406 auto* row = m_State->find(m_State->rootId);
407 if (!row)
408 return false;
409 attachment = row->attachment;
410 }
411 return m_State->makePath(attachment, attachment->root, result);
412}
413bool VfsMountView::createBootContext(FilesystemContextOwner& result) {
414 return m_State && m_State->createContext(nullptr, result);
415}
416bool VfsMountView::State::createContext(const VfsFilesystemContext* parent,
417 FilesystemContextOwner& result) {
418#if THREADS && !defined(STANDALONE_MUTEXES)
419 TerminationDeferral lifetime;
420#endif
421 if (result) {
422 SYSCALL_ERROR(InvalidArgument);
423 return false;
424 }
425 auto reference = FilesystemContextRef::tryAdopt(new VfsFilesystemContext(view));
427 if (!reference || !row) {
428 SYSCALL_ERROR(OutOfMemory);
429 return false;
430 }
431 auto* created = static_cast<VfsFilesystemContext*>(reference.get());
432 row.get()->context = reference;
433 FilesystemPathRef initial;
434 VFS::NamespaceMutation writer(view.m_Vfs);
435 if (!parent) {
436 // Boot enrollment snapshots the current view root under the same writer
437 // admission as publication, so it cannot resurrect a root moved by pivot.
438 VfsAttachmentRef attachment;
439 {
440 LockGuard<Mutex> guard(graph);
441 auto* root = find(rootId);
442 if (!root) {
443 SYSCALL_ERROR(DoesNotExist);
444 return false;
445 }
446 attachment = root->attachment;
447 }
448 if (!makePath(attachment, attachment->root, initial))
449 return false;
450 }
451 {
452 LockGuard<Mutex> guard(graph);
453 if (parent && !parent->registration) {
454 SYSCALL_ERROR(DoesNotExist);
455 return false;
456 }
457 created->root = parent ? parent->root : initial;
458 created->cwd = parent ? parent->cwd : initial;
459 created->registration = row.get();
460 row.get()->next = contexts;
461 contexts = row.releaseOwnership();
462 ++contextCount;
463 }
464 result = FilesystemContextOwner::adopt(pedigree_std::move(reference));
465 return true;
466}
467
468VfsFilesystemContext::~VfsFilesystemContext() {
469 if (registration) {
470 FATAL("Filesystem context destroyed before owner retirement");
471 }
472 root.reset();
473 cwd.reset();
474 view.release();
475}
476bool VfsFilesystemContext::snapshot(FilesystemContextSnapshot& result) const {
477 FilesystemContextSnapshot replacement;
478 {
479 LockGuard<Mutex> guard(view.m_State->graph);
480 if (!registration)
481 return false;
482 replacement.root = root;
483 replacement.cwd = cwd;
484 replacement.contextGeneration = generation;
485 replacement.topologyGeneration = view.m_State->topology;
486 }
487 result = pedigree_std::move(replacement);
488 return true;
489}
490bool VfsFilesystemContext::forkForProcess(FilesystemContextOwner& result) const {
491 return view.m_State->createContext(this, result);
492}
493void VfsFilesystemContext::retireProcessOwner() {
494#if THREADS && !defined(STANDALONE_MUTEXES)
495 TerminationDeferral lifetime;
496#endif
497 VfsContextRow* retired = nullptr;
498 FilesystemPathRef retiredRoot, retiredCwd;
499 {
500 VFS::NamespaceMutation writer(view.m_Vfs);
501 LockGuard<Mutex> guard(view.m_State->graph);
502 if (!registration)
503 return;
504 auto** link = &view.m_State->contexts;
505 while (*link && *link != registration)
506 link = &(*link)->next;
507 if (!*link)
508 FATAL("Missing filesystem context enrollment");
509 retired = *link;
510 *link = retired->next;
511 registration = nullptr;
512 --view.m_State->contextCount;
513 retiredRoot = pedigree_std::move(root);
514 retiredCwd = pedigree_std::move(cwd);
515 }
516 delete retired;
517}
518
519bool VfsMountView::changeCwd(const FilesystemContextRef& context, const FilesystemPathRef& path) {
520 auto* owner = fromPath(path);
521 if (!owner || !owner->m_State->path(path) || !path->node()->isDirectory()) {
522 SYSCALL_ERROR(NotADirectory);
523 return false;
524 }
525 if (!VFS::checkAccess(path->node(), false, false, true))
526 return false;
527 FilesystemPathRef retired;
528 {
529 VFS::NamespaceMutation writer(m_Vfs);
530 LockGuard<Mutex> guard(m_State->graph);
531 VfsFilesystemContext* target = nullptr;
532 if (!m_State->context(context, target)) {
533 SYSCALL_ERROR(DoesNotExist);
534 return false;
535 }
536 retired = pedigree_std::move(target->cwd);
537 target->cwd = path;
538 ++target->generation;
539 }
540 return true;
541}
542bool VfsMountView::changeRoot(const FilesystemContextRef& context, const FilesystemPathRef& path) {
543 if (!m_State->path(path) || !path->node()->isDirectory()) {
544 SYSCALL_ERROR(NotADirectory);
545 return false;
546 }
547 if (!VFS::checkAccess(path->node(), false, false, true))
548 return false;
549 FilesystemPathRef retired;
550 {
551 VFS::NamespaceMutation writer(m_Vfs);
552 LockGuard<Mutex> guard(m_State->graph);
553 VfsFilesystemContext* target = nullptr;
554 if (!m_State->context(context, target)) {
555 SYSCALL_ERROR(DoesNotExist);
556 return false;
557 }
558 retired = pedigree_std::move(target->root);
559 target->root = path;
560 ++target->generation;
561 }
562 return true;
563}
564uint64_t VfsMountView::attachmentId(const FilesystemPathRef& path) const {
565 auto* concrete = m_State ? m_State->path(path) : nullptr;
566 return concrete ? concrete->attachment->id : 0;
567}
568bool VfsMountView::samePath(const FilesystemPathRef& a, const FilesystemPathRef& b) const {
569 auto* first = m_State ? m_State->nodePath(a) : nullptr;
570 auto* second = m_State ? m_State->nodePath(b) : nullptr;
571 return first && second && first->kind == second->kind &&
572 first->attachment.get() == second->attachment.get() && first->node() == second->node();
573}
574bool VfsMountView::anonymousPath(File* node, FilesystemPathRef& result) {
575 VfsNodeReference retained;
576 if (!m_State || !retained.retainAnonymous(node)) {
577 SYSCALL_ERROR(NotADirectory);
578 return false;
579 }
580 auto path = FilesystemPathRef::tryAdopt(new VfsPath(*this, pedigree_std::move(retained)));
581 if (!path) {
582 SYSCALL_ERROR(OutOfMemory);
583 return false;
584 }
585 result = pedigree_std::move(path);
586 return true;
587}
588bool VfsMountView::pathForNode(const FilesystemPathRef& sameAttachment, File* node,
589 FilesystemPathRef& result) {
590 auto* path = m_State ? m_State->path(sameAttachment) : nullptr;
591 if (!path) {
592 SYSCALL_ERROR(InvalidArgument);
593 return false;
594 }
595 return m_State->makePath(path->attachment, node, result);
596}
Definition File.h:75
virtual bool isDirectory()
Definition File.cc:804
virtual File * getRoot() const =0
static ProcessorInformation & information()
static SharedPointer< VfsAttachment > tryAdopt(VfsAttachment *ptr)
T * get() const
void setErrno(size_t err)
Definition Thread.h:482
size_t getErrno()
Definition Thread.h:477
static UniquePointer< T > adopt(T *pointer)
Definition Pointers.h:101
Definition VFS.h:58
bool retireOwnedFilesystem(Filesystem *filesystem)
Definition VFS.cc:629
bool untrackFile(File *pFile, bool destroy=true)
Definition VFS.cc:1670
static bool checkAccess(File *pFile, bool bRead, bool bWrite, bool bExecute)
Definition VFS.cc:1502
static VFS & instance()
Definition VFS.cc:311
A vector / dynamic array.
Definition Vector.h:33
void pushBack(const T &value)
Definition Vector.h:275