|
|
// 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 <trkwks.h>
#include <trksvr.h>
#include <mountmgr.h> // 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<LogEntry*>(_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<const LogEntry*>(_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_
|