The Pedigree Project 0.1
MountView-mounts.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/syscallError.h"
3
4#include "MountView-internal.h"
5
6bool VfsMountView::State::attach(const FilesystemPathRef& covered, VFS::FilesystemPin&& pin,
7 const VFS::NamespaceMutation& writer, BackingOwnership ownership) {
8 auto* point = path(covered);
9 if (!writer.protects(view.m_Vfs) || !point || !point->node()->isDirectory() || !pin) {
10 SYSCALL_ERROR(InvalidArgument);
11 return false;
12 }
13 if (point->node() == point->attachment->root) {
14 SYSCALL_ERROR(OperationNotSupported);
15 return false;
16 }
17 if (Directory::fromFile(point->node())->isDetached()) {
18 SYSCALL_ERROR(DoesNotExist);
19 return false;
20 }
21 uint64_t id;
22 {
23 LockGuard<Mutex> guard(graph);
24 if (!contains(covered) || at(*point)) {
25 SYSCALL_ERROR(DeviceBusy);
26 return false;
27 }
28 if (nextId > 0x7fffffffU) {
29 SYSCALL_ERROR(OutOfMemory);
30 return false;
31 }
32 id = nextId++;
33 }
34 auto attachment = VfsAttachmentRef::tryAdopt(new VfsAttachment(pedigree_std::move(pin), id));
37 if (!attachment || !node || !row) {
38 SYSCALL_ERROR(OutOfMemory);
39 return false;
40 }
41 if (!node->retain(point->node(), point->attachment->backing.filesystem())) {
42 SYSCALL_ERROR(DoesNotExist);
43 return false;
44 }
45 row.get()->attachment = attachment;
46 row.get()->parent = point->attachment;
47 row.get()->covered = node;
48 {
49 LockGuard<Mutex> guard(graph);
50 attachment->owningRegistry = ownership == BackingOwnership::Attachment ? &view.m_Vfs : nullptr;
51 row.get()->next = attachments;
52 attachments = row.releaseOwnership();
53 ++topology;
54 }
55 return true;
56}
57
58bool VfsMountView::attach(const FilesystemContextRef& context, const FilesystemPathRef& covered,
59 Filesystem* backing, BackingOwnership ownership) {
61 if (!m_State) {
62 ERROR("VfsMountView::attach: no internal state");
63 SYSCALL_ERROR(DoesNotExist);
64 return false;
65 }
66 if (!m_Vfs.pinFilesystem(backing, pin)) {
67 ERROR("VfsMountView::attach: failed to pin filesystem");
68 SYSCALL_ERROR(DoesNotExist);
69 return false;
70 }
72 VFS::NamespaceMutation writer(m_Vfs);
73 if (!context) {
74 ERROR("VfsMountView::attach: no context");
75 SYSCALL_ERROR(InvalidArgument);
76 return false;
77 }
78 if (!context->snapshot(snapshot)) {
79 ERROR("VfsMountView::attach: failed to snapshot");
80 SYSCALL_ERROR(InvalidArgument);
81 return false;
82 }
83 if (!m_State->beneath(covered, snapshot.root)) {
84 ERROR("VfsMountView::attach: not an ancestor of " << snapshot.root->node()->getFullPath());
85 SYSCALL_ERROR(InvalidArgument);
86 return false;
87 }
88
89 if (!m_State->attach(covered, pedigree_std::move(pin), writer, ownership)) {
90 ERROR("VfsMountView::attach: internal attach failed");
91 return false;
92 }
93
94 return true;
95}
96
97void VfsMountView::State::reapDetached() {
98#if THREADS && !defined(STANDALONE_MUTEXES)
99 TerminationDeferral lifetime;
100#endif
101 VfsAttachmentRow* retired = nullptr;
102 {
103 LockGuard<Mutex> guard(graph);
104 // Graph rows own covered-node references, not user paths. A disconnected
105 // tree may drain only when none of its attachments has a retained path.
106 for (;;) {
107 VfsAttachmentRow* detached = nullptr;
108 for (auto* candidate = attachments; candidate; candidate = candidate->next) {
109 if (candidate->parent || candidate->attachment->id == rootId)
110 continue;
111 bool used = false;
112 for (auto* row = attachments; row && !used; row = row->next) {
113 auto* ancestor = row;
114 while (ancestor && ancestor != candidate)
115 ancestor = ancestor->parent ? find(ancestor->parent->id) : nullptr;
116 if (ancestor && row->attachment->paths)
117 used = true;
118 }
119 if (!used) {
120 detached = candidate;
121 break;
122 }
123 }
124 if (!detached)
125 break;
126 // Remove leaves first, keeping parent rows discoverable for the next
127 // ancestry check. Actual node/backing destruction happens after unlock.
128 while (true) {
129 VfsAttachmentRow** selected = nullptr;
130 for (auto** link = &attachments; *link; link = &(*link)->next) {
131 auto* row = *link;
132 auto* ancestor = row;
133 while (ancestor && ancestor != detached)
134 ancestor = ancestor->parent ? find(ancestor->parent->id) : nullptr;
135 if (!ancestor)
136 continue;
137 bool child = false;
138 for (auto* check = attachments; check; check = check->next)
139 if (check->parent.get() == row->attachment.get())
140 child = true;
141 if (!child) {
142 selected = link;
143 break;
144 }
145 }
146 if (!selected)
147 FATAL("Cyclic detached attachment graph");
148 auto* row = *selected;
149 const bool last = row == detached;
150 *selected = row->next;
151 row->next = retired;
152 retired = row;
153 if (last)
154 break;
155 }
156 ++topology;
157 }
158 }
159 while (retired) {
160 auto* next = retired->next;
161 delete retired;
162 retired = next;
163 }
164}
165
166bool VfsMountView::detach(const FilesystemContextRef& context, const String& target, bool lazy) {
167 if (!m_State || !context) {
168 SYSCALL_ERROR(InvalidArgument);
169 return false;
170 }
171 VfsAttachmentRef retiredParent;
172 SharedPointer<VfsNodeReference> retiredCovered;
173 FilesystemPathRef mounted;
175 VFS::NamespaceMutation writer(m_Vfs);
176 ResolveOptions options;
177 options.requireDirectory = true;
178 if (!context->snapshot(snapshot) ||
179 !m_State->resolve(snapshot, snapshot.cwd, target, options, mounted, &writer))
180 return false;
181 auto* path = m_State->path(mounted);
182 {
183 LockGuard<Mutex> guard(m_State->graph);
184 auto* row = path ? m_State->find(path->attachment->id) : nullptr;
185 if (!row || !m_State->contains(mounted) || path->node() != path->attachment->root) {
186 SYSCALL_ERROR(InvalidArgument);
187 return false;
188 }
189 if (row->attachment->id == m_State->rootId) {
190 SYSCALL_ERROR(DeviceBusy);
191 return false;
192 }
193 if (!lazy) {
194 // The lookup above owns exactly one newly materialized mount-root path.
195 if (row->attachment->paths != 1 || mounted.refcount() != 1) {
196 SYSCALL_ERROR(DeviceBusy);
197 return false;
198 }
199 for (auto* child = m_State->attachments; child; child = child->next) {
200 if (child->parent.get() == row->attachment.get()) {
201 SYSCALL_ERROR(DeviceBusy);
202 return false;
203 }
204 }
205 }
206 retiredParent = pedigree_std::move(row->parent);
207 retiredCovered = pedigree_std::move(row->covered);
208 ++m_State->topology;
209 }
210 return true;
211}
212
213bool VfsMountView::pivot(const FilesystemContextRef& context, const String& newRoot,
214 const String& putOld) {
215 if (!m_State || !context) {
216 SYSCALL_ERROR(InvalidArgument);
217 return false;
218 }
219 // All ownership which may retire backend state precedes the writer guard.
221 FilesystemPathRef newPath, oldPath;
222 VfsAttachmentRef retiredNewParent, retiredOldParent;
223 SharedPointer<VfsNodeReference> retiredNewCovered, retiredOldCovered;
225 Vector<FilesystemPathRef> retiredPaths;
226 VFS::NamespaceMutation writer(m_Vfs);
227 ResolveOptions options;
228 options.requireDirectory = true;
229 if (!context->snapshot(snapshot) ||
230 !m_State->resolve(snapshot, snapshot.cwd, newRoot, options, newPath, &writer) ||
231 !m_State->resolve(snapshot, snapshot.cwd, putOld, options, oldPath, &writer))
232 return false;
233 auto* callerRoot = m_State->path(snapshot.root);
234 auto* nextRoot = m_State->path(newPath);
235 auto* oldMountpoint = m_State->path(oldPath);
236 if (!callerRoot || !nextRoot || !oldMountpoint) {
237 SYSCALL_ERROR(InvalidArgument);
238 return false;
239 }
240 if (samePath(newPath, oldPath)) {
241 SYSCALL_ERROR(OperationNotSupported);
242 return false;
243 }
244 if (callerRoot->node() != callerRoot->attachment->root ||
245 nextRoot->node() != nextRoot->attachment->root ||
246 callerRoot->attachment.get() == nextRoot->attachment.get() ||
247 oldMountpoint->attachment.get() == callerRoot->attachment.get() ||
248 !m_State->beneath(newPath, snapshot.root) || !m_State->beneath(oldPath, newPath)) {
249 SYSCALL_ERROR(InvalidArgument);
250 return false;
251 }
252 if (Directory::fromFile(oldMountpoint->node())->isDetached()) {
253 SYSCALL_ERROR(DoesNotExist);
254 return false;
255 }
256 if (oldMountpoint->node() == oldMountpoint->attachment->root) {
257 SYSCALL_ERROR(OperationNotSupported);
258 return false;
259 }
260 size_t count;
261 {
262 LockGuard<Mutex> guard(m_State->graph);
263 count = m_State->contextCount;
264 auto* previous = m_State->find(callerRoot->attachment->id);
265 auto* next = m_State->find(nextRoot->attachment->id);
266 if (!previous || !next || !m_State->contains(newPath) || !m_State->contains(oldPath) ||
267 m_State->at(*oldMountpoint)) {
268 SYSCALL_ERROR(DeviceBusy);
269 return false;
270 }
271 }
272 if (count > ~size_t(0) / 2 || !retiredPaths.tryReserve(count * 2)) {
273 SYSCALL_ERROR(OutOfMemory);
274 return false;
275 }
277 if (!putOldNode) {
278 SYSCALL_ERROR(OutOfMemory);
279 return false;
280 }
281 if (!putOldNode->retain(oldMountpoint->node(), oldMountpoint->attachment->backing.filesystem())) {
282 SYSCALL_ERROR(DoesNotExist);
283 return false;
284 }
285 {
286 LockGuard<Mutex> guard(m_State->graph);
287 auto* previous = m_State->find(callerRoot->attachment->id);
288 auto* next = m_State->find(nextRoot->attachment->id);
289 // Writer admission freezes graph edges, enrollment and backend ancestry.
290 // Every operation below is an existing reference increment or move.
291 retiredNewParent = pedigree_std::move(next->parent);
292 retiredNewCovered = pedigree_std::move(next->covered);
293 retiredOldParent = pedigree_std::move(previous->parent);
294 retiredOldCovered = pedigree_std::move(previous->covered);
295 next->parent = retiredOldParent;
296 next->covered = retiredOldCovered;
297 previous->parent = oldMountpoint->attachment;
298 previous->covered = putOldNode;
299 if (m_State->rootId == previous->attachment->id)
300 m_State->rootId = next->attachment->id;
301 for (auto* row = m_State->contexts; row; row = row->next) {
302 auto* fs = static_cast<VfsFilesystemContext*>(row->context.get());
303 bool changed = false;
304 if (samePath(fs->root, snapshot.root)) {
305 retiredPaths.pushBack(pedigree_std::move(fs->root));
306 fs->root = newPath;
307 changed = true;
308 }
309 if (samePath(fs->cwd, snapshot.root)) {
310 retiredPaths.pushBack(pedigree_std::move(fs->cwd));
311 fs->cwd = newPath;
312 changed = true;
313 }
314 if (changed)
315 ++fs->generation;
316 }
317 ++m_State->topology;
318 }
319 return true;
320}
321
322bool VfsMountView::detachBackingForShutdown(Filesystem* backing) {
323#if THREADS && !defined(STANDALONE_MUTEXES)
324 TerminationDeferral lifetime;
325#endif
326 if (!m_State || !backing)
327 return true;
328 VfsAttachmentRow* retired = nullptr;
329 {
330 VFS::NamespaceMutation writer(m_Vfs);
331 LockGuard<Mutex> guard(m_State->graph);
332 for (auto* row = m_State->attachments; row; row = row->next) {
333 if (row->attachment->backing.filesystem() != backing)
334 continue;
335 if (row->attachment->id == m_State->rootId || row->attachment->paths) {
336 SYSCALL_ERROR(DeviceBusy);
337 return false;
338 }
339 for (auto* child = m_State->attachments; child; child = child->next) {
340 if (child->parent.get() == row->attachment.get() &&
341 child->attachment->backing.filesystem() != backing) {
342 SYSCALL_ERROR(DeviceBusy);
343 return false;
344 }
345 }
346 }
347 for (auto** link = &m_State->attachments; *link;) {
348 auto* row = *link;
349 if (row->attachment->backing.filesystem() != backing) {
350 link = &row->next;
351 continue;
352 }
353 *link = row->next;
354 row->next = retired;
355 retired = row;
356 }
357 ++m_State->topology;
358 }
359 while (retired) {
360 auto* next = retired->next;
361 delete retired;
362 retired = next;
363 }
364 return true;
365}
366
367bool VfsMountView::shutdown(Vector<Filesystem*>& ownedBackings) {
368 VfsAttachmentRow* retired;
369 {
370 VFS::NamespaceMutation writer(m_Vfs);
371 LockGuard<Mutex> guard(m_State->graph);
372 if (m_State->contexts || m_State->anonymousPaths) {
373 SYSCALL_ERROR(DeviceBusy);
374 return false;
375 }
376 size_t ownedCount = 0;
377 for (auto* row = m_State->attachments; row; row = row->next) {
378 if (row->attachment->paths) {
379 SYSCALL_ERROR(DeviceBusy);
380 return false;
381 }
382 if (row->attachment->owningRegistry)
383 ++ownedCount;
384 }
385 if (!ownedBackings.tryReserve(ownedBackings.count() + ownedCount)) {
386 SYSCALL_ERROR(OutOfMemory);
387 return false;
388 }
389 for (auto* row = m_State->attachments; row; row = row->next) {
390 if (row->attachment->owningRegistry) {
391 ownedBackings.pushBack(row->attachment->backing.filesystem());
392 // The terminal owner must check sync before deleting this backend.
393 // Clearing the shared attachment also handles multiple bind rows.
394 row->attachment->owningRegistry = nullptr;
395 }
396 }
397 retired = m_State->attachments;
398 m_State->attachments = nullptr;
399 m_State->rootId = 0;
400 ++m_State->topology;
401 }
402 while (retired) {
403 auto* next = retired->next;
404 delete retired;
405 retired = next;
406 }
407 return true;
408}
static Directory * fromFile(File *pF)
Definition Directory.h:148
bool isDetached() const
Definition Directory.h:179
size_t refcount() const
static SharedPointer< VfsAttachment > tryAdopt(VfsAttachment *ptr)
static UniquePointer< T > adopt(T *pointer)
Definition Pointers.h:101
A vector / dynamic array.
Definition Vector.h:33
void pushBack(const T &value)
Definition Vector.h:275
size_t count() const
Definition Vector.h:270