windows-nt/Source/XPSP1/NT/com/svcdlls/trksvcs/trkwks/trkwks.hxx

4266 lines
116 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
// 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_