1114 lines
31 KiB
C
1114 lines
31 KiB
C
|
#include "compdir.h"
|
|||
|
|
|||
|
#define InitializeListHead(ListHead) (\
|
|||
|
(ListHead)->Flink = (ListHead)->Blink = (ListHead) )
|
|||
|
|
|||
|
#define IsListEmpty(ListHead) (\
|
|||
|
( ((ListHead)->Flink == (ListHead)) ? TRUE : FALSE ) )
|
|||
|
|
|||
|
#define RemoveHeadList(ListHead) \
|
|||
|
(ListHead)->Flink;\
|
|||
|
{\
|
|||
|
PLIST_ENTRY FirstEntry;\
|
|||
|
FirstEntry = (ListHead)->Flink;\
|
|||
|
FirstEntry->Flink->Blink = (ListHead);\
|
|||
|
(ListHead)->Flink = FirstEntry->Flink;\
|
|||
|
}
|
|||
|
|
|||
|
#define InsertTailList(ListHead,Entry) \
|
|||
|
(Entry)->Flink = (ListHead);\
|
|||
|
(Entry)->Blink = (ListHead)->Blink;\
|
|||
|
(ListHead)->Blink->Flink = (Entry);\
|
|||
|
(ListHead)->Blink = (Entry)
|
|||
|
|
|||
|
#define ARGUMENT_PRESENT( ArgumentPointer ) (\
|
|||
|
(LPSTR)(ArgumentPointer) != (LPSTR)(NULL) )
|
|||
|
|
|||
|
#define ROUND_UP( Size, Amount ) (((Size) + ((Amount) - 1)) & ~((Amount) - 1))
|
|||
|
|
|||
|
VOID
|
|||
|
ProcessRequest(
|
|||
|
IN PWORK_QUEUE_ITEM WorkItem
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function is called whenever a work item is removed from
|
|||
|
the work queue by one of the worker threads. Which worker
|
|||
|
thread context this function is called in is arbitrary.
|
|||
|
|
|||
|
This functions keeps a pointer to state information in
|
|||
|
thread local storage.
|
|||
|
|
|||
|
This function is called once at the beginning with a
|
|||
|
special initialization call. During this call, this
|
|||
|
function allocates space for state information and
|
|||
|
remembers the pointer to the state information in
|
|||
|
a Thread Local Storage (TLS) slot.
|
|||
|
|
|||
|
This function is called once at the end with a special
|
|||
|
termination call. During this call, this function
|
|||
|
frees the state information allocated during the
|
|||
|
initialization call.
|
|||
|
|
|||
|
In between these two calls are zero or more calls to
|
|||
|
handle a work item. The work item is a copy request
|
|||
|
which is handled by the ProcessCopyFile function.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
WorkItem - Supplies a pointer to the work item just removed
|
|||
|
from the work queue. It is the responsibility of this
|
|||
|
routine to free the memory used to hold the work item.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
DWORD BytesWritten;
|
|||
|
PCOPY_REQUEST_STATE State;
|
|||
|
PCOPY_REQUEST CopyRequest;
|
|||
|
CHAR MessageBuffer[ 2 * MAX_PATH ];
|
|||
|
|
|||
|
if (WorkItem->Reason == WORK_INITIALIZE_ITEM) {
|
|||
|
//
|
|||
|
// First time initialization call. Allocate space for
|
|||
|
// state information.
|
|||
|
//
|
|||
|
|
|||
|
State = LocalAlloc( LMEM_ZEROINIT,
|
|||
|
sizeof( *State )
|
|||
|
);
|
|||
|
|
|||
|
if (State != NULL) {
|
|||
|
//
|
|||
|
// Now create a virtual buffer, with an initial commitment
|
|||
|
// of zero and a maximum commitment of 128KB. This buffer
|
|||
|
// will be used to accumulate the output during the copy
|
|||
|
// operation. This is so the output can be written to
|
|||
|
// standard output with a single write call, thus insuring
|
|||
|
// that it remains contiguous in the output stream, and is
|
|||
|
// not intermingled with the output of the other worker threads.
|
|||
|
//
|
|||
|
|
|||
|
if (CreateVirtualBuffer( &State->Buffer, 0, 2 * 64 * 1024 )) {
|
|||
|
//
|
|||
|
// The CurrentOutput field of the state block is
|
|||
|
// a pointer to where the next output goes in the
|
|||
|
// buffer. It is initialized here and reset each
|
|||
|
// time the buffer is flushed to standard output.
|
|||
|
//
|
|||
|
|
|||
|
State->CurrentOutput = State->Buffer.Base;
|
|||
|
}
|
|||
|
else {
|
|||
|
LocalFree( State );
|
|||
|
State = NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Remember the pointer to the state informaiton
|
|||
|
// thread local storage.
|
|||
|
//
|
|||
|
|
|||
|
TlsSetValue( TlsIndex, State );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Here to handle a work item or special terminate call.
|
|||
|
// Get the state pointer from thread local storage.
|
|||
|
//
|
|||
|
|
|||
|
State = (PCOPY_REQUEST_STATE)TlsGetValue( TlsIndex );
|
|||
|
if (State == NULL) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If this is the special terminate work item, free the virtual
|
|||
|
// buffer and state block allocated above and set the thread
|
|||
|
// local storage value to NULL. Return to caller.
|
|||
|
//
|
|||
|
|
|||
|
if (WorkItem->Reason == WORK_TERMINATE_ITEM) {
|
|||
|
FreeVirtualBuffer( &State->Buffer );
|
|||
|
LocalFree( State );
|
|||
|
TlsSetValue( TlsIndex, NULL );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If not an initialize or terminate work item, then must be a
|
|||
|
// copy request. Calculate the address of the copy request
|
|||
|
// block, based on the position of the WorkItem field in the
|
|||
|
// COPY_REQUEST structure.
|
|||
|
//
|
|||
|
|
|||
|
CopyRequest = CONTAINING_RECORD( WorkItem, COPY_REQUEST, WorkItem );
|
|||
|
|
|||
|
//
|
|||
|
// Actual copy operation is protected by a try ... except
|
|||
|
// block so that any attempts to store into the virtual buffer
|
|||
|
// will be handled correctly by extending the virtual buffer.
|
|||
|
//
|
|||
|
|
|||
|
_try {
|
|||
|
//
|
|||
|
// Perform the copy
|
|||
|
//
|
|||
|
ProcessCopyFile( CopyRequest, State );
|
|||
|
|
|||
|
//
|
|||
|
// If any output was written to the virtual buffer,
|
|||
|
// flush the output to standard output. Trim the
|
|||
|
// virtual buffer back to zero committed pages.
|
|||
|
//
|
|||
|
|
|||
|
if (State->CurrentOutput > (LPSTR)State->Buffer.Base) {
|
|||
|
WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ),
|
|||
|
State->Buffer.Base,
|
|||
|
(DWORD)(State->CurrentOutput - (LPSTR)State->Buffer.Base),
|
|||
|
&BytesWritten,
|
|||
|
NULL
|
|||
|
);
|
|||
|
|
|||
|
TrimVirtualBuffer( &State->Buffer );
|
|||
|
State->CurrentOutput = (LPSTR)State->Buffer.Base;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_except( VirtualBufferExceptionFilter( GetExceptionCode(),
|
|||
|
GetExceptionInformation(),
|
|||
|
&State->Buffer
|
|||
|
)
|
|||
|
) {
|
|||
|
|
|||
|
//
|
|||
|
// We will get here if the exception filter was unable to
|
|||
|
// commit the memory.
|
|||
|
//
|
|||
|
|
|||
|
WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ),
|
|||
|
MessageBuffer,
|
|||
|
sprintf( MessageBuffer, "can't commit memory\n" ),
|
|||
|
&BytesWritten,
|
|||
|
NULL
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free the storage used by the CopyRequest
|
|||
|
//
|
|||
|
|
|||
|
LocalFree( CopyRequest );
|
|||
|
|
|||
|
//
|
|||
|
// All done with this request. Return to the worker thread that
|
|||
|
// called us.
|
|||
|
//
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
ProcessCopyFile(
|
|||
|
IN PCOPY_REQUEST CopyRequest,
|
|||
|
IN PCOPY_REQUEST_STATE State
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function performs the actual copy of the contents of the
|
|||
|
passed file for the copy string given on the command line.
|
|||
|
If we are using synchronous I/O, then do the read operation
|
|||
|
now.
|
|||
|
|
|||
|
Copy the contents of the file for any matches, and accumulate
|
|||
|
the match output in the virtual buffer using sprintf, which is
|
|||
|
multi-thread safe, even with the single threaded version of
|
|||
|
the libraries.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
CopyRequest - Supplies a pointer to the copy request which
|
|||
|
contains the relevant information.
|
|||
|
|
|||
|
State - Supplies a pointer to state information for the current
|
|||
|
thread.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
LPSTR FullPathSrc, Destination;
|
|||
|
BOOL pend, CanDetectFreeSpace = TRUE;
|
|||
|
int i;
|
|||
|
DWORD sizeround;
|
|||
|
DWORD BytesPerCluster;
|
|||
|
ATTRIBUTE_TYPE Attributes;
|
|||
|
|
|||
|
int LastErrorGot;
|
|||
|
__int64 freespac;
|
|||
|
char root[5] = {'a',':','\\','\0'};
|
|||
|
DWORD cSecsPerClus, cBytesPerSec, cFreeClus, cTotalClus;
|
|||
|
|
|||
|
Destination = CopyRequest->Destination;
|
|||
|
FullPathSrc = CopyRequest->FullPathSrc;
|
|||
|
|
|||
|
root[0] = *Destination;
|
|||
|
|
|||
|
if( !GetDiskFreeSpace( root, &cSecsPerClus, &cBytesPerSec, &cFreeClus, &cTotalClus ) ) {
|
|||
|
CanDetectFreeSpace = FALSE;
|
|||
|
}
|
|||
|
else {
|
|||
|
freespac = ( (__int64)cBytesPerSec * (__int64)cSecsPerClus * (__int64)cFreeClus );
|
|||
|
BytesPerCluster = cSecsPerClus * cBytesPerSec;
|
|||
|
}
|
|||
|
|
|||
|
if (!fDontLowerCase) {
|
|||
|
_strlwr(FullPathSrc);
|
|||
|
_strlwr(Destination);
|
|||
|
}
|
|||
|
|
|||
|
State->CurrentOutput += sprintf( State->CurrentOutput, "%s => %s\t", FullPathSrc, Destination);
|
|||
|
|
|||
|
if (CanDetectFreeSpace) {
|
|||
|
|
|||
|
sizeround = CopyRequest->SizeLow;
|
|||
|
sizeround += BytesPerCluster - 1;
|
|||
|
sizeround /= BytesPerCluster;
|
|||
|
sizeround *= BytesPerCluster;
|
|||
|
|
|||
|
if (freespac < sizeround) {
|
|||
|
State->CurrentOutput += sprintf( State->CurrentOutput, "not enough space\n");
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
GET_ATTRIBUTES(Destination, Attributes);
|
|||
|
i = SET_ATTRIBUTES(Destination, Attributes & NONREADONLYSYSTEMHIDDEN );
|
|||
|
|
|||
|
i = 1;
|
|||
|
|
|||
|
do {
|
|||
|
|
|||
|
if (!fCreateLink) {
|
|||
|
if (!fBreakLinks) {
|
|||
|
pend = MyCopyFile (FullPathSrc, Destination, FALSE);
|
|||
|
}
|
|||
|
else {
|
|||
|
_unlink(Destination);
|
|||
|
pend = MyCopyFile (FullPathSrc, Destination, FALSE);
|
|||
|
}
|
|||
|
}
|
|||
|
else {
|
|||
|
if (i == 1) {
|
|||
|
pend = MakeLink (FullPathSrc, Destination, FALSE);
|
|||
|
}
|
|||
|
else {
|
|||
|
pend = MakeLink (FullPathSrc, Destination, TRUE);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (SparseTree && !pend) {
|
|||
|
|
|||
|
EnterCriticalSection( &CreatePathCriticalSection );
|
|||
|
|
|||
|
if (!MyCreatePath(Destination, FALSE)) {
|
|||
|
State->CurrentOutput += sprintf( State->CurrentOutput, "Unable to create path %s", Destination);
|
|||
|
ExitValue = 1;
|
|||
|
}
|
|||
|
|
|||
|
LeaveCriticalSection( &CreatePathCriticalSection );
|
|||
|
}
|
|||
|
|
|||
|
} while ((i++ < 2) && (!pend) );
|
|||
|
|
|||
|
if (!pend) {
|
|||
|
|
|||
|
LastErrorGot = GetLastError ();
|
|||
|
|
|||
|
if ((fCreateLink) && (LastErrorGot == 1)) {
|
|||
|
State->CurrentOutput += sprintf( State->CurrentOutput, "Can only make links on NTFS and OFS");
|
|||
|
}
|
|||
|
else if (fCreateLink) {
|
|||
|
State->CurrentOutput += sprintf( State->CurrentOutput, "(error = %d)", LastErrorGot);
|
|||
|
}
|
|||
|
else {
|
|||
|
State->CurrentOutput += sprintf( State->CurrentOutput, "Copy Error (error = %d)", LastErrorGot);
|
|||
|
}
|
|||
|
|
|||
|
ExitValue = 1;
|
|||
|
}
|
|||
|
|
|||
|
State->CurrentOutput += sprintf( State->CurrentOutput, "%s\n", pend == TRUE ? "[OK]" : "");
|
|||
|
|
|||
|
free (CopyRequest->Destination);
|
|||
|
free (CopyRequest->FullPathSrc);
|
|||
|
|
|||
|
//GET_ATTRIBUTES( FullPathSrc, Attributes);
|
|||
|
if ( !fDontCopyAttribs)
|
|||
|
{
|
|||
|
i = SET_ATTRIBUTES( Destination, CopyRequest->Attributes);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
i = SET_ATTRIBUTES( Destination, FILE_ATTRIBUTE_ARCHIVE);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
PWORK_QUEUE
|
|||
|
CreateWorkQueue(
|
|||
|
IN DWORD NumberOfWorkerThreads,
|
|||
|
IN PWORKER_ROUTINE WorkerRoutine
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function creates a work queue, with the specified number of
|
|||
|
threads to service work items placed in the queue. Work items
|
|||
|
are removed from the queue in the same order that they are placed
|
|||
|
in the queue.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
NumberOfWorkerThreads - Specifies how many threads this function
|
|||
|
should create to process work items placed in the queue.
|
|||
|
Must be greater than 0 and less than 128.
|
|||
|
|
|||
|
WorkerRoutine - Specifies the address of a routine to call
|
|||
|
for each work item as it is removed from the queue. The
|
|||
|
thread context the routine is called in is undefined.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
A pointer to the work queue. Returns NULL if unable to create
|
|||
|
the work queue and its worker threads. Extended error information
|
|||
|
is available from GetLastError()
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PWORK_QUEUE WorkQueue;
|
|||
|
HANDLE Thread;
|
|||
|
DWORD ThreadId;
|
|||
|
DWORD i;
|
|||
|
|
|||
|
//
|
|||
|
// Allocate space for the work queue, which includes an
|
|||
|
// array of thread handles.
|
|||
|
//
|
|||
|
|
|||
|
WorkQueue = LocalAlloc( LMEM_ZEROINIT,
|
|||
|
sizeof( *WorkQueue ) +
|
|||
|
(NumberOfWorkerThreads * sizeof( HANDLE ))
|
|||
|
);
|
|||
|
if (WorkQueue == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The work queue is controlled by a counting semaphore that
|
|||
|
// is incremented each time a work item is placed in the queue
|
|||
|
// and decremented each time a worker thread wakes up to remove
|
|||
|
// an item from the queue.
|
|||
|
//
|
|||
|
|
|||
|
if (WorkQueue->Semaphore = CreateSemaphore( NULL, 0, 100000, NULL )) {
|
|||
|
//
|
|||
|
// Mutual exclusion between the worker threads accessing
|
|||
|
// the work queue is done with a critical section.
|
|||
|
//
|
|||
|
|
|||
|
InitializeCriticalSection( &WorkQueue->CriticalSection );
|
|||
|
|
|||
|
//
|
|||
|
// The queue itself is just a doubly linked list, where
|
|||
|
// items are placed in the queue at the tail of the list
|
|||
|
// and removed from the queue from the head of the list.
|
|||
|
//
|
|||
|
|
|||
|
InitializeListHead( &WorkQueue->Queue );
|
|||
|
|
|||
|
//
|
|||
|
// Removed the address of the supplied worker function
|
|||
|
// in the work queue structure.
|
|||
|
//
|
|||
|
|
|||
|
WorkQueue->WorkerRoutine = WorkerRoutine;
|
|||
|
|
|||
|
//
|
|||
|
// Now create the requested number of worker threads.
|
|||
|
// The handle to each thread is remembered in an
|
|||
|
// array of thread handles in the work queue structure.
|
|||
|
//
|
|||
|
|
|||
|
for (i=0; i<NumberOfWorkerThreads; i++) {
|
|||
|
Thread = CreateThread( NULL,
|
|||
|
0,
|
|||
|
(LPTHREAD_START_ROUTINE) WorkerThread,
|
|||
|
WorkQueue,
|
|||
|
0,
|
|||
|
&ThreadId
|
|||
|
);
|
|||
|
if (Thread == NULL) {
|
|||
|
break;
|
|||
|
}
|
|||
|
else {
|
|||
|
WorkQueue->NumberOfWorkerThreads++;
|
|||
|
WorkQueue->WorkerThreads[ i ] = Thread;
|
|||
|
SetThreadPriority( Thread, THREAD_PRIORITY_ABOVE_NORMAL );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If we successfully created all of the worker threads
|
|||
|
// then return the address of the work queue structure
|
|||
|
// to indicate success.
|
|||
|
//
|
|||
|
|
|||
|
if (i == NumberOfWorkerThreads) {
|
|||
|
return WorkQueue;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Failed for some reason. Destroy whatever we managed
|
|||
|
// to create and return failure to the caller.
|
|||
|
//
|
|||
|
|
|||
|
DestroyWorkQueue( WorkQueue );
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
DestroyWorkQueue(
|
|||
|
IN OUT PWORK_QUEUE WorkQueue
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function destroys a work queue created with the CreateWorkQueue
|
|||
|
functions. It attempts to shut down the worker threads cleanly
|
|||
|
by queueing a terminate work item to each worker thread. It then
|
|||
|
waits for all the worker threads to terminate. If the wait is
|
|||
|
not satisfied within 30 seconds, then it goes ahead and terminates
|
|||
|
all of the worker threads.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
WorkQueue - Supplies a pointer to the work queue to destroy.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
DWORD i;
|
|||
|
DWORD rc;
|
|||
|
|
|||
|
//
|
|||
|
// If the semaphore handle field is not NULL, then there
|
|||
|
// may be threads to terminate.
|
|||
|
//
|
|||
|
|
|||
|
if (WorkQueue->Semaphore != NULL) {
|
|||
|
//
|
|||
|
// Set the termiating flag in the work queue and
|
|||
|
// signal the counting semaphore by the number
|
|||
|
// worker threads so they will all wake up and
|
|||
|
// notice the terminating flag and exit.
|
|||
|
//
|
|||
|
|
|||
|
EnterCriticalSection( &WorkQueue->CriticalSection );
|
|||
|
_try {
|
|||
|
WorkQueue->Terminating = TRUE;
|
|||
|
ReleaseSemaphore( WorkQueue->Semaphore,
|
|||
|
WorkQueue->NumberOfWorkerThreads,
|
|||
|
NULL
|
|||
|
);
|
|||
|
}
|
|||
|
_finally {
|
|||
|
LeaveCriticalSection( &WorkQueue->CriticalSection );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Wait for all worker threads to wake up and see the
|
|||
|
// terminate flag and then terminate themselves. Timeout
|
|||
|
// the wait after 30 seconds.
|
|||
|
//
|
|||
|
|
|||
|
while (TRUE) {
|
|||
|
rc = WaitForMultipleObjectsEx( WorkQueue->NumberOfWorkerThreads,
|
|||
|
WorkQueue->WorkerThreads,
|
|||
|
TRUE,
|
|||
|
3600000,
|
|||
|
TRUE
|
|||
|
);
|
|||
|
if (rc == WAIT_IO_COMPLETION) {
|
|||
|
//
|
|||
|
// If we came out of the wait because an I/O
|
|||
|
// completion routine was called, reissue the
|
|||
|
// wait.
|
|||
|
//
|
|||
|
continue;
|
|||
|
}
|
|||
|
else {
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now close our thread handles so they will actually
|
|||
|
// evaporate. If the wait above was unsuccessful,
|
|||
|
// then first attempt to force the termination of
|
|||
|
// each worker thread prior to closing the handle.
|
|||
|
//
|
|||
|
|
|||
|
for (i=0; i<WorkQueue->NumberOfWorkerThreads; i++) {
|
|||
|
if (rc != NO_ERROR) {
|
|||
|
TerminateThread( WorkQueue->WorkerThreads[ i ], rc );
|
|||
|
}
|
|||
|
|
|||
|
CloseHandle( WorkQueue->WorkerThreads[ i ] );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// All threads stopped, all thread handles closed. Now
|
|||
|
// delete the critical section and close the semaphore
|
|||
|
// handle.
|
|||
|
//
|
|||
|
|
|||
|
DeleteCriticalSection( &WorkQueue->CriticalSection );
|
|||
|
CloseHandle( WorkQueue->Semaphore );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Everything done, now free the memory used by the work queue.
|
|||
|
//
|
|||
|
|
|||
|
LocalFree( WorkQueue );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
BOOL
|
|||
|
QueueWorkItem(
|
|||
|
IN OUT PWORK_QUEUE WorkQueue,
|
|||
|
IN PWORK_QUEUE_ITEM WorkItem
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function queues a work item to the passed work queue that is
|
|||
|
processed by one of the worker threads associated with the queue.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
WorkQueue - Supplies a pointer to the work queue that is to
|
|||
|
receive the work item.
|
|||
|
|
|||
|
WorkItem - Supplies a pointer to the work item to add the the queue.
|
|||
|
The work item structure contains a doubly linked list entry, the
|
|||
|
address of a routine to call and a parameter to pass to that
|
|||
|
routine. It is the routine's responsibility to reclaim the
|
|||
|
storage occupied by the WorkItem structure.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
TRUE if operation was successful. Otherwise returns FALSE and
|
|||
|
extended error information is available from GetLastError()
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
BOOL Result;
|
|||
|
|
|||
|
//
|
|||
|
// Acquire the work queue critical section and insert the work item
|
|||
|
// in the queue and release the semaphore if the work item is not
|
|||
|
// already in the list.
|
|||
|
//
|
|||
|
|
|||
|
EnterCriticalSection( &WorkQueue->CriticalSection );
|
|||
|
Result = TRUE;
|
|||
|
_try {
|
|||
|
WorkItem->WorkQueue = WorkQueue;
|
|||
|
InsertTailList( &WorkQueue->Queue, &WorkItem->List );
|
|||
|
Result = ReleaseSemaphore( WorkQueue->Semaphore, 1, NULL );
|
|||
|
}
|
|||
|
_finally {
|
|||
|
LeaveCriticalSection( &WorkQueue->CriticalSection );
|
|||
|
}
|
|||
|
|
|||
|
return Result;
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
WorkerThread(
|
|||
|
LPVOID lpThreadParameter
|
|||
|
)
|
|||
|
{
|
|||
|
PWORK_QUEUE WorkQueue = (PWORK_QUEUE)lpThreadParameter;
|
|||
|
DWORD rc;
|
|||
|
WORK_QUEUE_ITEM InitWorkItem;
|
|||
|
PWORK_QUEUE_ITEM WorkItem;
|
|||
|
|
|||
|
//
|
|||
|
// Call the worker routine with an initialize work item
|
|||
|
// to give it a change to initialize some per thread
|
|||
|
// state that will passed to it for each subsequent
|
|||
|
// work item.
|
|||
|
//
|
|||
|
|
|||
|
InitWorkItem.Reason = WORK_INITIALIZE_ITEM;
|
|||
|
(WorkQueue->WorkerRoutine)( &InitWorkItem );
|
|||
|
while( TRUE ) {
|
|||
|
_try {
|
|||
|
|
|||
|
//
|
|||
|
// Wait until something is put in the queue (semaphore is
|
|||
|
// released), remove the item from the queue, mark it not
|
|||
|
// inserted, and execute the specified routine.
|
|||
|
//
|
|||
|
|
|||
|
rc = WaitForSingleObjectEx( WorkQueue->Semaphore, 0xFFFFFFFF, TRUE );
|
|||
|
if (rc == WAIT_IO_COMPLETION) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
EnterCriticalSection( &WorkQueue->CriticalSection );
|
|||
|
_try {
|
|||
|
if (WorkQueue->Terminating && IsListEmpty( &WorkQueue->Queue )) {
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
WorkItem = (PWORK_QUEUE_ITEM)RemoveHeadList( &WorkQueue->Queue );
|
|||
|
}
|
|||
|
_finally {
|
|||
|
LeaveCriticalSection( &WorkQueue->CriticalSection );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Execute the worker routine for this work item.
|
|||
|
//
|
|||
|
|
|||
|
(WorkQueue->WorkerRoutine)( WorkItem );
|
|||
|
}
|
|||
|
_except( EXCEPTION_EXECUTE_HANDLER ) {
|
|||
|
//
|
|||
|
// Ignore any exceptions from worker routine.
|
|||
|
//
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
InitWorkItem.Reason = WORK_TERMINATE_ITEM;
|
|||
|
(WorkQueue->WorkerRoutine)( &InitWorkItem );
|
|||
|
|
|||
|
ExitThread( 0 );
|
|||
|
return 0; // This will exit this thread
|
|||
|
}
|
|||
|
|
|||
|
BOOL
|
|||
|
CreateVirtualBuffer(
|
|||
|
OUT PVIRTUAL_BUFFER Buffer,
|
|||
|
IN SIZE_T CommitSize,
|
|||
|
IN SIZE_T ReserveSize OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function is called to create a virtual buffer. A virtual
|
|||
|
buffer is a contiguous range of virtual memory, where some initial
|
|||
|
prefix portion of the memory is committed and the remainder is only
|
|||
|
reserved virtual address space. A routine is provided to extend the
|
|||
|
size of the committed region incrementally or to trim the size of
|
|||
|
the committed region back to some specified amount.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Buffer - Pointer to the virtual buffer control structure that is
|
|||
|
filled in by this function.
|
|||
|
|
|||
|
CommitSize - Size of the initial committed portion of the buffer.
|
|||
|
May be zero.
|
|||
|
|
|||
|
ReserveSize - Amount of virtual address space to reserve for the
|
|||
|
buffer. May be zero, in which case amount reserved is the
|
|||
|
committed size plus one, rounded up to the next 64KB boundary.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
TRUE if operation was successful. Otherwise returns FALSE and
|
|||
|
extended error information is available from GetLastError()
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
SYSTEM_INFO SystemInformation;
|
|||
|
|
|||
|
//
|
|||
|
// Query the page size from the system for rounding
|
|||
|
// our memory allocations.
|
|||
|
//
|
|||
|
|
|||
|
GetSystemInfo( &SystemInformation );
|
|||
|
Buffer->PageSize = SystemInformation.dwPageSize;
|
|||
|
|
|||
|
//
|
|||
|
// If the reserve size was not specified, default it by
|
|||
|
// rounding up the initial committed size to a 64KB
|
|||
|
// boundary. This is because the Win32 Virtual Memory
|
|||
|
// API calls always allocate virtual address space on
|
|||
|
// 64KB boundaries, so we might well have it available
|
|||
|
// for commitment.
|
|||
|
//
|
|||
|
|
|||
|
if (!ARGUMENT_PRESENT( ReserveSize )) {
|
|||
|
ReserveSize = ROUND_UP( CommitSize + 1, 0x10000 );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Attempt to reserve the address space.
|
|||
|
//
|
|||
|
|
|||
|
Buffer->Base = VirtualAlloc( NULL,
|
|||
|
ReserveSize,
|
|||
|
MEM_RESERVE,
|
|||
|
PAGE_READWRITE
|
|||
|
);
|
|||
|
if (Buffer->Base == NULL) {
|
|||
|
//
|
|||
|
// Unable to reserve address space, return failure.
|
|||
|
//
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Attempt to commit some initial portion of the reserved region.
|
|||
|
//
|
|||
|
//
|
|||
|
|
|||
|
CommitSize = ROUND_UP( CommitSize, Buffer->PageSize );
|
|||
|
if (CommitSize == 0 ||
|
|||
|
VirtualAlloc( Buffer->Base,
|
|||
|
CommitSize,
|
|||
|
MEM_COMMIT,
|
|||
|
PAGE_READWRITE
|
|||
|
) != NULL
|
|||
|
) {
|
|||
|
//
|
|||
|
// Either the size of the committed region was zero or the
|
|||
|
// commitment succeeded. In either case calculate the
|
|||
|
// address of the first byte after the committed region
|
|||
|
// and the address of the first byte after the reserved
|
|||
|
// region and return successs.
|
|||
|
//
|
|||
|
|
|||
|
Buffer->CommitLimit = (LPVOID)
|
|||
|
((char *)Buffer->Base + CommitSize);
|
|||
|
|
|||
|
Buffer->ReserveLimit = (LPVOID)
|
|||
|
((char *)Buffer->Base + ReserveSize);
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If unable to commit the memory, release the virtual address
|
|||
|
// range allocated above and return failure.
|
|||
|
//
|
|||
|
|
|||
|
VirtualFree( Buffer->Base, 0, MEM_RELEASE );
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
BOOL
|
|||
|
ExtendVirtualBuffer(
|
|||
|
IN PVIRTUAL_BUFFER Buffer,
|
|||
|
IN LPVOID Address
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function is called to extend the committed portion of a virtual
|
|||
|
buffer.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Buffer - Pointer to the virtual buffer control structure.
|
|||
|
|
|||
|
Address - Byte at this address is committed, along with all memory
|
|||
|
from the beginning of the buffer to this address. If the
|
|||
|
address is already within the committed portion of the virtual
|
|||
|
buffer, then this routine does nothing. If outside the reserved
|
|||
|
portion of the virtual buffer, then this routine returns an
|
|||
|
error.
|
|||
|
|
|||
|
Otherwise enough pages are committed so that the memory from the
|
|||
|
base of the buffer to the passed address is a contiguous region
|
|||
|
of committed memory.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
TRUE if operation was successful. Otherwise returns FALSE and
|
|||
|
extended error information is available from GetLastError()
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
SIZE_T NewCommitSize;
|
|||
|
LPVOID NewCommitLimit;
|
|||
|
|
|||
|
//
|
|||
|
// See if address is within the buffer.
|
|||
|
//
|
|||
|
|
|||
|
if (Address >= Buffer->Base && Address < Buffer->ReserveLimit) {
|
|||
|
//
|
|||
|
// See if the address is within the committed portion of
|
|||
|
// the buffer. If so return success immediately.
|
|||
|
//
|
|||
|
|
|||
|
if (Address < Buffer->CommitLimit) {
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Address is within the reserved portion. Determine how many
|
|||
|
// bytes are between the address and the end of the committed
|
|||
|
// portion of the buffer. Round this size to a multiple of
|
|||
|
// the page size and this is the amount we will attempt to
|
|||
|
// commit.
|
|||
|
//
|
|||
|
|
|||
|
NewCommitSize =
|
|||
|
(ROUND_UP( (DWORD_PTR)Address + 1, Buffer->PageSize ) -
|
|||
|
(DWORD_PTR)Buffer->CommitLimit
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// Attempt to commit the memory.
|
|||
|
//
|
|||
|
|
|||
|
NewCommitLimit = VirtualAlloc( Buffer->CommitLimit,
|
|||
|
NewCommitSize,
|
|||
|
MEM_COMMIT,
|
|||
|
PAGE_READWRITE
|
|||
|
);
|
|||
|
if (NewCommitLimit != NULL) {
|
|||
|
//
|
|||
|
// Successful, so update the upper limit of the committed
|
|||
|
// region of the buffer and return success.
|
|||
|
//
|
|||
|
|
|||
|
Buffer->CommitLimit = (LPVOID)
|
|||
|
((DWORD_PTR)NewCommitLimit + NewCommitSize);
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Address is outside of the buffer, return failure.
|
|||
|
//
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
BOOL
|
|||
|
TrimVirtualBuffer(
|
|||
|
IN PVIRTUAL_BUFFER Buffer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function is called to decommit any memory that has been
|
|||
|
committed for this virtual buffer.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Buffer - Pointer to the virtual buffer control structure.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
TRUE if operation was successful. Otherwise returns FALSE and
|
|||
|
extended error information is available from GetLastError()
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
Buffer->CommitLimit = Buffer->Base;
|
|||
|
return VirtualFree( Buffer->Base, 0, MEM_DECOMMIT );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
BOOL
|
|||
|
FreeVirtualBuffer(
|
|||
|
IN PVIRTUAL_BUFFER Buffer
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function is called to free all the memory that is associated
|
|||
|
with this virtual buffer.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Buffer - Pointer to the virtual buffer control structure.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
TRUE if operation was successful. Otherwise returns FALSE and
|
|||
|
extended error information is available from GetLastError()
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
//
|
|||
|
// Decommit and release all virtual memory associated with
|
|||
|
// this virtual buffer.
|
|||
|
//
|
|||
|
|
|||
|
return VirtualFree( Buffer->Base, 0, MEM_RELEASE );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
int
|
|||
|
VirtualBufferExceptionFilter(
|
|||
|
IN DWORD ExceptionCode,
|
|||
|
IN PEXCEPTION_POINTERS ExceptionInfo,
|
|||
|
IN OUT PVIRTUAL_BUFFER Buffer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function is an exception filter that handles exceptions that
|
|||
|
referenced uncommitted but reserved memory contained in the passed
|
|||
|
virtual buffer. It this filter routine is able to commit the
|
|||
|
additional pages needed to allow the memory reference to succeed,
|
|||
|
then it will re-execute the faulting instruction. If it is unable
|
|||
|
to commit the pages, it will execute the callers exception handler.
|
|||
|
|
|||
|
If the exception is not an access violation or is an access
|
|||
|
violation but does not reference memory contained in the reserved
|
|||
|
portion of the virtual buffer, then this filter passes the exception
|
|||
|
on up the exception chain.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ExceptionCode - Reason for the exception.
|
|||
|
|
|||
|
ExceptionInfo - Information about the exception and the context
|
|||
|
that it occurred in.
|
|||
|
|
|||
|
Buffer - Points to a virtual buffer control structure that defines
|
|||
|
the reserved memory region that is to be committed whenever an
|
|||
|
attempt is made to access it.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Exception disposition code that tells the exception dispatcher what
|
|||
|
to do with this exception. One of three values is returned:
|
|||
|
|
|||
|
EXCEPTION_EXECUTE_HANDLER - execute the exception handler
|
|||
|
associated with the exception clause that called this filter
|
|||
|
procedure.
|
|||
|
|
|||
|
EXCEPTION_CONTINUE_SEARCH - Continue searching for an exception
|
|||
|
handler to handle this exception.
|
|||
|
|
|||
|
EXCEPTION_CONTINUE_EXECUTION - Dismiss this exception and return
|
|||
|
control to the instruction that caused the exception.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
LPVOID FaultingAddress;
|
|||
|
|
|||
|
//
|
|||
|
// If this is an access violation touching memory within
|
|||
|
// our reserved buffer, but outside of the committed portion
|
|||
|
// of the buffer, then we are going to take this exception.
|
|||
|
//
|
|||
|
|
|||
|
if (ExceptionCode == STATUS_ACCESS_VIOLATION) {
|
|||
|
//
|
|||
|
// Get the virtual address that caused the access violation
|
|||
|
// from the exception record. Determine if the address
|
|||
|
// references memory within the reserved but uncommitted
|
|||
|
// portion of the virtual buffer.
|
|||
|
//
|
|||
|
|
|||
|
FaultingAddress = (LPVOID)ExceptionInfo->ExceptionRecord->ExceptionInformation[ 1 ];
|
|||
|
if (FaultingAddress >= Buffer->CommitLimit &&
|
|||
|
FaultingAddress <= Buffer->ReserveLimit
|
|||
|
) {
|
|||
|
//
|
|||
|
// This is our exception. Try to extend the buffer
|
|||
|
// to including the faulting address.
|
|||
|
//
|
|||
|
|
|||
|
if (ExtendVirtualBuffer( Buffer, FaultingAddress )) {
|
|||
|
//
|
|||
|
// Buffer successfully extended, so re-execute the
|
|||
|
// faulting instruction.
|
|||
|
//
|
|||
|
|
|||
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|||
|
}
|
|||
|
else {
|
|||
|
//
|
|||
|
// Unable to extend the buffer. Stop copying
|
|||
|
// for exception handlers and execute the caller's
|
|||
|
// handler.
|
|||
|
//
|
|||
|
|
|||
|
return EXCEPTION_EXECUTE_HANDLER;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Not an exception we care about, so pass it up the chain.
|
|||
|
//
|
|||
|
|
|||
|
return EXCEPTION_CONTINUE_SEARCH;
|
|||
|
}
|