123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909 |
- /*++
- Copyright (c) 2013 Minoca Corp.
- This file is licensed under the terms of the GNU General Public License
- version 3. Alternative licensing terms are available. Contact
- info@minocacorp.com for details. See the LICENSE file at the root of this
- project for complete licensing information.
- Module Name:
- handles.c
- Abstract:
- This module implements support for handles and handle tables.
- Author:
- Evan Green 16-Feb-2013
- Environment:
- Kernel
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include <minoca/kernel/kernel.h>
- //
- // ---------------------------------------------------------------- Definitions
- //
- #define HANDLE_TABLE_ALLOCATION_TAG 0x646E6148 // 'dnaH'
- //
- // Define the initial size of the handle table, in entries.
- //
- #define HANDLE_TABLE_INITIAL_SIZE 16
- //
- // Define handle flags.
- //
- //
- // This flag is set when a handle table entry is allocated.
- //
- #define HANDLE_FLAG_ALLOCATED 0x80000000
- //
- // --------------------------------------------------------------------- Macros
- //
- //
- // These macros acquire and release the handle table locks if they exist.
- //
- #define OB_ACQUIRE_HANDLE_TABLE_LOCK(_Table) \
- if ((_Table)->Lock != NULL) { \
- KeAcquireQueuedLock((_Table)->Lock); \
- }
- #define OB_RELEASE_HANDLE_TABLE_LOCK(_Table) \
- if ((_Table)->Lock != NULL) { \
- KeReleaseQueuedLock((_Table)->Lock); \
- }
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- /*++
- Structure Description:
- This structure defines a handle table entry.
- Members:
- Flags - Stores a bitfield of flags associated with this handle. Most of
- these flags are available for the user. A couple of the high ones are
- reserved.
- HandleValue - Stores the actual value of the handle.
- --*/
- typedef struct _HANDLE_TABLE_ENTRY {
- ULONG Flags;
- PVOID HandleValue;
- } HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;
- /*++
- Structure Description:
- This structure defines a handle table.
- Members:
- Process - Stores a pointer to the process that owns the handle table.
- NextDescriptor - Stores the first free descriptor number.
- MaxDescriptor - Stores the maximum valid descriptor number.
- Entries - Stores the actual array of handles.
- ArraySize - Stores the number of elements in the array.
- Lock - Stores a pointer to a lock protecting access to the handle table.
- LookupCallback - Stores an optional pointer to a routine that is called
- whenever a handle is looked up.
- --*/
- struct _HANDLE_TABLE {
- PKPROCESS Process;
- ULONG NextDescriptor;
- ULONG MaxDescriptor;
- PHANDLE_TABLE_ENTRY Entries;
- ULONG ArraySize;
- PQUEUED_LOCK Lock;
- PHANDLE_TABLE_LOOKUP_CALLBACK LookupCallback;
- };
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- KSTATUS
- ObpExpandHandleTable (
- PHANDLE_TABLE Table,
- ULONG Descriptor
- );
- //
- // -------------------------------------------------------------------- Globals
- //
- //
- // ------------------------------------------------------------------ Functions
- //
- PHANDLE_TABLE
- ObCreateHandleTable (
- PVOID Process,
- PHANDLE_TABLE_LOOKUP_CALLBACK LookupCallbackRoutine
- )
- /*++
- Routine Description:
- This routine creates a new handle table. This routine must be called at low
- level.
- Arguments:
- Process - Supplies an optional pointer to the process that owns the handle
- table. When in doubt, supply NULL.
- LookupCallbackRoutine - Supplies an optional pointer that if supplied
- points to a function that will get called whenever a handle value is
- looked up (but not on iterates).
- Return Value:
- Returns a pointer to the new handle table on success.
- NULL on insufficient resource conditions.
- --*/
- {
- UINTN AllocationSize;
- PHANDLE_TABLE HandleTable;
- KSTATUS Status;
- ASSERT(KeGetRunLevel() == RunLevelLow);
- HandleTable = MmAllocatePagedPool(sizeof(HANDLE_TABLE),
- HANDLE_TABLE_ALLOCATION_TAG);
- if (HandleTable == NULL) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto CreateHandleTableEnd;
- }
- if (Process != NULL) {
- ObAddReference(Process);
- }
- HandleTable->Process = Process;
- HandleTable->Lock = NULL;
- HandleTable->NextDescriptor = 0;
- HandleTable->MaxDescriptor = 0;
- HandleTable->LookupCallback = LookupCallbackRoutine;
- AllocationSize = HANDLE_TABLE_INITIAL_SIZE * sizeof(HANDLE_TABLE_ENTRY);
- HandleTable->Entries = MmAllocatePagedPool(AllocationSize,
- HANDLE_TABLE_ALLOCATION_TAG);
- if (HandleTable->Entries == NULL) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto CreateHandleTableEnd;
- }
- RtlZeroMemory(HandleTable->Entries, AllocationSize);
- HandleTable->ArraySize = HANDLE_TABLE_INITIAL_SIZE;
- Status = STATUS_SUCCESS;
- CreateHandleTableEnd:
- if (!KSUCCESS(Status)) {
- if (HandleTable != NULL) {
- if (HandleTable->Entries != NULL) {
- MmFreePagedPool(HandleTable->Entries);
- }
- MmFreePagedPool(HandleTable);
- HandleTable = NULL;
- }
- }
- return HandleTable;
- }
- VOID
- ObDestroyHandleTable (
- PHANDLE_TABLE HandleTable
- )
- /*++
- Routine Description:
- This routine destroys a handle table. This routine must be called at low
- level.
- Arguments:
- HandleTable - Supplies a pointer to the handle table to destroy.
- Return Value:
- None.
- --*/
- {
- ASSERT(KeGetRunLevel() == RunLevelLow);
- if (HandleTable->Lock != NULL) {
- KeDestroyQueuedLock(HandleTable->Lock);
- }
- if (HandleTable->Entries != NULL) {
- MmFreePagedPool(HandleTable->Entries);
- }
- if (HandleTable->Process != NULL) {
- ObReleaseReference(HandleTable->Process);
- }
- MmFreePagedPool(HandleTable);
- return;
- }
- KSTATUS
- ObEnableHandleTableLocking (
- PHANDLE_TABLE HandleTable
- )
- /*++
- Routine Description:
- This routine enables locking on the given handle table.
- Arguments:
- HandleTable - Supplies a pointer to the handle table to enable locking for.
- Return Value:
- Status code.
- --*/
- {
- if (HandleTable->Lock == NULL) {
- HandleTable->Lock = KeCreateQueuedLock();
- if (HandleTable->Lock == NULL) {
- return STATUS_INSUFFICIENT_RESOURCES;
- }
- }
- return STATUS_SUCCESS;
- }
- KSTATUS
- ObCreateHandle (
- PHANDLE_TABLE Table,
- PVOID HandleValue,
- ULONG Flags,
- PHANDLE NewHandle
- )
- /*++
- Routine Description:
- This routine creates a new handle table entry. This routine must be called
- at low level.
- Arguments:
- Table - Supplies a pointer to the handle table.
- HandleValue - Supplies the value to be associated with the handle.
- Flags - Supplies a bitfield of flags to set with the handle. This value
- will be ANDed with HANDLE_FLAG_MASK, so bits set outside of that range
- will not stick.
- NewHandle - Supplies a pointer where the handle will be returned. On input,
- contains the minimum required value for the handle. Supply
- INVALID_HANDLE as the initial contents to let the system decide (which
- should be almost always).
- Return Value:
- STATUS_SUCCESS on success.
- STATUS_INSUFFICIENT_RESOURCES if memory could not be allocated for the
- handle table entry.
- STATUS_TOO_MANY_HANDLES if the given minimum handle value was too high.
- --*/
- {
- ULONG Descriptor;
- KSTATUS Status;
- ASSERT(KeGetRunLevel() == RunLevelLow);
- ASSERT((Table->Process == NULL) ||
- (Table->Process->ThreadCount == 0) ||
- (Table->Process == PsGetCurrentProcess()));
- OB_ACQUIRE_HANDLE_TABLE_LOCK(Table);
- //
- // Either use the next free slot, or try to use a descriptor at least as
- // high as the given handle.
- //
- if (*NewHandle == INVALID_HANDLE) {
- Descriptor = Table->NextDescriptor;
- } else {
- Descriptor = (UINTN)(*NewHandle);
- }
- //
- // Loop until a free slot is found.
- //
- while ((Descriptor < Table->ArraySize) &&
- ((Table->Entries[Descriptor].Flags & HANDLE_FLAG_ALLOCATED) != 0)) {
- Descriptor += 1;
- }
- if (*NewHandle == INVALID_HANDLE) {
- Table->NextDescriptor = Descriptor + 1;
- }
- //
- // Expand the table if needed.
- //
- if (Descriptor >= Table->ArraySize) {
- Status = ObpExpandHandleTable(Table, Descriptor);
- if (!KSUCCESS(Status)) {
- goto CreateHandleEnd;
- }
- }
- ASSERT(HandleValue != NULL);
- Table->Entries[Descriptor].Flags = HANDLE_FLAG_ALLOCATED |
- (Flags & HANDLE_FLAG_MASK);
- Table->Entries[Descriptor].HandleValue = HandleValue;
- *NewHandle = (HANDLE)(UINTN)Descriptor;
- if (Descriptor > Table->MaxDescriptor) {
- Table->MaxDescriptor = Descriptor;
- }
- Status = STATUS_SUCCESS;
- CreateHandleEnd:
- OB_RELEASE_HANDLE_TABLE_LOCK(Table);
- return Status;
- }
- VOID
- ObDestroyHandle (
- PHANDLE_TABLE Table,
- HANDLE Handle
- )
- /*++
- Routine Description:
- This routine destroys a handle.
- Arguments:
- Table - Supplies a pointer to the handle table.
- Handle - Supplies the handle returned when the handle was created.
- Return Value:
- None.
- --*/
- {
- ULONG Descriptor;
- ASSERT((Table->Process == NULL) ||
- (Table->Process->ThreadCount == 0) ||
- (Table->Process == PsGetCurrentProcess()));
- Descriptor = (UINTN)Handle;
- OB_ACQUIRE_HANDLE_TABLE_LOCK(Table);
- if (Descriptor >= Table->ArraySize) {
- goto DestroyHandleEnd;
- }
- if ((Table->Entries[Descriptor].Flags & HANDLE_FLAG_ALLOCATED) == 0) {
- goto DestroyHandleEnd;
- }
- Table->Entries[Descriptor].HandleValue = NULL;
- Table->Entries[Descriptor].Flags = 0;
- if (Table->NextDescriptor > Descriptor) {
- Table->NextDescriptor = Descriptor;
- }
- DestroyHandleEnd:
- OB_RELEASE_HANDLE_TABLE_LOCK(Table);
- return;
- }
- KSTATUS
- ObReplaceHandleValue (
- PHANDLE_TABLE Table,
- HANDLE Handle,
- PVOID NewHandleValue,
- ULONG NewFlags,
- PVOID *OldHandleValue,
- PULONG OldFlags
- )
- /*++
- Routine Description:
- This routine replaces a handle table entry, or creates a handle if none was
- there before. This routine must be called at low level.
- Arguments:
- Table - Supplies a pointer to the handle table.
- Handle - Supplies the handle to replace or create.
- NewHandleValue - Supplies the value to be associated with the handle.
- NewFlags - Supplies the new handle flags to set.
- OldHandleValue - Supplies an optional pointer where the original handle
- value will be returned.
- OldFlags - Supplies an optional pointer where the original handle flags
- will be returned.
- Return Value:
- STATUS_SUCCESS on success.
- STATUS_INSUFFICIENT_RESOURCES if memory could not be allocated for the
- handle table entry.
- STATUS_TOO_MANY_HANDLES if the given minimum handle value was too high.
- --*/
- {
- ULONG Descriptor;
- KSTATUS Status;
- ASSERT(KeGetRunLevel() == RunLevelLow);
- ASSERT((Table->Process == NULL) ||
- (Table->Process->ThreadCount == 0) ||
- (Table->Process == PsGetCurrentProcess()));
- OB_ACQUIRE_HANDLE_TABLE_LOCK(Table);
- ASSERT(Handle != INVALID_HANDLE);
- Descriptor = (UINTN)Handle;
- if (Descriptor >= Table->ArraySize) {
- Status = ObpExpandHandleTable(Table, Descriptor);
- if (!KSUCCESS(Status)) {
- goto ReplaceHandleValueEnd;
- }
- }
- ASSERT(NewHandleValue != NULL);
- if (OldFlags != NULL) {
- *OldFlags = Table->Entries[Descriptor].Flags & HANDLE_FLAG_MASK;
- }
- if (OldHandleValue != NULL) {
- *OldHandleValue = Table->Entries[Descriptor].HandleValue;
- }
- Table->Entries[Descriptor].Flags = HANDLE_FLAG_ALLOCATED |
- (NewFlags & HANDLE_FLAG_MASK);
- Table->Entries[Descriptor].HandleValue = NewHandleValue;
- if (Descriptor > Table->MaxDescriptor) {
- Table->MaxDescriptor = Descriptor;
- }
- Status = STATUS_SUCCESS;
- ReplaceHandleValueEnd:
- OB_RELEASE_HANDLE_TABLE_LOCK(Table);
- return Status;
- }
- PVOID
- ObGetHandleValue (
- PHANDLE_TABLE Table,
- HANDLE Handle,
- PULONG Flags
- )
- /*++
- Routine Description:
- This routine looks up the given handle and returns the value associated
- with that handle.
- Arguments:
- Table - Supplies a pointer to the handle table.
- Handle - Supplies the handle returned when the handle was created.
- Flags - Supplies an optional pointer that receives value of the handle's
- flags.
- Return Value:
- Returns the value associated with that handle upon success.
- NULL if the given handle is invalid.
- --*/
- {
- ULONG Descriptor;
- ULONG LocalFlags;
- PVOID Value;
- ASSERT((Table->Process == NULL) ||
- (Table->Process->ThreadCount == 0) ||
- (Table->Process == PsGetCurrentProcess()));
- Descriptor = (UINTN)Handle;
- LocalFlags = 0;
- Value = NULL;
- OB_ACQUIRE_HANDLE_TABLE_LOCK(Table);
- if (Descriptor >= Table->ArraySize) {
- goto GetHandleValueEnd;
- }
- LocalFlags = Table->Entries[Descriptor].Flags;
- if ((LocalFlags & HANDLE_FLAG_ALLOCATED) == 0) {
- goto GetHandleValueEnd;
- }
- Value = Table->Entries[Descriptor].HandleValue;
- if (Table->LookupCallback != NULL) {
- Table->LookupCallback(Table, (HANDLE)(UINTN)Descriptor, Value);
- }
- GetHandleValueEnd:
- OB_RELEASE_HANDLE_TABLE_LOCK(Table);
- if ((Flags != NULL) && (Value != NULL)) {
- *Flags = LocalFlags & HANDLE_FLAG_MASK;
- }
- return Value;
- }
- KSTATUS
- ObGetSetHandleFlags (
- PHANDLE_TABLE Table,
- HANDLE Handle,
- BOOL Set,
- PULONG Flags
- )
- /*++
- Routine Description:
- This routine sets and/or returns the flags associated with a handle. The
- lookup callback routine initialized with the handle table is not called
- during this operation.
- Arguments:
- Table - Supplies a pointer to the handle table.
- Handle - Supplies the handle whose flags should be retrieved.
- Set - Supplies a boolean indicating if the value in the flags parameter
- should be set as the new value.
- Flags - Supplies a pointer that on input contains the value of the flags
- to set if the set parameter is TRUE. This value will be ANDed with
- HANDLE_FLAG_MASK, so bits set outside of that mask will not stick.
- On output, contains the original value of the flags before the set was
- performed.
- Return Value:
- STATUS_SUCCESS on success.
- STATUS_INVALID_HANDLE if no such handle could be found.
- --*/
- {
- ULONG Descriptor;
- ULONG NewValue;
- ULONG OriginalValue;
- KSTATUS Status;
- ASSERT((Table->Process == NULL) ||
- (Table->Process->ThreadCount == 0) ||
- (Table->Process == PsGetCurrentProcess()));
- Status = STATUS_INVALID_HANDLE;
- Descriptor = (UINTN)Handle;
- OB_ACQUIRE_HANDLE_TABLE_LOCK(Table);
- if (Descriptor >= Table->ArraySize) {
- goto GetSetHandleFlagsEnd;
- }
- if ((Table->Entries[Descriptor].Flags & HANDLE_FLAG_ALLOCATED) == 0) {
- goto GetSetHandleFlagsEnd;
- }
- Status = STATUS_SUCCESS;
- NewValue = *Flags;
- OriginalValue = Table->Entries[Descriptor].Flags;
- *Flags = OriginalValue & HANDLE_FLAG_MASK;
- if (Set != FALSE) {
- Table->Entries[Descriptor].Flags = (NewValue & HANDLE_FLAG_MASK) |
- (OriginalValue & ~HANDLE_FLAG_MASK);
- }
- GetSetHandleFlagsEnd:
- OB_RELEASE_HANDLE_TABLE_LOCK(Table);
- return Status;
- }
- HANDLE
- ObGetHighestHandle (
- PHANDLE_TABLE Table
- )
- /*++
- Routine Description:
- This routine returns the highest allocated handle.
- Arguments:
- Table - Supplies a pointer to the handle table.
- Return Value:
- Returns the highest handle number (not the handle value).
- INVALID_HANDLE if the table is empty.
- --*/
- {
- ULONG Descriptor;
- HANDLE Handle;
- ASSERT((Table->Process == NULL) ||
- (Table->Process->ThreadCount == 0) ||
- (Table->Process == PsGetCurrentProcess()));
- Handle = INVALID_HANDLE;
- OB_ACQUIRE_HANDLE_TABLE_LOCK(Table);
- Descriptor = Table->MaxDescriptor;
- ASSERT(Descriptor < Table->ArraySize);
- while ((Table->Entries[Descriptor].Flags & HANDLE_FLAG_ALLOCATED) == 0) {
- if (Descriptor == 0) {
- break;
- }
- Descriptor -= 1;
- }
- if ((Table->Entries[Descriptor].Flags & HANDLE_FLAG_ALLOCATED) != 0) {
- Handle = (HANDLE)(UINTN)Descriptor;
- }
- Table->MaxDescriptor = Descriptor;
- OB_RELEASE_HANDLE_TABLE_LOCK(Table);
- return Handle;
- }
- VOID
- ObHandleTableIterate (
- PHANDLE_TABLE Table,
- PHANDLE_TABLE_ITERATE_ROUTINE IterateRoutine,
- PVOID IterateRoutineContext
- )
- /*++
- Routine Description:
- This routine iterates through all handles in the given handle table, and
- calls the given handle table for each one. The table will be locked when the
- iterate routine is called, so the iterate routine must not make any calls
- that would require use of the handle table.
- Arguments:
- Table - Supplies a pointer to the handle table to iterate through.
- IterateRoutine - Supplies a pointer to the routine to be called for each
- handle in the table.
- IterateRoutineContext - Supplies an opaque context pointer that will get
- passed to the iterate routine each time it is called.
- Return Value:
- None.
- --*/
- {
- ULONG Descriptor;
- ASSERT((Table->Process == NULL) ||
- (Table->Process->ThreadCount == 0) ||
- (Table->Process == PsGetCurrentProcess()));
- OB_ACQUIRE_HANDLE_TABLE_LOCK(Table);
- for (Descriptor = 0; Descriptor <= Table->MaxDescriptor; Descriptor += 1) {
- if ((Table->Entries[Descriptor].Flags & HANDLE_FLAG_ALLOCATED) == 0) {
- continue;
- }
- IterateRoutine(Table,
- (HANDLE)(UINTN)Descriptor,
- Table->Entries[Descriptor].Flags & HANDLE_FLAG_MASK,
- Table->Entries[Descriptor].HandleValue,
- IterateRoutineContext);
- }
- OB_RELEASE_HANDLE_TABLE_LOCK(Table);
- return;
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
- KSTATUS
- ObpExpandHandleTable (
- PHANDLE_TABLE Table,
- ULONG Descriptor
- )
- /*++
- Routine Description:
- This routine expands the given handle table to support a given number of
- descriptors.
- Arguments:
- Table - Supplies a pointer to the handle table to expand.
- Descriptor - Supplies the descriptor that will need to be inserted in the
- handle table.
- Return Value:
- Status code.
- --*/
- {
- UINTN AllocationSize;
- PVOID NewBuffer;
- UINTN NewCapacity;
- KSTATUS Status;
- if (Descriptor >= OB_MAX_HANDLES) {
- Status = STATUS_INVALID_HANDLE;
- goto ExpandHandleTableEnd;
- }
- //
- // Expand the table if needed.
- //
- if (Descriptor >= Table->ArraySize) {
- NewCapacity = Table->ArraySize * 2;
- while ((NewCapacity <= Descriptor) &&
- (NewCapacity >= Table->ArraySize)) {
- NewCapacity *= 2;
- }
- AllocationSize = NewCapacity * sizeof(HANDLE_TABLE_ENTRY);
- if ((NewCapacity <= Descriptor) ||
- (NewCapacity < Table->ArraySize) ||
- ((AllocationSize / sizeof(HANDLE_TABLE_ENTRY)) != NewCapacity)) {
- Status = STATUS_TOO_MANY_HANDLES;
- goto ExpandHandleTableEnd;
- }
- ASSERT((NewCapacity > Table->ArraySize) &&
- (NewCapacity > Table->NextDescriptor));
- NewBuffer = MmAllocatePagedPool(AllocationSize,
- HANDLE_TABLE_ALLOCATION_TAG);
- if (NewBuffer == NULL) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto ExpandHandleTableEnd;
- }
- RtlCopyMemory(NewBuffer,
- Table->Entries,
- Table->ArraySize * sizeof(HANDLE_TABLE_ENTRY));
- RtlZeroMemory(
- NewBuffer + (Table->ArraySize * sizeof(HANDLE_TABLE_ENTRY)),
- (NewCapacity - Table->ArraySize) * sizeof(HANDLE_TABLE_ENTRY));
- MmFreePagedPool(Table->Entries);
- Table->Entries = NewBuffer;
- Table->ArraySize = NewCapacity;
- }
- Status = STATUS_SUCCESS;
- ExpandHandleTableEnd:
- return Status;
- }
|