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"
29#include "Filesystem.h"
34#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
35Directory::RetainedLookupHook Directory::m_RetainedLookupHook =
nullptr;
38Directory::ChildLease::ChildLease()
40#if THREADS && !defined(STANDALONE_MUTEXES)
42 m_TerminationDeferral(true)
47Directory::ChildLease::~ChildLease() {
51void Directory::ChildLease::reset() {
59File* Directory::ChildLease::releaseOwnership() {
65void Directory::ChildLease::swap(ChildLease& other) {
67 m_pFile = other.m_pFile;
71void Directory::ChildLease::adopt(
File* file) {
72 assert(file !=
nullptr);
73 assert(m_pFile ==
nullptr);
77Directory::NameReservation::NameReservation()
78 : m_pDirectory(nullptr),
81 m_OwnsNamespaceLock(false)
82#if THREADS && !defined(STANDALONE_MUTEXES)
84 m_TerminationDeferral(true)
89Directory::NameReservation::~NameReservation() {
91 m_pDirectory->finishNameReservation(*
this, LookupStatus::Retry);
95void Directory::NameReservation::complete(LookupStatus result) {
97 m_pDirectory->finishNameReservation(*
this, result);
108 m_NextCacheGeneration(0),
117 Time::Timestamp creationTime, uintptr_t inode,
Filesystem* pFs,
size_t size,
119 :
File(name, accessedTime, modifiedTime, creationTime, inode, pFs, size, pParent),
121 m_ResidentEntries(nullptr),
123 m_EphemeralEntries(false),
124 m_CacheGenerations(0),
125 m_NextCacheGeneration(0),
126 m_InFlightLookups(nullptr),
127 m_bCachePopulated(false),
129 m_ParentInode(pParent ? pParent->getInode() : inode),
131 m_NamespaceMutationLock() {}
133Directory::InFlightLookup::InFlightLookup()
134 : changed(), users(1), result(LookupStatus::Retry), complete(false), invalidated(false) {}
145 return getParent() ? getParent() :
this;
148 SYSCALL_ERROR(OperationNotSupported);
157 } context = {n - 2, 0,
String(), 0};
160 Context* context =
reinterpret_cast<Context*
>(opaque);
161 if (context->seen++ == context->wanted) {
162 context->name = entry.name.toString();
163 context->cookie = entry.currentCookie;
170 ReadStatus status =
enumerate(cookie, emitter, &context);
171 if (context.name.length() == 0 ||
172 (status != ReadStatus::Stopped && status != ReadStatus::Complete)) {
186 ++*
reinterpret_cast<size_t*
>(opaque);
196 DirectoryEntryEmitter emitter;
203 if (entry.name.compare(
".", 1) || entry.name.compare(
"..", 2)) {
214 if (entry.currentCookie >= (ResidentCookie - 2) || entry.nextCookie >= (ResidentCookie - 2)) {
215 context->invalidCookie =
true;
219 DirectoryEntryView translated = {entry.name, entry.inode, entry.type, entry.currentCookie + 2,
220 entry.nextCookie + 2};
221 return context->emitter(context->context, translated);
227 if (!tryAcquireFilesystemOperation(operation)) {
228 return ReadStatus::IoError;
231 return ReadStatus::IoError;
233 if (cookie >= (ResidentCookie << 1)) {
234 return ReadStatus::IoError;
239 if (!emitter(context, dot)) {
240 return ReadStatus::Stopped;
248 EntryType::Directory, 1, 2};
249 if (!emitter(context, dotdot)) {
250 return ReadStatus::Stopped;
256 cookie = ResidentCookie;
257 return ReadStatus::Complete;
260 if (!(cookie & ResidentCookie)) {
261 uint64_t backingCookie = cookie - 2;
263 ReadStatus status =
readDirectory(backingCookie, emitBackingEntry, &adapter);
264 cookie = backingCookie + 2;
265 if (adapter.invalidCookie) {
266 return ReadStatus::IoError;
268 if (status != ReadStatus::Complete) {
271 cookie = ResidentCookie;
276 uint64_t generation = 0;
280 size_t slot =
static_cast<size_t>(cookie & ~ResidentCookie);
283 if (!candidateName.length()) {
286 if (candidateName.
compare(
".", 1) || candidateName.
compare(
"..", 2)) {
289 name = candidateName;
296 return ReadStatus::Complete;
301 return ReadStatus::IoError;
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;
321 *
reinterpret_cast<bool*
>(opaque) =
false;
326 if (status == ReadStatus::Stopped) {
327 return ReadStatus::Complete;
341 SYSCALL_ERROR(OperationNotSupported);
345 if (
lookupChild(s, child) != LookupStatus::Found) {
352 return lookupChild(s, child) == LookupStatus::Found;
356 return lookupChildInternal(s, 0,
false, child);
362 if (!tryAcquireFilesystemOperation(operation)) {
363 return LookupStatus::IoError;
365 if (cookie >= (ResidentCookie << 1)) {
366 return LookupStatus::IoError;
369 const bool dot = cookie == 0 && name ==
".";
370 const bool dotdot = cookie == 1 && name ==
"..";
371 if (!dot && !dotdot) {
372 return LookupStatus::NotFound;
380 File* file = dot ?
const_cast<Directory*
>(
this) : parent.get();
385 return LookupStatus::IoError;
388 replacement.adopt(file);
389 child.swap(replacement);
390 return LookupStatus::Found;
392 if (cookie & ResidentCookie) {
393 File* file =
nullptr;
397 const size_t slot =
static_cast<size_t>(cookie & ~ResidentCookie);
403 if (!resident.hasValue()) {
406 file = resident.value()->get();
408 return LookupStatus::IoError;
417 replacement.adopt(file);
418 child.swap(replacement);
419 return LookupStatus::Found;
421 return lookupChildInternal(name, cookie - 2,
true, child);
424Directory::LookupStatus Directory::lookupChildInternal(
const HashedStringView& s, uint64_t cookie,
425 bool hasCookie, ChildLease& child)
const {
427 if (!tryAcquireFilesystemOperation(operation)) {
428 return LookupStatus::IoError;
431 const String name = s.toString();
435 return LookupStatus::NotFound;
436 File* candidate =
nullptr;
437 LookupStatus status = hasCookie ? self->
resolveChildAt(cookie, name.view(), candidate)
439 if (status != LookupStatus::Found || !candidate) {
440 const bool invalid = candidate || status == LookupStatus::Found;
442 return invalid ? LookupStatus::IoError : status;
446 return LookupStatus::NotFound;
452 ChildLease replacement;
453 replacement.adopt(candidate);
454 child.swap(replacement);
455 return LookupStatus::Found;
459 File* replacement =
nullptr;
460 InFlightLookup*
lookup =
nullptr;
461 InFlightLookup* retiredLookup =
nullptr;
463 LookupStatus completedResult = LookupStatus::Retry;
467#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
468 RetainedLookupHook hook = __atomic_load_n(&m_RetainedLookupHook, __ATOMIC_ACQUIRE);
470 hook(self,
nullptr, RetainedLookupPhase::BeforeLookup);
475 return LookupStatus::NotFound;
479 if (pending.hasValue()) {
482 while (!
lookup->complete) {
485 completedResult =
lookup->result;
490 File* file = cachedFileLocked(s);
494#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
496 hook(self, file, RetainedLookupPhase::AfterRetain);
500 lookup =
new InFlightLookup;
508 delete retiredLookup;
511 ChildLease replacementLease;
512 replacementLease.adopt(replacement);
513 child.swap(replacementLease);
514 return LookupStatus::Found;
518 if (completedResult == LookupStatus::Found || completedResult == LookupStatus::Retry) {
521 return completedResult;
524 File* candidate =
nullptr;
525 LookupStatus result = hasCookie ? self->
resolveChildAt(cookie, name.view(), candidate)
527 if (result == LookupStatus::Found && !candidate) {
528 result = LookupStatus::IoError;
529 }
else if (result != LookupStatus::Found && candidate) {
532 result = LookupStatus::IoError;
537 if (
lookup->invalidated) {
538 result = LookupStatus::Retry;
539 }
else if (result == LookupStatus::Found) {
540 File* existing = cachedFileLocked(s);
543 DirectoryEntry* entry =
new DirectoryEntry(candidate);
546 const bool generationInserted =
548 assert(generationInserted);
555 lookup->changed.broadcast();
557 if (published.hasValue() && published.value() ==
lookup) {
566 delete retiredLookup;
568 if (result == LookupStatus::Found || result == LookupStatus::Retry) {
575#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
576void Directory::setRetainedLookupHookForHostedTest(RetainedLookupHook hook) {
577 __atomic_store_n(&m_RetainedLookupHook, hook, __ATOMIC_RELEASE);
580bool Directory::tryCacheLockForHostedTest()
const {
590size_t Directory::inFlightUsersForHostedTest(
const HashedStringView& name)
const {
593 return pending.hasValue() ? pending.value()->users : 0;
604 if (resident.hasValue()) {
605 residentEntry = resident.value();
606 tombstoneResidentLocked(s);
611 if (cached.hasValue()) {
612 cachedEntry = cached.value();
615 if (residentEntry || cachedEntry) {
619 if (pending.hasValue()) {
620 pending.value()->invalidated =
true;
623 delete residentEntry;
628 assert(!reservation);
629 const String ownedName = name.toString();
640 if (cachedFileLocked(name)) {
646 if (!pending.hasValue()) {
650 reservation.m_pDirectory =
this;
651 reservation.m_Name = ownedName;
652 reservation.m_pToken =
lookup;
653 reservation.m_OwnsNamespaceLock =
true;
659 while (!
lookup->complete) {
666 delete retiredLookup;
670void Directory::finishNameReservation(NameReservation& reservation, LookupStatus result) {
671 const bool ownsNamespaceLock = reservation.m_OwnsNamespaceLock;
672 InFlightLookup* retiredLookup =
nullptr;
675 InFlightLookupCache::LookupResult pending =
677 InFlightLookup*
lookup =
reinterpret_cast<InFlightLookup*
>(reservation.m_pToken);
678 assert(pending.hasValue() && pending.value() ==
lookup && !
lookup->complete);
682 lookup->changed.broadcast();
687 reservation.m_pDirectory =
nullptr;
688 reservation.m_Name.clear();
689 reservation.m_pToken =
nullptr;
690 reservation.m_OwnsNamespaceLock =
false;
692 delete retiredLookup;
693 if (ownsNamespaceLock) {
699 assert(!reservation);
708 if (!pending.hasValue()) {
712 reservation.m_pDirectory =
this;
713 reservation.m_Name = name;
714 reservation.m_pToken =
lookup;
719 while (!
lookup->complete) {
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;
743 resident = residentEntry.hasValue();
745 moved = residentEntry.value();
748 assert(cachedEntry.hasValue());
749 moved = cachedEntry.value();
751 assert(moved->get() == source);
765 if (existing.hasValue()) {
766 retired = existing.value();
771 if (cachedEntry.hasValue()) {
772 retired = cachedEntry.value();
787 const bool inserted = destination->
m_Cache.
insert(newName, moved);
795bool Directory::addResidentDirectoryEntry(NameReservation& reservation,
File* pTarget,
797 assert(pTarget !=
nullptr);
798 return addResidentDirectoryEntry(reservation,
new DirectoryEntry(pTarget), ephemeral, pTarget);
801bool Directory::addResidentDirectoryEntry(NameReservation& reservation, DirectoryEntry* entry,
802 bool ephemeral,
File* createdTarget) {
803 assert(reservation.m_pDirectory ==
this);
804 assert(entry !=
nullptr);
806 bool inserted =
false;
809 const String& name = reservation.m_Name;
815 assert(generationInserted);
818 assert(kindInserted);
828 reservation.complete(LookupStatus::Found);
836 assert(pTarget !=
nullptr);
839 ERROR(
"can't add directory entry for '" << name <<
"' as it already exists.");
843 return addResidentDirectoryEntry(reservation, pTarget,
false);
849 ERROR(
"can't add directory entry for '" << name <<
"' as it already exists.");
852 return addResidentDirectoryEntry(reservation,
new DirectoryEntry(pedigree_std::move(meta)),
857 assert(pTarget !=
nullptr);
858 assert(reservation.m_pDirectory ==
this);
860 bool inserted =
false;
863 const String& name = reservation.m_Name;
870 assert(generationInserted);
881 if (result.hasValue()) {
882 entry = result.value();
887 if (pending.hasValue()) {
888 pending.value()->invalidated =
true;
899 if (!resident.hasValue() || resident.value()->get() != expected) {
902 entry = resident.value();
903 tombstoneResidentLocked(name);
908 if (pending.hasValue()) {
909 pending.value()->invalidated =
true;
926 if (!tryAcquireFilesystemOperation(operation)) {
927 return AddStatus::IoError;
929 assert(pFile !=
nullptr);
932 return isDetached() ? AddStatus::Detached : AddStatus::Exists;
935 File* backing =
nullptr;
937 if (status == LookupStatus::Found && !backing) {
938 status = LookupStatus::IoError;
939 }
else if (status != LookupStatus::Found && backing) {
942 status = LookupStatus::IoError;
945 if (status == LookupStatus::Found) {
948 return AddStatus::Exists;
950 reservation.complete(LookupStatus::Found);
951 return AddStatus::Exists;
953 if (status != LookupStatus::NotFound) {
954 return AddStatus::IoError;
958 return addResidentDirectoryEntry(reservation, pFile,
true) ? AddStatus::Added : AddStatus::Exists;
971 if (entry->active()) {
972 File* file = entry->get();
979 retainDirectory(entry);
982 retainDirectory(entry);
990 for (
const auto& name : names) {
991 File* file =
nullptr;
995 if (entry.hasValue()) {
996 file = entry.value()->get();
1002 for (
File* child : children) {
1009 DirectoryEntry* entry =
nullptr;
1016 if (!resident.hasValue() || resident.value()->get() != expected) {
1019 entry = resident.value();
1020 tombstoneResidentLocked(name);
1025 if (pending.hasValue()) {
1026 pending.value()->invalidated =
true;
1035 if (!tryAcquireFilesystemOperation(operation)) {
1043 } first = {
String(), 0,
false};
1045 FirstEntry* first =
reinterpret_cast<FirstEntry*
>(opaque);
1046 first->name = entry.name.toString();
1047 first->cookie = entry.currentCookie;
1048 first->found =
true;
1052 uint64_t cookie = 2;
1053 const ReadStatus readStatus =
enumerate(cookie, captureFirst, &first);
1054 if (readStatus == ReadStatus::IoError) {
1058 return readStatus == ReadStatus::Complete;
1063 if (
lookup == LookupStatus::Retry) {
1070 if (
lookup == LookupStatus::NotFound) {
1073 if (
lookup != LookupStatus::Found) {
1076 if (!getFilesystem()->removeChild(
this, first.name, child.get())) {
1082void Directory::markDetached() {
1088 __atomic_store_n(&
m_Detached,
true, __ATOMIC_RELEASE);
1097 entries.pushBack(it);
1100 entries.pushBack(it);
1103 if (entry->active()) {
1104 entry->get()->retainDetachedParent();
1115 it->invalidated =
true;
1121 for (
auto it : entries) {
1126File* Directory::evaluateEntry(
const DirectoryEntryMetadata& meta) {
1127 if (!meta.pDirectory) {
1131 if (!meta.pDirectory->tryAcquireFilesystemOperation(operation)) {
1134 File* newFile = meta.pDirectory->convertToFile(meta);
1144void Directory::destroyEntry(
File* file) {
1154 return LookupStatus::NotFound;
1162 return ReadStatus::Complete;
1175uint64_t Directory::nextCacheGeneration() {
1176 ++m_NextCacheGeneration;
1177 if (!m_NextCacheGeneration) {
1178 ++m_NextCacheGeneration;
1180 return m_NextCacheGeneration;
1185 if (!generation.hasValue() || !generation.value() ||
1198 if (resident.hasValue()) {
1199 return resident.value()->get();
1201 DirectoryEntryCache::LookupResult cached =
m_Cache.
lookup(name);
1202 if (cached.hasValue()) {
1203 return cached.value()->get();
1208Directory::EntryType Directory::entryType(
File* file) {
1210 return EntryType::Unknown;
1213 return EntryType::Directory;
1216 return EntryType::Symlink;
1219 return EntryType::Fifo;
1222 return EntryType::Socket;
1224 return EntryType::Regular;
1227Directory::DirectoryEntryMetadata::DirectoryEntryMetadata()
1228 : pDirectory(nullptr), filename(), opaque() {}
1229Directory::DirectoryEntryMetadata::DirectoryEntryMetadata(
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;
1237Directory::DirectoryEntryMetadata::~DirectoryEntryMetadata() {
ReadStatus enumerate(uint64_t &cookie, DirectoryEntryEmitter emitter, void *context)
virtual LookupStatus resolveChildAt(uint64_t cookie, const StringView &name, File *&child)
static Directory * fromFile(File *pF)
void removeEphemeralFiles()
EphemeralEntryCache m_EphemeralEntries
InFlightLookupCache m_InFlightLookups
bool addDirectoryEntry(const String &name, File *pTarget)
void markCachePopulated()
virtual bool cacheResolvedChildren() const
Directory * m_ReparseTarget
void setReparsePoint(Directory *pTarget)
void invalidateDirectoryEntry(const HashedStringView &name)
AddStatus addEphemeralFile(File *pFile)
Add an ephemeral file to the directory.
virtual void cacheDirectoryContents()
void remove(const HashedStringView &s)
File * lookup(const HashedStringView &s) const
Mutex m_NamespaceMutationLock
bool removeDirectoryEntry(const HashedStringView &name, File *expected)
ReadStatus isEmpty(bool &empty)
virtual File * convertToFile(const DirectoryEntryMetadata &meta)
virtual ReadStatus readDirectory(uint64_t &cookie, DirectoryEntryEmitter emitter, void *context)
Vector< String > m_ResidentOrder
virtual LookupStatus resolveChild(const StringView &name, File *&child)
MUST_USE_RESULT LookupStatus lookupChildAt(uint64_t cookie, const HashedStringView &name, ChildLease &child) const
void preallocateDirectoryEntries(size_t count)
bool reserveRenameEntry(const String &name, NameReservation &reservation)
bool addCachedDirectoryEntry(NameReservation &reservation, File *pTarget)
MUST_USE_RESULT bool lookupRetained(const HashedStringView &s, ChildLease &child) const
DirectoryEntryGenerationCache m_CacheGenerations
MUST_USE_RESULT LookupStatus lookupChild(const HashedStringView &s, ChildLease &child) const
File * getChild(size_t n)
DirectoryEntryCache m_Cache
DirectoryEntryCache m_ResidentEntries
bool reserveDirectoryEntry(const HashedStringView &name, NameReservation &reservation)
Directory * getReparsePoint() const
Get the reparse point attached to this directory. Reparse points allow locations on the filesystem to...
virtual bool isCachePopulated() const
void getNamespace(ParentLease &parent, String &name) const
virtual bool isSocket() const
void retainDetachedParent()
virtual bool isDirectory()
void publishEvent(FileEventMask mask, const StringView &name=StringView(), bool targetIsDirectory=false)
virtual bool isFifo() const
LookupResult lookup(const K &k) const
bool insert(const K &k, const V &v)
void reserve(size_t numItems)
bool contains(const K &k) const
bool tryAcquire(size_t n=1)
bool acquire(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
bool compare(const char *s, size_t len) const
MUST_USE_RESULT bool retainTrackedFile(File *pFile)
bool untrackFile(File *pFile, bool destroy=true)
void trackFile(File *pFile)
Track a File object that exists. It is necessary to keep track of File objects, or at least those tha...
A vector / dynamic array.
void pushBack(const T &value)
void clear(bool freeMem=false)