/*++ 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; iNumberOfWorkerThreads++; 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; iNumberOfWorkerThreads; 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" ); } } }