/*++ Copyright (c) 2001 Microsoft Corporation Module Name: cleanup.cpp Abstract: This file implements the BITS server extensions cleanup worker --*/ #include "precomp.h" const UINT64 NanoSec100PerSec = 10000000; //no of 100 nanosecs per second inline UINT64 FILETIMEToUINT64( const FILETIME & FileTime ) { ULARGE_INTEGER LargeInteger; LargeInteger.HighPart = FileTime.dwHighDateTime; LargeInteger.LowPart = FileTime.dwLowDateTime; return LargeInteger.QuadPart; }; class PollKillError { public: HRESULT m_Hr; PollKillError( HRESULT Hr ) : m_Hr( Hr ) { } }; class CleanupWorker { public: CleanupWorker( BOOL DeleteAll, HWND hwnd, const WCHAR* Path, const WCHAR *WorkItemName, const WCHAR *GuidString ); ~CleanupWorker(); void DoIt(); private: BOOL m_DeleteAll; HWND m_hwnd; const WCHAR * m_Path; const WCHAR * m_WorkItemName; const WCHAR * m_GuidString; const WCHAR * m_ADSIPath; IADs * m_VDir; BSTR m_VDirPath; BSTR m_SessionDirectory; BSTR m_UNCUsername; BSTR m_UNCPassword; UINT64 m_CleanupThreshold; StringHandle m_SessionDirPath; StringHandle m_RequestsDirPath; StringHandle m_RepliesDirPath; VARIANT m_vt; HANDLE m_UserToken; HANDLE m_EventLog; BSTR m_BITSCleanupWorkItemKeyBSTR; BSTR m_BITSUploadEnabledBSTR; BSTR m_BITSSessionTimeoutBSTR; BSTR m_PathBSTR; BSTR m_BITSSessionDirectoryBSTR; BSTR m_UNCUserNameBSTR; BSTR m_UNCPasswordBSTR; void PollKill(); void DeleteDirectoryAndFiles( StringHandle Directory ); bool DirectoryExists( StringHandle Directory ); UINT64 LastDirectoryTime( StringHandle Directory ); void RemoveSession( StringHandle SessionGuid ); void RemoveSessions( bool SecondPass ); void RemoveConnectionsFromTree( const WCHAR * DirectoryPath, bool IsConnectionDirectory, const WCHAR * FileSystemPath = NULL ); void RemoveConnection( const WCHAR * ConnectionDirectory, const WCHAR *FilesystemPath, const WCHAR * SessionGuid ); void LogDeletedJob( const WCHAR *SessionGuid ); void LogUnableToRemoveSession( const WCHAR *SessionGuid, HRESULT Hr ); void LogUnexpectedError( HRESULT Hr ); void LogUnableToScanDirectory( const WCHAR *Path, HRESULT Hr ); }; void BITSSetCurrentThreadToken( HANDLE hToken ) { if ( !SetThreadToken( NULL, hToken ) ) { for( unsigned int i = 0; i < 100; i ++ ) { Sleep( 10 ); if ( SetThreadToken( NULL, hToken ) ) return; } TerminateProcess( NULL, GetLastError() ); } } CleanupWorker::CleanupWorker( BOOL DeleteAll, HWND hwnd, const WCHAR* Path, const WCHAR* WorkItemName, const WCHAR* GuidString ) : m_DeleteAll( DeleteAll ), m_hwnd( hwnd ), m_Path( Path ), m_WorkItemName( WorkItemName ), m_GuidString( GuidString ), m_ADSIPath( NULL ), m_VDir( NULL ), m_VDirPath( NULL ), m_SessionDirectory( NULL ), m_CleanupThreshold( 0 ), m_UNCUsername( NULL ), m_UNCPassword( NULL ), m_UserToken( NULL ), m_EventLog( NULL ), m_BITSCleanupWorkItemKeyBSTR( NULL ), m_BITSUploadEnabledBSTR( NULL ), m_BITSSessionTimeoutBSTR( NULL ), m_PathBSTR( NULL ), m_BITSSessionDirectoryBSTR( NULL ), m_UNCUserNameBSTR( NULL ), m_UNCPasswordBSTR( NULL ) { VariantInit( &m_vt ); m_BITSCleanupWorkItemKeyBSTR = SysAllocString( L"BITSCleanupWorkItemKey" ); m_BITSUploadEnabledBSTR = SysAllocString( L"BITSUploadEnabled" ); m_BITSSessionTimeoutBSTR = SysAllocString( L"BITSSessionTimeout" ); m_PathBSTR = SysAllocString( L"Path" ); m_BITSSessionDirectoryBSTR = SysAllocString( L"BITSSessionDirectory" ); m_UNCUserNameBSTR = SysAllocString( L"UNCUserName" ); m_UNCPasswordBSTR = SysAllocString( L"UNCPassword" ); if ( !m_BITSCleanupWorkItemKeyBSTR || !m_BITSUploadEnabledBSTR || !m_BITSSessionTimeoutBSTR || !m_PathBSTR || !m_BITSSessionDirectoryBSTR || !m_UNCUserNameBSTR || !m_UNCPasswordBSTR ) { SysFreeString( m_BITSCleanupWorkItemKeyBSTR ); SysFreeString( m_BITSUploadEnabledBSTR ); SysFreeString( m_BITSSessionTimeoutBSTR ); SysFreeString( m_PathBSTR ); SysFreeString( m_BITSSessionDirectoryBSTR ); SysFreeString( m_UNCUserNameBSTR ); SysFreeString( m_UNCPasswordBSTR ); throw ComError( E_OUTOFMEMORY ); } m_EventLog = RegisterEventSource( NULL, // server name EVENT_LOG_SOURCE_NAME // source name ); if ( !m_EventLog ) throw ComError( HRESULT_FROM_WIN32( GetLastError() ) ); } CleanupWorker::~CleanupWorker() { if ( m_EventLog ) DeregisterEventSource( m_EventLog ); if ( m_UserToken ) { BITSSetCurrentThreadToken( NULL ); CloseHandle( m_UserToken ); } delete m_ADSIPath; SysFreeString( m_VDirPath ); SysFreeString( m_SessionDirectory ); SysFreeString( m_UNCUsername ); SysFreeString( m_UNCPassword ); // Free hardcoded strings SysFreeString( m_BITSCleanupWorkItemKeyBSTR ); SysFreeString( m_BITSUploadEnabledBSTR ); SysFreeString( m_BITSSessionTimeoutBSTR ); SysFreeString( m_PathBSTR ); SysFreeString( m_BITSSessionDirectoryBSTR ); SysFreeString( m_UNCUserNameBSTR ); SysFreeString( m_UNCPasswordBSTR ); } //--------------------------------------------------------------------------- // CleanupWorker::DeleteDirectoryAndFiles() // // This method deletes the specified directory and the files it contains. If // the specified directory contains subdirectories, then they will not be // deleted (and the delete of the main directory will fail). // // If the directory is a reparse point, then it will do nothing. If the // specified directory contains reparse points then they will be ignored as // well. //--------------------------------------------------------------------------- void CleanupWorker::DeleteDirectoryAndFiles( StringHandle Directory ) { HANDLE FindHandle = INVALID_HANDLE_VALUE; WIN32_FILE_ATTRIBUTE_DATA FileAttributes; try { // Check the specified directory. If its not actually a directory, or // it's a reparse point then don't process it. if (!GetFileAttributesEx( Directory, GetFileExInfoStandard, &FileAttributes)) { throw ComError(HRESULT_FROM_WIN32(GetLastError())); } if ( (FileAttributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || !(FileAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) { return; } StringHandle SearchPath = Directory + StringHandle(L"\\*"); WIN32_FIND_DATA FindData; FindHandle = FindFirstFile( SearchPath, &FindData ); if ( INVALID_HANDLE_VALUE == FindHandle ) { throw ComError( HRESULT_FROM_WIN32( GetLastError() ) ); } do { if ( (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || (FindData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ) { continue; } StringHandle FileName = StringHandle( Directory ) + StringHandle( L"\\" ) + StringHandle( FindData.cFileName ); DeleteFile( FileName ); } while ( FindNextFile( FindHandle, &FindData ) ); FindClose( FindHandle ); FindHandle = INVALID_HANDLE_VALUE; if ( !RemoveDirectory( Directory ) ) { throw ComError( HRESULT_FROM_WIN32( GetLastError() ) ); } } catch( ComError Error ) { if ( INVALID_HANDLE_VALUE != FindHandle ) { FindClose( FindHandle ); } throw; } if ( INVALID_HANDLE_VALUE != FindHandle ) { FindClose( FindHandle ); } } bool CleanupWorker::DirectoryExists( StringHandle Directory ) { DWORD dwAttributes = GetFileAttributes( Directory ); if ( INVALID_FILE_ATTRIBUTES == dwAttributes ) { if ( GetLastError() == ERROR_PATH_NOT_FOUND || GetLastError() == ERROR_FILE_NOT_FOUND ) return false; throw ComError( HRESULT_FROM_WIN32( GetLastError() ) ); } return true; } //------------------------------------------------------------------------- // CleanupWorker::LastDirectoryTime() // // Walk a directory and find the most recent "Last Write Time" for it or // and of its contents. // // Note: Ignore reparse points. // //------------------------------------------------------------------------- UINT64 CleanupWorker::LastDirectoryTime( StringHandle Directory ) { UINT64 LatestTime = 0; HANDLE FindHandle = INVALID_HANDLE_VALUE; WIN32_FILE_ATTRIBUTE_DATA FileAttributesData; if (!GetFileAttributesEx( Directory, GetFileExInfoStandard, &FileAttributesData ) ) { throw ComError( HRESULT_FROM_WIN32( GetLastError() ) ); } LatestTime = FILETIMEToUINT64( FileAttributesData.ftCreationTime ); StringHandle SearchPath = Directory + StringHandle(L"\\*"); WIN32_FIND_DATA FindData; FindHandle = FindFirstFile( SearchPath, &FindData ); if ( INVALID_HANDLE_VALUE == FindHandle ) throw ComError( HRESULT_FROM_WIN32( GetLastError() ) ); try { do { if ( (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || (FindData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ) { continue; } UINT64 CreationTime = FILETIMEToUINT64( FindData.ftCreationTime ); UINT64 LastWriteTime = FILETIMEToUINT64( FindData.ftLastWriteTime ); LatestTime = max( LatestTime, max( CreationTime, LastWriteTime ) ); } while ( FindNextFile( FindHandle, &FindData ) ); FindClose( FindHandle ); FindHandle = INVALID_HANDLE_VALUE; } catch( ComError Error ) { if ( INVALID_HANDLE_VALUE == FindHandle ) FindClose( FindHandle ); throw; } return LatestTime; } void CleanupWorker::RemoveSession( StringHandle SessionGuid ) { StringHandle RequestDir = m_RequestsDirPath + StringHandle("\\") + SessionGuid; StringHandle ReplyDir = m_RepliesDirPath + StringHandle("\\") + SessionGuid; bool RequestExists = DirectoryExists( RequestDir ); bool ReplyExists = DirectoryExists( ReplyDir ); UINT64 LastTime = 0; if ( RequestExists ) { try { LastTime = LastDirectoryTime( RequestDir ); } catch( ComError Error ) { LogUnableToScanDirectory( RequestDir, Error.m_Hr ); return; } } if ( ReplyExists ) { try { LastTime = max( LastTime, LastDirectoryTime( ReplyDir ) ); } catch( ComError Error ) { LogUnableToScanDirectory( ReplyDir, Error.m_Hr ); return; } } FILETIME ftCurrentTime; GetSystemTimeAsFileTime( &ftCurrentTime ); UINT64 CurrentTime = FILETIMEToUINT64( ftCurrentTime ); if ( ( 0xFFFFFFFF - LastTime > m_CleanupThreshold ) && ( LastTime + m_CleanupThreshold < CurrentTime ) ) { try { if ( RequestExists ) { DeleteDirectoryAndFiles( RequestDir ); } if ( ReplyExists ) { DeleteDirectoryAndFiles( ReplyDir ); } LogDeletedJob( SessionGuid ); } catch( ComError Error ) { LogUnableToRemoveSession( SessionGuid, Error.m_Hr ); } } } //-------------------------------------------------------------------------- // CleanupWorker::RemoveSessions() // // Inspect the current "Replies directory" and "Requests directory" and // remove any BITS session directories that are older than the current // cleanup threshold. // // Arguments: // // SecondPass TRUE: Inspect the Replies Directory Path. // FALSE: Inspect the Requests Directory Path. //-------------------------------------------------------------------------- void CleanupWorker::RemoveSessions( IN bool SecondPass ) { StringHandle SearchDirectory = (SecondPass)? m_RepliesDirPath : m_RequestsDirPath; StringHandle SearchString = SearchDirectory + StringHandle( L"\\*" ); HANDLE FindHandle = INVALID_HANDLE_VALUE; WIN32_FIND_DATA FindData; WIN32_FILE_ATTRIBUTE_DATA FileAttributes; try { if (!GetFileAttributesEx(SearchDirectory, GetFileExInfoStandard, &FileAttributes)) { throw ComError(HRESULT_FROM_WIN32(GetLastError())); } if ( (FileAttributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || !(FileAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) { return; } FindHandle = FindFirstFile( SearchString, &FindData ); if ( INVALID_HANDLE_VALUE == FindHandle ) { throw ComError(HRESULT_FROM_WIN32(GetLastError())); } do { PollKill(); // If its not a directory then ignore it... if ( !(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) { continue; } // If its a reparse point then ignore it. if ( FindData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) { continue; } // If its this directory or the parent directory reference then // ignore it. if ( ( _wcsicmp( L".", FindData.cFileName ) == 0 ) || ( _wcsicmp( L"..", FindData.cFileName ) == 0 ) ) { continue; } // If the name isn't a GUID then ignore it. GUID Guid; if ( FAILED( IIDFromString( FindData.cFileName, &Guid ) ) ) { continue; } try { RemoveSession( StringHandle( FindData.cFileName ) ); } catch( ComError Error ) { LogUnexpectedError( Error.m_Hr ); } } while( FindNextFile( FindHandle, &FindData ) ); FindClose( FindHandle ); FindHandle = INVALID_HANDLE_VALUE; } catch( const ComError & ) { if ( INVALID_HANDLE_VALUE != FindHandle ) { FindClose( FindHandle ); } throw; } catch( const PollKillError & ) { if ( INVALID_HANDLE_VALUE != FindHandle ) { FindClose( FindHandle ); } } } void CleanupWorker::PollKill() { if ( m_hwnd ) { MSG msg; while( PeekMessage( &msg, m_hwnd, 0, 0, PM_REMOVE ) ) { if ( WM_QUIT == msg.message ) throw PollKillError( (HRESULT)msg.wParam ); TranslateMessage( &msg ); DispatchMessage( &msg ); } } } void CleanupWorker::DoIt() { try { m_ADSIPath = CSimplePropertyReader::ConvertObjectPathToADSI( m_Path ); try { THROW_COMERROR( ADsGetObject( m_ADSIPath, __uuidof(*m_VDir), (void**)&m_VDir ) ); if ( m_GuidString ) { BSTR BSTRGuid = CSimplePropertyReader::GetADsStringProperty( m_VDir, m_BITSCleanupWorkItemKeyBSTR ); int Result = wcscmp( (LPWSTR)BSTRGuid, m_GuidString ); SysFreeString( BSTRGuid ); if ( Result != 0 ) throw ComError( E_ADS_UNKNOWN_OBJECT ); } } catch( ComError Error ) { if ( ( Error.m_Hr == E_ADS_UNKNOWN_OBJECT ) || ( Error.m_Hr == HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ) ) || ( Error.m_Hr == E_ADS_PROPERTY_NOT_FOUND ) ) { // Somehow the virtual directory was deleted, but the // task scheduler work item wasn't. Try to delete it now. if ( m_WorkItemName ) { SmartITaskSchedulerPointer TaskScheduler; try { ConnectToTaskScheduler( NULL, &TaskScheduler ); TaskScheduler->Delete( m_WorkItemName ); } catch( ComError Error ) { } } } // Nothing more to do after deleting the task. return; } THROW_COMERROR( m_VDir->Get( m_BITSUploadEnabledBSTR, &m_vt ) ); THROW_COMERROR( VariantChangeType( &m_vt, &m_vt, 0, VT_BOOL ) ); if ( !m_vt.boolVal ) // Uploads arn't enabled on this directory return; if ( !m_DeleteAll ) { THROW_COMERROR( m_VDir->Get( m_BITSSessionTimeoutBSTR, &m_vt ) ); THROW_COMERROR( VariantChangeType( &m_vt, &m_vt, 0, VT_BSTR ) ); if ( L'-' == *m_vt.bstrVal ) return; // do not run cleanup in this directory since cleanup has been disabled UINT64 CleanupSeconds; if ( 1 != swscanf( (WCHAR*)m_vt.bstrVal, L"%I64u", &CleanupSeconds ) ) return; if ( CleanupSeconds > ( 0xFFFFFFFFFFFFFFFF / NanoSec100PerSec ) ) m_CleanupThreshold = 0xFFFFFFFFFFFFFFFF; // overflow case else m_CleanupThreshold = CleanupSeconds * NanoSec100PerSec; } else m_CleanupThreshold = 0; m_VDirPath = CSimplePropertyReader::GetADsStringProperty( m_VDir, m_PathBSTR ); m_SessionDirectory = CSimplePropertyReader::GetADsStringProperty( m_VDir, m_BITSSessionDirectoryBSTR ); m_UNCUsername = CSimplePropertyReader::GetADsStringProperty( m_VDir, m_UNCUserNameBSTR ); m_UNCPassword = CSimplePropertyReader::GetADsStringProperty( m_VDir, m_UNCPasswordBSTR ); m_SessionDirPath = StringHandle( (WCHAR*)m_VDirPath) + StringHandle(L"\\") + StringHandle( (WCHAR*)m_SessionDirectory ); m_RequestsDirPath = m_SessionDirPath + StringHandle(L"\\") + StringHandle( REQUESTS_DIR_NAMEW ); m_RepliesDirPath = m_SessionDirPath + StringHandle(L"\\") + StringHandle( REPLIES_DIR_NAMEW ); if (CAccessRemoteVDir::IsUNCPath(m_VDirPath)) { CAccessRemoteVDir::ImpersonateUNCUser(m_VDirPath, m_UNCUsername, m_UNCPassword, &m_UserToken); } RemoveSessions( false ); RemoveSessions( true ); } catch( PollKillError Error ) { throw; } catch( ComError Error ) { LogUnexpectedError( Error.m_Hr ); throw; } } void CleanupWorker::LogDeletedJob( const WCHAR *SessionGuid ) { if ( m_EventLog ) { const WCHAR *Strings[] = { (const WCHAR*)m_SessionDirPath, SessionGuid }; ReportEvent( m_EventLog, // handle to event log EVENTLOG_INFORMATION_TYPE, // event type BITSRV_EVENTLOG_CLEANUP_CATAGORY, // event category BITSSRV_EVENTLOG_DELETED_SESSION, // event identifier NULL, // user security identifier 2, // number of strings to merge 0, // size of binary data Strings, // array of strings to merge NULL // binary data buffer ); } } void CleanupWorker::LogUnableToRemoveSession( const WCHAR *SessionGuid, HRESULT Hr ) { if ( m_EventLog ) { const WCHAR *Strings[] = { (const WCHAR*)m_SessionDirPath, SessionGuid }; ReportEvent( m_EventLog, // handle to event log EVENTLOG_ERROR_TYPE, // event type BITSRV_EVENTLOG_CLEANUP_CATAGORY, // event category BITSSRV_EVENTLOG_CANT_REMOVE_SESSION, // event identifier NULL, // user security identifier 2, // number of strings to merge sizeof(Hr), // size of binary data Strings, // array of strings to merge &Hr // binary data buffer ); } } void CleanupWorker:: LogUnableToScanDirectory( const WCHAR *Path, HRESULT Hr ) { if ( m_EventLog ) { const WCHAR *Strings[] = { Path }; ReportEvent( m_EventLog, // handle to event log EVENTLOG_ERROR_TYPE, // event type BITSRV_EVENTLOG_CLEANUP_CATAGORY, // event category BITSSRV_EVENTLOG_CANT_SCAN_DIRECTORY, // event identifier NULL, // user security identifier 1, // number of strings to merge sizeof(Hr), // size of binary data Strings, // array of strings to merge &Hr // binary data buffer ); } } void CleanupWorker::LogUnexpectedError( HRESULT Hr ) { if ( m_EventLog ) { const WCHAR *Strings[] = { m_Path }; ReportEvent( m_EventLog, // handle to event log EVENTLOG_ERROR_TYPE, // event type BITSRV_EVENTLOG_CLEANUP_CATAGORY, // event category BITSSRV_EVENTLOG_UNEXPECTED_ERROR, // event identifier NULL, // user security identifier 1, // number of strings to merge sizeof( Hr ), // size of binary data Strings, // array of strings to merge &Hr // binary data buffer ); } } void Cleanup_RunDLLW(HWND hwndStub, HINSTANCE hAppInstance, LPWSTR lpszCmdLine, int nCmdShow ) { int NumArgs; LPWSTR * CommandArgs = CommandLineToArgvW( lpszCmdLine, &NumArgs ); if ( !CommandArgs ) return; if ( FAILED( CoInitializeEx( NULL, COINIT_MULTITHREADED ) ) ) return; if ( NumArgs != 2 && NumArgs != 3 ) return; LPWSTR Path = CommandArgs[0]; LPWSTR WorkItemName = CommandArgs[1]; LPWSTR GuidString = NumArgs == 3 ? CommandArgs[2] : NULL; try { CleanupWorker Worker( FALSE, hwndStub, Path, WorkItemName, GuidString ); Worker.DoIt(); } catch( PollKillError PollAbort ) { } catch( ComError Error ) { } CoUninitialize( ); GlobalFree( CommandArgs ); } void CleanupForRemoval( LPCWSTR Path ) { HANDLE hToken = NULL; try { if (!OpenThreadToken( GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hToken ) ) { if ( GetLastError() != ERROR_NO_TOKEN ) return; } CleanupWorker Worker( TRUE, NULL, Path, NULL, NULL ); Worker.DoIt(); } catch( ComError Error ) { } catch( PollKillError PollAbort ) { } if ( hToken ) BITSSetCurrentThreadToken( hToken ); }