/*++ Copyright (c) 2000-2001 Microsoft Corporation Module Name: exclproc.cpp Abstract: Exclude processing mechanism. Processes the FilesNotToBackup key and zero or more exclude files with exclude rules. Author: Stefan R. Steiner [ssteiner] 03-21-2000 Revision History: --*/ #include "stdafx.h" #include "match.h" #include static VOID FsdExpandEnvironmentStrings( IN LPCWSTR pwszInput, OUT CBsString &cwsExpandedStr ); static BOOL FsdEnsureLongNames( IN OUT CBsString& exclude_spec ); SFsdExcludeRule::~SFsdExcludeRule() { delete( psVolId ); psVolId = NULL; } /*++ Routine Description: Prints out information about one rule. If the rule caused files to be excluded it prints out those file too. Arguments: Return Value: --*/ VOID SFsdExcludeRule::PrintRule( IN FILE *fpOut, IN BOOL bInvalidRulePrint ) { if ( bInvalidRulePrint ) { if ( bInvalidRule ) fwprintf( fpOut, L"%-24s %-32s '%s'\n", cwsExcludeFromSource.c_str(), cwsExcludeDescription.c_str(), cwsExcludeRule.c_str() ); } else { // // Iterate though excluded file list // CBsString cwsExcludedFile; CVssDLListIterator< CBsString > cExcludedFilesIter( cExcludedFileList ); if ( cExcludedFilesIter.GetNext( cwsExcludedFile ) ) { // // At least one file excluded, print the header for the rule // fwprintf( fpOut, L"%-24s %-32s '%s'\n", cwsExcludeFromSource.c_str(), cwsExcludeDescription.c_str(), cwsExcludeRule.c_str() ); // // Now iterate // do { fwprintf( fpOut, L"\t%s\n", cwsExcludedFile.c_str() ); } while( cExcludedFilesIter.GetNext( cwsExcludedFile ) ); } } } CFsdExclusionManager::CFsdExclusionManager( IN CDumpParameters *pcDumpParameters ) : m_pcParams( pcDumpParameters ) { if ( !m_pcParams->m_bDontUseRegistryExcludes ) { ProcessRegistryExcludes( HKEY_LOCAL_MACHINE, L"HKEY_LOCAL_MACHINE" ); ProcessRegistryExcludes( HKEY_CURRENT_USER, L"HKEY_CURRENT_USER" ); } CBsString cwsEXEFileStreamExcludeFile( m_pcParams->m_cwsArgv0 + L":ExcludeList" ); if ( ProcessOneExcludeFile( cwsEXEFileStreamExcludeFile ) == FALSE ) m_pcParams->DumpPrint( L" NOTE: Exclude file: '%s' not found", cwsEXEFileStreamExcludeFile.c_str() ); ProcessExcludeFiles( m_pcParams->m_cwsFullPathToEXE ); CompileExclusionRules(); } CFsdExclusionManager::~CFsdExclusionManager() { SFsdExcludeRule *pER; // // Iterate through the exclude rule list and delete each element // CVssDLListIterator< SFsdExcludeRule * > cExclRuleIter( m_cCompleteExcludeList ); while( cExclRuleIter.GetNext( pER ) ) delete pER; } VOID CFsdExclusionManager::ProcessRegistryExcludes( IN HKEY hKey, IN LPCWSTR pwszFromSource ) { LPWSTR buffer ; HKEY key = NULL ; DWORD stat ; DWORD dwDisposition ; DWORD dwDataSize; DWORD dwIndex = 0; HRESULT hr = S_OK; m_pcParams->DumpPrint( L" Processing FilesNotToBackup reg key in %s", pwszFromSource ); buffer = new WCHAR[ FSD_MAX_PATH ]; if ( buffer == NULL ) throw E_OUTOFMEMORY; try { stat = ::RegOpenKeyEx( hKey, FSD_REG_EXCLUDE_PATH, 0, KEY_READ, &key ) ; dwIndex = 0 ; while ( stat == ERROR_SUCCESS ) { WCHAR pwszValue[ MAX_PATH ]; DWORD dwValSize = MAX_PATH; // prefix #118830 DWORD dwType; dwDataSize = FSD_MAX_PATH; // prefix #118830 stat = ::RegEnumValueW( key, dwIndex, pwszValue, &dwValSize, NULL, &dwType, (LPBYTE)buffer, &dwDataSize ) ; dwIndex++; if ( ( stat == ERROR_SUCCESS ) && ( dwType == REG_MULTI_SZ ) ) { LPWSTR p = buffer; while ( *p ) { SFsdExcludeRule *psExclRule; // // Now load up the exclude rule with the unprocessed // information // psExclRule = new SFsdExcludeRule; if ( psExclRule == NULL ) throw E_OUTOFMEMORY; psExclRule->cwsExcludeFromSource = pwszFromSource; psExclRule->cwsExcludeDescription = pwszValue; psExclRule->cwsExcludeRule = p; if ( m_pcParams->m_bPrintDebugInfo || m_pcParams->m_bNoHeaderFooter ) { m_pcParams->DumpPrint( L" \"%s\" \"%s\"", pwszValue, p ); } m_cCompleteExcludeList.AddTail( psExclRule ); p += ::wcslen( p ) + 1; } } } } catch ( HRESULT hrCaught ) { hr = hrCaught; } catch ( ... ) { hr = E_UNEXPECTED; } if ( key != NULL ) { ::RegCloseKey( key ) ; key = NULL ; } delete [] buffer ; if ( FAILED( hr ) ) throw hr; } VOID CFsdExclusionManager::ProcessExcludeFiles( IN const CBsString& cwsPathToExcludeFiles ) { HANDLE hFind = INVALID_HANDLE_VALUE; try { // // Iterate through all files in the directory looking for // files with .exclude extensions // DWORD dwRet; WIN32_FIND_DATAW sFindData; hFind = ::FindFirstFileExW( cwsPathToExcludeFiles + L"*.exclude", FindExInfoStandard, &sFindData, FindExSearchNameMatch, NULL, 0 ); if ( hFind == INVALID_HANDLE_VALUE ) { dwRet = ::GetLastError(); if ( dwRet == ERROR_NO_MORE_FILES || dwRet == ERROR_FILE_NOT_FOUND ) return; else { m_pcParams->ErrPrint( L"CFsdExclusionManager::ProcessExcludeFiles - FindFirstFileEx( '%s' ) returned: dwRet: %d, skipping looking for .exclude files", cwsPathToExcludeFiles.c_str(), ::GetLastError() ); return; } } // // Now run through the directory // do { // Check and make sure the file such as ".", ".." and dirs are not considered if( ::wcscmp( sFindData.cFileName, L".") != 0 && ::wcscmp( sFindData.cFileName, L"..") != 0 && !( sFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) { ProcessOneExcludeFile( cwsPathToExcludeFiles + sFindData.cFileName ); } } while ( ::FindNextFile( hFind, &sFindData ) ); } catch ( ... ) { m_pcParams->ErrPrint( L"CFsdExclusionManager::ProcessExcludeFiles: Caught an unknown exception, dirPath: '%s'", cwsPathToExcludeFiles.c_str() ); } if ( hFind != INVALID_HANDLE_VALUE ) ::FindClose( hFind ); } BOOL CFsdExclusionManager::ProcessOneExcludeFile( IN const CBsString& cwsExcludeFileName ) { FILE *fpExclFile; fpExclFile = ::_wfopen( cwsExcludeFileName, L"r" ); if ( fpExclFile == NULL ) { return FALSE; } m_pcParams->DumpPrint( L" Processing exclude file: '%s'", cwsExcludeFileName.c_str() ); CBsString cwsInputLine; while( ::fgetws( cwsInputLine.GetBuffer( FSD_MAX_PATH), FSD_MAX_PATH, fpExclFile ) ) { cwsInputLine.ReleaseBuffer(); cwsInputLine = cwsInputLine.Left( cwsInputLine.GetLength() - 1 ); // get rid of '\n' cwsInputLine.TrimLeft(); cwsInputLine.TrimRight(); // // See if it is a comment, either // or # // if ( cwsInputLine[ 0 ] == L'#' || cwsInputLine.Left( 2 ) == L"//" || cwsInputLine.IsEmpty() ) continue; if ( m_pcParams->m_bPrintDebugInfo || m_pcParams->m_bNoHeaderFooter ) { m_pcParams->DumpPrint( L" %s", cwsInputLine.c_str() ); } CBsString cwsLine( cwsInputLine ); SFsdExcludeRule *psExclRule; psExclRule = new SFsdExcludeRule; if ( psExclRule == NULL ) { ::fclose( fpExclFile ); throw E_OUTOFMEMORY; } INT iLeft; INT iRight; // // This is gross. With the updated string class, this can // be simplified. // iLeft = cwsLine.Find( L'\"' ); if ( iLeft != -1 ) { cwsLine = cwsLine.Mid( iLeft + 1 ); iRight = cwsLine.Find( L'\"' ); if ( iRight != -1 ) { psExclRule->cwsExcludeDescription = cwsLine.Left( iRight ); cwsLine = cwsLine.Mid( iRight + 1 ); iLeft = cwsLine.Find( L'\"' ); if ( iLeft != -1 ) { cwsLine = cwsLine.Mid( iLeft + 1 ); iRight = cwsLine.Find( L'\"' ); if ( iRight != -1 ) { psExclRule->cwsExcludeRule = cwsLine.Left( iRight ); psExclRule->cwsExcludeFromSource = cwsExcludeFileName.c_str() + cwsExcludeFileName.ReverseFind( L'\\' ) + 1; m_cCompleteExcludeList.AddTail( psExclRule ); continue; } } } } else { m_pcParams->ErrPrint( L"Parse error in exclusion rule file '%s', rule text '%s', skipping", cwsExcludeFileName.c_str(), cwsInputLine.c_str() ); } delete psExclRule; } ::fclose( fpExclFile ); return TRUE; } VOID CFsdExclusionManager::CompileExclusionRules() { SFsdExcludeRule *psER; // // Iterate through the exclude rule list and compile each rule // CVssDLListIterator< SFsdExcludeRule * > cExclRuleIter( m_cCompleteExcludeList ); CBsString cws; if ( m_pcParams->m_bPrintDebugInfo ) wprintf( L"Exclusion rule debug info:\n" ); while( cExclRuleIter.GetNext( psER ) ) { INT i; ::FsdExpandEnvironmentStrings( psER->cwsExcludeRule, cws ); if ( m_pcParams->m_bPrintDebugInfo ) { wprintf( L"\t%s : %s : %s : %s", psER->cwsExcludeFromSource.c_str(), psER->cwsExcludeDescription.c_str(), psER->cwsExcludeRule.c_str(), cws.c_str() ); } // // Get rid of leading spaces and lower case the whole mess // cws.TrimLeft(); cws.MakeUpper(); // // First see if /s is at end of string // i = cws.Find( L"/S" ); if ( i > 0 ) { cws = cws.Left( i ); psER->bInclSubDirs = TRUE; } cws.TrimRight(); // // Now see if there are any wildcards // i = cws.FindOneOf( L"*?" ); if ( i != -1 ) { psER->bWCInFileName = TRUE; } // // Now see if this is for any volume // if ( cws.GetLength() >= 2 && cws[0] == L'\\' && cws[1] != L'\\' ) psER->bAnyVol = TRUE; else if ( cws.GetLength() >= 2 && cws[1] != L':' ) psER->bAnyVol = TRUE; if ( psER->bAnyVol ) { if ( cws[0] == L'\\' ) { // Get rid of first '\' cws = cws.Mid( 1 ); } } else { // // Specific volume case // CBsString cwsVolPath; psER->psVolId = new SFsdVolumeId; if ( psER->psVolId == NULL ) // Prefix 118832 { m_pcParams->ErrPrint( L"CFsdExclusionManager::CompileExclusionRules - out of memory" ); throw E_OUTOFMEMORY; // Prefix #118832 } if ( CFsdVolumeStateManager::GetVolumeIdAndPath( m_pcParams, cws, psER->psVolId, cwsVolPath ) != ERROR_SUCCESS ) psER->bInvalidRule = TRUE; else { // // Slice off the volume part of the path // cws = cws.Mid( cwsVolPath.GetLength() ); } } INT iFileNameOffset; iFileNameOffset = cws.ReverseFind( L'\\' ); if ( iFileNameOffset == -1 ) { // // No dirpath // // psER->cwsDirPath = L"\\"; psER->cwsFileNamePattern = cws; } else { psER->cwsFileNamePattern = cws.Mid( iFileNameOffset + 1 ); psER->cwsDirPath = cws.Left( iFileNameOffset + 1 ); } // // Now convert the file name pattern into a form that the pattern matcher // can use. // ::FsdRtlConvertWildCards( psER->cwsFileNamePattern ); if ( m_pcParams->m_bPrintDebugInfo ) { if ( psER->bInclSubDirs ) wprintf( L" - SubDir" ); if ( psER->bWCInFileName ) wprintf( L" - WC" ); if ( psER->bAnyVol ) wprintf( L" - AnyVol" ); wprintf( L" - VolId: 0x%08x, DirPath: '%s', FileName: '%s'", ( psER->psVolId ) ? psER->psVolId->m_dwVolSerialNumber : 0xFFFFFFFF, psER->cwsDirPath.c_str(), psER->cwsFileNamePattern.c_str() ); if ( psER->bInvalidRule ) wprintf( L" - ERROR, invalid rule" ); wprintf( L"\n" ); } } } VOID CFsdExclusionManager::GetFileSystemExcludeProcessor( IN CBsString cwsVolumePath, IN SFsdVolumeId *psVolId, OUT CFsdFileSystemExcludeProcessor **ppcFSExcludeProcessor ) { CFsdFileSystemExcludeProcessor *pExclProc; *ppcFSExcludeProcessor = NULL; // // Get a new exclude processor for the file system // pExclProc = new CFsdFileSystemExcludeProcessor( m_pcParams, cwsVolumePath, psVolId ); if ( pExclProc == NULL ) { m_pcParams->ErrPrint( L"CFsdExclusionManager::CFsdGetFileSystemExcludeProcessor - Could not new a CFsdFileSystemExcludeProcessor object" ); throw E_OUTOFMEMORY; } SFsdExcludeRule *pER; // // Now go through the complete exclude list to find exclude rules that are relevant to // this file system // CVssDLListIterator< SFsdExcludeRule * > cExclRuleIter( m_cCompleteExcludeList ); while( cExclRuleIter.GetNext( pER ) ) { if ( !pER->bInvalidRule ) { if ( pER->bAnyVol || pER->psVolId->IsEqual( psVolId ) ) { pExclProc->m_cFSExcludeList.AddTail( pER ); } } } *ppcFSExcludeProcessor = pExclProc; } /*++ Routine Description: Goes through the list of exclusion rules and dumps information about each. Arguments: Return Value: --*/ VOID CFsdExclusionManager::PrintExclusionInformation() { SFsdExcludeRule *pER; CVssDLListIterator< SFsdExcludeRule * > cExclRuleIter( m_cCompleteExcludeList ); m_pcParams->DumpPrint( L"" ); m_pcParams->DumpPrint( L"----------------------------------------------------------------------------" ); m_pcParams->DumpPrint( L"Invalid exclusion rules (invalid because volume not found or parsing error)" ); m_pcParams->DumpPrint( L"----------------------------------------------------------------------------" ); m_pcParams->DumpPrint( L"From Application Exclusion rule" ); while( cExclRuleIter.GetNext( pER ) ) pER->PrintRule( m_pcParams->GetDumpFile(), TRUE ); m_pcParams->DumpPrint( L"" ); m_pcParams->DumpPrint( L"----------------------------------------------------------------------------" ); m_pcParams->DumpPrint( L"Files excluded by valid exclusion rule" ); m_pcParams->DumpPrint( L"----------------------------------------------------------------------------" ); m_pcParams->DumpPrint( L"From Application Exclusion rule" ); cExclRuleIter.Reset(); while( cExclRuleIter.GetNext( pER ) ) pER->PrintRule( m_pcParams->GetDumpFile(), FALSE ); } CFsdFileSystemExcludeProcessor::CFsdFileSystemExcludeProcessor( IN CDumpParameters *pcDumpParameters, IN const CBsString& cwsVolumePath, IN SFsdVolumeId *psVolId ) : m_pcParams( pcDumpParameters ), m_cwsVolumePath( cwsVolumePath), m_psVolId( NULL ) { m_psVolId = new SFsdVolumeId; if ( m_psVolId == NULL ) // Prefix #118829 throw E_OUTOFMEMORY; *m_psVolId = *psVolId; } CFsdFileSystemExcludeProcessor::~CFsdFileSystemExcludeProcessor() { delete m_psVolId; } BOOL CFsdFileSystemExcludeProcessor::IsExcludedFile( IN const CBsString &cwsFullDirPath, IN DWORD dwEndOfVolMountPointOffset, IN const CBsString &cwsFileName ) { BOOL bFoundMatch = FALSE; SFsdExcludeRule *pER; CBsString cwsUpperFileName( cwsFileName ); CBsString cwsDirPath( cwsFullDirPath.Mid( dwEndOfVolMountPointOffset ) ); cwsUpperFileName.MakeUpper(); // Make uppercased for match check cwsDirPath.MakeUpper(); // wprintf( L"Exclude proc: DirPath: %s, fileName: %s\n", cwsDirPath.c_str(), cwsUpperFileName.c_str() ); CVssDLListIterator< SFsdExcludeRule * > cExclRuleIter( m_cFSExcludeList ); while( !bFoundMatch && cExclRuleIter.GetNext( pER ) ) { if ( pER->bInclSubDirs ) { // // First check most common case \XXX /s // if ( pER->cwsDirPath.GetLength() != 0 ) { if ( ::wcsncmp( pER->cwsDirPath.c_str(), cwsDirPath.c_str(), pER->cwsDirPath.GetLength() ) != 0 ) continue; } } else { // // Fixed path check // if ( pER->cwsDirPath != cwsDirPath ) { continue; } } if ( pER->bWCInFileName ) { // // Pattern match check // if ( ::FsdRtlIsNameInExpression( pER->cwsFileNamePattern, cwsUpperFileName ) ) bFoundMatch = TRUE; } else { // // Constant string match // if ( pER->cwsFileNamePattern == cwsUpperFileName ) bFoundMatch = TRUE; } } if ( bFoundMatch ) { pER->cExcludedFileList.AddTail( cwsFullDirPath + cwsFileName ); if ( m_pcParams->m_bPrintDebugInfo ) wprintf( L" EXCLUDING: %s%s\n", cwsFullDirPath.c_str(), cwsFileName.c_str() ); } return bFoundMatch; } static VOID FsdExpandEnvironmentStrings( IN LPCWSTR pwszInput, OUT CBsString &cwsExpandedStr ) { BOOL isOK = FALSE; LPWSTR pwszBuffer; DWORD dwSize = ::ExpandEnvironmentStringsW( pwszInput, NULL, 0 ) ; if ( pwszBuffer = cwsExpandedStr.GetBufferSetLength( dwSize + 1 ) ) { isOK = ( 0 != ::ExpandEnvironmentStringsW( pwszInput, pwszBuffer, dwSize ) ) ; cwsExpandedStr.ReleaseBuffer( ) ; } if ( !isOK ) { // have never seen ExpandEnvironmentStrings fail... even with undefined env var... but just in case cwsExpandedStr = pwszInput ; } ::FsdEnsureLongNames( cwsExpandedStr ); } /*++ Routine Description: Originally from NtBackup. This takes a path with short name components and expands them to long name components. The path obviously has to exist on the system to expand short names. Uses a little shell magic (yuck) to translate it into a long name. Arguments: Return Value: TRUE if expanded properly --*/ static BOOL FsdEnsureLongNames( IN OUT CBsString& exclude_spec ) { IShellFolder * desktop ; ITEMIDLIST * id_list ; ULONG parsed_ct = 0 ; BOOL isOK = FALSE ; // initialize it, prefix bug # 180281 CBsString path ; int last_slash ; // strip off the filename, and all that other detritus... path = exclude_spec ; if ( -1 != ( last_slash = path.ReverseFind( TEXT( '\\' ) ) ) ) { path = path.Left( last_slash ) ; if ( SUCCEEDED( SHGetDesktopFolder( &desktop ) ) ) { WCHAR * ptr = path.GetBufferSetLength( FSD_MAX_PATH ) ; if ( SUCCEEDED( desktop->ParseDisplayName( NULL, NULL, ptr, &parsed_ct, &id_list, NULL ) ) ) { IMalloc * imalloc ; isOK = SHGetPathFromIDList( id_list, ptr ) ; SHGetMalloc( &imalloc ) ; imalloc->Free( id_list ) ; imalloc->Release( ) ; } path.ReleaseBuffer( ) ; desktop->Release( ) ; } if ( isOK ) { // put it back together with the new & improved path... exclude_spec = path + exclude_spec.Mid( last_slash ) ; } } return isOK; }