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