The Pedigree Project 0.1
Filesystem.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "Filesystem.h"
21#include "pedigree/kernel/LockGuard.h"
22#include "pedigree/kernel/Log.h"
23#include "pedigree/kernel/process/Process.h"
24#include "pedigree/kernel/process/Thread.h"
25#include "pedigree/kernel/processor/Processor.h"
26#include "pedigree/kernel/processor/ProcessorInformation.h"
27#include "pedigree/kernel/syscallError.h"
28#include "pedigree/kernel/utilities/LazyEvaluate.h"
29#include "pedigree/kernel/utilities/StringView.h"
30#include "pedigree/kernel/utilities/utility.h"
31
32#include "Directory.h"
33#include "File.h"
34#include "MountView.h"
35#include "Symlink.h"
36#include "VFS.h"
37
38Filesystem::Filesystem() : m_bReadOnly(false), m_pDisk(0) {}
39
41
42Filesystem::~Filesystem() = default;
43
44Filesystem::SyncStatus Filesystem::sync() {
45 return m_bReadOnly ? SyncStatus::Success : SyncStatus::Unsupported;
46}
47
48Filesystem::SyncStatus Filesystem::shutdown() {
49 // Persistent writable backends must explicitly certify their teardown path.
50 return m_bReadOnly || !m_pDisk ? sync() : SyncStatus::Unsupported;
51}
52
53FileHandleStatus Filesystem::encodeFileHandle(File&, FileHandle& handle) {
54 handle = FileHandle();
55 return FileHandleStatus::Unsupported;
56}
57
58FileHandleStatus Filesystem::decodeFileHandle(const FileHandle&, RetainedFile& file) {
59 file.reset();
60 return FileHandleStatus::Unsupported;
61}
62
63FileHandleStatus Filesystem::fileHandleFsid(FileSystemId& id) {
64 id = FileSystemId();
65 return FileHandleStatus::Unsupported;
66}
67
68namespace {
69class InodeRetirementDrain {
70 public:
71 ~InodeRetirementDrain() {
72 if (m_File)
73 m_File.get()->finishInodeRetirement();
74 }
75 bool retain(File* file) {
76 if (!file)
77 return true;
78 if (!file->retainVfsReference()) {
79 SYSCALL_ERROR(IoError);
80 return false;
81 }
82 m_File.adopt(file);
83 return true;
84 }
85
86 private:
87 RetainedFile m_File;
88};
89
90class TrueRootLease {
91 public:
92 explicit TrueRootLease(Filesystem* filesystem) : m_pRoot(filesystem->getRoot()) {}
93 File* get() const {
94 return m_pRoot;
95 }
96
97 private:
98 File* m_pRoot;
99};
100
101bool targetAbsentForCreate(File* parent, const String& filename) {
102 if (!filename.length() || filename == "." || filename == "..") {
103 SYSCALL_ERROR(InvalidArgument);
104 return false;
105 }
106 if (!parent->isDirectory()) {
107 SYSCALL_ERROR(NotADirectory);
108 return false;
109 }
110
111 Directory::ChildLease existing;
112 const Directory::LookupStatus status =
113 Directory::fromFile(parent)->lookupChild(HashedStringView(filename), existing);
114 if (status == Directory::LookupStatus::NotFound) {
115 return true;
116 }
117 if (status == Directory::LookupStatus::IoError) {
118 SYSCALL_ERROR(IoError);
119 } else {
120 SYSCALL_ERROR(FileExists);
121 }
122 return false;
123}
124} // namespace
125
127 return findNode(nullptr, path);
128}
129
130File* Filesystem::find(const String& path) {
131 return findNode(nullptr, path.view());
132}
133
134File* Filesystem::find(const StringView& path, File* pStartNode) {
135 assert(pStartNode != nullptr);
136 File* a = findNode(pStartNode, path);
137 return a;
138}
139
140File* Filesystem::find(const String& path, File* pStartNode) {
141 return find(path.view(), pStartNode);
142}
143
145 File* pStartNode) {
146 TrueRootLease rootLease(this);
147 File* trueRoot = rootLease.get();
148 if (!pStartNode) {
149 pStartNode = trueRoot;
150 }
151
152 File* retained = nullptr;
153 File* found = findNode(pStartNode, path, pStartNode, trueRoot, &retained);
154 if (!found) {
155 return nullptr;
156 }
157
158 Directory::ChildLease replacement;
159 if (retained) {
160 replacement.adopt(retained);
161 }
162 result.swap(replacement);
163 return found;
164}
165
166bool Filesystem::createFile(const StringView& path, uint32_t mask, File* pStartNode) {
167 TrueRootLease startLease(this);
168 if (!pStartNode) {
169 pStartNode = startLease.get();
170 }
171
172 String filename;
173 Directory::ChildLease parentLease;
174 File* retainedParent = nullptr;
175 File* pParent = findParent(path, pStartNode, filename, &retainedParent);
176 if (retainedParent)
177 parentLease.adopt(retainedParent);
178
179 // Check the parent existed.
180 if (!pParent) {
181 SYSCALL_ERROR(DoesNotExist);
182 return false;
183 }
184
185 if (!targetAbsentForCreate(pParent, filename))
186 return false;
187
188 // Are we allowed to make the file?
189 if (!VFS::checkAccess(pParent, false, true, true)) {
190 return false;
191 }
192
193 // May need to create on a different filesytem (if the traversal crossed
194 // over to a different fs)
195 Filesystem* pFs = pParent->getFilesystem();
196
197 // Now make the file.
198 return pFs->createFile(pParent, filename, mask);
199}
200
201bool Filesystem::createDirectory(const StringView& path, uint32_t mask, File* pStartNode) {
202 TrueRootLease startLease(this);
203 if (!pStartNode) {
204 pStartNode = startLease.get();
205 }
206
207 String filename;
208 Directory::ChildLease parentLease;
209 File* retainedParent = nullptr;
210 File* pParent = findParent(path, pStartNode, filename, &retainedParent);
211 if (retainedParent)
212 parentLease.adopt(retainedParent);
213
214 // Check the parent existed.
215 if (!pParent) {
216 SYSCALL_ERROR(DoesNotExist);
217 return false;
218 }
219
220 if (!targetAbsentForCreate(pParent, filename))
221 return false;
222
223 // Are we allowed to make the file?
224 if (!VFS::checkAccess(pParent, false, true, true)) {
225 return false;
226 }
227
228 // May need to create on a different filesytem (if the traversal crossed
229 // over to a different fs)
230 Filesystem* pFs = pParent->getFilesystem();
231
232 // Now make the directory.
233 return pFs->createDirectory(pParent, filename, mask);
234}
235
236bool Filesystem::createSymlink(const StringView& path, const String& value, File* pStartNode) {
237 TrueRootLease startLease(this);
238 if (!pStartNode) {
239 pStartNode = startLease.get();
240 }
241
242 String filename;
243 Directory::ChildLease parentLease;
244 File* retainedParent = nullptr;
245 File* pParent = findParent(path, pStartNode, filename, &retainedParent);
246 if (retainedParent)
247 parentLease.adopt(retainedParent);
248
249 // Check the parent existed.
250 if (!pParent) {
251 SYSCALL_ERROR(DoesNotExist);
252 return false;
253 }
254
255 if (!targetAbsentForCreate(pParent, filename))
256 return false;
257
258 // Are we allowed to make the file?
259 if (!VFS::checkAccess(pParent, false, true, true)) {
260 return false;
261 }
262
263 // May need to create on a different filesytem (if the traversal crossed
264 // over to a different fs)
265 Filesystem* pFs = pParent->getFilesystem();
266
267 // Now make the symlink.
268 return pFs->createSymlink(pParent, filename, value);
269}
270
271bool Filesystem::createLink(const StringView& path, File* target, File* pStartNode) {
272 TrueRootLease startLease(this);
273 if (!pStartNode) {
274 pStartNode = startLease.get();
275 }
276
277 String filename;
278 Directory::ChildLease parentLease;
279 File* retainedParent = nullptr;
280 File* pParent = findParent(path, pStartNode, filename, &retainedParent);
281 if (retainedParent)
282 parentLease.adopt(retainedParent);
283
284 // Check the parent existed.
285 if (!pParent) {
286 SYSCALL_ERROR(DoesNotExist);
287 return false;
288 }
289
290 if (!targetAbsentForCreate(pParent, filename))
291 return false;
292
293 // Are we allowed to make the file?
294 if (!VFS::checkAccess(pParent, false, true, true)) {
295 return false;
296 }
297
298 // Links can't cross filesystems (symlinks can, though).
299 if (this != target->getFilesystem()) {
300 SYSCALL_ERROR(CrossDeviceLink);
301 return false;
302 }
303
304 // May need to create on a different filesytem (if the traversal crossed
305 // over to a different fs)
306 Filesystem* pFs = pParent->getFilesystem();
307
308 // Now make the symlink.
309 return pFs->createLink(pParent, filename, target);
310}
311
312bool Filesystem::remove(const StringView& path, File* pStartNode) {
313 return remove(path, pStartNode, nullptr);
314}
315
316bool Filesystem::remove(const StringView& path, File* pStartNode, File* expected) {
317 TrueRootLease startLease(this);
318 if (!pStartNode) {
319 pStartNode = startLease.get();
320 }
321
322 String filename;
323 Directory::ChildLease parentLease;
324 File* retainedParent = nullptr;
325 File* pParent = findParent(path, pStartNode, filename, &retainedParent);
326 if (retainedParent)
327 parentLease.adopt(retainedParent);
328
329 // Check the parent existed.
330 if (!pParent) {
331 SYSCALL_ERROR(DoesNotExist);
332 return false;
333 }
334
335 // Dot entries are traversal operators, not removable directory entries.
336 // In-memory filesystems may represent them explicitly, so reject them
337 // before lookup rather than relying on individual drivers to do so.
338 if (!filename.length() || filename == "." || filename == "..") {
339 SYSCALL_ERROR(InvalidArgument);
340 return false;
341 }
342
343 // Are we allowed to delete the file?
344 if (!VFS::checkAccess(pParent, false, true, true)) {
345 return false;
346 }
347
348 // May need to create on a different filesytem (if the traversal crossed
349 // over to a different fs)
350 Filesystem* pFs = pParent->getFilesystem();
351 return pFs->removeChild(pParent, filename, expected);
352}
353
354bool Filesystem::remove(File* parent, File* file) {
355 if (!file) {
356 SYSCALL_ERROR(DoesNotExist);
357 return false;
358 }
359 return removeChild(parent, file->getName(), file);
360}
361
362bool Filesystem::rename(const StringView& oldPath, File* oldStart, const StringView& newPath,
363 File* newStart, bool noReplace) {
364 if (!oldPath.length() || !newPath.length()) {
365 SYSCALL_ERROR(DoesNotExist);
366 return false;
367 }
368 TrueRootLease rootLease(this);
369 if (!oldStart) {
370 oldStart = rootLease.get();
371 }
372 if (!newStart) {
373 newStart = rootLease.get();
374 }
375 if (!oldStart || !newStart) {
376 SYSCALL_ERROR(DoesNotExist);
377 return false;
378 }
379
380 String oldName;
381 String newName;
382 Directory::ChildLease oldParentLease;
383 Directory::ChildLease newParentLease;
384 File* retained = nullptr;
385 File* oldParentFile =
386 oldStart->getFilesystem()->findParent(oldPath, oldStart, oldName, &retained);
387 if (retained) {
388 oldParentLease.adopt(retained);
389 }
390 retained = nullptr;
391 File* newParentFile =
392 newStart->getFilesystem()->findParent(newPath, newStart, newName, &retained);
393 if (retained) {
394 newParentLease.adopt(retained);
395 }
396 return renameChildren(
397 oldParentFile, oldName, newParentFile, newName, noReplace,
398 oldPath[oldPath.length() - 1] == '/' || newPath[newPath.length() - 1] == '/');
399}
400
401bool Filesystem::renameChildren(File* oldParentFile, const String& oldName, File* newParentFile,
402 const String& newName, bool noReplace, bool sourceMustBeDirectory) {
403 InodeRetirementDrain retirement;
404 VFS::NamespaceMutation namespaceWriter(VFS::instance());
405 if (!oldParentFile || !newParentFile) {
406 SYSCALL_ERROR(DoesNotExist);
407 return false;
408 }
409 if (!oldParentFile->isDirectory() || !newParentFile->isDirectory()) {
410 SYSCALL_ERROR(NotADirectory);
411 return false;
412 }
413 if (!oldName.length() || !newName.length() || oldName == "." || oldName == ".." ||
414 newName == "." || newName == "..") {
415 SYSCALL_ERROR(InvalidArgument);
416 return false;
417 }
418 Filesystem* filesystem = oldParentFile->getFilesystem();
419 if (filesystem != newParentFile->getFilesystem()) {
420 SYSCALL_ERROR(CrossDeviceLink);
421 return false;
422 }
423 LockGuard<Mutex> structureGuard(filesystem->m_StructureLock);
424 if (filesystem->isReadOnly()) {
425 SYSCALL_ERROR(ReadOnlyFilesystem);
426 return false;
427 }
428 if (!VFS::checkAccess(oldParentFile, false, true, true) ||
429 !VFS::checkAccess(newParentFile, false, true, true)) {
430 return false;
431 }
432
433 Directory* oldParent = Directory::fromFile(oldParentFile);
434 Directory* newParent = Directory::fromFile(newParentFile);
435 const bool oldFirst =
436 reinterpret_cast<uintptr_t>(oldParent) < reinterpret_cast<uintptr_t>(newParent);
437 Directory* first = oldFirst ? oldParent : newParent;
438 Directory* second = oldFirst ? newParent : oldParent;
439 LockGuard<Mutex> firstGuard(first->namespaceMutationLock());
440 LockGuard<Mutex> secondGuard(second->namespaceMutationLock(), second != first);
441 if (oldParent->isDetached() || newParent->isDetached()) {
442 SYSCALL_ERROR(DoesNotExist);
443 return false;
444 }
445
446 Directory::ChildLease sourceLease;
447 Directory::ChildLease replacedLease;
448 const auto sourceStatus = oldParent->lookupChild(HashedStringView(oldName), sourceLease);
449 if (sourceStatus != Directory::LookupStatus::Found) {
450 syscallError(sourceStatus == Directory::LookupStatus::IoError ? Error::IoError
451 : Error::DoesNotExist);
452 return false;
453 }
454 File* source = sourceLease.get();
455 if (oldParent == newParent && oldName == newName) {
456 if (noReplace) {
457 SYSCALL_ERROR(FileExists);
458 return false;
459 }
460 return true;
461 }
462 const auto replacedStatus = newParent->lookupChild(HashedStringView(newName), replacedLease);
463 if (replacedStatus == Directory::LookupStatus::IoError) {
464 SYSCALL_ERROR(IoError);
465 return false;
466 }
467 File* replaced = replacedLease.get();
468 // Both parent namespace locks exclude creators until reservation and commit.
469 // Check before the same-inode fast path: NOREPLACE rejects hard-link aliases too.
470 if (noReplace && replaced) {
471 SYSCALL_ERROR(FileExists);
472 return false;
473 }
474 if (source->getFilesystem() != filesystem ||
475 (replaced && replaced->getFilesystem() != filesystem)) {
476 SYSCALL_ERROR(CrossDeviceLink);
477 return false;
478 }
479 if (replaced &&
480 (source == replaced || (source->getInode() && source->getInode() == replaced->getInode()))) {
481 return true;
482 }
483 if (sourceMustBeDirectory && !source->isDirectory()) {
484 SYSCALL_ERROR(NotADirectory);
485 return false;
486 }
487 if (replaced && replaced->isDirectory() != source->isDirectory()) {
488 syscallError(replaced->isDirectory() ? Error::IsADirectory : Error::NotADirectory);
489 return false;
490 }
491 Directory* sourceDirectory = source->isDirectory() ? Directory::fromFile(source) : nullptr;
492 Directory* replacedDirectory =
493 replaced && replaced->isDirectory() ? Directory::fromFile(replaced) : nullptr;
494 auto* view = VFS::instance().mountView();
495 if ((sourceDirectory && (view ? view->isMountpoint(sourceDirectory)
496 : sourceDirectory->getReparsePoint() != nullptr)) ||
497 (replacedDirectory && (view ? view->isMountpoint(replacedDirectory)
498 : replacedDirectory->getReparsePoint() != nullptr))) {
499 SYSCALL_ERROR(DeviceBusy);
500 return false;
501 }
502 if (sourceDirectory) {
503 File* ancestor = newParent;
504 File::ParentLease ancestorLease;
505 while (ancestor) {
506 if (ancestor == source) {
507 SYSCALL_ERROR(InvalidArgument);
508 return false;
509 }
511 String unused;
512 ancestor->getNamespace(next, unused);
513 ancestorLease.swap(next);
514 ancestor = ancestorLease.get();
515 }
516 }
517 if (replacedDirectory == oldParent || replacedDirectory == newParent) {
518 SYSCALL_ERROR(NotEmpty);
519 return false;
520 }
521
522 LockGuard<Mutex> sourceGuard(sourceDirectory ? sourceDirectory->namespaceMutationLock()
523 : oldParent->namespaceMutationLock(),
524 sourceDirectory != nullptr);
525 LockGuard<Mutex> replacedGuard(replacedDirectory ? replacedDirectory->namespaceMutationLock()
526 : oldParent->namespaceMutationLock(),
527 replacedDirectory != nullptr);
528 if (replacedDirectory) {
529 bool empty = false;
530 if (replacedDirectory->isEmpty(empty) != Directory::ReadStatus::Complete) {
531 SYSCALL_ERROR(IoError);
532 return false;
533 }
534 if (!empty) {
535 SYSCALL_ERROR(NotEmpty);
536 return false;
537 }
538 }
539
540 Directory::NameReservation oldReservation;
541 Directory::NameReservation newReservation;
542 if (!oldParent->reserveRenameEntry(oldName, oldReservation) ||
543 !newParent->reserveRenameEntry(newName, newReservation)) {
544 SYSCALL_ERROR(DoesNotExist);
545 return false;
546 }
547 bool sourceEphemeral = false;
548 bool replacedEphemeral = false;
549 {
550 LockGuard<Mutex> guard(oldParent->m_CacheLock);
551 sourceEphemeral = oldParent->m_EphemeralEntries.lookup(oldName).hasValue();
552 }
553 if (replaced) {
554 LockGuard<Mutex> guard(newParent->m_CacheLock);
555 replacedEphemeral = newParent->m_EphemeralEntries.lookup(newName).hasValue();
556 }
557 if (sourceEphemeral) {
558 if (newName.length() > 255) {
559 SYSCALL_ERROR(NameTooLong);
560 return false;
561 }
562 if (sourceDirectory) {
563 SYSCALL_ERROR(OperationNotSupported);
564 return false;
565 }
566 // Overlay names have no backing record. Remove a backing victim before
567 // the non-fallible namespace publication, keeping both names reserved.
568 if (replaced && !replacedEphemeral &&
569 (!retirement.retain(replaced) || !filesystem->removeNode(newParent, newName, replaced))) {
570 return false;
571 }
572 } else if (!retirement.retain(replacedEphemeral ? nullptr : replaced) ||
573 !filesystem->renameNode(oldParent, oldName, source, newParent, newName,
574 replacedEphemeral ? nullptr : replaced)) {
575 return false;
576 }
577 if (replaced) {
578 replaced->retainDetachedParent();
579 if (replacedDirectory) {
580 replacedDirectory->markDetached();
581 }
582 }
583 source->moveNamespace(newName, newParent);
584 if (sourceDirectory) {
585 __atomic_store_n(&sourceDirectory->m_ParentInode, newParent->getInode(), __ATOMIC_RELEASE);
586 }
587 oldParent->moveReservedEntry(oldReservation, newParent, newReservation, source);
588 oldReservation.complete(Directory::LookupStatus::NotFound);
589 newReservation.complete(Directory::LookupStatus::Found);
590 if (replaced) {
591 replaced->publishEvent(FileEvents::DeletedSelf);
592 }
593 return true;
594}
595
597 SYSCALL_ERROR(OperationNotSupported);
598 return false;
599}
600
601bool Filesystem::removeChild(File* parent, const String& filename, File* expected) {
602 InodeRetirementDrain retirement;
603 VFS::NamespaceMutation namespaceWriter(VFS::instance());
604 LockGuard<Mutex> structureGuard(m_StructureLock);
605 if (!parent || !parent->isDirectory()) {
606 SYSCALL_ERROR(NotADirectory);
607 return false;
608 }
609 if (!filename.length() || filename == "." || filename == "..") {
610 SYSCALL_ERROR(InvalidArgument);
611 return false;
612 }
613
614 Directory* directory = Directory::fromFile(parent);
615 LockGuard<Mutex> namespaceGuard(directory->namespaceMutationLock());
616
617 auto publishRemoval = [&](File* target) {
618 // Publish the detached state before terminal event delivery. The event
619 // source then linearizes subscription closure with its final snapshot.
620 target->retainDetachedParent();
621 directory->publishEvent(FileEvents::Removed, filename.view(), target->isDirectory());
622 // TODO: FileEventSource currently follows a VFS namespace node. Linux
623 // retires an inode watch only after its final link/open lifecycle; open
624 // unlink and hard-link aliases need a shared inode-identity event source.
625 target->publishEvent(FileEvents::DeletedSelf);
626 };
627
629 const Directory::LookupStatus lookup = directory->lookupChild(HashedStringView(filename), target);
630 if (lookup != Directory::LookupStatus::Found) {
631 if (lookup == Directory::LookupStatus::IoError) {
632 SYSCALL_ERROR(IoError);
633 } else {
634 SYSCALL_ERROR(DoesNotExist);
635 }
636 return false;
637 }
638 if (expected && target.get() != expected) {
639 SYSCALL_ERROR(DoesNotExist);
640 return false;
641 }
642
643 auto* view = VFS::instance().mountView();
644 if (view && view->isMountpoint(target.get())) {
645 SYSCALL_ERROR(DeviceBusy);
646 return false;
647 }
648
649 if (target.get()->isDirectory()) {
650 Directory* childDirectory = Directory::fromFile(target.get());
651 LockGuard<Mutex> childNamespaceGuard(childDirectory->namespaceMutationLock());
652 bool empty = false;
653 const Directory::ReadStatus status = childDirectory->isEmpty(empty);
654 if (status != Directory::ReadStatus::Complete) {
655 SYSCALL_ERROR(IoError);
656 return false;
657 }
658 if (!empty) {
659 SYSCALL_ERROR(NotEmpty);
660 return false;
661 }
662
663 // Ephemeral directories never reach a filesystem driver, so the VFS
664 // must complete their detachment while both namespace boundaries are
665 // held. Backing drivers recheck emptiness after acquiring this lock.
666 if (directory->removeEphemeralFileLocked(HashedStringView(filename), target.get())) {
667 childDirectory->markDetached();
668 publishRemoval(target.get());
669 return true;
670 }
671 }
672
673 // Ephemeral overlays belong to the VFS namespace, not to the backing
674 // filesystem whose directory they appear in. Classification and removal
675 // share the same namespace critical section as backing removal.
676 if (directory->removeEphemeralFileLocked(HashedStringView(filename), target.get())) {
677 publishRemoval(target.get());
678 return true;
679 }
680
681 if (!retirement.retain(target.get()) || !removeNode(parent, filename, target.get())) {
682 return false;
683 }
684
685 publishRemoval(target.get());
686 return true;
687}
688
690 TrueRootLease rootLease(this);
691 File* trueRoot = rootLease.get();
692 if (!pNode) {
693 pNode = trueRoot;
694 }
695 return findNode(pNode, path, pNode, trueRoot, nullptr);
696}
697
698File* Filesystem::findNode(File* pNode, StringView path, File* stableStart, File* trueRoot,
699 File** retainedResult) {
700 if (UNLIKELY(path.length() == 0)) {
701 if (retainedResult && !*retainedResult) {
702 if (VFS::instance().retainTrackedFile(pNode)) {
703 *retainedResult = pNode;
704 } else if (pNode != stableStart && pNode != trueRoot) {
705 return nullptr;
706 }
707 }
708 return pNode;
709 }
710
711 // If the pathname has a leading slash, cd to root and remove it.
712 else if (path[0] == '/') {
713 pNode = trueRoot;
714 path = path.substring(1, path.length());
715 }
716
717 // Grab the next filename component.
718 size_t i = 0;
719 size_t nExtra = 0;
720 while ((i < path.length()) && path[i] != '/') {
721 i = path.nextCharacter(i);
722 }
723 while (i < path.length()) {
724 size_t n = path.nextCharacter(i);
725 if (n >= path.length()) {
726 break;
727 } else if (path[n] == '/') {
728 i = n;
729 ++nExtra;
730 } else {
731 break;
732 }
733 }
734
735 StringView currentComponent = path.substring(0, i - nExtra);
736 StringView restOfPath = path.substring(path.nextCharacter(i), path.length());
737
738 // At this point 'currentComponent' contains the token to search for.
739 // 'restOfPath' contains the path for the next recursion (or nil).
740
741 // If 'path' is zero-lengthed, ignore and recurse.
742 if (currentComponent.length() == 0) {
743 return findNode(pNode, restOfPath, stableStart, trueRoot, retainedResult);
744 }
745
746 // Firstly, if the current node is a symlink, follow it.
748 Directory::ChildLease followedLease;
749 while (pNode->isSymlink()) {
750 Directory::ChildLease nextLease;
751 pNode = Symlink::fromFile(pNode)->followLinkRetained(nextLease);
752 if (!pNode) {
753 return nullptr;
754 }
755 followedLease.swap(nextLease);
756 }
757
758 // Next, if the current node isn't a directory, die.
759 if (!pNode->isDirectory()) {
760 SYSCALL_ERROR(NotADirectory);
761 return 0;
762 }
763
764 bool dot = currentComponent == ".";
765 bool dotdot = currentComponent == "..";
766 File::ParentLease parentLease;
767 String unusedName;
768 if (dotdot) {
769 pNode->getNamespace(parentLease, unusedName);
770 }
771 File* parent = parentLease.get();
772
773 // '.' section, or '..' with no parent, or '..' and we're at the root.
774 if (dot || (dotdot && !parent) || (dotdot && pNode == trueRoot)) {
775 return findNode(pNode, restOfPath, stableStart, trueRoot, retainedResult);
776 } else if (dotdot) {
777 return findNode(parent, restOfPath, stableStart, trueRoot, retainedResult);
778 }
779
780 Directory* pDir = Directory::fromFile(pNode);
781 if (!pDir) {
782 SYSCALL_ERROR(NotADirectory);
783 return 0;
784 }
785
786 // Is this a reparse point? If so we need to change where we perform the
787 // next lookup.
788 Directory* reparse = pDir->getReparsePoint();
789 if (reparse) {
790 String fullPath, reparseFullPath;
791 pDir->getFullPath(fullPath);
792 pDir->getFullPath(reparseFullPath);
793 WARNING("VFS: found reparse point at '"
794 << fullPath << "', following it (new target: " << reparseFullPath << ")");
795 pDir = reparse;
796 }
797
798 // Are we allowed to access files in this directory?
799 if (!VFS::checkAccess(pNode, false, false, true)) {
800 return 0;
801 }
802
803 if (!retainedResult && !pDir->cacheResolvedChildren()) {
804 // Generated nodes have no cache owner after the lookup lease leaves scope.
805 SYSCALL_ERROR(OperationNotSupported);
806 return nullptr;
807 }
808
810 Directory::LookupStatus lookup = pDir->lookupChild(HashedStringView(currentComponent), child);
811 if (lookup == Directory::LookupStatus::Found) {
812 return findNode(child.get(), restOfPath, stableStart, trueRoot, retainedResult);
813 }
814 if (lookup == Directory::LookupStatus::IoError) {
815 SYSCALL_ERROR(IoError);
816 }
817 return nullptr;
818}
819
820File* Filesystem::findParent(StringView path, File* pStartNode, String& filename,
821 File** retainedParent) {
822 if (retainedParent) {
823 *retainedParent = nullptr;
824 }
825 TrueRootLease rootLease(this);
826 File* trueRoot = rootLease.get();
827
828 // If the final character of the string is '/', this log falls apart. So,
829 // check for that and chomp it. But, we also need to not do that for e.g.
830 // path == '/'.
831 if (path.length() > 1 && path[path.length() - 1] == '/') {
832 path = path.substring(0, path.length() - 1);
833 }
834
835 // Work forwards to the end of the path string, attempting to find the last
836 // '/'.
837 ssize_t lastSlash = -1;
838 for (ssize_t i = path.length() - 1; i >= 0; i = path.prevCharacter(i)) {
839 if (path[i] == '/') {
840 lastSlash = i;
841 break;
842 }
843 }
844
845 // Now, if there were no slashes, the parent node is pStartNode.
846 File* parentNode = nullptr;
847 if (lastSlash == -1) {
848 filename = path.toString();
849 parentNode = pStartNode;
850 if (retainedParent && VFS::instance().retainTrackedFile(parentNode)) {
851 *retainedParent = parentNode;
852 }
853 } else {
854 // Else split the filename off from the rest of the path and follow it.
855 filename = path.substring(path.nextCharacter(lastSlash), path.length()).toString();
856 path = path.substring(0, lastSlash);
857 if (lastSlash == 0) {
858 parentNode = trueRoot;
859 if (retainedParent && VFS::instance().retainTrackedFile(parentNode)) {
860 *retainedParent = parentNode;
861 }
862 } else {
863 parentNode = findNode(pStartNode, path, pStartNode, trueRoot, retainedParent);
864 }
865 }
866
867 // Handle immediate parent node being a reparse point.
868 if (parentNode) {
869 if (parentNode->isDirectory()) {
870 File* reparseNode = Directory::fromFile(parentNode)->getReparsePoint();
871 if (reparseNode) {
872 if (retainedParent && *retainedParent) {
873 VFS::instance().untrackFile(*retainedParent);
874 *retainedParent = nullptr;
875 }
876 if (retainedParent && VFS::instance().retainTrackedFile(reparseNode)) {
877 *retainedParent = reparseNode;
878 }
879 parentNode = reparseNode;
880 }
881 }
882 }
883
884 return parentNode;
885}
886
887bool Filesystem::createLink(File* parent, const String& filename, File* target) {
888 // Default stubbed implementation, works for filesystems that can't handle
889 // hard links.
890 return false;
891}
static Directory * fromFile(File *pF)
Definition Directory.h:148
EphemeralEntryCache m_EphemeralEntries
Definition Directory.h:348
virtual bool cacheResolvedChildren() const
Definition Directory.h:293
uintptr_t m_ParentInode
Definition Directory.h:370
Mutex & namespaceMutationLock()
Definition Directory.h:410
bool isDetached() const
Definition Directory.h:179
ReadStatus isEmpty(bool &empty)
Definition Directory.cc:308
bool reserveRenameEntry(const String &name, NameReservation &reservation)
Definition Directory.cc:680
MUST_USE_RESULT LookupStatus lookupChild(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:345
Mutex m_CacheLock
Definition Directory.h:373
Directory * getReparsePoint() const
Get the reparse point attached to this directory. Reparse points allow locations on the filesystem to...
Definition Directory.cc:898
Definition File.h:74
virtual void getFullPath(String &result, bool bWithMount=true)
Definition File.cc:1428
virtual bool retainVfsReference()
Definition File.cc:827
String getName() const
Definition File.cc:699
void getNamespace(ParentLease &parent, String &name) const
Definition File.cc:893
virtual bool isSymlink()
Definition File.cc:717
void retainDetachedParent()
Definition File.cc:926
virtual bool isDirectory()
Definition File.cc:721
void publishEvent(FileEventMask mask, const StringView &name=StringView(), bool targetIsDirectory=false)
Definition File.cc:778
virtual bool removeNode(File *parent, const String &filename, File *file)=0
File * findRetained(const StringView &path, Directory::ChildLease &result, File *pStartNode=nullptr)
Disk * m_pDisk
Definition Filesystem.h:180
virtual bool renameNode(Directory *oldParent, const String &oldName, File *source, Directory *newParent, const String &newName, File *replaced)
bool m_bReadOnly
Definition Filesystem.h:178
bool removeChild(File *parent, const String &filename, File *expected)
File * findParent(StringView path, File *pStartNode, String &filename, File **retainedParent=nullptr)
bool createSymlink(const StringView &path, const String &value, File *pStartNode=0)
bool createDirectory(const StringView &path, uint32_t mask, File *pStartNode=0)
bool rename(const StringView &oldPath, File *oldStart, const StringView &newPath, File *newStart, bool noReplace=false)
static Mutex m_StructureLock
Definition Filesystem.h:185
virtual ~Filesystem()
bool createLink(const StringView &path, File *target, File *pStartNode=0)
virtual SyncStatus shutdown()
Definition Filesystem.cc:48
virtual File * find(const StringView &path)
bool isReadOnly()
Definition Filesystem.h:142
File * findNode(File *pNode, StringView path)
virtual SyncStatus sync()
Definition Filesystem.cc:44
bool createFile(const StringView &path, uint32_t mask, File *pStartNode=0)
virtual FileHandleStatus encodeFileHandle(File &file, FileHandle &handle)
Definition Filesystem.cc:53
bool remove(const StringView &path, File *pStartNode=0)
LookupResult lookup(const K &k) const
Definition HashTable.h:168
Definition Mutex.h:56
StringView substring(size_t start, size_t end, bool hashed=HASH_STRINGVIEWS_BY_DEFAULT) const
StringView view() const
Definition String.cc:768
MUST_USE_RESULT bool retainTrackedFile(File *pFile)
Definition VFS.cc:1521
bool untrackFile(File *pFile, bool destroy=true)
Definition VFS.cc:1551
static bool checkAccess(File *pFile, bool bRead, bool bWrite, bool bExecute)
Definition VFS.cc:1392
static VFS & instance()
Definition VFS.cc:310
#define assert(x)
Definition assert.h:39