/** * PROJECT: ExectOS * COPYRIGHT: See COPYING.md in the top level directory * FILE: xtoskrnl/ob/obdir.cc * DESCRIPTION: Object Manager Directory * DEVELOPERS: Aiken Harris */ #include /** * Acquires an exclusive pushlock for the specified object directory. * * @param Directory * Supplies a pointer to the object directory being locked. * * @param LookupContext * Supplies a pointer to the lookup context used to track the lock state. * * @return This routine does not return any value. * * @since XT 1.0 */ XTFASTCALL VOID OB::Directory::AcquireExclusiveDirectoryLock(IN OUT POBJECT_DIRECTORY Directory, IN OUT POBJECT_LOOKUP_CONTEXT LookupContext) { /* Set the initial lock state */ LookupContext->LockStateSignature = OBJECT_LOCK_STATE_WAIT_EXCLUSIVE; /* Disable kernel APCs */ KE::KThread::EnterCriticalRegion(); /* Acquire the directory lock for exclusive access */ KE::PushLock::AcquireExclusivePushLock(&Directory->Lock); /* Update the lock state */ LookupContext->LockStateSignature = OBJECT_LOCK_STATE_OWNED_EXCLUSIVE; } /** * Acquires a shared pushlock for the specified object directory. * * @param Directory * Supplies a pointer to the object directory being locked. * * @param LookupContext * Supplies a pointer to the lookup context used to track the lock state. * * @return This routine does not return any value. * * @since XT 1.0 */ XTFASTCALL VOID OB::Directory::AcquireSharedDirectoryLock(IN POBJECT_DIRECTORY Directory, IN POBJECT_LOOKUP_CONTEXT LookupContext) { /* Set the initial lock state */ LookupContext->LockStateSignature = OBJECT_LOCK_WAITSHARED_SIGNATURE; /* Disable kernel APCs */ KE::KThread::EnterCriticalRegion(); /* Acquire the directory lock for shared access */ KE::PushLock::AcquireSharedPushLock(&Directory->Lock); /* Update the lock state */ LookupContext->LockStateSignature = OBJECT_LOCK_OWNEDSHARED_SIGNATURE; } /** * Computes the case-insensitive hash index for an object name. * * @param Name * Supplies a pointer to the unicode string containing the object name. * * @return This routine returns the computed hash index. * * @since XT 1.0 */ XTFASTCALL ULONG OB::Directory::ComputeObjectNameHash(IN PCUNICODE_STRING Name) { ULONG CharacterLength, HashIndex; WCHAR Character; PWCH Buffer; /* Initialize the buffer pointer, character count, and starting hash value */ Buffer = Name->Buffer; CharacterLength = Name->Length / sizeof(WCHAR); HashIndex = 0; /* Iterate through each wide character */ while(CharacterLength--) { /* Fetch the current character and advance the buffer pointer */ Character = *Buffer++; /* Apply the NT string hashing polynomial */ HashIndex += (HashIndex << 1) + (HashIndex >> 1); /* Check if the character is uppercase, numeric, or a symbol */ if(Character < L'a') { /* Add the unmodified character */ HashIndex += Character; } else if(Character > L'z') { /* Convert extended Unicode characters to uppercase */ HashIndex += RTL::Nls::ToUpperUnicodeCharacter(Character); } else { /* Perform ASCII conversion from lowercase to uppercase */ HashIndex += (Character - (L'a' - L'A')); } } /* Return the final hash value */ return HashIndex % OBJECT_NUMBER_HASH_BUCKETS; } /** * Removes an object directory entry from its hash bucket and frees its memory. * * @param LookupContext * Supplies a pointer to the lookup context containing the directory and hash index. * * @return This routine returns TRUE if the entry was successfully unlinked and freed, or FALSE otherwise. * * @since XT 1.0 */ XTAPI BOOLEAN OB::Directory::DeleteDirectoryEntry(IN POBJECT_LOOKUP_CONTEXT LookupContext) { POBJECT_DIRECTORY_ENTRY DirectoryEntry, *HashBucket; POBJECT_DIRECTORY Directory; /* Extract the directory from the lookup context */ Directory = LookupContext->Directory; /* Verify that the directory pointer is valid */ if(Directory) { /* Resolve the head of the collision chain */ HashBucket = (POBJECT_DIRECTORY_ENTRY*)&Directory->HashBuckets[LookupContext->HashIndex]; /* Retrieve the entry at the head of the chain */ DirectoryEntry = *HashBucket; /* Ensure there isan entry to delete */ if(DirectoryEntry) { /* Unlink the entry from the hash bucket chain */ *HashBucket = DirectoryEntry->ChainLink; DirectoryEntry->ChainLink = NULLPTR; /* Free the memory */ MM::Allocator::FreePool(DirectoryEntry); /* Return TRUE */ return TRUE; } } /* No entry found, return FALSE */ return FALSE; } /** * Initializes the Object Manager lookup context. * * @param LookupContext * Supplies a pointer to the caller-allocated lookup context to initialize. * * @return This routine does not return any value. * * @since XT 1.0 */ XTFASTCALL VOID OB::Directory::InitializeLookupContext(OUT POBJECT_LOOKUP_CONTEXT LookupContext) { /* Initialize the lookup context */ LookupContext->Directory = NULLPTR; LookupContext->DirectoryLocked = FALSE; LookupContext->LockStateSignature = OBJECT_LOCK_STATE_INITIALIZED; LookupContext->Object = NULLPTR; } /** * Inserts a newly created object into an object directory. * * @param Directory * Pointer to the object directory where the object will be inserted. * * @param LookupContext * Pointer to the lookup context containing the precomputed hash index. * * @param ObjectHeader * Pointer to the object header of the object being inserted. * * @return TRUE if the insertion was successful, FALSE if pool allocation failed. * * @since XT 1.0 */ XTAPI BOOLEAN OB::Directory::InsertDirectoryEntry(IN OUT POBJECT_DIRECTORY Directory, IN POBJECT_LOOKUP_CONTEXT LookupContext, IN OUT POBJECT_HEADER ObjectHeader) { POBJECT_DIRECTORY_ENTRY *HashBucket, NewEntry; POBJECT_HEADER_NAME_INFO NameInfo; XTSTATUS Status; /* Extract the name information header */ NameInfo = OB::LifeCycle::GetObjectNameInformation(ObjectHeader); /* Allocate the directory entry */ Status = MM::Allocator::AllocatePool(PagedPool, sizeof(OBJECT_DIRECTORY_ENTRY), (PVOID *)&NewEntry, TAG_OB_DIRECTORY); if(Status != STATUS_SUCCESS || !NewEntry) { /* Allocation failed, return FALSE */ return FALSE; } /* Resolve the hash bucket head */ HashBucket = (POBJECT_DIRECTORY_ENTRY*)&Directory->HashBuckets[LookupContext->HashIndex]; /* Insert the entry at the head of the collision chain */ NewEntry->ChainLink = *HashBucket; *HashBucket = NewEntry; /* Link the entry to the object body and establish the parent directory */ NewEntry->Object = &ObjectHeader->Body; NameInfo->Directory = Directory; /* Return TRUE */ return TRUE; } /** * Acquires an exclusive lock on the specified object directory. * * @param Directory * Supplies a pointer to the object directory to lock. * * @param LookupContext * Supplies a pointer to the active lookup context used to track the lock state. * * @return This routine does not return any value. * * @since XT 1.0 */ XTFASTCALL VOID OB::Directory::LockLookupContext(IN POBJECT_DIRECTORY Directory, IN POBJECT_LOOKUP_CONTEXT LookupContext) { /* Acquire exclusive access to the directory */ AcquireExclusiveDirectoryLock(Directory, LookupContext); /* Cache the directory pointer and lock ownership */ LookupContext->Directory = Directory; LookupContext->DirectoryLocked = TRUE; } /** * Looks up an object directory entry by name within the namespace. * * @param Directory * Supplies a pointer to the primary object directory to search. * * @param Name * Supplies a pointer to the name of the object to locate. * * @param Attributes * Supplies the object attributes bitmask. * * @param SearchGlobalDirectory * Specifies whether to fallback into the device map's global shadow directory if the object is not found * in the primary directory. * * @param LookupContext * Supplies a pointer to the tracking context. * * @return This routine returns a pointer to the referenced object body, or NULLPTR if resolution fails. * * @since XT 1.0 */ XTAPI PVOID OB::Directory::LookupDirectoryEntry(IN POBJECT_DIRECTORY Directory, IN PUNICODE_STRING Name, IN ULONG Attributes, IN BOOLEAN SearchGlobalDirectory, OUT POBJECT_LOOKUP_CONTEXT LookupContext) { POBJECT_HEADER_NAME_INFO PreviousNameInfo; POBJECT_DIRECTORY CurrentDirectory; BOOLEAN CaseInsensitive; PVOID FoundObject; ULONG HashIndex; /* Initialize the default return value */ FoundObject = NULLPTR; /* Check if global device map traversal is available */ if(!OB::DeviceMap::GetUniqueDeviceMaps()) { /* Override caller's request to prevent shadow directory traversal */ SearchGlobalDirectory = FALSE; } /* Guard against invalid parameter combination */ if(Directory != NULLPTR && Name != NULLPTR && Name->Length != 0 && Name->Buffer != NULLPTR) { /* Determine if the string comparison should ignore case */ CaseInsensitive = (Attributes & OBJECT_CASE_INSENSITIVE) ? TRUE : FALSE; /* Precompute the hash index */ HashIndex = ComputeObjectNameHash(Name); /* Initialize the traversal pointer */ CurrentDirectory = Directory; /* Cache the computed hash index in the lookup context */ LookupContext->HashIndex = (USHORT)HashIndex; /* Iterate through the directory and linked shadow boundaries */ while(CurrentDirectory) { /* Check if the directory is locked */ if(!LookupContext->DirectoryLocked) { /* Acquire the directory lock */ AcquireSharedDirectoryLock(CurrentDirectory, LookupContext); } /* Traverse the hash bucket chain to find a matching entry */ FoundObject = SearchDirectory(CurrentDirectory, Name, CaseInsensitive, HashIndex, LookupContext); if(FoundObject) { /* Match found, secure the object and its name info */ OB::LifeCycle::ReferenceObjectNameInformation(CONTAIN_RECORD(FoundObject, OBJECT_HEADER, Body)); OB::LifeCycle::ReferenceObject(FoundObject); /* Check if the directory is locked */ if(!LookupContext->DirectoryLocked) { /* Drop the lock and restore standard kernel APC delivery */ OB::Directory::ReleaseDirectoryLock(CurrentDirectory, LookupContext); } /* Target resolved, break the loop */ break; } /* Check if the directory is locked */ if(!LookupContext->DirectoryLocked) { /* Drop the lock and restore standard kernel APC delivery */ OB::Directory::ReleaseDirectoryLock(CurrentDirectory, LookupContext); } /* Check if fallback to the global shadow directory is possible */ if(SearchGlobalDirectory && CurrentDirectory->DeviceMap != NULLPTR) { /* Resolve the shadow global devices directory in next iteration */ CurrentDirectory = OB::DeviceMap::GetGlobalDevicesDirectory(CurrentDirectory); } else { /* Terminate the traversal loop */ CurrentDirectory = NULLPTR; } } } /* Check if the lookup context contains a previously resolved object */ if(LookupContext->Object != NULLPTR) { /* Resolve the old object's name information header */ PreviousNameInfo = OB::LifeCycle::GetObjectNameInformation(CONTAIN_RECORD(LookupContext->Object, OBJECT_HEADER, Body)); /* Release the references */ OB::LifeCycle::DereferenceObjectNameInformation(PreviousNameInfo); OB::LifeCycle::DereferenceObject(LookupContext->Object); } /* Store the newly found object payload inside the caller's lookup context */ LookupContext->Object = FoundObject; /* Return the object body */ return FoundObject; } /** * Releases a previously acquired object directory lock. * * @param Directory * Supplies a pointer to the object directory to unlock. * * @param LookupContext * Supplies a pointer to the lookup context for state tracking. * * @return This routine does not return any value. * * @since XT 1.0 */ XTFASTCALL VOID OB::Directory::ReleaseDirectoryLock(IN OUT POBJECT_DIRECTORY Directory, IN OUT POBJECT_LOOKUP_CONTEXT LookupContext) { /* Release the directory lock */ KE::PushLock::ReleasePushLock(&Directory->Lock); /* Update the lock state */ LookupContext->LockStateSignature = OBJECT_LOCK_STATE_RELEASED_SIGNATURE; /* Restore APC delivery */ KE::KThread::LeaveCriticalRegion(); } /** * Releases all resources held within an Object Manager lookup context. * * @param LookupContext * Supplies a pointer to the active lookup context to be released. * * @return This routine does not return any value. * * @since XT 1.0 */ XTFASTCALL VOID OB::Directory::ReleaseLookupContext(IN OUT POBJECT_LOOKUP_CONTEXT LookupContext) { /* Verify if a directory lock is held */ if(LookupContext->DirectoryLocked) { /* Release the directory lock */ ReleaseDirectoryLock(LookupContext->Directory, LookupContext); /* Update lookup context state */ LookupContext->Directory = NULLPTR; LookupContext->DirectoryLocked = FALSE; } /* Release the object references held within a lookup context */ ReleaseLookupContextObject(LookupContext); } /** * Releases the object reference held within a lookup context. * * @param LookupContext * Supplies a pointer to the active lookup context containing the object to be released. * * @return This routine does not return any value. * * @since XT 1.0 */ XTFASTCALL VOID OB::Directory::ReleaseLookupContextObject(IN OUT POBJECT_LOOKUP_CONTEXT LookupContext) { POBJECT_HEADER_NAME_INFO NameInfo; POBJECT_HEADER ObjectHeader; PVOID Object; /* Extract the cached object pointer from the lookup context */ Object = LookupContext->Object; if(Object) { /* Resolve the object header */ ObjectHeader = CONTAIN_RECORD(Object, OBJECT_HEADER, Body); /* Retrieve the optional name information header */ NameInfo = OB::LifeCycle::GetObjectNameInformation(ObjectHeader); /* Verify if the object has a registered name */ if(NameInfo) { /* Drop the name query reference */ OB::LifeCycle::DereferenceObjectNameInformation(NameInfo); } /* Release the pointer reference */ OB::LifeCycle::DereferenceObject(Object); /* Purge the pointer */ LookupContext->Object = NULLPTR; } } /** * Traverses an object directory hash bucket to find a matching entry. * * @param Directory * Supplies a pointer to the object directory being searched. * * @param Name * Supplies ap ointer to the target object name. * * @param CaseInsensitive * Specifies whether the string comparison should ignore case. * * @param HashIndex * Supplies the pre-computed hash bucket index used to locate the collision chain. * * @param LookupContext * Supplies a pointer to the caller's lookup context. * * @return This routine returns a pointer to the resolved object body if found, or NULLPTR otherwise. * * @since XT 1.0 */ XTFASTCALL PVOID OB::Directory::SearchDirectory(IN POBJECT_DIRECTORY Directory, IN PCUNICODE_STRING Name, IN BOOLEAN CaseInsensitive, IN ULONG HashIndex, IN POBJECT_LOOKUP_CONTEXT LookupContext) { POBJECT_DIRECTORY_ENTRY DirectoryEntry, *HeadDirectoryEntry, *LookupBucket; POBJECT_HEADER_NAME_INFO NameInfo; POBJECT_HEADER ObjectHeader; /* Resolve the head pointer of the target hash bucket chain */ LookupBucket = (POBJECT_DIRECTORY_ENTRY*)&Directory->HashBuckets[HashIndex]; /* Iterate through the collision chain */ HeadDirectoryEntry = LookupBucket; while((DirectoryEntry = *HeadDirectoryEntry) != NULLPTR) { /* Resolve the underlying object header */ ObjectHeader = CONTAIN_RECORD(DirectoryEntry->Object, OBJECT_HEADER, Body); /* Retrieve the optional name information header */ NameInfo = OB::LifeCycle::GetObjectNameInformation(ObjectHeader); /* Check if the names match */ if((Name->Length == NameInfo->Name.Length) && RTL::Unicode::CompareString(Name, &NameInfo->Name, CaseInsensitive) == 0) { /* Check if it is already at the head of the bucket */ if(HeadDirectoryEntry != LookupBucket) { /* Check if the directory is locked */ if(LookupContext->DirectoryLocked || KE::PushLock::ConvertSharedPushLockToExclusive(&Directory->Lock)) { /* Relocate the entry to the front */ *HeadDirectoryEntry = DirectoryEntry->ChainLink; DirectoryEntry->ChainLink = *LookupBucket; *LookupBucket = DirectoryEntry; } } /* Return the pointer to the object body */ return DirectoryEntry->Object; } /* Advance to the next node entry */ HeadDirectoryEntry = &DirectoryEntry->ChainLink; } /* Return NULL pointer*/ return NULLPTR; }