reactos/reactos/ntoskrnl/ex/resource.c
Eric Kohl 0aac416a18 Update after server failure.
Fixed STDCALL issues in Ex functions.

svn path=/trunk/; revision=1177
2000-06-07 13:05:09 +00:00

779 lines
21 KiB
C

/* $Id: resource.c,v 1.12 2000/06/07 13:04:34 ekohl Exp $
*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS kernel
* FILE: ntoskrnl/ex/resource.c
* PURPOSE: Resource synchronization construct
* PROGRAMMER: Unknown
* UPDATE HISTORY:
* Created 22/05/98
*/
/*
* Usage of ERESOURCE members is not documented.
* From names of members and functionnalities, we can assume :
*
* OwnerTable = list of threads who have shared access(if more than one)
* ActiveCount = number of threads who have access to the resource
* Flag = bits : ResourceOwnedExclusive=0x80
* ResourceNeverExclusive=0x10
* ResourceReleaseByOtherThread=0x20
* SharedWaiters = semaphore, used to manage wait list of shared waiters.
* ExclusiveWaiters = event, used to manage wait list of exclusive waiters.
* OwnerThreads[0]= thread who have exclusive access
* OwnerThreads[1]= if only one thread own the resource
* thread who have shared access
* else
* OwnerThread=0
* and TableSize = number of entries in the owner table
* NumberOfExclusiveWaiters = number of threads waiting for exclusive access.
* NumberOfSharedWaiters = number of threads waiting for exclusive access.
*
*/
#define ResourceOwnedExclusive 0x80
/* INCLUDES *****************************************************************/
#include <ddk/ntddk.h>
#include <internal/ke.h>
#include <stddef.h>
#include <internal/string.h>
#define NDEBUG
#include <internal/debug.h>
/* FUNCTIONS *****************************************************************/
BOOLEAN
STDCALL
ExTryToAcquireResourceExclusiveLite (
PERESOURCE Resource
)
/*
* FUNCTION: Attempts to require the resource for exclusive access
* ARGUMENTS:
* Resource = Points to the resource of be acquired
* RETURNS: TRUE if the resource was acquired for the caller
* NOTES: Must be acquired at IRQL < DISPATCH_LEVEL
*/
{
return(ExAcquireResourceExclusiveLite(Resource,FALSE));
}
BOOLEAN
STDCALL
ExAcquireResourceExclusive (
PERESOURCE Resource,
BOOLEAN Wait
)
{
return(ExAcquireResourceExclusiveLite(Resource,Wait));
}
BOOLEAN
STDCALL
ExAcquireResourceExclusiveLite (
PERESOURCE Resource,
BOOLEAN Wait
)
/*
* FUNCTION: Acquires a resource exclusively for the calling thread
* ARGUMENTS:
* Resource = Points to the resource to acquire
* Wait = Is set to TRUE if the caller should wait to acquire the
* resource if it can't be acquired immediately
* RETURNS: TRUE if the resource was acquired,
* FALSE otherwise
* NOTES: Must be called at IRQL < DISPATCH_LEVEL
*/
{
KIRQL oldIrql;
DPRINT("ExAcquireResourceExclusiveLite(Resource %x, Wait %d)\n",
Resource, Wait);
KeAcquireSpinLock(&Resource->SpinLock, &oldIrql);
/* resource already locked */
if((Resource->Flag & ResourceOwnedExclusive)
&& Resource->OwnerThreads[0].OwnerThread == ExGetCurrentResourceThread())
{
/* it's ok : same lock for same thread */
Resource->OwnerThreads[0].a.OwnerCount++;
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireResourceExclusiveLite() = TRUE\n");
return(TRUE);
}
if (Resource->ActiveCount && !Wait)
{
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireResourceExclusiveLite() = FALSE\n");
return(FALSE);
}
/*
* This is slightly better than it looks because other exclusive
* threads who are waiting won't be woken up but there is a race
* with new threads trying to grab the resource so we must have
* the spinlock, still normally this loop will only be executed
* once
* NOTE: We might want to set a timeout to detect deadlock
* (10 minutes?)
*/
while (Resource->ActiveCount)
{
Resource->NumberOfExclusiveWaiters++;
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
KeWaitForSingleObject(Resource->ExclusiveWaiters,
Executive,
KernelMode,
FALSE,
NULL);
KeAcquireSpinLock(&Resource->SpinLock, &oldIrql);
Resource->NumberOfExclusiveWaiters--;
}
Resource->Flag |= ResourceOwnedExclusive;
Resource->ActiveCount = 1;
Resource->OwnerThreads[0].OwnerThread = ExGetCurrentResourceThread();
Resource->OwnerThreads[0].a.OwnerCount = 1;
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireResourceExclusiveLite() = TRUE\n");
return(TRUE);
}
static BOOLEAN EiRemoveSharedOwner(PERESOURCE Resource,
ERESOURCE_THREAD ResourceThreadId)
/*
* FUNCTION: Removes the current thread from the shared owners of the resource
* ARGUMENTS:
* Resource = Pointer to the resource for which the thread is to be
* added
* NOTE: Must be called with the resource spinlock held
*/
{
ULONG i;
if (Resource->OwnerThreads[1].OwnerThread == ResourceThreadId)
{
Resource->OwnerThreads[1].a.OwnerCount--;
Resource->ActiveCount--;
if (Resource->OwnerThreads[1].a.OwnerCount == 0)
{
Resource->OwnerThreads[1].OwnerThread = 0;
}
return(TRUE);
}
if (Resource->OwnerThreads[1].OwnerThread)
{
/* Oh dear, the caller didn't own the resource after all */
return(FALSE);;
}
for (i=0; i<Resource->OwnerThreads[1].a.TableSize; i++)
{
if (Resource->OwnerTable[i].OwnerThread == ResourceThreadId)
{
Resource->OwnerTable[1].a.OwnerCount--;
Resource->ActiveCount--;
if (Resource->OwnerTable[1].a.OwnerCount == 0)
{
Resource->OwnerTable[i].OwnerThread = 0;
}
}
return(TRUE);
}
return(FALSE);
}
static BOOLEAN EiAddSharedOwner(PERESOURCE Resource)
/*
* FUNCTION: Adds the current thread to the shared owners of the resource
* ARGUMENTS:
* Resource = Pointer to the resource for which the thread is to be
* added
* NOTE: Must be called with the resource spinlock held
*/
{
ERESOURCE_THREAD CurrentThread = ExGetCurrentResourceThread();
POWNER_ENTRY freeEntry;
ULONG i = 0;
DPRINT("EiAddSharedOwner(Resource %x)\n", Resource);
if (Resource->ActiveCount == 0)
{
/* no owner, it's easy */
Resource->OwnerThreads[1].OwnerThread = ExGetCurrentResourceThread();
Resource->OwnerThreads[1].a.OwnerCount = 1;
if (Resource->OwnerTable != NULL)
{
ExFreePool(Resource->OwnerTable);
}
Resource->OwnerTable = NULL;
Resource->ActiveCount = 1;
DPRINT("EiAddSharedOwner() = TRUE\n");
return(TRUE);
}
/*
* now, we must search if this thread has already acquired this resource
* then increase ownercount if found, else create new entry or reuse free
* entry
*/
if (Resource->OwnerTable == NULL)
{
DPRINT("Creating owner table\n");
/* allocate ownertable,memset to 0, initialize first entry */
Resource->OwnerTable = ExAllocatePool(NonPagedPool,
sizeof(OWNER_ENTRY)*3);
if (Resource->OwnerTable == NULL)
{
KeBugCheck(0);
return(FALSE);
}
memset(Resource->OwnerTable,sizeof(OWNER_ENTRY)*3,0);
memcpy(&Resource->OwnerTable[0], &Resource->OwnerThreads[1],
sizeof(OWNER_ENTRY));
Resource->OwnerThreads[1].OwnerThread = 0;
Resource->OwnerThreads[1].a.TableSize = 3;
Resource->OwnerTable[1].OwnerThread = CurrentThread;
Resource->OwnerTable[1].a.OwnerCount = 1;
return(TRUE);
}
DPRINT("Search free entries\n");
DPRINT("Number of entries %d\n",
Resource->OwnerThreads[1].a.TableSize);
freeEntry = NULL;
for (i=0; i<Resource->OwnerThreads[1].a.TableSize; i++)
{
if (Resource->OwnerTable[i].OwnerThread == CurrentThread)
{
DPRINT("Thread already owns resource\n");
Resource->OwnerTable[i].a.OwnerCount++;
return(TRUE);
}
if (Resource->OwnerTable[i].OwnerThread == 0)
{
freeEntry = &Resource->OwnerTable[i];
}
}
DPRINT("Found free entry %x\n", freeEntry);
if (!freeEntry)
{
DPRINT("Allocating new entry\n");
/* reallocate ownertable with one more entry */
freeEntry = ExAllocatePool(NonPagedPool,
sizeof(OWNER_ENTRY)*
(Resource->OwnerThreads[1].a.TableSize+1));
if (freeEntry == NULL)
{
KeBugCheck(0);
return(FALSE);
}
memcpy(freeEntry,Resource->OwnerTable,
sizeof(OWNER_ENTRY)*(Resource->OwnerThreads[1].a.TableSize));
ExFreePool(Resource->OwnerTable);
Resource->OwnerTable=freeEntry;
freeEntry=&Resource->OwnerTable[Resource->OwnerThreads[1].a.TableSize];
Resource->OwnerThreads[1].a.TableSize++;
}
DPRINT("Creating entry\n");
freeEntry->OwnerThread=ExGetCurrentResourceThread();
freeEntry->a.OwnerCount=1;
Resource->ActiveCount++;
return(TRUE);
}
BOOLEAN
STDCALL
ExAcquireResourceSharedLite (
PERESOURCE Resource,
BOOLEAN Wait
)
/*
* FUNCTION: Acquires the given resource for shared access by the calling
* thread
* ARGUMENTS:
* Resource = Points to the resource to acquire
* Wait = Is set to TRUE if the caller should be put into wait state
* until the resource can be acquired if it cannot be acquired
* immediately
* RETURNS: TRUE, if the resource is acquire
* FALSE otherwise
*/
{
KIRQL oldIrql;
DPRINT("ExAcquireResourceSharedLite(Resource %x, Wait %d)\n",
Resource, Wait);
KeAcquireSpinLock(&Resource->SpinLock, &oldIrql);
/* first, resolve trivial cases */
if (Resource->ActiveCount == 0)
{
EiAddSharedOwner(Resource);
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireResourceSharedLite() = TRUE\n");
return(TRUE);
}
if ((Resource->Flag & ResourceOwnedExclusive)
&& Resource->OwnerThreads[0].OwnerThread==ExGetCurrentResourceThread())
{
/* exclusive, but by same thread : it's ok */
/*
* NOTE: Is this correct? Seems the same as ExConvertExclusiveToShared
*/
Resource->OwnerThreads[0].a.OwnerCount++;
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireResourceSharedLite() = TRUE\n");
return(TRUE);
}
if ((Resource->Flag & ResourceOwnedExclusive)
|| Resource->NumberOfExclusiveWaiters)
{
/* exclusive by another thread , or thread waiting for exclusive */
if (!Wait)
{
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireResourceSharedLite() = FALSE\n");
return(FALSE);
}
else
{
Resource->NumberOfSharedWaiters++;
/* wait for the semaphore */
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
KeWaitForSingleObject(Resource->SharedWaiters,0,0,0,0);
KeAcquireSpinLock(&Resource->SpinLock, &oldIrql);
Resource->NumberOfSharedWaiters--;
}
}
EiAddSharedOwner(Resource);
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireResourceSharedLite() = TRUE\n");
return(TRUE);
}
VOID
STDCALL
ExConvertExclusiveToSharedLite (
PERESOURCE Resource
)
/*
* FUNCTION: Converts a given resource from acquired for exclusive access
* to acquire for shared access
* ARGUMENTS:
* Resource = Points to the resource for which the access should be
* converted
* NOTES: Caller must be running at IRQL < DISPATCH_LEVEL
*/
{
ULONG oldWaiters;
KIRQL oldIrql;
DPRINT("ExConvertExclusiveToSharedLite(Resource %x)\n", Resource);
KeAcquireSpinLock(&Resource->SpinLock, &oldIrql);
oldWaiters = Resource->NumberOfSharedWaiters;
if (!(Resource->Flag & ResourceOwnedExclusive))
{
/* Might not be what the caller expects, better bug check */
KeBugCheck(0);
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
return;
}
//transfer infos from entry 0 to entry 1 and erase entry 0
Resource->OwnerThreads[1].OwnerThread=Resource->OwnerThreads[0].OwnerThread;
Resource->OwnerThreads[1].a.OwnerCount=Resource->OwnerThreads[0].a.OwnerCount;
Resource->OwnerThreads[0].OwnerThread=0;
Resource->OwnerThreads[0].a.OwnerCount=0;
/* erase exclusive flag */
Resource->Flag &= (~ResourceOwnedExclusive);
/* if no shared waiters, that's all */
if (!oldWaiters)
{
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
return;
}
/* else, awake the waiters */
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
KeReleaseSemaphore(Resource->SharedWaiters,0,oldWaiters,0);
DPRINT("ExConvertExclusiveToSharedLite() finished\n");
}
ULONG
STDCALL
ExGetExclusiveWaiterCount (
PERESOURCE Resource
)
{
return(Resource->NumberOfExclusiveWaiters);
}
BOOLEAN
STDCALL
ExAcquireSharedStarveExclusive (
PERESOURCE Resource,
BOOLEAN Wait
)
/*
* FUNCTION: Acquires a given resource for shared access without waiting
* for any pending attempts to acquire exclusive access to the
* same resource
* ARGUMENTS:
* Resource = Points to the resource to be acquired for shared access
* Wait = Is set to TRUE if the caller will wait until the resource
* becomes available when access can't be granted immediately
* RETURNS: TRUE if the requested access is granted. The routine returns
* FALSE if the input Wait is FALSE and shared access can't be
* granted immediately
*/
{
KIRQL oldIrql;
DPRINT("ExAcquireSharedStarveExclusive(Resource %x, Wait %d)\n",
Resource, Wait);
KeAcquireSpinLock(&Resource->SpinLock, &oldIrql);
/* no owner, it's easy */
if (Resource->ActiveCount == 0)
{
Resource->OwnerThreads[1].OwnerThread=ExGetCurrentResourceThread();
Resource->OwnerThreads[1].a.OwnerCount=1;
Resource->ActiveCount=1;
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireSharedStarveExclusive() = TRUE\n");
return(TRUE);
}
if ((Resource->Flag & ResourceOwnedExclusive)
&& Resource->OwnerThreads[0].OwnerThread==ExGetCurrentResourceThread())
{
/* exclusive, but by same thread : it's ok */
Resource->OwnerThreads[0].a.OwnerCount++;
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireSharedStarveExclusive() = TRUE\n");
return(TRUE);
}
if (Resource->Flag & ResourceOwnedExclusive)
{
/* exclusive by another thread */
if (!Wait)
{
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireSharedStarveExclusive() = FALSE\n");
return(FALSE);
}
else
{
Resource->NumberOfSharedWaiters++;
/* wait for the semaphore */
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
KeWaitForSingleObject(Resource->SharedWaiters,0,0,0,0);
KeAcquireSpinLock(&Resource->SpinLock, &oldIrql);
Resource->NumberOfSharedWaiters--;
}
}
EiAddSharedOwner(Resource);
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExAcquireSharedStarveExclusive() = TRUE\n");
return(TRUE);
}
BOOLEAN
STDCALL
ExAcquireSharedWaitForExclusive (
PERESOURCE Resource,
BOOLEAN Wait
)
{
return(ExAcquireResourceSharedLite(Resource,Wait));
}
NTSTATUS
STDCALL
ExDeleteResource (
PERESOURCE Resource
)
{
return(ExDeleteResourceLite(Resource));
}
NTSTATUS
STDCALL
ExDeleteResourceLite (
PERESOURCE Resource
)
{
DPRINT("ExDeleteResourceLite(Resource %x)\n", Resource);
if (Resource->OwnerTable) ExFreePool(Resource->OwnerTable);
if (Resource->SharedWaiters) ExFreePool(Resource->SharedWaiters);
if (Resource->ExclusiveWaiters) ExFreePool(Resource->ExclusiveWaiters);
return(STATUS_SUCCESS);;
}
ERESOURCE_THREAD
STDCALL
ExGetCurrentResourceThread (
VOID
)
{
return((ERESOURCE_THREAD)PsGetCurrentThread());
}
ULONG
STDCALL
ExGetSharedWaiterCount (
PERESOURCE Resource
)
{
return(Resource->NumberOfSharedWaiters);
}
NTSTATUS
STDCALL
ExInitializeResource (
PERESOURCE Resource
)
{
return(ExInitializeResourceLite(Resource));
}
NTSTATUS
STDCALL
ExInitializeResourceLite (
PERESOURCE Resource
)
{
DPRINT("ExInitializeResourceLite(Resource %x)\n", Resource);
memset(Resource,0,sizeof(ERESOURCE));
Resource->NumberOfSharedWaiters = 0;
Resource->NumberOfExclusiveWaiters = 0;
KeInitializeSpinLock(&Resource->SpinLock);
Resource->Flag = 0;
Resource->ExclusiveWaiters = ExAllocatePool(NonPagedPool, sizeof(KEVENT));
KeInitializeEvent(Resource->ExclusiveWaiters,
SynchronizationEvent,
FALSE);
Resource->SharedWaiters = ExAllocatePool(NonPagedPool ,sizeof(KSEMAPHORE));
KeInitializeSemaphore(Resource->SharedWaiters,0,0x7fffffff);
Resource->ActiveCount = 0;
return(0);
}
BOOLEAN
STDCALL
ExIsResourceAcquiredExclusiveLite (
PERESOURCE Resource
)
/*
* FUNCTION: Returns whether the current thread has exclusive access to
* a given resource
* ARGUMENTS:
* Resource = Points to the resource to be queried
* RETURNS: TRUE if the caller has exclusive access to the resource,
* FALSE otherwise
*/
{
return((Resource->Flag & ResourceOwnedExclusive)
&& Resource->OwnerThreads[0].OwnerThread==ExGetCurrentResourceThread());
}
ULONG
STDCALL
ExIsResourceAcquiredSharedLite (
PERESOURCE Resource
)
/*
* FUNCTION: Returns whether the current thread has shared access to a given
* resource
* ARGUMENTS:
* Resource = Points to the resource to be queried
* RETURNS: The number of times the caller has acquired shared access to the
* given resource
*/
{
ULONG i;
if (Resource->OwnerThreads[0].OwnerThread == ExGetCurrentResourceThread())
{
return(Resource->OwnerThreads[0].a.OwnerCount);
}
if (Resource->OwnerThreads[1].OwnerThread == ExGetCurrentResourceThread())
{
return(Resource->OwnerThreads[1].a.OwnerCount);
}
if (!Resource->OwnerThreads[1].a.TableSize)
{
return(0);
}
for (i=0; i<Resource->OwnerThreads[1].a.TableSize; i++)
{
if (Resource->OwnerTable[i].OwnerThread==ExGetCurrentResourceThread())
{
return Resource->OwnerTable[i].a.OwnerCount;
}
}
return(0);
}
VOID
STDCALL
ExReinitializeResourceLite (
PERESOURCE Resource
)
{
Resource->NumberOfSharedWaiters = 0;
Resource->NumberOfExclusiveWaiters = 0;
KeInitializeSpinLock(&Resource->SpinLock);
Resource->Flag=0;
KeInitializeEvent(Resource->ExclusiveWaiters,SynchronizationEvent,
FALSE);
KeInitializeSemaphore(Resource->SharedWaiters,0,0x7fffffff);
Resource->ActiveCount = 0;
if (Resource->OwnerTable)
{
ExFreePool(Resource->OwnerTable);
}
Resource->OwnerThreads[0].OwnerThread=0;
Resource->OwnerThreads[0].a.OwnerCount=0;
Resource->OwnerThreads[1].OwnerThread=0;
Resource->OwnerThreads[1].a.OwnerCount=0;
}
VOID
STDCALL
ExReleaseResourceLite (
PERESOURCE Resource
)
{
return(ExReleaseResourceForThreadLite(Resource,
ExGetCurrentResourceThread()));
}
VOID
STDCALL
ExReleaseResource (
PERESOURCE Resource
)
{
return ExReleaseResourceForThreadLite(Resource,ExGetCurrentResourceThread());
}
VOID
STDCALL
ExReleaseResourceForThread (
PERESOURCE Resource,
ERESOURCE_THREAD ResourceThreadId
)
{
return(ExReleaseResourceForThreadLite(Resource,ResourceThreadId));
}
VOID
STDCALL
ExReleaseResourceForThreadLite (
PERESOURCE Resource,
ERESOURCE_THREAD ResourceThreadId
)
/*
* FUNCTION: Releases a resource for the given thread
* ARGUMENTS:
* Resource = Points to the release to release
* ResourceThreadId = Identifies the thread that originally acquired
* the resource
* NOTES: Must be running at IRQL < DISPATCH_LEVEL
* BUG: We don't support starving exclusive waiters
*/
{
KIRQL oldIrql;
DPRINT("ExReleaseResourceForThreadLite(Resource %x, ResourceThreadId %x)\n",
Resource, ResourceThreadId);
KeAcquireSpinLock(&Resource->SpinLock, &oldIrql);
if (Resource->Flag & ResourceOwnedExclusive)
{
DPRINT("Releasing from exclusive access\n");
Resource->OwnerThreads[0].a.OwnerCount--;
if (Resource->OwnerThreads[0].a.OwnerCount > 0)
{
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExReleaseResourceForThreadLite() finished\n");
return;
}
Resource->OwnerThreads[0].OwnerThread = 0;
Resource->ActiveCount--;
Resource->Flag &=(~ResourceOwnedExclusive);
assert(Resource->ActiveCount == 0);
DPRINT("Resource->NumberOfExclusiveWaiters %d\n",
Resource->NumberOfExclusiveWaiters);
if (Resource->NumberOfExclusiveWaiters)
{
/* get resource to first exclusive waiter */
KeSetEvent(Resource->ExclusiveWaiters,
IO_NO_INCREMENT,
FALSE);
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExReleaseResourceForThreadLite() finished\n");
return;
}
DPRINT("Resource->NumberOfSharedWaiters %d\n",
Resource->NumberOfSharedWaiters);
if (Resource->NumberOfSharedWaiters)
{
DPRINT("Releasing semaphore\n");
KeReleaseSemaphore(Resource->SharedWaiters,
IO_NO_INCREMENT,
Resource->NumberOfSharedWaiters,
FALSE);
}
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExReleaseResourceForThreadLite() finished\n");
return;
}
EiRemoveSharedOwner(Resource, ResourceThreadId);
if (Resource->ActiveCount == 0)
{
if (Resource->NumberOfExclusiveWaiters)
{
/* get resource to first exclusive waiter */
KeSetEvent(Resource->ExclusiveWaiters,
IO_NO_INCREMENT,
FALSE);
}
}
KeReleaseSpinLock(&Resource->SpinLock, oldIrql);
DPRINT("ExReleaseResourceForThreadLite() finished\n");
}
/* EOF */