/*++ © 1998 Seagate Software, Inc. All rights reserved. Module Name: fsatrunc.cpp Abstract: This class handles the automatic truncation of files that have already been premigrated. Author: Chuck Bardeen [cbardeen] 20-Feb-1997 Revision History: --*/ #include "stdafx.h" #define WSB_TRACE_IS WSB_TRACE_BIT_FSA #include "wsb.h" #include "fsa.h" #include "fsaprem.h" #include "fsarcvy.h" #include "fsasrvr.h" #include "fsatrunc.h" #include "job.h" #define DEFAULT_MAX_FILES_PER_RUN 10000 #define DEFAULT_RUN_INTERVAL (15 * 60 * 1000) // 15 minutes in milliseconds #define STRINGIZE(_str) (OLESTR( #_str )) #define RETURN_STRINGIZED_CASE(_case) \ case _case: \ return ( STRINGIZE( _case ) ); static const OLECHAR * FsaStateAsString ( IN HSM_JOB_STATE state ) /*++ Routine Description: Gives back a static string representing the connection state. Arguments: state - the state to return a string for. Return Value: NULL - invalid state passed in. Otherwise, a valid char *. --*/ { // // Do the Switch // switch ( state ) { RETURN_STRINGIZED_CASE( HSM_JOB_STATE_ACTIVE ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_CANCELLED ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_CANCELLING ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_DONE ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_FAILED ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_IDLE ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_PAUSED ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_PAUSING ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_RESUMING ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_SKIPPED ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_STARTING ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_SUSPENDED ); RETURN_STRINGIZED_CASE( HSM_JOB_STATE_SUSPENDING ); default: return ( OLESTR("Invalid Value") ); } } static const OLECHAR * FsaEventAsString ( IN HSM_JOB_EVENT event ) /*++ Routine Description: Gives back a static string representing the connection event. Arguments: event - the event to return a string for. Return Value: NULL - invalid event passed in. Otherwise, a valid char *. --*/ { // // Do the Switch // switch ( event ) { RETURN_STRINGIZED_CASE( HSM_JOB_EVENT_CANCEL ); RETURN_STRINGIZED_CASE( HSM_JOB_EVENT_FAIL ); RETURN_STRINGIZED_CASE( HSM_JOB_EVENT_LOWER_PRIORITY ); RETURN_STRINGIZED_CASE( HSM_JOB_EVENT_PAUSE ); RETURN_STRINGIZED_CASE( HSM_JOB_EVENT_RAISE_PRIORITY ); RETURN_STRINGIZED_CASE( HSM_JOB_EVENT_RESUME ); RETURN_STRINGIZED_CASE( HSM_JOB_EVENT_START ); RETURN_STRINGIZED_CASE( HSM_JOB_EVENT_SUSPEND ); default: return ( OLESTR("Invalid Value") ); } } static const OLECHAR * FsaSortOrderAsString ( IN FSA_PREMIGRATED_SORT_ORDER SortOrder ) /*++ Routine Description: Gives back a static string representing the connection SortOrder. Arguments: SortOrder - the SortOrder to return a string for. Return Value: NULL - invalid SortOrder passed in. Otherwise, a valid char *. --*/ { // // Do the Switch // switch ( SortOrder ) { RETURN_STRINGIZED_CASE( FSA_SORT_PL_BY_ACCESS_TIME ); RETURN_STRINGIZED_CASE( FSA_SORT_PL_BY_SIZE ); RETURN_STRINGIZED_CASE( FSA_SORT_PL_BY_PATH_NAME ); RETURN_STRINGIZED_CASE( FSA_SORT_PL_BY_SIZE_AND_TIME ); default: return ( OLESTR("Invalid Value") ); } } DWORD FsaStartTruncator( void* pVoid ) /*++ --*/ { return(((CFsaTruncator*) pVoid)->StartScan()); } HRESULT CFsaTruncator::Cancel( HSM_JOB_EVENT event ) /*++ Implements: IFsaTruncator::Cancel(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::Cancel"), OLESTR("event = <%ls>"), FsaEventAsString( event )); // Lock this object to avoid having the state change between testing its value // and setting it to a new value Lock(); try { // If we have started, but haven't finished, then change the state of the job. The thread // will exit on it's own. if ((HSM_JOB_STATE_IDLE != m_state) && (HSM_JOB_STATE_DONE != m_state) && (HSM_JOB_STATE_FAILED != m_state) && (HSM_JOB_STATE_CANCELLED != m_state)) { if (HSM_JOB_EVENT_CANCEL == event) { WsbAffirmHr(SetState(HSM_JOB_STATE_CANCELLED)); } else if (HSM_JOB_EVENT_SUSPEND == event) { WsbAffirmHr(SetState(HSM_JOB_STATE_SUSPENDED)); } else if (HSM_JOB_EVENT_FAIL == event) { WsbAffirmHr(SetState(HSM_JOB_STATE_FAILED)); } else { WsbAssert(FALSE, E_UNEXPECTED); } } } WsbCatch(hr); Unlock(); WsbTraceOut(OLESTR("CFsaTruncator::Cancel"), OLESTR("hr = <%ls> m_state = <%ls>"), WsbHrAsString(hr), FsaStateAsString( m_state ) ); return(hr); } HRESULT CFsaTruncator::FinalConstruct( void ) /*++ Implements: CComObjectRoot::FinalConstruct(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::FinalConstruct"), OLESTR("")); try { WsbAffirmHr(CWsbPersistStream::FinalConstruct()); m_state = HSM_JOB_STATE_IDLE; m_priority = HSM_JOB_PRIORITY_NORMAL; m_threadHandle = 0; m_threadId = 0; m_threadHr = S_OK; m_maxFiles = DEFAULT_MAX_FILES_PER_RUN; m_runInterval = DEFAULT_RUN_INTERVAL; m_runId = 0; m_subRunId = 0; m_pSession = 0; m_SortOrder = FSA_SORT_PL_BY_ACCESS_TIME; m_keepRecallTime = WsbLLtoFT(WSB_FT_TICKS_PER_MINUTE); m_event = 0; } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::FinalConstruct"), OLESTR("")); return(hr); } HRESULT CFsaTruncator::FinalRelease( void ) /*++ Implements: CComObjectRoot::FinalRelease(). --*/ { HRESULT hr = S_OK; HSM_SYSTEM_STATE SysState; WsbTraceIn(OLESTR("CFsaTruncator::FinalRelease"), OLESTR("")); SysState.State = HSM_STATE_SHUTDOWN; ChangeSysState(&SysState); CWsbPersistStream::FinalRelease(); // Free String members // Note: Member objects held in smart-pointers are freed when the // smart-pointer destructor is being called (as part of this object destruction) m_currentPath.Free(); WsbTraceOut(OLESTR("CFsaTruncator::FinalRelease"), OLESTR("")); return(hr); } HRESULT CFsaTruncator::GetClassID( OUT CLSID* pClsid ) /*++ Implements: IPersist::GetClassID(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::GetClassID"), OLESTR("")); try { WsbAssert(0 != pClsid, E_POINTER); *pClsid = CLSID_CFsaTruncatorNTFS; } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::GetClassID"), OLESTR("hr = <%ls>, CLSID = <%ls>"), WsbHrAsString(hr), WsbGuidAsString(*pClsid)); return(hr); } HRESULT CFsaTruncator::GetKeepRecallTime( OUT FILETIME* pTime ) /*++ Implements: IFsaTruncator::GetKeepRecallTime(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::GetKeepRecallTime"), OLESTR("")); try { WsbAssert(0 != pTime, E_POINTER); *pTime = m_keepRecallTime; } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::GetKeepRecallTime"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaTruncator::GetMaxFilesPerRun( OUT LONGLONG* pMaxFiles ) /*++ Implements: IFsaTruncator::GetMaxFilesPerRun(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::GetMaxFilesPerRun"), OLESTR("")); try { WsbAssert(0 != pMaxFiles, E_POINTER); *pMaxFiles = m_maxFiles; } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::GetMaxFilesPerRun"), OLESTR("hr = <%ls> maxFiles = <%ls>"), WsbHrAsString(hr), WsbLonglongAsString( *pMaxFiles ) ); return(hr); } HRESULT CFsaTruncator::GetPremigratedSortOrder( OUT FSA_PREMIGRATED_SORT_ORDER* pSortOrder ) /*++ Implements: IFsaTruncator::GetPremigratedSortOrder(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::GetPremigratedSortOrder"), OLESTR("")); try { WsbAssert(0 != pSortOrder, E_POINTER); *pSortOrder = m_SortOrder; } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::GetPremigratedSortOrder"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaTruncator::GetRunInterval( OUT ULONG* pMilliseconds ) /*++ Implements: IFsaTruncator::GetRunInterval(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::GetRunInterval"), OLESTR("")); try { WsbAssert(0 != pMilliseconds, E_POINTER); *pMilliseconds = m_runInterval; } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::GetRunInterval"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaTruncator::GetSession( OUT IHsmSession** ppSession ) /*++ Implements: IFsaTruncator::GetSession(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::GetSession"), OLESTR("")); try { WsbAssert(0 != ppSession, E_POINTER); *ppSession = m_pSession; if (m_pSession != 0) { m_pSession.p->AddRef(); } } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::GetSession"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaTruncator::GetSizeMax( OUT ULARGE_INTEGER* pSize ) /*++ Implements: IPersistStream::GetSizeMax(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::GetSizeMax"), OLESTR("")); try { WsbAssert(0 != pSize, E_POINTER); // Determine the size for a rule with no criteria. pSize->QuadPart = WsbPersistSizeOf(LONGLONG) + 3 * WsbPersistSizeOf(ULONG) + WsbPersistSizeOf(FILETIME); } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::GetSizeMax"), OLESTR("hr = <%ls>, Size = <%ls>"), WsbHrAsString(hr), WsbPtrToUliAsString(pSize)); return(hr); } HRESULT CFsaTruncator::KickStart( void ) /*++ Implements: IFsaTruncator:KickStart Data was just moved for this volume - wake up the truncator thread in case we need space. --*/ { HRESULT hr = S_OK; CComPtr pResource; ULONG freeLevel; ULONG hsmLevel; WsbTraceIn(OLESTR("CFsaTruncator::KickStart"), OLESTR("")); try { if (m_pSession) { WsbAffirmHr(m_pSession->GetResource(&pResource)); // If the truncator is running and the resource does not have enough free space // check to see if the resource is over the threshold and truncation is needed. WsbAffirmHr(pResource->GetHsmLevel(&hsmLevel)); WsbAffirmHr(pResource->GetFreeLevel(&freeLevel)); if (freeLevel < hsmLevel) { WsbTrace(OLESTR("CFsaTruncator::KickStarting truncator.\n")); SetEvent(m_event); } } } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::KickStart"), OLESTR("hr = <%ls>>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaTruncator::Load( IN IStream* pStream ) /*++ Implements: IPersistStream::Load(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::Load"), OLESTR("")); try { USHORT us_tmp; ULONG ul_tmp; WsbAssert(0 != pStream, E_POINTER); // Do the easy stuff, but make sure that this order matches the order // in the save method. WsbAffirmHr(WsbLoadFromStream(pStream, &ul_tmp)); m_priority = static_cast(ul_tmp); WsbAffirmHr(WsbLoadFromStream(pStream, &m_maxFiles)); WsbAffirmHr(WsbLoadFromStream(pStream, &m_runInterval)); WsbAffirmHr(WsbLoadFromStream(pStream, &m_runId)); WsbAffirmHr(WsbLoadFromStream(pStream, &m_keepRecallTime)); WsbAffirmHr(WsbLoadFromStream(pStream, &us_tmp)); m_SortOrder = static_cast(us_tmp); // Check to see if values for maxFiles and runInterval are specified in the registry. // If so, use these values instead of the ones stored. { DWORD sizeGot; CWsbStringPtr tmpString; WsbAffirmHr(tmpString.Alloc(256)); if (SUCCEEDED(WsbGetRegistryValueString(NULL, FSA_REGISTRY_PARMS, FSA_REGISTRY_TRUNCATOR_INTERVAL, tmpString, 256, &sizeGot))) { m_runInterval = 1000 * wcstoul(tmpString, NULL, 10); } else { m_runInterval = DEFAULT_RUN_INTERVAL; } if (SUCCEEDED(WsbGetRegistryValueString(NULL, FSA_REGISTRY_PARMS, FSA_REGISTRY_TRUNCATOR_FILES, tmpString, 256, &sizeGot))) { m_maxFiles = (LONGLONG) wcstoul(tmpString, NULL, 10); } else { m_maxFiles = DEFAULT_MAX_FILES_PER_RUN; } } } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::Load"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaTruncator::LowerPriority( void ) /*++ --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::LowerPriority"), OLESTR("")); try { WsbAssert(0 != m_threadHandle, E_UNEXPECTED); WsbAssert(m_pSession != 0, E_UNEXPECTED); switch(m_priority) { case HSM_JOB_PRIORITY_IDLE: WsbAffirm(FALSE, E_UNEXPECTED); break; case HSM_JOB_PRIORITY_LOWEST: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_IDLE)); m_priority = HSM_JOB_PRIORITY_IDLE; break; case HSM_JOB_PRIORITY_LOW: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_LOWEST)); m_priority = HSM_JOB_PRIORITY_LOWEST; break; case HSM_JOB_PRIORITY_NORMAL: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_BELOW_NORMAL)); m_priority = HSM_JOB_PRIORITY_LOW; break; case HSM_JOB_PRIORITY_HIGH: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_NORMAL)); m_priority = HSM_JOB_PRIORITY_NORMAL; break; case HSM_JOB_PRIORITY_HIGHEST: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_ABOVE_NORMAL)); m_priority = HSM_JOB_PRIORITY_HIGH; break; default: case HSM_JOB_PRIORITY_CRITICAL: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_HIGHEST)); m_priority = HSM_JOB_PRIORITY_HIGHEST; break; } WsbAffirmHr(m_pSession->ProcessPriority(HSM_JOB_PHASE_SCAN, m_priority)); } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::LowerPriority"), OLESTR("hr = <%ls>"), WsbHrAsString(hr) ); return(hr); } HRESULT CFsaTruncator::Pause( void ) /*++ Implements: IFsaTruncator::Pause(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::Pause"), OLESTR("state = %ls"), FsaStateAsString(m_state)); // Lock this object to avoid having the state change between testing its value // and setting it to a new value Lock(); try { // If we are running, then suspend the thread. WsbAssert(HSM_JOB_STATE_ACTIVE == m_state, E_UNEXPECTED); // Set the state & the active thread will not do any work WsbAffirmHr(SetState(HSM_JOB_STATE_PAUSING)); // We would like to wait until the thread is really inactive, but that's // hard to tell because it could be in a sleep interval } WsbCatch(hr); Unlock(); WsbTraceOut(OLESTR("CFsaTruncator::Pause"), OLESTR("hr = <%ls>"), WsbHrAsString(hr) ); return(hr); } HRESULT CFsaTruncator::ProcessSessionEvent( IN IHsmSession* pSession, IN HSM_JOB_PHASE phase, IN HSM_JOB_EVENT event ) /*++ --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::ProcessSessionEvent"), OLESTR("")); try { WsbAssert(0 != pSession, E_POINTER); // If the phase applies to use (SCAN or ALL), then do any work required by the // event. if ((HSM_JOB_PHASE_ALL == phase) || (HSM_JOB_PHASE_SCAN == phase)) { switch(event) { case HSM_JOB_EVENT_SUSPEND: case HSM_JOB_EVENT_CANCEL: case HSM_JOB_EVENT_FAIL: WsbAffirmHr(Cancel(event)); break; case HSM_JOB_EVENT_PAUSE: WsbAffirmHr(Pause()); break; case HSM_JOB_EVENT_RESUME: WsbAffirmHr(Resume()); break; case HSM_JOB_EVENT_RAISE_PRIORITY: WsbAffirmHr(RaisePriority()); break; case HSM_JOB_EVENT_LOWER_PRIORITY: WsbAffirmHr(LowerPriority()); break; default: case HSM_JOB_EVENT_START: WsbAssert(FALSE, E_UNEXPECTED); break; } } } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::ProcessSessionEvent"), OLESTR("hr = <%ls>"), WsbHrAsString(hr) ); return(S_OK); } HRESULT CFsaTruncator::RaisePriority( void ) /*++ --*/ { HRESULT hr = S_OK; try { WsbAssert(0 != m_threadHandle, E_UNEXPECTED); WsbAssert(m_pSession != 0, E_UNEXPECTED); switch(m_priority) { case HSM_JOB_PRIORITY_IDLE: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_LOWEST)); m_priority = HSM_JOB_PRIORITY_LOWEST; break; case HSM_JOB_PRIORITY_LOWEST: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_BELOW_NORMAL)); m_priority = HSM_JOB_PRIORITY_LOW; break; case HSM_JOB_PRIORITY_LOW: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_NORMAL)); m_priority = HSM_JOB_PRIORITY_NORMAL; break; case HSM_JOB_PRIORITY_NORMAL: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_ABOVE_NORMAL)); m_priority = HSM_JOB_PRIORITY_HIGH; break; case HSM_JOB_PRIORITY_HIGH: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_HIGHEST)); m_priority = HSM_JOB_PRIORITY_HIGHEST; break; case HSM_JOB_PRIORITY_HIGHEST: WsbAffirmStatus(SetThreadPriority(m_threadHandle, THREAD_PRIORITY_TIME_CRITICAL)); m_priority = HSM_JOB_PRIORITY_CRITICAL; break; default: case HSM_JOB_PRIORITY_CRITICAL: WsbAffirm(FALSE, E_UNEXPECTED); break; } WsbAffirmHr(m_pSession->ProcessPriority(HSM_JOB_PHASE_SCAN, m_priority)); } WsbCatch(hr); return(hr); } HRESULT CFsaTruncator::Resume( void ) /*++ Implements: IFsaTruncator::Resume(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::Resume"), OLESTR("state = %ls"), FsaStateAsString(m_state)); // Lock this object to avoid having the state change between testing its value // and setting it to a new value Lock(); try { // We should only see a resume from a paused state, so ignore the resume if we are // in some other state. NOTE: This used to be an assert, but it scared people since it // can occur occassionally. if ((HSM_JOB_STATE_PAUSING == m_state) || (HSM_JOB_STATE_PAUSED == m_state)) { WsbAffirmHr(SetState(HSM_JOB_STATE_ACTIVE)); } } WsbCatch(hr); Unlock(); WsbTraceOut(OLESTR("CFsaTruncator::Resume"), OLESTR("hr = <%ls>"), WsbHrAsString(hr) ); return(hr); } HRESULT CFsaTruncator::Save( IN IStream* pStream, IN BOOL clearDirty ) /*++ Implements: IPersistStream::Save(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::Save"), OLESTR("clearDirty = <%ls>"), WsbBoolAsString(clearDirty)); try { WsbAssert(0 != pStream, E_POINTER); // Do the easy stuff, but make sure that this order matches the order // in the save method. WsbAffirmHr(WsbSaveToStream(pStream, static_cast(m_priority))); WsbAffirmHr(WsbSaveToStream(pStream, m_maxFiles)); WsbAffirmHr(WsbSaveToStream(pStream, m_runInterval)); WsbAffirmHr(WsbSaveToStream(pStream, m_runId)); WsbAffirmHr(WsbSaveToStream(pStream, m_keepRecallTime)); WsbAffirmHr(WsbSaveToStream(pStream, static_cast(m_SortOrder))); // If we got it saved and we were asked to clear the dirty bit, then // do so now. if (clearDirty) { m_isDirty = FALSE; } } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::Save"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaTruncator::SetKeepRecallTime( IN FILETIME time ) /*++ Implements: IFsaTruncator::SetKeepRecallTime(). --*/ { WsbTraceIn(OLESTR("CFsaTruncator::SetKeepRecallTime"), OLESTR("")); m_keepRecallTime = time; WsbTraceOut(OLESTR("CFsaTruncator::SetKeepRecallTime"), OLESTR("hr = <%ls>"), WsbHrAsString(S_OK)); return(S_OK); } HRESULT CFsaTruncator::SetMaxFilesPerRun( IN LONGLONG maxFiles ) /*++ Implements: IFsaTruncator::SetMaxFilesPerRun(). --*/ { WsbTraceIn(OLESTR("CFsaTruncator::SetMaxFilesPerRun"), OLESTR("")); m_maxFiles = maxFiles; WsbTraceOut(OLESTR("CFsaTruncator::SetMaxFilesPerRun"), OLESTR("hr = <%ls>"), WsbHrAsString(S_OK)); return(S_OK); } HRESULT CFsaTruncator::SetPremigratedSortOrder( IN FSA_PREMIGRATED_SORT_ORDER SortOrder ) /*++ Implements: IFsaTruncator::SetSortOrder(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::SetPremigratedSortOrder"), OLESTR("SortOrder = <%ls>"), FsaSortOrderAsString( SortOrder ) ); // This key has not been implmented yet. if (FSA_SORT_PL_BY_SIZE_AND_TIME == SortOrder) { hr = E_NOTIMPL; } else { m_SortOrder = SortOrder; } WsbTraceOut(OLESTR("CFsaTruncator::SetPremigratedSortOrder"), OLESTR("hr = <%ls> m_SortOrder = <%ls>"), WsbHrAsString(S_OK) , FsaSortOrderAsString( m_SortOrder ) ); return(hr); } HRESULT CFsaTruncator::SetRunInterval( IN ULONG milliseconds ) /*++ Implements: IFsaTruncator::SetRunInterval(). --*/ { BOOL DoKick = FALSE; WsbTraceIn(OLESTR("CFsaTruncator::SetRunInterval"), OLESTR("milliseconds = <%ls>"), WsbPtrToUlongAsString( &milliseconds ) ); if (milliseconds < m_runInterval) { DoKick = TRUE; } m_runInterval = milliseconds; // Wake up the Truncator if the interval has decreased if (DoKick) { KickStart(); } WsbTraceOut(OLESTR("CFsaTruncator::SetRunInterval"), OLESTR("hr = <%ls> m_runInterval = <%ls>"), WsbHrAsString(S_OK), WsbPtrToUlongAsString( &m_runInterval ) ); return(S_OK); } HRESULT CFsaTruncator::SetState( IN HSM_JOB_STATE state ) /*++ --*/ { HRESULT hr = S_OK; BOOL bLog = FALSE; WsbTraceIn(OLESTR("CFsaTruncator::SetState"), OLESTR("state = <%ls>"), FsaStateAsString( state ) ); // Change the state and report the change to the session. Lock(); m_state = state; Unlock(); hr = m_pSession->ProcessState(HSM_JOB_PHASE_SCAN, m_state, m_currentPath, bLog); WsbTraceOut(OLESTR("CFsaTruncator::SetState"), OLESTR("hr = <%ls> m_state = <%ls>"), WsbHrAsString(hr), FsaStateAsString( m_state ) ); return(hr); } HRESULT CFsaTruncator::ChangeSysState( IN OUT HSM_SYSTEM_STATE* pSysState ) /*++ Implements: IHsmSystemState::ChangeSysState(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaTruncator::ChangeSysState"), OLESTR("thread is %ls"), (m_threadHandle ? OLESTR("active") : OLESTR("inactive"))); try { if (pSysState->State & HSM_STATE_SUSPEND) { if (HSM_JOB_STATE_ACTIVE == m_state) { Pause(); } } else if (pSysState->State & HSM_STATE_RESUME) { if ((HSM_JOB_STATE_PAUSING == m_state) || (HSM_JOB_STATE_PAUSED == m_state)) { Resume(); } } else if (pSysState->State & HSM_STATE_SHUTDOWN) { // Make sure the thread is stopped if (m_threadHandle) { m_state = HSM_JOB_STATE_DONE; if (m_event) { SetEvent(m_event); } // Wait for the thread to end if (m_threadHandle) { WsbTrace(OLESTR("CFsaTruncator::ChangeSysState, waiting for truncator thread to end\n")); switch (WaitForSingleObject(m_threadHandle, 120000)) { case WAIT_FAILED: WsbTrace(OLESTR("CFsaTruncator::ChangeSysState, WaitforSingleObject returned error %lu\n"), GetLastError()); break; case WAIT_TIMEOUT: WsbTrace(OLESTR("CFsaTruncator::ChangeSysState, timeout.\n")); break; default: break; } } // If the thread is still active, terminate it if (m_threadHandle) { WsbTrace(OLESTR("CFsaTruncator::ChangeSysState: calling TerminateThread\n")); if (!TerminateThread(m_threadHandle, 0)) { WsbTrace(OLESTR("CFsaTruncator::ChangeSysState: TerminateThread returned error %lu\n"), GetLastError()); } } } if (m_event) { CloseHandle(m_event); m_event = 0; } } } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::ChangeSysState"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaTruncator::Start( IFsaResource* pResource ) /*++ Implements: IFsaTruncator::Start(). --*/ { HRESULT hr = S_OK; CComPtr pSession; CComPtr pCPC; CComPtr pCP; CComPtr pSink; CWsbStringPtr name; WsbTraceIn(OLESTR("CFsaTruncator::Start"), OLESTR("m_state = <%ls>"), FsaStateAsString( m_state ) ); try { if (0 == m_threadId) { // // If the thread is dead, start one. // // Make sure that we don't already have a session, and that we haven't started already. WsbAssert(m_pSession == 0, E_UNEXPECTED); WsbAssert( (HSM_JOB_STATE_IDLE == m_state) || (HSM_JOB_STATE_DONE == m_state) || (HSM_JOB_STATE_CANCELLED == m_state) || (HSM_JOB_STATE_FAILED == m_state), E_UNEXPECTED); // Get the name for the session, increment the runId, and reset the subRunId. WsbAffirmHr(name.LoadFromRsc(_Module.m_hInst, IDS_FSA_TRUNCATOR_NAME)); m_runId++; m_subRunId = 0; // Begin a Session. WsbAffirmHr(pResource->BeginSession(name, HSM_JOB_LOG_NONE, m_runId, m_subRunId, &pSession)); m_pSession = pSession; // Ask the session to advise of every event. WsbAffirmHr(pSession->QueryInterface(IID_IConnectionPointContainer, (void**) &pCPC)); WsbAffirmHr(pCPC->FindConnectionPoint(IID_IHsmSessionSinkEveryEvent, &pCP)); WsbAffirmHr(((IUnknown*) (IFsaTruncator*) this)->QueryInterface(IID_IHsmSessionSinkEveryEvent, (void**) &pSink)); WsbAffirmHr(pCP->Advise(pSink, &m_cookie)); try { if (0 == m_event) { WsbAssertHandle(m_event = CreateEvent(NULL, FALSE, FALSE, NULL)); } // Now that we have prepared, create the thread that will do the scanning! WsbAffirm((m_threadHandle = CreateThread(0, 0, FsaStartTruncator, (void*) this, 0, &m_threadId)) != 0, HRESULT_FROM_WIN32(GetLastError())); } WsbCatchAndDo(hr, SetState(HSM_JOB_STATE_FAILED);); } else { // The thread is still alive, just keep it going. If it is in a state that would // cause it to exit, then make it active again. WsbAssert(m_pSession != 0, E_UNEXPECTED); if ((HSM_JOB_STATE_ACTIVE != m_state) && (HSM_JOB_STATE_PAUSING != m_state) && (HSM_JOB_STATE_PAUSED != m_state) && (HSM_JOB_STATE_RESUMING != m_state)) { WsbAffirmHr(SetState(HSM_JOB_STATE_ACTIVE)); } } } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaTruncator::Start"), OLESTR("hr = <%ls> m_state = <%ls>"), WsbHrAsString(hr), FsaStateAsString( m_state ) ); return(hr); } HRESULT CFsaTruncator::StartScan( void ) /*++ --*/ { ULONG adjustedFreeLevel = 0; HRESULT hr = S_OK; HRESULT hr2; LONGLONG itemOffset; LONGLONG itemSize = 0; LONGLONG fileId; ULONG freeLevel; ULONG hsmLevel; BOOL skipFile; BOOL dummy; LONGLONG llLastTruncTime = 0; LONGLONG llRunIntervalTicks; FILETIME recallTime, currentTime, accessTime, criteriaTime, premRecAccessTime; LONGLONG totalVolumeSpace; CComPtr pResource; CComPtr pResourcePriv; CComPtr pScanItem; CComPtr pPremRec; CComPtr pCPC; CComPtr pCP; try { WsbTrace(OLESTR("CFsaTruncator::StartScan - starting loop\n")); // Increment the ref count so this object (or its session) doesn't // get released before this thread ends ((IUnknown *)(IFsaTruncator *)this)->AddRef(); WsbAssert(m_pSession != 0, E_POINTER); // The thread is running. WsbAffirmHr(SetState(HSM_JOB_STATE_ACTIVE)); // Get the resource WsbAffirmHr(m_pSession->GetResource(&pResource)); WsbAffirmHr(pResource->QueryInterface(IID_IFsaResourcePriv, (void**) &pResourcePriv)); WsbAffirmHr(pResource->GetSizes(&totalVolumeSpace, NULL, NULL, NULL)); // Start with the first path. while ((HSM_JOB_STATE_ACTIVE == m_state) || (HSM_JOB_STATE_PAUSING == m_state) || (HSM_JOB_STATE_PAUSED == m_state) || (HSM_JOB_STATE_RESUMING == m_state)) { WsbTrace(OLESTR("CFsaTruncator::StartScan, top of outside while loop, state = <%ls>\n"), FsaStateAsString( m_state ) ); // If the truncator is running and the resource does not have enough free space // check to see if the resource is over the threshold and truncation is needed. WsbAffirmHr(pResource->GetHsmLevel(&hsmLevel)); WsbAffirmHr(pResource->GetFreeLevel(&freeLevel)); // Because the truncation is asynchronous (FsaPostIt is sent to Engine for // verification and then returned to FSA for actual truncation), the // measured freeLevel may not be very accurate if there are truncations // pending. To compensate for this, we keep an adjustedFreeLevel which // attempts to take into account the pending truncates. We synchronize the // adjustedFreeLevel to the measured freeLevel the first time through and // after we have slept for a while (on the assumption that the pending // truncates have had time to be performed). This still leaves open the possiblility // that the measured freeLevel is wrong (because truncates are pending), but // should be an improvement over just using the measured freeLevel. llRunIntervalTicks = m_runInterval * (WSB_FT_TICKS_PER_SECOND / 1000); GetSystemTimeAsFileTime(¤tTime); if (0 == adjustedFreeLevel || ((WsbFTtoLL(currentTime) - llLastTruncTime) > llRunIntervalTicks)) { adjustedFreeLevel = freeLevel; WsbTrace(OLESTR("CFsaTruncator::StartScan, resetting adjusted free level, RunInterval = %ls, time diff = %ls\n"), WsbQuickString(WsbLonglongAsString(llRunIntervalTicks)), WsbQuickString(WsbLonglongAsString(WsbFTtoLL(currentTime) - llLastTruncTime))); } WsbTrace(OLESTR("CFsaTruncator::StartScan, desired level = %u, free level = %u, adjusted free level = %u\n"), hsmLevel, freeLevel, adjustedFreeLevel); if (adjustedFreeLevel < hsmLevel && HSM_JOB_STATE_ACTIVE == m_state) { CComPtr pDbSession; CComPtr pPremDb; // Open the premigration list, and set the order in which it will be scanned. WsbAffirmHr(pResourcePriv->GetPremigrated(IID_IFsaPremigratedDb, (void**) &pPremDb)); WsbAffirmHr(pPremDb->Open(&pDbSession)); try { WsbAffirmHr(pPremDb->GetEntity(pDbSession, PREMIGRATED_REC_TYPE, IID_IFsaPremigratedRec, (void**) &pPremRec)); // Set the order to get items from the Premigrated List switch (m_SortOrder) { case FSA_SORT_PL_BY_SIZE: WsbAffirmHr(pPremRec->UseKey(PREMIGRATED_SIZE_KEY_TYPE)); break; case FSA_SORT_PL_BY_PATH_NAME: // We use the BagId and offsets instead WsbAffirmHr(pPremRec->UseKey(PREMIGRATED_BAGID_OFFSETS_KEY_TYPE)); break; case FSA_SORT_PL_BY_SIZE_AND_TIME: // We don't know how to handle this one yet WsbThrow(E_UNEXPECTED); break; case FSA_SORT_PL_BY_ACCESS_TIME: default: WsbAffirmHr(pPremRec->UseKey(PREMIGRATED_ACCESS_TIME_KEY_TYPE)); break; } // Make a pass through the list of premigrated files until the // desired level has been reached. Some items that are on the // list may be in a state that causes them to be skipped, but left on the list. WsbAffirmHr(pPremRec->First()); while ((adjustedFreeLevel < hsmLevel) && (HSM_JOB_STATE_ACTIVE == m_state)) { CComPtr pRecRec; WsbTrace(OLESTR("CFsaTruncator::StartScan (top of inside while loop) desired level = %u, adjusted free level = %u\n"), hsmLevel, adjustedFreeLevel); try { skipFile = FALSE; // // Get the access time as recorded in the premigrated record // Note that the real access time cannot be older than the one // in the premigrated list but it can be newer // WsbAffirmHr(pPremRec->GetAccessTime(&premRecAccessTime)); WsbAffirmHr(pResource->GetManageableItemAccessTime(&dummy, &criteriaTime)); if (WsbCompareFileTimes(premRecAccessTime, criteriaTime, TRUE, FALSE) < 0 ) { if (pPremRec->IsWaitingForClose() == S_FALSE) { // // Can skip the current file but NOT break out of the loop since // files with access time old enough and WaitingForClose flag set // may still exists in the list // skipFile = TRUE; } else { // // The access time in the prem. rec is within the window. // This means there aren't any other records which are outside the // user-desired last access window. So break out // WsbTrace(OLESTR("CFsaTruncator::StartScan: breaking out of auto-truncator, encountered item with access time not within criteria\n")); hr = WSB_E_NOTFOUND; break; } } // Get information about the file that could be truncated. WsbAffirmHr(pPremRec->GetFileId(&fileId)); WsbAffirmHr(pPremRec->GetOffset(&itemOffset)); WsbAffirmHr(pPremRec->GetSize(&itemSize)); m_currentPath.Free(); WsbAffirmHr(pPremRec->GetPath(&m_currentPath, 0)); WsbAffirmHr(pPremRec->GetRecallTime(&recallTime)); GetSystemTimeAsFileTime(¤tTime); // Make sure that this file wasn't recently recalled. For now, // this will check for 1 minute. if ((! skipFile) && ( (pPremRec->IsWaitingForClose() == S_FALSE) || ((WsbFTtoLL(currentTime) > WsbFTtoLL(recallTime)) && (WsbCompareFileTimes(recallTime, m_keepRecallTime, TRUE, FALSE) >= 0)) )) { hr = pResource->FindFileId(fileId, m_pSession, &pScanItem); if (hr == WSB_E_NOTFOUND) { // // The file does not exist anymore - remove the record from the list. // WsbAffirmHr(pDbSession->TransactionBegin()); try { // Make sure the record is still in the DB WsbAffirmHr(pPremRec->FindEQ()); WsbAffirmHr(pPremRec->Remove()); WsbAffirmHr(pResourcePriv->RemovePremigratedSize(itemSize)); } WsbCatch(hr); WsbAffirmHr(pDbSession->TransactionEnd()); WsbThrow(hr); } else if (hr != S_OK) { // // Any other error is unexpected - log it and continue // WsbLogEvent(FSA_E_ACCESS_ERROR, 0, NULL, m_currentPath, WsbHrAsString(hr), NULL); WsbThrow(hr); } // // Verify that the file is still in a premigrated state // if (S_OK == pScanItem->IsPremigrated(itemOffset, itemSize)) { WsbAffirmHr(pScanItem->GetAccessTime(&accessTime)); // // accessTime is the last access time for the file // criteriaTime is the 'not accessed in so many ticks' criteria for truncating // the file. // So if (currentTime - accessTime) >= criteriaTime, then the file is ok to be truncated // if (WsbCompareFileTimes(accessTime, criteriaTime, TRUE, FALSE) >=0 ) { // // The file was not accessed within the last access window // WsbTrace(OLESTR("CFsaTruncator::StartScan, truncating file <%ls>\n"), (WCHAR *)m_currentPath); // Try to truncate the file. try { // Create and save a recovery record in case something goes wrong WsbAffirmHr(pPremDb->GetEntity(pDbSession, RECOVERY_REC_TYPE, IID_IFsaRecoveryRec, (void**) &pRecRec)); WsbAffirmHr(pRecRec->SetPath(m_currentPath)); // If the record already exists rewrite it, otherwise create a new record. hr2 = pRecRec->FindEQ(); if (WSB_E_NOTFOUND == hr2) { hr2 = S_OK; WsbAffirmHr(pRecRec->MarkAsNew()); } else if (FAILED(hr2)) { WsbThrow(hr2); } WsbAffirmHr(pRecRec->SetFileId(fileId)); WsbAffirmHr(pRecRec->SetOffsetSize(itemOffset, itemSize)); WsbAffirmHr(pRecRec->SetStatus(FSA_RECOVERY_FLAG_TRUNCATING)); WsbAffirmHr(pRecRec->Write()); // // Set the waiting for close flag to prevent this file // from being selected again while the engine is // processing the truncate. Set the recall time to // now plus 1 hour so we are sure not to retry this // until we have had a chance to truncate it. // WsbAffirmHr(pPremRec->SetIsWaitingForClose(TRUE)); WsbAffirmHr(pPremRec->SetRecallTime(WsbLLtoFT(WsbFTtoLL(currentTime) + WSB_FT_TICKS_PER_HOUR))); hr2 = pPremRec->Write(); // Special code to deal with a problem that has been seen // but isn't understood if (WSB_E_IDB_PRIMARY_KEY_CHANGED == hr2) { WsbAffirmHr(pPremRec->Remove()); WsbAffirmHr(pResourcePriv->RemovePremigratedSize(itemSize)); // Ignore result from DeletePlaceholder since there's nothing we // can do anyway. pScanItem->DeletePlaceholder(itemOffset, itemSize); WsbThrow(FSA_E_SKIPPED); } else { WsbAffirmHr(hr2); } // // Set IsWaitingForClose back to false so that the FindGt done later gets the next record. // This affects the in memory record only and not the persisted record. // WsbAffirmHr(pPremRec->SetIsWaitingForClose(FALSE)); WsbAffirmHr(pScanItem->Truncate(itemOffset, itemSize)); llLastTruncTime = WsbFTtoLL(currentTime); // Add the file size to the adjustedFreeLevel so we know when to // stop doing truncations. Unfortunately, the itemSize is in // bytes but adjustedFreeLevl is a fixed-point percentage so we // have to do a calculation to convert the itemSize adjustedFreeLevel += (ULONG) (((double)itemSize / (double)totalVolumeSpace) * (double)FSA_HSMLEVEL_100); } WsbCatchAndDo(hr, // Do we need to skip this file for the time being? if (FSA_E_SKIPPED == hr) { // Do nothing } else if ((FSA_E_ITEMCHANGED != hr) && (FSA_E_NOTMANAGED != hr)) { // Something unexpected happened, so report the error. WsbAffirmHr(m_pSession->ProcessHr(HSM_JOB_PHASE_FSA_ACTION, 0, 0, hr)); } ); } else { // // File is premigrated, but skipped because the last access was too recent // WsbTrace(OLESTR("CFsaTruncator::StartScan, skipping file <%ls> which is premigrated but last access is too recent\n"), (WCHAR *)m_currentPath); hr = FSA_E_SKIPPED; // // Update the access time in the db for this file // WsbAffirmHr(pPremRec->SetAccessTime(accessTime)); // // Commit this // WsbAffirmHr(pPremRec->Write()); // // Revert the in-memory accessTime to the old access time to // let the enumeration continue (so that FindGT will fetch the next record) // WsbAffirmHr(pPremRec->SetAccessTime(premRecAccessTime)); } } else { // // If the file is no longer managed by HSM or truncated (may have been modified // after it was premigrated) - we remove the record from the list. // Note that if we reached this else close, the condition below should be TRUE // if ( (S_FALSE == pScanItem->IsManaged(itemOffset, itemSize)) || (S_OK == pScanItem->IsTruncated(itemOffset, itemSize)) ) { WsbAffirmHr(pDbSession->TransactionBegin()); try { // Make sure the record is still in the DB WsbAffirmHr(pPremRec->FindEQ()); WsbAffirmHr(pPremRec->Remove()); WsbAffirmHr(pResourcePriv->RemovePremigratedSize(itemSize)); } WsbCatch(hr); WsbAffirmHr(pDbSession->TransactionEnd()); // Ignore hr of the removal itself (truncated files may have been removed by another thread) hr = WSB_E_NOTFOUND; WsbThrow(hr); } } // Tell the session we saw the file, and whether we were able to truncate it. WsbAffirmHr(m_pSession->ProcessItem(HSM_JOB_PHASE_FSA_ACTION, HSM_JOB_ACTION_TRUNCATE, pScanItem, hr)); // Don't let this errors stop us from continuing to process the list. hr = S_OK; } else { // // File is premigrated, but skipped because the last access was too recent or // because it was recalled recently // WsbTrace(OLESTR("CFsaTruncator::StartScan, skipping file <%ls> since its last access time is too recent or recently recalled\n"), (WCHAR *)m_currentPath); hr = FSA_E_SKIPPED; } } WsbCatchAndDo(hr, if (WSB_E_NOTFOUND != hr) { m_pSession->ProcessHr(HSM_JOB_PHASE_FSA_ACTION, __FILE__, __LINE__, hr); } // Don't let this errors stop us from continuing to process the list. hr = S_OK; ); // If item is skipped - set hr to OK (this is not really an error) if (FSA_E_SKIPPED == hr) { hr = S_OK; } // Remove recovery record if (pRecRec) { WsbAffirmHr(pRecRec->FindEQ()); WsbAffirmHr(pRecRec->Remove()); pRecRec = NULL; } // Get the desired level again in case it changed WsbAffirmHr(pResource->GetHsmLevel(&hsmLevel)); // Free the scan item. pScanItem = 0; // Whether we removed or skipped the item, go on to the next item. WsbAffirmHr(pPremRec->FindGT()); WsbTrace(OLESTR("CFsaTruncator::StartScan, bottom of inside while loop, state = <%ls>\n"), FsaStateAsString( m_state ) ); } // inner while } WsbCatch(hr); // Free the premigrated record object and close the data base. try { pPremRec = 0; WsbAffirmHr(pPremDb->Close(pDbSession)); } WsbCatchAndDo(hr2, m_pSession->ProcessHr(HSM_JOB_PHASE_ALL, __FILE__, __LINE__, hr2); ); } // Sleep or wait for an event signal. // If the event is signaled it means that data was just moved for this // volume and there should be something to do. if (SUCCEEDED(hr) || WSB_E_NOTFOUND == hr) { ULONG l_runInterval; // If we got to the end of the list, then wait a little longer. This // is because we probably won't be able to do anything when we retry. if (WSB_E_NOTFOUND == hr) { l_runInterval = m_runInterval * 10; } else { l_runInterval = m_runInterval; } WsbTrace(OLESTR("CFsaTruncator::StartScan, sleeping for %lu msec\n"), l_runInterval); switch(WaitForSingleObject(m_event, l_runInterval)) { case WAIT_FAILED: WsbTrace(OLESTR("CFsaTruncator::StartScan, Wait for Single Object returned error %lu\n"), GetLastError()); break; case WAIT_TIMEOUT: WsbTrace(OLESTR("CFsaTruncator::StartScan, Awakened by timeout.\n")); // Set adjustedFreeLevel to zero so it will get reset to current freeLevel; adjustedFreeLevel = 0; break; default: WsbTrace(OLESTR("CFsaTruncator::StartScan, Awakened by kick start.\n")); break; } } else { WsbThrow(hr); } WsbTrace(OLESTR("CFsaTruncator::StartScan, bottom of outside while loop, state = <%ls>\n"), FsaStateAsString( m_state ) ); } } WsbCatch(hr); m_threadHr = hr; // The thread is exiting, so tell the session. if (FAILED(hr)) { hr2 = SetState(HSM_JOB_STATE_FAILED); } else { hr2 = SetState(HSM_JOB_STATE_DONE); } if (FAILED(hr2)) { m_pSession->ProcessHr(HSM_JOB_PHASE_ALL, __FILE__, __LINE__, hr2); } // Regardless of how this thread is exiting, we need to unadvise from the session. // Indicate that we no longer want to be advised of events. if ((m_pSession != 0) && (m_cookie != 0)) { try { WsbAffirmHr(m_pSession->QueryInterface(IID_IConnectionPointContainer, (void**) &pCPC)); WsbAffirmHr(pCPC->FindConnectionPoint(IID_IHsmSessionSinkEveryEvent, &pCP)); pCP->Unadvise(m_cookie); m_cookie = 0; } WsbCatch(hr); } // Since we have terminated, we should release the session. m_pSession = 0; // Clean up after this thread. CloseHandle(m_threadHandle); m_threadId = 0; m_threadHandle = 0; // Decrement ref count so this object can be release ((IUnknown *)(IFsaTruncator *)this)->Release(); WsbTrace(OLESTR("CFsaTruncator::StartScan - terminating, hr = <%ls>, m_state = <%ls>\n"), WsbHrAsString(hr), FsaStateAsString( m_state ) ); return(hr); }