The Pedigree Project 0.1
MountView-path.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/syscallError.h"
3#include "pedigree/kernel/utilities/utility.h"
4
5#include "MountView-internal.h"
6#include "Symlink.h"
7#ifndef VFS_STANDALONE
8#include "pedigree/kernel/process/Process.h"
9#endif
10#if THREADS && !defined(STANDALONE_MUTEXES)
11#include "pedigree/kernel/process/Thread.h"
12#include "pedigree/kernel/processor/Processor.h"
13#include "pedigree/kernel/processor/ProcessorInformation.h"
14#endif
15
16namespace {
17// Declared before attempt-local owners so their retirement cannot select errno.
18class ResolutionAttempt {
19 public:
20#if THREADS && !defined(STANDALONE_MUTEXES)
21 ResolutionAttempt()
22 : m_Thread(Processor::information().getCurrentThread()),
23 m_Error(m_Thread ? m_Thread->getErrno() : 0) {}
24 ~ResolutionAttempt() {
25 if (m_Thread)
26 m_Thread->setErrno(m_Error);
27 }
28 void failure() {
29 if (m_Thread)
30 m_Error = m_Thread->getErrno();
31 }
32
33 private:
34 TerminationDeferral m_Lifetime;
35 Thread* const m_Thread;
36 size_t m_Error;
37#else
38 void failure() {}
39#endif
40};
41} // namespace
42
43bool VfsMountView::State::cross(const FilesystemPathRef& reference, FilesystemPathRef& result) {
44 auto* owner = VfsMountView::fromPath(reference);
45 if (owner && owner != &view) {
46 return owner->m_State->cross(reference, result);
47 }
48 FilesystemPathRef selected = reference;
49 for (size_t depth = 0; depth < 4096; ++depth) {
50 auto* current = path(selected);
51 if (!current) {
52 return false;
53 }
54 VfsAttachmentRef target;
55 {
56 LockGuard<Mutex> guard(graph);
57 auto* row = at(*current);
58 if (row) {
59 target = row->attachment;
60 }
61 }
62 if (!target) {
63 result = pedigree_std::move(selected);
64 return true;
65 }
66 if (!makePath(target, target->root, selected)) {
67 return false;
68 }
69 }
70 SYSCALL_ERROR(LoopExists);
71 return false;
72}
73
74bool VfsMountView::State::parent(const FilesystemPathRef& reference, FilesystemPathRef& result,
75 const FilesystemPathRef& boundary) {
76 auto* owner = VfsMountView::fromPath(reference);
77 if (owner && owner != &view) {
78 return owner->m_State->parent(reference, result, boundary);
79 }
80 auto* current = path(reference);
81 if (!current)
82 return false;
83 if (!current->node()->isDirectory() && current->lookupParent) {
84 result = current->lookupParent;
85 return true;
86 }
87 if (current->node() == current->attachment->root) {
88 VfsAttachmentRef parentAttachment;
90 {
91 LockGuard<Mutex> guard(graph);
92 auto* row = find(current->attachment->id);
93 if (row) {
94 parentAttachment = row->parent;
95 covered = row->covered;
96 }
97 }
98 if (!parentAttachment) {
99 // A detached mount's root never escapes through the global boot root.
100 result = reference;
101 return true;
102 }
103 FilesystemPathRef mountpoint;
104 if (!covered || !makePath(parentAttachment, covered->get(), mountpoint))
105 return false;
106 if (view.samePath(mountpoint, boundary)) {
107 result = pedigree_std::move(mountpoint);
108 return true;
109 }
110 return parent(mountpoint, result, boundary);
111 }
112 File::ParentLease retained;
113 String unused;
114 current->node()->getNamespace(retained, unused);
115 if (!retained.get()) {
116 result = reference;
117 return true;
118 }
119 return makePath(current->attachment, retained.get(), result);
120}
121
122bool VfsMountView::State::beneath(const FilesystemPathRef& descendant,
123 const FilesystemPathRef& ancestor) {
124 FilesystemPathRef current = descendant;
125 // Directory ancestry is acyclic, but bound corrupted backend parent chains.
126 for (size_t depth = 0; depth < 4096; ++depth) {
127 if (view.samePath(current, ancestor))
128 return true;
130 if (!parent(current, next) || view.samePath(current, next))
131 return false;
132 current = pedigree_std::move(next);
133 }
134 return false;
135}
136
137bool VfsMountView::State::follow(const FilesystemContextSnapshot& context,
138 const FilesystemPathRef& selected, const ResolveOptions& options,
139 FilesystemPathRef& result, size_t& links) {
140 // An empty internal pathname means follow this already retained final node.
141 return walk(context, selected, String(), options, result, links);
142}
143
144bool VfsMountView::State::walk(const FilesystemContextSnapshot& context,
145 const FilesystemPathRef& start, const String& pathname,
146 const ResolveOptions& options, FilesystemPathRef& result,
147 size_t& links) {
148 const bool selectedOnly = !pathname.length();
149 FilesystemPathRef current = !selectedOnly && pathname[0] == '/' ? context.root : start;
150 auto* currentView = VfsMountView::fromPath(current);
151 if (!currentView || !VfsMountView::fromPath(context.root)) {
152 SYSCALL_ERROR(DoesNotExist);
153 return false;
154 }
155 if (!selectedOnly && !currentView->m_State->path(current)) {
156 SYSCALL_ERROR(NotADirectory);
157 return false;
158 }
159 FilesystemCredentials credentials;
160#ifndef VFS_STANDALONE
161 if (!Process::currentFilesystemCredentials(credentials)) {
162 credentials.valid = false;
163 }
164#endif
165 bool trailingSlash = !selectedOnly && pathname[pathname.length() - 1] == '/';
166 bool followCurrent = selectedOnly;
167 bool crossCurrent = !selectedOnly && pathname[0] == '/';
168 StringView pending = pathname.view();
169 UniqueArray<char> pendingStorage, linkStorage;
170 size_t offset = 0;
171 for (;;) {
172 if (followCurrent && current->node()->isSymlink()) {
173 if (++links > 40) {
174 SYSCALL_ERROR(LoopExists);
175 return false;
176 }
177 auto* link = Symlink::fromFile(current->node());
178 if (link->isPathLink()) {
179 FilesystemPathRef target;
180 if (!link->followPath(target))
181 return false;
182 if (!VfsMountView::fromPath(target)) {
183 SYSCALL_ERROR(CrossDeviceLink);
184 return false;
185 }
186 current = pedigree_std::move(target);
187 // A typed jump keeps its opening attachment, even beneath an overmount.
188 crossCurrent = false;
189 continue;
190 }
191 if (!VfsMountView::fromPath(current)->m_State->path(current)) {
192 SYSCALL_ERROR(LoopExists);
193 return false;
194 }
195 if (!linkStorage) {
196 linkStorage = UniqueArray<char>::allocate(4096);
197 if (!linkStorage) {
198 SYSCALL_ERROR(OutOfMemory);
199 return false;
200 }
201 }
202 const int length = link->followLink(linkStorage.get(), 4096);
203 if (length < 0)
204 return false;
205 if (!length) {
206 SYSCALL_ERROR(DoesNotExist);
207 return false;
208 }
209 if (length >= 4096) {
210 SYSCALL_ERROR(NameTooLong);
211 return false;
212 }
213 FilesystemPathRef relative;
214 if (!parent(current, relative))
215 return false;
216 const size_t targetLength = static_cast<size_t>(length);
217 const size_t remaining = pending.length() - offset;
218 const size_t separator = remaining ? 1 : 0;
219 if (remaining > ~size_t(0) - targetLength - separator) {
220 SYSCALL_ERROR(NameTooLong);
221 return false;
222 }
223 const size_t combined = targetLength + separator + remaining;
224 auto expanded = UniqueArray<char>::allocate(combined);
225 if (!expanded) {
226 SYSCALL_ERROR(OutOfMemory);
227 return false;
228 }
229 MemoryCopy(expanded.get(), linkStorage.get(), targetLength);
230 if (remaining) {
231 expanded.get()[targetLength] = '/';
232 MemoryCopy(expanded.get() + targetLength + 1, pending.str() + offset, remaining);
233 } else if (linkStorage.get()[targetLength - 1] == '/') {
234 trailingSlash = true;
235 }
236 current = linkStorage.get()[0] == '/' ? context.root : relative;
237 // Keep just the pending suffix, rather than a call frame for each link.
238 pendingStorage = pedigree_std::move(expanded);
239 pending = StringView(pendingStorage.get(), combined);
240 offset = 0;
241 followCurrent = false;
242 crossCurrent = false;
243 continue;
244 }
245 if (crossCurrent && VfsMountView::fromPath(current)->m_State->path(current)) {
246 FilesystemPathRef crossed;
247 if (!cross(current, crossed))
248 return false;
249 current = pedigree_std::move(crossed);
250 }
251 crossCurrent = false;
252 while (offset < pending.length() && pending[offset] == '/')
253 ++offset;
254 if (offset == pending.length())
255 break;
256 const size_t begin = offset;
257 while (offset < pending.length() && pending[offset] != '/')
258 ++offset;
259 StringView component = pending.substring(begin, offset);
260 size_t next = offset;
261 while (next < pending.length() && pending[next] == '/')
262 ++next;
263 const bool final = next == pending.length();
264 auto* selectedState = VfsMountView::fromPath(current)->m_State;
265 if (!selectedState->path(current) || !current->node()->isDirectory()) {
266 SYSCALL_ERROR(NotADirectory);
267 return false;
268 }
269 if (!VFS::checkAccess(current->node(), false, false, true, credentials)) {
270 return false;
271 }
272 followCurrent = false;
273 if (component == ".") {
274 offset = next;
275 continue;
276 }
277 if (component == "..") {
278 if (!view.samePath(current, context.root)) {
279 FilesystemPathRef above;
280 if (!parent(current, above, context.root))
281 return false;
282 current = pedigree_std::move(above);
283 }
284 offset = next;
285 continue;
286 }
288 const auto status =
289 Directory::fromFile(current->node())->lookupChild(HashedStringView(component), child);
290 if (status != Directory::LookupStatus::Found) {
291 syscallError(status == Directory::LookupStatus::IoError ? Error::IoError
292 : status == Directory::LookupStatus::Retry ? Error::NoMoreProcesses
293 : Error::DoesNotExist);
294 return false;
295 }
296 FilesystemPathRef candidate;
297 if (!selectedState->makePath(selectedState->path(current)->attachment, VfsNodeReference(child),
298 candidate)) {
299 return false;
300 }
301 if (!candidate->node()->isDirectory()) {
302 static_cast<VfsPath*>(candidate.get())->lookupParent = current;
303 static_cast<VfsPath*>(candidate.get())->lookupName = String(component);
304 }
305 current = pedigree_std::move(candidate);
306 offset = next;
307 followCurrent = !final || options.followFinal || trailingSlash;
308 crossCurrent = !final || options.crossFinalMount;
309 }
310 if ((options.requireDirectory || trailingSlash) && !current->node()->isDirectory()) {
311 SYSCALL_ERROR(NotADirectory);
312 return false;
313 }
314 result = pedigree_std::move(current);
315 return true;
316}
317
318bool VfsMountView::State::resolve(const FilesystemContextSnapshot& context,
319 const FilesystemPathRef& start, const String& pathname,
320 const ResolveOptions& options, FilesystemPathRef& result,
321 const VFS::NamespaceMutation* writer) {
322 if (!pathname.length()) {
323 SYSCALL_ERROR(DoesNotExist);
324 return false;
325 }
326 if (writer && !writer->protects(view.m_Vfs)) {
327 SYSCALL_ERROR(InvalidArgument);
328 return false;
329 }
330 for (;;) {
331 ResolutionAttempt attempt;
332 const uint64_t generation = view.m_Vfs.namespaceGeneration();
333 if (!writer && (generation & 1)) {
334 // Wait for admission instead of spinning while a writer performs I/O.
335 VFS::NamespaceMutation completed(view.m_Vfs);
336 continue;
337 }
338 FilesystemPathRef found;
339 size_t links = 0;
340 const bool success = walk(context, start, pathname, options, found, links);
341 if (generation != view.m_Vfs.namespaceGeneration())
342 continue;
343 if (!success) {
344 attempt.failure();
345 return false;
346 }
347 result = pedigree_std::move(found);
348 return true;
349 }
350}
351
352bool VfsMountView::resolve(const FilesystemContextRef& context, const FilesystemPathRef& start,
353 const String& pathname, const ResolveOptions& options,
354 FilesystemPathRef& result) {
355 if (!m_State || !context) {
356 SYSCALL_ERROR(DoesNotExist);
357 return false;
358 }
359 for (;;) {
360 ResolutionAttempt attempt;
362 if (!context->snapshot(snapshot)) {
363 SYSCALL_ERROR(DoesNotExist);
364 attempt.failure();
365 return false;
366 }
367 FilesystemPathRef found;
368 const bool success =
369 m_State->resolve(snapshot, start ? start : snapshot.cwd, pathname, options, found);
370 bool coherent;
371 {
372 LockGuard<Mutex> guard(m_State->graph);
373 VfsFilesystemContext* live = nullptr;
374 coherent = m_State->context(context, live) &&
375 live->generation == snapshot.contextGeneration &&
376 m_State->topology == snapshot.topologyGeneration;
377 }
378 if (!coherent)
379 continue;
380 if (!success) {
381 attempt.failure();
382 return false;
383 }
384 result = pedigree_std::move(found);
385 return true;
386 }
387}
388
389bool VfsMountView::follow(const FilesystemContextRef& context, const FilesystemPathRef& selected,
390 FilesystemPathRef& result) {
391 if (!m_State || !context || !VfsMountView::fromPath(selected)) {
392 SYSCALL_ERROR(DoesNotExist);
393 return false;
394 }
395 for (;;) {
396 ResolutionAttempt attempt;
397 const uint64_t generation = m_Vfs.namespaceGeneration();
398 if (generation & 1) {
399 VFS::NamespaceMutation completed(m_Vfs);
400 continue;
401 }
403 if (!context->snapshot(snapshot)) {
404 SYSCALL_ERROR(DoesNotExist);
405 attempt.failure();
406 return false;
407 }
408 FilesystemPathRef found;
409 size_t links = 0;
410 ResolveOptions options;
411 const bool success = m_State->follow(snapshot, selected, options, found, links);
412 if (generation != m_Vfs.namespaceGeneration())
413 continue;
414 bool coherent;
415 {
416 LockGuard<Mutex> guard(m_State->graph);
417 VfsFilesystemContext* live = nullptr;
418 coherent = m_State->context(context, live) &&
419 live->generation == snapshot.contextGeneration &&
420 m_State->topology == snapshot.topologyGeneration;
421 }
422 if (!coherent)
423 continue;
424 if (!success) {
425 attempt.failure();
426 return false;
427 }
428 result = pedigree_std::move(found);
429 return true;
430 }
431}
432
433bool VfsMountView::resolveParent(const FilesystemContextRef& context,
434 const FilesystemPathRef& start, const String& pathname,
435 FilesystemPathRef& parent, String& basename) {
436 if (!pathname.length()) {
437 SYSCALL_ERROR(DoesNotExist);
438 return false;
439 }
440 size_t end = pathname.length();
441 while (end > 1 && pathname[end - 1] == '/')
442 --end;
443 size_t slash = end;
444 while (slash && pathname[slash - 1] != '/')
445 --slash;
446 String name(pathname.view().substring(slash, end));
447 if (name.length() > 255) {
448 SYSCALL_ERROR(NameTooLong);
449 return false;
450 }
451 String prefix(slash ? pathname.view().substring(0, slash) : StringView("."));
452 ResolveOptions options;
453 options.requireDirectory = true;
454 FilesystemPathRef resolved;
455 if (!resolve(context, start, prefix, options, resolved))
456 return false;
457 parent = pedigree_std::move(resolved);
458 basename = pedigree_std::move(name);
459 return true;
460}
static Directory * fromFile(File *pF)
Definition Directory.h:151
MUST_USE_RESULT LookupStatus lookupChild(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:355
virtual bool isSymlink()
Definition File.cc:800
virtual bool isDirectory()
Definition File.cc:804
T * get() const
StringView substring(size_t start, size_t end, bool hashed=HASH_STRINGVIEWS_BY_DEFAULT) const
StringView view() const
Definition String.cc:783
static bool checkAccess(File *pFile, bool bRead, bool bWrite, bool bExecute)
Definition VFS.cc:1502
uint64_t namespaceGeneration() const