You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4264 lines
109 KiB
4264 lines
109 KiB
|
|
// Copyright (c) 1996-1999 Microsoft Corporation
|
|
|
|
//+============================================================================
|
|
//
|
|
// trklib.hxx
|
|
//
|
|
// Private classes and declarations shared between
|
|
// Tracking (Workstation) Service and
|
|
// Tracking (Server) Service.
|
|
//
|
|
//
|
|
//+============================================================================
|
|
|
|
#pragma once
|
|
|
|
#include <cfiletim.hxx>
|
|
|
|
#define ELEMENTS(x) (sizeof(x)/sizeof(x[0]))
|
|
|
|
#define HIGH_WORD(dword) ( (WORD) (dword >> 16) )
|
|
#define LO_WORD(dword) ( (WORD) (dword & 0xFFFF) )
|
|
#define HIGH_BYTE(word) ( (BYTE) (word >> 8) )
|
|
#define LO_BYTE(word) ( (BYTE) (word & 0xFF) )
|
|
|
|
#define LINKDATA_AS_CLASS
|
|
#include <linkdata.hxx>
|
|
#include <trk.h>
|
|
#include <rpcasync.h>
|
|
#include <trkwks.h>
|
|
#include <..\\common\\config.hxx>
|
|
|
|
#include <disttrk.hxx>
|
|
#include "debug.hxx"
|
|
|
|
struct CDomainRelativeObjId;
|
|
|
|
#if !DBG || defined(lint) || defined(_lint)
|
|
#define DBGSTATIC static // hidden function
|
|
#else
|
|
#define DBGSTATIC // visible for use in debugger.
|
|
#endif
|
|
|
|
|
|
#ifdef TRKDATA_ALLOCATE
|
|
#define EXTERN
|
|
#define INIT( _x) = _x
|
|
#else
|
|
#define EXTERN extern
|
|
#define INIT( _x)
|
|
#endif
|
|
|
|
|
|
const extern TCHAR s_tszKeyNameLinkTrack[];
|
|
|
|
// RTL_SYSTEM_VOLUME_INFORMATION_FOLDER is "System Volume Information"
|
|
const extern TCHAR s_tszSystemVolumeInformation[] INIT( RTL_SYSTEM_VOLUME_INFORMATION_FOLDER );
|
|
const extern TCHAR s_tszLogFileName[] INIT( L"\\" RTL_SYSTEM_VOLUME_INFORMATION_FOLDER L"\\tracking.log" );
|
|
|
|
const extern TCHAR s_tszOldLogFileName[] INIT(TEXT("\\~secure.nt\\tracking.log"));
|
|
|
|
// ------------
|
|
// Debug Levels
|
|
// ------------
|
|
|
|
//
|
|
// The following defines are used to describe debug messages.
|
|
// Each TrkLog call is categorized in one of three ways:
|
|
// error - TRKDBG_ERROR
|
|
// scenario - e.g. TRKDBG_MOVE
|
|
// component - e.g. TRKDBG_SVR
|
|
//
|
|
|
|
//
|
|
// Tracing based on classes:
|
|
//
|
|
|
|
#define TRKDBG_TIMER 0x10000000
|
|
#define TRKDBG_IDT 0x08000000 // CIntraDomainTable
|
|
#define TRKDBG_SVR 0x04000000 // CTrkSvrSvc
|
|
#define TRKDBG_LOG 0x02000000 // CLog, CVolumeList
|
|
#define TRKDBG_PORT 0x01000000 // CPort
|
|
#define TRKDBG_WORKMAN 0x00800000 // CWorkMan
|
|
#define TRKDBG_VOLTAB 0x00400000 // CVolumeTable
|
|
#define TRKDBG_LDAP 0x00200000 // ldap_ calls
|
|
#define TRKDBG_WKS 0x00100000 // CTrkWksSvc
|
|
#define TRKDBG_VOLMAP 0x00080000 // CPersistentVolumeMap
|
|
#define TRKDBG_ADMIN 0x00040000
|
|
#define TRKDBG_RPC 0x00020000
|
|
#define TRKDBG_VOL_REFCNT 0x00010000
|
|
#define TRKDBG_DENIAL 0x00008000 // CDenialChecker
|
|
#define TRKDBG_VOLCACHE 0x00004000
|
|
#define TRKDBG_OBJID_DELETIONS 0x00002000
|
|
#define TRKDBG_TUNNEL 0x00001000
|
|
#define TRKDBG_QUOTA 0x00000800 // CQuotaTable
|
|
#define TRKDBG_MISC 0x00000400 // Miscellaneous
|
|
#define TRKDBG_VOLUME 0x00000200
|
|
|
|
//
|
|
// Tracing based on scenarios:
|
|
//
|
|
|
|
#define TRKDBG_MOVE 0x00000800 // move, volume synchronization
|
|
#define TRKDBG_MEND 0x00000400 // mend
|
|
#define TRKDBG_GARBAGE_COLLECT 0x00000200 // all garbage collection
|
|
#define TRKDBG_VOLTAB_RESTORE 0x00000100 // volume table restore
|
|
#define TRKDBG_CREATE 0x00000080 // create link
|
|
|
|
// Composite debug messages
|
|
#define TRKDBG_ERROR 0x80000000
|
|
#define TRKDBG_WARNING 0x40000000
|
|
|
|
|
|
|
|
//
|
|
// Internal error codes (those that are used across
|
|
// and RPC boundary are in trk.idl).
|
|
//
|
|
|
|
#define TRK_E_CORRUPT_LOG 0x8dead001
|
|
#define TRK_E_TIMER_REGISTRY_CORRUPT 0x8dead002
|
|
#define TRK_E_REGISTRY_REFRESH_CORRUPT 0x8dead003
|
|
#define TRK_E_CORRUPT_IDT 0x8dead004
|
|
#define TRK_E_DB_CONNECT_ERROR 0x8dead005
|
|
#define TRK_E_DN_TOO_LONG 0x8dead006
|
|
#define TRK_E_DOMAIN_COMPUTER_NAMES_TOO_LONG 0x8dead007
|
|
#define TRK_E_BAD_USERNAME_NO_SLASH 0x8dead008
|
|
#define TRK_E_UNKNOWN_SID 0x8dead009
|
|
#define TRK_E_IMPERSONATED_COMPUTERNAME_TOO_LONG 0x8dead00a
|
|
#define TRK_E_UNKNOWN_SVR_MESSAGE_TYPE 0x8dead00b
|
|
#define TRK_E_FAIL_TEST 0x8dead00c
|
|
#define TRK_E_DENIAL_OF_SERVICE_ATTACK 0x8dead00d
|
|
#define TRK_E_SERVICE_NOT_RUNNING 0x8dead00e
|
|
#define TRK_E_TOO_MANY_UNSHORTENED_NOTIFICATIONS 0x8dead00f
|
|
#define TRK_E_CORRUPT_CLNTSYNC 0x8dead010
|
|
#define TRK_E_COMPUTER_NAME_TOO_LONG 0x8dead011
|
|
#define TRK_E_SERVICE_STOPPING 0x8dead012
|
|
#define TRK_E_BIRTHIDS_DONT_MATCH 0x8dead013
|
|
#define TRK_E_CORRUPT_VOLTAB 0x8dead014
|
|
#define TRK_E_INTERNAL_ERROR 0x8dead015
|
|
#define TRK_E_PATH_TOO_LONG 0x8dead016
|
|
#define TRK_E_GET_MACHINE_NAME_FAIL 0x8dead017
|
|
#define TRK_E_SET_VOLUME_STATE_FAIL 0x8dead018
|
|
#define TRK_E_VOLUME_ACCESS_DENIED 0x8dead019
|
|
#define TRK_E_NOT_FOUND 0x8dead01b
|
|
#define TRK_E_VOLUME_QUOTA_EXCEEDED 0x8dead01c
|
|
|
|
#define TRK_E_SERVER_TOO_BUSY 0x8dead01e
|
|
#define TRK_E_INVALID_VOLUME_ID 0x8dead01f
|
|
#define TRK_E_CALLER_NOT_MACHINE_ACCOUNT 0x8dead020
|
|
#define TRK_E_VOLUME_NOT_DRIVE 0x8dead021
|
|
|
|
#if DBG
|
|
TCHAR * StringizeServiceControl( DWORD dwControl );
|
|
const TCHAR * GetErrorString(HRESULT hr);
|
|
#endif
|
|
|
|
HRESULT MapTR2HR( HRESULT tr );
|
|
|
|
// ------
|
|
// Macros
|
|
// ------
|
|
|
|
#if DBG
|
|
#define IFDBG(x) x
|
|
#else
|
|
#define IFDBG(x)
|
|
#endif
|
|
|
|
#define SECONDS_IN_DAY (60 * 60 * 24)
|
|
|
|
// Num characters in a string-ized GUID, not including the null terminator.
|
|
// E.g. "{48dad90c-51fb-11d0-8c59-00c04fd90f85}"
|
|
#define CCH_GUID_STRING 38
|
|
|
|
// Verify that TCHARs are WCHARs
|
|
#define ASSERT_TCHAR_IS_WCHAR TrkAssert( sizeof(TCHAR) == sizeof(WCHAR) )
|
|
|
|
#define TRK_MAX_DOMAINNAME 15
|
|
#define TRK_MAX_USERNAME 15
|
|
|
|
#define RELEASE_INTERFACE(punk) if( NULL != punk ) { punk->Release(); punk = NULL; }
|
|
|
|
|
|
|
|
// This structure is used to map TRK_E error codes to HRESULTs, and
|
|
// to strings for debug use.
|
|
|
|
struct TrkEMap
|
|
{
|
|
HRESULT tr; // TRK_E_* codes
|
|
HRESULT hr;
|
|
|
|
#if DBG
|
|
TCHAR *ptszDescription;
|
|
#endif
|
|
};
|
|
|
|
|
|
|
|
// -------------------
|
|
// Function Prototypes
|
|
// -------------------
|
|
|
|
FILETIME GetFileTimeNow();
|
|
BOOL EnablePrivilege( const TCHAR *ptszPrivilegeName );
|
|
|
|
extern BOOL g_fRestorePrivilegeEnabled INIT(FALSE);
|
|
|
|
inline void EnableRestorePrivilege()
|
|
{
|
|
if( !g_fRestorePrivilegeEnabled )
|
|
{
|
|
g_fRestorePrivilegeEnabled = TRUE;
|
|
EnablePrivilege( SE_RESTORE_NAME );
|
|
}
|
|
}
|
|
|
|
BOOL RunningAsAdministratorHack();
|
|
|
|
|
|
|
|
#if DBG
|
|
|
|
typedef VOID (*PFNRtlCheckForOrphanedCriticalSections)( HANDLE hThread );
|
|
|
|
inline void
|
|
TrkRtlCheckForOrphanedCriticalSections( HANDLE hThread )
|
|
{
|
|
static PFNRtlCheckForOrphanedCriticalSections pfnRtlCheckForOrphanedCriticalSections = NULL;
|
|
static BOOL fGetProcAddress = FALSE;
|
|
|
|
if( !fGetProcAddress )
|
|
{
|
|
fGetProcAddress = TRUE;
|
|
pfnRtlCheckForOrphanedCriticalSections
|
|
= (PFNRtlCheckForOrphanedCriticalSections)
|
|
GetProcAddress( GetModuleHandle(TEXT("ntdll.dll")), "RtlCheckForOrphanedCriticalSections" );
|
|
|
|
}
|
|
|
|
if( NULL != pfnRtlCheckForOrphanedCriticalSections )
|
|
pfnRtlCheckForOrphanedCriticalSections( hThread );
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
LONG _BreakOnDebuggableException(DWORD dwException, EXCEPTION_POINTERS* pException );
|
|
|
|
#define BreakOnDebuggableException() \
|
|
( (GetExceptionCode() == STATUS_ACCESS_VIOLATION) || (GetExceptionCode() == STATUS_POSSIBLE_DEADLOCK) \
|
|
? _BreakThenReturn( EXCEPTION_EXECUTE_HANDLER, GetExceptionCode(), GetExceptionInformation() ) \
|
|
: EXCEPTION_EXECUTE_HANDLER)
|
|
|
|
// Break to debugger within an exception handler
|
|
|
|
#define BREAK_THEN_RETURN( i ) \
|
|
_BreakThenReturn( i, GetExceptionCode(), GetExceptionInformation() )
|
|
|
|
inline int _BreakThenReturn( int i, DWORD dwException, EXCEPTION_POINTERS* pException )
|
|
{
|
|
#if DBG
|
|
|
|
if( NULL != pException )
|
|
{
|
|
TrkLog(( TRKDBG_ERROR, TEXT("!cxr %p;!exr %p"),
|
|
pException->ContextRecord, pException->ExceptionRecord ));
|
|
}
|
|
|
|
DebugBreak();
|
|
|
|
#endif
|
|
|
|
return( i );
|
|
}
|
|
|
|
|
|
TCHAR * wcstotcs(TCHAR *ptszBuf, const WCHAR *pwsz);
|
|
CHAR * tcstombs(CHAR *pszBuf, const TCHAR *ptsz);
|
|
WCHAR * tcstowcs(WCHAR *pwszBuf, const TCHAR *ptsz);
|
|
TCHAR * mbstotcs(TCHAR *ptszBuf, const CHAR *psz);
|
|
|
|
BOOL IsLocalObjectVolume(const TCHAR *pwszPath);
|
|
BOOL IsLocalObjectVolume( ULONG iVolume );
|
|
|
|
DWORD
|
|
TrkTimeUnits(const SYSTEMTIME &st );
|
|
|
|
DWORD
|
|
TrkTimeUnits(const CFILETIME &cft );
|
|
|
|
extern VOID
|
|
SZToCLSID( LPCSTR szCLSID, CLSID *pclsid );
|
|
|
|
|
|
|
|
#include <fileops.hxx>
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Inline routines to raise exceptions
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#if DBG
|
|
|
|
#define TrkRaiseException(e) dbgRaiseException(e, __FILE__, __LINE__)
|
|
#define TrkRaiseWin32Error(e) dbgRaiseWin32Error(e, __FILE__, __LINE__)
|
|
#define TrkRaiseLastError() dbgRaiseLastError( __FILE__, __LINE__)
|
|
#define TrkRaiseNtStatus(e) dbgRaiseNtStatus(e, __FILE__, __LINE__)
|
|
|
|
inline void dbgRaiseException( HRESULT hr, const char * pszFile, int line )
|
|
{
|
|
TrkLog((TRKDBG_WARNING, TEXT("Exception %08x at %hs:%d"), hr, pszFile, line));
|
|
RaiseException( hr, 0, 0, NULL );
|
|
}
|
|
|
|
inline void dbgRaiseWin32Error( long lError, const char * pszFile, int line )
|
|
{
|
|
dbgRaiseException( HRESULT_FROM_WIN32( lError ), pszFile, line );
|
|
}
|
|
|
|
inline void dbgRaiseLastError( const char * pszFile, int line )
|
|
{
|
|
dbgRaiseWin32Error( GetLastError(), pszFile, line );
|
|
}
|
|
|
|
inline void dbgRaiseNtStatus( NTSTATUS ntstatus, const char * pszFile, int line )
|
|
{
|
|
dbgRaiseException( ntstatus, pszFile, line );
|
|
}
|
|
|
|
#else
|
|
|
|
inline void TrkRaiseException( HRESULT hr )
|
|
{
|
|
RaiseException( hr, 0, 0, NULL );
|
|
}
|
|
|
|
inline void TrkRaiseWin32Error( long lError )
|
|
{
|
|
TrkRaiseException( HRESULT_FROM_WIN32( lError ) );
|
|
}
|
|
|
|
inline void TrkRaiseLastError( )
|
|
{
|
|
TrkRaiseWin32Error( GetLastError() );
|
|
}
|
|
|
|
inline void TrkRaiseNtStatus( NTSTATUS ntstatus )
|
|
{
|
|
TrkRaiseException( ntstatus );
|
|
}
|
|
|
|
#endif
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Services.exe simulation
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
//HANDLE
|
|
//TrkSvcAddWorkItem (
|
|
// IN HANDLE hWaitableObject,
|
|
// IN PSVCS_WORKER_CALLBACK pCallbackFunction,
|
|
// IN PVOID pContext,
|
|
// IN DWORD dwFlags,
|
|
// IN DWORD dwTimeout,
|
|
// IN HANDLE hDllReference
|
|
// );
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Time
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CGuid //
|
|
// //
|
|
//-------------------------------------------------------------------//
|
|
|
|
#define MAX_STRINGIZED_GUID 35 // not including NULL
|
|
|
|
void
|
|
HexStringizeGuid(const GUID &g, TCHAR * & rptsz );
|
|
|
|
void
|
|
HexStringizeGuidA(const GUID &g, char * & rpsz);
|
|
|
|
BOOL
|
|
HexUnstringizeGuid(const TCHAR * &ptsz, GUID * pg);
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// CVolumeSecret inlines
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
inline
|
|
CVolumeSecret::CVolumeSecret()
|
|
{
|
|
memset(this, 0, sizeof(*this));
|
|
}
|
|
|
|
inline
|
|
CVolumeSecret::operator == (const CVolumeSecret & other) const
|
|
{
|
|
return memcmp( _abSecret, other._abSecret, sizeof(_abSecret) ) == 0;
|
|
}
|
|
|
|
inline
|
|
CVolumeSecret::operator != (const CVolumeSecret & other) const
|
|
{
|
|
return memcmp( _abSecret, other._abSecret, sizeof(_abSecret) ) != 0;
|
|
}
|
|
|
|
inline void
|
|
CVolumeSecret::Stringize(TCHAR * & rptsz) const
|
|
{
|
|
wsprintf( rptsz, TEXT("%02X%02X%02X%02X%02X%02X%02X%02X"),
|
|
_abSecret[0],
|
|
_abSecret[1],
|
|
_abSecret[2],
|
|
_abSecret[3],
|
|
_abSecret[4],
|
|
_abSecret[5],
|
|
_abSecret[6],
|
|
_abSecret[7] );
|
|
|
|
rptsz += 16;
|
|
}
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CObjId inlines
|
|
// //
|
|
//-------------------------------------------------------------------//
|
|
|
|
inline void
|
|
CObjId::DebugPrint(const TCHAR *ptszName)
|
|
{
|
|
TCHAR tsz[256];
|
|
TCHAR *ptsz = tsz;
|
|
Stringize(ptsz);
|
|
printf("%s=%s", ptszName, tsz);
|
|
}
|
|
|
|
inline void
|
|
CObjId::Stringize(TCHAR * & rptsz) const
|
|
{
|
|
HexStringizeGuid(_object, rptsz);
|
|
}
|
|
|
|
inline BOOL
|
|
CObjId::Unstringize(const TCHAR *&rptsz)
|
|
{
|
|
return( HexUnstringizeGuid( rptsz, (GUID*)&_object ));
|
|
}
|
|
|
|
inline RPC_STATUS
|
|
CObjId::UuidCreate()
|
|
{
|
|
return(::UuidCreate(&_object));
|
|
}
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CVolumeId inlines
|
|
// //
|
|
//-------------------------------------------------------------------//
|
|
|
|
// Convert a zero-relative drive letter index into a TCHAR
|
|
|
|
inline TCHAR
|
|
VolChar( LONG iVol )
|
|
{
|
|
if( 0 > iVol || 26 <= iVol )
|
|
return( TEXT('?') );
|
|
else
|
|
return static_cast<TCHAR>( TEXT('A') + (TCHAR)iVol );
|
|
}
|
|
|
|
|
|
// Initialize a volid from a stringized representation of a GUID
|
|
|
|
inline
|
|
CVolumeId::CVolumeId(const TCHAR * ptszStringizedGuid, HRESULT hr)
|
|
{
|
|
if ( _tcslen(ptszStringizedGuid) < 32)
|
|
{
|
|
TrkRaiseException(hr);
|
|
}
|
|
HexUnstringizeGuid( ptszStringizedGuid, &_volume );
|
|
}
|
|
|
|
// Initialize a volid from the result of an
|
|
// NtQueryVolumeInformationFile(FileFsObjectIdInformation)
|
|
|
|
inline
|
|
CVolumeId::operator FILE_FS_OBJECTID_INFORMATION () const
|
|
{
|
|
FILE_FS_OBJECTID_INFORMATION ffoi;
|
|
|
|
memset( ffoi.ExtendedInfo, 0, sizeof(ffoi.ExtendedInfo) );
|
|
memcpy( ffoi.ObjectId, &_volume, sizeof(ffoi.ObjectId) );
|
|
|
|
return(ffoi);
|
|
}
|
|
|
|
// Write out the volid as a hex string.
|
|
|
|
inline void
|
|
CVolumeId::Stringize(TCHAR * & rptsz) const
|
|
{
|
|
HexStringizeGuid(_volume, rptsz);
|
|
}
|
|
|
|
// Read in the volid from a hex string.
|
|
|
|
inline BOOL
|
|
CVolumeId::Unstringize(const TCHAR * & rptsz)
|
|
{
|
|
return( HexUnstringizeGuid( rptsz, (GUID*)&_volume ));
|
|
}
|
|
|
|
//
|
|
// We use bit 0 of the volume id as a flag that indicates
|
|
// the file has been moved across volumes.
|
|
//
|
|
|
|
inline BOOL
|
|
CVolumeId::GetUserBitState() const
|
|
{
|
|
return(_volume.Data1 & 1);
|
|
}
|
|
|
|
// Create a volume ID, ensuring that the cross-volume
|
|
// bit is clear.
|
|
|
|
inline RPC_STATUS
|
|
CVolumeId::UuidCreate()
|
|
{
|
|
RPC_STATUS Status;
|
|
int l=0;
|
|
do
|
|
{
|
|
Status = ::UuidCreate(&_volume);
|
|
l++;
|
|
|
|
// UuidCreate uses the new randomized algorithm,
|
|
// so we shouldn't get a net-based value any longer.
|
|
TrkAssert( RPC_S_UUID_LOCAL_ONLY != Status );
|
|
|
|
} while (l<100 && Status == RPC_S_OK && (_volume.Data1 & 1));
|
|
if (l==100)
|
|
{
|
|
return(CO_E_FAILEDTOGENUUID);
|
|
}
|
|
return(Status);
|
|
}
|
|
|
|
inline
|
|
CVolumeId:: operator == (const CVolumeId & Other) const
|
|
{
|
|
return( 0 == memcmp( &_volume, &Other._volume, sizeof(_volume) ) );
|
|
}
|
|
|
|
inline
|
|
CVolumeId:: operator != (const CVolumeId & Other) const
|
|
{
|
|
return ! (Other == *this);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CMachineId inlines
|
|
// //
|
|
//-------------------------------------------------------------------//
|
|
|
|
#define MCID_BYTE_FORMAT_STRING TEXT("(%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X)")
|
|
|
|
// Initialize from a char buffer.
|
|
|
|
inline CMachineId::CMachineId(const char * pb, ULONG cb, HRESULT hr)
|
|
{
|
|
if (cb < sizeof(_szMachine))
|
|
{
|
|
TrkRaiseException(hr);
|
|
}
|
|
memcpy(_szMachine, pb, sizeof(_szMachine));
|
|
}
|
|
|
|
|
|
// Return the mcid's computer name in Unicode.
|
|
|
|
inline void
|
|
CMachineId::GetName(TCHAR *ptsz, DWORD cch) const
|
|
{
|
|
RaiseIfInvalid();
|
|
|
|
TrkAssert(cch >= sizeof(_szMachine));
|
|
mbstotcs(ptsz, _szMachine);
|
|
}
|
|
|
|
// Dump the mcid as a string
|
|
|
|
inline void
|
|
CMachineId::Stringize(TCHAR * & rptsz) const
|
|
{
|
|
RaiseIfInvalid();
|
|
|
|
mbstotcs( rptsz, _szMachine );
|
|
rptsz += _tcslen(rptsz);
|
|
}
|
|
|
|
// Dump the mcid as a GUID
|
|
|
|
inline void
|
|
CMachineId::StringizeAsGuid(TCHAR * & rptsz) const
|
|
{
|
|
_stprintf(rptsz, TEXT("\\%02x\\%02x\\%02x\\%02x\\%02x\\%02x\\%02x\\%02x")
|
|
TEXT("\\%02x\\%02x\\%02x\\%02x\\%02x\\%02x\\%02x\\%02x"),
|
|
(BYTE)_szMachine[0], (BYTE)_szMachine[1], (BYTE)_szMachine[2], (BYTE)_szMachine[3],
|
|
(BYTE)_szMachine[4], (BYTE)_szMachine[5], (BYTE)_szMachine[6], (BYTE)_szMachine[7],
|
|
(BYTE)_szMachine[8], (BYTE)_szMachine[9], (BYTE)_szMachine[10], (BYTE)_szMachine[11],
|
|
(BYTE)_szMachine[12], (BYTE)_szMachine[13], (BYTE)_szMachine[14], (BYTE)_szMachine[15]);
|
|
rptsz += 3 * 16;
|
|
}
|
|
|
|
inline void
|
|
CMachineId::RaiseIfInvalid() const
|
|
{
|
|
if( !IsTerminated() )
|
|
{
|
|
TrkLog(( TRKDBG_WARNING,
|
|
TEXT("Invalid CMachineId: ")
|
|
MCID_BYTE_FORMAT_STRING,
|
|
_szMachine,
|
|
(BYTE)_szMachine[0], (BYTE)_szMachine[1], (BYTE)_szMachine[2], (BYTE)_szMachine[3],
|
|
(BYTE)_szMachine[4], (BYTE)_szMachine[5], (BYTE)_szMachine[6], (BYTE)_szMachine[7],
|
|
(BYTE)_szMachine[8], (BYTE)_szMachine[9], (BYTE)_szMachine[10], (BYTE)_szMachine[11],
|
|
(BYTE)_szMachine[12], (BYTE)_szMachine[13], (BYTE)_szMachine[14], (BYTE)_szMachine[15] ));
|
|
|
|
TrkRaiseWin32Error( ERROR_INVALID_COMPUTERNAME );
|
|
}
|
|
|
|
}
|
|
|
|
inline BOOL
|
|
CMachineId::IsTerminated() const
|
|
{
|
|
// We're valid if we're terminated.
|
|
return('\0' == _szMachine[sizeof(_szMachine)-1] );
|
|
}
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CDomainRelativeObjId inlines
|
|
// //
|
|
//-------------------------------------------------------------------//
|
|
|
|
#if DBG
|
|
inline
|
|
CDomainRelativeObjId::CDomainRelativeObjId(const TCHAR * ptszTest)
|
|
{
|
|
// test constructor only
|
|
char * pszWrite = (char*)&_object;
|
|
do
|
|
{
|
|
*pszWrite = (char) *ptszTest;
|
|
} while (*pszWrite != '\0' && ptszTest[1] != TEXT(' ') && pszWrite++ && ptszTest++);
|
|
}
|
|
#endif
|
|
|
|
|
|
inline void
|
|
CDomainRelativeObjId::Init()
|
|
{
|
|
_volume.Init();
|
|
_object.Init();
|
|
}
|
|
|
|
inline
|
|
CDomainRelativeObjId::operator == (const CDomainRelativeObjId &Other) const
|
|
{
|
|
return(_volume == Other._volume && _object == Other._object);
|
|
}
|
|
|
|
inline
|
|
CDomainRelativeObjId::operator != (const CDomainRelativeObjId &Other) const
|
|
{
|
|
return !(*this == Other);
|
|
}
|
|
|
|
inline void
|
|
CDomainRelativeObjId::DebugPrint(const TCHAR *ptszName)
|
|
{
|
|
TCHAR tsz[256];
|
|
TCHAR *ptsz = tsz;
|
|
_volume.Stringize(ptsz);
|
|
*ptsz++ = TEXT('-');
|
|
_object.Stringize(ptsz);
|
|
_tprintf(TEXT(" %s=%s\n"), ptszName, tsz);
|
|
}
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Class CTrkRpcConfig
|
|
//
|
|
// Base class for RPC configuration. This is inherited by CRpcClientBinding
|
|
// and CRpcServer. This class inherits from CTrkConfiguration, which
|
|
// represents the base key in the registry of the service's configuration.
|
|
// This class represents the custom DC name values, the presence of which
|
|
// controls whether secure RPC is used.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
class CTrkRpcConfig : protected CTrkConfiguration
|
|
{
|
|
protected:
|
|
|
|
CTrkRpcConfig();
|
|
|
|
public:
|
|
friend void
|
|
ServiceStopCallback( PVOID pContext, BOOLEAN fTimeout );
|
|
friend VOID
|
|
SVCS_ENTRY_POINT(
|
|
DWORD NumArgs,
|
|
LPTSTR *ArgsArray,
|
|
PSVCHOST_GLOBAL_DATA pSvcsGlobalData,
|
|
IN HANDLE SvcRefHandle
|
|
);
|
|
|
|
|
|
static BOOL RpcSecurityEnabled()
|
|
{
|
|
return( 0 == _mtszCustomDcName.NumStrings() );
|
|
}
|
|
|
|
static BOOL UseCustomDc()
|
|
{
|
|
return( 0 != _mtszCustomDcName.NumStrings() );
|
|
}
|
|
|
|
static BOOL UseCustomSecureDc()
|
|
{
|
|
return( 0 != _mtszCustomSecureDcName.NumStrings() );
|
|
}
|
|
|
|
static const TCHAR *GetCustomDcName()
|
|
{
|
|
return( _mtszCustomDcName );
|
|
}
|
|
|
|
static const TCHAR *GetCustomSecureDcName()
|
|
{
|
|
return( _mtszCustomDcName );
|
|
}
|
|
|
|
protected:
|
|
|
|
// Since this class is used in multiple places as a base
|
|
// class, the data members are static so that we only
|
|
// read the registry once.
|
|
|
|
static BOOL _fInitialized;
|
|
|
|
// List of DC names to use. If this exists, then we're
|
|
// not using secure RPC.
|
|
static CMultiTsz _mtszCustomDcName;
|
|
|
|
// List of DC names to use. If this exists, then we
|
|
// are using security RPC (this has priority over the
|
|
// previous value).
|
|
static CMultiTsz _mtszCustomSecureDcName;
|
|
|
|
}; // class CTrkRpcConfig
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// CObjIdEnumerator
|
|
//
|
|
// Enumerate the object IDs on a given volume. This uses the
|
|
// FindFirst/FindNext model, and automatically ignores the volume
|
|
// ID (which is stored in the NTFS object ID index).
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
|
|
// The following defines shows how to determine if a filereference
|
|
// in the enumeration is actually the volume ID.
|
|
|
|
#define FILEREF_VOL 3
|
|
#define FILEREF_MASK 0x0000ffffffffffff
|
|
|
|
inline BOOL
|
|
IsVolumeFileReference( LONGLONG FileReference )
|
|
{
|
|
return (FileReference & FILEREF_MASK) == FILEREF_VOL;
|
|
}
|
|
|
|
class CObjIdEnumerator
|
|
{
|
|
|
|
private:
|
|
|
|
BOOL _fInitializeCalled:1;
|
|
|
|
// Handle to the object ID index directory
|
|
HANDLE _hDir;
|
|
|
|
// Count of bytes returned by NtQueryDirectoryFile
|
|
ULONG _cbObjIdInfo;
|
|
|
|
// Buffer of data returned from an enumeration of the object ID index,
|
|
// and a pointer into that buffer that represents the current location
|
|
// (i.e. the cursor).
|
|
|
|
FILE_OBJECTID_INFORMATION _ObjIdInfo[32];
|
|
FILE_OBJECTID_INFORMATION * _pObjIdInfo;
|
|
|
|
public:
|
|
|
|
CObjIdEnumerator() : _fInitializeCalled(FALSE), _hDir(INVALID_HANDLE_VALUE) { }
|
|
~CObjIdEnumerator() { UnInitialize(); }
|
|
|
|
BOOL Initialize( const TCHAR *ptszVolumeDeviceName );
|
|
void UnInitialize();
|
|
|
|
inline BOOL FindFirst( CObjId * pobjid, CDomainRelativeObjId * pdroidBirth );
|
|
inline BOOL FindNext( CObjId * pobjid, CDomainRelativeObjId * pdroidBirth );
|
|
|
|
private:
|
|
|
|
static
|
|
inline void UnloadFileObjectIdInfo( const FILE_OBJECTID_INFORMATION & ObjIdInfo,
|
|
CObjId * pobjid, CDomainRelativeObjId * pdroidBirth );
|
|
BOOL Find( CObjId * pobjid, CDomainRelativeObjId * pdroidBirth, BOOL fRestart );
|
|
|
|
|
|
};
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CObjIdEnumerator inlines
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
inline void
|
|
CObjIdEnumerator::UnloadFileObjectIdInfo( const FILE_OBJECTID_INFORMATION & ObjIdInfo,
|
|
CObjId * pobjid,
|
|
CDomainRelativeObjId * pdroidBirth )
|
|
{
|
|
pdroidBirth->InitFromFOI( ObjIdInfo );
|
|
*pobjid = CObjId( FOI_OBJECTID, ObjIdInfo );
|
|
}
|
|
|
|
// Get the first entry in the object ID index
|
|
inline BOOL
|
|
CObjIdEnumerator::FindFirst( CObjId * pobjid, CDomainRelativeObjId * pdroidBirth )
|
|
{
|
|
return( Find( pobjid, pdroidBirth, TRUE ) );
|
|
}
|
|
|
|
// Get the next entry in the object ID index.
|
|
inline BOOL
|
|
CObjIdEnumerator::FindNext( CObjId * pobjid, CDomainRelativeObjId * pdroidBirth )
|
|
{
|
|
TrkAssert(_cbObjIdInfo % sizeof(_ObjIdInfo[0]) == 0);
|
|
|
|
// Start by looking to see
|
|
// if there's another entry in the _ObjIdInfo buffer.
|
|
|
|
while (_pObjIdInfo < &_ObjIdInfo[_cbObjIdInfo / sizeof(_ObjIdInfo[0])])
|
|
{
|
|
// There's an entry in the buffer. Is it a real entry, or the
|
|
// volume ID?
|
|
|
|
if( !IsVolumeFileReference( _pObjIdInfo->FileReference ))
|
|
{
|
|
// It's a real file entry. Return it to the user.
|
|
UnloadFileObjectIdInfo( *_pObjIdInfo, pobjid, pdroidBirth );
|
|
_pObjIdInfo ++;
|
|
return(TRUE);
|
|
}
|
|
else
|
|
{
|
|
// It's the volume ID. Go on to the next entry in
|
|
// the buffer.
|
|
|
|
TrkLog((TRKDBG_GARBAGE_COLLECT|TRKDBG_VOLUME,
|
|
TEXT("CObjIdEnumerator::FindNext() skipping volume id.")));
|
|
_pObjIdInfo++;
|
|
}
|
|
}
|
|
|
|
// If we get here, there are no more entries in the _ObjIdInfo buffer.
|
|
// Query the object ID index directory for a new set of records.
|
|
|
|
return(Find(pobjid, pdroidBirth, FALSE)); // don't restart scan
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// CRpcServer
|
|
//
|
|
// This class maintains a server interface handle. It registers
|
|
// the server, registers the endpoint (if it's dynamic), and
|
|
// registers authentication info if necessary.
|
|
//
|
|
// This class is always used as a base class. The derived class
|
|
// is responsible for the RpcUseProtseq calls.
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
enum RPC_WAIT_FLAG
|
|
{
|
|
DONT_WAIT,
|
|
WAIT
|
|
};
|
|
|
|
class CRpcServer : public CTrkRpcConfig
|
|
{
|
|
public:
|
|
CRpcServer() : _ifspec(NULL), _fEpRegister(FALSE) {}
|
|
~CRpcServer() { UnInitialize(); }
|
|
|
|
void Initialize( RPC_IF_HANDLE ifspec, ULONG grfRpcServerRegisterInterfaceEx,
|
|
UINT cMaxCalls, BOOL fSetAuthInfo,
|
|
const TCHAR *ptszProtSeqForEpRegistration );
|
|
void UnInitialize();
|
|
|
|
private:
|
|
|
|
RPC_IF_HANDLE _ifspec;
|
|
BOOL _fEpRegister;
|
|
};
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// PWorkItem - Virtual function which is called by the CWorkManager //
|
|
// when the corresponding waitable handle is signalled. //
|
|
// Handles are registered using RegisterWorkItem //
|
|
// //
|
|
//-------------------------------------------------------------------//
|
|
|
|
EXTERN DWORD g_cThreadPoolMaxThreads;
|
|
EXTERN DWORD g_cThreadPoolThreads;
|
|
|
|
#if DBG
|
|
EXTERN LONG g_cThreadPoolRegistrations;
|
|
#endif
|
|
|
|
|
|
// Callback functions for the Win32 thread pool services.
|
|
// Call a PWorkItem
|
|
VOID NTAPI ThreadPoolCallbackFunction( PVOID pvWorkItem, BOOLEAN fTimeout );
|
|
VOID NTAPI ThreadPoolWorkItemFunction( PVOID pvWorkItem );
|
|
|
|
|
|
#ifdef PRIVATE_THREAD_POOL
|
|
#include "workman2.hxx"
|
|
#endif
|
|
|
|
// Wrapper for [Un]RegisterWaitForSingleObjectEx
|
|
inline HANDLE
|
|
TrkRegisterWaitForSingleObjectEx( HANDLE hObject, WAITORTIMERCALLBACK Callback, PVOID Context, ULONG dwMilliseconds, ULONG dwFlags )
|
|
{
|
|
HANDLE hWait;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
#ifdef PRIVATE_THREAD_POOL
|
|
hWait = g_pworkman2->RegisterWait( hObject, Callback, Context, dwMilliseconds, dwFlags );
|
|
#else
|
|
Status = RtlRegisterWait( &hWait, hObject, Callback, Context, dwMilliseconds, dwFlags );
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
SetLastError( RtlNtStatusToDosError( Status ));
|
|
hWait = NULL;
|
|
}
|
|
#endif
|
|
|
|
#if DBG
|
|
if( NULL != hWait )
|
|
InterlockedIncrement( &g_cThreadPoolRegistrations );
|
|
#endif
|
|
|
|
TrkLog(( TRKDBG_WORKMAN, TEXT("hWait=%p registered with thread pool (%08x)"), hWait, Status ));
|
|
return( hWait );
|
|
}
|
|
inline BOOL
|
|
TrkUnregisterWait( HANDLE hWait, HANDLE hCompletionEvent = (HANDLE)-1 )
|
|
{
|
|
BOOL fRet = FALSE;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
IFDBG( InterlockedDecrement( &g_cThreadPoolRegistrations ));
|
|
|
|
#ifdef PRIVATE_THREAD_POOL
|
|
fRet = g_pworkman2->UnregisterWait( hWait );
|
|
#else
|
|
Status = RtlDeregisterWaitEx( hWait, hCompletionEvent );
|
|
if( NT_SUCCESS(Status) )
|
|
fRet = TRUE;
|
|
else
|
|
SetLastError( RtlNtStatusToDosError( Status ));
|
|
#endif
|
|
|
|
TrkLog(( TRKDBG_WORKMAN, TEXT("hWait=%p %s unregistered from thread pool"),
|
|
hWait,
|
|
fRet ? TEXT("successfully"):TEXT("unsuccessfully") ));
|
|
return( fRet );
|
|
}
|
|
|
|
inline NTSTATUS
|
|
TrkQueueWorkItem( PVOID Context, ULONG dwFlags )
|
|
{
|
|
return RtlQueueWorkItem( ThreadPoolWorkItemFunction,
|
|
Context,
|
|
dwFlags );
|
|
}
|
|
|
|
|
|
class PWorkItem
|
|
{
|
|
public:
|
|
virtual void DoWork() = 0;
|
|
|
|
#if DBG
|
|
TCHAR _tszWorkItemSig[48];
|
|
PWorkItem() { _tszWorkItemSig[0] = TEXT('\0'); }
|
|
#endif
|
|
};
|
|
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Class: CCriticalSection
|
|
//
|
|
// This class wraps a CRITICAL_SECTION, and exists to ensure that
|
|
// InitializeCriticalSection is properly handled (that API can
|
|
// raise STATUS_NO_MEMORY). That is, the critsec is only deleted
|
|
// if it has been properly initialized.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
class CCriticalSection
|
|
{
|
|
private:
|
|
|
|
BOOL _fInitialized;
|
|
CRITICAL_SECTION _cs;
|
|
|
|
public:
|
|
|
|
CCriticalSection()
|
|
{
|
|
_fInitialized = FALSE;
|
|
}
|
|
|
|
|
|
~CCriticalSection()
|
|
{
|
|
UnInitialize();
|
|
}
|
|
|
|
|
|
public:
|
|
|
|
void Initialize()
|
|
{
|
|
TrkAssert( !_fInitialized );
|
|
|
|
#if DBG
|
|
__try
|
|
{
|
|
InitializeCriticalSection( &_cs );
|
|
_fInitialized = TRUE;
|
|
}
|
|
__finally
|
|
{
|
|
if( AbnormalTermination() )
|
|
{
|
|
TrkLog(( TRKDBG_WARNING, TEXT("InitializeCriticalSection raised") ));
|
|
}
|
|
}
|
|
#else
|
|
|
|
InitializeCriticalSection( &_cs );
|
|
_fInitialized = TRUE;
|
|
|
|
#endif
|
|
}
|
|
|
|
BOOL IsInitialized()
|
|
{
|
|
return _fInitialized;
|
|
}
|
|
|
|
void UnInitialize()
|
|
{
|
|
if( _fInitialized )
|
|
{
|
|
_fInitialized = FALSE;
|
|
DeleteCriticalSection( &_cs );
|
|
}
|
|
}
|
|
|
|
void Enter()
|
|
{
|
|
if( !_fInitialized )
|
|
TrkRaiseException( STATUS_NO_MEMORY );
|
|
else
|
|
EnterCriticalSection( &_cs );
|
|
}
|
|
|
|
BOOL TryEnter()
|
|
{
|
|
if( !_fInitialized )
|
|
{
|
|
TrkRaiseException( STATUS_NO_MEMORY );
|
|
return 0; // Make compiler happy
|
|
}
|
|
else
|
|
return TryEnterCriticalSection( &_cs );
|
|
}
|
|
|
|
void Leave()
|
|
{
|
|
TrkAssert( _fInitialized );
|
|
if( _fInitialized )
|
|
LeaveCriticalSection( &_cs );
|
|
}
|
|
|
|
}; // CCriticalSection
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Class: CActiveThreadList
|
|
//
|
|
// This class is used to maintain a list of all active threads, be they from
|
|
// the NTDLL thread pool or from the RPC thread pool. When such a thread
|
|
// begins executing (within the context of this service), Add is called,
|
|
// and on completion Remove is called. The reason this class exists is
|
|
// for the CancelAllRpc method; this is used during service stop to call
|
|
// RpcCancelThread on all active threads.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
// Initial size of the array to hold the active threads
|
|
#define NUM_ACTIVE_THREAD_LIST 10
|
|
|
|
// Incremental size by which that array grows
|
|
#define INCREMENT_ACTIVE_THREAD_LIST 2
|
|
|
|
class CActiveThreadList
|
|
{
|
|
private:
|
|
|
|
ULONG _cMaxThreads;
|
|
ULONG _cActiveThreads;
|
|
DWORD *_prgdwThreadIDs;
|
|
CCriticalSection _cs;
|
|
|
|
public:
|
|
|
|
inline CActiveThreadList();
|
|
inline ~CActiveThreadList();
|
|
|
|
public:
|
|
|
|
inline void Initialize();
|
|
HRESULT AddCurrent( ); // Add the current thread to the list
|
|
HRESULT RemoveCurrent( ); // Remove the current thread
|
|
void CancelAllRpc(); // Cancel RPCs on all threads in list
|
|
|
|
inline ULONG GetCount() const;
|
|
|
|
private:
|
|
|
|
HRESULT Grow();
|
|
|
|
}; // class CActiveThreadList
|
|
|
|
|
|
// ------------------------------------
|
|
//
|
|
// CActiveThreadList::CActiveThreadList
|
|
//
|
|
// Initialize the thread list
|
|
//
|
|
// ------------------------------------
|
|
|
|
CActiveThreadList::CActiveThreadList()
|
|
{
|
|
|
|
_cActiveThreads = _cMaxThreads = 0;
|
|
|
|
// Allocate an array for the thread IDs. If this alloc fails,
|
|
// we'll try to realloc in the AddCurrent method.
|
|
|
|
_prgdwThreadIDs = new DWORD[ NUM_ACTIVE_THREAD_LIST ];
|
|
|
|
if( NULL != _prgdwThreadIDs )
|
|
{
|
|
_cMaxThreads = NUM_ACTIVE_THREAD_LIST;
|
|
memset( _prgdwThreadIDs, 0, sizeof(_prgdwThreadIDs[0]) * _cMaxThreads );
|
|
}
|
|
else
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't alloc CActiveThreadList in constructor") ));
|
|
}
|
|
|
|
|
|
inline void
|
|
CActiveThreadList::Initialize()
|
|
{
|
|
_cs.Initialize();
|
|
}
|
|
|
|
|
|
// -------------------------------------
|
|
//
|
|
// CActiveThreadList::~CActiveThreadList
|
|
//
|
|
// Frees the thread list
|
|
//
|
|
// -------------------------------------
|
|
|
|
CActiveThreadList::~CActiveThreadList()
|
|
{
|
|
if( NULL != _prgdwThreadIDs )
|
|
delete [] _prgdwThreadIDs;
|
|
_prgdwThreadIDs = NULL;
|
|
|
|
_cActiveThreads = _cMaxThreads = 0;
|
|
|
|
}
|
|
|
|
// ---------------------------
|
|
// CActiveThreadList::GetCount
|
|
// ---------------------------
|
|
|
|
ULONG
|
|
CActiveThreadList::GetCount() const
|
|
{
|
|
return( _cActiveThreads );
|
|
}
|
|
|
|
// Global pointer to the one-and-only CActiveThreadList instantiation
|
|
extern "C" CActiveThreadList *g_pActiveThreadList INIT(NULL);
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// SThreadFromPoolState
|
|
//
|
|
// This structure stores useful thread state information, which is
|
|
// really just the HardErrorMode. When we get a thread from one of
|
|
// the thread pools, we save this value, then modify it so that
|
|
// we don't get hard errors. When we're done with the thread we
|
|
// restore it.
|
|
//
|
|
// E.g.:
|
|
// SThreadFromPoolState state = InitializeThreadFromPool();
|
|
// ...
|
|
// UnInitializedThreadFromPool( state );
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
|
|
struct SThreadFromPoolState
|
|
{
|
|
ULONG HardErrorMode;
|
|
};
|
|
|
|
inline SThreadFromPoolState
|
|
InitializeThreadFromPool()
|
|
{
|
|
SThreadFromPoolState state;
|
|
|
|
// Disable the hard error popup dialog
|
|
RtlSetThreadErrorMode(RTL_ERRORMODE_FAILCRITICALERRORS,
|
|
&state.HardErrorMode);
|
|
|
|
// Set the RPC cancel timeout so that RpcCancelThread takes immediate effect.
|
|
|
|
RPC_STATUS rpcstatus = RpcMgmtSetCancelTimeout( 0 );
|
|
if( RPC_S_OK != rpcstatus )
|
|
{
|
|
// There's no reason this call should fail, and it's not important enough
|
|
// to justify an abort.
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Ignoring RpcMgtSetCancelTimeout error, %lu"), rpcstatus ));
|
|
}
|
|
|
|
// Add this thread to our list of actively-running threads.
|
|
|
|
if( NULL != g_pActiveThreadList )
|
|
g_pActiveThreadList->AddCurrent( );
|
|
|
|
return( state );
|
|
}
|
|
|
|
inline void
|
|
UnInitializeThreadFromPool( SThreadFromPoolState state )
|
|
{
|
|
if( NULL != g_pActiveThreadList )
|
|
g_pActiveThreadList->RemoveCurrent( );
|
|
|
|
RtlSetThreadErrorMode(state.HardErrorMode, NULL);
|
|
}
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Begin/EndSingleInstanceTask
|
|
//
|
|
// Based on a counter that counts active instances of a type of tasks,
|
|
// and based on the atomicity of Interlocked*, determine if this
|
|
// is the first instance of this task. If so, return TRUE, if not,
|
|
// restore the counter, and return FALSE.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
inline BOOL
|
|
BeginSingleInstanceTask( LONG *pcInstances )
|
|
{
|
|
LONG cInstances = InterlockedIncrement( pcInstances );
|
|
TrkAssert( 1 <= cInstances );
|
|
if( 1 < cInstances )
|
|
{
|
|
TrkLog(( TRKDBG_LOG, TEXT("Skipping single instance task (%d)"), cInstances-1 ));
|
|
#if DBG
|
|
TrkVerify( 0 <= InterlockedDecrement( pcInstances ));
|
|
#else
|
|
InterlockedDecrement( pcInstances );
|
|
#endif
|
|
return( FALSE );
|
|
}
|
|
else
|
|
return( TRUE );
|
|
}
|
|
|
|
// After a successful call to BeginSingleInstanceTask, the following
|
|
// should eventually be called.
|
|
|
|
inline void
|
|
EndSingleInstanceTask( LONG *pcInstances )
|
|
{
|
|
#if DBG
|
|
TrkVerify( 0 <= InterlockedDecrement( pcInstances ));
|
|
#else
|
|
InterlockedDecrement( pcInstances );
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CommonDllInit/CommonDllUnInit
|
|
//
|
|
// Services.exe never unloads service DLLs, so we can't count on dll init
|
|
// to init global non-const variables.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
inline void
|
|
CommonDllInit( LONG *pcServiceInstances )
|
|
{
|
|
// Ensure that we're the only copy of this service that has been started.
|
|
// If we're not, raise.
|
|
|
|
if( !BeginSingleInstanceTask( pcServiceInstances ))
|
|
{
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Two copies of service have been started") ));
|
|
TrkRaiseWin32Error( ERROR_SERVICE_ALREADY_RUNNING );
|
|
}
|
|
|
|
IFDBG( g_cThreadPoolThreads = g_cThreadPoolMaxThreads = g_cThreadPoolRegistrations = 0; )
|
|
g_fRestorePrivilegeEnabled = FALSE;
|
|
|
|
|
|
// Create the object that tracks all threads that are running
|
|
// in this service.
|
|
|
|
g_pActiveThreadList = new CActiveThreadList();
|
|
if( NULL != g_pActiveThreadList )
|
|
g_pActiveThreadList->Initialize();
|
|
|
|
#if DBG
|
|
if( NULL == g_pActiveThreadList )
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't alloc ActiveThreadList") ));
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
inline void
|
|
CommonDllUnInit( LONG *pcServiceInstances )
|
|
{
|
|
// Delete the list of active threads (should be empty by now).
|
|
|
|
// Make sure all thread pool threads go away. All are implemented
|
|
// so that they never run for a long time.
|
|
|
|
if( NULL != g_pActiveThreadList )
|
|
{
|
|
for( int i = 0; i < 5 && 1 < g_pActiveThreadList->GetCount(); i++ )
|
|
{
|
|
TrkLog(( TRKDBG_WARNING,
|
|
TEXT("Waiting for work items to complete (%d)"),
|
|
g_pActiveThreadList->GetCount() ));
|
|
Sleep( 1000 );
|
|
}
|
|
}
|
|
|
|
TrkAssert( NULL == g_pActiveThreadList || 0 == g_pActiveThreadList->GetCount() );
|
|
|
|
if( NULL != g_pActiveThreadList )
|
|
delete g_pActiveThreadList;
|
|
g_pActiveThreadList = NULL;
|
|
|
|
// Show that there is no instace of this service running.
|
|
|
|
EndSingleInstanceTask( pcServiceInstances );
|
|
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// CSvcCtrlInterface
|
|
//
|
|
// This class manages the SCM for both services. It implements
|
|
// the default ServiceHandler routine for the services, but
|
|
// calls to the service to do the service-specific work.
|
|
//
|
|
// ** Note ** There can only be once instance of this class
|
|
// per DLL, because of the static _fStoppedOrStopping member.
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
enum PROCESS_TYPE
|
|
{
|
|
STANDALONE_DEBUGGABLE_PROCESS,
|
|
SERVICE_PROCESS
|
|
};
|
|
|
|
// The services implement this callback to receive
|
|
// ServiceHandler calls
|
|
class IServiceHandler
|
|
{
|
|
public:
|
|
virtual DWORD ServiceHandler(DWORD dwControl,
|
|
DWORD dwEventType,
|
|
PVOID EventData,
|
|
PVOID pData) = 0;
|
|
};
|
|
|
|
#define DEFAULT_WAIT_HINT 30000
|
|
class CSvcCtrlInterface
|
|
{
|
|
|
|
private:
|
|
|
|
BOOL _fInitializeCalled:1;
|
|
|
|
// This flag is set if the service has received a service_stop
|
|
// or service_shutdown request. This is used by tasks that run for an extended
|
|
// period to determine if they should abort.
|
|
// This is static so that we can handle a PNP timing problem
|
|
// (see the comment in CSvcCtrlInterface::ServiceHandler).
|
|
|
|
static BOOL _fStoppedOrStopping;
|
|
|
|
// Service-specific callback
|
|
IServiceHandler * _pServiceHandler;
|
|
|
|
// Values to pass to the SCM on SetServiceStatus
|
|
DWORD _dwState;
|
|
DWORD _dwCheckPoint;
|
|
DWORD _dwControlsAccepted;
|
|
|
|
public:
|
|
|
|
inline CSvcCtrlInterface() : _fInitializeCalled(FALSE), _ssh(NULL) {}
|
|
inline ~CSvcCtrlInterface() { UnInitialize(); }
|
|
|
|
public:
|
|
|
|
void Initialize(const TCHAR *ptszServiceName, IServiceHandler *pServiceHandler);
|
|
inline void UnInitialize();
|
|
|
|
void SetServiceStatus(DWORD dwState, DWORD dwControlsAccepted, DWORD dwWin32ExitCode);
|
|
|
|
void UpdateWaitHint(DWORD dwMilliseconds);
|
|
|
|
friend DWORD WINAPI ServiceHandler(DWORD dwControl,
|
|
DWORD dwEventType,
|
|
PVOID EventData,
|
|
PVOID pData);
|
|
|
|
inline DWORD GetState();
|
|
|
|
inline const BOOL * GetStopFlagAddress() const;
|
|
inline BOOL IsStopping() const;
|
|
|
|
SERVICE_STATUS_HANDLE _ssh; // used for registering for PnP notification for volume mount/dismount, lock/unlock.
|
|
|
|
private:
|
|
|
|
static
|
|
DWORD ServiceHandler(DWORD dwControl,
|
|
DWORD dwEventType,
|
|
PVOID EventData,
|
|
PVOID pData);
|
|
|
|
};
|
|
|
|
inline DWORD
|
|
CSvcCtrlInterface::GetState()
|
|
{
|
|
return(_dwState);
|
|
}
|
|
|
|
inline const BOOL *
|
|
CSvcCtrlInterface::GetStopFlagAddress() const
|
|
{
|
|
return(&_fStoppedOrStopping);
|
|
}
|
|
|
|
inline BOOL
|
|
CSvcCtrlInterface::IsStopping() const
|
|
{
|
|
return _fStoppedOrStopping;
|
|
}
|
|
|
|
inline void
|
|
CSvcCtrlInterface::UnInitialize()
|
|
{
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// CNewTimer
|
|
//
|
|
// This class wraps the NT timer object. It adds the ability
|
|
// to make a timer persistent (using the registry), and to perform
|
|
// retries with random or exponential backoff (possibly subject
|
|
// to a max lifetime).
|
|
//
|
|
// When a CNewTimer is initialized, the caller provides a PTimerCallback
|
|
// pointer. The Timer method on this abstract base class is called
|
|
// when the NT timer fires.
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
class PTimerCallback
|
|
{
|
|
public:
|
|
|
|
// Enumeration of what the timer should do after it has
|
|
// fired and the Timer method has been called.
|
|
|
|
enum TimerContinuation
|
|
{
|
|
CONTINUE_TIMER, // Set a recurring timer again
|
|
BREAK_TIMER, // Stop the timer
|
|
RETRY_TIMER // Retry the timer
|
|
};
|
|
|
|
public:
|
|
virtual TimerContinuation Timer( ULONG ulTimerContext ) = 0;
|
|
};
|
|
|
|
|
|
EXTERN_C void __cdecl _tmain( int argc, TCHAR **argv ); // for ttimer test
|
|
|
|
class CNewTimer : public PWorkItem
|
|
{
|
|
|
|
public:
|
|
|
|
// Specifies how Retries should be handled.
|
|
enum TimerRetryType
|
|
{
|
|
NO_RETRY,
|
|
RETRY_RANDOMLY,
|
|
RETRY_WITH_BACKOFF
|
|
};
|
|
|
|
private:
|
|
|
|
// For persistent timers, the following structure is saved to the
|
|
// registry.
|
|
|
|
struct PersistentState
|
|
{
|
|
CFILETIME cftSet; // When the timer was set
|
|
CFILETIME cftDue; // When it's next due
|
|
ULONG ulCurrentRetryTime; // If applicable, the current retry value
|
|
};
|
|
|
|
|
|
private:
|
|
|
|
BOOL _fInitializeCalled:1; // Critical section has be initialized
|
|
BOOL _fRunning:1; // Timer is running
|
|
BOOL _fRecurring:1; // SetRecurring was called (not SetSingleShot)
|
|
BOOL _fTimerSignalInProgress:1; // We're in the call to pTimerCallback->Timer
|
|
|
|
#if DBG
|
|
mutable LONG _cLocks;
|
|
#endif
|
|
|
|
mutable CCriticalSection _cs; // Protects this class
|
|
HANDLE _hTimer; // The NT timer
|
|
const TCHAR * _ptszName; // If non-null, this is persistent timer
|
|
TimerRetryType _RetryType; // How to handle Retries
|
|
|
|
// Cookie from RegisterWaitForSingleObjectEx
|
|
HANDLE _hRegisterWaitForSingleObjectEx;
|
|
|
|
PTimerCallback * _pTimerCallback; // Who to call in DoWork
|
|
ULONG _ulTimerContext; // Passed to timer callback
|
|
|
|
// All the following are in seconds
|
|
ULONG _ulPeriodInSeconds;
|
|
ULONG _ulLowerRetryTime;
|
|
ULONG _ulUpperRetryTime;
|
|
ULONG _ulMaxLifetime;
|
|
ULONG _ulCurrentRetryTime;
|
|
|
|
CFILETIME _cftDue; // When the timer is due
|
|
CFILETIME _cftSet; // When the timer was set
|
|
|
|
public:
|
|
|
|
inline CNewTimer();
|
|
inline ~CNewTimer() { UnInitialize(); }
|
|
|
|
public:
|
|
|
|
void Initialize( PTimerCallback *pTimerCallback,
|
|
const TCHAR *ptszName,
|
|
ULONG ulTimerContext,
|
|
ULONG ulPeriodInSeconds,
|
|
TimerRetryType retrytype,
|
|
ULONG ulLowerRetryTime,
|
|
ULONG ulUpperRetryTime,
|
|
ULONG ulMaxLifetime );
|
|
void UnInitialize();
|
|
|
|
|
|
public:
|
|
|
|
// Start the timer, and when it fires it's done; it doesn't get reset.
|
|
void SetSingleShot();
|
|
|
|
// Start the timer, and when it fires, start it again.
|
|
void SetRecurring();
|
|
|
|
// Change the period on the timer.
|
|
inline void ReInitialize( ULONG ulPeriodInSeconds );
|
|
|
|
#if DBG
|
|
// Show the state of the timer (for debug)
|
|
void DebugStringize( ULONG cch, TCHAR *ptsz ) const;
|
|
#endif
|
|
|
|
|
|
// Stop the timer.
|
|
inline void Cancel( );
|
|
|
|
// When was the timer originally due to expire?
|
|
inline CFILETIME QueryOriginalDueTime( ) const;
|
|
|
|
// What is the period of this timer?
|
|
inline ULONG QueryPeriodInSeconds() const;
|
|
|
|
// PWorkItem overload: called by the thread pool when the timer handle signals
|
|
void DoWork();
|
|
|
|
// Is this a recurring timer?
|
|
inline BOOL IsRecurring() const;
|
|
|
|
// Does this use exponential, random, or no retries?
|
|
inline TimerRetryType
|
|
GetRetryType() const;
|
|
|
|
private:
|
|
|
|
inline void Lock() const;
|
|
inline void Unlock() const;
|
|
|
|
#if DBG
|
|
inline LONG GetLockCount() const;
|
|
#endif
|
|
inline const TCHAR *GetTimerName() const; // Dbg only
|
|
|
|
|
|
void SetTimer();
|
|
|
|
void SaveToRegistry();
|
|
void LoadFromRegistry();
|
|
void RemoveFromRegistry();
|
|
|
|
|
|
// Test friends
|
|
|
|
friend BOOL IsRegistryEntryExtant();
|
|
friend BOOL IsRegistryEntryCorrect( const CFILETIME &cftExpected );
|
|
friend void __cdecl _tmain( int argc, TCHAR **argv );
|
|
friend class CTimerTest;
|
|
friend class CTimerTest1;
|
|
friend class CTimerTest2;
|
|
friend class CTimerTest3;
|
|
friend class CTimerTest4;
|
|
friend class CTimerTest5;
|
|
friend class CTimerTest6;
|
|
|
|
};
|
|
|
|
|
|
|
|
inline
|
|
CNewTimer::CNewTimer() : _cftDue(0), _cftSet(0)
|
|
{
|
|
_fInitializeCalled = _fTimerSignalInProgress = FALSE;
|
|
_cftDue = 0;
|
|
_cftSet = 0;
|
|
_fRunning = _fRecurring = FALSE;
|
|
_hTimer = NULL;
|
|
_ptszName = NULL;
|
|
_ulPeriodInSeconds = 0;
|
|
_ulLowerRetryTime = _ulUpperRetryTime = _ulMaxLifetime = 0;
|
|
_ulCurrentRetryTime = 0;
|
|
_pTimerCallback = NULL;
|
|
_ulTimerContext = 0;
|
|
_hRegisterWaitForSingleObjectEx = NULL;
|
|
|
|
IFDBG( _cLocks = 0 );
|
|
}
|
|
|
|
|
|
#if DBG
|
|
inline LONG
|
|
CNewTimer::GetLockCount() const
|
|
{
|
|
return( _cLocks );
|
|
}
|
|
#endif
|
|
|
|
inline const TCHAR *
|
|
CNewTimer::GetTimerName() const
|
|
{
|
|
return (NULL == _ptszName) ? TEXT("") : _ptszName;
|
|
}
|
|
|
|
|
|
inline CFILETIME
|
|
CNewTimer::QueryOriginalDueTime() const
|
|
{
|
|
// When was this timer originally due? We can't look at _cftDue,
|
|
// because we could be retry mode.
|
|
|
|
TrkAssert( _fInitializeCalled );
|
|
CFILETIME cft = _cftSet;
|
|
cft.IncrementSeconds( _ulPeriodInSeconds );
|
|
return(cft);
|
|
}
|
|
|
|
inline ULONG
|
|
CNewTimer::QueryPeriodInSeconds() const
|
|
{
|
|
TrkAssert( _fInitializeCalled );
|
|
return _ulPeriodInSeconds;
|
|
}
|
|
|
|
|
|
inline void
|
|
CNewTimer::Lock() const
|
|
{
|
|
TrkAssert( _fInitializeCalled );
|
|
_cs.Enter();
|
|
IFDBG( _cLocks++ );
|
|
}
|
|
|
|
inline void
|
|
CNewTimer::Unlock() const
|
|
{
|
|
TrkAssert( _fInitializeCalled );
|
|
IFDBG( _cLocks-- );
|
|
_cs.Leave();
|
|
}
|
|
|
|
inline CNewTimer::TimerRetryType
|
|
CNewTimer::GetRetryType() const
|
|
{
|
|
return( _RetryType );
|
|
}
|
|
|
|
inline BOOL
|
|
CNewTimer::IsRecurring() const
|
|
{
|
|
return( _fRecurring );
|
|
}
|
|
|
|
|
|
inline void
|
|
CNewTimer::SetRecurring()
|
|
{
|
|
// Start the timer, and record that it should
|
|
// be started again after firing.
|
|
|
|
TrkAssert( _fInitializeCalled );
|
|
|
|
Lock();
|
|
IFDBG( LONG cLocks = GetLockCount(); )
|
|
__try
|
|
{
|
|
_fRecurring = TRUE;
|
|
SetTimer();
|
|
}
|
|
__finally
|
|
{
|
|
TrkAssert( GetLockCount() == cLocks );
|
|
Unlock();
|
|
}
|
|
}
|
|
|
|
inline void
|
|
CNewTimer::SetSingleShot()
|
|
{
|
|
// Start the timer, and record that it should not start
|
|
// again after firing.
|
|
|
|
TrkAssert( _fInitializeCalled );
|
|
|
|
Lock();
|
|
IFDBG( LONG cLocks = GetLockCount(); )
|
|
__try
|
|
{
|
|
_fRecurring = FALSE;
|
|
SetTimer();
|
|
}
|
|
__finally
|
|
{
|
|
TrkAssert( GetLockCount() == cLocks );
|
|
Unlock();
|
|
}
|
|
}
|
|
|
|
inline void
|
|
CNewTimer::ReInitialize( ULONG ulPeriodInSeconds )
|
|
{
|
|
TrkAssert( _fInitializeCalled );
|
|
|
|
Lock();
|
|
__try
|
|
{
|
|
_ulPeriodInSeconds = ulPeriodInSeconds;
|
|
Cancel();
|
|
}
|
|
__finally
|
|
{
|
|
Unlock();
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// PShareMerit
|
|
//
|
|
// This abstract base class should be used by classes which can
|
|
// calculate the merit of a share. A share's merit is a linear
|
|
// value which accounts for the access on the share (more is better),
|
|
// a share's hidden-ness (visible is better), and the share's coverage
|
|
// of the volume (more is better).
|
|
//
|
|
// These share attributes are mapped onto a Merit value in the form
|
|
// of:
|
|
//
|
|
// 0x00AAHCCC
|
|
//
|
|
// where:
|
|
//
|
|
// "AA" represents the access level
|
|
// "H" represents the hidden-ness
|
|
// "CC" represents the coverage of the disk
|
|
//
|
|
// This is structured so as to give highest priority to the access
|
|
// level, next highest priority to the hidden-ness, and least
|
|
// priority to the share's coverage of the volume. The coverage
|
|
// of the volume is represented as 0xFFF minus the share path's
|
|
// length.
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
|
|
// This class prototype maps the various aspects of a share
|
|
// into a linear measure of its worthiness.
|
|
|
|
class PShareMerit
|
|
{
|
|
|
|
public:
|
|
PShareMerit() {};
|
|
~PShareMerit() {};
|
|
|
|
public:
|
|
|
|
// The access level is the most significant attribute.
|
|
|
|
enum enumAccessLevels
|
|
{
|
|
AL_UNKNOWN = 0x00010000,
|
|
AL_NO_ACCESS = 0x00020000,
|
|
AL_WRITE_ACCESS = 0x00030000,
|
|
AL_READ_ACCESS = 0x00040000,
|
|
AL_READ_WRITE_ACCESS = 0x00050000,
|
|
AL_FULL_ACCESS = 0x00060000
|
|
};
|
|
|
|
// The hidden-ness is the second most significant
|
|
// attribute
|
|
|
|
enum enumHiddenStates
|
|
{
|
|
HS_UNKNOWN = 0x00001000,
|
|
HS_HIDDEN = 0x00002000,
|
|
HS_VISIBLE = 0x00003000
|
|
};
|
|
|
|
// The volume coverage is the least significant
|
|
// attribute
|
|
|
|
enum enumSharePathCoverage
|
|
{
|
|
SPC_MAX_COVERAGE = 0x00000fff
|
|
};
|
|
|
|
|
|
public:
|
|
|
|
virtual ULONG GetMerit() = 0;
|
|
|
|
// The minimum merit is guaranteed to be >= 1
|
|
inline ULONG GetMinimumMerit()
|
|
{
|
|
return( AL_WRITE_ACCESS | HS_HIDDEN );
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// CShareEnumerator
|
|
//
|
|
// This class represents the enumeration of a network share. It
|
|
// can be queried for attributes of a share (share name, covered
|
|
// path, etc.).
|
|
//
|
|
// Note that each of these methods may raise an exception.
|
|
//
|
|
// Also note that this implementation is LanMan-specific. When/if we
|
|
// add support for Netware shares, we should rename this class to
|
|
// CLMShareEnumerator, and add two new classes - CNWShareEnumerator,
|
|
// and CShareEnumerator (which would wrap the other two).
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
class CShareEnumerator : public PShareMerit, public CTrkRpcConfig
|
|
{
|
|
|
|
// ------------
|
|
// Constructors
|
|
// ------------
|
|
|
|
public:
|
|
|
|
CShareEnumerator()
|
|
{
|
|
InitLocals();
|
|
}
|
|
|
|
inline ~CShareEnumerator()
|
|
{
|
|
UnInitialize();
|
|
};
|
|
|
|
|
|
// --------------
|
|
// Public Methods
|
|
// --------------
|
|
|
|
public:
|
|
|
|
VOID Initialize( RPC_BINDING_HANDLE IDL_handle, const TCHAR *ptszMachineName = NULL );
|
|
VOID UnInitialize();
|
|
VOID InitLocals()
|
|
{
|
|
_fInitialized = FALSE;
|
|
_cchSharePath = 0;
|
|
_cEntries = 0;
|
|
_enumHiddenState = HS_UNKNOWN;
|
|
_iCurrentEntry = 0;
|
|
_IDL_handle = NULL;
|
|
_prgshare_info = NULL;
|
|
_tszMachineName[0] = TEXT('\0');
|
|
}
|
|
|
|
|
|
BOOL Next();
|
|
|
|
inline const TCHAR *GetMachineName() const;
|
|
inline const TCHAR *GetShareName() const;
|
|
inline const TCHAR *GetSharePath();
|
|
inline ULONG QueryCCHSharePath();
|
|
|
|
BOOL CoversDrivePath( const TCHAR *ptszDrivePath );
|
|
BOOL GenerateUNCPath( TCHAR *ptszUNCPath, const TCHAR * ptszDrivePath );
|
|
|
|
// Override the PShareMerit method.
|
|
ULONG GetMerit();
|
|
|
|
// ---------------
|
|
// Private Methods
|
|
// ---------------
|
|
|
|
private:
|
|
|
|
VOID _ClearCache();
|
|
BOOL _IsValidShare();
|
|
BOOL _IsHiddenShare();
|
|
BOOL _IsAdminShare();
|
|
enumAccessLevels _GetAccessLevel();
|
|
void _AbsoluteSDHelper( const PSECURITY_DESCRIPTOR pSDRelative,
|
|
PSECURITY_DESCRIPTOR *ppSDAbs, ULONG *pcbSDAbs,
|
|
PACL *ppDaclAbs, ULONG *pcbDaclAbs,
|
|
PACL *ppSaclAbs, ULONG *pcbSaclAbs,
|
|
PSID *ppSidOwnerAbs, ULONG *pcbSidOwnerAbs,
|
|
PSID *ppSidGroupAbs, ULONG *pcbSidGroupAbs );
|
|
|
|
// ------------
|
|
// Private Data
|
|
// ------------
|
|
|
|
private:
|
|
|
|
BOOL _fInitialized;
|
|
|
|
ULONG _cEntries;
|
|
ULONG _iCurrentEntry;
|
|
SHARE_INFO_502 *_prgshare_info;
|
|
TCHAR _tszMachineName[ MAX_PATH + 1 ];
|
|
ULONG _cchSharePath;
|
|
ULONG _ulMerit;
|
|
enumHiddenStates _enumHiddenState;
|
|
RPC_BINDING_HANDLE _IDL_handle;
|
|
|
|
}; // class CShareEnumerator
|
|
|
|
|
|
// ---------------------------------
|
|
// CShareEnumerator::QueryCCHSharePath
|
|
// ---------------------------------
|
|
|
|
// Determine the character-count of the share's path, and
|
|
// cache a copy for subsequent calls.
|
|
|
|
inline ULONG
|
|
CShareEnumerator::QueryCCHSharePath()
|
|
{
|
|
if( (ULONG) -1 == _cchSharePath )
|
|
_cchSharePath = _tcslen( GetSharePath() );
|
|
|
|
return( _cchSharePath );
|
|
}
|
|
|
|
// --------------------------------
|
|
// CShareEnumerator::GetMachineName
|
|
// --------------------------------
|
|
|
|
inline const TCHAR *
|
|
CShareEnumerator::GetMachineName() const
|
|
{
|
|
return( _tszMachineName );
|
|
}
|
|
|
|
|
|
// ------------------------------
|
|
// CShareEnumerator::GetShareName
|
|
// ------------------------------
|
|
|
|
// Return the share's name from the current entry
|
|
// in the SHARE_INFO structure.
|
|
|
|
inline const TCHAR *
|
|
CShareEnumerator::GetShareName() const
|
|
{
|
|
TrkAssert( _fInitialized );
|
|
TrkAssert( _iCurrentEntry < _cEntries );
|
|
return( _prgshare_info[ _iCurrentEntry ].shi502_netname );
|
|
}
|
|
|
|
// ------------------------------
|
|
// CShareEnumerator::GetSharePath
|
|
// ------------------------------
|
|
|
|
// Get the path name covered by the share from the
|
|
// SHARE_INFO structure.
|
|
|
|
inline const TCHAR *
|
|
CShareEnumerator::GetSharePath()
|
|
{
|
|
TrkAssert( _fInitialized );
|
|
TrkAssert( _iCurrentEntry < _cEntries );
|
|
|
|
return( _prgshare_info[ _iCurrentEntry ].shi502_path );
|
|
|
|
}
|
|
|
|
|
|
|
|
enum RC_AUTHENTICATE
|
|
{
|
|
NO_AUTHENTICATION,
|
|
PRIVACY_AUTHENTICATION,
|
|
INTEGRITY_AUTHENTICATION
|
|
};
|
|
|
|
|
|
const extern TCHAR s_tszTrkWksLocalRpcProtocol[] INIT( TEXT("ncalrpc") );
|
|
const extern TCHAR s_tszTrkWksLocalRpcEndPoint[] INIT( TRKWKS_LRPC_ENDPOINT_NAME );
|
|
|
|
const extern TCHAR s_tszTrkWksRemoteRpcProtocol[] INIT( TEXT("ncacn_np") );
|
|
const extern TCHAR s_tszTrkWksRemoteRpcEndPoint[] INIT( TEXT("\\pipe\\trkwks") );
|
|
const extern TCHAR s_tszTrkWksRemoteRpcEndPointOld[] INIT( TEXT("\\pipe\\ntsvcs") );
|
|
const extern TCHAR s_tszTrkSvrRpcProtocol[] INIT( TEXT("ncacn_ip_tcp") );
|
|
const extern TCHAR s_tszTrkSvrRpcEndPoint[] INIT( TEXT("") );
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CRpcClientBinding
|
|
//
|
|
// This class represents an RPC client-side binding handle. The
|
|
// RcInitialize method creates the binding handle, and optionally sets
|
|
// the appropriate security.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
class CRpcClientBinding : public CTrkRpcConfig
|
|
{
|
|
|
|
private:
|
|
|
|
BOOL _fBound;
|
|
RPC_BINDING_HANDLE _BindingHandle;
|
|
|
|
public:
|
|
|
|
inline CRpcClientBinding()
|
|
{
|
|
_fBound = FALSE;
|
|
}
|
|
|
|
~CRpcClientBinding()
|
|
{
|
|
UnInitialize();
|
|
}
|
|
|
|
void RcInitialize(const CMachineId &mcid,
|
|
const TCHAR *ptszRpcProtocol = s_tszTrkWksRemoteRpcProtocol,
|
|
const TCHAR *ptszRpcEndPoint = s_tszTrkWksRemoteRpcEndPoint,
|
|
RC_AUTHENTICATE auth = INTEGRITY_AUTHENTICATION );
|
|
|
|
void UnInitialize();
|
|
inline BOOL IsConnected();
|
|
inline operator RPC_BINDING_HANDLE () const;
|
|
|
|
|
|
};
|
|
|
|
inline BOOL
|
|
CRpcClientBinding::IsConnected()
|
|
{
|
|
return(_fBound);
|
|
}
|
|
|
|
inline
|
|
CRpcClientBinding::operator RPC_BINDING_HANDLE () const
|
|
{
|
|
TrkAssert(_fBound);
|
|
return(_BindingHandle);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
//
|
|
// CTrkRegistryKey
|
|
//
|
|
// This class represents the registry key for this service
|
|
// (either trkwks or trksvr). Use its methods to read/write
|
|
// registry values.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
class CTrkRegistryKey
|
|
{
|
|
|
|
private:
|
|
|
|
HKEY _hkey;
|
|
CCriticalSection _cs;
|
|
|
|
public:
|
|
|
|
inline CTrkRegistryKey();
|
|
inline ~CTrkRegistryKey();
|
|
|
|
public:
|
|
|
|
void Initialize();
|
|
LONG SetDword( const TCHAR *ptszName, DWORD dw );
|
|
LONG GetDword( const TCHAR *ptszName, DWORD *pdwRead, DWORD dwDefault );
|
|
LONG Delete( const TCHAR *ptszName );
|
|
|
|
private:
|
|
|
|
inline LONG Open();
|
|
inline void Close();
|
|
|
|
};
|
|
|
|
|
|
inline
|
|
CTrkRegistryKey::CTrkRegistryKey()
|
|
{
|
|
_hkey = NULL;
|
|
}
|
|
|
|
inline void
|
|
CTrkRegistryKey::Initialize()
|
|
{
|
|
_cs.Initialize();
|
|
}
|
|
|
|
inline
|
|
CTrkRegistryKey::~CTrkRegistryKey()
|
|
{
|
|
Close();
|
|
_cs.UnInitialize();
|
|
}
|
|
|
|
inline void
|
|
CTrkRegistryKey::Close()
|
|
{
|
|
if( NULL != _hkey )
|
|
{
|
|
RegCloseKey( _hkey );
|
|
_hkey = NULL;
|
|
}
|
|
}
|
|
|
|
inline LONG
|
|
CTrkRegistryKey::Open()
|
|
{
|
|
if( NULL != _hkey )
|
|
return( ERROR_SUCCESS );
|
|
else
|
|
return( RegOpenKey( HKEY_LOCAL_MACHINE, s_tszKeyNameLinkTrack, &_hkey ));
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// CRegBoolParameter
|
|
//
|
|
// This class represents a bool parameter stored in the registry.
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
|
|
class CRegBoolParameter : private CTrkRegistryKey
|
|
{
|
|
private:
|
|
|
|
BOOL _fSet:1;
|
|
|
|
// We're fully initialized when Initialize() has been called,
|
|
// and Set, Clear, or IsSet has been called.
|
|
BOOL _fFullyInitialized:1;
|
|
|
|
const TCHAR *_ptszName;
|
|
|
|
public:
|
|
|
|
inline CRegBoolParameter( const TCHAR *ptszName );
|
|
inline void Initialize();
|
|
|
|
public:
|
|
|
|
inline HRESULT Set();
|
|
inline HRESULT Clear();
|
|
inline BOOL IsSet();
|
|
|
|
};
|
|
|
|
inline
|
|
CRegBoolParameter::CRegBoolParameter( const TCHAR *ptszName )
|
|
{
|
|
_ptszName = ptszName;
|
|
_fSet = _fFullyInitialized = FALSE;
|
|
}
|
|
|
|
inline HRESULT
|
|
CRegBoolParameter::Set()
|
|
{
|
|
HRESULT hr = SetDword( _ptszName, TRUE );
|
|
if( ERROR_SUCCESS != hr )
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
|
|
_fFullyInitialized = TRUE;
|
|
_fSet = TRUE;
|
|
|
|
return( hr );
|
|
}
|
|
|
|
inline HRESULT
|
|
CRegBoolParameter::Clear()
|
|
{
|
|
HRESULT hr = Delete( _ptszName );
|
|
if( ERROR_SUCCESS != hr )
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
|
|
_fFullyInitialized = TRUE;
|
|
_fSet = FALSE;
|
|
|
|
return( hr );
|
|
}
|
|
|
|
inline BOOL
|
|
CRegBoolParameter::IsSet()
|
|
{
|
|
if( !_fFullyInitialized )
|
|
{
|
|
DWORD dw;
|
|
LONG lRet = GetDword( _ptszName, &dw, FALSE );
|
|
TrkAssert( ERROR_SUCCESS == lRet );
|
|
|
|
_fSet = dw;
|
|
}
|
|
|
|
return( _fSet );
|
|
}
|
|
|
|
inline void
|
|
CRegBoolParameter::Initialize()
|
|
{
|
|
CTrkRegistryKey::Initialize();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// CRegDwordParameter
|
|
//
|
|
// This class represents a DWORD parameter stored in the registry.
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
|
|
class CRegDwordParameter : private CTrkRegistryKey
|
|
{
|
|
private:
|
|
|
|
DWORD _dwValue;
|
|
|
|
// We're fully initialized when Initialize() has been called,
|
|
// and Set, Clear, GetValue has been called.
|
|
BOOL _fFullyInitialized:1;
|
|
|
|
const TCHAR *_ptszName;
|
|
|
|
public:
|
|
|
|
inline CRegDwordParameter( const TCHAR *ptszName );
|
|
inline void Initialize();
|
|
|
|
public:
|
|
|
|
inline HRESULT Set( DWORD dw );
|
|
inline HRESULT Clear();
|
|
inline DWORD GetValue();
|
|
|
|
};
|
|
|
|
inline
|
|
CRegDwordParameter::CRegDwordParameter( const TCHAR *ptszName )
|
|
{
|
|
_dwValue = 0;
|
|
_ptszName = ptszName;
|
|
_fFullyInitialized = FALSE;
|
|
}
|
|
|
|
inline HRESULT
|
|
CRegDwordParameter::Set( DWORD dw)
|
|
{
|
|
HRESULT hr = SetDword( _ptszName, dw );
|
|
if( ERROR_SUCCESS != hr )
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
|
|
_fFullyInitialized = TRUE;
|
|
_dwValue = dw;
|
|
|
|
return( hr );
|
|
}
|
|
|
|
inline HRESULT
|
|
CRegDwordParameter::Clear()
|
|
{
|
|
HRESULT hr = Delete( _ptszName );
|
|
if( ERROR_SUCCESS != hr )
|
|
hr = HRESULT_FROM_WIN32(hr);
|
|
|
|
_fFullyInitialized = TRUE;
|
|
_dwValue = 0;
|
|
|
|
return( hr );
|
|
}
|
|
|
|
inline DWORD
|
|
CRegDwordParameter::GetValue()
|
|
{
|
|
if( !_fFullyInitialized )
|
|
{
|
|
DWORD dw = 0;
|
|
LONG lRet = GetDword( _ptszName, &dw, FALSE );
|
|
TrkAssert( ERROR_SUCCESS == lRet );
|
|
|
|
_fFullyInitialized = TRUE;
|
|
_dwValue = dw;
|
|
}
|
|
|
|
return( _dwValue );
|
|
}
|
|
|
|
inline void
|
|
CRegDwordParameter::Initialize()
|
|
{
|
|
CTrkRegistryKey::Initialize();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CSID //
|
|
// //
|
|
//-------------------------------------------------------------------//
|
|
|
|
// Wrapper class for NT Security IDs
|
|
|
|
class CSID
|
|
{
|
|
public:
|
|
|
|
CSID()
|
|
{
|
|
memset( this, 0, sizeof(*this) );
|
|
}
|
|
|
|
~CSID()
|
|
{
|
|
UnInitialize();
|
|
}
|
|
|
|
public:
|
|
|
|
// Standard Authorities
|
|
|
|
enum enumCSIDAuthority
|
|
{
|
|
CSID_NT_AUTHORITY = 0
|
|
};
|
|
|
|
public:
|
|
|
|
// The primary Initialize method (takes a SID count as the first parameter).
|
|
|
|
VOID Initialize( enumCSIDAuthority enumcsidAuthority,
|
|
BYTE cSubAuthorities ,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3,
|
|
DWORD dwSubAuthority4,
|
|
DWORD dwSubAuthority5,
|
|
DWORD dwSubAuthority6,
|
|
DWORD dwSubAuthority7 );
|
|
|
|
// Helper Initialize methods (these do not take a SID count).
|
|
|
|
inline VOID Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3,
|
|
DWORD dwSubAuthority4,
|
|
DWORD dwSubAuthority5,
|
|
DWORD dwSubAuthority6,
|
|
DWORD dwSubAuthority7 );
|
|
|
|
inline VOID Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3,
|
|
DWORD dwSubAuthority4,
|
|
DWORD dwSubAuthority5,
|
|
DWORD dwSubAuthority6 );
|
|
|
|
inline VOID Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3,
|
|
DWORD dwSubAuthority4,
|
|
DWORD dwSubAuthority5 );
|
|
|
|
inline VOID Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3,
|
|
DWORD dwSubAuthority4 );
|
|
|
|
inline VOID Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3 );
|
|
|
|
inline VOID Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2 );
|
|
|
|
inline VOID Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1 );
|
|
|
|
inline VOID Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0 );
|
|
|
|
VOID UnInitialize();
|
|
|
|
inline operator const PSID();
|
|
|
|
|
|
private:
|
|
|
|
BOOL _fInitialized;
|
|
PSID _psid;
|
|
|
|
};
|
|
|
|
|
|
// ------------------------
|
|
// CSID::Initialize methods
|
|
// ------------------------
|
|
|
|
// Each of these methods simply calls the primary Initialize
|
|
// method, with the appropriate cSID value.
|
|
|
|
inline VOID
|
|
CSID::Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3,
|
|
DWORD dwSubAuthority4,
|
|
DWORD dwSubAuthority5,
|
|
DWORD dwSubAuthority6,
|
|
DWORD dwSubAuthority7 )
|
|
{
|
|
Initialize( csidAuthority, 8,
|
|
dwSubAuthority0, dwSubAuthority1, dwSubAuthority2, dwSubAuthority3,
|
|
dwSubAuthority4, dwSubAuthority5, dwSubAuthority6, dwSubAuthority7 );
|
|
}
|
|
|
|
inline VOID
|
|
CSID::Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3,
|
|
DWORD dwSubAuthority4,
|
|
DWORD dwSubAuthority5,
|
|
DWORD dwSubAuthority6 )
|
|
{
|
|
Initialize( csidAuthority, 7,
|
|
dwSubAuthority0, dwSubAuthority1, dwSubAuthority2, dwSubAuthority3,
|
|
dwSubAuthority4, dwSubAuthority5, dwSubAuthority6, 0 );
|
|
}
|
|
|
|
inline VOID
|
|
CSID::Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3,
|
|
DWORD dwSubAuthority4,
|
|
DWORD dwSubAuthority5 )
|
|
{
|
|
Initialize( csidAuthority, 6,
|
|
dwSubAuthority0, dwSubAuthority1, dwSubAuthority2, dwSubAuthority3,
|
|
dwSubAuthority4, dwSubAuthority5, 0, 0 );
|
|
}
|
|
|
|
inline VOID
|
|
CSID::Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3,
|
|
DWORD dwSubAuthority4 )
|
|
{
|
|
Initialize( csidAuthority, 5,
|
|
dwSubAuthority0, dwSubAuthority1, dwSubAuthority2, dwSubAuthority3,
|
|
dwSubAuthority4, 0, 0, 0 );
|
|
}
|
|
|
|
|
|
inline VOID
|
|
CSID::Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2,
|
|
DWORD dwSubAuthority3 )
|
|
{
|
|
Initialize( csidAuthority, 4,
|
|
dwSubAuthority0, dwSubAuthority1, dwSubAuthority2, dwSubAuthority3,
|
|
0, 0, 0, 0);
|
|
}
|
|
|
|
inline VOID
|
|
CSID::Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1,
|
|
DWORD dwSubAuthority2 )
|
|
{
|
|
Initialize( csidAuthority, 3,
|
|
dwSubAuthority0, dwSubAuthority1, dwSubAuthority2, 0, 0, 0, 0, 0 );
|
|
}
|
|
|
|
inline VOID
|
|
CSID::Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0,
|
|
DWORD dwSubAuthority1 )
|
|
{
|
|
Initialize( csidAuthority, 2,
|
|
dwSubAuthority0, dwSubAuthority1, 0, 0, 0, 0, 0, 0 );
|
|
}
|
|
|
|
inline VOID
|
|
CSID::Initialize( enumCSIDAuthority csidAuthority,
|
|
DWORD dwSubAuthority0 )
|
|
{
|
|
Initialize( csidAuthority, 1,
|
|
dwSubAuthority0, 0, 0, 0, 0, 0, 0, 0 );
|
|
}
|
|
|
|
// -------------------
|
|
// CSID::OPERATOR PSID
|
|
// -------------------
|
|
|
|
inline
|
|
CSID::operator const PSID()
|
|
{
|
|
return( _psid );
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------//
|
|
//
|
|
// CACL
|
|
//
|
|
// This class wraps a DACL, providing methods to add ACEs,
|
|
// and a conversion operator to get the PACL. The current
|
|
// implementation only supports a 128 byte buffer size.
|
|
//
|
|
//-------------------------------------------------------------------//
|
|
|
|
// Wrapper for an Access Control List
|
|
|
|
#define MIN_ACL_SIZE 128
|
|
|
|
class CACL
|
|
{
|
|
public:
|
|
|
|
CACL()
|
|
{
|
|
memset( this, 0, sizeof(*this) );
|
|
}
|
|
|
|
~CACL()
|
|
{
|
|
UnInitialize();
|
|
}
|
|
|
|
public:
|
|
|
|
VOID Initialize();
|
|
VOID UnInitialize();
|
|
|
|
//VOID SetSize( const PSID psid );
|
|
operator const PACL() const;
|
|
|
|
inline VOID SetDirty();
|
|
inline VOID ClearDirty();
|
|
inline BOOL IsDirty();
|
|
inline BOOL IsInitialized();
|
|
|
|
inline BOOL AddAccessAllowed( ACCESS_MASK access_mask, const PSID psid );
|
|
inline BOOL AddAccessDenied( ACCESS_MASK access_mask, const PSID psid );
|
|
|
|
private:
|
|
|
|
PACL _pacl;
|
|
ULONG _cbacl;
|
|
BOOL _fInitialized:1;
|
|
BOOL _fDirty:1;
|
|
|
|
};
|
|
|
|
// ----------------------
|
|
// CACL Dirty Bit methods
|
|
// ----------------------
|
|
|
|
inline VOID
|
|
CACL::SetDirty()
|
|
{
|
|
_fDirty = TRUE;
|
|
}
|
|
|
|
inline VOID
|
|
CACL::ClearDirty()
|
|
{
|
|
_fDirty = FALSE;
|
|
}
|
|
|
|
inline BOOL
|
|
CACL::IsDirty()
|
|
{
|
|
return( _fDirty );
|
|
}
|
|
|
|
// -------------------
|
|
// CACL::IsInitialized
|
|
// -------------------
|
|
|
|
inline BOOL
|
|
CACL::IsInitialized()
|
|
{
|
|
return( _fInitialized );
|
|
}
|
|
|
|
// -------------------------
|
|
// CACL::operator const PACL
|
|
// -------------------------
|
|
|
|
inline
|
|
CACL::operator const PACL() const
|
|
{
|
|
return( _pacl );
|
|
}
|
|
|
|
// --------------------
|
|
// CACL Add ACE methods
|
|
// --------------------
|
|
|
|
inline BOOL
|
|
CACL::AddAccessAllowed( ACCESS_MASK access_mask, const PSID psid )
|
|
{
|
|
TrkAssert( _fInitialized );
|
|
return( AddAccessAllowedAce( _pacl, ACL_REVISION, access_mask, const_cast<PSID>(psid) ));
|
|
}
|
|
|
|
inline BOOL
|
|
CACL::AddAccessDenied( ACCESS_MASK access_mask, const PSID psid )
|
|
{
|
|
TrkAssert( _fInitialized );
|
|
return( AddAccessDeniedAce( _pacl, ACL_REVISION, access_mask, const_cast<PSID>(psid) ));
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CSecDescriptor
|
|
//
|
|
// Wrapper class for a security descriptor.
|
|
// //
|
|
//-------------------------------------------------------------------//
|
|
|
|
class CSecDescriptor
|
|
{
|
|
|
|
// ------------
|
|
// Construction
|
|
// ------------
|
|
|
|
public:
|
|
|
|
CSecDescriptor()
|
|
{
|
|
memset( this, 0, sizeof(*this) );
|
|
}
|
|
|
|
~CSecDescriptor()
|
|
{
|
|
UnInitialize();
|
|
}
|
|
|
|
public:
|
|
|
|
// Types of ACEs
|
|
enum enumAccessType
|
|
{
|
|
AT_ACCESS_ALLOWED,
|
|
AT_ACCESS_DENIED
|
|
};
|
|
|
|
// Types of ACLs
|
|
enum enumAclType
|
|
{
|
|
ACL_IS_SACL, // System (Audit) ACL
|
|
ACL_IS_DACL // Discretionary ACL
|
|
};
|
|
|
|
|
|
public:
|
|
|
|
VOID Initialize();
|
|
VOID UnInitialize();
|
|
|
|
void AddAce( const enumAclType AclType, const enumAccessType AccessType, const ACCESS_MASK access_mask, const PSID psid );
|
|
|
|
inline operator const PSECURITY_DESCRIPTOR();
|
|
|
|
|
|
private:
|
|
|
|
VOID _Allocate( ULONG cb );
|
|
VOID _ReloadAcl( enumAclType AclType );
|
|
|
|
private:
|
|
|
|
BOOL _fInitialized;
|
|
|
|
PSECURITY_DESCRIPTOR _psd;
|
|
|
|
CACL _cDacl;
|
|
CACL _cSacl;
|
|
};
|
|
|
|
|
|
|
|
// ----------------------------------------------------------
|
|
// CSecDescriptor::operator const PSECURITY_DESCRIPTOR()
|
|
// ----------------------------------------------------------
|
|
|
|
inline
|
|
CSecDescriptor::operator const PSECURITY_DESCRIPTOR()
|
|
{
|
|
if( _cDacl.IsDirty() )
|
|
_ReloadAcl( ACL_IS_DACL );
|
|
|
|
if( _cSacl.IsDirty() )
|
|
_ReloadAcl( ACL_IS_SACL );
|
|
|
|
return( _psd );
|
|
}
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CSystemSD
|
|
//
|
|
// Wrapper specifically for a security descriptor that allows
|
|
// access only to Administrators and System.
|
|
//
|
|
//-------------------------------------------------------------------//
|
|
|
|
#define MAX_SYSTEM_ACL_LENGTH 32
|
|
|
|
class CSystemSD
|
|
{
|
|
public:
|
|
|
|
enum ESystemSD
|
|
{
|
|
SYSTEM_AND_ADMINISTRATOR = 0,
|
|
SYSTEM_ONLY = 1
|
|
};
|
|
|
|
public:
|
|
|
|
void Initialize( ESystemSD = SYSTEM_AND_ADMINISTRATOR );
|
|
void UnInitialize();
|
|
|
|
inline operator const PSECURITY_DESCRIPTOR();
|
|
|
|
private:
|
|
CSecDescriptor _csd;
|
|
CSID _csidAdministrators;
|
|
CSID _csidSystem;
|
|
|
|
};
|
|
|
|
inline
|
|
CSystemSD::operator const PSECURITY_DESCRIPTOR ()
|
|
{
|
|
return(_csd.operator const PSECURITY_DESCRIPTOR());
|
|
}
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CSystemSA //
|
|
// sets up System-and-admin security attributes //
|
|
//-------------------------------------------------------------------//
|
|
|
|
|
|
class CSystemSA
|
|
{
|
|
public:
|
|
|
|
inline void Initialize();
|
|
inline void UnInitialize();
|
|
|
|
inline operator LPSECURITY_ATTRIBUTES();
|
|
|
|
private:
|
|
CSystemSD _ssd;
|
|
SECURITY_ATTRIBUTES _sa;
|
|
|
|
};
|
|
|
|
inline void
|
|
CSystemSA::Initialize()
|
|
{
|
|
_ssd.Initialize();
|
|
_sa.nLength = sizeof(_sa);
|
|
_sa.lpSecurityDescriptor = _ssd.operator const PSECURITY_DESCRIPTOR();
|
|
_sa.bInheritHandle = FALSE;
|
|
}
|
|
|
|
inline void
|
|
CSystemSA::UnInitialize()
|
|
{
|
|
_ssd.UnInitialize();
|
|
}
|
|
|
|
inline
|
|
CSystemSA::operator LPSECURITY_ATTRIBUTES()
|
|
{
|
|
return(&_sa);
|
|
}
|
|
|
|
|
|
/*
|
|
//+------------------------------------------------------------------
|
|
//
|
|
// CRpcPipeControl
|
|
//
|
|
//+------------------------------------------------------------------
|
|
|
|
|
|
template <class T_PIPE_ELEMENT> class TPRpcPipeCallback
|
|
{
|
|
public:
|
|
|
|
virtual void Pull( T_PIPE_ELEMENT *pElement, unsigned long cbBuffer, unsigned long * pcElems ) = 0;
|
|
virtual void Push( T_PIPE_ELEMENT *pElement, unsigned long cElems ) = 0;
|
|
virtual void Alloc( unsigned long cbRequested, T_PIPE_ELEMENT **ppElement, unsigned long * pcbActual ) = 0;
|
|
|
|
}; // template <...> class TPRpcPipeCallback
|
|
|
|
|
|
template <class T_PIPE, class T_PIPE_ELEMENT, class T_C_CALLBACK> class TRpcPipeControl
|
|
{
|
|
|
|
public:
|
|
|
|
TRpcPipeControl( T_C_CALLBACK *pCallback )
|
|
{
|
|
_pipe.state = reinterpret_cast<char*>(pCallback);
|
|
|
|
_pipe.pull = &Pull;
|
|
_pipe.push = &Push;
|
|
_pipe.alloc = &Alloc;
|
|
}
|
|
|
|
operator T_PIPE()
|
|
{
|
|
return( _pipe );
|
|
}
|
|
|
|
private:
|
|
|
|
static void Pull( char * state, T_PIPE_ELEMENT * buf, unsigned long esize, unsigned long * ecount )
|
|
{
|
|
reinterpret_cast<T_C_CALLBACK*>(state)->Pull( buf, esize, ecount );
|
|
}
|
|
|
|
|
|
static void Push( char * state, T_PIPE_ELEMENT * buf, unsigned long ecount )
|
|
{
|
|
reinterpret_cast<T_C_CALLBACK*>(state)->Push( buf, ecount );
|
|
}
|
|
|
|
static void Alloc( char * state, unsigned long bsize, T_PIPE_ELEMENT ** buf, unsigned long * bcount )
|
|
{
|
|
reinterpret_cast<T_C_CALLBACK*>(state)->Alloc( bsize, buf, bcount );
|
|
}
|
|
|
|
private:
|
|
|
|
T_PIPE _pipe;
|
|
|
|
}; // class CRpcPipeControl
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CDebugString
|
|
//
|
|
// This class provides conversion routines for use in dbg output.
|
|
// //
|
|
//-------------------------------------------------------------------//
|
|
|
|
// This struct stringizes a service state value. It's a struct so that it
|
|
// can be used as an overload to CDebugString, and it's all inline so that
|
|
// it doesn't compile into the free non-test code.
|
|
|
|
struct SServiceState
|
|
{
|
|
DWORD _dwState;
|
|
|
|
SServiceState( DWORD dwState )
|
|
{
|
|
_dwState = dwState;
|
|
}
|
|
|
|
void Stringize( ULONG cch, TCHAR *ptsz ) const
|
|
{
|
|
UNREFERENCED_PARAMETER(cch);
|
|
|
|
switch( _dwState )
|
|
{
|
|
case SERVICE_STOPPED:
|
|
_tcscpy( ptsz, TEXT("Service Stopped") );
|
|
break;
|
|
case SERVICE_START_PENDING:
|
|
_tcscpy( ptsz, TEXT("Service Start Pending") );
|
|
break;
|
|
case SERVICE_STOP_PENDING:
|
|
_tcscpy( ptsz, TEXT("Service Stop Pending") );
|
|
break;
|
|
case SERVICE_RUNNING:
|
|
_tcscpy( ptsz, TEXT("Service Running") );
|
|
break;
|
|
case SERVICE_CONTINUE_PENDING:
|
|
_tcscpy( ptsz, TEXT("Service Continue Pending") );
|
|
break;
|
|
case SERVICE_PAUSE_PENDING:
|
|
_tcscpy( ptsz, TEXT("Service Pause Pending") );
|
|
break;
|
|
case SERVICE_PAUSED:
|
|
_tcscpy( ptsz, TEXT("Service Paused") );
|
|
break;
|
|
default:
|
|
_tcscpy( ptsz, TEXT( "Service State UNKNOWN") );
|
|
break;
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CStringize
|
|
//
|
|
// Create a string representation of various input types. The resulting
|
|
// string is contained within this object, so this is a convenient
|
|
// stack-based means of creating a string. For example:
|
|
//
|
|
// _tprintf( TEXT("VolId = %s\n"), (TCHAR*)CStringize(volid) );
|
|
//
|
|
// This class also handles un-stringization. For example:
|
|
//
|
|
// _stscanf( TEXT("VolId = %s\n"), tszVolId );
|
|
// CStringize strVolId;
|
|
// strVolId.Use( tszVolId );
|
|
// CVolumeId volid = strVolId;
|
|
//
|
|
// BUGBUG: Merge this with CDebugString.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
class CStringize
|
|
{
|
|
|
|
// ----
|
|
// Data
|
|
// ----
|
|
|
|
protected:
|
|
|
|
// Buffer for the string
|
|
TCHAR _tsz[ 2*MAX_PATH ];
|
|
|
|
// The current string. May or may not point to _tsz.
|
|
const TCHAR *_ptsz;
|
|
|
|
// ------------
|
|
// Constructors
|
|
// ------------
|
|
|
|
public:
|
|
|
|
// Default
|
|
CStringize()
|
|
{
|
|
_ptsz = _tsz;
|
|
_tsz[0] = TEXT('\0');
|
|
}
|
|
|
|
// TCHAR
|
|
CStringize( TCHAR tc )
|
|
{
|
|
new(this) CStringize;
|
|
_stprintf(_tsz, TEXT("%c"), tc);
|
|
}
|
|
|
|
// int
|
|
CStringize( int i )
|
|
{
|
|
new(this) CStringize;
|
|
_stprintf( _tsz, TEXT("%d"), i );
|
|
}
|
|
|
|
// ULONG
|
|
CStringize( unsigned long ul )
|
|
{
|
|
new(this) CStringize;
|
|
_stprintf( _tsz, TEXT("%lu"), ul );
|
|
}
|
|
|
|
// CVolumeId
|
|
CStringize( const CVolumeId &volid )
|
|
{
|
|
new(this) CStringize;
|
|
StringizeGUID( static_cast<GUID>(volid), &_tsz[0], sizeof(_tsz)/sizeof(TCHAR) );
|
|
}
|
|
|
|
// CVolumeSecret
|
|
CStringize( const CVolumeSecret &volsecret )
|
|
{
|
|
new(this) CStringize;
|
|
TCHAR *ptsz = _tsz;
|
|
volsecret.Stringize(ptsz);
|
|
}
|
|
|
|
// CObjId
|
|
CStringize( const CObjId &objid )
|
|
{
|
|
new(this) CStringize;
|
|
StringizeGUID( static_cast<GUID>(objid), &_tsz[0], sizeof(_tsz)/sizeof(TCHAR) );
|
|
}
|
|
|
|
// CFILETIME
|
|
CStringize(const CFILETIME &cft)
|
|
{
|
|
cft.Stringize( sizeof(_tsz), _tsz );
|
|
}
|
|
|
|
// GUID
|
|
CStringize( const GUID &guid )
|
|
{
|
|
new(this) CStringize;
|
|
StringizeGUID( guid, &_tsz[0], sizeof(_tsz)/sizeof(TCHAR) );
|
|
}
|
|
|
|
// CMachineId
|
|
CStringize( const CMachineId &mcid )
|
|
{
|
|
new(this) CStringize;
|
|
mcid.GetName(_tsz, ELEMENTS(_tsz));
|
|
}
|
|
|
|
// CDomainRelativeObjId
|
|
CStringize( const CDomainRelativeObjId &droid )
|
|
{
|
|
new(this) CStringize;
|
|
ULONG cch = StringizeGUID( droid.GetVolumeId(), _tsz, sizeof(_tsz) );
|
|
StringizeGUID( droid.GetObjId(), &_tsz[cch], sizeof(_tsz)-cch*sizeof(WCHAR) );
|
|
}
|
|
|
|
// -----------
|
|
// Conversions
|
|
// -----------
|
|
|
|
public:
|
|
|
|
operator const TCHAR*() const
|
|
{
|
|
return( _tsz );
|
|
}
|
|
|
|
operator CVolumeId() const
|
|
{
|
|
CVolumeId volid;
|
|
if( !UnStringizeGUID( _ptsz, reinterpret_cast<GUID*>(&volid) )) {
|
|
// TrkRaiseException
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't UnStringizeGUID in CStringize::operator CVolumeId") ));
|
|
}
|
|
|
|
return( volid );
|
|
}
|
|
|
|
operator CObjId() const
|
|
{
|
|
CObjId objid;
|
|
if( !UnStringizeGUID( _ptsz, reinterpret_cast<GUID*>(&objid) )) {
|
|
// TrkRaiseException
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't UnStringizeGUID in CStringize::operator CObjId") ));
|
|
}
|
|
|
|
return( objid );
|
|
}
|
|
|
|
operator CDomainRelativeObjId() const
|
|
{
|
|
CDomainRelativeObjId droid;
|
|
CVolumeId volid;
|
|
CObjId objid;
|
|
|
|
if( !UnStringizeGUID( _ptsz, reinterpret_cast<GUID*>(&volid) ))
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't UnStringizeGUID in CStringize::operator CDomainRelativeObjId") ));
|
|
else
|
|
{
|
|
const TCHAR *ptszObjId = _tcschr( &_ptsz[1], TEXT('{') );
|
|
if( NULL == ptszObjId )
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't UnStringizeGUID in CStringize::operator CDomainRelativeObjId") ));
|
|
else
|
|
{
|
|
if( !UnStringizeGUID( ptszObjId, reinterpret_cast<GUID*>(&objid) ))
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't UnStringizeGUID in CStringize::operator CDomainRelativeObjId") ));
|
|
else
|
|
droid = CDomainRelativeObjId( volid, objid );
|
|
}
|
|
}
|
|
|
|
return( droid );
|
|
}
|
|
|
|
public:
|
|
|
|
// Use a special caller-provided string, rather than _tsz
|
|
inline void Use( const TCHAR *ptsz )
|
|
{
|
|
_tsz[0] = TEXT('\0');
|
|
_ptsz = ptsz;
|
|
}
|
|
|
|
private:
|
|
|
|
inline ULONG StringizeGUID( const GUID &guid, TCHAR *ptsz, ULONG cchMax ) const
|
|
{
|
|
TCHAR *ptszT = NULL;
|
|
RPC_STATUS rpc_status;
|
|
rpc_status = UuidToString( const_cast<GUID*>(&guid), &ptszT );
|
|
if( NULL != ptszT )
|
|
{
|
|
_tcscpy( ptsz, TEXT("{") );
|
|
_tcscat( ptsz, ptszT );
|
|
_tcscat( ptsz, TEXT("}") );
|
|
RpcStringFree( &ptszT );
|
|
return( _tcslen(ptsz) );
|
|
}
|
|
else
|
|
{
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Failed StringizeGUID (%lu)"), rpc_status ));
|
|
_tcscpy( ptsz, TEXT("") );
|
|
return( 0 );
|
|
}
|
|
}
|
|
|
|
inline BOOL UnStringizeGUID( const TCHAR *ptsz, GUID *pguid ) const
|
|
{
|
|
RPC_STATUS rpc_status;
|
|
TCHAR tszTemp[ MAX_PATH ];
|
|
TCHAR *ptszTemp = NULL;
|
|
|
|
if( TEXT('{') != ptsz[0] )
|
|
return( FALSE );
|
|
|
|
_tcscpy( tszTemp, &ptsz[1] );
|
|
ptszTemp = _tcschr( tszTemp, TEXT('}') );
|
|
if( NULL == ptsz )
|
|
return( FALSE );
|
|
|
|
*ptszTemp = TEXT('\0');
|
|
|
|
if( RPC_S_OK == UuidFromString( tszTemp, pguid ) )
|
|
{
|
|
return( TRUE );
|
|
}
|
|
else
|
|
return( FALSE );
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CDebugString
|
|
//
|
|
// This class has constructors for various common types. For each type,
|
|
// it stringizes the input, and puts it in the _tsz member. That member
|
|
// is public for easy access.
|
|
//
|
|
// The intent of this class is to provide an easy, stack-based mechanism
|
|
// to stringize a value for a debug output. E.g.:
|
|
//
|
|
// CObjId objid;
|
|
// objid = ...
|
|
// TrkLog(( TRKDBG_ERROR, TEXT("objid = %s"), CDebugString(objid)._tsz ));
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
|
|
#if DBG
|
|
|
|
class CDebugString : public CStringize
|
|
{
|
|
public:
|
|
|
|
CDebugString(const CObjId &objid ) : CStringize( objid ) {};
|
|
CDebugString(const CMachineId & mcid) : CStringize( mcid ) {};
|
|
CDebugString(const CFILETIME & cft) : CStringize( cft ) {};
|
|
CDebugString(const CVolumeSecret & secret) : CStringize( secret ) {};
|
|
CDebugString(const CVolumeId & volume) : CStringize( volume ) {};
|
|
CDebugString(const CDomainRelativeObjId &droid) : CStringize( droid ) {};
|
|
CDebugString(const GUID &guid ) : CStringize( guid ) {};
|
|
|
|
CDebugString(const enum WMNG_STATE state );
|
|
CDebugString(const SServiceState sServiceState );
|
|
CDebugString(LONG iVolume, const PFILE_NOTIFY_INFORMATION );
|
|
CDebugString(const TRKSVR_MESSAGE_TYPE MsgType);
|
|
CDebugString(const CNewTimer &ctimer );
|
|
|
|
};
|
|
|
|
inline
|
|
CDebugString::CDebugString(const CNewTimer & ctimer)
|
|
{
|
|
ctimer.DebugStringize( ELEMENTS(_tsz), _tsz );
|
|
}
|
|
|
|
|
|
inline
|
|
CDebugString::CDebugString( const SServiceState sServiceState )
|
|
{
|
|
sServiceState.Stringize( ELEMENTS(_tsz), _tsz );
|
|
}
|
|
|
|
#endif // #if DBG
|
|
|
|
//-------------------------------------------------------------------//
|
|
// //
|
|
// CVolumeMap
|
|
//
|
|
// Note - not currently used.
|
|
// //
|
|
//-------------------------------------------------------------------//
|
|
|
|
#ifdef VOL_REPL
|
|
|
|
class CVolumeMap
|
|
{
|
|
|
|
public:
|
|
CVolumeMap() : _pVolumeMapEntries(NULL), _cVolumeMapEntries(0), _iNextEntry(0) { }
|
|
~CVolumeMap() { TrkAssert(_pVolumeMapEntries == NULL); }
|
|
|
|
void CopyTo( CVolumeMap * p ) const;
|
|
void MoveTo( CVolumeMap * p );
|
|
|
|
void MoveTo( ULONG * pcVolumeMapEntries, VolumeMapEntry ** ppVolumeMapEntries );
|
|
|
|
void Initialize( ULONG cVolumeMapEntries, VolumeMapEntry ** ppVolumeMapEntries );
|
|
void UnInitialize( );
|
|
|
|
void SetSize( ULONG cVolumeMapEntries ); // can throw
|
|
void Add( const CVolumeId & volume, const CMachineId & machine );
|
|
void Compact();
|
|
|
|
inline int Reset();
|
|
|
|
inline const VolumeMapEntry &
|
|
Current();
|
|
|
|
inline void Next();
|
|
|
|
BOOL Merge( CVolumeMap * pOther ); // destroys the contents of pOther, TRUE if changed
|
|
|
|
#if DBG
|
|
inline ULONG Count() const;
|
|
#endif
|
|
|
|
protected:
|
|
|
|
ULONG _cVolumeMapEntries;
|
|
VolumeMapEntry * _pVolumeMapEntries;
|
|
ULONG _iNextEntry;
|
|
|
|
};
|
|
|
|
inline int
|
|
CVolumeMap::Reset()
|
|
{
|
|
_iNextEntry = 0;
|
|
return(_cVolumeMapEntries);
|
|
}
|
|
|
|
inline void
|
|
CVolumeMap::Next()
|
|
{
|
|
_iNextEntry++;
|
|
}
|
|
|
|
inline const VolumeMapEntry &
|
|
CVolumeMap::Current()
|
|
{
|
|
return(_pVolumeMapEntries[_iNextEntry]);
|
|
}
|
|
|
|
#if DBG
|
|
inline ULONG
|
|
CVolumeMap::Count() const
|
|
{
|
|
return(_cVolumeMapEntries);
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//
|
|
// QueryVolumeId
|
|
//
|
|
// Get the CVolumeId from a file handle.
|
|
//
|
|
|
|
inline NTSTATUS
|
|
QueryVolumeId( const HANDLE hFile, CVolumeId *pvolid )
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
IO_STATUS_BLOCK Iosb;
|
|
FILE_FS_OBJECTID_INFORMATION fsobOID;
|
|
|
|
|
|
status = NtQueryVolumeInformationFile( hFile, &Iosb, &fsobOID, sizeof(fsobOID),
|
|
FileFsObjectIdInformation );
|
|
if( !NT_SUCCESS(status) ) goto Exit;
|
|
|
|
*pvolid = CVolumeId( fsobOID );
|
|
|
|
Exit:
|
|
#if DBG
|
|
if( !NT_SUCCESS(status) && STATUS_OBJECT_NAME_NOT_FOUND != status )
|
|
TrkLog(( TRKDBG_ERROR, TEXT("QueryVolumeId failed (status=%08x)"), status ));
|
|
#endif
|
|
|
|
return( status );
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// QueryVolumeId
|
|
//
|
|
// Get the CVolumeId from a file name.
|
|
//
|
|
|
|
inline NTSTATUS
|
|
QueryVolumeId( const WCHAR *pwszPath, CVolumeId *pvolid )
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
HANDLE hFile = NULL;
|
|
|
|
status = TrkCreateFile( pwszPath, FILE_READ_ATTRIBUTES,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, FILE_OPEN,
|
|
FILE_OPEN_NO_RECALL | FILE_SYNCHRONOUS_IO_NONALERT,
|
|
NULL, &hFile );
|
|
if( !NT_SUCCESS(status) )
|
|
{
|
|
hFile = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
status = QueryVolumeId( hFile, pvolid );
|
|
if( !NT_SUCCESS(status) ) goto Exit;
|
|
|
|
Exit:
|
|
|
|
if( NULL != hFile )
|
|
NtClose( hFile );
|
|
|
|
return( status );
|
|
}
|
|
|
|
|
|
//
|
|
// QueryVolumeId
|
|
//
|
|
// Get the CVolumeId from a drive letter index.
|
|
//
|
|
|
|
inline NTSTATUS
|
|
QueryVolumeId( ULONG iVol, CVolumeId *pvolid )
|
|
{
|
|
TCHAR tszPath[] = TEXT("A:\\");
|
|
|
|
tszPath[0] += static_cast<TCHAR>(iVol);
|
|
|
|
return QueryVolumeId(tszPath, pvolid);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// QueryVolRelativePath
|
|
//
|
|
// Get the volume-relative path of a file from its handle.
|
|
//
|
|
//
|
|
|
|
inline NTSTATUS
|
|
QueryVolRelativePath( HANDLE hFile, TCHAR *ptszVolRelativePath )
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK Iosb;
|
|
|
|
// FILE_NAME_INFORMATION buffer. This size allows for a MAX_PATH path,
|
|
// plus a string terminator (because sizeof FILE_NAME_INFORMATION already
|
|
// has a character for the name).
|
|
|
|
BYTE rgbFileNameInformation[ 2 * MAX_PATH + sizeof(FILE_NAME_INFORMATION) ];
|
|
FILE_NAME_INFORMATION *pfile_name_information
|
|
= (FILE_NAME_INFORMATION*) rgbFileNameInformation;
|
|
|
|
// Get the volume-relative path.
|
|
|
|
status = NtQueryInformationFile(
|
|
hFile,
|
|
&Iosb,
|
|
pfile_name_information,
|
|
sizeof(rgbFileNameInformation),
|
|
FileNameInformation );
|
|
|
|
#ifndef UNICODE
|
|
#error Only Unicode supported
|
|
#endif
|
|
|
|
if( !NT_SUCCESS(status) ) goto Exit;
|
|
|
|
pfile_name_information->FileName[ pfile_name_information->FileNameLength / sizeof(WCHAR) ]
|
|
= L'\0';
|
|
_tcscpy( ptszVolRelativePath, pfile_name_information->FileName );
|
|
|
|
Exit:
|
|
#if DBG
|
|
if( !NT_SUCCESS(status) )
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Failed QueryVolRelativePath, status=%08x"), status ));
|
|
#endif
|
|
|
|
return( status );
|
|
}
|
|
|
|
|
|
//
|
|
// FileIsDirectory
|
|
//
|
|
// Determine if a file is a directory file.
|
|
//
|
|
|
|
inline BOOL
|
|
FileIsDirectory( HANDLE hFile )
|
|
{
|
|
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK Iosb;
|
|
FILE_BASIC_INFORMATION file_basic_information;
|
|
|
|
// Get the volume-relative path.
|
|
|
|
status = NtQueryInformationFile(
|
|
hFile,
|
|
&Iosb,
|
|
&file_basic_information,
|
|
sizeof(file_basic_information),
|
|
FileBasicInformation );
|
|
|
|
return( 0 != (file_basic_information.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) );
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Generate a "random" dword, based primarily on the
|
|
// performance counter.
|
|
//
|
|
|
|
inline DWORD
|
|
QuasiRandomDword()
|
|
{
|
|
DWORD dwRet;
|
|
CFILETIME cftNow;
|
|
LARGE_INTEGER li;
|
|
|
|
QueryPerformanceCounter( &li );
|
|
dwRet = li.HighPart ^ li.LowPart;
|
|
dwRet ^= cftNow.LowDateTime();
|
|
dwRet ^= cftNow.HighDateTime();
|
|
|
|
return( dwRet );
|
|
}
|
|
|
|
|
|
//
|
|
// TrkCharUpper
|
|
//
|
|
// Convert to upper case.
|
|
//
|
|
|
|
inline WCHAR
|
|
TrkCharUpper( WCHAR wc )
|
|
{
|
|
if( !LCMapStringW( LOCALE_USER_DEFAULT,
|
|
LCMAP_UPPERCASE,
|
|
(LPWSTR)&wc,
|
|
1,
|
|
(LPWSTR)&wc,
|
|
1 ))
|
|
{
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Failed LcMapStringW ('%c', %lu)"),
|
|
wc, GetLastError() ));
|
|
}
|
|
|
|
return( wc );
|
|
}
|
|
|
|
// These are the errors that we see when we attempt to use a volume
|
|
// that has been dismounted and locked from under us.
|
|
inline BOOL
|
|
IsErrorDueToLockedVolume( NTSTATUS status )
|
|
{
|
|
return( STATUS_VOLUME_DISMOUNTED == status // Handle has been broken
|
|
||
|
|
STATUS_ACCESS_DENIED == status // Volume locked, couldn't open a handle
|
|
||
|
|
ERROR_ACCESS_DENIED == status
|
|
||
|
|
HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) == status
|
|
||
|
|
HRESULT_FROM_WIN32(ERROR_OPEN_FAILED) == status );
|
|
}
|
|
|
|
// These are the errors that we get from a disk operation that
|
|
// are in some way recoverable.
|
|
inline BOOL
|
|
IsRecoverableDiskError( NTSTATUS status )
|
|
{
|
|
return( TRK_E_CORRUPT_LOG == status
|
|
||
|
|
ERROR_NOT_READY == status
|
|
||
|
|
HRESULT_FROM_WIN32(ERROR_NOT_READY) == status
|
|
||
|
|
STATUS_DEVICE_NOT_READY == status
|
|
||
|
|
IsErrorDueToLockedVolume( status ));
|
|
}
|
|
|
|
|
|
|
|
// Check to see if the current thread token is a member
|
|
// of the Authenticated Users group. This class is never
|
|
// instantiated, it's just a static helper class.
|
|
|
|
class CVerifyAuthentication
|
|
{
|
|
private:
|
|
|
|
// The SID for the built-in Authenticated Users group.
|
|
static PSID _psidAuthenticatedUsersGroup;
|
|
|
|
private:
|
|
|
|
// Constructor is private; this is just a static helper class.
|
|
CVerifyAuthentication()
|
|
{
|
|
}
|
|
|
|
public:
|
|
|
|
// Initialization
|
|
|
|
static void Initialize()
|
|
{
|
|
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
|
|
|
// We shouldn't already be initialized
|
|
|
|
if( NULL != _psidAuthenticatedUsersGroup )
|
|
{
|
|
TrkLog(( TRKDBG_WARNING, TEXT("InitForIsAuthenticatedUser called multiple times") ));
|
|
TrkRaiseWin32Error( ERROR_INTERNAL_DB_CORRUPTION );
|
|
}
|
|
|
|
// Allocate the Authenticated Users SID
|
|
|
|
if( !AllocateAndInitializeSid( &NtAuthority,
|
|
1,
|
|
SECURITY_AUTHENTICATED_USER_RID,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
&_psidAuthenticatedUsersGroup ))
|
|
{
|
|
TrkLog(( TRKDBG_WARNING, TEXT("InitForIsAuthenticatedUser failed (%lu)"), GetLastError() ));
|
|
TrkRaiseLastError();
|
|
}
|
|
}
|
|
|
|
|
|
// Uninitialization
|
|
|
|
static void Uninitialize()
|
|
{
|
|
if( NULL != _psidAuthenticatedUsersGroup )
|
|
{
|
|
FreeSid( _psidAuthenticatedUsersGroup );
|
|
_psidAuthenticatedUsersGroup = NULL;
|
|
}
|
|
}
|
|
|
|
// Verify that the caller on this RPC handle is authenticated,
|
|
// by verifying that they're a member of the Authenticated Users
|
|
// group. Raise if not.
|
|
|
|
static void VerifyAuthentication( handle_t IDL_handle )
|
|
{
|
|
BOOL fAuthenticatedUser = FALSE;
|
|
|
|
// Impersonate the user so we can get their user token.
|
|
|
|
RPC_STATUS rpc_status = RpcImpersonateClient( IDL_handle );
|
|
if( S_OK == rpc_status )
|
|
{
|
|
|
|
#if DBG
|
|
TCHAR tszCurrentUser[ 200 ] = { TEXT("") };
|
|
ULONG cchCurrentUser = sizeof(tszCurrentUser);
|
|
GetUserName( tszCurrentUser, &cchCurrentUser );
|
|
TrkLog(( TRKDBG_MEND, TEXT("\nCall from %s"), tszCurrentUser ));
|
|
#endif
|
|
|
|
// See if this user is authenticated
|
|
|
|
if( IsAuthenticatedUser() )
|
|
fAuthenticatedUser = TRUE;
|
|
|
|
// Stop impersonating
|
|
|
|
RpcRevertToSelf();
|
|
}
|
|
else
|
|
{
|
|
TrkLog(( TRKDBG_WARNING, TEXT("VerifyAuthentication couldn't impersonate") ));
|
|
}
|
|
|
|
if( !fAuthenticatedUser )
|
|
{
|
|
TrkLog(( TRKDBG_WARNING, TEXT("Attempt at unauthenticated access") ));
|
|
TrkRaiseWin32Error( ERROR_NOT_AUTHENTICATED );
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
// Check if the current user on this thread is a member
|
|
// of the Authenticated Users group.
|
|
|
|
static BOOL IsAuthenticatedUser()
|
|
{
|
|
BOOL fAuthenticatedUser = FALSE;
|
|
|
|
TrkAssert( NULL != _psidAuthenticatedUsersGroup );
|
|
if( NULL == _psidAuthenticatedUsersGroup )
|
|
return FALSE;
|
|
|
|
if( !CheckTokenMembership( NULL, _psidAuthenticatedUsersGroup, &fAuthenticatedUser ) )
|
|
{
|
|
TrkLog(( TRKDBG_WARNING, TEXT("Failed CheckTokenMembership (%lu)"), GetLastError() ));
|
|
}
|
|
|
|
return fAuthenticatedUser;
|
|
}
|
|
|
|
}; // CVerifyAuthentication
|
|
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// CHexStringize
|
|
//
|
|
// Create a string with a hex representation of the input. E.g.:
|
|
//
|
|
// _tprintf( TEXT("1234 in hex is 0x%s\n"), CHexStringize(1234)._tsz );
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
|
|
class CHexStringize
|
|
{
|
|
public:
|
|
TCHAR _tsz[ MAX_PATH ];
|
|
|
|
public:
|
|
|
|
// Stringize a long
|
|
CHexStringize( long l)
|
|
{
|
|
_stprintf(_tsz, TEXT("%08x"), l);
|
|
}
|
|
|
|
// Stringize a long, and take a caller-specified prefix
|
|
CHexStringize( TCHAR *ptsz, long l )
|
|
{
|
|
_stprintf( _tsz, TEXT("%s%08x"), ptsz, l );
|
|
}
|
|
|
|
operator const TCHAR*() const
|
|
{
|
|
return( _tsz );
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Event logging prototypes
|
|
|
|
HRESULT TrkReportRawEvent(DWORD EventId,
|
|
WORD wType,
|
|
DWORD cbRawData,
|
|
const void *pvRawData,
|
|
va_list pargs );
|
|
|
|
inline HRESULT
|
|
TrkReportRawEventWrapper( DWORD EventId,
|
|
WORD wType,
|
|
DWORD cbRawData,
|
|
const void *pvRawData,
|
|
... )
|
|
{
|
|
va_list va;
|
|
va_start( va, pvRawData );
|
|
return TrkReportRawEvent( EventId, wType, cbRawData, pvRawData, va );
|
|
}
|
|
|
|
|
|
inline HRESULT
|
|
TrkReportEvent( DWORD EventId, WORD wType, ... )
|
|
{
|
|
va_list va;
|
|
va_start( va, wType );
|
|
return TrkReportRawEvent( EventId, wType, 0, NULL, va );
|
|
}
|
|
|
|
HRESULT TrkReportInternalError(DWORD dwFileNo,
|
|
DWORD dwLineNo,
|
|
HRESULT hr,
|
|
const TCHAR* ptszData = NULL );
|
|
|
|
|
|
#define TRKREPORT_LAST_PARAM NULL
|
|
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// COperationLog
|
|
//
|
|
// This class represents a simple, optional, circular log of events that have
|
|
// occurred in the service. By default this log is off, but it can be turned
|
|
// on with a registry setting.
|
|
//
|
|
//+----------------------------------------------------------------------------
|
|
|
|
|
|
#define OPERATION_LOG_VERSION 1
|
|
|
|
class COperationLog
|
|
{
|
|
|
|
public:
|
|
|
|
// The shutdown bit is set in the log header on a clean
|
|
// service stop.
|
|
|
|
enum EHeaderFlags
|
|
{
|
|
PROPER_SHUTDOWN = 0x1
|
|
};
|
|
|
|
|
|
// The following operations are defined. These are the "opcode"
|
|
// for an entry in the log.
|
|
|
|
enum EOperation
|
|
{
|
|
TRKSVR_START = 1,
|
|
TRKSVR_SEARCH,
|
|
TRKSVR_MOVE_NOTIFICATION,
|
|
TRKSVR_REFRESH,
|
|
TRKSVR_SYNC_VOLUMES,
|
|
TRKSVR_DELETE_NOTIFY,
|
|
TRKSVR_STATISTICS,
|
|
TRKSVR_QUOTA,
|
|
TRKSVR_GC
|
|
};
|
|
|
|
|
|
private:
|
|
|
|
// The small log header.
|
|
|
|
struct SHeader
|
|
{
|
|
DWORD dwVersion;
|
|
DWORD iRecord;
|
|
DWORD grfFlags;
|
|
DWORD dwReserved;
|
|
};
|
|
|
|
// The structure of a record in the log.
|
|
|
|
struct SRecord
|
|
{
|
|
DWORD dwOperation;
|
|
FILETIME ftOperation;
|
|
CMachineId mcidSource;
|
|
HRESULT hr;
|
|
DWORD rgdwExtra[8];
|
|
};
|
|
|
|
private:
|
|
|
|
#ifndef NO_OPLOG
|
|
|
|
// Has the class been initialized?
|
|
BOOL _fInitialized:1;
|
|
|
|
// Has the log file itself been intiailize (it is
|
|
// initialized lazily)?
|
|
BOOL _fLogFileInitialized:1;
|
|
|
|
CCriticalSection _critsec;
|
|
|
|
// Handles to the file
|
|
HANDLE _hFile;
|
|
HANDLE _hFileMapping;
|
|
|
|
// Record-granular index into the file.
|
|
DWORD _iRecord;
|
|
ULONG _cRecords;
|
|
|
|
// Mapped views of the file
|
|
SHeader *_pHeader;
|
|
SRecord *_prgRecords;
|
|
|
|
// File name
|
|
const TCHAR *_ptszOperationLog;
|
|
#endif
|
|
|
|
|
|
public:
|
|
|
|
COperationLog( )
|
|
{
|
|
#ifndef NO_OPLOG
|
|
_fInitialized = _fLogFileInitialized = FALSE;
|
|
_iRecord = 0;
|
|
_hFile = INVALID_HANDLE_VALUE;
|
|
_hFileMapping = NULL;
|
|
_ptszOperationLog = NULL;
|
|
_pHeader = NULL;
|
|
_prgRecords = NULL;
|
|
_ptszOperationLog = NULL;
|
|
#endif
|
|
}
|
|
|
|
~COperationLog()
|
|
{
|
|
#ifndef NO_OPLOG
|
|
if( NULL != _pHeader )
|
|
{
|
|
TrkLog(( TRKDBG_VOLUME, TEXT("Flushing operation log with index at %d"), _iRecord ));
|
|
Flush();
|
|
_pHeader->grfFlags |= PROPER_SHUTDOWN;
|
|
Flush();
|
|
UnmapViewOfFile( _pHeader );
|
|
}
|
|
|
|
if( NULL != _hFileMapping )
|
|
CloseHandle( _hFileMapping );
|
|
|
|
if( INVALID_HANDLE_VALUE != _hFile )
|
|
CloseHandle( _hFile );
|
|
|
|
_critsec.UnInitialize();
|
|
#endif
|
|
}
|
|
|
|
// Return TRUE if logging is turned on
|
|
inline BOOL Logging()
|
|
{
|
|
#ifndef NO_OPLOG
|
|
return( NULL != _ptszOperationLog );
|
|
#else
|
|
return( FALSE );
|
|
#endif
|
|
}
|
|
|
|
// Enter the critsec
|
|
void Lock()
|
|
{
|
|
#ifndef NO_OPLOG
|
|
_critsec.Enter();
|
|
#endif
|
|
}
|
|
|
|
// Leave the critsec
|
|
void Unlock()
|
|
{
|
|
#ifndef NO_OPLOG
|
|
_critsec.Leave();
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
|
|
// Initialize the log file when it's actually needed
|
|
HRESULT InitializeLogFile();
|
|
|
|
public:
|
|
|
|
// Initialize the class
|
|
inline HRESULT Initialize( const TCHAR *ptszOperationLog );
|
|
|
|
// Flush the log/mappings to disk.
|
|
void Flush();
|
|
|
|
// Add an entry
|
|
inline void Add( DWORD dwOperation,
|
|
HRESULT hr = S_OK,
|
|
const CMachineId &mcidSource = CMachineId(MCID_INVALID),
|
|
DWORD dwExtra0 = 0,
|
|
DWORD dwExtra1 = 0,
|
|
DWORD dwExtra2 = 0,
|
|
DWORD dwExtra3 = 0,
|
|
DWORD dwExtra4 = 0,
|
|
DWORD dwExtra5 = 0,
|
|
DWORD dwExtra6 = 0,
|
|
DWORD dwExtra7 = 0
|
|
);
|
|
|
|
// Wrapper to add an mcid/droid
|
|
inline void Add( DWORD dwOperation,
|
|
HRESULT hr,
|
|
const CMachineId &mcidSource,
|
|
const CDomainRelativeObjId &droid );
|
|
|
|
// Wrapper to add an mcid/volid
|
|
inline void Add( DWORD dwOperation,
|
|
HRESULT hr,
|
|
const CMachineId &mcidSource,
|
|
const CVolumeId &volid,
|
|
DWORD dwExtra );
|
|
|
|
private:
|
|
|
|
#ifndef NO_OPLOG
|
|
void InternalAdd( DWORD dwOperation, HRESULT hr, const CMachineId &mcidSource,
|
|
DWORD dwExtra0, DWORD dwExtra1, DWORD dwExtra2, DWORD dwExtra3,
|
|
DWORD dwExtra4, DWORD dwExtra5, DWORD dwExtra6, DWORD dwExtra7 );
|
|
#endif
|
|
|
|
|
|
};
|
|
|
|
inline void
|
|
COperationLog::Add( DWORD dwOperation,
|
|
HRESULT hr,
|
|
const CMachineId &mcidSource,
|
|
DWORD dwExtra0,
|
|
DWORD dwExtra1,
|
|
DWORD dwExtra2,
|
|
DWORD dwExtra3,
|
|
DWORD dwExtra4,
|
|
DWORD dwExtra5,
|
|
DWORD dwExtra6,
|
|
DWORD dwExtra7
|
|
)
|
|
{
|
|
#ifndef NO_OPLOG
|
|
if( !Logging() )
|
|
return;
|
|
|
|
InternalAdd( dwOperation, hr, mcidSource,
|
|
dwExtra0, dwExtra1, dwExtra2, dwExtra3, dwExtra4, dwExtra5, dwExtra6, dwExtra7 );
|
|
#endif
|
|
}
|
|
|
|
#define DROID_DWORD(droid,i) ( ((const DWORD*)(&(droid)))[i] )
|
|
|
|
inline void
|
|
COperationLog::Add( DWORD dwOperation,
|
|
HRESULT hr,
|
|
const CMachineId &mcidSource,
|
|
const CDomainRelativeObjId &droid )
|
|
{
|
|
#ifndef NO_OPLOG
|
|
|
|
// Parameter validation (protect against attack)
|
|
// mcidSource is generated by us, not the client, so it's safe.
|
|
if (!((const void*)(&(droid))))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Add( dwOperation, hr, mcidSource,
|
|
DROID_DWORD( droid, 0 ),
|
|
DROID_DWORD( droid, 1 ),
|
|
DROID_DWORD( droid, 2 ),
|
|
DROID_DWORD( droid, 3 ),
|
|
DROID_DWORD( droid, 4 ),
|
|
DROID_DWORD( droid, 5 ),
|
|
DROID_DWORD( droid, 6 ),
|
|
DROID_DWORD( droid, 7 ) );
|
|
#endif
|
|
}
|
|
|
|
|
|
#define GUID_DWORD(guid,i) ( ((const DWORD*)(&(guid)))[i] )
|
|
|
|
inline void
|
|
COperationLog::Add( DWORD dwOperation,
|
|
HRESULT hr,
|
|
const CMachineId &mcidSource,
|
|
const CVolumeId &volid,
|
|
DWORD dwExtra )
|
|
{
|
|
#ifndef NO_OPLOG
|
|
|
|
// Parameter validation (protect against attack)
|
|
// mcidSource is generated by us, not the client, so it's safe.
|
|
|
|
if (!((const void*)(&(volid))))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Add( dwOperation, hr, mcidSource,
|
|
GUID_DWORD( volid, 0 ),
|
|
GUID_DWORD( volid, 1 ),
|
|
GUID_DWORD( volid, 2 ),
|
|
GUID_DWORD( volid, 3 ),
|
|
dwExtra );
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
inline HRESULT
|
|
COperationLog::Initialize( const TCHAR *ptszOperationLog )
|
|
{
|
|
#ifndef NO_OPLOG
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if( NULL == ptszOperationLog || TEXT('\0') == *ptszOperationLog )
|
|
return( S_OK );
|
|
|
|
_critsec.Initialize();
|
|
_fInitialized = TRUE;
|
|
_ptszOperationLog = ptszOperationLog;
|
|
TrkLog(( TRKDBG_ERROR, TEXT("Operation log = %s"), _ptszOperationLog ));
|
|
|
|
hr = S_OK;
|
|
|
|
return( hr );
|
|
#else
|
|
return( S_OK );
|
|
#endif
|
|
}
|
|
|
|
|
|
|