mirror of
https://github.com/reactos/reactos.git
synced 2024-11-09 08:08:38 +00:00
c501d8112c
svn path=/branches/aicom-network-fixes/; revision=34994
676 lines
20 KiB
C
676 lines
20 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: ntoskrnl/ob/refderef.c
|
|
* PURPOSE: Manages the referencing and de-referencing of all Objects,
|
|
* as well as the Object Fast Reference implementation.
|
|
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
|
|
* Eric Kohl
|
|
* Thomas Weidenmueller (w3seek@reactos.org)
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* PRIVATE FUNCTIONS *********************************************************/
|
|
|
|
BOOLEAN
|
|
FASTCALL
|
|
ObReferenceObjectSafe(IN PVOID Object)
|
|
{
|
|
POBJECT_HEADER ObjectHeader;
|
|
LONG OldValue, NewValue;
|
|
|
|
/* Get the object header */
|
|
ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);
|
|
|
|
/* Get the current reference count and fail if it's zero */
|
|
OldValue = ObjectHeader->PointerCount;
|
|
if (!OldValue) return FALSE;
|
|
|
|
/* Start reference loop */
|
|
do
|
|
{
|
|
/* Increase the reference count */
|
|
NewValue = InterlockedCompareExchange(&ObjectHeader->PointerCount,
|
|
OldValue + 1,
|
|
OldValue);
|
|
if (OldValue == NewValue) return TRUE;
|
|
|
|
/* Keep looping */
|
|
OldValue = NewValue;
|
|
} while (OldValue);
|
|
|
|
/* If we got here, then the reference count is now 0 */
|
|
return FALSE;
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
ObpDeferObjectDeletion(IN POBJECT_HEADER Header)
|
|
{
|
|
PVOID Entry;
|
|
|
|
/* Loop while trying to update the list */
|
|
do
|
|
{
|
|
/* Get the current entry */
|
|
Entry = ObpReaperList;
|
|
|
|
/* Link our object to the list */
|
|
Header->NextToFree = Entry;
|
|
|
|
/* Update the list */
|
|
} while (InterlockedCompareExchangePointer(&ObpReaperList,
|
|
Header,
|
|
Entry) != Entry);
|
|
|
|
/* Queue the work item if needed */
|
|
if (!Entry) ExQueueWorkItem(&ObpReaperWorkItem, CriticalWorkQueue);
|
|
}
|
|
|
|
LONG
|
|
FASTCALL
|
|
ObReferenceObjectEx(IN PVOID Object,
|
|
IN LONG Count)
|
|
{
|
|
/* Increment the reference count and return the count now */
|
|
return InterlockedExchangeAdd(&OBJECT_TO_OBJECT_HEADER(Object)->
|
|
PointerCount,
|
|
Count);
|
|
}
|
|
|
|
LONG
|
|
FASTCALL
|
|
ObDereferenceObjectEx(IN PVOID Object,
|
|
IN LONG Count)
|
|
{
|
|
POBJECT_HEADER Header;
|
|
LONG NewCount;
|
|
|
|
/* Extract the object header */
|
|
Header = OBJECT_TO_OBJECT_HEADER(Object);
|
|
|
|
/* Check whether the object can now be deleted. */
|
|
NewCount = InterlockedExchangeAdd(&Header->PointerCount, -Count);
|
|
if (!Count) ObpDeferObjectDeletion(Header);
|
|
|
|
/* Return the current count */
|
|
return NewCount;
|
|
}
|
|
|
|
VOID
|
|
FASTCALL
|
|
ObInitializeFastReference(IN PEX_FAST_REF FastRef,
|
|
IN PVOID Object OPTIONAL)
|
|
{
|
|
/* Check if we were given an object and reference it 7 times */
|
|
if (Object) ObReferenceObjectEx(Object, MAX_FAST_REFS);
|
|
|
|
/* Sanity check */
|
|
ASSERT(!(((ULONG_PTR)Object) & MAX_FAST_REFS));
|
|
|
|
/* Check if the caller gave us an object */
|
|
if (Object)
|
|
{
|
|
/* He did, so write the biased pointer */
|
|
FastRef->Object = (PVOID)((ULONG_PTR)Object | MAX_FAST_REFS);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, clear the current object */
|
|
FastRef->Object = NULL;
|
|
}
|
|
}
|
|
|
|
PVOID
|
|
FASTCALL
|
|
ObFastReferenceObjectLocked(IN PEX_FAST_REF FastRef)
|
|
{
|
|
PVOID Object;
|
|
|
|
/* Get the object and reference it slowly */
|
|
Object = (PVOID)((ULONG_PTR)FastRef->Object & MAX_FAST_REFS);
|
|
if (Object) ObReferenceObject(Object);
|
|
return Object;
|
|
}
|
|
|
|
PVOID
|
|
FASTCALL
|
|
ObFastReferenceObject(IN PEX_FAST_REF FastRef)
|
|
{
|
|
ULONG_PTR Value, NewValue;
|
|
ULONG_PTR Count;
|
|
PVOID Object;
|
|
|
|
/* Start reference loop */
|
|
for (;;)
|
|
{
|
|
/* Get the current count */
|
|
Value = FastRef->Value;
|
|
if (!(Value & MAX_FAST_REFS)) break;
|
|
|
|
/* Increase the reference count */
|
|
NewValue = Value - 1;
|
|
if (ExpChangeRundown(FastRef, NewValue, Value) == Value) break;
|
|
}
|
|
|
|
/* Get the object and count */
|
|
Object = (PVOID)(Value &~ MAX_FAST_REFS);
|
|
Count = Value & MAX_FAST_REFS;
|
|
|
|
/* Check if the reference count is over 1 */
|
|
if (Count > 1) return Object;
|
|
|
|
/* Check if the reference count has reached 0 */
|
|
if (!Count) return NULL;
|
|
|
|
/* Otherwise, reference the object 7 times */
|
|
ObReferenceObjectEx(Object, MAX_FAST_REFS);
|
|
ASSERT(!(((ULONG_PTR)Object) & MAX_FAST_REFS));
|
|
|
|
for (;;)
|
|
{
|
|
/* Check if the current count is too high */
|
|
Value = FastRef->Value;
|
|
if (((FastRef->RefCnt + MAX_FAST_REFS) > MAX_FAST_REFS) ||
|
|
((PVOID)((ULONG_PTR)FastRef->Object &~ MAX_FAST_REFS) != Object))
|
|
{
|
|
/* Completely dereference the object */
|
|
ObDereferenceObjectEx(Object, MAX_FAST_REFS);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* Increase the reference count */
|
|
NewValue = Value + MAX_FAST_REFS;
|
|
if (ExpChangeRundown(FastRef, NewValue, Value) == Value) break;
|
|
}
|
|
}
|
|
|
|
/* Return the Object */
|
|
return Object;
|
|
}
|
|
|
|
VOID
|
|
FASTCALL
|
|
ObFastDereferenceObject(IN PEX_FAST_REF FastRef,
|
|
IN PVOID Object)
|
|
{
|
|
ULONG_PTR Value, NewValue;
|
|
|
|
/* Sanity checks */
|
|
ASSERT(Object);
|
|
ASSERT(!(((ULONG_PTR)Object) & MAX_FAST_REFS));
|
|
|
|
/* Start dereference loop */
|
|
for (;;)
|
|
{
|
|
/* Get the current count */
|
|
Value = FastRef->Value;
|
|
if ((Value ^ (ULONG_PTR)Object) < MAX_FAST_REFS)
|
|
{
|
|
/* Decrease the reference count */
|
|
NewValue = Value + 1;
|
|
if (ExpChangeRundown(FastRef, NewValue, Value) == Value) return;
|
|
}
|
|
else
|
|
{
|
|
/* Do a normal Dereference */
|
|
ObDereferenceObject(Object);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
PVOID
|
|
FASTCALL
|
|
ObFastReplaceObject(IN PEX_FAST_REF FastRef,
|
|
PVOID Object)
|
|
{
|
|
ULONG_PTR NewValue;
|
|
EX_FAST_REF OldRef;
|
|
PVOID OldObject;
|
|
|
|
/* Check if we were given an object and reference it 7 times */
|
|
if (Object) ObReferenceObjectEx(Object, MAX_FAST_REFS);
|
|
|
|
/* Sanity check */
|
|
ASSERT(!(((ULONG_PTR)Object) & MAX_FAST_REFS));
|
|
|
|
/* Check if the caller gave us an object */
|
|
if (Object)
|
|
{
|
|
/* He did, so bias the pointer */
|
|
NewValue = (ULONG_PTR)Object | MAX_FAST_REFS;
|
|
}
|
|
else
|
|
{
|
|
/* No object, we're clearing */
|
|
NewValue = 0;
|
|
}
|
|
|
|
/* Switch objects */
|
|
OldRef.Value = InterlockedExchange((PLONG)&FastRef->Value, NewValue);
|
|
OldObject = (PVOID)((ULONG_PTR)OldRef.Object &~ MAX_FAST_REFS);
|
|
if ((OldObject) && (OldRef.RefCnt))
|
|
{
|
|
/* Dereference the old object */
|
|
ObDereferenceObjectEx(OldObject, OldRef.RefCnt);
|
|
}
|
|
|
|
/* Return the old object */
|
|
return OldObject;
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS *********************************************************/
|
|
|
|
LONG_PTR
|
|
FASTCALL
|
|
ObfReferenceObject(IN PVOID Object)
|
|
{
|
|
ASSERT(Object);
|
|
|
|
/* Get the header and increment the reference count */
|
|
return InterlockedIncrement(&OBJECT_TO_OBJECT_HEADER(Object)->PointerCount);
|
|
}
|
|
|
|
LONG_PTR
|
|
FASTCALL
|
|
ObfDereferenceObject(IN PVOID Object)
|
|
{
|
|
POBJECT_HEADER Header;
|
|
LONG_PTR OldCount;
|
|
|
|
/* Extract the object header */
|
|
Header = OBJECT_TO_OBJECT_HEADER(Object);
|
|
|
|
if (Header->PointerCount < Header->HandleCount)
|
|
{
|
|
DPRINT1("Misbehaving object: %wZ\n", &Header->Type->Name);
|
|
return Header->PointerCount;
|
|
}
|
|
|
|
/* Check whether the object can now be deleted. */
|
|
OldCount = InterlockedDecrement(&Header->PointerCount);
|
|
if (!OldCount)
|
|
{
|
|
/* Sanity check */
|
|
if (Header->HandleCount)
|
|
{
|
|
DPRINT1("Misbehaving object: %wZ\n", &Header->Type->Name);
|
|
return Header->PointerCount;
|
|
}
|
|
|
|
/* Check if APCs are still active */
|
|
if (!KeAreAllApcsDisabled())
|
|
{
|
|
/* Remove the object */
|
|
ObpDeleteObject(Object, FALSE);
|
|
}
|
|
else
|
|
{
|
|
/* Add us to the deferred deletion list */
|
|
ObpDeferObjectDeletion(Header);
|
|
}
|
|
}
|
|
|
|
/* Return the old count */
|
|
return OldCount;
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
ObDereferenceObjectDeferDelete(IN PVOID Object)
|
|
{
|
|
POBJECT_HEADER Header = OBJECT_TO_OBJECT_HEADER(Object);
|
|
|
|
/* Check whether the object can now be deleted. */
|
|
if (!InterlockedDecrement(&Header->PointerCount))
|
|
{
|
|
/* Add us to the deferred deletion list */
|
|
ObpDeferObjectDeletion(Header);
|
|
}
|
|
}
|
|
|
|
#undef ObDereferenceObject
|
|
VOID
|
|
NTAPI
|
|
ObDereferenceObject(IN PVOID Object)
|
|
{
|
|
/* Call the fastcall function */
|
|
ObfDereferenceObject(Object);
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
ObReferenceObjectByPointer(IN PVOID Object,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_TYPE ObjectType,
|
|
IN KPROCESSOR_MODE AccessMode)
|
|
{
|
|
POBJECT_HEADER Header;
|
|
|
|
/* Get the header */
|
|
Header = OBJECT_TO_OBJECT_HEADER(Object);
|
|
|
|
/*
|
|
* Validate object type if the call is for UserMode.
|
|
* NOTE: Unless it's a symbolic link (Caz Yokoyama [MSFT])
|
|
*/
|
|
if ((Header->Type != ObjectType) && ((AccessMode != KernelMode) ||
|
|
(ObjectType == ObSymbolicLinkType)))
|
|
{
|
|
/* Invalid type */
|
|
return STATUS_OBJECT_TYPE_MISMATCH;
|
|
}
|
|
|
|
/* Increment the reference count and return success */
|
|
InterlockedIncrement(&Header->PointerCount);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
ObReferenceObjectByName(IN PUNICODE_STRING ObjectPath,
|
|
IN ULONG Attributes,
|
|
IN PACCESS_STATE PassedAccessState,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_TYPE ObjectType,
|
|
IN KPROCESSOR_MODE AccessMode,
|
|
IN OUT PVOID ParseContext,
|
|
OUT PVOID* ObjectPtr)
|
|
{
|
|
PVOID Object = NULL;
|
|
UNICODE_STRING ObjectName;
|
|
NTSTATUS Status;
|
|
OBP_LOOKUP_CONTEXT Context;
|
|
AUX_ACCESS_DATA AuxData;
|
|
ACCESS_STATE AccessState;
|
|
PAGED_CODE();
|
|
|
|
/* Fail quickly */
|
|
if (!ObjectPath) return STATUS_OBJECT_NAME_INVALID;
|
|
|
|
/* Capture the name */
|
|
Status = ObpCaptureObjectName(&ObjectName, ObjectPath, AccessMode, TRUE);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
|
|
/* We also need a valid name after capture */
|
|
if (!ObjectName.Length) return STATUS_OBJECT_NAME_INVALID;
|
|
|
|
/* Check if we didn't get an access state */
|
|
if (!PassedAccessState)
|
|
{
|
|
/* Use our built-in access state */
|
|
PassedAccessState = &AccessState;
|
|
Status = SeCreateAccessState(&AccessState,
|
|
&AuxData,
|
|
DesiredAccess,
|
|
&ObjectType->TypeInfo.GenericMapping);
|
|
if (!NT_SUCCESS(Status)) goto Quickie;
|
|
}
|
|
|
|
/* Find the object */
|
|
*ObjectPtr = NULL;
|
|
Status = ObpLookupObjectName(NULL,
|
|
&ObjectName,
|
|
Attributes,
|
|
ObjectType,
|
|
AccessMode,
|
|
ParseContext,
|
|
NULL,
|
|
NULL,
|
|
PassedAccessState,
|
|
&Context,
|
|
&Object);
|
|
|
|
/* Cleanup after lookup */
|
|
ObpReleaseLookupContext(&Context);
|
|
|
|
/* Check if the lookup succeeded */
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Check if access is allowed */
|
|
if (ObpCheckObjectReference(Object,
|
|
PassedAccessState,
|
|
FALSE,
|
|
AccessMode,
|
|
&Status))
|
|
{
|
|
/* Return the object */
|
|
*ObjectPtr = Object;
|
|
}
|
|
}
|
|
|
|
/* Free the access state */
|
|
if (PassedAccessState == &AccessState)
|
|
{
|
|
SeDeleteAccessState(PassedAccessState);
|
|
}
|
|
|
|
Quickie:
|
|
/* Free the captured name if we had one, and return status */
|
|
ObpFreeObjectNameBuffer(&ObjectName);
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
ObReferenceObjectByHandle(IN HANDLE Handle,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_TYPE ObjectType,
|
|
IN KPROCESSOR_MODE AccessMode,
|
|
OUT PVOID* Object,
|
|
OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL)
|
|
{
|
|
PHANDLE_TABLE_ENTRY HandleEntry;
|
|
POBJECT_HEADER ObjectHeader;
|
|
ACCESS_MASK GrantedAccess;
|
|
ULONG Attributes;
|
|
PEPROCESS CurrentProcess;
|
|
PVOID HandleTable;
|
|
PETHREAD CurrentThread;
|
|
NTSTATUS Status;
|
|
PAGED_CODE();
|
|
|
|
/* Assume failure */
|
|
*Object = NULL;
|
|
|
|
/* Check if this is a special handle */
|
|
if (HandleToLong(Handle) < 0)
|
|
{
|
|
/* Check if this is the current process */
|
|
if (Handle == NtCurrentProcess())
|
|
{
|
|
/* Check if this is the right object type */
|
|
if ((ObjectType == PsProcessType) || !(ObjectType))
|
|
{
|
|
/* Get the current process and granted access */
|
|
CurrentProcess = PsGetCurrentProcess();
|
|
GrantedAccess = CurrentProcess->GrantedAccess;
|
|
|
|
/* Validate access */
|
|
/* ~GrantedAccess = RefusedAccess.*/
|
|
/* ~GrantedAccess & DesiredAccess = list of refused bits. */
|
|
/* !(~GrantedAccess & DesiredAccess) == TRUE means ALL requested rights are granted */
|
|
if ((AccessMode == KernelMode) ||
|
|
!(~GrantedAccess & DesiredAccess))
|
|
{
|
|
/* Check if the caller wanted handle information */
|
|
if (HandleInformation)
|
|
{
|
|
/* Return it */
|
|
HandleInformation->HandleAttributes = 0;
|
|
HandleInformation->GrantedAccess = GrantedAccess;
|
|
}
|
|
|
|
/* Reference ourselves */
|
|
ObjectHeader = OBJECT_TO_OBJECT_HEADER(CurrentProcess);
|
|
InterlockedExchangeAdd(&ObjectHeader->PointerCount, 1);
|
|
|
|
/* Return the pointer */
|
|
*Object = CurrentProcess;
|
|
ASSERT(*Object != NULL);
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
/* Access denied */
|
|
Status = STATUS_ACCESS_DENIED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* The caller used this special handle value with a non-process type */
|
|
Status = STATUS_OBJECT_TYPE_MISMATCH;
|
|
}
|
|
|
|
/* Return the status */
|
|
return Status;
|
|
}
|
|
else if (Handle == NtCurrentThread())
|
|
{
|
|
/* Check if this is the right object type */
|
|
if ((ObjectType == PsThreadType) || !(ObjectType))
|
|
{
|
|
/* Get the current process and granted access */
|
|
CurrentThread = PsGetCurrentThread();
|
|
GrantedAccess = CurrentThread->GrantedAccess;
|
|
|
|
/* Validate access */
|
|
/* ~GrantedAccess = RefusedAccess.*/
|
|
/* ~GrantedAccess & DesiredAccess = list of refused bits. */
|
|
/* !(~GrantedAccess & DesiredAccess) == TRUE means ALL requested rights are granted */
|
|
if ((AccessMode == KernelMode) ||
|
|
!(~GrantedAccess & DesiredAccess))
|
|
{
|
|
/* Check if the caller wanted handle information */
|
|
if (HandleInformation)
|
|
{
|
|
/* Return it */
|
|
HandleInformation->HandleAttributes = 0;
|
|
HandleInformation->GrantedAccess = GrantedAccess;
|
|
}
|
|
|
|
/* Reference ourselves */
|
|
ObjectHeader = OBJECT_TO_OBJECT_HEADER(CurrentThread);
|
|
InterlockedExchangeAdd(&ObjectHeader->PointerCount, 1);
|
|
|
|
/* Return the pointer */
|
|
*Object = CurrentThread;
|
|
ASSERT(*Object != NULL);
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
/* Access denied */
|
|
Status = STATUS_ACCESS_DENIED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* The caller used this special handle value with a non-process type */
|
|
Status = STATUS_OBJECT_TYPE_MISMATCH;
|
|
}
|
|
|
|
/* Return the status */
|
|
return Status;
|
|
}
|
|
else if (AccessMode == KernelMode)
|
|
{
|
|
/* Use the kernel handle table and get the actual handle value */
|
|
Handle = ObKernelHandleToHandle(Handle);
|
|
HandleTable = ObpKernelHandleTable;
|
|
}
|
|
else
|
|
{
|
|
/* Invalid access, fail */
|
|
return STATUS_INVALID_HANDLE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise use this process's handle table */
|
|
HandleTable = PsGetCurrentProcess()->ObjectTable;
|
|
}
|
|
|
|
/* Enter a critical region while we touch the handle table */
|
|
ASSERT(HandleTable != NULL);
|
|
KeEnterCriticalRegion();
|
|
|
|
/* Get the handle entry */
|
|
HandleEntry = ExMapHandleToPointer(HandleTable, Handle);
|
|
if (HandleEntry)
|
|
{
|
|
/* Get the object header and validate the type*/
|
|
ObjectHeader = ObpGetHandleObject(HandleEntry);
|
|
if (!(ObjectType) || (ObjectType == ObjectHeader->Type))
|
|
{
|
|
/* Get the granted access and validate it */
|
|
GrantedAccess = HandleEntry->GrantedAccess;
|
|
|
|
/* Validate access */
|
|
/* ~GrantedAccess = RefusedAccess.*/
|
|
/* ~GrantedAccess & DesiredAccess = list of refused bits. */
|
|
/* !(~GrantedAccess & DesiredAccess) == TRUE means ALL requested rights are granted */
|
|
if ((AccessMode == KernelMode) ||
|
|
!(~GrantedAccess & DesiredAccess))
|
|
{
|
|
/* Reference the object directly since we have its header */
|
|
InterlockedIncrement(&ObjectHeader->PointerCount);
|
|
|
|
/* Mask out the internal attributes */
|
|
Attributes = HandleEntry->ObAttributes & OBJ_HANDLE_ATTRIBUTES;
|
|
|
|
/* Check if the caller wants handle information */
|
|
if (HandleInformation)
|
|
{
|
|
/* Fill out the information */
|
|
HandleInformation->HandleAttributes = Attributes;
|
|
HandleInformation->GrantedAccess = GrantedAccess;
|
|
}
|
|
|
|
/* Return the pointer */
|
|
*Object = &ObjectHeader->Body;
|
|
|
|
/* Unlock the handle */
|
|
ExUnlockHandleTableEntry(HandleTable, HandleEntry);
|
|
KeLeaveCriticalRegion();
|
|
|
|
/* Return success */
|
|
ASSERT(*Object != NULL);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
/* Requested access failed */
|
|
DPRINT("Rights not granted: %x\n", ~GrantedAccess & DesiredAccess);
|
|
Status = STATUS_ACCESS_DENIED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Invalid object type */
|
|
Status = STATUS_OBJECT_TYPE_MISMATCH;
|
|
}
|
|
|
|
/* Unlock the entry */
|
|
ExUnlockHandleTableEntry(HandleTable, HandleEntry);
|
|
}
|
|
else
|
|
{
|
|
/* Invalid handle */
|
|
Status = STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
/* Return failure status */
|
|
KeLeaveCriticalRegion();
|
|
*Object = NULL;
|
|
return Status;
|
|
}
|
|
|
|
/* EOF */
|