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