The Pedigree Project 0.1
Directory.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 "Directory.h"
21#include "pedigree/kernel/LockGuard.h"
22#include "pedigree/kernel/syscallError.h"
23#include "pedigree/kernel/utilities/Iterator.h"
24#include "pedigree/kernel/utilities/Pair.h"
25#include "pedigree/kernel/utilities/Result.h"
26#include "pedigree/kernel/utilities/StringView.h"
27#include "pedigree/kernel/utilities/Vector.h"
28
29#include "Filesystem.h"
30#include "VFS.h"
31
33
34#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
35Directory::RetainedLookupHook Directory::m_RetainedLookupHook = nullptr;
36#endif
37
38Directory::ChildLease::ChildLease()
39 : m_pFile(nullptr)
40#if THREADS && !defined(STANDALONE_MUTEXES)
41 ,
42 m_TerminationDeferral(true)
43#endif
44{
45}
46
47Directory::ChildLease::~ChildLease() {
48 reset();
49}
50
51void Directory::ChildLease::reset() {
52 File* file = m_pFile;
53 m_pFile = nullptr;
54 if (file) {
56 }
57}
58
59File* Directory::ChildLease::releaseOwnership() {
60 File* file = m_pFile;
61 m_pFile = nullptr;
62 return file;
63}
64
65void Directory::ChildLease::swap(ChildLease& other) {
66 File* file = m_pFile;
67 m_pFile = other.m_pFile;
68 other.m_pFile = file;
69}
70
71void Directory::ChildLease::adopt(File* file) {
72 assert(file != nullptr);
73 assert(m_pFile == nullptr);
74 m_pFile = file;
75}
76
77Directory::NameReservation::NameReservation()
78 : m_pDirectory(nullptr),
79 m_Name(),
80 m_pToken(nullptr),
81 m_OwnsNamespaceLock(false)
82#if THREADS && !defined(STANDALONE_MUTEXES)
83 ,
84 m_TerminationDeferral(true)
85#endif
86{
87}
88
89Directory::NameReservation::~NameReservation() {
90 if (m_pDirectory) {
91 m_pDirectory->finishNameReservation(*this, LookupStatus::Retry);
92 }
93}
94
95void Directory::NameReservation::complete(LookupStatus result) {
96 if (m_pDirectory) {
97 m_pDirectory->finishNameReservation(*this, result);
98 }
99}
100
102 : File(),
103 m_Cache(nullptr),
104 m_ResidentEntries(nullptr),
106 m_EphemeralEntries(false),
108 m_NextCacheGeneration(0),
109 m_InFlightLookups(nullptr),
110 m_bCachePopulated(false),
111 m_Detached(false),
112 m_ParentInode(0),
113 m_CacheLock(),
115
116Directory::Directory(const String& name, Time::Timestamp accessedTime, Time::Timestamp modifiedTime,
117 Time::Timestamp creationTime, uintptr_t inode, Filesystem* pFs, size_t size,
118 File* pParent)
119 : File(name, accessedTime, modifiedTime, creationTime, inode, pFs, size, pParent),
120 m_Cache(nullptr),
121 m_ResidentEntries(nullptr),
122 m_ResidentOrder(),
123 m_EphemeralEntries(false),
124 m_CacheGenerations(0),
125 m_NextCacheGeneration(0),
126 m_InFlightLookups(nullptr),
127 m_bCachePopulated(false),
128 m_Detached(false),
129 m_ParentInode(pParent ? pParent->getInode() : inode),
130 m_CacheLock(),
131 m_NamespaceMutationLock() {}
132
133Directory::InFlightLookup::InFlightLookup()
134 : changed(), users(1), result(LookupStatus::Retry), complete(false), invalidated(false) {}
135
139
141 if (n == 0) {
142 return this;
143 }
144 if (n == 1) {
145 return getParent() ? getParent() : this;
146 }
147 if (!cacheResolvedChildren()) {
148 SYSCALL_ERROR(OperationNotSupported);
149 return nullptr;
150 }
151
152 struct Context {
153 size_t wanted;
154 size_t seen;
155 String name;
156 uint64_t cookie;
157 } context = {n - 2, 0, String(), 0};
158
159 auto emitter = [](void* opaque, const DirectoryEntryView& entry) -> bool {
160 Context* context = reinterpret_cast<Context*>(opaque);
161 if (context->seen++ == context->wanted) {
162 context->name = entry.name.toString();
163 context->cookie = entry.currentCookie;
164 return false;
165 }
166 return true;
167 };
168
169 uint64_t cookie = 2;
170 ReadStatus status = enumerate(cookie, emitter, &context);
171 if (context.name.length() == 0 ||
172 (status != ReadStatus::Stopped && status != ReadStatus::Complete)) {
173 return nullptr;
174 }
175
176 ChildLease child;
177 if (lookupChildAt(context.cookie, HashedStringView(context.name), child) != LookupStatus::Found) {
178 return nullptr;
179 }
180 return child.get();
181}
182
184 size_t count = 0;
185 auto emitter = [](void* opaque, const DirectoryEntryView&) -> bool {
186 ++*reinterpret_cast<size_t*>(opaque);
187 return true;
188 };
189 uint64_t cookie = 0;
190 enumerate(cookie, emitter, &count);
191 return count;
192}
193
195 Directory* directory;
196 DirectoryEntryEmitter emitter;
197 void* context;
198 bool invalidCookie;
199};
200
201bool Directory::emitBackingEntry(void* opaque, const DirectoryEntryView& entry) {
202 EnumerationContext* context = reinterpret_cast<EnumerationContext*>(opaque);
203 if (entry.name.compare(".", 1) || entry.name.compare("..", 2)) {
204 return true;
205 }
206
207 {
208 LockGuard<Mutex> guard(context->directory->m_CacheLock);
209 if (context->directory->m_ResidentEntries.lookup(HashedStringView(entry.name)).hasValue()) {
210 return true;
211 }
212 }
213
214 if (entry.currentCookie >= (ResidentCookie - 2) || entry.nextCookie >= (ResidentCookie - 2)) {
215 context->invalidCookie = true;
216 return false;
217 }
218
219 DirectoryEntryView translated = {entry.name, entry.inode, entry.type, entry.currentCookie + 2,
220 entry.nextCookie + 2};
221 return context->emitter(context->context, translated);
222}
223
224Directory::ReadStatus Directory::enumerate(uint64_t& cookie, DirectoryEntryEmitter emitter,
225 void* context) {
226 OperationBarrier::Lease operation;
227 if (!tryAcquireFilesystemOperation(operation)) {
228 return ReadStatus::IoError;
229 }
230 if (!emitter) {
231 return ReadStatus::IoError;
232 }
233 if (cookie >= (ResidentCookie << 1)) {
234 return ReadStatus::IoError;
235 }
236
237 if (cookie == 0) {
238 DirectoryEntryView dot = {StringView("."), getInode(), EntryType::Directory, 0, 1};
239 if (!emitter(context, dot)) {
240 return ReadStatus::Stopped;
241 }
242 cookie = 1;
243 }
244
245 if (cookie == 1) {
246 DirectoryEntryView dotdot = {StringView(".."),
247 __atomic_load_n(&m_ParentInode, __ATOMIC_ACQUIRE),
248 EntryType::Directory, 1, 2};
249 if (!emitter(context, dotdot)) {
250 return ReadStatus::Stopped;
251 }
252 cookie = 2;
253 }
254
255 if (isDetached()) {
256 cookie = ResidentCookie;
257 return ReadStatus::Complete;
258 }
259
260 if (!(cookie & ResidentCookie)) {
261 uint64_t backingCookie = cookie - 2;
262 EnumerationContext adapter = {this, emitter, context, false};
263 ReadStatus status = readDirectory(backingCookie, emitBackingEntry, &adapter);
264 cookie = backingCookie + 2;
265 if (adapter.invalidCookie) {
266 return ReadStatus::IoError;
267 }
268 if (status != ReadStatus::Complete) {
269 return status;
270 }
271 cookie = ResidentCookie;
272 }
273
274 while (true) {
275 String name;
276 uint64_t generation = 0;
277 ChildLease child;
278 {
280 size_t slot = static_cast<size_t>(cookie & ~ResidentCookie);
281 while (slot < m_ResidentOrder.count()) {
282 const String& candidateName = m_ResidentOrder[slot++];
283 if (!candidateName.length()) {
284 continue;
285 }
286 if (candidateName.compare(".", 1) || candidateName.compare("..", 2)) {
287 continue;
288 }
289 name = candidateName;
290 generation = slot;
291 break;
292 }
293
294 if (!generation) {
295 cookie = ResidentCookie | m_ResidentOrder.count();
296 return ReadStatus::Complete;
297 }
298
299 File* file = cachedFileLocked(HashedStringView(name));
300 if (!file || !VFS::instance().retainTrackedFile(file)) {
301 return ReadStatus::IoError;
302 }
303 child.adopt(file);
304 }
305
306 const uint64_t currentCookie = ResidentCookie | (generation - 1);
307 const uint64_t nextCookie = ResidentCookie | generation;
308 cookie = currentCookie;
309 DirectoryEntryView entry = {name.view(), child.get()->getInode(), entryType(child.get()),
310 currentCookie, nextCookie};
311 if (!emitter(context, entry)) {
312 return ReadStatus::Stopped;
313 }
314 cookie = nextCookie;
315 }
316}
317
318Directory::ReadStatus Directory::isEmpty(bool& empty) {
319 empty = true;
320 auto emitter = [](void* opaque, const DirectoryEntryView&) -> bool {
321 *reinterpret_cast<bool*>(opaque) = false;
322 return false;
323 };
324 uint64_t cookie = 2;
325 ReadStatus status = enumerate(cookie, emitter, &empty);
326 if (status == ReadStatus::Stopped) {
327 return ReadStatus::Complete;
328 }
329 return status;
330}
331
333
338
340 if (!cacheResolvedChildren()) {
341 SYSCALL_ERROR(OperationNotSupported);
342 return nullptr;
343 }
344 ChildLease child;
345 if (lookupChild(s, child) != LookupStatus::Found) {
346 return nullptr;
347 }
348 return child.get();
349}
350
352 return lookupChild(s, child) == LookupStatus::Found;
353}
354
355Directory::LookupStatus Directory::lookupChild(const HashedStringView& s, ChildLease& child) const {
356 return lookupChildInternal(s, 0, false, child);
357}
358
359Directory::LookupStatus Directory::lookupChildAt(uint64_t cookie, const HashedStringView& name,
360 ChildLease& child) const {
361 OperationBarrier::Lease operation;
362 if (!tryAcquireFilesystemOperation(operation)) {
363 return LookupStatus::IoError;
364 }
365 if (cookie >= (ResidentCookie << 1)) {
366 return LookupStatus::IoError;
367 }
368 if (cookie < 2) {
369 const bool dot = cookie == 0 && name == ".";
370 const bool dotdot = cookie == 1 && name == "..";
371 if (!dot && !dotdot) {
372 return LookupStatus::NotFound;
373 }
374
375 ParentLease parent;
376 String unused;
377 if (dotdot) {
378 getNamespace(parent, unused);
379 }
380 File* file = dot ? const_cast<Directory*>(this) : parent.get();
381 if (!file) {
382 file = const_cast<Directory*>(this);
383 }
384 if (!VFS::instance().retainTrackedFile(file)) {
385 return LookupStatus::IoError;
386 }
387 ChildLease replacement;
388 replacement.adopt(file);
389 child.swap(replacement);
390 return LookupStatus::Found;
391 }
392 if (cookie & ResidentCookie) {
393 File* file = nullptr;
394 bool stale = false;
395 {
397 const size_t slot = static_cast<size_t>(cookie & ~ResidentCookie);
398 if (m_InFlightLookups.lookup(name).hasValue() || slot >= m_ResidentOrder.count() ||
399 m_ResidentOrder[slot] != name) {
400 stale = true;
401 } else {
403 if (!resident.hasValue()) {
404 stale = true;
405 } else {
406 file = resident.value()->get();
407 if (!file || !VFS::instance().retainTrackedFile(file)) {
408 return LookupStatus::IoError;
409 }
410 }
411 }
412 }
413 if (stale) {
414 return lookupChild(name, child);
415 }
416 ChildLease replacement;
417 replacement.adopt(file);
418 child.swap(replacement);
419 return LookupStatus::Found;
420 }
421 return lookupChildInternal(name, cookie - 2, true, child);
422}
423
424Directory::LookupStatus Directory::lookupChildInternal(const HashedStringView& s, uint64_t cookie,
425 bool hasCookie, ChildLease& child) const {
426 OperationBarrier::Lease operation;
427 if (!tryAcquireFilesystemOperation(operation)) {
428 return LookupStatus::IoError;
429 }
430 Directory* self = const_cast<Directory*>(this);
431 const String name = s.toString();
432
433 if (!cacheResolvedChildren()) {
434 if (isDetached())
435 return LookupStatus::NotFound;
436 File* candidate = nullptr;
437 LookupStatus status = hasCookie ? self->resolveChildAt(cookie, name.view(), candidate)
438 : self->resolveChild(name.view(), candidate);
439 if (status != LookupStatus::Found || !candidate) {
440 const bool invalid = candidate || status == LookupStatus::Found;
441 delete candidate;
442 return invalid ? LookupStatus::IoError : status;
443 }
444 if (isDetached()) {
445 delete candidate;
446 return LookupStatus::NotFound;
447 }
448 // No directory cache owns this reference. Keep the parent until the
449 // last lookup/OFD reference retires the generated child.
450 candidate->retainDetachedParent();
451 VFS::instance().trackFile(candidate);
452 ChildLease replacement;
453 replacement.adopt(candidate);
454 child.swap(replacement);
455 return LookupStatus::Found;
456 }
457
458 while (true) {
459 File* replacement = nullptr;
460 InFlightLookup* lookup = nullptr;
461 InFlightLookup* retiredLookup = nullptr;
462 bool owner = false;
463 LookupStatus completedResult = LookupStatus::Retry;
464
465 {
467#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
468 RetainedLookupHook hook = __atomic_load_n(&m_RetainedLookupHook, __ATOMIC_ACQUIRE);
469 if (hook) {
470 hook(self, nullptr, RetainedLookupPhase::BeforeLookup);
471 }
472#endif
473
474 if (isDetached()) {
475 return LookupStatus::NotFound;
476 }
477
478 InFlightLookupCache::LookupResult pending = m_InFlightLookups.lookup(s);
479 if (pending.hasValue()) {
480 lookup = pending.value();
481 ++lookup->users;
482 while (!lookup->complete) {
483 lookup->changed.waitForCompletion(m_CacheLock);
484 }
485 completedResult = lookup->result;
486 if (!--lookup->users) {
487 retiredLookup = lookup;
488 }
489 } else {
490 File* file = cachedFileLocked(s);
491 if (file && VFS::instance().retainTrackedFile(file)) {
492 replacement = file;
493
494#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
495 if (hook) {
496 hook(self, file, RetainedLookupPhase::AfterRetain);
497 }
498#endif
499 } else {
500 lookup = new InFlightLookup;
501 const bool inserted = m_InFlightLookups.insert(name, lookup);
502 assert(inserted);
503 owner = true;
504 }
505 }
506 }
507
508 delete retiredLookup;
509
510 if (replacement) {
511 ChildLease replacementLease;
512 replacementLease.adopt(replacement);
513 child.swap(replacementLease);
514 return LookupStatus::Found;
515 }
516
517 if (!owner) {
518 if (completedResult == LookupStatus::Found || completedResult == LookupStatus::Retry) {
519 continue;
520 }
521 return completedResult;
522 }
523
524 File* candidate = nullptr;
525 LookupStatus result = hasCookie ? self->resolveChildAt(cookie, name.view(), candidate)
526 : self->resolveChild(name.view(), candidate);
527 if (result == LookupStatus::Found && !candidate) {
528 result = LookupStatus::IoError;
529 } else if (result != LookupStatus::Found && candidate) {
530 delete candidate;
531 candidate = nullptr;
532 result = LookupStatus::IoError;
533 }
534
535 {
537 if (lookup->invalidated) {
538 result = LookupStatus::Retry;
539 } else if (result == LookupStatus::Found) {
540 File* existing = cachedFileLocked(s);
541 if (!existing) {
542 VFS::instance().trackFile(candidate);
543 DirectoryEntry* entry = new DirectoryEntry(candidate);
544 const bool inserted = self->m_Cache.insert(name, entry);
545 assert(inserted);
546 const bool generationInserted =
547 self->m_CacheGenerations.insert(name, self->nextCacheGeneration());
548 assert(generationInserted);
549 candidate = nullptr;
550 }
551 }
552
553 lookup->result = result;
554 lookup->complete = true;
555 lookup->changed.broadcast();
556 InFlightLookupCache::LookupResult published = m_InFlightLookups.lookup(s);
557 if (published.hasValue() && published.value() == lookup) {
559 }
560 if (!--lookup->users) {
561 retiredLookup = lookup;
562 }
563 }
564
565 delete candidate;
566 delete retiredLookup;
567
568 if (result == LookupStatus::Found || result == LookupStatus::Retry) {
569 continue;
570 }
571 return result;
572 }
573}
574
575#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
576void Directory::setRetainedLookupHookForHostedTest(RetainedLookupHook hook) {
577 __atomic_store_n(&m_RetainedLookupHook, hook, __ATOMIC_RELEASE);
578}
579
580bool Directory::tryCacheLockForHostedTest() const {
581 if (!m_CacheLock.tryAcquire()) {
582 return false;
583 }
585 return true;
586}
587#endif
588
589#if HOSTED
590size_t Directory::inFlightUsersForHostedTest(const HashedStringView& name) const {
592 InFlightLookupCache::LookupResult pending = m_InFlightLookups.lookup(name);
593 return pending.hasValue() ? pending.value()->users : 0;
594}
595#endif
596
599 DirectoryEntry* residentEntry = nullptr;
600 DirectoryEntry* cachedEntry = nullptr;
601 {
604 if (resident.hasValue()) {
605 residentEntry = resident.value();
606 tombstoneResidentLocked(s);
607 m_ResidentEntries.remove(s.toString());
608 m_EphemeralEntries.remove(s.toString());
609 }
611 if (cached.hasValue()) {
612 cachedEntry = cached.value();
613 m_Cache.remove(s.toString());
614 }
615 if (residentEntry || cachedEntry) {
616 m_CacheGenerations.remove(s.toString());
617 }
619 if (pending.hasValue()) {
620 pending.value()->invalidated = true;
621 }
622 }
623 delete residentEntry;
624 delete cachedEntry;
625}
626
628 assert(!reservation);
629 const String ownedName = name.toString();
631 if (isDetached()) {
633 return false;
634 }
635
636 while (true) {
637 InFlightLookup* retiredLookup = nullptr;
638 {
640 if (cachedFileLocked(name)) {
642 return false;
643 }
644
646 if (!pending.hasValue()) {
648 const bool inserted = m_InFlightLookups.insert(ownedName, lookup);
649 assert(inserted);
650 reservation.m_pDirectory = this;
651 reservation.m_Name = ownedName;
652 reservation.m_pToken = lookup;
653 reservation.m_OwnsNamespaceLock = true;
654 return true;
655 }
656
657 InFlightLookup* lookup = pending.value();
658 ++lookup->users;
659 while (!lookup->complete) {
660 lookup->changed.waitForCompletion(m_CacheLock);
661 }
662 if (!--lookup->users) {
663 retiredLookup = lookup;
664 }
665 }
666 delete retiredLookup;
667 }
668}
669
670void Directory::finishNameReservation(NameReservation& reservation, LookupStatus result) {
671 const bool ownsNamespaceLock = reservation.m_OwnsNamespaceLock;
672 InFlightLookup* retiredLookup = nullptr;
673 {
675 InFlightLookupCache::LookupResult pending =
676 m_InFlightLookups.lookup(HashedStringView(reservation.m_Name));
677 InFlightLookup* lookup = reinterpret_cast<InFlightLookup*>(reservation.m_pToken);
678 assert(pending.hasValue() && pending.value() == lookup && !lookup->complete);
679
680 lookup->result = result;
681 lookup->complete = true;
682 lookup->changed.broadcast();
683 m_InFlightLookups.remove(reservation.m_Name);
684 if (!--lookup->users) {
685 retiredLookup = lookup;
686 }
687 reservation.m_pDirectory = nullptr;
688 reservation.m_Name.clear();
689 reservation.m_pToken = nullptr;
690 reservation.m_OwnsNamespaceLock = false;
691 }
692 delete retiredLookup;
693 if (ownsNamespaceLock) {
695 }
696}
697
698bool Directory::reserveRenameEntry(const String& name, NameReservation& reservation) {
699 assert(!reservation);
700 while (true) {
701 InFlightLookup* retired = nullptr;
702 {
704 if (isDetached()) {
705 return false;
706 }
707 auto pending = m_InFlightLookups.lookup(name);
708 if (!pending.hasValue()) {
710 const bool inserted = m_InFlightLookups.insert(name, lookup);
711 assert(inserted);
712 reservation.m_pDirectory = this;
713 reservation.m_Name = name;
714 reservation.m_pToken = lookup;
715 return true;
716 }
717 InFlightLookup* lookup = pending.value();
718 ++lookup->users;
719 while (!lookup->complete) {
720 lookup->changed.waitForCompletion(m_CacheLock);
721 }
722 if (!--lookup->users) {
723 retired = lookup;
724 }
725 }
726 delete retired;
727 }
728}
729
730void Directory::moveReservedEntry(NameReservation& sourceReservation, Directory* destination,
731 NameReservation& destinationReservation, File* source) {
732 assert(sourceReservation.m_pDirectory == this);
733 assert(destinationReservation.m_pDirectory == destination);
734 const String& oldName = sourceReservation.m_Name;
735 const String& newName = destinationReservation.m_Name;
736 DirectoryEntry* moved = nullptr;
737 DirectoryEntry* retired = nullptr;
738 bool resident = false;
739 bool ephemeral = false;
740 {
742 auto residentEntry = m_ResidentEntries.lookup(oldName);
743 resident = residentEntry.hasValue();
744 if (resident) {
745 moved = residentEntry.value();
746 } else {
747 auto cachedEntry = m_Cache.lookup(oldName);
748 assert(cachedEntry.hasValue());
749 moved = cachedEntry.value();
750 }
751 assert(moved->get() == source);
752 ephemeral = m_EphemeralEntries.lookup(oldName).hasValue();
753 if (resident) {
754 tombstoneResidentLocked(HashedStringView(oldName));
755 m_ResidentEntries.remove(oldName);
756 m_EphemeralEntries.remove(oldName);
757 } else {
758 m_Cache.remove(oldName);
759 }
760 m_CacheGenerations.remove(oldName);
761 }
762 {
763 LockGuard<Mutex> guard(destination->m_CacheLock);
764 auto existing = destination->m_ResidentEntries.lookup(newName);
765 if (existing.hasValue()) {
766 retired = existing.value();
767 destination->tombstoneResidentLocked(HashedStringView(newName));
768 destination->m_ResidentEntries.remove(newName);
769 } else {
770 auto cachedEntry = destination->m_Cache.lookup(newName);
771 if (cachedEntry.hasValue()) {
772 retired = cachedEntry.value();
773 destination->m_Cache.remove(newName);
774 }
775 }
776 destination->m_EphemeralEntries.remove(newName);
777 destination->m_CacheGenerations.remove(newName);
778 if (resident) {
779 const bool inserted = destination->m_ResidentEntries.insert(newName, moved);
780 assert(inserted);
781 destination->m_CacheGenerations.insert(newName, destination->m_ResidentOrder.count() + 1);
782 destination->m_ResidentOrder.pushBack(newName);
783 if (ephemeral) {
784 destination->m_EphemeralEntries.insert(newName, true);
785 }
786 } else {
787 const bool inserted = destination->m_Cache.insert(newName, moved);
788 assert(inserted);
789 destination->m_CacheGenerations.insert(newName, destination->nextCacheGeneration());
790 }
791 }
792 delete retired;
793}
794
795bool Directory::addResidentDirectoryEntry(NameReservation& reservation, File* pTarget,
796 bool ephemeral) {
797 assert(pTarget != nullptr);
798 return addResidentDirectoryEntry(reservation, new DirectoryEntry(pTarget), ephemeral, pTarget);
799}
800
801bool Directory::addResidentDirectoryEntry(NameReservation& reservation, DirectoryEntry* entry,
802 bool ephemeral, File* createdTarget) {
803 assert(reservation.m_pDirectory == this);
804 assert(entry != nullptr);
805
806 bool inserted = false;
807 {
809 const String& name = reservation.m_Name;
810 if (!m_ResidentEntries.lookup(name).hasValue() && !m_Cache.lookup(name).hasValue()) {
811 inserted = m_ResidentEntries.insert(name, entry);
812 assert(inserted);
813 const bool generationInserted = m_CacheGenerations.insert(name, m_ResidentOrder.count() + 1);
815 assert(generationInserted);
816 if (ephemeral) {
817 const bool kindInserted = m_EphemeralEntries.insert(name, true);
818 assert(kindInserted);
819 }
820 m_bCachePopulated = true;
821 }
822 }
823
824 if (inserted) {
825 if (createdTarget) {
826 publishEvent(FileEvents::Created, reservation.m_Name.view(), createdTarget->isDirectory());
827 }
828 reservation.complete(LookupStatus::Found);
829 } else {
830 delete entry;
831 }
832 return inserted;
833}
834
835bool Directory::addDirectoryEntry(const String& name, File* pTarget) {
836 assert(pTarget != nullptr);
837 NameReservation reservation;
838 if (!reserveDirectoryEntry(HashedStringView(name), reservation)) {
839 ERROR("can't add directory entry for '" << name << "' as it already exists.");
840 return false;
841 }
842 VFS::instance().trackFile(pTarget);
843 return addResidentDirectoryEntry(reservation, pTarget, false);
844}
845
847 NameReservation reservation;
848 if (!reserveDirectoryEntry(HashedStringView(name), reservation)) {
849 ERROR("can't add directory entry for '" << name << "' as it already exists.");
850 return false;
851 }
852 return addResidentDirectoryEntry(reservation, new DirectoryEntry(pedigree_std::move(meta)),
853 false);
854}
855
857 assert(pTarget != nullptr);
858 assert(reservation.m_pDirectory == this);
859
860 bool inserted = false;
861 {
863 const String& name = reservation.m_Name;
864 if (!m_ResidentEntries.lookup(name).hasValue() && !m_Cache.lookup(name).hasValue()) {
865 VFS::instance().trackFile(pTarget);
866 DirectoryEntry* entry = new DirectoryEntry(pTarget);
867 inserted = m_Cache.insert(name, entry);
868 assert(inserted);
869 const bool generationInserted = m_CacheGenerations.insert(name, nextCacheGeneration());
870 assert(generationInserted);
871 }
872 }
873 return inserted;
874}
875
877 DirectoryEntry* entry = nullptr;
878 {
881 if (result.hasValue()) {
882 entry = result.value();
883 m_Cache.remove(name.toString());
884 m_CacheGenerations.remove(name.toString());
885 }
887 if (pending.hasValue()) {
888 pending.value()->invalidated = true;
889 }
890 }
891 delete entry;
892}
893
895 DirectoryEntry* entry = nullptr;
896 {
899 if (!resident.hasValue() || resident.value()->get() != expected) {
900 return false;
901 }
902 entry = resident.value();
903 tombstoneResidentLocked(name);
904 m_ResidentEntries.remove(name.toString());
905 m_EphemeralEntries.remove(name.toString());
906 m_CacheGenerations.remove(name.toString());
908 if (pending.hasValue()) {
909 pending.value()->invalidated = true;
910 }
911 }
912 delete entry;
913 return true;
914}
915
917 return __atomic_load_n(&m_ReparseTarget, __ATOMIC_ACQUIRE);
918}
919
921 __atomic_store_n(&m_ReparseTarget, pTarget, __ATOMIC_RELEASE);
922}
923
924Directory::AddStatus Directory::addEphemeralFile(File* pFile) {
925 OperationBarrier::Lease operation;
926 if (!tryAcquireFilesystemOperation(operation)) {
927 return AddStatus::IoError;
928 }
929 assert(pFile != nullptr);
930 NameReservation reservation;
931 if (!reserveDirectoryEntry(HashedStringView(pFile->getName()), reservation)) {
932 return isDetached() ? AddStatus::Detached : AddStatus::Exists;
933 }
934
935 File* backing = nullptr;
936 LookupStatus status = resolveChild(pFile->getName().view(), backing);
937 if (status == LookupStatus::Found && !backing) {
938 status = LookupStatus::IoError;
939 } else if (status != LookupStatus::Found && backing) {
940 delete backing;
941 backing = nullptr;
942 status = LookupStatus::IoError;
943 }
944
945 if (status == LookupStatus::Found) {
946 if (!addCachedDirectoryEntry(reservation, backing)) {
947 delete backing;
948 return AddStatus::Exists;
949 }
950 reservation.complete(LookupStatus::Found);
951 return AddStatus::Exists;
952 }
953 if (status != LookupStatus::NotFound) {
954 return AddStatus::IoError;
955 }
956
957 VFS::instance().trackFile(pFile);
958 return addResidentDirectoryEntry(reservation, pFile, true) ? AddStatus::Added : AddStatus::Exists;
959}
960
962 Vector<File*> children;
963 {
965 Vector<String> names;
966 {
968 // Only materialised directories can own ephemeral entries. Avoid disk
969 // reads or following symlinks while retiring the transient namespace.
970 auto retainDirectory = [&](DirectoryEntry* entry) {
971 if (entry->active()) {
972 File* file = entry->get();
973 if (file->isDirectory() && VFS::instance().retainTrackedFile(file)) {
974 children.pushBack(file);
975 }
976 }
977 };
978 for (auto entry : m_Cache) {
979 retainDirectory(entry);
980 }
981 for (auto entry : m_ResidentEntries) {
982 retainDirectory(entry);
983 }
984 for (const auto& name : m_ResidentOrder) {
985 if (m_EphemeralEntries.contains(name)) {
986 names.pushBack(name);
987 }
988 }
989 }
990 for (const auto& name : names) {
991 File* file = nullptr;
992 {
994 auto entry = m_ResidentEntries.lookup(name);
995 if (entry.hasValue()) {
996 file = entry.value()->get();
997 }
998 }
999 removeEphemeralFileLocked(HashedStringView(name), file);
1000 }
1001 }
1002 for (File* child : children) {
1004 VFS::instance().untrackFile(child);
1005 }
1006}
1007
1008bool Directory::removeEphemeralFileLocked(const HashedStringView& name, File* expected) {
1009 DirectoryEntry* entry = nullptr;
1010 {
1012 if (!m_EphemeralEntries.lookup(name).hasValue()) {
1013 return false;
1014 }
1015 DirectoryEntryCache::LookupResult resident = m_ResidentEntries.lookup(name);
1016 if (!resident.hasValue() || resident.value()->get() != expected) {
1017 return false;
1018 }
1019 entry = resident.value();
1020 tombstoneResidentLocked(name);
1021 m_ResidentEntries.remove(name.toString());
1022 m_EphemeralEntries.remove(name.toString());
1023 m_CacheGenerations.remove(name.toString());
1024 InFlightLookupCache::LookupResult pending = m_InFlightLookups.lookup(name);
1025 if (pending.hasValue()) {
1026 pending.value()->invalidated = true;
1027 }
1028 }
1029 delete entry;
1030 return true;
1031}
1032
1034 OperationBarrier::Lease operation;
1035 if (!tryAcquireFilesystemOperation(operation)) {
1036 return false;
1037 }
1038 while (true) {
1039 struct FirstEntry {
1040 String name;
1041 uint64_t cookie;
1042 bool found;
1043 } first = {String(), 0, false};
1044 auto captureFirst = [](void* opaque, const DirectoryEntryView& entry) -> bool {
1045 FirstEntry* first = reinterpret_cast<FirstEntry*>(opaque);
1046 first->name = entry.name.toString();
1047 first->cookie = entry.currentCookie;
1048 first->found = true;
1049 return false;
1050 };
1051
1052 uint64_t cookie = 2;
1053 const ReadStatus readStatus = enumerate(cookie, captureFirst, &first);
1054 if (readStatus == ReadStatus::IoError) {
1055 return false;
1056 }
1057 if (!first.found) {
1058 return readStatus == ReadStatus::Complete;
1059 }
1060
1061 ChildLease child;
1062 const LookupStatus lookup = lookupChildAt(first.cookie, HashedStringView(first.name), child);
1063 if (lookup == LookupStatus::Retry) {
1064 continue;
1065 }
1066 // A driver may deliberately enumerate an entry type it cannot
1067 // materialise (for example, an unsupported device inode). Treat that as
1068 // a failed empty operation instead of selecting the same stable record
1069 // forever.
1070 if (lookup == LookupStatus::NotFound) {
1071 return false;
1072 }
1073 if (lookup != LookupStatus::Found) {
1074 return false;
1075 }
1076 if (!getFilesystem()->removeChild(this, first.name, child.get())) {
1077 return false;
1078 }
1079 }
1080}
1081
1082void Directory::markDetached() {
1083 if (isDetached()) {
1084 return;
1085 }
1086
1088 __atomic_store_n(&m_Detached, true, __ATOMIC_RELEASE);
1089}
1090
1094 {
1096 for (auto it : m_Cache) {
1097 entries.pushBack(it);
1098 }
1099 for (auto it : m_ResidentEntries) {
1100 entries.pushBack(it);
1101 }
1102 for (DirectoryEntry* entry : entries) {
1103 if (entry->active()) {
1104 entry->get()->retainDetachedParent();
1105 }
1106 }
1107
1108 m_Cache.clear();
1113 m_bCachePopulated = false;
1114 for (auto it : m_InFlightLookups) {
1115 it->invalidated = true;
1116 }
1117 }
1118
1119 // Now that the hashtable is flattened into this vector, it's safe to
1120 // delete without worrying about our deletion modifying the table.
1121 for (auto it : entries) {
1122 delete it;
1123 }
1124}
1125
1126File* Directory::evaluateEntry(const DirectoryEntryMetadata& meta) {
1127 if (!meta.pDirectory) {
1128 return nullptr;
1129 }
1130 OperationBarrier::Lease operation;
1131 if (!meta.pDirectory->tryAcquireFilesystemOperation(operation)) {
1132 return nullptr;
1133 }
1134 File* newFile = meta.pDirectory->convertToFile(meta);
1135
1136 if (newFile) {
1137 // Track this lazy-loaded directory entry.
1138 VFS::instance().trackFile(newFile);
1139 }
1140
1141 return newFile;
1142}
1143
1144void Directory::destroyEntry(File* file) {
1145 VFS::instance().untrackFile(file);
1146}
1147
1149 return nullptr;
1150}
1151
1152Directory::LookupStatus Directory::resolveChild(const StringView&, File*& child) {
1153 child = nullptr;
1154 return LookupStatus::NotFound;
1155}
1156
1157Directory::LookupStatus Directory::resolveChildAt(uint64_t, const StringView& name, File*& child) {
1158 return resolveChild(name, child);
1159}
1160
1161Directory::ReadStatus Directory::readDirectory(uint64_t&, DirectoryEntryEmitter, void*) {
1162 return ReadStatus::Complete;
1163}
1164
1169
1174
1175uint64_t Directory::nextCacheGeneration() {
1176 ++m_NextCacheGeneration;
1177 if (!m_NextCacheGeneration) {
1178 ++m_NextCacheGeneration;
1179 }
1180 return m_NextCacheGeneration;
1181}
1182
1183void Directory::tombstoneResidentLocked(const HashedStringView& name) {
1184 DirectoryEntryGenerationCache::LookupResult generation = m_CacheGenerations.lookup(name);
1185 if (!generation.hasValue() || !generation.value() ||
1186 generation.value() > m_ResidentOrder.count()) {
1187 return;
1188 }
1189
1190 String& slot = m_ResidentOrder[generation.value() - 1];
1191 if (slot == name) {
1192 slot.clear();
1193 }
1194}
1195
1196File* Directory::cachedFileLocked(const HashedStringView& name) const {
1197 DirectoryEntryCache::LookupResult resident = m_ResidentEntries.lookup(name);
1198 if (resident.hasValue()) {
1199 return resident.value()->get();
1200 }
1201 DirectoryEntryCache::LookupResult cached = m_Cache.lookup(name);
1202 if (cached.hasValue()) {
1203 return cached.value()->get();
1204 }
1205 return nullptr;
1206}
1207
1208Directory::EntryType Directory::entryType(File* file) {
1209 if (!file) {
1210 return EntryType::Unknown;
1211 }
1212 if (file->isDirectory()) {
1213 return EntryType::Directory;
1214 }
1215 if (file->isSymlink()) {
1216 return EntryType::Symlink;
1217 }
1218 if (file->isFifo()) {
1219 return EntryType::Fifo;
1220 }
1221 if (file->isSocket()) {
1222 return EntryType::Socket;
1223 }
1224 return EntryType::Regular;
1225}
1226
1227Directory::DirectoryEntryMetadata::DirectoryEntryMetadata()
1228 : pDirectory(nullptr), filename(), opaque() {}
1229Directory::DirectoryEntryMetadata::DirectoryEntryMetadata(
1230 Directory::DirectoryEntryMetadata&& other) noexcept
1231 : pDirectory(pedigree_std::move(other.pDirectory)),
1232 filename(pedigree_std::move(other.filename)),
1233 opaque(pedigree_std::move(other.opaque)) {
1234 other.pDirectory = nullptr;
1235}
1236
1237Directory::DirectoryEntryMetadata::~DirectoryEntryMetadata() {
1238 opaque.reset();
1239}
ReadStatus enumerate(uint64_t &cookie, DirectoryEntryEmitter emitter, void *context)
Definition Directory.cc:224
virtual LookupStatus resolveChildAt(uint64_t cookie, const StringView &name, File *&child)
static Directory * fromFile(File *pF)
Definition Directory.h:151
void removeEphemeralFiles()
Definition Directory.cc:961
EphemeralEntryCache m_EphemeralEntries
Definition Directory.h:354
InFlightLookupCache m_InFlightLookups
Definition Directory.h:361
bool addDirectoryEntry(const String &name, File *pTarget)
Definition Directory.cc:835
void markCachePopulated()
bool empty()
virtual bool cacheResolvedChildren() const
Definition Directory.h:299
Directory * m_ReparseTarget
Definition Directory.h:373
uintptr_t m_ParentInode
Definition Directory.h:376
void emptyCache()
void setReparsePoint(Directory *pTarget)
Definition Directory.cc:920
void invalidateDirectoryEntry(const HashedStringView &name)
Definition Directory.cc:876
AddStatus addEphemeralFile(File *pFile)
Add an ephemeral file to the directory.
Definition Directory.cc:924
bool m_bCachePopulated
Definition Directory.h:367
bool isDetached() const
Definition Directory.h:182
virtual void cacheDirectoryContents()
Definition Directory.cc:332
void remove(const HashedStringView &s)
Definition Directory.cc:597
bool m_Detached
Definition Directory.h:370
File * lookup(const HashedStringView &s) const
Definition Directory.cc:339
Mutex m_NamespaceMutationLock
Definition Directory.h:382
bool removeDirectoryEntry(const HashedStringView &name, File *expected)
Definition Directory.cc:894
ReadStatus isEmpty(bool &empty)
Definition Directory.cc:318
size_t getNumChildren()
Definition Directory.cc:183
virtual File * convertToFile(const DirectoryEntryMetadata &meta)
virtual ReadStatus readDirectory(uint64_t &cookie, DirectoryEntryEmitter emitter, void *context)
Vector< String > m_ResidentOrder
Definition Directory.h:351
virtual LookupStatus resolveChild(const StringView &name, File *&child)
MUST_USE_RESULT LookupStatus lookupChildAt(uint64_t cookie, const HashedStringView &name, ChildLease &child) const
Definition Directory.cc:359
void preallocateDirectoryEntries(size_t count)
bool reserveRenameEntry(const String &name, NameReservation &reservation)
Definition Directory.cc:698
bool addCachedDirectoryEntry(NameReservation &reservation, File *pTarget)
Definition Directory.cc:856
MUST_USE_RESULT bool lookupRetained(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:351
DirectoryEntryGenerationCache m_CacheGenerations
Definition Directory.h:357
MUST_USE_RESULT LookupStatus lookupChild(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:355
File * getChild(size_t n)
Definition Directory.cc:140
Mutex m_CacheLock
Definition Directory.h:379
DirectoryEntryCache m_Cache
Definition Directory.h:345
DirectoryEntryCache m_ResidentEntries
Definition Directory.h:348
bool reserveDirectoryEntry(const HashedStringView &name, NameReservation &reservation)
Definition Directory.cc:627
Directory * getReparsePoint() const
Get the reparse point attached to this directory. Reparse points allow locations on the filesystem to...
Definition Directory.cc:916
virtual ~Directory()
Definition Directory.cc:136
virtual bool isCachePopulated() const
Definition Directory.cc:334
Definition File.h:75
String getName() const
Definition File.cc:782
void getNamespace(ParentLease &parent, String &name) const
Definition File.cc:992
virtual bool isSocket() const
Definition File.cc:816
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 isFifo() const
Definition File.cc:812
LookupResult lookup(const K &k) const
Definition HashTable.h:168
void remove(const K &k)
Definition HashTable.h:271
bool insert(const K &k, const V &v)
Definition HashTable.h:218
void reserve(size_t numItems)
Definition HashTable.h:312
void clear() noexcept
Definition HashTable.h:123
bool contains(const K &k) const
Definition HashTable.h:138
void release(size_t n=1)
Definition Semaphore.cc:549
bool tryAcquire(size_t n=1)
Definition Semaphore.cc:484
bool acquire(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:355
StringView view() const
Definition String.cc:776
bool compare(const char *s, size_t len) const
Definition String.cc:202
MUST_USE_RESULT bool retainTrackedFile(File *pFile)
Definition VFS.cc:1631
bool untrackFile(File *pFile, bool destroy=true)
Definition VFS.cc:1661
static VFS & instance()
Definition VFS.cc:311
void trackFile(File *pFile)
Track a File object that exists. It is necessary to keep track of File objects, or at least those tha...
Definition VFS.cc:1616
A vector / dynamic array.
Definition Vector.h:33
void pushBack(const T &value)
Definition Vector.h:275
void clear(bool freeMem=false)
Definition Vector.h:378
size_t count() const
Definition Vector.h:270