/*++ Copyright (c) 1995 Microsoft Corporation Module Name : getdirp.cxx Abstract: This module implements the functions for getting directory listings and transparently caching them. ( This uses OS specific functions to obtain the directory). Author: Murali R. Krishnan ( MuraliK ) 13-Jan-1995 Project: Tsunami Lib ( Common caching and directory functions for Internet Services) Functions Exported: BOOL TsGetDirectoryListingA() BOOL TsFreeDirectoryListing() int __cdecl AlphaCompareFileBothDirInfo( IN const void * pvFileInfo1, IN const void * pvFileInfo2) TS_DIRECTORY_HEADER::ReadFromNtDirectoryFile( IN LPCWSTR pwszDirectoryName ) TS_DIRECTORY_HEADER::BuildInfoPointers( IN LPCWSTR pwszDirectoryName ) TS_DIRECTORY_HEADER::CleanupThis() Revision History: --*/ /************************************************************ * Include Headers ************************************************************/ # include "tsunamip.hxx" # include # include # include /************************************************************ * Type Definitions ************************************************************/ #define DIRECTORY_BUFFER_SIZE 8160 /* < 8192 bytes */ /************************************************************ * Functions ************************************************************/ BOOL FreeDirectoryHeaderContents( PVOID pvOldBlock ); dllexp BOOL TsGetDirectoryListing( IN const TSVC_CACHE &tsCache, IN PCSTR pszDirectoryName, IN HANDLE ListingUser, OUT PTS_DIRECTORY_HEADER * ppTsDirectoryHeader ) /*++ This function obtains the directory listing for dir specified in pszDirectoryName. Arguments: tsCache Cache structure which is used for lookup pwszDirectoryName pointer to string containing the directory name ListingUser Handle for the user opening the directory ppTsDirectoryHeader pointer to pointer to class containing directory information. Filled on successful return. On failure this will be NULL Returns: TRUE on success and FALSE if there is a failure. --*/ { ASSERT( tsCache.IsValid() ); ASSERT( pszDirectoryName != NULL ); ASSERT( ppTsDirectoryHeader != NULL); PVOID pvBlob = NULL; ULONG ulSize = 0; BOOL bSuccess; // // First, check to see if we have already cached a listing of this // directory. // *ppTsDirectoryHeader = NULL; bSuccess = TsCheckOutCachedBlob( tsCache, pszDirectoryName, RESERVED_DEMUX_DIRECTORY_LISTING, ( PVOID * )&pvBlob, &ulSize ); if ( bSuccess ) { ASSERT( BLOB_IS_OR_WAS_CACHED( pvBlob ) ); *ppTsDirectoryHeader = (PTS_DIRECTORY_HEADER )pvBlob; ASSERT ( (*ppTsDirectoryHeader)->IsValid()); // // Make sure the user tokens match // if ( hListingUser == (*ppTsDirectoryHeader)->QueryListingUser() ) { IF_DEBUG( DIR_LIST) { DBGPRINTF( (DBG_CONTEXT, " Obtained DirectoryListing (%s) from Cache ( %08x)\n", pszDirectoryName, *ppTsDirectoryHeader)); (*ppTsDirectoryHeader)->Print(); } return TRUE; } // // User token doesn't match, don't return it // bSuccess = TsCheckInCachedBlob( pvBlob ); ASSERT( bSuccess ); } // // The block was not present in cache. // Obtain a fresh copy of the directory listing and cache it. // IF_DEBUG( DIR_LIST) { DBGPRINTF( (DBG_CONTEXT, "Missing DirListing (%s) in cache. Generating newly\n", pszDirectoryName)); } *ppTsDirectoryHeader = TsGetFreshDirectoryHeader( tsCache, pszDirectoryName, hListingUser ); bSuccess = ( *ppTsDirectoryHeader != NULL); return ( bSuccess); } // TsGetDirectoryListing dllexp BOOL TsFreeDirectoryListing( IN const TSVC_CACHE & tsCache, IN PTS_DIRECTORY_HEADER pDirectoryHeader ) { BOOL fReturn; BOOL fCached = BLOB_IS_OR_WAS_CACHED( (PVOID ) pDirectoryHeader); IF_DEBUG( DIR_LIST) { DBGPRINTF( ( DBG_CONTEXT, "TsFreeDirectoryListing( %08x) called. Cached = %d\n", pDirectoryHeader, fCached)); pDirectoryHeader->Print(); } if ( fCached ) { fReturn = TsCheckInCachedBlob( ( PVOID )pDirectoryHeader ); } else { fReturn = TsFree( tsCache, ( PVOID )pDirectoryHeader ); } return( fReturn); } // TsFreeDirectoryListing() BOOL FreeDirectoryHeaderContents( PVOID pvOldBlock ) { PTS_DIRECTORY_HEADER pDirectoryHeader; pDirectoryHeader = ( PTS_DIRECTORY_HEADER )pvOldBlock; pDirectoryHeader->CleanupThis(); // // The item may never have been added to the cache, don't // count it in this case // if ( BLOB_IS_OR_WAS_CACHED( pvOldBlock ) ) { DEC_COUNTER( BLOB_GET_SVC_ID( pvOldBlock ), CurrentDirLists ); } return ( TRUE); } // FreeDirectoryHeaderContents() int __cdecl AlphaCompareFileBothDirInfo( IN const void * pvFileInfo1, IN const void * pvFileInfo2) { const FILE_BOTH_DIR_INFORMATION * pFileInfo1 = *((const FILE_BOTH_DIR_INFORMATION **) pvFileInfo1); const FILE_BOTH_DIR_INFORMATION * pFileInfo2 = *((const FILE_BOTH_DIR_INFORMATION **) pvFileInfo2); ASSERT( pFileInfo1 != NULL && pFileInfo2 != NULL); return ( lstrcmp( (LPCSTR )pFileInfo1->FileName, (LPCSTR )pFileInfo2->FileName)); } // AlphaCompareFileBothDirInfo() BOOL SortInPlaceFileInfoPointers( IN OUT PFILE_BOTH_DIR_INFORMATION * prgFileInfo, IN int nEntries, IN PFN_CMP_FILE_BOTH_DIR_INFO pfnCompare) /*++ This is a generic function to sort the pointers to file information array in place using pfnCompare to compare the records for ordering. Returns: TRUE on success and FALSE on failure. --*/ { DWORD dwTime; #ifdef INSERTION_SORT int idxInner; int idxOuter; dwTime = GetTickCount(); // // A simple insertion sort is performed. May be modified in future. // for( idxOuter = 1; idxOuter < nEntries; idxOuter++) { for( idxInner = idxOuter; idxInner > 0; idxInner-- ) { int iCmp = ( *pfnCompare)( prgFileInfo[ idxInner - 1], prgFileInfo[ idxInner]); if ( iCmp <= 0) { // // The entries in prgFileInfo[0 .. idxOuter] are in order. // Stop bubbling the outer down. // break; } else { // // Swap the two entries. idxInner, idxInner - 1 // PFILE_BOTH_DIR_INFORMATION pFInfoTmp; pFInfoTmp = prgFileInfo[ idxInner - 1]; prgFileInfo[ idxInner - 1] = prgFileInfo[idxInner]; prgFileInfo[ idxInner] = pFInfoTmp; } } // inner for } // for dwTime = GetTickCount() - dwTime; # else IF_DEBUG( DIR_LIST) { DBGPRINTF( ( DBG_CONTEXT, "Qsorting the FileInfo Array %08x ( Total = %d)\n", prgFileInfo, nEntries)); } dwTime = GetTickCount(); qsort( (PVOID ) prgFileInfo, nEntries, sizeof( PFILE_BOTH_DIR_INFORMATION), pfnCompare); dwTime = GetTickCount() - dwTime; # endif // INSERTION_SORT IF_DEBUG( DIR_LIST) { DBGPRINTF( ( DBG_CONTEXT, " Time to sort %d entries = %d\n", nEntries, dwTime)); } return ( TRUE); } // SortInPlaceFileInfoPointers() /********************************************************************** * TS_DIRECTORY_HEADER related member functions **********************************************************************/ inline USHORT ConvertUnicodeToAnsiInPlace( IN OUT LPWSTR pwszUnicode, IN USHORT usLen) /*++ Converts given Unicode strings to Ansi In place and returns the length of the modified string. --*/ { CHAR achAnsi[MAX_PATH+1]; DWORD cch; if ( usLen > sizeof(achAnsi) ) { ASSERT( FALSE ); *pwszUnicode = L'\0'; return 0; } // // usLen is a byte count and the unicode string isn't terminated // cch = WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK, pwszUnicode, usLen / sizeof(WCHAR), achAnsi, sizeof( achAnsi ), NULL, NULL ); if ( !cch || (cch + 1) > sizeof( achAnsi ) ) { ASSERT( FALSE ); *pwszUnicode = L'\0'; return 0; } achAnsi[cch] = '\0'; RtlCopyMemory( pwszUnicode, achAnsi, cch + 1 ); return (USHORT) cch; } // ConvertUnicodeToAnsiInPlace() BOOL TS_DIRECTORY_HEADER::ReadFromNtDirectoryFile( IN LPCWSTR pwszDirectoryName, IN OUT DWORD * pcbMemUsed ) /*++ Opens and reads the directory file for given directory to obtain information about files and directories in the dir. Returns: TRUE on success and FALSE on failure. Use GetLastError() for further error information. --*/ { BOOL fReturn = TRUE; // default assumed. UNICODE_STRING PathName; RTL_RELATIVE_NAME RelativeName; OBJECT_ATTRIBUTES Obja; HANDLE hFindFile = INVALID_HANDLE_VALUE; NTSTATUS Status; IO_STATUS_BLOCK IoStatusBlock; BOOL fFirstTime; DWORD cbExtraMem = 0; PFILE_BOTH_DIR_INFORMATION pFileDirInfo; PFILE_BOTH_DIR_INFORMATION pFileDirInfoPrior; // // Initialize the variables properly // memset( (PVOID ) &PathName, 0, sizeof(PathName)); memset( (PVOID ) &RelativeName, 0, sizeof(RelativeName)); // // Convert the DOS name of directory to NT name for NT API to open it. // fReturn = RtlDosPathNameToNtPathName_U( pwszDirectoryName, &PathName, NULL, &RelativeName ); // // If translation fails or // If this directory name is in the form \, // the caller has messed up. // if ( !fReturn || RelativeName.RelativeName.Length != 0) { SetLastError(ERROR_PATH_NOT_FOUND); if ( PathName.Buffer != NULL) { // free up the space. RtlFreeHeap( RtlProcessHeap(), 0, PathName.Buffer); } return( fReturn); } // // Remember that we need to free the buffer containing the NT name. // i.e. PathName.Buffer // ASSERT( RelativeName.ContainingDirectory == NULL ); ASSERT( RelativeName.RelativeName.Length == 0); InitializeObjectAttributes( &Obja, &PathName, OBJ_CASE_INSENSITIVE, RelativeName.ContainingDirectory, NULL ); // // Open the directory for list access // Status = NtOpenFile( &hFindFile, FILE_LIST_DIRECTORY | SYNCHRONIZE, &Obja, &IoStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT ); // // The Buffer is not required any more. Free it before checking status. // RtlFreeHeap( RtlProcessHeap(), 0, PathName.Buffer ); PathName.Buffer = NULL; if ( !NT_SUCCESS(Status) ) { // // The full path does not refer to a directory. This could be a // device, but (unlike FindFirstFile()...) we don't care. If it's // not a directory, we don't list it. // IF_DEBUG( DIR_LIST) { DBGPRINTF( ( DBG_CONTEXT, "Failed to open Dir %ws. Handle = %x\n", pwszDirectoryName, hFindFile)); } SetLastError( ERROR_PATH_NOT_FOUND); return ( FALSE); } InitializeListHead( &m_listDirectoryBuffers); // // Loop through getting subsequent entries in the directory. // for( fFirstTime = TRUE; ; fFirstTime = FALSE) { PVOID pvBuffer; // // Get the next chunk of directory information. // Obtained in a buffer with LIST_ENTRY as the first member of buffer // #define DIR_ALLOC_SIZE (DIRECTORY_BUFFER_SIZE + sizeof (LIST_ENTRY)) pvBuffer = ALLOC( DIR_ALLOC_SIZE ); cbExtraMem += DIR_ALLOC_SIZE; if ( pvBuffer == NULL ) { // // Allocation failure. // SetLastError( ERROR_NOT_ENOUGH_MEMORY); fReturn = FALSE; break; // Get out of the loop with failure. } pFileDirInfo = ( PFILE_BOTH_DIR_INFORMATION ) ((( PCHAR ) pvBuffer) + sizeof( LIST_ENTRY ) ); Status = NtQueryDirectoryFile( hFindFile, // fileHandle NULL, // Event NULL, // Apc Routine NULL, // ApcContext &IoStatusBlock, // PIoStatusBlock pFileDirInfo, // PFileInfo DIRECTORY_BUFFER_SIZE, // Len FileBothDirectoryInformation, //Class FALSE, // fSingleEntry ? NULL, //FileName fFirstTime ); // RestartScan ? // // If the NT API returns STATUS_NO_MORE_FILES, then it did not use // our buffer at all. We can just free it. // Now's the time that we leave this loop, and stop reading the // directory file. // if ( Status == STATUS_NO_MORE_FILES ) { FREE( pvBuffer ); // // Decrement the memory size so we don't get charged for it // cbExtraMem -= DIR_ALLOC_SIZE; fReturn = TRUE; break; } if ( NT_SUCCESS( Status ) || ( Status == STATUS_BUFFER_OVERFLOW ) ) { ULONG Offset; // // The buffer contains directory entries. // Place it on the list of such buffers for this directory. // InsertBufferInTail( (PLIST_ENTRY ) pvBuffer); pFileDirInfoPrior = NULL; // // Scan thru the entries in the buffer, // truncate the last entry ( if partial) and // convert the Unicode strings, inplace to ansi strings. // do { pFileDirInfoPrior = pFileDirInfo; Offset = pFileDirInfo->NextEntryOffset; if ( ( Offset == 0 ) && ( Status == STATUS_BUFFER_OVERFLOW ) ) { // // If Status==STATUS_BUFFER_OVERFLOW, the last entry in // buffer may be a partial entry, broken on boundary of // buffer. The NT API will give us this entry again next // time around, so for now we patch the buffer up to // appear as if it does not contain the partial entry. // if ( pFileDirInfoPrior != NULL ) { pFileDirInfoPrior->NextEntryOffset = 0; } else { // // Some fatal problem. Should get out this loop. // BREAKPOINT(); fReturn = FALSE; goto Failure; } } else { IncrementDirEntries(); if ( pFileDirInfo->FileNameLength != 0) { pFileDirInfo->FileNameLength = ConvertUnicodeToAnsiInPlace( (LPWSTR ) pFileDirInfo->FileName, (USHORT ) pFileDirInfo->FileNameLength); } if ( pFileDirInfo->ShortNameLength != 0) { pFileDirInfo->ShortNameLength = (CCHAR ) ConvertUnicodeToAnsiInPlace( (LPWSTR ) pFileDirInfo->ShortName, pFileDirInfo->ShortNameLength); } // Get the next entry in buffer pFileDirInfo = ( PFILE_BOTH_DIR_INFORMATION ) ((( PCHAR )pFileDirInfo ) + Offset ); } } while ( Offset != 0); Status = STATUS_SUCCESS; } if ( !NT_SUCCESS(Status) ) { fReturn = FALSE; break; } } Failure: ASSERT( PathName.Buffer == NULL); if ( hFindFile != INVALID_HANDLE_VALUE) { NtClose( hFindFile ); hFindFile = INVALID_HANDLE_VALUE; } *pcbMemUsed += cbExtraMem; return ( fReturn); } // TS_DIRECTORY_HEADER::ReadFromNtDirectoryFile() VOID TS_DIRECTORY_HEADER::CleanupThis( VOID) { PLIST_ENTRY pEntry; PLIST_ENTRY pNextEntry; for ( pEntry = QueryDirBuffersListEntry()->Flink; pEntry != QueryDirBuffersListEntry(); pEntry = pNextEntry ) { pNextEntry = pEntry->Flink; // // The buffers are allocated such that first member of buffer is // LIST_ENTRY object. Free it entirely. // FREE( pEntry ); } InitializeListHead( QueryDirBuffersListEntry()); if ( m_ppFileInfo != NULL) { FREE( m_ppFileInfo); m_ppFileInfo = NULL; } m_hListingUser = INVALID_HANDLE_VALUE; m_nEntries = 0; return; } // TS_DIRECTORY_HEADER::CleanupThis() BOOL TS_DIRECTORY_HEADER::BuildFileInfoPointers( IN OUT DWORD * pcbMemUsed ) /*++ This constructs the indirection pointers from the buffers containing the file information. This array of indirection enables faster access to the file information structures stored. Should be always called after ReadFromNtDirectoryFile() to construct the appropriate pointers. Returns: TRUE on success and FALSE if there are any failures. --*/ { BOOL fReturn = FALSE; DWORD cbAlloc; ASSERT( QueryNumEntries() != 0); // Any directory will atleast have "." // // Alloc space for holding the pointers for numEntries pointers. // cbAlloc = QueryNumEntries() * sizeof( PFILE_BOTH_DIR_INFORMATION ); m_ppFileInfo = (PFILE_BOTH_DIR_INFORMATION *) ALLOC( cbAlloc ); if ( m_ppFileInfo != NULL ) { int index; PLIST_ENTRY pEntry; ULONG Offset; PFILE_BOTH_DIR_INFORMATION pFileDirInfo; // // Get the link to first buffer and start enumeration. // pEntry = QueryDirBuffersListEntry()->Flink; pFileDirInfo = (PFILE_BOTH_DIR_INFORMATION )( pEntry + 1 ); for ( index = 0; index < QueryNumEntries(); index++ ) { ASSERT( pEntry != QueryDirBuffersListEntry()); m_ppFileInfo[index] = pFileDirInfo; // store the pointer. Offset = pFileDirInfo->NextEntryOffset; if ( Offset != 0 ) { pFileDirInfo = (PFILE_BOTH_DIR_INFORMATION ) ((( PCHAR )pFileDirInfo ) + Offset ); } else { // // we are moving to the next buffer. // pEntry = pEntry->Flink; if ( pEntry == QueryDirBuffersListEntry()) { ASSERT( index == QueryNumEntries() - 1); break; } pFileDirInfo = ( PFILE_BOTH_DIR_INFORMATION )( pEntry + 1 ); } } // for ASSERT( Offset == 0 ); fReturn = SortInPlaceFileInfoPointers( m_ppFileInfo, QueryNumEntries(), AlphaCompareFileBothDirInfo); } // valid alloc of the pointers. *pcbMemUsed += cbAlloc; return ( fReturn); } // TS_DIRECTORY_HEADER::BuildFileInfoPointers() # if DBG VOID TS_DIRECTORY_HEADER::Print( VOID) const { DBGPRINTF( ( DBG_CONTEXT, "Printing TS_DIRECTORY_HEADER ( %08x).\n", this)); DBGPRINTF( ( DBG_CONTEXT, "ListingUser Handle = %08x\t Num Entries = %08x\n", m_hListingUser, m_nEntries)); DBGPRINTF( ( DBG_CONTEXT, "Pointer to array of indirection pointers %08x\n", m_ppFileInfo)); // // The buffers containing the data of the file information not printed // return; } // TS_DIRECTORY_HEADER::Print() # endif // DBG /************************ End of File ***********************/