/*++ 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 CleanupWorker { public: CleanupWorker( HWND hwnd, const WCHAR* Path, const WCHAR *WorkItemName, const WCHAR *GuidString ); ~CleanupWorker(); void DoIt(); private: 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; VARIANT m_vt; HANDLE m_FindHandle; HANDLE m_UserToken; WCHAR * BuildPath( const WCHAR * Dir, const WCHAR *Sub ); BSTR GetBSTRProp( BSTR PropName ); void LogonIfRequired(); void PollKill(); void RemoveConnectionsFromTree( const WCHAR * DirectoryPath, bool IsConnectionDirectory ); void RemoveConnection( const WCHAR * ConnectionDirectory ); }; CleanupWorker::CleanupWorker( HWND hwnd, const WCHAR* Path, const WCHAR* WorkItemName, const WCHAR* GuidString ) : 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 ) { VariantInit( &m_vt ); } CleanupWorker::~CleanupWorker() { if ( m_UserToken ) { SetThreadToken( NULL, NULL ); CloseHandle( m_UserToken ); } delete m_ADSIPath; SysFreeString( m_VDirPath ); SysFreeString( m_SessionDirectory ); SysFreeString( m_UNCUsername ); SysFreeString( m_UNCPassword ); } void CleanupWorker::RemoveConnection( const WCHAR * ConnectionDirectory ) { UINT64 LatestTime = 0; HANDLE FindHandle = INVALID_HANDLE_VALUE; WCHAR *SearchPath = NULL; WCHAR *FileName = NULL; try { SearchPath = BuildPath( ConnectionDirectory, L"*" ); WIN32_FIND_DATA FindData; FindHandle = FindFirstFile( SearchPath, &FindData ); if ( INVALID_HANDLE_VALUE == FindHandle ) throw ComError( HRESULT_FROM_WIN32( GetLastError() ) ); bool FoundFile = false; do { if ( FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) continue; FoundFile = true; 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; FILETIME ftCurrentTime; GetSystemTimeAsFileTime( &ftCurrentTime ); UINT64 CurrentTime = FILETIMEToUINT64( ftCurrentTime ); if ( FoundFile && ( 0xFFFFFFFF - LatestTime > m_CleanupThreshold ) && ( LatestTime + m_CleanupThreshold < CurrentTime ) ) { FindHandle = FindFirstFile( SearchPath, &FindData ); if ( INVALID_HANDLE_VALUE == FindHandle ) throw ComError( HRESULT_FROM_WIN32( GetLastError() ) ); do { if ( FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) continue; FileName = BuildPath( ConnectionDirectory, FindData.cFileName ); DeleteFile( FileName ); delete FileName; FileName = NULL; } while ( FindNextFile( FindHandle, &FindData ) ); FindClose( FindHandle ); FindHandle = INVALID_HANDLE_VALUE; } RemoveDirectory( ConnectionDirectory ); } catch( ComError Error ) { if ( INVALID_HANDLE_VALUE != FindHandle ) FindClose( FindHandle ); delete SearchPath; delete FileName; throw; } if ( INVALID_HANDLE_VALUE != FindHandle ) FindClose( FindHandle ); delete SearchPath; delete FileName; } void CleanupWorker::RemoveConnectionsFromTree( const WCHAR * DirectoryPath, bool IsConnectionDirectory ) { WCHAR *ConnectionDir = NULL; HANDLE FindHandle = INVALID_HANDLE_VALUE; WCHAR *SearchString = NULL; WCHAR *NextSearchPath = NULL; try { // Look for BITS-Sessions directory in connection tree SearchString = BuildPath( DirectoryPath, L"*" ); WIN32_FIND_DATA FindData; FindHandle = FindFirstFile( SearchString, &FindData ); if ( INVALID_HANDLE_VALUE == FindHandle ) return; do { PollKill(); if ( !(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) continue; if ( _wcsicmp( L".", FindData.cFileName ) == 0 ) continue; if ( _wcsicmp( L"..", FindData.cFileName ) == 0 ) continue; if ( IsConnectionDirectory ) { GUID Guid; if (SUCCEEDED( IIDFromString( FindData.cFileName, &Guid ) ) ) { NextSearchPath = BuildPath( DirectoryPath, FindData.cFileName ); RemoveConnection( NextSearchPath ); delete NextSearchPath; NextSearchPath = NULL; } } else { if ( _wcsicmp( m_SessionDirectory, FindData.cFileName ) == 0 ) { NextSearchPath = BuildPath( DirectoryPath, FindData.cFileName ); RemoveConnectionsFromTree( NextSearchPath, true ); // Mark this as the connection directory so it // will be closed after the search handles are closed. ConnectionDir = NextSearchPath; NextSearchPath = NULL; } else { // just another directory to recurse into NextSearchPath = BuildPath( DirectoryPath, FindData.cFileName ); RemoveConnectionsFromTree( NextSearchPath, false ); delete NextSearchPath; NextSearchPath = NULL; } } } while( FindNextFile( FindHandle, &FindData ) ); if ( INVALID_HANDLE_VALUE != FindHandle ) FindClose( FindHandle ); delete SearchString; delete NextSearchPath; if ( ConnectionDir ) { // The attempt to remove the directory will fail if // the directory still has valid connections RemoveDirectory( ConnectionDir ); delete ConnectionDir; } } catch( ComError Error ) { if ( INVALID_HANDLE_VALUE != FindHandle ) FindClose( FindHandle ); delete SearchString; delete NextSearchPath; delete ConnectionDir; throw; } } void CleanupWorker::PollKill() { MSG msg; while( PeekMessage( &msg, m_hwnd, 0, 0, PM_REMOVE ) ) { if ( WM_QUIT == msg.message ) throw ComError( (HRESULT)msg.wParam ); TranslateMessage( &msg ); DispatchMessage( &msg ); } } WCHAR * CleanupWorker::BuildPath( const WCHAR *Dir, const WCHAR *Sub ) { SIZE_T DirLen = wcslen( Dir ); SIZE_T SubLen = wcslen( Sub ); SIZE_T MaxStringSize = DirLen + SubLen + 2; // one slash, one terminator WCHAR *RetString = new WCHAR[ MaxStringSize ]; if ( !RetString ) throw ComError( E_OUTOFMEMORY ); memcpy( RetString, Dir, sizeof(WCHAR) * (DirLen + 1) ); WCHAR *p = RetString + DirLen; if ( p != RetString && *(p - 1) != L'\\' && *(p - 1) != L'/' ) *p++ = L'\\'; memcpy( p, Sub, sizeof(WCHAR) * ( SubLen + 1 ) ); return RetString; } BSTR CleanupWorker::GetBSTRProp( BSTR PropName ) { BSTR Retval; THROW_COMERROR( m_VDir->Get( PropName, &m_vt ) ); THROW_COMERROR( VariantChangeType( &m_vt, &m_vt, 0, VT_BSTR ) ); Retval = m_vt.bstrVal; m_vt.bstrVal = NULL; VariantClear( &m_vt ); return Retval; } void CleanupWorker::LogonIfRequired() { // Don't logon if the path isn't a UNC path // or the user name is blank if ( ((WCHAR*)m_VDirPath)[0] != L'\\' || ((WCHAR*)m_VDirPath)[1] != L'\\' || *(WCHAR*)m_UNCUsername == L'\0' ) return; // no logon required // crack the user name into a user and domain WCHAR *UserName = (WCHAR*)m_UNCUsername; WCHAR *DomainName = NULL; WCHAR *p = UserName; while(*p != L'\0') { if(*p == L'\\') { *p = L'\0'; p++; // // first part is domain // second is user. // DomainName = UserName; UserName = p; break; } p++; } if ( !LogonUser( UserName, DomainName, (WCHAR*)m_UNCPassword, LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &m_UserToken ) ) { if ( GetLastError() == ERROR_LOGON_TYPE_NOT_GRANTED ) { if ( !LogonUser( UserName, DomainName, (WCHAR*)m_UNCPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &m_UserToken ) ) { throw ComError( HRESULT_FROM_WIN32( GetLastError() ) ); } } } if ( !ImpersonateLoggedOnUser( m_UserToken ) ) throw ComError( HRESULT_FROM_WIN32( GetLastError() ) ); } void CleanupWorker::DoIt() { m_ADSIPath = ConvertObjectPathToADSI( m_Path ); try { THROW_COMERROR( ADsGetObject( m_ADSIPath, __uuidof(*m_VDir), (void**)&m_VDir ) ); if ( m_GuidString ) { BSTR BSTRGuid = GetBSTRProp( (BSTR)L"BITSCleanupWorkItemKey" ); 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. ITaskScheduler *TaskScheduler; if ( SUCCEEDED( ConnectToTaskScheduler( NULL, &TaskScheduler ) ) ) { TaskScheduler->Delete( m_WorkItemName ); TaskScheduler->Release(); } } throw; } THROW_COMERROR( m_VDir->Get( (BSTR)L"BITSUploadEnabled", &m_vt ) ); THROW_COMERROR( VariantChangeType( &m_vt, &m_vt, 0, VT_BOOL ) ); if ( !m_vt.boolVal ) // Uploads arn't enabled on this directory return; THROW_COMERROR( m_VDir->Get( (BSTR)L"BITSSessionTimeout", &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; m_VDirPath = GetBSTRProp( (BSTR)L"Path" ); m_SessionDirectory = GetBSTRProp( (BSTR)L"BITSSessionDirectory" ); m_UNCUsername = GetBSTRProp( (BSTR)L"UNCUserName" ); m_UNCPassword = GetBSTRProp( (BSTR)L"UNCPassword" ); LogonIfRequired(); RemoveConnectionsFromTree( (WCHAR*)m_VDirPath, false ); } 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( hwndStub, Path, WorkItemName, GuidString ); Worker.DoIt(); } catch( ComError Error ) { } CoUninitialize( ); GlobalFree( CommandArgs ); }