mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2070 lines
60 KiB
2070 lines
60 KiB
/*++
|
|
|
|
Copyright (c) 1992 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
pdc.c
|
|
|
|
Abstract:
|
|
|
|
This is the main source file for the Windows/NT PDC API demonstration
|
|
program. This program demonstrates how to use many of the advanced
|
|
operating system features provided by the Win32 API set on Windows/NT.
|
|
|
|
This file and its corresponding header file, pdc.h, can be found
|
|
in the sample programs directory of the PDC CD-ROM.
|
|
|
|
This program has a real purpose, although the implementation is
|
|
somewhat contrived in order to demonstrate the various operating
|
|
system features of Windows/NT.
|
|
|
|
The features that this program demonstrate are:
|
|
|
|
- Creating multiple threads, using critical sections
|
|
and semaphores for synchronization.
|
|
|
|
- Thread termination.
|
|
|
|
- Virtual memory, commitment vs reservation.
|
|
|
|
- Structured exception handling, including finally
|
|
clauses and an exception filter procedure.
|
|
|
|
- Enumeration of directory entries.
|
|
|
|
- Mapped file I/O
|
|
|
|
- Asynchronous file I/O via completion routine.
|
|
|
|
- Synchronous file I/O
|
|
|
|
PDC is a character mode program for searching the files in a
|
|
directory tree for a match against a pattern. It uses multiple
|
|
threads to do it's work, with each thread processing a file at a
|
|
time, accumulating it's matches and outputting them to standard
|
|
output contiguously when it is done searching a file.
|
|
|
|
The command line syntax is:
|
|
|
|
Usage: PDC [-h] [-v] [-y] [-a | -s | -m] [-t n] SearchString [DirectoryPath]
|
|
|
|
where:
|
|
|
|
-h - prints this message.
|
|
|
|
-v - generates verbose output.
|
|
|
|
-y - ignores case when doing comparisons.
|
|
|
|
-a - specifies that the program should use asynchronous file
|
|
I/O to read the files being searched.
|
|
|
|
-s - specifies that the program should use synchronous file
|
|
I/O to read the files being searched.
|
|
|
|
-m - specifies that the program should use mapped file I/O
|
|
to read the files being searched.
|
|
|
|
-t - specifies the number of threads to use when doing the
|
|
search. Default is 4 * the number of processors.
|
|
|
|
SearchString - specifies the text to search for. Enclose in
|
|
quotes if it contains spaces or punctuation.
|
|
|
|
DirectoryPath - specifies the root of the tree to begin the
|
|
search at. Defaults to the current directory.
|
|
|
|
|
|
--*/
|
|
|
|
#include "pdc.h"
|
|
|
|
|
|
int
|
|
_CRTAPI1 main(
|
|
int argc,
|
|
char *argv[]
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the main procedure for the PDC program, and is called by
|
|
the C Runtime startup code when the program starts.
|
|
|
|
Arguments:
|
|
|
|
argc - number of argumments in the argv array.
|
|
|
|
argv - pointer to an array of null terminated string pointers.
|
|
|
|
Return Value:
|
|
|
|
Process exit status. The value returned by this function will
|
|
be used as the exit code parameter passed to ExitProcess.
|
|
|
|
--*/
|
|
|
|
{
|
|
SYSTEM_INFO SystemInformation;
|
|
PWORK_QUEUE WorkQueue;
|
|
|
|
//
|
|
// Query the number of processors from the system and
|
|
// default the number of worker threads to 4 times that.
|
|
//
|
|
|
|
GetSystemInfo( &SystemInformation );
|
|
NumberOfWorkerThreads = SystemInformation.dwNumberOfProcessors * 4;
|
|
|
|
//
|
|
// Process the arguments given on the command line.
|
|
//
|
|
|
|
if (!ProcessCommandLineArguments( argc, argv )) {
|
|
exit( 1 );
|
|
}
|
|
|
|
//
|
|
// Allocate a thread local storage slot for use by our worker
|
|
// thread routine (ProcessRequest). This call reserves a
|
|
// 32-bit slot in the thread local storage array for every
|
|
// thread in this process. Remember the slot index in a global
|
|
// variable for use by our worker thread routine.
|
|
//
|
|
|
|
TlsIndex = TlsAlloc();
|
|
if (TlsIndex == 0xFFFFFFFF) {
|
|
fprintf( stderr, "PDC: Unable to allocated thread local storage.\n" );
|
|
exit( 1 );
|
|
}
|
|
|
|
|
|
//
|
|
// Create a work queue, which will create the specified number of threads
|
|
// to process.
|
|
//
|
|
|
|
WorkQueue = CreateWorkQueue( NumberOfWorkerThreads, ProcessRequest );
|
|
if (WorkQueue == NULL) {
|
|
fprintf( stderr, "PDC: Unable to create %u worker threads.\n", NumberOfWorkerThreads );
|
|
exit( 1 );
|
|
}
|
|
|
|
//
|
|
// If using asynchronous I/O, create an event that will be signalled
|
|
// when there are no more outstanding I/O requests. The event is
|
|
// a manual reset event, that once signalled via SetEvent, will
|
|
// remain signalled until ResetEvent is called.
|
|
//
|
|
|
|
if (ASyncIO) {
|
|
IoCompletedEvent = CreateEvent( NULL, // Not inherited
|
|
TRUE, // Manual reset
|
|
FALSE, // Initially reset
|
|
NULL // No name
|
|
);
|
|
}
|
|
|
|
//
|
|
// Now walk the directory tree, which will call our procedure
|
|
// (QueueSearchFile) for each directory and file in the tree.
|
|
//
|
|
|
|
EnumerateDirectoryTree( DirectoryPath,
|
|
QueueSearchFile,
|
|
WorkQueue
|
|
);
|
|
|
|
//
|
|
// Done walking the tree. If using asynchronous I/O, wait for all of
|
|
// the outstanding I/O requests to be completed.
|
|
//
|
|
|
|
if (ASyncIO) {
|
|
//
|
|
// We use an alertable wait in a loop, as I/O completion
|
|
// will terminate the wait, even through the event we
|
|
// are waiting on is not signalled.
|
|
//
|
|
|
|
while (WaitForSingleObjectEx( IoCompletedEvent,
|
|
0xFFFFFFFF,
|
|
TRUE
|
|
) == WAIT_IO_COMPLETION
|
|
) {
|
|
;
|
|
}
|
|
}
|
|
|
|
//
|
|
// All done, destroy the work queue. This will wait for the work queues
|
|
// to empty before terminating the worker threads and destroying the
|
|
// queue.
|
|
//
|
|
|
|
DestroyWorkQueue( WorkQueue );
|
|
|
|
if (Verbose && MatchedLineCount) {
|
|
fprintf( stderr,
|
|
"Found %u lines with matches in %u files, out of %u files searched.\n",
|
|
MatchedLineCount,
|
|
MatchedFileCount,
|
|
SearchedFileCount
|
|
);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
QueueSearchFile(
|
|
LPSTR Path,
|
|
PWIN32_FIND_DATA FindFileData,
|
|
PVOID EnumerateParameter
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the directory enumeration function. It is called by the
|
|
EnumerateDirectoryTree function once for each file and directory
|
|
in the tree.
|
|
|
|
This function, if it decides it wants to search the file, will
|
|
open the file and then, depending upon the I/O method selected via
|
|
the command line, will:
|
|
|
|
- map the file PAGE_READONLY for mapped file I/O
|
|
|
|
- read the file into an allocated buffer for synchronous
|
|
I/O.
|
|
|
|
- will allocate the buffer and start a read operation
|
|
to read the entire file into the buffer. A completion
|
|
routine will be invoked when the read completes, possibly
|
|
in another thread context.
|
|
|
|
Finally it will queue a search request to the work queue, with the
|
|
relevant information contained in the request. For asynchronous
|
|
I/O, the search request is allocated and initialized here but is
|
|
not actually queued to the work queue until the I/O completion
|
|
routine has been called.
|
|
|
|
Arguments:
|
|
|
|
Path - Supplies a pointer to a null terminated string that contains
|
|
the fully qualified path of the file or directory.
|
|
|
|
FindFileData - Supplies the directory information associated
|
|
with the file or directory specified by the Path argument.
|
|
|
|
EnumerateParameter - Uninterpreted 32-bit value. Not used.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PWORK_QUEUE WorkQueue = (PWORK_QUEUE)EnumerateParameter;
|
|
PSEARCH_REQUEST SearchRequest;
|
|
HANDLE File;
|
|
HANDLE Mapping;
|
|
LPVOID FileData;
|
|
DWORD FileSize;
|
|
|
|
//
|
|
// Ignore directories or zero length files, as there
|
|
// is nothing to search in these cases.
|
|
//
|
|
|
|
if (FindFileData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ||
|
|
!(FileSize = FindFileData->nFileSizeLow)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Open the file using the fully qualified path. Specify the
|
|
// sequential scan hint to the cache manager and if asynchronous
|
|
// I/O will be used, specified the overlapped flag as well.
|
|
//
|
|
|
|
File = CreateFile( Path,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_SEQUENTIAL_SCAN |
|
|
(ASyncIO ? FILE_FLAG_OVERLAPPED : 0),
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Since NULL might be a valid file object handle, failure
|
|
// is indicated by a special return value.
|
|
//
|
|
|
|
if (File == INVALID_HANDLE_VALUE) {
|
|
fprintf( stderr, "%s(0) : error %u: Unable to open file.\n",
|
|
Path,
|
|
GetLastError()
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// File successfully opened for read access.
|
|
//
|
|
if (MappedFileIO) {
|
|
//
|
|
// If mapped file I/O, create a file mapping object, backed by
|
|
// the file we just opened. Make the default page protection
|
|
// for the mapping be readonly.
|
|
//
|
|
|
|
Mapping = CreateFileMapping( File,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
0,
|
|
0,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Okay to close the file handle now, since the mapping object
|
|
// has a reference to the file that will cause the open file
|
|
// object to remain until the reference is destroyed. Note that
|
|
// any sharing information is lost at this point, so somebody
|
|
// could come in an open it for write access.
|
|
//
|
|
|
|
CloseHandle( File );
|
|
|
|
//
|
|
// Here, a null value indicates an error.
|
|
//
|
|
|
|
if (Mapping == NULL) {
|
|
fprintf( stderr, "%s(0) : error %u: Unable to create mapping object.\n",
|
|
Path,
|
|
GetLastError()
|
|
);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Finally, map a view of the file, using the mapping object
|
|
// just created, into the address space of this process.
|
|
// The offset and size are zero, which means to map the
|
|
// entire file.
|
|
//
|
|
|
|
FileData = MapViewOfFile( Mapping,
|
|
FILE_MAP_READ,
|
|
0,
|
|
0,
|
|
0
|
|
);
|
|
|
|
//
|
|
// Okay to close the mapping object handle now, it will remained
|
|
// referenced as long as the view remains mapped.
|
|
//
|
|
|
|
CloseHandle( Mapping );
|
|
|
|
//
|
|
// A null value indicates the map operation failed.
|
|
//
|
|
|
|
if (FileData == NULL) {
|
|
fprintf( stderr, "%s(0) : error %u: Unable to map file.\n",
|
|
Path,
|
|
GetLastError()
|
|
);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// All done mapping the file. Set the File handle to NULL as
|
|
// it has been closed already. Both the file object and mapping
|
|
// objects created above will be freed when the map view is
|
|
// unmapped.
|
|
//
|
|
|
|
File = NULL;
|
|
}
|
|
else {
|
|
//
|
|
// Not using mapped I/O, so allocate a buffer big enough to
|
|
// contain the entire file.
|
|
//
|
|
|
|
FileData = VirtualAlloc( NULL,
|
|
FileSize,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE
|
|
);
|
|
if (FileData == NULL) {
|
|
fprintf( stderr, "%s(0) : error %u: Unable to allocate memory to contain file.\n",
|
|
Path,
|
|
GetLastError()
|
|
);
|
|
CloseHandle( File );
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We have successfully opened the file and are ready to go.
|
|
// Allocate space for a search request and fill it in
|
|
// with the information needed by the ProcessSearchFile
|
|
// function when it runs.
|
|
//
|
|
|
|
SearchRequest = LocalAlloc( LMEM_ZEROINIT,
|
|
sizeof( *SearchRequest ) +
|
|
strlen( Path ) + 1
|
|
);
|
|
if (SearchRequest == NULL) {
|
|
fprintf( stderr, "PDC: Out of memory\n" );
|
|
exit( 1 );
|
|
}
|
|
|
|
SearchRequest->WorkItem.Reason = WORK_ITEM;
|
|
SearchRequest->File = File;
|
|
SearchRequest->FileSize = FileSize;
|
|
SearchRequest->FileData = FileData;
|
|
strcpy( SearchRequest->FullPathName, Path );
|
|
|
|
if (!ASyncIO) {
|
|
//
|
|
// If not using asynchronous I/O, then queue the search
|
|
// request to the work queue.
|
|
//
|
|
|
|
QueueWorkItem( WorkQueue, &SearchRequest->WorkItem );
|
|
}
|
|
else {
|
|
//
|
|
// Using asynchronous I/O, so queue the read operation.
|
|
// The file handle must remain open while the read operation
|
|
// is pending.
|
|
//
|
|
|
|
if (!ReadFileEx( File,
|
|
FileData,
|
|
FileSize,
|
|
&SearchRequest->OverlappedIO,
|
|
ProcessReadFileCompletion
|
|
)
|
|
) {
|
|
fprintf( stderr, "%s(0) : error %u: Unable to queue read of file.\n",
|
|
Path,
|
|
GetLastError()
|
|
);
|
|
|
|
VirtualFree( FileData, 0, MEM_RELEASE );
|
|
CloseHandle( File );
|
|
LocalFree( SearchRequest );
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Successfully queued the read operation. Keep a count
|
|
// of outstanding read operations so we know when it is
|
|
// okay to terminate.
|
|
//
|
|
|
|
OutstandingIOOperations += 1;
|
|
}
|
|
|
|
//
|
|
// Return back to the EnumerateDirectoryTree function so that it
|
|
// can call us with the next file or directrry.
|
|
//
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
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 search request
|
|
which is handled by the ProcessSearchFile 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;
|
|
PSEARCH_REQUEST_STATE State;
|
|
PSEARCH_REQUEST SearchRequest;
|
|
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 matched strings output
|
|
// during the search of a single file. 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 = (PSEARCH_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
|
|
// search request. Calculate the address of the search request
|
|
// block, based on the position of the WorkItem field in the
|
|
// SEARCH_REQUEST structure.
|
|
//
|
|
|
|
SearchRequest = CONTAINING_RECORD( WorkItem, SEARCH_REQUEST, WorkItem );
|
|
|
|
//
|
|
// Actual search 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 search against this file.
|
|
//
|
|
|
|
ProcessSearchFile( SearchRequest, State );
|
|
|
|
//
|
|
// Done with this file. If using asynchronous I/O, decrement the
|
|
// count of outstanding I/O operations and it if goes to zero,
|
|
// then signal the IoCompletedEvent as there are no more outstanding
|
|
// I/O operations.
|
|
//
|
|
|
|
if (ASyncIO && InterlockedDecrement( &OutstandingIOOperations ) == 0) {
|
|
SetEvent( IoCompletedEvent );
|
|
}
|
|
|
|
//
|
|
// 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,
|
|
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,
|
|
"%s(0) : error 0: too many matches for file\n",
|
|
SearchRequest->FullPathName
|
|
),
|
|
&BytesWritten,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
//
|
|
// Free the storage used by the SearchRequest
|
|
//
|
|
|
|
LocalFree( SearchRequest );
|
|
|
|
//
|
|
// All done with this request. Return to the worker thread that
|
|
// called us.
|
|
//
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
ProcessReadFileCompletion(
|
|
DWORD dwErrorCode,
|
|
DWORD dwNumberOfBytesTransfered,
|
|
LPOVERLAPPED lpOverlapped
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called whenever an asynchronous I/O operation,
|
|
queued by the previous function, completes. This function
|
|
calulates the address of the search request block and then
|
|
queue the request to the work queue, now that the data is
|
|
in memory.
|
|
|
|
Arguments:
|
|
|
|
dwErrorCode - Supplies the error code that the I/O completed with.
|
|
|
|
dwNumberOfBytesTransfered - Supplies the actual number of bytes
|
|
transferred.
|
|
|
|
lpOverlapped - Supplies a pointer to the structure given to
|
|
ReadFileEx when the I/O operation was queued.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSEARCH_REQUEST SearchRequest;
|
|
|
|
//
|
|
// Since the data we need is now in memory, queue the search
|
|
// request to the work queue.
|
|
//
|
|
|
|
SearchRequest = CONTAINING_RECORD( lpOverlapped, SEARCH_REQUEST, OverlappedIO );
|
|
QueueWorkItem( SearchRequest->WorkItem.WorkQueue, &SearchRequest->WorkItem );
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
ProcessSearchFile(
|
|
IN PSEARCH_REQUEST SearchRequest,
|
|
IN PSEARCH_REQUEST_STATE State
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function performs the actual search of the contents of the
|
|
passed file for the search string given on the command line.
|
|
If we are using synchronous I/O, then do the read operation
|
|
now.
|
|
|
|
Search 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:
|
|
|
|
SearchRequest - Supplies a pointer to the search request which
|
|
contains the relevant information.
|
|
|
|
State - Supplies a pointer to state information for the current
|
|
thread.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
LPSTR FileData, s, s1, BegLine, EndLine, EndOfFile;
|
|
DWORD LineNumber;
|
|
DWORD MatchesFound;
|
|
DWORD BytesRead;
|
|
|
|
// Get a pointer to the beginning of the file data in memory
|
|
// and calculate the address of the end of file point in
|
|
// memory.
|
|
//
|
|
|
|
FileData = SearchRequest->FileData;
|
|
EndOfFile = FileData + SearchRequest->FileSize;
|
|
|
|
//
|
|
// If using synchronous I/O, then we have not read in the
|
|
// file contents yet, so issue the synchronous read to get
|
|
// the data into memory.
|
|
//
|
|
|
|
if (SyncIO) {
|
|
if (!ReadFile( SearchRequest->File,
|
|
FileData,
|
|
SearchRequest->FileSize,
|
|
&BytesRead,
|
|
NULL
|
|
) ||
|
|
BytesRead != SearchRequest->FileSize
|
|
) {
|
|
State->CurrentOutput += sprintf( State->CurrentOutput,
|
|
"%s(0) : error %u: Unable to read file contents.\n",
|
|
SearchRequest->FullPathName,
|
|
GetLastError()
|
|
);
|
|
|
|
CloseHandle( SearchRequest->File );
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Close any open file handle associated with this request.
|
|
//
|
|
|
|
if (SearchRequest->File != NULL) {
|
|
CloseHandle( SearchRequest->File );
|
|
}
|
|
|
|
//
|
|
// Search the file contents, keeping track of line breaks
|
|
// so we can tell the line number of each match.
|
|
//
|
|
|
|
s = FileData;
|
|
LineNumber = 0;
|
|
MatchesFound = 0;
|
|
while (s < EndOfFile) {
|
|
BegLine = s;
|
|
while (s < EndOfFile && *s != '\n') {
|
|
s++;
|
|
}
|
|
|
|
if (*s == '\n') {
|
|
LineNumber += 1;
|
|
EndLine = s - 1;
|
|
if (EndLine > BegLine && EndLine[ -1 ] == '\r') {
|
|
EndLine -= 1;
|
|
}
|
|
|
|
s1 = BegLine;
|
|
while (s1 < (EndLine - SearchStringLength)) {
|
|
if (!(SearchFunction)( s1, SearchString, SearchStringLength )) {
|
|
//
|
|
// We have a match for this line. Append the
|
|
// output to the virtual buffer and update the
|
|
// current output pointer.
|
|
//
|
|
|
|
State->CurrentOutput += sprintf( State->CurrentOutput,
|
|
"%s(%u) : %.*s\n",
|
|
SearchRequest->FullPathName,
|
|
LineNumber,
|
|
EndLine - BegLine,
|
|
BegLine
|
|
);
|
|
MatchesFound += 1;
|
|
break;
|
|
}
|
|
|
|
s1++;
|
|
}
|
|
|
|
s++;
|
|
}
|
|
}
|
|
|
|
if (MatchesFound) {
|
|
MatchedLineCount += MatchesFound;
|
|
MatchedFileCount += 1;
|
|
}
|
|
SearchedFileCount += 1;
|
|
|
|
//
|
|
// All done with the file contents. Discard it either by
|
|
// unmapping the view of the file in the case of mapped file
|
|
// I/O or free the virtual memory for other types of I/O
|
|
//
|
|
|
|
if (MappedFileIO) {
|
|
if (!UnmapViewOfFile( FileData )) {
|
|
State->CurrentOutput += sprintf( State->CurrentOutput,
|
|
"%s(%u) : UnmapViewOfFile( %08x ) failed, error == %u\n",
|
|
SearchRequest->FullPathName,
|
|
LineNumber,
|
|
FileData,
|
|
GetLastError()
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
VirtualFree( FileData, 0, MEM_RELEASE );
|
|
}
|
|
}
|
|
|
|
|
|
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,
|
|
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 );
|
|
WorkQueue->Terminating = TRUE;
|
|
ReleaseSemaphore( WorkQueue->Semaphore,
|
|
WorkQueue->NumberOfWorkerThreads,
|
|
NULL
|
|
);
|
|
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,
|
|
30000,
|
|
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 DWORD CommitSize,
|
|
IN DWORD 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()
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD 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 =
|
|
((DWORD)ROUND_UP( (DWORD)Address + 1, Buffer->PageSize ) -
|
|
(DWORD)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)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 searching
|
|
// 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;
|
|
}
|
|
|
|
|
|
BOOL
|
|
EnumerateDirectoryTree(
|
|
LPSTR DirectoryPath,
|
|
PDIRECTORY_ENUMERATE_ROUTINE EnumerateRoutine,
|
|
PVOID EnumerateParameter
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function walks a directory tree, depth first, calling the
|
|
passed enumeration routine for each directory and file found
|
|
in the tree. The enumeration routine is passed the full path
|
|
of the file, the directory information associated with the file
|
|
and an enumeration parameter that is uninterpreted by this
|
|
function.
|
|
|
|
Arguments:
|
|
|
|
DirectoryPath - Absolute or relative path to the directory that
|
|
will is the root of the tree to enumerate.
|
|
|
|
EnumerateRoutine - Pointer to an enumeration routine to call
|
|
for each file and directory found.
|
|
|
|
EnumerateParameter - Uninterpreted 32-bit value that is passed
|
|
to the EnumerationRoutine each time it is called.
|
|
|
|
Return Value:
|
|
|
|
TRUE if operation was successful. Otherwise returns FALSE and
|
|
extended error information is available from GetLastError()
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL Result;
|
|
VIRTUAL_BUFFER Buffer;
|
|
PENUMERATE_DIRECTORY_STATE State;
|
|
PENUMERATE_DIRECTORY_STACK Stack;
|
|
WIN32_FIND_DATA FindFileData;
|
|
|
|
//
|
|
// Create a virtual buffer with an initial committed size of
|
|
// our directory state buffer, and a maximum reserved size of
|
|
// the longest possible full path based on the maximum depth
|
|
// we handle and the maximum length of each path component.
|
|
//
|
|
|
|
if (!CreateVirtualBuffer( &Buffer,
|
|
sizeof( ENUMERATE_DIRECTORY_STATE ),
|
|
sizeof( ENUMERATE_DIRECTORY_STATE ) +
|
|
MAX_DEPTH * MAX_PATH
|
|
)
|
|
) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// This buffer will be used to maintain a stack of directory
|
|
// search handles, as well as accumulate the full path string
|
|
// as we descend the directory tree.
|
|
//
|
|
|
|
State = (PENUMERATE_DIRECTORY_STATE)Buffer.Base;
|
|
State->Depth = 0;
|
|
Stack = &State->Stack[ 0 ];
|
|
|
|
//
|
|
// Enter a try ... finally block so we can insure that we clean
|
|
// up after ourselves on exit.
|
|
//
|
|
|
|
try {
|
|
//
|
|
// First translate the passed in DirectoryPath into a fully
|
|
// qualified path. This path will be the initial value in
|
|
// our path buffer. The initial allocation of the path buffer
|
|
// is big enough for this initial request, so does not need
|
|
// to be guarded by a try ... except clause.
|
|
//
|
|
|
|
if (GetFullPathName( DirectoryPath, MAX_PATH, State->Path, &Stack->PathEnd )) {
|
|
//
|
|
// Now enter a try ... except block that will be used to
|
|
// manage the commitment of space in the path buffer as
|
|
// we append subdirectory names and file names to it.
|
|
// Using the virtual buffer allows us to handle full
|
|
// path names up to 16KB in length, with an initial
|
|
// allocation of 4KB.
|
|
//
|
|
|
|
try {
|
|
//
|
|
// Walk the directory tree. The outer loop is executed
|
|
// once for each directory in the tree.
|
|
//
|
|
|
|
while (TRUE) {
|
|
startDirectorySearch:
|
|
//
|
|
// Find the end of the current path, and make sure
|
|
// there is a trailing path separator.
|
|
//
|
|
|
|
Stack->PathEnd = strchr( State->Path, '\0' );
|
|
if (Stack->PathEnd > State->Path && Stack->PathEnd[ -1 ] != '\\') {
|
|
*(Stack->PathEnd)++ = '\\';
|
|
}
|
|
|
|
//
|
|
// Now append the wild card specification that will
|
|
// let us enumerate all the entries in this directory.
|
|
// Call FindFirstFile to find the first entry in the
|
|
// directory.
|
|
//
|
|
|
|
strcpy( Stack->PathEnd, "*.*" );
|
|
Stack->FindHandle = FindFirstFile( State->Path,
|
|
&FindFileData
|
|
);
|
|
if (Stack->FindHandle != INVALID_HANDLE_VALUE) {
|
|
//
|
|
// Entry found. Now loop through the entire
|
|
// directory processing each entry found,
|
|
// including the first one.
|
|
//
|
|
do {
|
|
//
|
|
// Ignore bogus pseudo-directories that are
|
|
// returned by some file systems (e.g. FAT).
|
|
//
|
|
|
|
if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
|
|
(!strcmp( FindFileData.cFileName, "." ) ||
|
|
!strcmp( FindFileData.cFileName, ".." )
|
|
)
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Copy the file name portion from the current
|
|
// directory entry to the last component in the
|
|
// path buffer.
|
|
//
|
|
|
|
strcpy( Stack->PathEnd, FindFileData.cFileName );
|
|
|
|
//
|
|
// Call the supplied enumeration routine with the
|
|
// full path we have built up in the path buffer,
|
|
// the directory information for this directory
|
|
// entry and the supplied enumeration parameter.
|
|
//
|
|
|
|
(*EnumerateRoutine)( State->Path, &FindFileData, EnumerateParameter );
|
|
|
|
//
|
|
// If this is entry is a subdirectory, then it is
|
|
// time to recurse. Do this by incrementing the
|
|
// stack pointer and depth and jumping to the top
|
|
// of the outer loop to process current contents
|
|
// of the path buffer as a fully qualified name of
|
|
// a directory.
|
|
//
|
|
|
|
if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
Stack++;
|
|
State->Depth++;
|
|
goto startDirectorySearch;
|
|
restartDirectorySearch: ;
|
|
}
|
|
|
|
//
|
|
// Here to find the next entry in the current directory.
|
|
//
|
|
}
|
|
|
|
while ( FindNextFile( Stack->FindHandle, &FindFileData ) );
|
|
|
|
//
|
|
// No more entries in the current directory, so close
|
|
// the search handle and fall into the code that will
|
|
// pop our stack of directory seacrh handles.
|
|
|
|
FindClose( Stack->FindHandle );
|
|
}
|
|
|
|
//
|
|
// Here when done with a directory. See if we are pushed
|
|
// inside another directory. If not, then we are done
|
|
// enumerating the whole tree, so break out of the loop.
|
|
//
|
|
|
|
if (!State->Depth) {
|
|
Result = TRUE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// We were pushed within another directory search,
|
|
// so pop the stack to restore its search handle
|
|
// and path buffer position and resume the search
|
|
// within that directory.
|
|
|
|
State->Depth--;
|
|
--Stack;
|
|
goto restartDirectorySearch;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Any of the code that appends to the path buffer within
|
|
// the above try ... except clause can cause an access
|
|
// violation if the path buffer becomes longer than its
|
|
// current committed size. This exception filter
|
|
// will dynamically commit additional pages as needed
|
|
// and resume execution.
|
|
//
|
|
|
|
except( VirtualBufferExceptionFilter( GetExceptionCode(),
|
|
GetExceptionInformation(),
|
|
&Buffer
|
|
)
|
|
) {
|
|
//
|
|
// We will get here if the exception filter was unable to
|
|
// commit the memory.
|
|
//
|
|
|
|
Result = FALSE;
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// Initial GetFullPathName failed, so return a failure.
|
|
//
|
|
|
|
Result = FALSE;
|
|
}
|
|
}
|
|
finally {
|
|
//
|
|
// Here on our way out of the outer try ... finally block.
|
|
// Make sure all our search handles have been closed and then
|
|
// free the virtual buffer. The only way this code is not
|
|
// executed is if code within the try ... finally block
|
|
// called ExitThread or ExitProcess, or an external thread
|
|
// or process terminated this thread or process.
|
|
//
|
|
// In the case of process death, this is not a problem, because
|
|
// process terminate closes all open handles attached to the process
|
|
// and frees all private virtual memory that is part of the address
|
|
// space of the process.
|
|
//
|
|
// In the case ot thread death, the code below is not executed if
|
|
// the thread terminates via ExitThread in the context of the
|
|
// try .. finally or if an external thread, either in this process
|
|
// or another process called TerminateThread on this thread.
|
|
//
|
|
|
|
while (State->Depth--) {
|
|
--Stack;
|
|
FindClose( Stack->FindHandle );
|
|
}
|
|
|
|
FreeVirtualBuffer( &Buffer );
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
BOOL
|
|
ProcessCommandLineArguments(
|
|
int argc,
|
|
char *argv[]
|
|
)
|
|
{
|
|
BOOL Result;
|
|
LPSTR s;
|
|
|
|
Result = FALSE;
|
|
try {
|
|
if (argc < 1) {
|
|
return Result;
|
|
}
|
|
|
|
while (--argc) {
|
|
s = *++argv;
|
|
if (*s == '-' || *s == '/') {
|
|
while (*++s) {
|
|
switch( tolower( *s ) ) {
|
|
case 'm':
|
|
MappedFileIO = TRUE;
|
|
break;
|
|
|
|
case 'a':
|
|
ASyncIO = TRUE;
|
|
break;
|
|
|
|
case 's':
|
|
SyncIO = TRUE;
|
|
break;
|
|
|
|
case 'v':
|
|
Verbose = TRUE;
|
|
break;
|
|
|
|
case 'y':
|
|
IgnoreCase = TRUE;
|
|
break;
|
|
|
|
case 't':
|
|
if (--argc) {
|
|
NumberOfWorkerThreads = atoi( *++argv );
|
|
if (NumberOfWorkerThreads > 0 && NumberOfWorkerThreads < 128) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// fall through if -t argument missing.
|
|
|
|
case '?':
|
|
case 'h':
|
|
default:
|
|
return Result;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
if (SearchString == NULL) {
|
|
SearchString = s;
|
|
}
|
|
else
|
|
if (DirectoryPath == NULL) {
|
|
DirectoryPath = s;
|
|
}
|
|
else {
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
if (SearchString == NULL) {
|
|
return Result;
|
|
}
|
|
|
|
SearchStringLength = strlen( SearchString );
|
|
if (SearchStringLength == 0) {
|
|
return Result;
|
|
}
|
|
|
|
if (IgnoreCase) {
|
|
SearchFunction = _strnicmp;
|
|
}
|
|
else {
|
|
SearchFunction = strncmp;
|
|
}
|
|
|
|
if (DirectoryPath == NULL) {
|
|
DirectoryPath = ".";
|
|
}
|
|
|
|
if (!(MappedFileIO || ASyncIO || SyncIO)) {
|
|
MappedFileIO = TRUE;
|
|
}
|
|
|
|
if (Verbose) {
|
|
fprintf( stderr, "Directory Tree: %s\n", DirectoryPath );
|
|
fprintf( stderr, "Search String: '%s'\n", SearchString );
|
|
fprintf( stderr, "Case %ssensitive\n", IgnoreCase ? "in" : "" );
|
|
fprintf( stderr, "Number of Worker Threads: %u\n", NumberOfWorkerThreads );
|
|
if (MappedFileIO) {
|
|
fprintf( stderr, "Using Mapped File I/O\n" );
|
|
}
|
|
else
|
|
if (ASyncIO) {
|
|
fprintf( stderr, "Using ASynchronous File I/O\n" );
|
|
}
|
|
else
|
|
if (MappedFileIO) {
|
|
fprintf( stderr, "Using Synchronous File I/O\n" );
|
|
}
|
|
}
|
|
|
|
Result = TRUE;
|
|
return Result;
|
|
}
|
|
finally {
|
|
if (!Result) {
|
|
fprintf( stderr, "usage: PDC [-h] [-v] [-y] [-a | -s | -m] [-t n] SearchString [DirectoryPath]\n" );
|
|
fprintf( stderr, "Where...\n" );
|
|
fprintf( stderr, " -h - prints this message\n" );
|
|
fprintf( stderr, " -v - generates verbose output\n" );
|
|
fprintf( stderr, " -y - ignores case when doing comparision\n" );
|
|
fprintf( stderr, " -t - specifies the number of threads to use (defaults to 4 * number of processors)\n" );
|
|
fprintf( stderr, " -a - uses asynchronous file I/O\n" );
|
|
fprintf( stderr, " -s - uses synchronous file I/O\n" );
|
|
fprintf( stderr, " -m - uses mapped file I/O (default)\n" );
|
|
fprintf( stderr, " SearchString - specifies the text to search for\n" );
|
|
fprintf( stderr, " DirectoryPath - specifies the directory to start from (defaults to .)\n" );
|
|
}
|
|
}
|
|
}
|
|
|