// Copyright (c) 1996-1999 Microsoft Corporation //+------------------------------------------------------------------------- // // trkwks.hxx // // Definitions local to this directory. // //-------------------------------------------------------------------------- #ifndef _TRKWKS_HXX_ #define _TRKWKS_HXX_ #include "trklib.hxx" #include "wksconfig.hxx" #include #include #include // MOUNTMGR_CHANGE_NOTIFY_INFO, MOUNTDEV_MOUNTED_DEVICE_GUID // // General defines // extern const GUID s_guidLogSignature; #ifndef EVENT_SOURCE_DEFINED #define EVENT_SOURCE_DEFINED const extern TCHAR* g_ptszEventSource INIT( TEXT("Distributed Link Tracking Client") ); #endif TRKSVR_MESSAGE_PRIORITY GetSvrMessagePriority( LONGLONG llLastDue, LONGLONG llPeriod ); // pass in in seconds //+---------------------------------------------------------------------------- // // CDirectoryName // // This class just holds a directory name, and offers a method // to create one from a file name. // //+---------------------------------------------------------------------------- class CDirectoryName { private: // Name of the directory. TCHAR _tsz[ MAX_PATH + 1 ]; public: // Assumes that ptszFile is an absolute path (either Win32 // or NT). inline BOOL SetFromFileName( const TCHAR *ptszFile ); inline operator const TCHAR *() const; }; // Infer the directory name from a file name. inline BOOL CDirectoryName::SetFromFileName( const TCHAR *ptszFile ) { // Compose the directory name by copying the path, finding its last // whack, and replacing it with a terminator. TCHAR *ptcTmp = NULL; _tcscpy( _tsz, ptszFile ); ptcTmp = _tcsrchr( _tsz, TEXT('\\') ); if(NULL == ptcTmp) { TrkLog((TRKDBG_ERROR, TEXT("Can't get directory name for (%s)"), ptszFile)); return( FALSE ); } *ptcTmp = TEXT('\0'); return( TRUE ); } // Return the directory name inline CDirectoryName::operator const TCHAR *() const { return( _tsz ); } //+------------------------------------------------------------------------- // // CSecureFile // // A file only accessible to admins/system // // This class maintains the file handle. Note that // the file is opened asynchronous. // //-------------------------------------------------------------------------- class CSecureFile { public: inline CSecureFile(); inline ~CSecureFile(); inline void Initialize(); // Open/close/create operations inline BOOL IsOpen() const; inline void CloseFile(); NTSTATUS CreateAlwaysSecureFile(const TCHAR * ptszFile); NTSTATUS CreateSecureDirectory( const TCHAR *ptszDirectory ); NTSTATUS OpenExistingSecureFile( const TCHAR * ptszFile, BOOL fReadOnly ); NTSTATUS RenameSecureFile( const TCHAR *ptszFile ); // Win32 operations inline DWORD GetFileSize(LPDWORD lpFileSizeHigh = NULL); inline BOOL SetEndOfFile(); inline DWORD SetFilePointer(LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod); inline BOOL ReadFile( LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped = NULL); inline BOOL WriteFile(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped = NULL); protected: inline LONG Lock(); inline LONG Unlock(); protected: // Handle to the file. HANDLE _hFile; // Critical section for this class. CCriticalSection _csSecureFile; LONG _cLocks; }; // class CSecureFile inline CSecureFile::CSecureFile() { _hFile = NULL; _cLocks = 0; } inline void CSecureFile::Initialize() { // This routine can be called multiple times, since the log // file may be re-opened. if( !_csSecureFile.IsInitialized() ) _csSecureFile.Initialize(); } inline CSecureFile::~CSecureFile() { TrkAssert( _hFile == NULL ); _csSecureFile.UnInitialize(); } // Take the critical section inline LONG CSecureFile::Lock() { return InterlockedIncrement( &_cLocks ); } // Leave the critical section inline LONG CSecureFile::Unlock() { LONG cLocks = _cLocks; InterlockedDecrement( &_cLocks ); return( cLocks ); } // Is _hFile open? inline BOOL CSecureFile::IsOpen() const { return(_hFile != NULL); } // Close _hFile inline void CSecureFile::CloseFile() // doesn't raise { LONG cLocks = Lock(); __try { if( IsOpen() ) NtClose( _hFile ); } __except( BreakOnDebuggableException() ) { TrkLog(( TRKDBG_ERROR, TEXT("Exception %08x in CSecureFile::CloseFile"), GetExceptionCode() )); } _hFile = NULL; #if DBG TrkVerify( Unlock() == cLocks ); #else Unlock(); #endif } // Ge the size of the file inline DWORD CSecureFile::GetFileSize(LPDWORD lpFileSizeHigh) { DWORD dwSize; LONG cLocks = Lock(); { TrkAssert(IsOpen()); dwSize = ::GetFileSize(_hFile, lpFileSizeHigh); } #if DBG TrkVerify( Unlock() == cLocks ); #else Unlock(); #endif return( dwSize ); } // Set the size of the file inline BOOL CSecureFile::SetEndOfFile() { BOOL fReturn; LONG cLocks = Lock(); { TrkAssert(IsOpen()); fReturn = ::SetEndOfFile(_hFile); } #if DBG TrkVerify( Unlock() == cLocks ); #else Unlock(); #endif return( fReturn ); } // Seek _hFile inline DWORD CSecureFile::SetFilePointer(LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod) { DWORD dwReturn; LONG cLocks = Lock(); { TrkAssert(IsOpen()); dwReturn = ::SetFilePointer(_hFile, lDistanceToMove, lpDistanceToMoveHigh, dwMoveMethod); } #if DBG TrkVerify( Unlock() == cLocks ); #else Unlock(); #endif return( dwReturn ); } // Read from _hFile inline BOOL CSecureFile::ReadFile( LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped) { BOOL fReturn; LONG cLocks = Lock(); __try { TrkAssert(IsOpen()); fReturn = ::ReadFile( _hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped); } __finally { #if DBG TrkVerify( Unlock() == cLocks ); #else Unlock(); #endif } return( fReturn ); } // Write to _hFile inline BOOL CSecureFile::WriteFile(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped) { BOOL fReturn; LONG cLocks = Lock(); { TrkAssert(IsOpen()); fReturn = ::WriteFile(_hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); } #if DBG TrkVerify( Unlock() == cLocks ); #else Unlock(); #endif return( fReturn ); } //+------------------------------------------------------------------------- // // PRobustlyCreateableFile // // Abstraction of what makes a file robustly creatable // // Deals with creating the temporary file names used // when creating a file (or recreating a corrupt file) // and the transacted file rename operation (MoveFile) // //-------------------------------------------------------------------------- enum RCF_RESULT { CORRUPT, // file closed after being found to be corrupt NOT_FOUND, // file not found OK // file now open after being validated as not corrupt // All other conditions result in an exception.. }; class PRobustlyCreateableFile { protected: void RobustlyCreateFile( const TCHAR * ptszFile, TCHAR tcVolumeDriveLetter ); void RobustlyDeleteFile( const TCHAR * ptszFile ); virtual RCF_RESULT OpenExistingFile( const TCHAR * ptszFile ) = 0; virtual void CreateAlwaysFile( const TCHAR * ptszTempFile ) = 0; }; //+------------------------------------------------------------------------- // // CLogMoveMessage // // Structure for logging moves in the log // //-------------------------------------------------------------------------- class CLogMoveMessage { public: CLogMoveMessage( const MOVE_MESSAGE & MoveMessage ); // The link source's birth ID. CDomainRelativeObjId _droidBirth; // The link source's droid before the move. CDomainRelativeObjId _droidCurrent; // The link source's droid after the move. CDomainRelativeObjId _droidNew; // The machine to which the link source was moved. CMachineId _mcidNew; }; inline CLogMoveMessage::CLogMoveMessage( const MOVE_MESSAGE & MoveMessage ) : _droidCurrent( CVolumeId(MoveMessage.SourceVolumeId), // LINK_TRACKING_INFORMATION CObjId(FOB_OBJECTID, MoveMessage.SourceObjectId) ), // FILE_OBJECTID_BUFFER _droidNew( CVolumeId(MoveMessage.TargetVolumeId), // LINK_TRACKING_INFORMATION CObjId(MoveMessage.TargetObjectId) ), // GUID _droidBirth( CVolumeId(MoveMessage.SourceObjectId), // FILE_OBJECTID_BUFFER CObjId(FOB_BIRTHID, MoveMessage.SourceObjectId) ), // FILE_OBJECTID_BUFFER _mcidNew( MoveMessage.MachineId ) { } //+---------------------------------------------------------------------------- // // Log-related structures // //+---------------------------------------------------------------------------- // // The Tracking (Workstation) move notification log is used to cache // move notifications. We write to this log during the move, then // asynchronously copy the notifications up to the Tracking (Server) // service (the DC). // // The first sector of the log file is used for a header. There // is a primary header, and an 'extended' header. The extended // header area is used as a general storage area by clients // of CLogFile. // // The rest of the file is composed of a doubly-linked list of // move notifications. Each notification is always wholly within // a sector, so we can assume that a notification is always written // atomically. Every time we write a move notification, we need to // update some log meta-data. Instead of writing this to the log // header, which would require us to do two sector writes and // therefore require us to add transactioning, we write the meta // data to an "entry header", which is at the end of each sector. // So each of the sectors beyond sector 0 has a few move notifications // and an entry-header. // // If necessary we can grow the log file, initializing the new area as // a linked list, and linking it into the existing list. The structure // is shown below. // // +------------------------------+ <---+ // | Log Header | | // | | | // | -------------------------- | | // | | +---- Sector 0 // | Extended Header: | | // | Log Info | | // | Volume Persistent Info | | // | | | // +------------------------------+ <---+ // | | | // | Log Move Notification | | // | | | // | -------------------------- | | // | | | // | Log Move Notification | | // | | | // | -------------------------- | +---- Sectors 1 - n // | | | // | Log Move Notification | | // | | | // | -------------------------- | | // | | | // | Log Entry Header | | // +------------------------------+ | // * | // * | // * // // There are four classes which combine to provide access to the log // file. The outermost class is CLog, and it is through this class // that most of the code accesses the log. CLog, though, relies // on the CLogFile class to maintain the physical layout of the file; // CLog understands, e.g., move notifications, while CLogFile just // understands the header and the linked-list of log entries. // CLogFile relies on the CLogFileHeader and CLogFileSector helper // classes. // // PerfBug: Note that if we ever modify this log format, // we should get rid of the source volid from the move // notification entries (or some other similar optimization); // since a log applies to a volume, the volid is usually // redundant. // The format (version) of the log is in major/minor form // (16 bits for each). Incrementing the minor revision level // indicates a change to the log that is compatible with // downlevel versions. Such versions, though, should set // the DownlevelDirtied flag in the header to indicate that // uplevel versions may need to do a cleanup. #define CLOG_MAJOR_FORMAT 0x1 #define CLOG_MINOR_FORMAT 0x0 #define CLOG_FORMAT ( (CLOG_MAJOR_FORMAT<<16) | CLOG_MINOR_FORMAT ) inline WORD GetLogMajorFormat( DWORD dw ) { return( dw >> 16 ); } inline WORD GetLogMinorFormat( DWORD dw ) { return( dw & 0xFFFF ); } // A LogIndex is a zero-based index of log entries // in the *physical* file. It cannot, for example, be advanced simply // by incrementing, it must be advanced by traversing the linked-list. typedef unsigned long LogIndex; // ilog // Types of an entry in the log typedef enum enumLE_TYPE { LE_TYPE_UNINITIALIZED = 0, LE_TYPE_EMPTY = 1, LE_TYPE_MOVE = 2 } LE_TYPE; // A move notification structure shows all the necessary // information about a move; where it was born, where it // was a moment ago, and where it's moved to. This // structure is written to the workstation tracking log. // BUGBUG: Use a compression (lookup table) so that in the // typical case where all the volids are the same, we don't // need to use 16 bytes here. typedef struct // lmn { // Type (empty or move) LE_TYPE type; // Sequence number for this entry, used to keep // in sync with trksvr. SequenceNumber seq; // Reserved for future use DWORD iVolIdCurrent; // Object ID before the move. CObjId objidCurrent; // Droid after the move. CDomainRelativeObjId droidNew; // Machine to which the file was moved. CMachineId mcidNew; // Birth ID of the link source. CDomainRelativeObjId droidBirth; // Time of the move. DWORD DateWritten; // Reserved for future use. DWORD dwReserved; } LogMoveNotification; // The LogHeader structure is maintained by CLogFile, and // stores general data about the log has a whole. // This structure is always stored at the very beginning of the file. // If the shutdown bit is not set, then CLogFile performs a recovery. // Clients of CLogFile (i.e. CLog) then have an opportunity to perform // their own recovery. #define NUM_HEADER_SECTORS 1 enum ELogHeaderFlags { PROPER_SHUTDOWN = 0x1, // Indicates log shutdown properly DOWNLEVEL_DIRTIED = 0x2 // Indicates a downlevel implemetation touched log. }; typedef struct _LogHeader { GUID guidSignature; // Signature for the log DWORD dwFormat; // Version of the log DWORD dwFlags; // ELogHeaderFlags enum DWORD dwReserved; // The 'expand' sub-structure holds the dimensions of // a log before it's expanded. If we crash during the // expand, we'll use this info to restore the log to it's // pre-expansion shape. struct { LogIndex ilogStart; LogIndex ilogEnd; ULONG cbFile; } expand; inline BOOL IsShutdown( ) const { return( 0 != (dwFlags & PROPER_SHUTDOWN) ); } inline BOOL IsDownlevelDirtied( ) const { return( 0 != (dwFlags & DOWNLEVEL_DIRTIED) ); } } LogHeader; // The LogInfo structure is used by the workstation service to store // the runtime log information. This information can also be // determined by reading through the log, so this is really a // cache of information. Since this data can be re-calculated // (albeit slowly), we don't bother to write it to disk every time // we update it. It's just used to save some time during an Open // in the normal case where the log was previously shutdown properly. // This structure is stored in the log header, after the LogHeader // structure, as part of the 'extended' log header area. typedef struct _LogInfo { LogIndex ilogStart; // The beginning of the linked-list LogIndex ilogEnd; // The end of the linked-list LogIndex ilogWrite; // The next entry to be written LogIndex ilogRead; // The next entry to be read LogIndex ilogLast; // The most entry most recently written SequenceNumber seqNext; // The new seq num to use SequenceNumber seqLastRead; // The seq num of entry[ilogRead-1] } LogInfo; // The VolumePersistentInfo structure is stored in the log // header, also as part of the 'extended' log header area, // and allows us to detect when a volume has been moved or // a machine has been renamed. In such an event, the password // allows us to re-claim the volume with the DC. typedef struct _VolumePersistentInfo { CMachineId machine; CVolumeId volid; CVolumeSecret secret; CFILETIME cftLastRefresh; CFILETIME cftEnterNotOwned; BOOL fDoMakeAllOidsReborn; BOOL fNotCreated; void Initialize() { memset(this, 0, sizeof(struct _VolumePersistentInfo)); machine = CMachineId(); volid = CVolumeId(); secret = CVolumeSecret(); cftLastRefresh = cftEnterNotOwned = CFILETIME(0); //fDoMakeAllOidsReborn = fNotCreated = FALSE; } BOOL operator == (const struct _VolumePersistentInfo & volinfo) const { return( machine == volinfo.machine && volid == volinfo.volid && secret == volinfo.secret && cftLastRefresh == volinfo.cftLastRefresh && cftEnterNotOwned == volinfo.cftEnterNotOwned ); } BOOL operator != (const struct _VolumePersistentInfo & volinfo) const { return( !(*this == volinfo) ); } } VolumePersistentInfo; // These defines determine how the ExtendedHeader area is divied up. // (We'll ensure that this doesn't exceed 512 - sizeof(LogHeader) bytes, // and assume that sectors are always >= 512). #define CVOLUME_HEADER_START 0 #define CVOLUME_HEADER_LENGTH sizeof(VolumePersistentInfo) #define CLOG_LOGINFO_START (CVOLUME_HEADER_START + CVOLUME_HEADER_LENGTH) #define CLOG_LOGINFO_LENGTH sizeof(LogInfo) #define CB_EXTENDED_HEADER ( CLOG_LOGINFO_START + CLOG_LOGINFO_LENGTH ) // We require that the volume's sector size be at least 256 #define MIN_LOG_SECTOR_SIZE 256 // The following structure defines a header which is conceptually // associate with an entry. When a client writes to an entry // header, all previous entry headers are considered invalid. // In truth, this header is stored per-sector rather than // per-entry, but this is opaque to the client (CLogFile). typedef struct _LogEntryHeader { LogIndex ilogRead; // Next record to be uploaded to the server SequenceNumber seq; // Next sequence number to use (during a write) DWORD rgdwReserved[2]; } LogEntryHeader; // An entry in the log. CLogFile sees LogEntry, CLog only sees the 'move' field. // The log is composed of a header sector (with LogHeader and the extended // header area), followed by a linked-list of LogEntry's. typedef struct tagLogEntry // le { LogIndex ilogNext; LogIndex ilogPrevious; LogMoveNotification move; } LogEntry; // Types of flushes #define FLUSH_IF_DIRTY 0 #define FLUSH_UNCONDITIONALLY 1 #define FLUSH_TO_CACHE 0 #define FLUSH_THROUGH_CACHE 2 //+---------------------------------------------------------------------------- // // Class: CLogFileHeader // // This class represents the header portion of the log file. It is used // exclusively by CLogFile, and therefore provides no thread-safety mechanisms // of its own. // // The dirty bit is automatically maintained, and flushes are automatic, // though the caller may still make explicit flushes. // //+---------------------------------------------------------------------------- #define C_LOG_FILE_HEADER_SECTORS 1 class CLogFileHeader { // ------------ // Construction // ------------ public: CLogFileHeader() { memset( this, 0, sizeof(*this) ); _hFile = NULL; } ~CLogFileHeader() { UnInitialize(); } // -------------- // Initialization // -------------- public: void Initialize( ULONG cbSector ); void UnInitialize(); // --------------- // Exposed Methods // --------------- public: void OnCreate( HANDLE hFile ); void OnOpen( HANDLE hFile ); BOOL IsOpen() const; BOOL IsDirty() const; void OnClose(); void SetShutdown( BOOL fShutdown = TRUE ); void SetDirty( BOOL fDirty = TRUE ); GUID GetSignature() const; DWORD GetFormat() const; WORD GetMajorFormat() const; WORD GetMinorFormat() const; void SetDownlevelDirtied(); ULONG NumSectors() const; BOOL IsShutdown() const; void Flush( ); void SetExpansionData( ULONG cbLogFile, ULONG ilogStart, ULONG ilogEnd ); void ClearExpansionData(); BOOL IsExpansionDataClear(); BOOL ExpansionInProgress() const; ULONG GetPreExpansionSize() const; LogIndex GetPreExpansionStart() const; LogIndex GetPreExpansionEnd() const; void ReadExtended( ULONG iOffset, void *pv, ULONG cb ); void WriteExtended( ULONG iOffset, const void *pv, ULONG cb ); // ---------------- // Internal Methods // ---------------- private: void LoadHeader( HANDLE hFile ); void RaiseIfNotOpen() const; // -------------- // Internal State // -------------- private: // Should the header be flushed? BOOL _fDirty:1; // The underlying log file. This is only non-NULL if the header has // been successfully loaded. HANDLE _hFile; // The beginning of the sector containing the header LogHeader *_plogheader; // The extended portion of the header (points within the buffer // pointed to by _plogheader). void *_pextendedheader; // The size of the header sector. ULONG _cbSector; }; // ---------------------- // CLogFileHeader inlines // ---------------------- // How many sectors are used by the header? inline ULONG CLogFileHeader::NumSectors() const { return( C_LOG_FILE_HEADER_SECTORS ); } inline void CLogFileHeader::RaiseIfNotOpen() const { if( NULL == _plogheader || !IsOpen() ) TrkRaiseWin32Error( ERROR_OPEN_FAILED ); } // Set the shutdown flag inline void CLogFileHeader::SetShutdown( BOOL fShutdown ) { RaiseIfNotOpen(); // If this is a new shutdown state, we must flush to disk. if( _plogheader->IsShutdown() && !fShutdown || !_plogheader->IsShutdown() && fShutdown ) { if( fShutdown ) _plogheader->dwFlags |= PROPER_SHUTDOWN; else _plogheader->dwFlags &= ~PROPER_SHUTDOWN; _fDirty = TRUE; Flush(); } } // Get the log signature inline GUID CLogFileHeader::GetSignature() const { RaiseIfNotOpen(); return( _plogheader->guidSignature ); } // Get the format version of the log inline DWORD CLogFileHeader::GetFormat() const { RaiseIfNotOpen(); return( _plogheader->dwFormat ); } inline WORD CLogFileHeader::GetMajorFormat() const { RaiseIfNotOpen(); return( _plogheader->dwFormat >> 16 ); } inline WORD CLogFileHeader::GetMinorFormat() const { RaiseIfNotOpen(); return( _plogheader->dwFormat & 0xFFFF ); } // Show that the log file has been touched by a downlevel // implementation (i.e. one that supported the same major // version level, but an older minor version level). inline void CLogFileHeader::SetDownlevelDirtied() { // The act of setting this bit doesn't itself make // the header dirty. RaiseIfNotOpen(); _plogheader->dwFlags |= DOWNLEVEL_DIRTIED; } // Has the log been properly shut down? inline BOOL CLogFileHeader::IsShutdown() const { RaiseIfNotOpen(); return( _plogheader->IsShutdown() ); } // Was the log closed while an expansion was in progress? inline BOOL CLogFileHeader::ExpansionInProgress() const { RaiseIfNotOpen(); return( 0 != _plogheader->expand.cbFile ); } // How big was the log before the expansion started? inline ULONG CLogFileHeader::GetPreExpansionSize() const { RaiseIfNotOpen(); return( _plogheader->expand.cbFile ); } // Where did the log start before the expansion started? inline LogIndex CLogFileHeader::GetPreExpansionStart() const { RaiseIfNotOpen(); return( _plogheader->expand.ilogStart ); } // Where did the log end before the expansion started? inline LogIndex CLogFileHeader::GetPreExpansionEnd() const { RaiseIfNotOpen(); return( _plogheader->expand.ilogEnd ); } // Clear the expansion data and flush it to the disk // (called after an expansion). inline void CLogFileHeader::ClearExpansionData() { RaiseIfNotOpen(); memset( &_plogheader->expand, 0, sizeof(_plogheader->expand) ); Flush( ); } // Is there no expansion data? inline BOOL CLogFileHeader::IsExpansionDataClear() { RaiseIfNotOpen(); if(_plogheader->expand.ilogStart == 0 && _plogheader->expand.ilogEnd == 0 && _plogheader->expand.cbFile == 0) { return TRUE; } return FALSE; } // Handle a file create event inline void CLogFileHeader::OnCreate( HANDLE hFile ) { TrkAssert( NULL == _hFile ); TrkAssert( NULL != hFile ); TrkAssert( NULL != _plogheader ); // Initialize the header memset( _plogheader, 0, _cbSector ); _plogheader->dwFormat = CLOG_FORMAT; _plogheader->guidSignature = s_guidLogSignature; _hFile = hFile; SetDirty(); } // Handle a file Open event inline void CLogFileHeader::OnOpen( HANDLE hFile ) { #if DBG TrkAssert( NULL == _hFile ); TrkAssert( NULL != hFile ); #endif LoadHeader( hFile ); } // Is the file open? inline BOOL CLogFileHeader::IsOpen() const { return( NULL != _hFile ); } // Is the file dirty? inline BOOL CLogFileHeader::IsDirty() const { return( _fDirty ); } // Handle a file Close event inline void CLogFileHeader::OnClose() { #if DBG if( _fDirty ) TrkLog(( TRKDBG_ERROR, TEXT("LogFileHeader closed while dirty") )); #endif _fDirty = FALSE; _hFile = NULL; } // Set/clear the dirty bit inline void CLogFileHeader::SetDirty( BOOL fDirty ) { _fDirty = fDirty; if( _fDirty ) SetShutdown( FALSE ); } //+---------------------------------------------------------------------------- // // Class: CLogFileSector // // This class represents the sectors of the log file, and is used exclusively // by the CLogFile class. Therefore, it provides no thread-safety mechanisms // of its own. // // The dirty bit and flushes are maintained automatically, though the caller // may still make explicite flushes. // //+---------------------------------------------------------------------------- class CLogFileSector { // ------------ // Construction // ------------ public: CLogFileSector() { memset( this, 0, sizeof(*this) ); _hFile = NULL; } ~CLogFileSector() { UnInitialize(); } // -------------- // Initialization // -------------- public: void Initialize( ULONG cSkipSectors, ULONG cbSector ); void UnInitialize(); // --------------- // Exposed Methods // --------------- public: void SetDirty( BOOL fDirty = TRUE ); void Flush( ); ULONG NumEntries() const; void OnCreate( HANDLE hFile ); void OnOpen( HANDLE hFile ); BOOL IsOpen() const; BOOL IsDirty() const; void OnClose(); void InitSectorHeader(); LogEntry *GetLogEntry( LogIndex ilogEntry ); // Sets the dirty flag const LogEntry *ReadLogEntry( LogIndex ilogEntry ); // Doesn't set dirty void WriteMoveNotification( LogIndex ilogEntry, const LogMoveNotification &lmn ); LogEntryHeader ReadEntryHeader( LogIndex ilogEntry ); void WriteEntryHeader( LogIndex ilogEntry, LogEntryHeader entryheader ); // ---------------- // Internal Methods // ---------------- private: void LoadSector( LogIndex ilogEntry ); void RaiseIfNotOpen() const; // -------------- // Internal State // -------------- private: // Is _pvSector valid? BOOL _fValid:1; // Are we dirty? BOOL _fDirty:1; // A handle to the log file HANDLE _hFile; // How many sectors aren't we allowed to use at // the front of the log file? ULONG _cSkipSectors; // The size of the sector, and the number of entries it can hold ULONG _cbSector; ULONG _cEntriesPerSector; // The index of the entry which is at the front // of _pvSector LogIndex _ilogCurrentFirst; // Points to the loaded sector (when _fValid) void *_pvSector; // Points to the entry header of the valid sector LogEntryHeader *_pEntryHeader; }; // ------------------ // CLogSector Inlines // ------------------ // Called when a log file is created. inline void CLogFileSector::OnCreate( HANDLE hFile ) { OnOpen( hFile ); } inline void CLogFileSector::RaiseIfNotOpen() const { if( NULL == _pvSector || !IsOpen() ) TrkRaiseWin32Error( ERROR_OPEN_FAILED ); } // Called when a log file is opened. inline void CLogFileSector::OnOpen( HANDLE hFile ) { #if DBG TrkAssert( NULL != _pvSector ); TrkAssert( NULL == _hFile ); TrkAssert( NULL != hFile ); #endif _hFile = hFile; } inline BOOL CLogFileSector::IsOpen() const { return( NULL != _hFile ); } inline void CLogFileSector::InitSectorHeader() { if( NULL != _pEntryHeader ) memset( _pEntryHeader, 0, sizeof(*_pEntryHeader) ); } inline BOOL CLogFileSector::IsDirty() const { return( _fDirty ); } // Called when a log file is closed inline void CLogFileSector::OnClose() { #if DBG if( _fDirty ) TrkLog(( TRKDBG_ERROR, TEXT("LogFileSector closed while dirty") )); #endif _fDirty = FALSE; _hFile = NULL; _fValid = FALSE; } // Set the dirty bit inline void CLogFileSector::SetDirty( BOOL fDirty ) { RaiseIfNotOpen(); _fDirty = fDirty; } // Read & return the requested log entry. Note that the // sector is now considered dirty (unlike ReadLogEntry) inline LogEntry* CLogFileSector::GetLogEntry( LogIndex ilogEntry ) { TrkAssert( _pvSector ); RaiseIfNotOpen(); LoadSector( ilogEntry ); SetDirty(); return( &static_cast(_pvSector)[ ilogEntry - _ilogCurrentFirst ] ); } // Read & return the requested log entry. The sector // is not subsequently considered dirty (unlike GetLogEntry) inline const LogEntry* CLogFileSector::ReadLogEntry( LogIndex ilogEntry ) { TrkAssert( _pvSector ); RaiseIfNotOpen(); LoadSector( ilogEntry ); return( &static_cast(_pvSector)[ ilogEntry - _ilogCurrentFirst ] ); } // Write an entry header inline void CLogFileSector::WriteEntryHeader( LogIndex ilogEntry, LogEntryHeader entryheader ) { TrkAssert( _pvSector ); RaiseIfNotOpen(); LoadSector( ilogEntry ); *_pEntryHeader = entryheader; SetDirty(); } // Read an entry header inline LogEntryHeader CLogFileSector::ReadEntryHeader( LogIndex ilogEntry ) { RaiseIfNotOpen(); LoadSector( ilogEntry ); return( *_pEntryHeader ); } // Write a move notification inline void CLogFileSector::WriteMoveNotification( LogIndex ilogEntry, const LogMoveNotification &lmn ) { RaiseIfNotOpen(); GetLogEntry( ilogEntry )->move = lmn; SetDirty(); } // How many entries can fit in a sector? inline ULONG CLogFileSector::NumEntries() const { return( _cEntriesPerSector ); } //+------------------------------------------------------------------------- // // Class: CLogFile // // Purpose: This class represents the file which contains the // Tracking/Workstation move notification log. Clients // of this class may request one entry or header at a time, // using based on a log entry index. // // Entries in the log file are joined by a linked-list, // so this class includes methods that clients use to // advance their log entry index (i.e., traverse the list). // // Notes: CLogFile reads/writes a sector at a time for reliability. // When a client modifies a log entry in one sector, then // attempts to access another sector, CLogFile automatically // flushes the changes. This is dependent, however, on the // client properly calling the SetDirty method whenever it // changes a log entry or header. // // CLogFile implements no thread-safety mechanism; rather // it relies on the caller. This is acceptable in the // link-tracking design, because CLog is wholely-owned // by CVolume, which synchronizes with a mutex. // //-------------------------------------------------------------------------- class CTestLog; class PTimerCallback; class PLogFileNotify { public: virtual void OnHandlesMustClose() = 0; }; // The CLogFile class declaration class CLogFile : public CSecureFile, protected PRobustlyCreateableFile, public PWorkItem { // Give full access to the unit test & dltadmin. friend class CTestLog; friend BOOL EmptyLogFile( LONG iVol ); // ------------ // Construction // ------------ public: CLogFile() { _pcTrkWksConfiguration = NULL; _cbLogSector = 0; _cbLogFile = 0; _cEntriesInFile = 0; _tcVolume = TEXT('?'); _ptszVolumeDeviceName = NULL; _heventOplock = INVALID_HANDLE_VALUE; _hRegisterWaitForSingleObjectEx = NULL; _fWriteProtected = TRUE; } ~CLogFile() { UnInitialize(); } // -------------- // Initialization // -------------- public: void Initialize( const TCHAR *ptszVolumeDeviceName, const CTrkWksConfiguration *pcTrkWksConfiguration, PLogFileNotify *pLogFileNotify, TCHAR tcVolume ); void UnInitialize(); // --------------- // Exposed Methods // --------------- public: enum AdjustLimitEnum { ADJUST_WITHIN_LIMIT = 1, ADJUST_WITHOUT_LIMIT = 2 }; void AdjustLogIndex( LogIndex *pilog, LONG iDelta, AdjustLimitEnum adjustLimitEnum = ADJUST_WITHOUT_LIMIT, LogIndex ilogLimit = 0 ); void ReadMoveNotification( LogIndex ilogEntry, LogMoveNotification *plmn ); void WriteMoveNotification( LogIndex ilogEntry, const LogMoveNotification &lmn, const LogEntryHeader &entryheader ); LogEntryHeader ReadEntryHeader( LogIndex ilogEntry ); void WriteEntryHeader( LogIndex ilogEntry, const LogEntryHeader &EntryHeader ); void ReadExtendedHeader( ULONG iOffset, void *pv, ULONG cb ); void WriteExtendedHeader( ULONG iOffset, const void *pv, ULONG cb ); void Expand( LogIndex ilogStart ); BOOL IsMaxSize() const; BOOL IsDirty() const; BOOL IsWriteProtected() const; ULONG NumEntriesInFile() const; BOOL IsShutdown() const; void SetShutdown( BOOL fShutdown ); void Flush( ); void Delete(); void ActivateLogFile(); void SetOplock(); void RegisterOplockWithThreadPool(); void UnregisterOplockFromThreadPool( HANDLE hCompletionEvent = (HANDLE)-1 ); void Close( ); // Doesn't raise void DoWork(); // PWorkItem override // ---------------- // Internal Methods // ---------------- protected: virtual void CreateAlwaysFile( const TCHAR * ptszFile ); virtual RCF_RESULT OpenExistingFile( const TCHAR * ptszFile ); private: void CloseLog(); // Doesn't raise void GetSize(); void InitializeLogEntries( LogIndex ilogFirst, LogIndex ilogLast ); ULONG CalcNumEntriesInFile( ); ULONG NumEntriesPerSector(); BOOL SetSize( DWORD cbLogFile ); BOOL Validate( ); // -------------- // Internal State // -------------- private: // Is the volume write-protected? BOOL _fWriteProtected:1; // The name of the file const TCHAR *_ptszVolumeDeviceName; // Configuration parameters for the log const CTrkWksConfiguration *_pcTrkWksConfiguration; // Events are raises to the following notify handler PLogFileNotify *_pLogFileNotify; // Size of sectors for the volume containing the log DWORD _cbLogSector; // Size of the log file. DWORD _cbLogFile; // The number of entries in the file DWORD _cEntriesInFile; // The volume on which this log is stored. TCHAR _tcVolume; // Classes to control the header and sectors CLogFileHeader _header; CLogFileSector _sector; // Handles used to oplock the log file HANDLE _heventOplock; HANDLE _hRegisterWaitForSingleObjectEx; IO_STATUS_BLOCK _iosbOplock; }; // class CLogFile // Open/create the log file inline void CLogFile::ActivateLogFile() { if( !IsOpen( ) ) { TCHAR tszLogFile[ MAX_PATH + 1 ]; _tcscpy( tszLogFile, _ptszVolumeDeviceName ); _tcscat( tszLogFile, s_tszLogFileName ); RobustlyCreateFile(tszLogFile, _tcVolume ); } } // Mark the file as having been successfully shut down. inline void CLogFile::SetShutdown( BOOL fShutdown ) { _header.SetShutdown( fShutdown ); if( fShutdown ) Flush(); } // Is the log file dirty? inline BOOL CLogFile::IsDirty() const { return( _sector.IsDirty() || _header.IsDirty() ); } // Flush to disk inline void CLogFile::Flush( ) { if( IsOpen() && IsDirty() ) { _sector.Flush(); _header.Flush(); FlushFileBuffers( _hFile ); } } // How many move notification entries can this file hold? inline ULONG CLogFile::NumEntriesInFile() const { return( _cEntriesInFile ); } // Determine the number of entries this file can hold, given the size // of the file. inline ULONG CLogFile::CalcNumEntriesInFile( ) { if( (_cbLogFile/_cbLogSector) <= _header.NumSectors() ) { TrkLog(( TRKDBG_ERROR, TEXT("Corrupt log on %c: _cbLogFile=%lu, _cbLogSector=%lu"), _tcVolume, _cbLogFile, _cbLogSector )); TrkRaiseException( TRK_E_CORRUPT_LOG ); } // The number of entries is the non-header file size times the // number of entries in a sector. _cEntriesInFile = ( (_cbLogFile / _cbLogSector) - _header.NumSectors() ) * _sector.NumEntries(); return( _cEntriesInFile ); } // How many move notification entries can we fit in a single // disk sector. inline ULONG CLogFile::NumEntriesPerSector() { return _sector.NumEntries(); } // Is this file already as big as we're allowed to make it? inline BOOL CLogFile::IsMaxSize() const { return _cbLogFile + _cbLogSector > _pcTrkWksConfiguration->GetMaxLogKB() * 1024; } inline BOOL CLogFile::IsWriteProtected() const { return _fWriteProtected; } // Is the file currently in the proper-shutdown state? inline BOOL CLogFile::IsShutdown() const { return( _header.IsShutdown() ); } // Called when it's time to close the log (we don't want to hold // the log open indefinitely, because it locks the volume). inline void CLogFile::Close() // doesn't raise { CloseLog(); } // Delete the underlying log file inline void CLogFile::Delete() { TCHAR tszLogFile[ MAX_PATH + 1 ]; CloseLog(); _tcscpy( tszLogFile, _ptszVolumeDeviceName ); _tcscat( tszLogFile, s_tszLogFileName ); RobustlyDeleteFile( tszLogFile ); } //+------------------------------------------------------------------------- // // Class: CLog // // Purpose: This class implements the tracking workstation log. // // Notes: CLog implements no thread-safety mechanism; rather // it relies on the caller. This is acceptable in the // link-tracking design, because CLog is wholely-owned // by CVolume, which synchronizes with a mutex. // //-------------------------------------------------------------------------- // Prototype for a callback class. CLog calls this class whenever // it has new information. class PLogCallback { public: virtual void OnEntriesAvailable() = 0; }; // The CLog declaration class CLog { // Give the unit test full control. friend class CTestLog; // // The log may contain entries for objects that have not actually // moved off the machine. This can occur when the log is written // with a move notification but the source machine // crashes before the source is deleted. If the source still exists // on the machine, the we need to delete the entry from the log // before it gets notified to the DC. I.e. the update to the // log should somehow be transacted with the move. // // ------------ // Construction // ------------ public: CLog() { _fDirty = FALSE; memset( &_loginfo, 0, sizeof(_loginfo) ); _pcLogFile = NULL; _pcTrkWksConfiguration = NULL; _pLogCallback = NULL; } ~CLog() { } // -------------- // Initialization // -------------- public: void Initialize( PLogCallback *pLogCallback, const CTrkWksConfiguration *pcTrkWksConfig, CLogFile *pcLogFile ); void Flush( ); // Flushes only to cache // --------------- // Exposed Methods // --------------- public: void Append( const CVolumeId &volidCurrent, const CObjId &objidCurrent, const CDomainRelativeObjId &droidNew, const CMachineId &mcidNew, const CDomainRelativeObjId &droidBirth); void Read( CObjId rgobjidCurrent[], CDomainRelativeObjId rgdroidBirth[], CDomainRelativeObjId rgdroidNew[], SequenceNumber *pseqFirst, IN OUT ULONG *pcRead); SequenceNumber GetNextSeqNumber( ) const; // Required not to raise BOOL Search(const CObjId &objidCurrent, CDomainRelativeObjId *pdroidNew, CMachineId *pmcidNew, CDomainRelativeObjId *pdroidBirth); BOOL Seek( const SequenceNumber &seq ); void Seek( int nOrigin, int iSeek ); // ---------------- // Internal Methods // ---------------- private: DWORD AgeOf( ULONG ilogEntry ); LogInfo QueryLogInfo(); void GenerateDefaultLogInfo( LogIndex ilogEnd ); void ExpandLog(); BOOL IsEmpty() const; BOOL IsFull() const; BOOL IsRead() const; // Has the whole log been read? BOOL IsRead( LogIndex ilog ); // Has this entry been read? void SetDirty( BOOL fDirty ); BOOL IsOldEnoughToOverwrite( ULONG iLogEntry ); void WriteEntryHeader(); BOOL Search(SequenceNumber seqSearch, ULONG *piLogEntry ); BOOL DoSearch( BOOL fSearchUsingSeq, SequenceNumber seqSearch, // Use this if fSearchUsingSeq const CObjId &objidCurrent, // Use this if !fSearchUsingSeq ULONG *piFound, CDomainRelativeObjId *pdroidNew, CMachineId *pmcidNew, CDomainRelativeObjId *pdroidBirth ); // ----- // State // ----- private: // Should we do anything in a flush? BOOL _fDirty:1; // The object which represents the log file. We never directly access // the underlying file. CLogFile *_pcLogFile; // Meta-data for the log, which is also written to the log header LogInfo _loginfo; // Who to call when we have data to be read. PLogCallback *_pLogCallback; // Configuration information (e.g., max log size) const CTrkWksConfiguration *_pcTrkWksConfiguration; }; // ------------ // CLog Inlines // ------------ // Can this entry be overwritten? inline BOOL CLog::IsOldEnoughToOverwrite( ULONG iLogEntry ) { return( AgeOf(iLogEntry) >= _pcTrkWksConfiguration->GetLogOverwriteAge() ); } // How old is this entry, in TrkTimeUnits? inline DWORD CLog::AgeOf( ULONG ilogEntry ) { CFILETIME cftNow; LogMoveNotification lmn; _pcLogFile->ReadMoveNotification( ilogEntry, &lmn ); return TrkTimeUnits(cftNow) - lmn.DateWritten; } // Is the current log too full to add another entry? inline BOOL CLog::IsFull() const { return( _loginfo.ilogWrite == _loginfo.ilogEnd ); } // Are there no entries in the log? inline BOOL CLog::IsEmpty() const { return( _loginfo.ilogWrite == _loginfo.ilogStart ); } // SetDirty must be called before making a change to _loginfo. It // marks the logfile as not properly shutdown, and if this is the first time it's // been marked as such, it will do a flush of the logfile header. inline void CLog::SetDirty( BOOL fDirty ) { _fDirty = fDirty; if( _fDirty ) _pcLogFile->SetShutdown( FALSE ); } // Has everything in the log been read? inline BOOL CLog::IsRead() const { return( _loginfo.ilogWrite == _loginfo.ilogRead ); } // Get the next sequence number which will be used inline SequenceNumber CLog::GetNextSeqNumber( ) const // Never raises { return( _loginfo.seqNext ); } // Write the header data to the current headers (both the data that goes // to the log header, and the data that goes to the header of the // last entry). inline void CLog::WriteEntryHeader() { LogEntryHeader entryheader; entryheader = _pcLogFile->ReadEntryHeader( _loginfo.ilogLast ); entryheader.ilogRead = _loginfo.ilogRead; entryheader.seq = _loginfo.seqNext; _pcLogFile->WriteEntryHeader( _loginfo.ilogLast, entryheader ); } //-------------------------------------------------------------------// // // // CObjIdIndexChangeNotifier // // // //-------------------------------------------------------------------// class CVolume; class PObjIdIndexChangedCallback { public: virtual void NotifyAddOrDelete( ULONG Action, const CDomainRelativeObjId & droidBirth ) = 0; }; enum EAggressiveness { AGGRESSIVE = 1, PASSIVE }; const ULONG MAX_FS_OBJID_NOTIFICATIONS = 4; class CObjIdIndexChangeNotifier : public PWorkItem { public: CObjIdIndexChangeNotifier() : _fInitialized(FALSE), _hCompletionEvent(NULL), _hRegisterWaitForSingleObjectEx(NULL), _hDir(INVALID_HANDLE_VALUE) { IFDBG( _tcscpy( _tszWorkItemSig, TEXT("CObjIdIndexChangeNotifier") )); } void Initialize( TCHAR *ptszVolumeDeviceName, PObjIdIndexChangedCallback * pObjIdIndexChangedCallback, CVolume * pVolumeForTunnelNotification ); void UnInitialize(); BOOL AsyncListen( ); void StopListeningAndClose(); inline BOOL IsOpen() const; // PWorkItem void DoWork(); private: void StartListening(); friend void OverlappedCompletionRoutine( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped ); /* void OverlappedCompletionRoutine( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered ); */ private: BOOL _fInitialized:1; public: // Handle to the object ID index directory. // Hack: this is public so that we can use it to register // for PNP notification. HANDLE _hDir; private: // Event we use in the async ReadDirectoryChanges call. HANDLE _hCompletionEvent; // Thread pool registration handle (for the above event). HANDLE _hRegisterWaitForSingleObjectEx; // Name of the volume (in mount manager format) TCHAR * _ptszVolumeDeviceName; // Critsec for this class. CCriticalSection _cs; // This buffer is passed to the ReadDirectoryChanges call // for the result of the read. BYTE _Buffer[ MAX_FS_OBJID_NOTIFICATIONS * (sizeof(FILE_NOTIFY_INFORMATION) + sizeof(WCHAR) * (1+MAX_PATH)) ]; // FILE_NOTIFY_INFORMATION // Dummy for the ReadDirectoryChanges call. DWORD _dwDummyBytesReturned; // Overlapped structure used in the ReadDirectoryChanges call. OVERLAPPED _Overlapped; // Who to call when we get a notification. PObjIdIndexChangedCallback * _pObjIdIndexChangedCallback; // The volume for which we're watching the index. CVolume * _pVolume; // TrkWks configuration information. const CTrkWksConfiguration * _pTrkWksConfiguration; }; inline BOOL CObjIdIndexChangeNotifier::IsOpen() const { return( INVALID_HANDLE_VALUE != _hDir ); } //------------------------------------------------------------------- // // Class: CVolume // // Purpose: This class represents a local volume, whether // or not that volume is used for link-tracking. // For trackable volumes, this class holds the // move notification log (CLog). In fact, this class // is persisted to the header of the log file. // //------------------------------------------------------------------- class CDeletionsManager; class CTestSync; class CVolumeManager; class CVolume : public PLogFileNotify { // ------------ // Construction // ------------ public: CVolume() : _VolQuotaReached( TEXT("VolQuotaReached") ) { _fInitialized = FALSE; _fDirty = FALSE; _lRef = 1; _lVolidUpdates = 0; _fVolumeLocked = _fVolumeDismounted = _fDeleteSelfPending = _fInSetVolIdOnVolume = FALSE; _fDeleteLogAndReInitializeVolume = _fCloseAndReopenVolumeHandles = FALSE; _fVolInfoInitialized = FALSE; #if DBG _cLocks = 0; _cVolumes ++; #endif _cHandleLocks = 0; _cCloseVolumeHandlesInProgress = 0; } ~CVolume() { TrkLog(( TRKDBG_LOG, TEXT("Destructing %c:"), VolChar(_iVol) )); UnInitialize(); TrkAssert( _lRef == 0 ); #if DBG _cVolumes --; #endif } // -------------- // Initialization // -------------- public: BOOL Initialize( TCHAR *ptszVolumeName, const CTrkWksConfiguration * pTrkWksConfiguration, CVolumeManager *pVolMgr, PLogCallback * pLogCallback, PObjIdIndexChangedCallback * pDeletionsManager, SERVICE_STATUS_HANDLE ssh #if DBG , CTestSync * pTunnelTest #endif ); void UnInitialize(); public: enum CVOL_STATE { VOL_STATE_OWNED, // volume is VOL_STATE_OWNED VOL_STATE_NOTOWNED, // volume is not VOL_STATE_OWNED VOL_STATE_NOTCREATED, // volume is not created VOL_STATE_UNKNOWN // volume is in VOL_STATE_UNKNOWN state }; // --------------- // Exposed Methods // --------------- public: // add and release reference ULONG AddRef(); ULONG Release(); // Operations on the Move Notification log void Append( const CDomainRelativeObjId &droidCurrent, const CDomainRelativeObjId &droidNew, const CMachineId &mcidNew, const CDomainRelativeObjId &droidBirth); BOOL Search( const CDomainRelativeObjId & droidCurrent, CDomainRelativeObjId * pdroidNew, CMachineId *pmcidNew, CDomainRelativeObjId * pdroidBirth ); enum EHandleChangeReason { VOLUME_LOCK_CHANGE, VOLUME_MOUNT_CHANGE, NO_VOLUME_CHANGE }; void CloseVolumeHandles( HDEVNOTIFY hdnVolume = NULL, EHandleChangeReason = NO_VOLUME_CHANGE ); // Doesn't raise BOOL ReopenVolumeHandles(); void CloseAndReopenVolumeHandles(); void OnVolumeLock( HDEVNOTIFY hdnVolume ); void OnVolumeUnlock( HDEVNOTIFY hdnVolume ); void OnVolumeLockFailed( HDEVNOTIFY hdnVolume ); void OnVolumeMount( HDEVNOTIFY hdnVolume ); void OnVolumeDismount( HDEVNOTIFY hdnVolume ); void OnVolumeDismountFailed( HDEVNOTIFY hdnVolume ); // Get this volume's id, secret, and the machine id stored in the log. const CVolumeId GetVolumeId( ); inline void SetVolIdInVolInfo( const CVolumeId &volid ); inline RPC_STATUS GenerateVolumeIdInVolInfo(); inline void GetVolumeSecret( CVolumeSecret *psecret ); inline void GetMachineId( CMachineId *pmcid ); inline BOOL EnumObjIds( CObjIdEnumerator *pobjidenum ); inline USHORT GetIndex() const; inline const TCHAR* GetVolumeDeviceName() const; // Load/unload from/to a SyncVolume request structure BOOL LoadSyncVolume(TRKSVR_SYNC_VOLUME * pSyncVolume, EAggressiveness eAggressiveness, BOOL* pfSyncNeeded= NULL ); BOOL LoadQueryVolume( TRKSVR_SYNC_VOLUME * pQueryVolume ); BOOL UnloadSyncVolume( TRKSVR_SYNC_VOLUME * pSyncVolume ); BOOL UnloadQueryVolume( const TRKSVR_SYNC_VOLUME * pQueryVolume ); void Read( CObjId *pobjidCurrent, CDomainRelativeObjId *pdroidBirth, CDomainRelativeObjId *pdroidNew, SequenceNumber *pseqFirst, ULONG *pcNotifications); BOOL Seek( SequenceNumber seq ); void Seek( int origin, int iSeek ); // inline void OnLogCloseTimer(); inline void OnObjIdIndexReopenTimer(); // Open a file on this volume by Object ID BOOL OpenFile( const CObjId &objid, ACCESS_MASK AccessMask, ULONG ShareAccess, HANDLE *ph); LONG GetVolIndex() { return( _iVol ); } HRESULT OnRestore(); BOOL NotOwnedExpired(); void SetState(CVOL_STATE volstateTarget); CVOL_STATE GetState(); void Refresh(); void FileActionIdNotTunnelled( FILE_OBJECTID_INFORMATION * poi ); void NotifyAddOrDelete( ULONG Action, const CObjId & objid ); inline void MarkForMakeAllOidsReborn(); inline void ClearMarkForMakeAllOidsReborn(); inline BOOL IsMarkedForMakeAllOidsReborn(); inline NTSTATUS SetVolIdOnVolume( const CVolumeId &volid ); inline NTSTATUS SetVolId( const TCHAR *ptszVolumeDeviceName, const CVolumeId &volid ); BOOL MakeAllOidsReborn(); void SetLocallyGeneratedVolId(); void Flush(BOOL fServiceShutdown = FALSE); // PLogFileNotify override void OnHandlesMustClose(); public: // Debugging state #if DBG static int _cVolumes; #endif // ---------------- // Internal Methods // ---------------- private: void MarkSelfForDelete(); void DeleteAndReinitializeLog(); void DeleteLogAndReInitializeVolume(); void PrepareToReopenVolumeHandles( HDEVNOTIFY hdnVolume, EHandleChangeReason eHandleChangeReason ); void VolumeSanityCheck( BOOL fVolIndexSetAlready = FALSE ); void LoadVolInfo(); void SaveVolInfo( ); ULONG Lock(); ULONG Unlock(); void AssertLocked(); void BreakIfRequested(); ULONG LockHandles(); ULONG UnlockHandles(); inline BOOL IsHandsOffVolumeMode() const; inline BOOL IsWriteProtectedVolume() const; inline void RaiseIfWriteProtectedVolume() const; void RegisterPnPVolumeNotification(); void UnregisterPnPVolumeNotification(); // ----- // State // ----- private: // Has this class been initialized? BOOL _fInitialized:1; // Are we in need of a flush? BOOL _fDirty:1; // Have we called LoadVolInfo yet? BOOL _fVolInfoInitialized:1; // Are we in hands-off mode because the volume has been FSCTL_LOCK_VOLUME-ed? BOOL _fVolumeLocked:1; // And/or are we in hands-off mode because the volume has been FSCTL_DISMOUNT_VOLUME-ed? BOOL _fVolumeDismounted:1; // If true, then a final UnLock calls this->Release(); BOOL _fDeleteSelfPending:1; // If true, we're in the middle of a SetVolIdOnVolume BOOL _fInSetVolIdOnVolume:1; // These flags are used to guarantee that we don't infinitely recurse. BOOL _fDeleteLogAndReInitializeVolume:1; BOOL _fCloseAndReopenVolumeHandles:1; HANDLE _hVolume; // volume handle for tunnelling // (relative FileReference opens) // Configuration information const CTrkWksConfiguration *_pTrkWksConfiguration; CVolumeManager *_pVolMgr; // Volume index (0=>a:, 1=>b:, ...), -1 => no drive letter LONG _iVol; // Unmatched calls to Lock() ULONG _cLocks; ULONG _cHandleLocks; // Mount manager volume name, without the trailing whack. E.g. // \\?\Volume{guid} TCHAR _tszVolumeDeviceName[ CCH_MAX_VOLUME_NAME + 1 ]; // Thread-safety CCriticalSection _csVolume; // Required for any CVolume operation CCriticalSection _csHandles; // Required to open/close all handles // Information about the volume which is persisted (in the log) VolumePersistentInfo _volinfo; // The log file and the log. CLogFile _cLogFile; CLog _cLog; // This is used to count the updates to the volid. // It is incremented before an update, and incremented // again after the update. It is used by GetVolumeId // to ensure we get a good volid without taking the lock. LONG _lVolidUpdates; // Reference count long _lRef; CObjIdIndexChangeNotifier _ObjIdIndexChangeNotifier; CVolumeSecret _tempSecret; #if DBG CTestSync * _pTunnelTest; #endif PLogCallback * _pLogCallback; HDEVNOTIFY _hdnVolumeLock; SERVICE_STATUS_HANDLE _ssh; CRegBoolParameter _VolQuotaReached; // See CloseVolumeHandles method for usage long _cCloseVolumeHandlesInProgress; }; // class CVolume // Set a volume ID in the _volinfo structure. inline void CVolume::SetVolIdInVolInfo( const CVolumeId &volid ) { // We have to update _lVolidUpdates before and // after the update, to be thread-safe and avoid // using a lock. Set GetVolumeId for a description. InterlockedIncrement( &_lVolidUpdates ); _volinfo.volid = volid; InterlockedIncrement( &_lVolidUpdates ); } // Generate a new volume ID in the _volinfo structure. inline RPC_STATUS CVolume::GenerateVolumeIdInVolInfo( ) { // We have to update _lVolidUpdates before and // after the update, to be thread-safe and avoid // using a lock. Set GetVolumeId for a description. InterlockedIncrement( &_lVolidUpdates ); // See GetVolumeId RPC_STATUS rpc_status = _volinfo.volid.UuidCreate(); InterlockedIncrement( &_lVolidUpdates ); // See GetVolumeId return rpc_status; } // Get the volume's zero-relative drive letter index. inline USHORT CVolume::GetIndex() const { return(_iVol); } // Handle a volume-lock event inline void CVolume::OnVolumeLock( HDEVNOTIFY hdnVolume ) { #if DBG if( NULL == hdnVolume || _hdnVolumeLock == hdnVolume ) TrkLog(( TRKDBG_VOLUME, TEXT("Volume %c: is being locked"), VolChar(_iVol) )); #endif CloseVolumeHandles( hdnVolume, VOLUME_LOCK_CHANGE ); } // Handle the external failure of a volume-lock event. inline void CVolume::OnVolumeLockFailed( HDEVNOTIFY hdnVolume ) { #if DBG if( NULL == hdnVolume || _hdnVolumeLock == hdnVolume ) TrkLog(( TRKDBG_VOLUME, TEXT("Volume %c: was not locked successfully"), VolChar(_iVol) )); #endif PrepareToReopenVolumeHandles( hdnVolume, VOLUME_LOCK_CHANGE ); } // Call SetVolId, with the _fInSetVolIdOnVolume set during // the call. inline NTSTATUS CVolume::SetVolIdOnVolume( const CVolumeId &volid ) { NTSTATUS status; TrkAssert( !_fInSetVolIdOnVolume ); _fInSetVolIdOnVolume = TRUE; status = ::SetVolId( _tszVolumeDeviceName, volid ); _fInSetVolIdOnVolume = FALSE; return status; } // This routine exists to ensure we don't accidentally call // the global SetVolId from within CVolume (use SetVolIdOnVolume // instead). inline NTSTATUS CVolume::SetVolId( const TCHAR *ptszVolumeDeviceName, const CVolumeId &volid ) { TrkAssert( !TEXT("SetVolId called from CVolume") ); return SetVolIdOnVolume( volid ); } // Is the volume currently locked or dismounted? inline BOOL CVolume::IsHandsOffVolumeMode() const { return( _fVolumeLocked || _fVolumeDismounted ); } // Is the volume currently read-only? inline BOOL CVolume::IsWriteProtectedVolume() const { return _cLogFile.IsWriteProtected(); } // Handle a volume-unlock event. inline void CVolume::OnVolumeUnlock( HDEVNOTIFY hdnVolume ) { #if DBG if( NULL == hdnVolume || _hdnVolumeLock == hdnVolume ) TrkLog(( TRKDBG_VOLUME, TEXT("Volume %c: unlocked"), VolChar(_iVol) )); #endif PrepareToReopenVolumeHandles( hdnVolume, VOLUME_LOCK_CHANGE ); } // Handle a volume dismount event. inline void CVolume::OnVolumeDismount( HDEVNOTIFY hdnVolume ) { #if DBG if( _hdnVolumeLock == hdnVolume ) TrkLog(( TRKDBG_LOG, TEXT("Volume %c: dismounted"), VolChar(_iVol) )); #endif CloseVolumeHandles( hdnVolume, VOLUME_MOUNT_CHANGE ); } // Handle the external failure of a volume-dismount event. inline void CVolume::OnVolumeDismountFailed( HDEVNOTIFY hdnVolume ) { #if DBG if( _hdnVolumeLock == hdnVolume ) TrkLog(( TRKDBG_LOG, TEXT("Volume %c: dismount failed"), VolChar(_iVol) )); #endif PrepareToReopenVolumeHandles( hdnVolume, VOLUME_MOUNT_CHANGE ); } // Handle a volume-mount event. inline void CVolume::OnVolumeMount( HDEVNOTIFY hdnVolume ) { #if DBG if( _hdnVolumeLock == hdnVolume ) TrkLog(( TRKDBG_LOG, TEXT("Volume %c: mounted"), VolChar(_iVol) )); #endif PrepareToReopenVolumeHandles( hdnVolume, VOLUME_MOUNT_CHANGE ); } // Get the volume device name (in mount manager format). inline const TCHAR* CVolume::GetVolumeDeviceName() const { return( _tszVolumeDeviceName ); } // Get this volume's secret. inline void CVolume::GetVolumeSecret( CVolumeSecret *psecret ) { Lock(); __try { *psecret = _volinfo.secret; } __finally { Unlock(); } } // Get the machine ID stored in the log of this volume (not necessarily // the current machine ID). inline void CVolume::GetMachineId( CMachineId *pmcid ) { Lock(); __try { *pmcid = _volinfo.machine; } __finally { Unlock(); } } // Enumerate the object IDs on this volume. inline BOOL CVolume::EnumObjIds( CObjIdEnumerator *pobjidenum ) { Lock(); __try { if( !pobjidenum->Initialize( _tszVolumeDeviceName )) { TrkLog((TRKDBG_ERROR, TEXT("CObjIdEnumerator::Initialize failed") )); TrkRaiseException( E_FAIL ); } } __finally { Unlock(); } return( TRUE ); } // Set a flag indicating that we should clear the birth IDs for all // files on this volume. The actual clearing activity is performed // asynchronously on another thread. This flag is written to the // _volinfo in the log and flushed to the disk, so that we continue // if the service is stopped before it is performed. inline void CVolume::MarkForMakeAllOidsReborn() { Lock(); __try { TrkLog(( TRKDBG_LOG, TEXT("Marking to make all OIDs reborn on %c:"), VolChar(_iVol) )); _fDirty = TRUE; _volinfo.fDoMakeAllOidsReborn = TRUE; Flush(); } __finally { Unlock(); } } // Has the above flag been set? inline BOOL CVolume::IsMarkedForMakeAllOidsReborn() { BOOL fDoMakeAllOidsReborn; Lock(); fDoMakeAllOidsReborn = _volinfo.fDoMakeAllOidsReborn; Unlock(); return fDoMakeAllOidsReborn; } // Raise an exception if this volume is write-protected. This method is // called before any attempt to modify the volume (or to modify state that // we eventually want to write to a volume). inline void CVolume::RaiseIfWriteProtectedVolume() const { if( IsWriteProtectedVolume() ) { TrkLog(( TRKDBG_VOLUME, TEXT("Volume is write-protected") )); TrkRaiseException( E_UNEXPECTED ); } } // Clear the above flag and flush. inline void CVolume::ClearMarkForMakeAllOidsReborn() { Lock(); __try { _fDirty = TRUE; _volinfo.fDoMakeAllOidsReborn = FALSE; Flush(); } _finally { Unlock(); } } // Take the primary CVolume critsec. inline ULONG CVolume::Lock() { _csVolume.Enter(); return( _cLocks++ ); } // Release the primary CVolume critsec. inline ULONG CVolume::Unlock() { TrkAssert( 0 < _cLocks ); ULONG cLocksNew = --_cLocks; // If we just dropped the lock count down to zero, and // we're marked to be deleted, then do a Release (to counter // the AddRef done in MarkSelfForDelete). Ordinarily this will // be the final release, and will cause the object to delete // itself. if( 0 == cLocksNew && _fDeleteSelfPending ) { TrkLog(( TRKDBG_LOG, TEXT("Releasing %c: for delete (%d)"), VolChar(_iVol), _lRef )); // Clear the delete flag. This is necessary because the following // Release isn't necessarily the last one. If another thread has // a ref and is about to do a lock, we don't want its unlock to // also do a release. At this point we're out of the volume manager's // linked list, so when that thread does its final release, the object // will be deleted successfully. _fDeleteSelfPending = FALSE; // Leave the critical section. We have to do this before the Release, // because the Release will probably delete the critsec. _csVolume.Leave(); // Do what is probably the final release. Release(); } else _csVolume.Leave(); return( cLocksNew ); } // Take the volume handle critsec inline ULONG CVolume::LockHandles() { _csHandles.Enter(); return( _cHandleLocks++ ); } // Release the volume handle critsec inline ULONG CVolume::UnlockHandles() { ULONG cHandleLocks = --_cHandleLocks; _csHandles.Leave(); return( cHandleLocks ); } // Verify that the volume critsec has been taken. inline void CVolume::AssertLocked() { TrkAssert( _cLocks > 0 ); } // This routine is called when an error occurs that should never // happen, and we want to break into the debugger if the user // has configured us to do so. This was added so that we could // catch such occurrences in stress. inline void CVolume::BreakIfRequested() { #if DBG if( _pTrkWksConfiguration->GetBreakOnErrors() ) DebugBreak(); #endif } //+---------------------------------------------------------------------------- // // CVolumeNode // // A node in the volume manager's linked list of CVolume objects. // //+---------------------------------------------------------------------------- class CVolumeNode { public: CVolumeNode() : _pVolume(NULL), _pNext(NULL) { } CVolume * _pVolume; CVolumeNode * _pNext; }; //----------------------------------------------------------------------------- // // CDeletionsManager // // This class manages the deletions of link source files, wrt their object // IDs. When a file has been deleted, it's a link source, and it's been moved // across volumes, we must notify the DC (trksvr) so that it can remove // that entry (in order to save space). We know a file has been moved across // volumes because doing so sets a bit in the birth ID. // // If a file is deleted, but then recreated and therefore the objid is // tunnelled, then we will initially believe that it needs to be deleted // from trksvr, when in fact it doesn't. Another problem is that we may // see a delete and send a delete-notify before the (move-notify that // moved the file here) is sent. // // To handle these two problems and for batching performance, this // deletions manager always holds on to delete-notifies for a minimum of 5 // minutes. This hueristic is intended to allow move-notifies to be received // and processed by trksvr. And if an objid is tunnelled, it can be // removed from the list of delete-notifies before it is sent to trksvr. // // The 5 minute delay is implemented by maintaining two linked-lists. // When we discover that a link source has been deleted, that birth ID // gets added to the "latest" list and a 5 minute timer is started. // When the timer goes off, that list (which may now have more delete- // notifies in it) is switched to the "oldest" list, and another 5 minute // timer is started. During that 5 minutes, any new delete-notifies // are added to the "latest" list. When that timer goes off, the // items in the "oldest" list are sent to trksvr, the list entries are freed, // and the "latest" items are moved to the "oldest" list. // //----------------------------------------------------------------------------- class CTrkWksSvc; class CDeletionsManager : public PTimerCallback, public PObjIdIndexChangedCallback { public: CDeletionsManager() : _fInitialized(FALSE) { } void Initialize( const CTrkWksConfiguration *pconfigWks); void UnInitialize(); void NotifyAddOrDelete( ULONG Action, const CDomainRelativeObjId & droid ); TimerContinuation Timer( ULONG ulTimerId ); private: enum DELTIMERID { DELTIMER_DELETE_NOTIFY = 1 }; PTimerCallback::TimerContinuation OnDeleteNotifyTimeout(); void FreeDeletions( DROID_LIST_ELEMENT *pStop = NULL ); private: // Has Initialize been called? BOOL _fInitialized:1; // Critsec to protect this class. CCriticalSection _csDeletions; // List of recent and old deletions. If either // is non-NULL, then the timer should be running. DROID_LIST_ELEMENT* _pLatestDeletions; DROID_LIST_ELEMENT* _pOldestDeletions; ULONG _cLatestDeletions; // When this timer fires, we send the old deletions to // trksvr, and move the "latest" entries to the "oldest" list. CNewTimer _timerDeletions; // TrkWks configuration information. const CTrkWksConfiguration *_pconfigWks; }; // class CDeletionsManager //------------------------------------------------------------------- // // CVolumeManager // // This class maintains all the CVolume's on the machine (in a // linked list). // //------------------------------------------------------------------- #define CVOLUMEMANAGER_SIG 'GMLV' // VLMG #define CVOLUMEMANAGER_SIGDEL 'gMlV' // VlMg class CVolume; class CVolumeNode; class CVolumeManager : public PTimerCallback, public PWorkItem { friend class CVolumeEnumerator; // ------------ // Construction // ------------ public: inline CVolumeManager(); inline ~CVolumeManager(); // -------------- // Initialization // -------------- public: void Initialize( CTrkWksSvc * pTrkWks, const CTrkWksConfiguration *pTrkWksConfiguration, PLogCallback * pLogCallback, SERVICE_STATUS_HANDLE ssh #if DBG , CTestSync * pTunnelTest #endif ); void InitializeDomainObjects(); void StartDomainTimers(); void UnInitialize(); void UnInitializeDomainObjects(); void OnVolumeToBeReopened(); // --------------- // Exposed Methods // --------------- public: void Append( const CDomainRelativeObjId &droidCurrent, const CDomainRelativeObjId &droidNew, const CMachineId &mcidNew, const CDomainRelativeObjId &droidBirth); ULONG GetVolumeIds( CVolumeId * pVolumeIds, ULONG cMax ); HRESULT Search( DWORD Restrictions, const CDomainRelativeObjId & droidBirthLast, const CDomainRelativeObjId & droidLast, CDomainRelativeObjId * pdroidBirthNext, CDomainRelativeObjId * pdroidNext, CMachineId * pmcidNext, TCHAR * ptszLocalPath ); void Seek( CVolumeId vol, SequenceNumber seq ); CVolume * FindVolume( const CVolumeId &vol ); CVolume * FindVolume( const TCHAR *ptszVolumeDeviceName ); void FlushAllVolumes( BOOL fServiceShutdown = TRUE ); CVolume * IsDuplicateVolId( CVolume *pvolCheck, const CVolumeId &volid ); HRESULT OnRestore(); enum EEnumType { ENUM_UNOPENED_VOLUMES = 1, ENUM_OPENED_VOLUMES = 2 }; CVolumeEnumerator Enum( EEnumType eEnumType = ENUM_OPENED_VOLUMES ); BOOL IsLocal( const CVolumeId &vol ); HRESULT DcCallback(ULONG cVolumes, TRKSVR_SYNC_VOLUME* rgVolumes); void CloseVolumeHandles( HDEVNOTIFY hdnVolume = NULL ); void SetReopenVolumeHandlesTimer(); BOOL ReopenVolumeHandles(); void ForceVolumeClaims(); void OnEntriesAvailable(); void RefreshVolumes( PLogCallback *pLogCallback, SERVICE_STATUS_HANDLE ssh #if DBG , CTestSync *pTunnelTest #endif ); void RemoveVolumeFromLinkedList( const CVolume *pvol ); inline void SetVolInitTimer(); inline void OnVolumeLock( HDEVNOTIFY hdnVolume ); inline void OnVolumeLockFailed( HDEVNOTIFY hdnVolume ); inline void OnVolumeUnlock( HDEVNOTIFY hdnVolume ); inline void OnVolumeMount( HDEVNOTIFY hdnVolume ); inline void OnVolumeDismount( HDEVNOTIFY hdnVolume ); inline void OnVolumeDismountFailed( HDEVNOTIFY hdnVolume ); // ---------------- // Internal Methods // ---------------- private: // SyncVolumes doesn't raise BOOL SyncVolumes( EAggressiveness eAggressiveness, CFILETIME cftLastDue = 0, ULONG ulPeriodInSeconds = 0 ); BOOL CheckSequenceNumbers(); void InitializeVolumeList( const CTrkWksConfiguration *pTrkWksConfiguration, PLogCallback *pLogCallback, SERVICE_STATUS_HANDLE ssh #if DBG , CTestSync *pTunnelTest #endif ); enum EVolumeDeviceEvent { ON_VOLUME_LOCK, ON_VOLUME_UNLOCK, ON_VOLUME_LOCK_FAILED, ON_VOLUME_MOUNT, ON_VOLUME_DISMOUNT, ON_VOLUME_DISMOUNT_FAILED }; void VolumeDeviceEvent( HDEVNOTIFY hdnVolume, EVolumeDeviceEvent eVolumeDeviceEvent ); VOID CheckVolumeList(); // void OnLogCloseTimer(); PTimerCallback::TimerContinuation OnVolumeTimer( DWORD dwTimerId ); TimerContinuation Timer( DWORD dwTimerId ); void DoWork(); // called when the lock volume event is signalled void CleanUpOids(); void AddNodeToLinkedList( CVolumeNode *pVolNode ); // ----- // State // ----- private: ULONG _sig; enum VOLTIMERID { VOLTIMER_FREQUENT = 1, VOLTIMER_INFREQUENT = 2, VOLTIMER_INIT = 3, // VOLTIMER_LOG_CLOSE = 4, VOLTIMER_OBJID_INDEX_REOPEN = 5, VOLTIMER_REFRESH = 6, VOLTIMER_NOTIFY = 7, }; // Initialize has been called? BOOL _fInitialized:1; // Are we hesitating before sending refresh notifications? BOOL _fRefreshHesitation:1; // Are we in the delay before executing a frequent or infrequent task? BOOL _fFrequentTaskHesitation:1; BOOL _fInfrequentTaskHesitation:1; // The following is set once ReopenVolumeHandles has been called // at least once (they are not opened in the Initialize method; they // must be opened on an IO thread). Until this flag is set, the Enum // method will not return any volumes. BOOL _fVolumesHaveBeenOpenedOnce:1; // When this timer goes off, we check for things like whether // we can get out of the not-owned state. CNewTimer _timerFrequentTasks; // When this timer goes off, we check to see if we're in sync // with the trksvr. CNewTimer _timerInfrequentTasks; // When this timer goes off, the volumes need to be // (re) initialized for some reason (e.g. service start). CNewTimer _timerVolumeInit; // When this timer goes off, we should try to reopen the // volume handles. CNewTimer _timerObjIdIndexReopen; // When this timer fires, we refresh our entries in trksvr CNewTimer _timerRefresh; // When this timer fires, we send move notifications to trksvr // from the volume logs. CNewTimer _timerNotify; // Use this counter to ensure we only have one sync/refresh // volume operation in progress at a time. LONG _cSyncVolumesInProgress; LONG _cRefreshVolumesInProgress; // This is used with Begin/EndSingleInstanceTask in // ReopenVolumeHandles so that we don't do that routine // simultaneously in multiple threads. LONG _cReopenVolumeHandles; // Set this event if there is a volume that needs to be // reopened. HANDLE _heventVolumeToBeReopened; HANDLE _hRegisterWaitForSingleObjectEx; // This object watches the oid index for interesting deletes. CDeletionsManager _deletions; // Trkwks service configuration values. const CTrkWksConfiguration *_pTrkWksConfiguration; CTrkWksSvc *_pTrkWks; // Linked list of CVolume's. CVolumeNode * _pVolumeNodeListHead; // linked list of CVolumes CCriticalSection _csVolumeNodeList; // and a critsec to protect it. // The list of volumes that are being synced with trksvr. CVolume* _rgVolumesToUpdate[ NUM_VOLUMES ]; HDEVNOTIFY _hdnVolumeLock; }; inline CVolumeManager::CVolumeManager() { _sig = CVOLUMEMANAGER_SIG; _fInitialized = FALSE; _pVolumeNodeListHead = NULL; _fFrequentTaskHesitation = FALSE; _fVolumesHaveBeenOpenedOnce = FALSE; _fInfrequentTaskHesitation = FALSE; _fRefreshHesitation = FALSE; _cReopenVolumeHandles = 0; _cSyncVolumesInProgress = _cRefreshVolumesInProgress = 0; _heventVolumeToBeReopened = NULL; IFDBG( _tcscpy( _tszWorkItemSig, TEXT("CVolumeManager") )); } inline CVolumeManager::~CVolumeManager() { UnInitialize(); _sig = CVOLUMEMANAGER_SIGDEL; } // When a volume needs to be reopend, set an event // so that the reopen can happen on a worker thread. inline void CVolumeManager::OnVolumeToBeReopened() { TrkVerify( SetEvent( _heventVolumeToBeReopened )); } inline void CVolumeManager::OnVolumeLock( HDEVNOTIFY hdnVolume ) { //CloseAllVolumeHandles( hdnVolume ); VolumeDeviceEvent( hdnVolume, ON_VOLUME_LOCK ); } inline void CVolumeManager::OnVolumeUnlock( HDEVNOTIFY hdnVolume ) { //FindAndSetReopenVolume( hdnVolume ); VolumeDeviceEvent( hdnVolume, ON_VOLUME_UNLOCK ); } inline void CVolumeManager::OnVolumeLockFailed( HDEVNOTIFY hdnVolume ) { //FindAndSetReopenVolume( hdnVolume ); VolumeDeviceEvent( hdnVolume, ON_VOLUME_LOCK_FAILED ); } inline void CVolumeManager::OnVolumeMount( HDEVNOTIFY hdnVolume ) { VolumeDeviceEvent( hdnVolume, ON_VOLUME_MOUNT ); } inline void CVolumeManager::OnVolumeDismount( HDEVNOTIFY hdnVolume ) { VolumeDeviceEvent( hdnVolume, ON_VOLUME_DISMOUNT ); } inline void CVolumeManager::OnVolumeDismountFailed( HDEVNOTIFY hdnVolume ) { VolumeDeviceEvent( hdnVolume, ON_VOLUME_DISMOUNT_FAILED ); } // This timer is set by CTrkWksSvc when it tries to do a MoveNotify // and gets TRK_E_SERVER_TOO_BUSY. inline void CVolumeManager::SetVolInitTimer() { if( !_pTrkWksConfiguration->_fIsWorkgroup ) { _timerVolumeInit.SetSingleShot(); TrkLog(( TRKDBG_LOG, TEXT("VolInit timer: %s"), (const TCHAR*)CDebugString( _timerVolumeInit ) )); } } //------------------------------------------------------------------- // // CVolumeEnumerator // // This class performs an enumeration of the volumes on this machine. // //------------------------------------------------------------------- class CVolumeEnumerator { // ------------ // Construction // ------------ public: CVolumeEnumerator(CVolumeNode** pprgVol = NULL, CCriticalSection *pcs = NULL ) : _ppVolumeNodeListHead(pprgVol), _pcs(pcs), _pVolNodeLast(NULL) {} // --------------- // Exposed Methods // --------------- public: CVolume * GetNextVolume(); void UnInitialize() { _ppVolumeNodeListHead = NULL; _pcs = NULL; } // ----- // State // ----- private: // Head of this linked list of volumes. CVolumeNode **_ppVolumeNodeListHead; // The critsec that protects the above list. CCriticalSection *_pcs; // Current seek position in the list. CVolumeNode *_pVolNodeLast; }; // class CVolumeEnumerator //------------------------------------------------------------------- // // CAllVolumesObjIdEnumerator // // This class performs an enumeration of all object IDs on this // machine. // //------------------------------------------------------------------- class CAllVolumesObjIdEnumerator { public: inline CAllVolumesObjIdEnumerator() { _pvol = NULL; } inline ~CAllVolumesObjIdEnumerator() { UnInitialize(); } inline void UnInitialize(); inline BOOL FindFirst( CVolumeManager *pVolMgr, CObjId * pobjid, CDomainRelativeObjId * pdroidBirth ); inline BOOL FindNext( CObjId * pobjid, CDomainRelativeObjId * pdroidBirth ); private: inline BOOL FindFirstOnVolume( CObjId *pobjid, CDomainRelativeObjId *pdroidBirth ); private: // A volume enumerator CVolumeEnumerator _volenum; // The object ID enumerator for the current volume. CObjIdEnumerator _objidenum; // The current volume. CVolume *_pvol; }; // class CAllVolumesObjIdEnumerator inline void CAllVolumesObjIdEnumerator::UnInitialize() { if( NULL != _pvol ) { _pvol->Release(); _pvol = NULL; } _objidenum.UnInitialize(); _volenum.UnInitialize(); } // Get the next entry in the enumeration. inline BOOL CAllVolumesObjIdEnumerator::FindNext( CObjId * pobjid, CDomainRelativeObjId * pdroid ) { // See if there's another object ID on this volume. if( _objidenum.FindNext( pobjid, pdroid )) { // Yes, there is. if( pdroid->GetVolumeId().GetUserBitState() ) { TrkLog(( TRKDBG_GARBAGE_COLLECT, TEXT("Refreshing file %s, %s"), (const TCHAR*)CDebugString(*pobjid), (const TCHAR*)CDebugString(*pdroid) )); } return( TRUE ); } // There are no more object IDs on this volume. Move on // to the next volume and return the first ID on that volue. _objidenum.UnInitialize(); _pvol->Release(); _pvol = _volenum.GetNextVolume(); return( FindFirstOnVolume( pobjid, pdroid )); } // Restart the enumeration. inline BOOL CAllVolumesObjIdEnumerator::FindFirst( CVolumeManager *pVolMgr, CObjId * pobjid, CDomainRelativeObjId * pdroid ) { _volenum = pVolMgr->Enum(); _pvol = _volenum.GetNextVolume(); return( FindFirstOnVolume( pobjid, pdroid )); } // Restart the object ID enueration on the current volume _pvol. inline BOOL CAllVolumesObjIdEnumerator::FindFirstOnVolume( CObjId *pobjid, CDomainRelativeObjId *pdroid ) { if( NULL == _pvol || !_pvol->EnumObjIds( &_objidenum ) ) return( FALSE ); TrkLog(( TRKDBG_GARBAGE_COLLECT, TEXT("Refreshing volume %c: (%s)"), VolChar(_pvol->GetIndex()), _pvol->GetVolumeDeviceName() )); // Find the first object ID. if( _objidenum.FindFirst( pobjid, pdroid )) { #if DBG { if( pdroid->GetVolumeId().GetUserBitState() ) TrkLog(( TRKDBG_GARBAGE_COLLECT, TEXT("Refreshing file %s, %s"), (const TCHAR*)CDebugString(*pobjid), (const TCHAR*)CDebugString(*pdroid) )); } #endif return( TRUE ); } else { // There weren't any object IDs on this volume. Move // to the next volume. _objidenum.UnInitialize(); _pvol->Release(); _pvol = _volenum.GetNextVolume(); return( FindFirstOnVolume( pobjid, pdroid )); } } //-------------------------------------------------------------------// // // // CPersistentVolumeMap // // Not currently implemented. // //-------------------------------------------------------------------// #ifdef VOL_REPL #define PVM_VERSION 1 class CPersistentVolumeMap : private CVolumeMap, private CSecureFile, protected PRobustlyCreateableFile { public: inline CPersistentVolumeMap(); inline ~CPersistentVolumeMap(); void Initialize(); void UnInitialize(); void CopyTo(DWORD * pcVolumes, VolumeMapEntry ** ppVolumeChanges); BOOL FindVolume( const CVolumeId & volume, CMachineId * pmcid ); CFILETIME GetLastUpdateTime( ); void SetLastUpdateTime( const CFILETIME & cftFirstChange ); void Merge( CVolumeMap * pOther ); // destroys other protected: virtual RCF_RESULT OpenExistingFile( const TCHAR * ptszFile ); virtual void CreateAlwaysFile( const TCHAR * ptszTempFile ); private: void Load(); void Save(); private: CFILETIME _cft; CCritcalSection _cs; BOOL _fInitializeCalled; BOOL _fMergeDirtiedMap; }; inline CPersistentVolumeMap::CPersistentVolumeMap() : _fInitializeCalled(FALSE), _fMergeDirtiedMap(FALSE) { } inline CPersistentVolumeMap::~CPersistentVolumeMap() { TrkAssert( !IsOpen() ); TrkAssert( !_fInitializeCalled ); } #endif //------------------------------------------------------------------- // // CPort // // This class represents the LPC port to which IO sends // move notifications (in the context of MoveFile). // //------------------------------------------------------------------- #define MOVE_BATCH_DUE_TIME 15 // 15 seconds #define MAX_MOVE_BATCH_DUE_TIME (6 * 60 * 60) // 6 hours class CWorkManager; class CPort : private PWorkItem { public: CPort() : _fInitializeCalled(FALSE) { IFDBG( _tcscpy( _tszWorkItemSig, TEXT("CPort") )); } ~CPort() { UnInitialize(); } void Initialize( CTrkWksSvc *pTrkWks, DWORD dwThreadKeepAliveTime ); void UnInitialize(); void EnableKernelNotifications(); void DisableKernelNotifications(); private: enum ENUM_ACCEPT_PORT { ACCEPT, REJECT }; NTSTATUS OnConnectionRequest( TRKWKS_PORT_CONNECT_REQUEST *pPortConnectRequest, BOOL *pfStopPortThread ); BOOL RegisterWorkItemWithThreadPool(); // PWorkItem void DoWork(); friend DWORD WINAPI PortThreadStartRoutine(LPVOID lpThreadParameter); private: // Has Initialize been called? BOOL _fInitializeCalled:1; // Is the service stopping? BOOL _fTerminating:1; // Thread pool registration of _hListenPort HANDLE _hRegisterWaitForSingleObjectEx; // The listen handle (NtCreateWaitablePort) HANDLE _hListenPort; // The port handle (NtAcceptConnectPort) HANDLE _hLpcPort; // Syncrhonization with IO HANDLE _hEvent; // How long to keep a thread sitting on the // NtReplyWaitReceivePortEx. LARGE_INTEGER _ThreadKeepAliveTime; CTrkWksSvc * _pTrkWks; }; // class CPort //------------------------------------------------------------------- // // CVolumeLocationCache // // This class maintains a cache of volid->mcid mappings that // have been discovered by this service during this instance // (i.e. it's not persisted). It's a circular cache, with // timestamps on the entries in order to determine trust. // //------------------------------------------------------------------- typedef struct { CVolumeId volid; CMachineId mcid; CFILETIME cft; // The time this entry was last updated #if defined(_AXP64_) // // There is currently a bug in at least the axp64 compiler that requires // this structure to be 8-byte aligned. // // The symptom of the failure is an alignment fault at // ??0VolumeLocation@@QEA@XZ + 0x14. // PVOID Alignment; #endif } VolumeLocation; #define MAX_CACHED_VOLUME_LOCATIONS 32 class CVolumeLocationCache { public: CVolumeLocationCache() : _fInitialized(FALSE) {} void Initialize( DWORD dwEntryLifetimeSeconds ); void UnInitialize(); BOOL FindVolume(const CVolumeId & volid, CMachineId * pmcid, BOOL *pfRecentEntry ); void AddVolume(const CVolumeId & volid, const CMachineId & mcid); private: int _FindVolume(const CVolumeId & volid); private: // Has Initialize been called? BOOL _fInitialized; // Critsec to protect this class. CCriticalSection _cs; // The array of volume entries int _cVols; VolumeLocation _vl[ MAX_CACHED_VOLUME_LOCATIONS ]; // The age after which an entry is considered old and // not trustworthy. CFILETIME _cftEntryLifetime; }; //------------------------------------------------------------------- // // CEntropyRecorder // // This class maintains an array of "entropy"; randomly generated // data. The source of the data is a munging of data from the // performance counter. The source of the entropy (the times at // which the performance counter is queried) as the assumed randomness // in disk access times. // //------------------------------------------------------------------- #define MAX_ENTROPY 256 class CEntropyRecorder { public: CEntropyRecorder(); void Initialize(); void UnInitialize(); void Put(); BOOL InitializeSecret( CVolumeSecret * pSecret ); void ReturnUnusedSecret( CVolumeSecret * pSecret ); private: BOOL GetEntropy( void * pv, ULONG cb ); void PutEntropy( BYTE b ); private: // Has Initialize been called? BOOL _fInitialized; // Critsec to protect the array. CCriticalSection _cs; // Array of bytes, seek pointer, and max available // bytes. DWORD _iNext; DWORD _cbTotalEntropy; BYTE _abEntropy[MAX_ENTROPY]; }; inline CEntropyRecorder::CEntropyRecorder() { _fInitialized = FALSE; } //+---------------------------------------------------------------------------- // // CTrkWksRpcServer // // This class implements the RPC server code for the trkwks service. // //+---------------------------------------------------------------------------- class CTrkWksRpcServer : public CRpcServer { public: void Initialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData, CTrkWksConfiguration *pTrkWksConfig ); void UnInitialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData ); }; //-------------------------------------------------------------------// // // // CTestSync - class to allow test synchronization // // // //-------------------------------------------------------------------// #if DBG class CTestSync { public: CTestSync() : _hSemReached(NULL), _hSemWait(NULL), _hSemFlag(NULL) {} ~CTestSync() { UnInitialize(); } void Initialize(const TCHAR *ptszBaseName); void UnInitialize(); void ReleaseAndWait(); private: HANDLE _hSemFlag; HANDLE _hSemReached; HANDLE _hSemWait; }; #endif // #if DBG //-------------------------------------------------------------------// // // CMountManager // // Not currently implemented. // //-------------------------------------------------------------------// #if 0 class CMountManager : public PWorkItem { public: CMountManager() { _pTrkWksSvc = NULL; _pVolMgr = NULL; _hCompletionEvent = NULL; _hMountManager = NULL; _hRegisterWaitForSingleObjectEx = NULL; IFDBG( _tcscpy( _tszWorkItemSig, TEXT("CMountManager") )); } void Initialize(CTrkWksSvc * pTrkWksSvc, CVolumeManager *pVolMgr ); void UnInitialize(); // PWorkItem overloads virtual void DoWork(); // handle the signalled timer handle private: void AsyncListen(); private: BOOL _fInitialized:1; CTrkWksSvc * _pTrkWksSvc; CVolumeManager * _pVolMgr; MOUNTMGR_CHANGE_NOTIFY_INFO _info; HANDLE _hCompletionEvent; HANDLE _hMountManager; HANDLE _hRegisterWaitForSingleObjectEx; }; #endif // #if 0 //------------------------------------------------------------------- // // CAvailableDc // // This class maintains a client-side binding handle to a DC // in the domain. The CallAvailableDc method can be used // to send a tracking message to that (or another if necessary) // DC. If a DC becomes unavailable, it automatically attempts // to fail over to one that is available. // //------------------------------------------------------------------- class CAvailableDc : public CTrkRpcConfig { private: CRpcClientBinding _rcDomain; CMachineId _mcidDomain; public: CAvailableDc() {} ~CAvailableDc() { UnInitialize(); } void UnInitialize(); HRESULT CallAvailableDc(TRKSVR_MESSAGE_UNION *pMsg, RC_AUTHENTICATE auth = INTEGRITY_AUTHENTICATION ); }; //-------------------------------------------------------------------// // // // GLOBALS // // //-------------------------------------------------------------------// EXTERN CTrkWksSvc * g_ptrkwks INIT(NULL); // Used by RPC stubs EXTERN LONG g_ctrkwks INIT(0); // Used to protect against multiple service instances #define CTRKWKSSVC_SIG 'KWRT' // TRWK #define CTRKWKSSVC_SIGDEL 'kWrT' // TrWk #if DBG EXTERN LONG g_cTrkWksRpcThreads INIT(0); #endif //-------------------------------------------------------------------// // // CDomainNameChangeNotify // // This class watches for notifications that the machine has // been moved into a new domain. Such an event is actually // dealt with in CTrkWksSvc. // //-------------------------------------------------------------------// class CDomainNameChangeNotify : public PWorkItem { public: CDomainNameChangeNotify() : _fInitialized(FALSE), _hDomainNameChangeNotification(INVALID_HANDLE_VALUE), _hRegisterWaitForSingleObjectEx(NULL) { IFDBG( _tcscpy( _tszWorkItemSig, TEXT("CDomainNameChangeNotify") )); } ~CDomainNameChangeNotify() { UnInitialize(); } void Initialize(); void UnInitialize(); // Work manager callback virtual void DoWork(); private: BOOL _fInitialized; // Handle from NetRegisterDomainNameChangeNotification HANDLE _hDomainNameChangeNotification; // Thread pool handle for registration of above handle HANDLE _hRegisterWaitForSingleObjectEx; }; // class CDomainNameChangeNotify //+---------------------------------------------------------------------------- // // CTrkWksSvc // // This is the primary class in the Tracking Service (trkwks). // It contains a CVolumeManager object, which maintains a list of // CVolume objects, one for each NTFS5 volume in the system. // // CTrkWksSvc also handles SCM requests, and maintains some maintenance // timers (the move notification timer, which indicates that a set of move // notifications should be sent to the DC, and the refresh timer, which // indicates that the DC's tables should be updated to show which objects // on this machine are still alive. // // This is also the class that receives two RPC requests. One is a mend request // that comes from clients (in NT5 the shell is the only client). The client // provides the IDs and we search for the new location of the link source. // The second request is a search request that comes from the trkwks service // on another machine (this call is made as part of its processing of a // Mend request). // //+---------------------------------------------------------------------------- class CTrkWksSvc : public IServiceHandler, public PLogCallback, public CTrkRpcConfig, public PWorkItem { public: // Initialization inline CTrkWksSvc(); inline ~CTrkWksSvc(); void Initialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData ); void UnInitialize(HRESULT hrStatusForServiceController); public: // RPC interface HRESULT CallSvrMessage( handle_t IDL_handle, TRKSVR_MESSAGE_UNION * pMsg ); HRESULT GetBackup( DWORD * pcVolumes, VolumeMapEntry ** ppVolumeChanges, FILETIME * pft); HRESULT GetFileTrackingInformation( const CDomainRelativeObjId & droidCurrent, TrkInfoScope scope, TRK_FILE_TRACKING_INFORMATION_PIPE pipeFileInfo ); HRESULT GetVolumeTrackingInformation( const CVolumeId & volid, TrkInfoScope scope, TRK_VOLUME_TRACKING_INFORMATION_PIPE pipeVolInfo ); HRESULT MendLink( RPC_BINDING_HANDLE IDL_handle, DWORD dwTickCountDeadline, DWORD Restrictions, const CDomainRelativeObjId &droidBirthLast, const CDomainRelativeObjId &droidLast, const CMachineId &mcidLast, CDomainRelativeObjId * pdroidBirthNew, CDomainRelativeObjId * pdroidNew, CMachineId * pmcidNew, ULONG * pcbPath, WCHAR * wsz ); inline HRESULT OnRestore( ); HRESULT SearchMachine( RPC_BINDING_HANDLE IDL_handle, DWORD Restrictions, const CDomainRelativeObjId &droidBirthLast, const CDomainRelativeObjId &droidLast, CDomainRelativeObjId * pdroidBirthNext, CDomainRelativeObjId * pdroidNext, CMachineId * pmcidNext, TCHAR* ptszPath ); HRESULT SetVolumeId( ULONG iVolume, const CVolumeId VolId ); HRESULT TriggerVolumeClaims( ULONG cVolumes, const CVolumeId *rgvolid ); public: // PWorkItem override virtual void DoWork(); inline ULONG GetSignature() const; inline const CTrkWksConfiguration & GetConfig(); CMachineId GetDcName( BOOL fForce = FALSE ); // called by NT port for each message in NT port // puts the entry in the move log and starts the DC notification timer NTSTATUS OnPortNotification(const TRKWKS_REQUEST *pRequest); // called by service controller from CSvcCtrlInterface DWORD ServiceHandler(DWORD dwControl, DWORD dwEventType, PVOID EventData, PVOID pData); inline void RaiseIfStopped() const; // PLogCallback overload void OnEntriesAvailable(); // send all those log entries that haven't been acknowledged persisted by DC PTimerCallback::TimerContinuation OnMoveBatchTimeout( EAggressiveness eAggressiveness = PASSIVE ); // send ids of volumes and cross-volume-moved sources for refreshing DC data PTimerCallback::TimerContinuation OnRefreshTimeout( CFILETIME cftOriginalDueTime, ULONG ulPeriodInSeconds ); // add volume changes from DC to the persistent volume list void CallDcSyncVolumes(ULONG cVolumes, TRKSVR_SYNC_VOLUME rgSyncVolumes[] ); // called by the CVolumeManager in a timer CEntropyRecorder _entropy; friend HRESULT StubLnkSvrMessageCallback(TRKSVR_MESSAGE_UNION* pMsg); inline void SetReopenVolumeHandlesTimer(); void OnDomainNameChange(); private: enum SEARCH_FLAGS { SEARCH_FLAGS_DEFAULT = 0, USE_SPECIFIED_MCID = 1, DONT_USE_DC = 2 }; void InitializeProcessPrivileges() const; void StartDomainTimers() {}; // Doesn't raise void UnInitializeDomainObjects(); void CheckForDomainOrWorkgroup(); // Sets _configWks._fIsWorkgroup HRESULT ConnectAndSearchDomain( IN const CDomainRelativeObjId &droidBirthLast, IN OUT DWORD *pRestrictions, IN OUT CDomainRelativeObjId *pdroidLast, OUT CMachineId *pmcidLast ); HRESULT OldConnectAndSearchDomain( IN const CDomainRelativeObjId &droidBirthLast, IN OUT CDomainRelativeObjId *pdroidLast, OUT CMachineId *pmcidLast ); HRESULT ConnectAndSearchMachine( RPC_BINDING_HANDLE IDL_handle, const CMachineId & mcid, IN OUT DWORD *pRestrictions, IN OUT CDomainRelativeObjId *pdroidBirthLast, IN OUT CDomainRelativeObjId *pdroidLast, OUT CMachineId *pmcidLast, TCHAR *tsz); HRESULT FindAndSearchVolume(RPC_BINDING_HANDLE IDL_handle, IN OUT DWORD *pRestrictions, IN SEARCH_FLAGS SearchFlags, IN OUT CDomainRelativeObjId *pdroidBirthLast, IN OUT CDomainRelativeObjId *pdroidLast, IN OUT CMachineId *pmcidLast, TCHAR *ptsz); HRESULT SearchChain(RPC_BINDING_HANDLE IDL_handle, int cMaxReferrals, DWORD dwTickCountDeadline, IN OUT DWORD *pRestrictions, IN SEARCH_FLAGS SearchFlags, IN OUT SAllIDs *pallidsLast, OUT TCHAR *ptsz); private: ULONG _sig; // Has Initialize been called? BOOL _fInitializeCalled:1; // Keep track of the number of threads doing a move notification // to trksvr (so that all but the first can NOOP). LONG _cOnMoveBatchTimeout; #if !TRK_OWN_PROCESS SVCHOST_GLOBAL_DATA * _pSvcsGlobalData; #endif // LPC port from which IO move notifications are read. CPort _port; // CMountManager _mountmanager; // Code to perform the work to make us an RPC server CTrkWksRpcServer _rpc; // Interactions with the SCM CSvcCtrlInterface _svcctrl; // TrkWks configuration parameters CTrkWksConfiguration _configWks; // Test syncs to check race conditions #if DBG CTestSync _testsyncMoveBatch; CTestSync _testsyncTunnel; #endif CVolumeManager _volumes; //HDEVNOTIFY _hdnDeviceInterface; #ifdef VOL_REPL CPersistentVolumeMap _persistentVolumeMap; #endif // Cache of volid->mcid mappings CVolumeLocationCache _volumeLocCache; // If true, don't bother trying to send move notifications // for a while (days). CRegBoolParameter _MoveQuotaReached; // Monitor for domain name changes (entering a new domain). CDomainNameChangeNotify _dnchnotify; CCriticalSection _csDomainNameChangeNotify; //COperationLog _OperationLog; // Get an available DC for this domain for use in contacting // trksvr. CAvailableDc _adc; // The latest found DC for this domain. CMachineId _mcidDC; CCriticalSection _csmcidDC; }; // class CTrkWksSvc // ---------------------- // CTrkWksSvc::CTrkWksSvc // ---------------------- inline CTrkWksSvc::CTrkWksSvc() : _fInitializeCalled(FALSE), _sig(CTRKWKSSVC_SIG), _cOnMoveBatchTimeout(0), _MoveQuotaReached( TEXT("MoveQuotaReached") ) { //_hdnDeviceInterface = NULL; } // ----------------------- // CTrkWksSvc::~CTrkWksSvc // ----------------------- inline CTrkWksSvc::~CTrkWksSvc() { TrkAssert( 0 == _cOnMoveBatchTimeout ); _csmcidDC.UnInitialize(); _sig = CTRKWKSSVC_SIGDEL; TrkAssert(!_fInitializeCalled); } inline void CTrkWksSvc::SetReopenVolumeHandlesTimer() { _volumes.SetReopenVolumeHandlesTimer(); } inline void CTrkWksSvc::RaiseIfStopped( ) const { if( _svcctrl.IsStopping() ) TrkRaiseException( TRK_E_SERVICE_STOPPING ); } inline const CTrkWksConfiguration & CTrkWksSvc::GetConfig() { return(_configWks); } inline ULONG CTrkWksSvc::GetSignature() const { return( _sig ); } inline HRESULT CTrkWksSvc::OnRestore( ) { return(_volumes.OnRestore()); } /* class CTestLog : public PTimerCallback { public: CTestLog( CTrkWksConfiguration *pTrkWksConfiguration, CWorkManager *pWorkManager ); public: void ReInitialize(); void UnInitialize(); void CreateLog( PLogCallback *pLogCallback, BOOL fValidate = TRUE ); void OpenLog( PLogCallback *pLogCallback, BOOL fValidate = TRUE ); void CloseLog(); void Append( ULONG cMoves, const CObjId rgobjidCurrent[], const CDomainRelativeObjId rgdroidNew[], const CDomainRelativeObjId rgdroidBirth[] ); void DelayUntilClose(); ULONG Read( ULONG cRead, CObjId rgobjidCurrent[], CDomainRelativeObjId rgobjidNew[], CDomainRelativeObjId rgobjidBirth[], SequenceNumber *pseqFirst ); void ReadAndValidate( ULONG cToRead, ULONG cExpected, const TRKSVR_MOVE_NOTIFICATION rgNotificationsExpected[], TRKSVR_MOVE_NOTIFICATION rgNotificationsRead[], SequenceNumber seqExpected ); void ReadExtendedHeader( ULONG iOffset, void *pv, ULONG cb ); void WriteExtendedHeader( ULONG iOffset, const void *pv, ULONG cb ); SequenceNumber GetNextSeqNumber( ); BOOL Search( const CDomainRelativeObjId &droid, TRKSVR_MOVE_NOTIFICATION *pNotification ); void Seek( SequenceNumber seq ); void Seek( int origin, int iSeek ); LogIndex GetReadIndex(); LogIndex GetStartIndex(); LogIndex GetEndIndex(); const TCHAR *LogFileName(); void SetReadIndex( LogIndex ilogRead ); ULONG NumEntriesInFile( ); ULONG NumEntriesPerSector(); ULONG NumEntriesPerKB(); ULONG CBSector() const; void Timer( DWORD dwTimerId ); ULONG DataSectorOffset() const; BOOL IsEmpty(); public: void ValidateLog(); ULONG GetCbLog(); void MakeEntryOld(); ULONG GetNumEntries(); void GenerateLogName(); private: CLog _cLog; CTrkWksConfiguration *_pTrkWksConfiguration; CLogFile _cLogFile; DWORD _cbSector; TCHAR _tszLogFile[ MAX_PATH + 1 ]; CWorkManager *_pWorkManager; CSimpleTimer _cSimpleTimer; }; */ #endif // #ifndef _TRKWKS_HXX_