2448 lines
79 KiB
C++
2448 lines
79 KiB
C++
|
|
||
|
// Copyright (c) 1996-1999 Microsoft Corporation
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Microsoft Windows
|
||
|
//
|
||
|
// File: svrsvc.cxx
|
||
|
//
|
||
|
// Contents: Code for CTrkSvrSvc
|
||
|
//
|
||
|
// Classes:
|
||
|
//
|
||
|
// Functions:
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
// History: 18-Nov-96 BillMo Created.
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
// Codework:
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
#include "pch.cxx"
|
||
|
#pragma hdrstop
|
||
|
|
||
|
#include "trksvr.hxx"
|
||
|
#include "ntlsa.h"
|
||
|
|
||
|
#define THIS_FILE_NUMBER SVRSVC_CXX_FILE_NO
|
||
|
|
||
|
#if DBG
|
||
|
DWORD g_Debug = 0;
|
||
|
#endif
|
||
|
|
||
|
const extern TCHAR s_tszKeyNameLinkTrack[] = TEXT("System\\CurrentControlSet\\Services\\TrkSvr\\Parameters");
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::Initialize
|
||
|
//
|
||
|
// Initialize the TrkSvr service.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::Initialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData )
|
||
|
{
|
||
|
|
||
|
__try
|
||
|
{
|
||
|
_cLowestAvailableThreads = _cAvailableThreads = MAX_SVR_THREADS;
|
||
|
|
||
|
_fInitializeCalled = TRUE;
|
||
|
g_ptrksvr = this;
|
||
|
_pSvcsGlobalData = pSvcsGlobalData;
|
||
|
|
||
|
// Initialize the object that manages the SCM.
|
||
|
_svcctrl.Initialize(TEXT("TrkSvr"), this);
|
||
|
|
||
|
// Initialize registry-configurable parameters.
|
||
|
_configSvr.Initialize();
|
||
|
|
||
|
// If requested, prepare to log all operations (to a file)
|
||
|
if( _configSvr.UseOperationLog() )
|
||
|
_OperationLog.Initialize( _configSvr.GetOperationLog() );
|
||
|
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("Distributed Link Tracking (Server) service starting on thread=%d(0x%x)"),
|
||
|
GetCurrentThreadId(), GetCurrentThreadId() ));
|
||
|
|
||
|
// This is a hacked stub that looks and acts like the Win32 thread pool services
|
||
|
#ifdef PRIVATE_THREAD_POOL
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
g_pworkman2 = new CThreadPoolStub;
|
||
|
if( NULL == g_pworkman2 )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't create the thread pool manager") ));
|
||
|
TrkRaiseWin32Error( ERROR_NOT_ENOUGH_MEMORY );
|
||
|
}
|
||
|
|
||
|
hr = g_pworkman2->Initialize();
|
||
|
if( FAILED(hr) )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't initialize the thread pool manager") ));
|
||
|
TrkRaiseException( hr );
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// The denial checker provides protection against a denial-of-service
|
||
|
// attack, where a client floods us with calls.
|
||
|
_denial.Initialize(_configSvr.GetHistoryPeriod() );
|
||
|
|
||
|
// This critsec protects _cWritesPerHour & _cftWritesPerHour.
|
||
|
// See CTrkSvrSvc::CheckWritesPerHour
|
||
|
_csWritesPerHour.Initialize();
|
||
|
|
||
|
// This maintains the "time" for purposes of refreshing entries.
|
||
|
_refreshSequence.Initialize();
|
||
|
|
||
|
// The cross-domain table
|
||
|
_cdt.Initialize();
|
||
|
|
||
|
// The intra-domain table
|
||
|
_idt.Initialize( &_configSvr, &_qtable );
|
||
|
|
||
|
// The volume table
|
||
|
_voltab.Initialize( &_configSvr, &_qtable );
|
||
|
|
||
|
// The quota manager
|
||
|
_qtable.Initialize(&_voltab, &_idt, this, &_configSvr );
|
||
|
|
||
|
// Set the quota timer. This was originally every 30 days, but is now
|
||
|
// every day. In order to maintain compatibility with the tests, we
|
||
|
// still use the GCPeriod value (30 days), but divide it by the new
|
||
|
// GCDivisor value (30) to get the correct period.
|
||
|
// This timer doesn't have a standard retry, because of the way
|
||
|
// we hesitate 30 minutes before doing anything. So retries are
|
||
|
// done explicitely.
|
||
|
|
||
|
_timerGC.Initialize(this,
|
||
|
TEXT("NextGarbageCollectTime"), // This is a persistent timer
|
||
|
0, // Context ID
|
||
|
_configSvr.GetGCPeriod() / _configSvr.GetGCDivisor(),
|
||
|
CNewTimer::NO_RETRY,
|
||
|
0, 0, 0 ); // No retries or max lifetime
|
||
|
_timerGC.SetRecurring();
|
||
|
TrkLog(( TRKDBG_VOLUME, TEXT("GC timer: %s"),
|
||
|
(const TCHAR*) CDebugString(_timerGC) ));
|
||
|
|
||
|
// Used in the Timer method to determine if we should reset the
|
||
|
// move table counter value.
|
||
|
_MoveCounterReset.Initialize();
|
||
|
|
||
|
// Initialize ourself as an RPC server
|
||
|
_rpc.Initialize( _pSvcsGlobalData, &_configSvr );
|
||
|
|
||
|
// Tell the SCM that we're running.
|
||
|
|
||
|
_svcctrl.SetServiceStatus(SERVICE_RUNNING,
|
||
|
SERVICE_ACCEPT_STOP |
|
||
|
SERVICE_ACCEPT_SHUTDOWN,
|
||
|
NO_ERROR);
|
||
|
|
||
|
_OperationLog.Add( COperationLog::TRKSVR_START );
|
||
|
}
|
||
|
|
||
|
__except( BreakOnDebuggableException() )
|
||
|
{
|
||
|
// Don't log an event for protseq-not-supported; this happens during a normal
|
||
|
// setup.
|
||
|
if( HRESULT_FROM_WIN32(RPC_S_PROTSEQ_NOT_SUPPORTED) != GetExceptionCode() )
|
||
|
{
|
||
|
TrkReportEvent( EVENT_TRK_SERVICE_START_FAILURE, EVENTLOG_ERROR_TYPE,
|
||
|
static_cast<const TCHAR*>( CHexStringize( GetExceptionCode() )),
|
||
|
NULL );
|
||
|
}
|
||
|
TrkRaiseException( GetExceptionCode() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::UnInitialize
|
||
|
//
|
||
|
// Cancel any out-going RPCs, stop all timers, close everything down,
|
||
|
// and send a service_stopped to the SCM.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::UnInitialize(HRESULT hr)
|
||
|
{
|
||
|
if (_fInitializeCalled)
|
||
|
{
|
||
|
_fInitializeCalled = FALSE;
|
||
|
|
||
|
// Cancel any out-going RPCs on threads in this service
|
||
|
|
||
|
if( NULL != g_pActiveThreadList )
|
||
|
g_pActiveThreadList->CancelAllRpc();
|
||
|
|
||
|
// stop classes that use threads first ...
|
||
|
_rpc.UnInitialize( _pSvcsGlobalData );
|
||
|
_timerGC.UnInitialize();
|
||
|
_csWritesPerHour.UnInitialize();
|
||
|
|
||
|
// ... then release used resources
|
||
|
_qtable.UnInitialize();
|
||
|
_voltab.UnInitialize();
|
||
|
_idt.UnInitialize();
|
||
|
_cdt.UnInitialize();
|
||
|
_dbc.UnInitialize();
|
||
|
_denial.UnInitialize();
|
||
|
|
||
|
if (_configSvr.GetTestFlags() & TRK_TEST_FLAG_WAIT_ON_EXIT)
|
||
|
{
|
||
|
TrkLog((TRKDBG_ERROR, TEXT("Waiting 60 seconds before exitting for heap dump")));
|
||
|
Sleep(60000);
|
||
|
}
|
||
|
|
||
|
#if PRIVATE_THREAD_POOL
|
||
|
{
|
||
|
g_pworkman2->UnInitialize();
|
||
|
delete g_pworkman2;
|
||
|
g_pworkman2 = NULL;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
g_ptrksvr = NULL;
|
||
|
|
||
|
// If the error is protseq-not-supported, ignore it. This is normal
|
||
|
// during setup.
|
||
|
|
||
|
if( (hr & 0x0FFF0000) == FACILITY_WIN32 )
|
||
|
hr = hr & ~(0x0FFF0000);
|
||
|
|
||
|
_svcctrl.SetServiceStatus(SERVICE_STOPPED, 0,
|
||
|
HRESULT_FROM_WIN32(RPC_S_PROTSEQ_NOT_SUPPORTED) == hr ? 0 : hr
|
||
|
);
|
||
|
//_svcctrl.UnInitialize();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::ServiceHandler
|
||
|
//
|
||
|
// This method gets called by the SCM for notification of all service
|
||
|
// activity.
|
||
|
//
|
||
|
// NOTE: In services.exe, this method is called on the one and only ServiceHandler
|
||
|
// thread. So while we execute, no other service in this process can
|
||
|
// receive notifications. Thus it is important that we do nothing
|
||
|
// blocking or time-consuming here.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
DWORD
|
||
|
CTrkSvrSvc::ServiceHandler(DWORD dwControl,
|
||
|
DWORD dwEventType,
|
||
|
PVOID EventData,
|
||
|
PVOID pData)
|
||
|
{
|
||
|
DWORD dwRet = NO_ERROR;
|
||
|
|
||
|
|
||
|
switch (dwControl)
|
||
|
{
|
||
|
case SERVICE_CONTROL_SHUTDOWN:
|
||
|
case SERVICE_CONTROL_STOP:
|
||
|
|
||
|
_fStopping = TRUE;
|
||
|
_qtable.OnServiceStopRequest();
|
||
|
|
||
|
ServiceStopCallback( this, FALSE );
|
||
|
|
||
|
break;
|
||
|
case SERVICE_CONTROL_PAUSE:
|
||
|
break;
|
||
|
case SERVICE_CONTROL_CONTINUE:
|
||
|
break;
|
||
|
case SERVICE_CONTROL_INTERROGATE:
|
||
|
break;
|
||
|
default:
|
||
|
dwRet = ERROR_CALL_NOT_IMPLEMENTED;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return(dwRet);
|
||
|
}
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::RaiseIfStopped
|
||
|
//
|
||
|
// This method raises an exception if a global flag is set indicating
|
||
|
// that we've received a service stop/shutdown request. This is used
|
||
|
// in places where we have a thread that could run for a while; we periodically
|
||
|
// call this method to prevent service stop from blocking.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::RaiseIfStopped()
|
||
|
{
|
||
|
if ( * _svcctrl.GetStopFlagAddress() )
|
||
|
TrkRaiseException( TRK_E_SERVICE_STOPPING );
|
||
|
}
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::CheckWritesPerHour
|
||
|
//
|
||
|
// Check _cWritesPerHour to see if we're writing to much to the DS.
|
||
|
// This is a simplistic algorithm in an effort to reduce risk. We
|
||
|
// just let _cWritesPerHour increment until it hits the max, then
|
||
|
// check to see when that count was started. If more than an
|
||
|
// hour ago, then reset the count & the clock.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
BOOL
|
||
|
CTrkSvrSvc::CheckWritesPerHour()
|
||
|
{
|
||
|
BOOL fExceeded = FALSE;
|
||
|
|
||
|
if( _cWritesPerHour >= _configSvr.GetMaxDSWritesPerHour() )
|
||
|
{
|
||
|
_csWritesPerHour.Enter();
|
||
|
__try
|
||
|
{
|
||
|
// Check the count again, as it may have changed whil we
|
||
|
// were waiting for the critsec.
|
||
|
if( _cWritesPerHour >= _configSvr.GetMaxDSWritesPerHour() )
|
||
|
{
|
||
|
CFILETIME cft; // Defaults to current time
|
||
|
|
||
|
// Did the "hour" for _cWritesPerHour actually start more
|
||
|
// than an hour ago?
|
||
|
|
||
|
cft.DecrementSeconds( _configSvr.GetMaxDSWritesPeriod() ); // An hour
|
||
|
|
||
|
if( cft > _cftWritesPerHour )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("Resetting writes-per-hour clock (%d)"),
|
||
|
_cWritesPerHour ));
|
||
|
|
||
|
// Yes, this write is OK, and we should reset the write time.
|
||
|
_cftWritesPerHour = CFILETIME();
|
||
|
_cWritesPerHour = 0;
|
||
|
_Stats.cCurrentFailedWrites = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TrkLog(( TRKDBG_WARNING,
|
||
|
TEXT("Exceeded writes-per-hour (started at %s)"),
|
||
|
(const TCHAR*) CDebugString(_cftWritesPerHour) ));
|
||
|
|
||
|
if( 0 == _Stats.cCurrentFailedWrites )
|
||
|
_Stats.cMaxDsWriteEvents++;
|
||
|
_Stats.cCurrentFailedWrites++;
|
||
|
|
||
|
fExceeded = TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
__finally
|
||
|
{
|
||
|
_csWritesPerHour.Leave();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fExceeded;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::Scan(
|
||
|
IN const CDomainRelativeObjId * pdroidNotificationCurrent, OPTIONAL
|
||
|
IN const CDomainRelativeObjId * pdroidNotificationNew, OPTIONAL
|
||
|
IN const CDomainRelativeObjId & droidBirth,
|
||
|
OUT CDomainRelativeObjId * pdroidList,
|
||
|
IN int cdroidList,
|
||
|
OUT int * pcSegments,
|
||
|
IN OUT CDomainRelativeObjId * pdroidScan,
|
||
|
OUT BOOL * pfStringDeleted
|
||
|
)
|
||
|
{
|
||
|
CDomainRelativeObjId droidNextBirth, droidNextNew;
|
||
|
BOOL fFound = FALSE;
|
||
|
BOOL fBirthSame = FALSE;
|
||
|
BOOL fCycle = FALSE;
|
||
|
|
||
|
*pfStringDeleted = FALSE;
|
||
|
|
||
|
//
|
||
|
// loop through the string until the birth ids don't match, or
|
||
|
// we've run out of buffer space, or we get to the end of the string
|
||
|
//
|
||
|
|
||
|
do
|
||
|
{
|
||
|
if (pdroidNotificationCurrent && *pdroidScan == *pdroidNotificationCurrent)
|
||
|
{
|
||
|
TrkAssert(pdroidNotificationNew);
|
||
|
|
||
|
droidNextNew = *pdroidNotificationNew;
|
||
|
droidNextBirth = droidBirth;
|
||
|
fFound = TRUE;
|
||
|
*pfStringDeleted = FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fFound = _idt.Query(*pdroidScan, &droidNextNew, &droidNextBirth, pfStringDeleted );
|
||
|
RaiseIfStopped();
|
||
|
}
|
||
|
|
||
|
if (fFound)
|
||
|
{
|
||
|
TrkLog((TRKDBG_MEND, TEXT("CTrkSvrSvc::Scan() - iSegment=%d, %s --> %s [%s] found"),
|
||
|
*pcSegments,
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(*pdroidScan)),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(droidNextNew)),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(droidNextBirth)) ));
|
||
|
|
||
|
// Check to see if we've already been here before.
|
||
|
// E.g., don't loop forever on A->Ba, B->Aa.
|
||
|
|
||
|
for( int j = 0; j < *pcSegments; j++ )
|
||
|
{
|
||
|
if( pdroidList[ j ] == droidNextNew )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_MEND, TEXT("Cycle detected during mend (on %s)"),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(*pdroidScan)) ));
|
||
|
fCycle = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( !fCycle )
|
||
|
{
|
||
|
fBirthSame = droidNextBirth == droidBirth;
|
||
|
if (fBirthSame)
|
||
|
{
|
||
|
pdroidList[ (*pcSegments)++ ] = *pdroidScan;
|
||
|
*pdroidScan = droidNextNew;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We can stop searching. We found a segment that starts
|
||
|
// with *pdroidScan, but it's from another string because
|
||
|
// it has a different birth ID.
|
||
|
|
||
|
TrkLog(( TRKDBG_MEND, TEXT("Birth IDs don't match: %s, %s"),
|
||
|
(const TCHAR*) CDebugString(droidBirth),
|
||
|
(const TCHAR*) CDebugString(droidNextBirth) ));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} while ( *pcSegments < cdroidList && fFound && fBirthSame && !fCycle );
|
||
|
|
||
|
if ( *pcSegments == cdroidList || fCycle )
|
||
|
{
|
||
|
TrkRaiseException(TRK_E_TOO_MANY_UNSHORTENED_NOTIFICATIONS);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::MoveNotify
|
||
|
//
|
||
|
// Handle a move notify request from trkwks.
|
||
|
//
|
||
|
// This routine is complicated by DS replication. It is possible that two trksvr
|
||
|
// services may modify the same entry in the IDT within a replication window.
|
||
|
// The only way to prevent this is to design such that only the designated DC
|
||
|
// modifies entries. For this MoveNotify routine, that would mean that an
|
||
|
// entry would be added for each notification, the designated DC would then
|
||
|
// shorten the base entry, and delete this new one. That's not friendly
|
||
|
// to the DS, however, because deleted objects must continue to be stored
|
||
|
// for an extended period of time.
|
||
|
//
|
||
|
// Consequently, if this notify modifies an existing entry, this routine
|
||
|
// performs a modify rather than an add. For example, if a file is
|
||
|
// moved from A to B to C, and this routine is being called for that
|
||
|
// second move, it would just modify the existing entry from A->B to
|
||
|
// A->C.
|
||
|
//
|
||
|
// The risk here is that another DC will attempt to modify this entry
|
||
|
// within the same replication window. We don't have to worry though
|
||
|
// about another DC doing a notify; trkwks only sends to one DC.
|
||
|
// There are two cases to worry about. One is the case where another DC
|
||
|
// marks an entry to be deleted. If that happens after we do the modify
|
||
|
// here, then there is no problem; the entry is no longer needed anyway.
|
||
|
// If that happens before we do our modify here, then the delete flag
|
||
|
// will be lost. This case is rare, and the unnecessary entry won't
|
||
|
// stay in the table forever; it will be garbage collected.
|
||
|
//
|
||
|
// The other case of potential conflict is if an entry has not
|
||
|
// yet been counted; in this case the designated DC might count
|
||
|
// it and clear the uncounted flag. If our modify causes that
|
||
|
// flag to be uncleared, then the move table count would be corrupted.
|
||
|
// So if the uncounted flag is set, we do an add rather than a modify.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::MoveNotify(const CDomainRelativeObjId &droidCurrent,
|
||
|
const CDomainRelativeObjId &droidBirth,
|
||
|
const CDomainRelativeObjId &droidNew,
|
||
|
BOOL *pfQuotaExceeded )
|
||
|
{
|
||
|
BOOL fAdded = FALSE, fModified = FALSE, fExists = FALSE;
|
||
|
BOOL fDeleted = FALSE, fCounted = FALSE;
|
||
|
|
||
|
// ignore cross-domain moves for now
|
||
|
|
||
|
CDomainRelativeObjId droidNextNew;
|
||
|
CDomainRelativeObjId droidNextBirth;
|
||
|
CDomainRelativeObjId droidNewIDT, droidBirthIDT;
|
||
|
|
||
|
TrkLog((TRKDBG_MOVE, TEXT("CTrkSvrSvc::MoveNotify\n curr = %s\n new = %s\n birth = %s"),
|
||
|
(const TCHAR*)CDebugString(droidCurrent),
|
||
|
(const TCHAR*) CDebugString(droidNew),
|
||
|
(const TCHAR*) CDebugString(droidBirth) ));
|
||
|
|
||
|
// Does the entry exist already?
|
||
|
|
||
|
fExists = _idt.Query( droidBirth, &droidNewIDT, &droidBirthIDT, &fDeleted, &fCounted );
|
||
|
|
||
|
|
||
|
#if DBG
|
||
|
if( fExists )
|
||
|
TrkLog(( TRKDBG_MOVE, TEXT("Birth entry already exists (%s, %s)"),
|
||
|
fDeleted ? TEXT("deleted") : TEXT("not deleted"),
|
||
|
fCounted ? TEXT("counted") : TEXT("not counted") ));
|
||
|
#endif
|
||
|
|
||
|
if( fExists
|
||
|
&&
|
||
|
fCounted
|
||
|
&&
|
||
|
droidNewIDT == droidCurrent )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_MOVE, TEXT("Attempting to modify existing entry") ));
|
||
|
|
||
|
// The birth entry for this file already points to the source
|
||
|
// of the notify. We can just modify it.
|
||
|
|
||
|
fModified = _idt.Modify( droidBirth, droidNew, droidBirth );
|
||
|
}
|
||
|
|
||
|
// If the modify didn't work or wasn't attempted, then just add this
|
||
|
// new entry
|
||
|
|
||
|
if( !fModified )
|
||
|
fAdded = _idt.Add( droidCurrent, droidNew, droidBirth, pfQuotaExceeded );
|
||
|
|
||
|
TrkLog((TRKDBG_MEND, TEXT("CTrkSvrSvc::MoveNotify() %s %s --> %s [%s]"),
|
||
|
fModified ? TEXT("modified")
|
||
|
: (fAdded ? TEXT("added") : TEXT("couldn't be added") ),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(droidCurrent)),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(droidNew)),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(droidBirth)) ));
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::Search
|
||
|
//
|
||
|
// Given a droid, look up the new droid for that object, and look up
|
||
|
// the mcid of the machine that owns that droid's volume.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::Search(/*in, out*/ TRK_FILE_TRACKING_INFORMATION *pSearch)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
CDomainRelativeObjId droidNew;
|
||
|
CDomainRelativeObjId droidBirth;
|
||
|
CMachineId mcidNew;
|
||
|
BOOL fFoundObject;
|
||
|
|
||
|
IFDBG( TCHAR * ptszRoute=TEXT(""); )
|
||
|
|
||
|
TrkLog(( TRKDBG_MEND, TEXT("Searching for %s"),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(pSearch->droidLast)) ));
|
||
|
|
||
|
// If all the move notifies for a file have reached the DC, we can do a
|
||
|
// lookup based on the birth ID. But if one segment is missing, this would fail.
|
||
|
// So we look up based on the last ID first, and if that files try the birth ID.
|
||
|
|
||
|
// Try to map the last ID to the current droid.
|
||
|
|
||
|
fFoundObject = _idt.Query( pSearch->droidLast, &droidNew, &droidBirth);
|
||
|
if ( fFoundObject )
|
||
|
{
|
||
|
IFDBG( ptszRoute = TEXT("'last' found in IDT"); )
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We couldn't find the last known ID. Try mapping
|
||
|
// from the birth ID.
|
||
|
|
||
|
fFoundObject = _idt.Query( pSearch->droidBirth, &droidNew, &droidBirth );
|
||
|
if( fFoundObject )
|
||
|
{
|
||
|
IFDBG( ptszRoute = TEXT("'birth' found in IDT"); )
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Did we find the new droid for the file?
|
||
|
|
||
|
if ( fFoundObject )
|
||
|
{
|
||
|
// Yes, we found it. Is it really the same file (the birth ID matches)?
|
||
|
|
||
|
if( droidBirth != pSearch->droidBirth )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_MEND, TEXT("Birth ID unexpected:\n %s,\n %s,\n %s"),
|
||
|
(const TCHAR*) CDebugString(droidBirth),
|
||
|
(const TCHAR*) CDebugString(pSearch->droidBirth),
|
||
|
(const TCHAR*) CDebugString(droidNew) ));
|
||
|
pSearch->hr = TRK_E_NOT_FOUND;
|
||
|
goto Exit;
|
||
|
}
|
||
|
|
||
|
// We have a good ID. This file may have multiple segments in the DS.
|
||
|
// Starting with the one we have, scan across any additional segments
|
||
|
// to find the most up-to-date droid.
|
||
|
|
||
|
CDomainRelativeObjId droidList[MAX_SHORTENABLE_SEGMENTS];
|
||
|
int cSegments = 0;
|
||
|
BOOL fStringDeleted = FALSE;
|
||
|
|
||
|
Scan(
|
||
|
NULL,
|
||
|
NULL,
|
||
|
droidBirth,
|
||
|
droidList,
|
||
|
sizeof(droidList)/sizeof(droidList[0]),
|
||
|
&cSegments,
|
||
|
&droidNew,
|
||
|
&fStringDeleted
|
||
|
);
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We couldn't find either the birth or last ID.
|
||
|
|
||
|
pSearch->hr = TRK_E_NOT_FOUND;
|
||
|
TrkLog(( TRKDBG_MEND, TEXT("neither 'birth' nor 'last' found") ));
|
||
|
}
|
||
|
|
||
|
// If we found the object in the move table, look up the machine ID in the
|
||
|
// volume table.
|
||
|
|
||
|
if (fFoundObject)
|
||
|
{
|
||
|
TrkLog((TRKDBG_MEND, TEXT("CTrkSvrSvc::Search( birth=%s last=%s ) successful, 'new=%s', %s"),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(pSearch->droidBirth)),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(pSearch->droidLast)),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(droidNew)),
|
||
|
ptszRoute ));
|
||
|
|
||
|
// Find the volume that holds this droid.
|
||
|
|
||
|
pSearch->hr = _voltab.FindVolume( droidNew.GetVolumeId(),
|
||
|
&mcidNew );
|
||
|
if( S_OK == pSearch->hr )
|
||
|
{
|
||
|
// We found the volume.
|
||
|
|
||
|
TrkLog(( TRKDBG_MEND, TEXT("CTrkSvrSvc::Search, volid found (%s -> %s)"),
|
||
|
(const TCHAR*) CDebugString(droidNew.GetVolumeId()),
|
||
|
(const TCHAR*) CDebugString(mcidNew) ));
|
||
|
|
||
|
pSearch->hr = S_OK;
|
||
|
pSearch->droidLast = droidNew;
|
||
|
pSearch->mcidLast = mcidNew;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We were able to find the object in the move table, but couldn't
|
||
|
// find the volume in the volume table.
|
||
|
|
||
|
TrkLog(( TRKDBG_MEND, TEXT("CTrkSvrSvc::Search, volid not found (%s, %08x)"),
|
||
|
(const TCHAR*) CDebugString(droidNew.GetVolumeId()),
|
||
|
pSearch->hr ));
|
||
|
pSearch->hr = TRK_E_NOT_FOUND;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
// We couldn't find the object in the move table.
|
||
|
|
||
|
TrkLog((TRKDBG_MEND, TEXT("CTrkSvrSvc::Search( birth=%s last=%s ) not found, %s"),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(pSearch->droidBirth)),
|
||
|
static_cast<const TCHAR*>(CAbbreviatedIDString(pSearch->droidLast)),
|
||
|
ptszRoute));
|
||
|
|
||
|
|
||
|
// As an optimization, try looking up the last volume anyway. When this
|
||
|
// search request fails, the trkwks service typically looks up the location
|
||
|
// of the volume ID in droidLast, so we do that lookup now instead of forcing
|
||
|
// trkwks to make a separate request.
|
||
|
|
||
|
hr = _voltab.FindVolume( pSearch->droidLast.GetVolumeId(),
|
||
|
&mcidNew );
|
||
|
if( S_OK == hr )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_MEND, TEXT("CTrkSvrSvc::Search, but last volid found (%s -> %s)"),
|
||
|
(const TCHAR*) CDebugString(pSearch->droidLast.GetVolumeId()),
|
||
|
(const TCHAR*) CDebugString(mcidNew) ));
|
||
|
|
||
|
pSearch->hr = TRK_E_NOT_FOUND_BUT_LAST_VOLUME_FOUND;
|
||
|
pSearch->mcidLast = mcidNew;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TrkLog(( TRKDBG_MEND, TEXT("CTrkSvrSvc::Search, volid not found either (%s, %08x)"),
|
||
|
(const TCHAR*) CDebugString(pSearch->droidLast.GetVolumeId()),
|
||
|
pSearch->hr ));
|
||
|
pSearch->hr = TRK_E_NOT_FOUND_AND_LAST_VOLUME_NOT_FOUND;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
Exit:
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::old_Search
|
||
|
//
|
||
|
// This method is provided for compatibility with NT5/Beta2 clients. Those
|
||
|
// clients would do two RPCs, one to get the new droid, then another to map
|
||
|
// the volid in that droid to an mcid. In the modern SEARCH request, the
|
||
|
// client gets both back in a single call.
|
||
|
//
|
||
|
// The distinction between the two kinds of clients is made by the
|
||
|
// TRKSVR_MESSAGE_TYPE in the request. Old clients pass the value
|
||
|
// now defined as old_SEARCH, new clients pass the value SEARCH.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::old_Search(/*in, out*/ old_TRK_FILE_TRACKING_INFORMATION *pSearch)
|
||
|
{
|
||
|
TRK_FILE_TRACKING_INFORMATION FileTrkInfo;
|
||
|
|
||
|
FileTrkInfo.droidBirth = pSearch->droidBirth;
|
||
|
FileTrkInfo.droidLast = pSearch->droidLast;
|
||
|
|
||
|
Search(&FileTrkInfo);
|
||
|
|
||
|
pSearch->hr = FileTrkInfo.hr;
|
||
|
pSearch->droidLast = FileTrkInfo.droidLast;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::Timer
|
||
|
//
|
||
|
// This callback method is called by the GC timer when it's time to do a
|
||
|
// GC.
|
||
|
//
|
||
|
// This method doesn't raise.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
PTimerCallback::TimerContinuation
|
||
|
CTrkSvrSvc::Timer( ULONG ulTimerContext )
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
BOOL fInvalidateCache = FALSE;
|
||
|
TimerContinuation continuation = CONTINUE_TIMER;
|
||
|
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("\nGC timer has fired") ));
|
||
|
|
||
|
__try
|
||
|
{
|
||
|
// Only the designated DC does garbage collecting.
|
||
|
if( !_qtable.IsDesignatedDc( TRUE ) ) // TRUE => raise on error
|
||
|
{
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("Not GC-ing; not the designated DC") ));
|
||
|
continuation = CONTINUE_TIMER;
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
// See if this domain is too young to do anything.
|
||
|
|
||
|
if( _refreshSequence.GetSequenceNumber()
|
||
|
<
|
||
|
static_cast<SequenceNumber>(_configSvr.GetGCMinCycles()) )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_GARBAGE_COLLECT | TRKDBG_SVR,
|
||
|
TEXT("Nothing to GC (%d)"),
|
||
|
_refreshSequence.GetSequenceNumber() ));
|
||
|
|
||
|
continuation = CONTINUE_TIMER;
|
||
|
__leave;
|
||
|
}
|
||
|
|
||
|
// Is this the part one of the timer ?
|
||
|
|
||
|
if( !_fHesitatingBeforeGC )
|
||
|
{
|
||
|
// Yes, this is part one. We'll do some work, then
|
||
|
// reset the timer for a small delay (so that we don't
|
||
|
// do a bunch of work during system initialization).
|
||
|
|
||
|
#if DBG
|
||
|
{
|
||
|
if( _configSvr.GetGCHesitation() > (5*60) )
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("Hesitating for %d minutes before running GC"),
|
||
|
_configSvr.GetGCHesitation() / 60 ));
|
||
|
else
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("Hesitating for %d seconds before running GC"),
|
||
|
_configSvr.GetGCHesitation() ));
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
_fHesitatingBeforeGC = TRUE;
|
||
|
|
||
|
_timerGC.ReInitialize( _configSvr.GetGCHesitation() ); // Doesn't raise
|
||
|
continuation = CONTINUE_TIMER;
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_fHesitatingBeforeGC = FALSE;
|
||
|
|
||
|
_Stats.cEntriesGCed = 0;
|
||
|
|
||
|
// Update the sequence number
|
||
|
|
||
|
_refreshSequence.IncrementSequenceNumber();
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("Updated the GC counter to %d"),
|
||
|
_refreshSequence.GetSequenceNumber() ));
|
||
|
|
||
|
// See if we need to invalidate the move table count cache (once a month).
|
||
|
// This is done for robustness, so that if the count gets out of sync for any
|
||
|
// reason, we self-correct. _MoveCounterReset holds the sequence number
|
||
|
// of the last time we did an invalidate.
|
||
|
|
||
|
if( (SequenceNumber) _MoveCounterReset.GetValue()
|
||
|
>=
|
||
|
_refreshSequence.GetSequenceNumber() )
|
||
|
{
|
||
|
// Invalid value
|
||
|
TrkLog(( TRKDBG_WARNING,
|
||
|
TEXT("_MoveCounterReset is invalid (%d, %d), resetting"),
|
||
|
_MoveCounterReset.GetValue(),
|
||
|
_refreshSequence.GetSequenceNumber() ));
|
||
|
_MoveCounterReset.Set
|
||
|
( (DWORD) _refreshSequence.GetSequenceNumber() );
|
||
|
}
|
||
|
else if( _MoveCounterReset.GetValue() + _configSvr.GetGCDivisor()
|
||
|
<= _refreshSequence.GetSequenceNumber()
|
||
|
)
|
||
|
{
|
||
|
TrkLog(( TRKDBG_SVR | TRKDBG_GARBAGE_COLLECT,
|
||
|
TEXT("Cache will be invalidated (%d)"), _MoveCounterReset.GetValue() ));
|
||
|
fInvalidateCache = TRUE;
|
||
|
}
|
||
|
|
||
|
// Calculate the seq number of the oldest entry to keep.
|
||
|
|
||
|
ULONG seqOldestToKeep = _refreshSequence.GetSequenceNumber() - _configSvr.GetGCMinCycles() + 1;
|
||
|
|
||
|
TrkLog((TRKDBG_GARBAGE_COLLECT | TRKDBG_SVR,
|
||
|
TEXT("\nGarbage collecting all entries older than %d"),
|
||
|
seqOldestToKeep));
|
||
|
|
||
|
// Delete old entries from the move table
|
||
|
|
||
|
_Stats.cEntriesGCed
|
||
|
+= (SHORT)_idt.GarbageCollect( _refreshSequence.GetSequenceNumber(),
|
||
|
seqOldestToKeep,
|
||
|
_svcctrl.GetStopFlagAddress() );
|
||
|
|
||
|
// And delete old entries from the volume table
|
||
|
|
||
|
_Stats.cEntriesGCed
|
||
|
+= (SHORT)_voltab.GarbageCollect( _refreshSequence.GetSequenceNumber(),
|
||
|
seqOldestToKeep,
|
||
|
_svcctrl.GetStopFlagAddress() );
|
||
|
|
||
|
_OperationLog.Add( COperationLog::TRKSVR_GC, S_OK, CMachineId(MCID_INVALID),
|
||
|
seqOldestToKeep, _Stats.cEntriesGCed );
|
||
|
|
||
|
// Reset the timer to its normal period.
|
||
|
|
||
|
_timerGC.ReInitialize( _configSvr.GetGCPeriod() / _configSvr.GetGCDivisor() );
|
||
|
continuation = CONTINUE_TIMER;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
__except( BreakOnDebuggableException() )
|
||
|
{
|
||
|
hr = GetExceptionCode();
|
||
|
TrkLog(( TRKDBG_WARNING,
|
||
|
TEXT("Ignoring exception in CTrkSvrSvc::Timer (%08x)"),
|
||
|
hr ));
|
||
|
_OperationLog.Add( COperationLog::TRKSVR_GC, hr, CMachineId(MCID_INVALID) );
|
||
|
}
|
||
|
|
||
|
// The Quota table's cached counts may be bad now that we've deleted
|
||
|
// entries from the tables.
|
||
|
|
||
|
if( fInvalidateCache )
|
||
|
{
|
||
|
_qtable.InvalidateCache();
|
||
|
_MoveCounterReset.Set( (DWORD) _refreshSequence.GetSequenceNumber() );
|
||
|
}
|
||
|
|
||
|
TrkAssert( _timerGC.IsRecurring() );
|
||
|
return( continuation );
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
SequenceNumber
|
||
|
CTrkSvrSvc::GetSequenceNumber( const CMachineId & mcidClient, const CVolumeId & volume )
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
SequenceNumber seq;
|
||
|
FILETIME ftLastRefresh;
|
||
|
|
||
|
hr = _voltab.QueryVolume(mcidClient, volume, &seq, &ftLastRefresh);
|
||
|
|
||
|
if( S_OK != hr )
|
||
|
{
|
||
|
// Raise on error. E.g. if mcidClient doesn't own this volume.
|
||
|
TrkLog(( TRKDBG_ERROR,
|
||
|
TEXT("CTrkSvrSvc::GetSequenceNumber --> %08x"), hr ));
|
||
|
TrkRaiseException(hr);
|
||
|
}
|
||
|
|
||
|
return(seq);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::SetSequenceNumber( const CVolumeId & volume, // must ensure that validation already done for volume
|
||
|
SequenceNumber seq )
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
|
||
|
hr = _voltab.SetSequenceNumber( volume, seq );
|
||
|
if (hr != S_OK)
|
||
|
{
|
||
|
TrkRaiseException( hr );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
HRESULT
|
||
|
CTrkSvrSvc::MoveNotify( const CMachineId & mcidClient,
|
||
|
TRKSVR_CALL_MOVE_NOTIFICATION * pMove )
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
SequenceNumber seqExpected;
|
||
|
CVolumeId volid;
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cMoveNotifyRequests) );
|
||
|
|
||
|
pMove->cProcessed = 0;
|
||
|
|
||
|
//
|
||
|
// ensure we have at least one notification because we assume that
|
||
|
// the current volume of the first notification is the same for all
|
||
|
// of the notifications in this rpc.
|
||
|
//
|
||
|
|
||
|
if (pMove->cNotifications == 0)
|
||
|
{
|
||
|
return(S_OK);
|
||
|
}
|
||
|
|
||
|
// Get the machine for this volume and the sequence number expected
|
||
|
// ensure that the machine is actually the owner of the volume.
|
||
|
// (This will raise if mcidClient doesn't own this volid.)
|
||
|
|
||
|
volid = *pMove->pvolid;
|
||
|
seqExpected = GetSequenceNumber(mcidClient, volid);
|
||
|
|
||
|
|
||
|
// Is this the sequence number we were expecting for this client volume?
|
||
|
|
||
|
TrkLog((TRKDBG_MOVE, TEXT("sequence no %d %sexpected for %s (%sforcing, expected %d)"),
|
||
|
pMove->seq,
|
||
|
seqExpected != pMove->seq ? TEXT("un") : TEXT(""),
|
||
|
(const TCHAR*) CDebugString(volid),
|
||
|
pMove->fForceSeqNumber ? TEXT("") : TEXT("not "),
|
||
|
seqExpected
|
||
|
));
|
||
|
|
||
|
|
||
|
if( seqExpected != pMove->seq )
|
||
|
{
|
||
|
// No, it's not the right sequence number.
|
||
|
|
||
|
if( !pMove->fForceSeqNumber )
|
||
|
{
|
||
|
// The caller hasn't requested an override, so this is an error.
|
||
|
|
||
|
pMove->seq = seqExpected;
|
||
|
return TRK_S_OUT_OF_SYNC;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Before processing the actual move notifications, ensure that we
|
||
|
// have enough quota... assume that each move notify is going to
|
||
|
// to need one unit of quota (the writes will actually update
|
||
|
// the quota accurately.)
|
||
|
//
|
||
|
#ifdef VOL_QUOTA
|
||
|
if (pMove->cNotifications > GetAvailableNotificationQuota( ) )
|
||
|
{
|
||
|
TrkRaiseException( TRK_E_NOTIFICATION_QUOTA_EXCEEDED );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
while (pMove->cProcessed < pMove->cNotifications)
|
||
|
{
|
||
|
// the only errors are fatal since we always make a record of
|
||
|
// a notification or merge it with an existing record
|
||
|
|
||
|
BOOL fQuotaExceeded = FALSE;
|
||
|
|
||
|
if( CheckWritesPerHour() )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("Stopping move-notifications due to too many writes (%d)"),
|
||
|
NumWritesThisHour() ));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
MoveNotify(CDomainRelativeObjId( volid, pMove->rgobjidCurrent[pMove->cProcessed] ),
|
||
|
pMove->rgdroidBirth[pMove->cProcessed],
|
||
|
pMove->rgdroidNew[pMove->cProcessed],
|
||
|
&fQuotaExceeded
|
||
|
);
|
||
|
|
||
|
if( fQuotaExceeded )
|
||
|
{
|
||
|
hr = TRK_S_NOTIFICATION_QUOTA_EXCEEDED;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pMove->cProcessed++;
|
||
|
IncrementWritesPerHour();
|
||
|
|
||
|
RaiseIfStopped();
|
||
|
}
|
||
|
|
||
|
if( 0 != pMove->cProcessed )
|
||
|
{
|
||
|
SetSequenceNumber( volid, pMove->seq + pMove->cProcessed );
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("Updated sequence number to %d"), pMove->seq+pMove->cProcessed ));
|
||
|
}
|
||
|
|
||
|
if( 0 != pMove->cNotifications )
|
||
|
{
|
||
|
//TrkLog(( TRKDBG_WARNING, TEXT("pMove = %p"), pMove ));
|
||
|
pMove->cNotifications = 0; // don't need to send the data back
|
||
|
|
||
|
//TrkLog(( TRKDBG_WARNING, TEXT("Free rgdroidNew (%p)"), pMove->rgdroidNew ));
|
||
|
//MIDL_user_free( pMove->rgdroidNew );
|
||
|
pMove->rgdroidNew = NULL;
|
||
|
|
||
|
//TrkLog(( TRKDBG_WARNING, TEXT("Free rgobjidCurrent (%p)"), pMove->rgobjidCurrent ));
|
||
|
//MIDL_user_free( pMove->rgobjidCurrent );
|
||
|
pMove->rgobjidCurrent = NULL;
|
||
|
|
||
|
//TrkLog(( TRKDBG_WARNING, TEXT("Free rgdroidBirth (%p)"), pMove->rgdroidBirth ));
|
||
|
//MIDL_user_free( pMove->rgdroidBirth );
|
||
|
pMove->rgdroidBirth = NULL;
|
||
|
}
|
||
|
|
||
|
return(hr);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
CTrkSvrSvc::VerifyMachineOwnsVolume( const CMachineId &mcid, const CVolumeId & volid )
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
SequenceNumber seq;
|
||
|
FILETIME ft;
|
||
|
|
||
|
hr = _voltab.QueryVolume(
|
||
|
mcid,
|
||
|
volid,
|
||
|
&seq,
|
||
|
&ft);
|
||
|
if (hr != S_OK)
|
||
|
return FALSE;
|
||
|
else
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::DeleteNotify
|
||
|
//
|
||
|
// Process a delete-notify request from a client. This request provides
|
||
|
// information about a file that has been deleted, so that we can purge
|
||
|
// it from the move table.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::DeleteNotify( const CMachineId & mcidClient, TRKSVR_CALL_DELETE * pDelete )
|
||
|
{
|
||
|
CVolumeId vol;
|
||
|
HRESULT hr = TRK_S_VOLUME_NOT_FOUND;
|
||
|
|
||
|
// Loop through all of the notifications in this batch.
|
||
|
|
||
|
for (ULONG i=0; i < pDelete->cdroidBirth; i++)
|
||
|
{
|
||
|
// Look up the current location of the file, and if it
|
||
|
// is on an owned volume, then allow the delete.
|
||
|
|
||
|
CDomainRelativeObjId droidCurrent;
|
||
|
CDomainRelativeObjId droidBirth;
|
||
|
|
||
|
// Don't embark on a slow operation if the service is stopping.
|
||
|
RaiseIfStopped();
|
||
|
|
||
|
// If we've already written a lot to the DS in the past hour,
|
||
|
// abort so we don't flood the replication queue.
|
||
|
|
||
|
if( CheckWritesPerHour() )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_WARNING, TEXT("Stopping delete-notify due to too many writes") ));
|
||
|
TrkRaiseException( TRK_E_SERVER_TOO_BUSY );
|
||
|
}
|
||
|
|
||
|
// Read the existing entry for this file.
|
||
|
|
||
|
if (_idt.Query(pDelete->adroidBirth[i], &droidCurrent, &droidBirth))
|
||
|
{
|
||
|
// The entry exists.
|
||
|
|
||
|
TrkAssert(droidBirth == pDelete->adroidBirth[i]);
|
||
|
|
||
|
// See if this is the same volume that we checked on the
|
||
|
// previous iteration through the loop. If so, no need to
|
||
|
// look up again.
|
||
|
|
||
|
if (vol == droidCurrent.GetVolumeId())
|
||
|
{
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We need to check that whoever sent this delete-notify
|
||
|
// request really owns the volume.
|
||
|
|
||
|
vol = droidCurrent.GetVolumeId();
|
||
|
if( !VerifyMachineOwnsVolume( mcidClient, vol ))
|
||
|
{
|
||
|
TrkLog((TRKDBG_OBJID_DELETIONS,
|
||
|
TEXT("DeleteNotify _voltab.QueryVolume( %s ) -> %s\n"),
|
||
|
(const TCHAR*) CDebugString( vol ),
|
||
|
GetErrorString(hr) ));
|
||
|
|
||
|
vol = CVolumeId();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If the volume is owned, go ahead with the deletion.
|
||
|
|
||
|
if (hr == S_OK)
|
||
|
{
|
||
|
BOOL f = _idt.Delete( pDelete->adroidBirth[i] );
|
||
|
|
||
|
TrkLog((TRKDBG_OBJID_DELETIONS,
|
||
|
TEXT("DeleteNotify _idt.Delete( %s ) -> %s\n"),
|
||
|
(const TCHAR*) CDebugString( pDelete->adroidBirth[i] ),
|
||
|
f ? TEXT("Ok") : TEXT("Not Found") ));
|
||
|
|
||
|
if( f )
|
||
|
IncrementWritesPerHour();
|
||
|
}
|
||
|
} // if (_idt.Query(pDelete->adroidBirth[i], &droidCurrent, &droidBirth))
|
||
|
|
||
|
else
|
||
|
{
|
||
|
// Attempted to delete an entry that doesn't exist.
|
||
|
|
||
|
TrkLog((TRKDBG_OBJID_DELETIONS,
|
||
|
TEXT("DeleteNotify _idt.Query( droidBirth=%s ) not found\n"),
|
||
|
(const TCHAR*) CDebugString( pDelete->adroidBirth[i] ) ));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( 0 != pDelete->cdroidBirth )
|
||
|
{
|
||
|
//MIDL_user_free( pDelete->adroidBirth );
|
||
|
//pDelete->adroidBirth = NULL;
|
||
|
pDelete->cdroidBirth = 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::Refresh( const CMachineId &mcidClient, TRKSVR_CALL_REFRESH * pRefresh )
|
||
|
{
|
||
|
// save away the input and zero the output so we don't marshall a
|
||
|
// ton of refresh info back to the client
|
||
|
|
||
|
ULONG cSources = pRefresh->cSources;
|
||
|
ULONG cVolumes = pRefresh->cVolumes;
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cRefreshRequests) );
|
||
|
|
||
|
pRefresh->cSources = 0;
|
||
|
pRefresh->cVolumes = 0;
|
||
|
|
||
|
// Touch the move table entries
|
||
|
|
||
|
for (ULONG i=0; i < cSources ; i++)
|
||
|
{
|
||
|
// Ensure we're not overloading the replication log
|
||
|
if( CheckWritesPerHour() )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("Aborting refresh due to writes-per-hour") ));
|
||
|
TrkRaiseException( TRK_E_SERVER_TOO_BUSY );
|
||
|
}
|
||
|
|
||
|
// Touch the entry in the move table.
|
||
|
if( _idt.Touch( pRefresh->adroidBirth[i] ))
|
||
|
IncrementWritesPerHour();
|
||
|
}
|
||
|
|
||
|
// Touch the volume table entries
|
||
|
|
||
|
for (i=0; i < cVolumes ; i++)
|
||
|
{
|
||
|
// Ensure we're not overloading the replication log
|
||
|
if( CheckWritesPerHour() )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("Aborting refresh due to writes-per-hour") ));
|
||
|
TrkRaiseException( TRK_E_SERVER_TOO_BUSY );
|
||
|
}
|
||
|
|
||
|
// Ensure this volume is owned by the machine.
|
||
|
// mikehill_test
|
||
|
|
||
|
if( !VerifyMachineOwnsVolume( mcidClient, pRefresh->avolid[i] ))
|
||
|
{
|
||
|
TrkLog(( TRKDBG_WARNING,
|
||
|
TEXT("Machine can't touch volume it doesn't own (%s, %s)"),
|
||
|
(const TCHAR*) CDebugString(mcidClient),
|
||
|
(const TCHAR*) CDebugString(pRefresh->avolid[i]) ));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Touch the entry in the volume table
|
||
|
if( _voltab.Touch( pRefresh->avolid[i] ))
|
||
|
IncrementWritesPerHour();
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::Statistics( TRKSVR_STATISTICS *pStatistics )
|
||
|
{
|
||
|
pStatistics->cSyncVolumeRequests = _Stats.cSyncVolumeRequests;
|
||
|
pStatistics->cSyncVolumeErrors = _Stats.cSyncVolumeErrors;
|
||
|
pStatistics->cSyncVolumeThreads = _Stats.cSyncVolumeThreads;
|
||
|
|
||
|
pStatistics->cCreateVolumeRequests = _Stats.cCreateVolumeRequests;
|
||
|
pStatistics->cCreateVolumeErrors = _Stats.cCreateVolumeErrors;
|
||
|
pStatistics->cClaimVolumeRequests = _Stats.cClaimVolumeRequests;
|
||
|
pStatistics->cClaimVolumeErrors = _Stats.cClaimVolumeErrors;
|
||
|
pStatistics->cQueryVolumeRequests = _Stats.cQueryVolumeRequests;
|
||
|
pStatistics->cQueryVolumeErrors = _Stats.cQueryVolumeErrors;
|
||
|
pStatistics->cFindVolumeRequests = _Stats.cFindVolumeRequests;
|
||
|
pStatistics->cFindVolumeErrors = _Stats.cFindVolumeErrors;
|
||
|
pStatistics->cTestVolumeRequests = _Stats.cTestVolumeRequests;
|
||
|
pStatistics->cTestVolumeErrors = _Stats.cTestVolumeErrors;
|
||
|
|
||
|
pStatistics->cSearchRequests = _Stats.cSearchRequests;
|
||
|
pStatistics->cSearchErrors = _Stats.cSearchErrors;
|
||
|
pStatistics->cSearchThreads = _Stats.cSearchThreads;
|
||
|
|
||
|
pStatistics->cMoveNotifyRequests = _Stats.cMoveNotifyRequests;
|
||
|
pStatistics->cMoveNotifyErrors = _Stats.cMoveNotifyErrors;
|
||
|
pStatistics->cMoveNotifyThreads = _Stats.cMoveNotifyThreads;
|
||
|
|
||
|
pStatistics->cRefreshRequests = _Stats.cRefreshRequests;
|
||
|
pStatistics->cRefreshErrors = _Stats.cRefreshErrors;
|
||
|
pStatistics->cRefreshThreads = _Stats.cRefreshThreads;
|
||
|
pStatistics->lRefreshCounter = _refreshSequence.GetSequenceNumber();
|
||
|
|
||
|
pStatistics->cDeleteNotifyRequests = _Stats.cDeleteNotifyRequests;
|
||
|
pStatistics->cDeleteNotifyErrors = _Stats.cDeleteNotifyErrors;
|
||
|
pStatistics->cDeleteNotifyThreads = _Stats.cDeleteNotifyThreads;
|
||
|
|
||
|
pStatistics->ftLastSuccessfulRequest = _Stats.cftLastSuccessfulRequest;
|
||
|
pStatistics->ftServiceStart = _Stats.cftServiceStartTime;
|
||
|
|
||
|
//pStatistics->ulGCIterationPeriod = _Stats.ulGCIterationPeriod;
|
||
|
//pStatistics->cEntriesToGC = _Stats.cEntriesToGC;
|
||
|
pStatistics->cEntriesGCed = _Stats.cEntriesGCed;
|
||
|
|
||
|
pStatistics->hrLastError = _Stats.hrLastError;
|
||
|
pStatistics->ftNextGC = _timerGC.QueryOriginalDueTime();
|
||
|
pStatistics->cLowestAvailableRpcThreads=_cLowestAvailableThreads;
|
||
|
pStatistics->cAvailableRpcThreads = _cAvailableThreads;
|
||
|
pStatistics->cMaxRpcThreads = MAX_SVR_THREADS;
|
||
|
|
||
|
pStatistics->cNumThreadPoolThreads = g_cThreadPoolThreads;
|
||
|
pStatistics->cMostThreadPoolThreads = g_cThreadPoolMaxThreads;
|
||
|
//pStatistics->SvcCtrlState = _svcctrl.GetState();
|
||
|
pStatistics->cMaxDsWriteEvents = _Stats.cMaxDsWriteEvents;
|
||
|
pStatistics->cCurrentFailedWrites = _Stats.cCurrentFailedWrites;
|
||
|
|
||
|
_qtable.Statistics( pStatistics );
|
||
|
|
||
|
OSVERSIONINFO verinfo;
|
||
|
memset( &verinfo, 0, sizeof(verinfo) );
|
||
|
verinfo.dwOSVersionInfoSize = sizeof(verinfo);
|
||
|
|
||
|
if( GetVersionEx( &verinfo ))
|
||
|
{
|
||
|
pStatistics->Version.dwMajor = verinfo.dwMajorVersion;
|
||
|
pStatistics->Version.dwMinor = verinfo.dwMinorVersion;
|
||
|
pStatistics->Version.dwBuildNumber = verinfo.dwBuildNumber;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Failed GetVersionInfo (%lu)"), GetLastError() ));
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
|
||
|
}
|
||
|
|
||
|
HRESULT
|
||
|
CTrkSvrSvc::SyncVolume(const CMachineId & mcidClient, TRKSVR_SYNC_VOLUME * pSyncVolume,
|
||
|
ULONG cUncountedCreates )
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
switch (pSyncVolume->SyncType)
|
||
|
{
|
||
|
case CREATE_VOLUME:
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cCreateVolumeRequests) );
|
||
|
|
||
|
if( CheckWritesPerHour() )
|
||
|
{
|
||
|
hr = TRK_E_SERVER_TOO_BUSY;
|
||
|
TrkLog(( TRKDBG_VOLTAB | TRKDBG_WARNING,
|
||
|
TEXT("Rejected CreateVolume, too many writes (%d)"),
|
||
|
NumWritesThisHour() ));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = _voltab.PreCreateVolume(
|
||
|
mcidClient,
|
||
|
pSyncVolume->secret,
|
||
|
cUncountedCreates,
|
||
|
&pSyncVolume->volume );
|
||
|
if( SUCCEEDED(hr) )
|
||
|
IncrementWritesPerHour();
|
||
|
}
|
||
|
|
||
|
if(hr == S_OK)
|
||
|
{
|
||
|
TrkLog((TRKDBG_VOLTAB,
|
||
|
TEXT("CreateVolume(machine=%s secret=%s volid(out)=%s) -> VOLUME_OK"),
|
||
|
(const TCHAR*) CDebugString(mcidClient),
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->secret),
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->volume) ));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cCreateVolumeErrors) );
|
||
|
|
||
|
TrkLog((TRKDBG_VOLTAB,
|
||
|
TEXT("CreateVolume(machine=%s secret=%s) -> CreateFailed (%08x)"),
|
||
|
(const TCHAR*) CDebugString(mcidClient),
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->secret),
|
||
|
hr ));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case QUERY_VOLUME:
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cQueryVolumeRequests) );
|
||
|
|
||
|
hr = _voltab.QueryVolume(
|
||
|
mcidClient,
|
||
|
pSyncVolume->volume,
|
||
|
&pSyncVolume->seq,
|
||
|
&pSyncVolume->ftLastRefresh
|
||
|
);
|
||
|
|
||
|
TrkLog((TRKDBG_VOLTAB,
|
||
|
TEXT("QueryVolume(machine=%s volid=%s seq(out)=%d ftLastRefresh(out)=%d) -> %s"),
|
||
|
(const TCHAR*) CDebugString(mcidClient),
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->volume),
|
||
|
pSyncVolume->seq,
|
||
|
pSyncVolume->ftLastRefresh.dwLowDateTime,
|
||
|
GetErrorString(hr)));
|
||
|
|
||
|
if( FAILED(hr) )
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cQueryVolumeErrors) );
|
||
|
|
||
|
break;
|
||
|
|
||
|
case FIND_VOLUME:
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cFindVolumeRequests) );
|
||
|
|
||
|
hr = _voltab.FindVolume(
|
||
|
pSyncVolume->volume,
|
||
|
&pSyncVolume->machine
|
||
|
);
|
||
|
|
||
|
TrkLog((TRKDBG_VOLTAB,
|
||
|
TEXT("FindVolume(volid=%s machine(out)=%s) -> %s"),
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->volume),
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->machine),
|
||
|
GetErrorString(hr)));
|
||
|
|
||
|
if( FAILED(hr) )
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cFindVolumeErrors) );
|
||
|
|
||
|
break;
|
||
|
|
||
|
case CLAIM_VOLUME:
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cClaimVolumeRequests) );
|
||
|
|
||
|
if( CheckWritesPerHour() )
|
||
|
{
|
||
|
hr = TRK_E_SERVER_TOO_BUSY;
|
||
|
TrkLog(( TRKDBG_VOLTAB | TRKDBG_WARNING,
|
||
|
TEXT("Rejected ClaimVolume, too many writes (%d)"),
|
||
|
NumWritesThisHour() ));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = _voltab.ClaimVolume(
|
||
|
mcidClient,
|
||
|
pSyncVolume->volume,
|
||
|
pSyncVolume->secretOld,
|
||
|
pSyncVolume->secret,
|
||
|
&pSyncVolume->seq,
|
||
|
&pSyncVolume->ftLastRefresh
|
||
|
);
|
||
|
if( S_OK == hr ) // Might return TRK_S_VOLUME_NOT_FOUND
|
||
|
IncrementWritesPerHour();
|
||
|
}
|
||
|
|
||
|
TrkLog((TRKDBG_VOLTAB,
|
||
|
TEXT("ClaimVolume(machine=%s volid=%s secret=%s->%s seq(out)=%d ftLastRefresh(out)=%s) -> %s"),
|
||
|
(const TCHAR*) CDebugString(mcidClient),
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->volume),
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->secretOld),
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->secret),
|
||
|
pSyncVolume->seq,
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->ftLastRefresh),
|
||
|
GetErrorString(hr)));
|
||
|
|
||
|
if( FAILED(hr) )
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cClaimVolumeErrors) );
|
||
|
|
||
|
break;
|
||
|
|
||
|
case TEST_VOLUME:
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cTestVolumeRequests) );
|
||
|
|
||
|
if( !(_configSvr.GetTestFlags() & TRK_TEST_FLAG_ALLOC_TEST_VOLUME) )
|
||
|
{
|
||
|
hr = E_NOTIMPL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if( CVolumeSecret() != pSyncVolume->secret )
|
||
|
{
|
||
|
hr = _voltab.SetSecret( pSyncVolume->volume, pSyncVolume->secret );
|
||
|
if( FAILED(hr) ) break;
|
||
|
}
|
||
|
|
||
|
hr = _voltab.SetSequenceNumber( pSyncVolume->volume, pSyncVolume->seq );
|
||
|
if( FAILED(hr) ) break;
|
||
|
|
||
|
if( CMachineId() != pSyncVolume->machine )
|
||
|
{
|
||
|
hr = _voltab.SetMachine( pSyncVolume->volume, pSyncVolume->machine );
|
||
|
if ( FAILED(hr) ) break;
|
||
|
}
|
||
|
|
||
|
if( SUCCEEDED(hr) )
|
||
|
IncrementWritesPerHour();
|
||
|
|
||
|
if( FAILED(hr) )
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cTestVolumeErrors) );
|
||
|
|
||
|
break;
|
||
|
|
||
|
case DELETE_VOLUME:
|
||
|
|
||
|
//InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cDeleteVolumeRequests) );
|
||
|
|
||
|
if( CheckWritesPerHour() )
|
||
|
{
|
||
|
hr = TRK_E_SERVER_TOO_BUSY;
|
||
|
TrkLog(( TRKDBG_VOLTAB | TRKDBG_WARNING,
|
||
|
TEXT("Rejected DeleteVolume, too many writes (%d)"),
|
||
|
NumWritesThisHour() ));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = _voltab.DeleteVolume(
|
||
|
mcidClient,
|
||
|
pSyncVolume->volume
|
||
|
);
|
||
|
if( SUCCEEDED(hr) )
|
||
|
IncrementWritesPerHour();
|
||
|
}
|
||
|
|
||
|
TrkLog((TRKDBG_VOLTAB,
|
||
|
TEXT("DeleteVolume(machine=%s volid=%s -> %s"),
|
||
|
(const TCHAR*) CDebugString(mcidClient),
|
||
|
(const TCHAR*) CDebugString(pSyncVolume->volume),
|
||
|
GetErrorString(hr)));
|
||
|
|
||
|
/*
|
||
|
if( FAILED(hr) )
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cClaimVolumeErrors) );
|
||
|
*/
|
||
|
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
TrkAssert(0 && "unknown switch type in SyncVolume");
|
||
|
hr = TRK_S_VOLUME_NOT_FOUND;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return(hr);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// If we have 0 threads, then accept any pri <=9
|
||
|
// If we have 1 thread, then accept any pri <=9
|
||
|
// If we have 2 threads, then accept any pri <=7
|
||
|
// If we have 3 threads, then accept any pri <=6
|
||
|
// If we have 4 threads, then accept any pri <=5
|
||
|
// If we have 5 threads, then accept any pri <=4
|
||
|
// If we have 6 threads, then accept any pri <=3
|
||
|
// If we have 7 threads, then accept any pri <=0
|
||
|
// If we have 8 threads, then accept any pri <=0
|
||
|
// If we have 9 threads, then accept any pri <=0
|
||
|
// If we have 10 threads, don't accept any
|
||
|
//
|
||
|
|
||
|
BOOL
|
||
|
CTrkSvrSvc::CountPrioritizedThread( const TRKSVR_MESSAGE_UNION * pMsg )
|
||
|
{
|
||
|
|
||
|
static LONG Accept[10] = { 0, 0, 0, 3, 4, 5, 6, 7, 9, 9 };
|
||
|
|
||
|
TrkAssert( ELEMENTS(Accept) == MAX_SVR_THREADS );
|
||
|
|
||
|
LONG l = InterlockedDecrement( &_cAvailableThreads );
|
||
|
|
||
|
// It's not worth a lock to protect this statistic, we'll just hope that we
|
||
|
// don't get pre-empted during the update.
|
||
|
_cLowestAvailableThreads = min( _cLowestAvailableThreads, l );
|
||
|
|
||
|
TrkAssert( l >= -1 && l < MAX_SVR_THREADS );
|
||
|
|
||
|
if (l == -1 || pMsg->Priority > Accept[ l ])
|
||
|
{
|
||
|
InterlockedIncrement( &_cAvailableThreads );
|
||
|
return( FALSE );
|
||
|
}
|
||
|
|
||
|
return(TRUE);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::ReleasePrioritizedThread()
|
||
|
{
|
||
|
LONG l = InterlockedIncrement( &_cAvailableThreads );
|
||
|
|
||
|
TrkAssert( l >= 0 && l <= MAX_SVR_THREADS );
|
||
|
}
|
||
|
|
||
|
HRESULT
|
||
|
CTrkSvrSvc::CreateVolume(const CMachineId & mcidClient, const TRKSVR_SYNC_VOLUME& pSyncVolume)
|
||
|
{
|
||
|
return _voltab.AddVolidToTable(pSyncVolume.volume, mcidClient, pSyncVolume.secret );
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::OnRequestStart( TRKSVR_MESSAGE_TYPE MsgType )
|
||
|
{
|
||
|
switch( MsgType )
|
||
|
{
|
||
|
case SEARCH:
|
||
|
case old_SEARCH:
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSearchRequests) );
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSearchThreads) );
|
||
|
break;
|
||
|
|
||
|
case MOVE_NOTIFICATION:
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cMoveNotifyRequests) );
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cMoveNotifyThreads) );
|
||
|
break;
|
||
|
|
||
|
case REFRESH:
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cRefreshRequests) );
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cRefreshThreads) );
|
||
|
break;
|
||
|
|
||
|
case SYNC_VOLUMES:
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSyncVolumeRequests) );
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSyncVolumeThreads) );
|
||
|
break;
|
||
|
|
||
|
case DELETE_NOTIFY:
|
||
|
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cDeleteNotifyRequests) );
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cDeleteNotifyThreads) );
|
||
|
break;
|
||
|
|
||
|
case STATISTICS:
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Invalid MsgType in CTrkSvrSvc::OnRequestStart(%d)"),
|
||
|
MsgType ));
|
||
|
TrkAssert( FALSE );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
CTrkSvrSvc::OnRequestEnd( TRKSVR_MESSAGE_UNION * pMsg, const CMachineId &mcid, HRESULT hr )
|
||
|
{
|
||
|
int i = 0;
|
||
|
|
||
|
__try
|
||
|
{
|
||
|
switch( pMsg->MessageType )
|
||
|
{
|
||
|
case SEARCH:
|
||
|
case old_SEARCH:
|
||
|
|
||
|
InterlockedDecrement( reinterpret_cast<LONG*>(&_Stats.cSearchThreads) );
|
||
|
if( FAILED(hr)
|
||
|
||
|
||
|
( 1 <= pMsg->Search.cSearch && S_OK != pMsg->Search.pSearches->hr ))
|
||
|
{
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSearchErrors) );
|
||
|
}
|
||
|
_OperationLog.Add( COperationLog::TRKSVR_SEARCH, hr, mcid, pMsg->Search.pSearches->droidBirth );
|
||
|
|
||
|
break;
|
||
|
|
||
|
case MOVE_NOTIFICATION:
|
||
|
|
||
|
InterlockedDecrement( reinterpret_cast<LONG*>(&_Stats.cMoveNotifyThreads) );
|
||
|
if( S_OK != hr )
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cMoveNotifyErrors) );
|
||
|
_OperationLog.Add( COperationLog::TRKSVR_MOVE_NOTIFICATION, hr, mcid );
|
||
|
|
||
|
break;
|
||
|
|
||
|
case REFRESH:
|
||
|
|
||
|
InterlockedDecrement( reinterpret_cast<LONG*>(&_Stats.cRefreshThreads) );
|
||
|
if( FAILED(hr) )
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cRefreshErrors) );
|
||
|
_OperationLog.Add( COperationLog::TRKSVR_REFRESH, hr, mcid, pMsg->Refresh.cSources, pMsg->Refresh.cVolumes );
|
||
|
|
||
|
break;
|
||
|
|
||
|
case SYNC_VOLUMES:
|
||
|
|
||
|
InterlockedDecrement( reinterpret_cast<LONG*>(&_Stats.cSyncVolumeThreads) );
|
||
|
if( FAILED(hr) )
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cSyncVolumeErrors) );
|
||
|
_OperationLog.Add( COperationLog::TRKSVR_SYNC_VOLUMES, hr, mcid );
|
||
|
break;
|
||
|
|
||
|
case DELETE_NOTIFY:
|
||
|
|
||
|
InterlockedDecrement( reinterpret_cast<LONG*>(&_Stats.cDeleteNotifyThreads) );
|
||
|
if( FAILED(hr) )
|
||
|
InterlockedIncrement( reinterpret_cast<LONG*>(&_Stats.cDeleteNotifyErrors) );
|
||
|
_OperationLog.Add( COperationLog::TRKSVR_DELETE_NOTIFY, hr, mcid );
|
||
|
|
||
|
break;
|
||
|
|
||
|
case STATISTICS:
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
if( FAILED(hr) )
|
||
|
SetLastError( hr );
|
||
|
else if( STATISTICS != pMsg->MessageType )
|
||
|
_Stats.cftLastSuccessfulRequest = CFILETIME();
|
||
|
}
|
||
|
__except( BreakOnDebuggableException() )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_WARNING, TEXT("Ignoring exception in OnRequestEnd") ));
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//+----------------------------------------------------------------------------
|
||
|
//
|
||
|
// CTrkSvrSvc::SvrMessage
|
||
|
//
|
||
|
// This is the primary starting point for processing of the trksvr
|
||
|
// service's LnkSvrMessage RPC method. For the most part, it looks
|
||
|
// at the MessageType (the request is in the form of a union, with
|
||
|
// a message-type and type-appropriate parameters), then switches
|
||
|
// to specific handler routine.
|
||
|
//
|
||
|
//+----------------------------------------------------------------------------
|
||
|
|
||
|
HRESULT
|
||
|
CTrkSvrSvc::SvrMessage(
|
||
|
handle_t IDL_handle,
|
||
|
TRKSVR_MESSAGE_UNION * pMsg)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
ULONG i;
|
||
|
ULONG cSources;
|
||
|
CMachineId mcidClient;
|
||
|
SThreadFromPoolState OriginalThreadFromPoolState;
|
||
|
|
||
|
if (GetState() != SERVICE_RUNNING)
|
||
|
{
|
||
|
return(TRK_E_SERVICE_NOT_RUNNING);
|
||
|
}
|
||
|
|
||
|
// If we're getting busy (wrt active threads), we may have
|
||
|
// to reject this request, based on how busy we are and the
|
||
|
// priority of the request.
|
||
|
|
||
|
if (!CountPrioritizedThread( pMsg ))
|
||
|
{
|
||
|
return(TRK_E_SERVER_TOO_BUSY);
|
||
|
}
|
||
|
|
||
|
|
||
|
__try
|
||
|
{
|
||
|
|
||
|
// Set thread-specific settings, saving the old settings.
|
||
|
OriginalThreadFromPoolState = InitializeThreadFromPool();
|
||
|
|
||
|
// Update statistics
|
||
|
OnRequestStart( pMsg->MessageType );
|
||
|
|
||
|
// Query the IDL handle for the client's mcid. Don't do it, though, if
|
||
|
// we're not using secure RPC (which only happens in testing),
|
||
|
// or if the message type is statistics.
|
||
|
|
||
|
if( STATISTICS == pMsg->MessageType )
|
||
|
{
|
||
|
// All messages that come in to this routine are from a machine account
|
||
|
// (in which trkwks runs). The exception to this rule is the Statistics
|
||
|
// request, which comes from a user. We'll allow all Authenticated Users
|
||
|
// access to the statistics (they can access the DS tables by default
|
||
|
// anyway).
|
||
|
|
||
|
if( RequireSecureRPC() )
|
||
|
{
|
||
|
RPC_STATUS RpcStatus;
|
||
|
|
||
|
RpcStatus = RpcImpersonateClient(IDL_handle);
|
||
|
if (RpcStatus != RPC_S_OK)
|
||
|
TrkRaiseWin32Error(RpcStatus);
|
||
|
RpcRevertToSelf();
|
||
|
}
|
||
|
mcidClient = CMachineId( MCID_LOCAL );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mcidClient = NULL != pMsg->ptszMachineID && !g_ptrksvr->RequireSecureRPC()
|
||
|
? CMachineId(pMsg->ptszMachineID)
|
||
|
: CMachineId(IDL_handle);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Check for a client doing a denial-of-service attack.
|
||
|
|
||
|
if( RequireSecureRPC() ) // Always true except in testing
|
||
|
CheckClient(mcidClient);
|
||
|
|
||
|
// Switch on the message type.
|
||
|
|
||
|
switch (pMsg->MessageType)
|
||
|
{
|
||
|
|
||
|
case SEARCH:
|
||
|
|
||
|
TrkLog((TRKDBG_MEND|TRKDBG_SVR, TEXT("SEARCH from \\\\%s"),
|
||
|
(const TCHAR*) CDebugString( mcidClient )));
|
||
|
|
||
|
for (i=0; i<pMsg->Search.cSearch; i++)
|
||
|
{
|
||
|
TrkAssert( NULL != pMsg->Search.pSearches );
|
||
|
pMsg->Search.pSearches[i].hr = TRK_E_UNAVAILABLE;
|
||
|
}
|
||
|
|
||
|
for (i=0; i<pMsg->Search.cSearch; i++)
|
||
|
{
|
||
|
Search(&pMsg->Search.pSearches[i]);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case old_SEARCH:
|
||
|
|
||
|
TrkLog((TRKDBG_MEND|TRKDBG_SVR, TEXT("old_SEARCH from \\\\%s"),
|
||
|
(const TCHAR*) CDebugString( mcidClient )));
|
||
|
|
||
|
for (i=0; i<pMsg->old_Search.cSearch; i++)
|
||
|
{
|
||
|
pMsg->old_Search.pSearches[i].hr = TRK_E_UNAVAILABLE;
|
||
|
}
|
||
|
|
||
|
for (i=0; i<pMsg->old_Search.cSearch; i++)
|
||
|
{
|
||
|
old_Search(&pMsg->old_Search.pSearches[i]);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case MOVE_NOTIFICATION:
|
||
|
|
||
|
TrkLog((TRKDBG_MOVE|TRKDBG_SVR, TEXT("MOVE_NOTIFICATION from \\\\%s (%d notifications)"),
|
||
|
(const TCHAR*) CDebugString( mcidClient ),
|
||
|
pMsg->MoveNotification.cNotifications ));
|
||
|
|
||
|
hr = MoveNotify( mcidClient, &pMsg->MoveNotification );
|
||
|
break;
|
||
|
|
||
|
case REFRESH:
|
||
|
|
||
|
TrkLog((TRKDBG_GARBAGE_COLLECT|TRKDBG_SVR, TEXT("REFRESH from \\\\%s"),
|
||
|
(const TCHAR*) CDebugString( mcidClient )));
|
||
|
|
||
|
Refresh( mcidClient, &pMsg->Refresh );
|
||
|
|
||
|
break;
|
||
|
|
||
|
case SYNC_VOLUMES:
|
||
|
{
|
||
|
|
||
|
BOOL fHaveCreateVolume = FALSE;
|
||
|
TRKSVR_SYNC_VOLUME rgCopyOfSyncVolumes[NUM_VOLUMES];
|
||
|
ULONG cUncountedCreates = 0;
|
||
|
|
||
|
// Validate the number of volumes in this request to protect against an unruly
|
||
|
// client.
|
||
|
|
||
|
if(pMsg->SyncVolumes.cVolumes > NUM_VOLUMES)
|
||
|
{
|
||
|
TrkLog((TRKDBG_ERROR, TEXT("Number of volumes exceeded per machine limit %d"), pMsg->SyncVolumes.cVolumes));
|
||
|
TrkRaiseException( E_INVALIDARG );
|
||
|
}
|
||
|
|
||
|
// Pre-initialize the return buffer.
|
||
|
|
||
|
for (i=0; i < pMsg->SyncVolumes.cVolumes; i++)
|
||
|
{
|
||
|
pMsg->SyncVolumes.pVolumes[i].hr = TRK_S_VOLUME_NOT_FOUND;
|
||
|
}
|
||
|
|
||
|
// Perform the sync for each of the volumes.
|
||
|
|
||
|
for (i=0; i < pMsg->SyncVolumes.cVolumes; i++)
|
||
|
{
|
||
|
// Perform the sync.
|
||
|
|
||
|
pMsg->SyncVolumes.pVolumes[i].hr
|
||
|
= SyncVolume(mcidClient, pMsg->SyncVolumes.pVolumes + i, cUncountedCreates );
|
||
|
|
||
|
// Keep track of the number of CREATE_VOLUME sub-requests.
|
||
|
|
||
|
if(CREATE_VOLUME == pMsg->SyncVolumes.pVolumes[i].SyncType &&
|
||
|
pMsg->SyncVolumes.pVolumes[i].hr == S_OK)
|
||
|
{
|
||
|
fHaveCreateVolume = TRUE;
|
||
|
cUncountedCreates++;
|
||
|
}
|
||
|
|
||
|
// Make a copy of the newly generated volume id so that we can
|
||
|
// add it to the DS later. We want to make sure we don't
|
||
|
// add the entry to the DS until we're sure it made it onto
|
||
|
// the client.
|
||
|
// (There once was a bug where the newly created
|
||
|
// volid was written to the DS, then returned to the client,
|
||
|
// but the client wasn't receiving the response due to an
|
||
|
// security problem. Consequently, the client kept asking
|
||
|
// and asking for the volume ID, all of which went into the DS.
|
||
|
// So now we make sure the client gets it, and we have a
|
||
|
// per-client volume quota.)
|
||
|
|
||
|
rgCopyOfSyncVolumes[i] = pMsg->SyncVolumes.pVolumes[i];
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifdef VOL_REPL
|
||
|
if (pMsg->SyncVolumes.ppVolumeChanges != NULL)
|
||
|
{
|
||
|
CFILETIME ftChangesUntilThisTime;
|
||
|
CVolumeMap VolMap;
|
||
|
|
||
|
if (CFILETIME(-1) != CFILETIME(pMsg->SyncVolumes.ftFirstChange))
|
||
|
{
|
||
|
_voltab.QueryVolumeChanges(
|
||
|
CFILETIME(pMsg->SyncVolumes.ftFirstChange),
|
||
|
&VolMap );
|
||
|
|
||
|
VolMap.MoveTo( &pMsg->SyncVolumes.cChanges, pMsg->SyncVolumes.ppVolumeChanges );
|
||
|
|
||
|
pMsg->SyncVolumes.ftFirstChange = ftChangesUntilThisTime;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// If there were successful CREATE_VOLUME sub-requests in this
|
||
|
// SYNC_VOLUME request, send the new IDs back to the client now,
|
||
|
// and write the volids to the DS.
|
||
|
|
||
|
if(TRUE == fHaveCreateVolume)
|
||
|
{
|
||
|
TrkLog((TRKDBG_LOG, TEXT("Calling back to trkwks")));
|
||
|
__try
|
||
|
{
|
||
|
// Note: A trkwks could tie up several threads by blocking
|
||
|
// in this callback. Worse case this DOS attack blocks
|
||
|
// trksvr, but it can't cause any great harm to the DC or DS.
|
||
|
|
||
|
hr = LnkSvrMessageCallback(pMsg);
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("LnkSvrMessageCallback returned %08x"), hr ));
|
||
|
}
|
||
|
__except(BreakOnDebuggableException())
|
||
|
{
|
||
|
hr = GetExceptionCode();
|
||
|
TrkLog((TRKDBG_ERROR, TEXT("Exception in Callback, %08x"), hr));
|
||
|
}
|
||
|
|
||
|
// If callback is successful, add the created volumes into the DS. Otherwise don't do
|
||
|
// anything.
|
||
|
|
||
|
TrkAssert(pMsg->SyncVolumes.cVolumes <= 26);
|
||
|
if( SUCCEEDED(hr) )
|
||
|
{
|
||
|
for (i=0; i < pMsg->SyncVolumes.cVolumes; i++)
|
||
|
{
|
||
|
if(CREATE_VOLUME == pMsg->SyncVolumes.pVolumes[i].SyncType && S_OK == pMsg->SyncVolumes.pVolumes[i].hr)
|
||
|
{
|
||
|
pMsg->SyncVolumes.pVolumes[i].hr =
|
||
|
CreateVolume(mcidClient, rgCopyOfSyncVolumes[i]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pMsg->SyncVolumes.cVolumes = 0;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DELETE_NOTIFY:
|
||
|
|
||
|
TrkLog((TRKDBG_OBJID_DELETIONS|TRKDBG_SVR,
|
||
|
TEXT("DELETE_NOTIFY from \\\\%s"),
|
||
|
(const TCHAR*) CDebugString( mcidClient )));
|
||
|
|
||
|
DeleteNotify( mcidClient, &pMsg->Delete );
|
||
|
break;
|
||
|
|
||
|
case STATISTICS:
|
||
|
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("TRKSVR_STATISTICS"),
|
||
|
(const TCHAR*) CDebugString(mcidClient) ));
|
||
|
|
||
|
memset( &pMsg->Statistics, 0, sizeof(TRKSVR_STATISTICS) );
|
||
|
Statistics( &pMsg->Statistics );
|
||
|
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
hr = TRK_E_UNKNOWN_SVR_MESSAGE_TYPE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
__except(BreakOnDebuggableException())
|
||
|
{
|
||
|
hr = GetExceptionCode();
|
||
|
TrkLog((TRKDBG_ERROR, TEXT("LnkSvrMessage exception %08X caught during %s"),
|
||
|
hr,
|
||
|
(const TCHAR*) CDebugString(pMsg->MessageType) ));
|
||
|
}
|
||
|
|
||
|
// Update statistics
|
||
|
OnRequestEnd( pMsg, mcidClient, hr );
|
||
|
|
||
|
ReleasePrioritizedThread();
|
||
|
|
||
|
// Restore the thread-specif settings.
|
||
|
UnInitializeThreadFromPool( OriginalThreadFromPoolState );
|
||
|
|
||
|
return(hr);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
StubLnkSvrMessage_Old(
|
||
|
handle_t IDL_handle,
|
||
|
TRKSVR_MESSAGE_UNION_OLD * pMsg)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
|
||
|
TrkLog((TRKDBG_SVR, TEXT("Received downlevel call: ... thunking")));
|
||
|
|
||
|
TRKSVR_MESSAGE_UNION Msg2;
|
||
|
|
||
|
Msg2.MessageType = pMsg->MessageType;
|
||
|
Msg2.Priority = PRI_5;
|
||
|
|
||
|
switch (Msg2.MessageType)
|
||
|
{
|
||
|
case (SEARCH):
|
||
|
Msg2.Search = pMsg->Search;
|
||
|
break;
|
||
|
case (MOVE_NOTIFICATION):
|
||
|
Msg2.MoveNotification = pMsg->MoveNotification;
|
||
|
break;
|
||
|
case (REFRESH):
|
||
|
Msg2.Refresh = pMsg->Refresh;
|
||
|
break;
|
||
|
case (SYNC_VOLUMES):
|
||
|
Msg2.SyncVolumes = pMsg->SyncVolumes;
|
||
|
break;
|
||
|
case (DELETE_NOTIFY):
|
||
|
Msg2.Delete = pMsg->Delete;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
Msg2.ptszMachineID = pMsg->ptszMachineID;
|
||
|
|
||
|
hr = StubLnkSvrMessage( IDL_handle, &Msg2 );
|
||
|
|
||
|
switch (Msg2.MessageType)
|
||
|
{
|
||
|
case (SEARCH):
|
||
|
pMsg->Search = Msg2.Search;
|
||
|
break;
|
||
|
case (MOVE_NOTIFICATION):
|
||
|
pMsg->MoveNotification = Msg2.MoveNotification;
|
||
|
break;
|
||
|
case (REFRESH):
|
||
|
pMsg->Refresh = Msg2.Refresh;
|
||
|
break;
|
||
|
case (SYNC_VOLUMES):
|
||
|
pMsg->SyncVolumes = Msg2.SyncVolumes ;
|
||
|
break;
|
||
|
case (DELETE_NOTIFY):
|
||
|
pMsg->Delete = Msg2.Delete;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pMsg->ptszMachineID = Msg2.ptszMachineID;
|
||
|
|
||
|
return(hr);
|
||
|
|
||
|
}
|
||
|
|
||
|
// must return a positive number (success code) if it doesn't want the caller
|
||
|
// to find another DC to do it on.
|
||
|
|
||
|
HRESULT
|
||
|
StubLnkSvrMessage(
|
||
|
handle_t IDL_handle,
|
||
|
TRKSVR_MESSAGE_UNION * pMsg)
|
||
|
{
|
||
|
return( g_ptrksvr->SvrMessage( IDL_handle, pMsg ));
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
CTrkSvrRpcServer::Initialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData, CTrkSvrConfiguration * pTrkSvrConfig )
|
||
|
{
|
||
|
RPC_STATUS rpcstatus;
|
||
|
NET_API_STATUS netstatus;
|
||
|
|
||
|
// Ensure there's a tcp/ip binding handle
|
||
|
|
||
|
rpcstatus = RpcServerUseProtseq( const_cast<TCHAR*>(s_tszTrkSvrRpcProtocol),
|
||
|
pTrkSvrConfig->GetSvrMaxRpcCalls(), NULL);
|
||
|
|
||
|
if (rpcstatus != RPC_S_OK &&
|
||
|
rpcstatus != RPC_S_DUPLICATE_ENDPOINT)
|
||
|
{
|
||
|
// Log an event, unless it's a not-supported error, which happens during
|
||
|
// a normal setup.
|
||
|
if( RPC_S_PROTSEQ_NOT_SUPPORTED != rpcstatus )
|
||
|
{
|
||
|
TrkLog((TRKDBG_ERROR, TEXT("RpcServerUseProtseqEp %08x"), rpcstatus));
|
||
|
TrkReportInternalError( THIS_FILE_NUMBER, __LINE__, HRESULT_FROM_WIN32(rpcstatus),
|
||
|
TRKREPORT_LAST_PARAM );
|
||
|
}
|
||
|
TrkRaiseWin32Error(rpcstatus);
|
||
|
}
|
||
|
|
||
|
// If we don't have a pSvcsGlobalData (we're not running in services.exe),
|
||
|
// tell RpcServerRegisterIfEx to automatically set up a listen thread.
|
||
|
|
||
|
CRpcServer::Initialize( Stubtrksvr_v1_0_s_ifspec,
|
||
|
NULL == pSvcsGlobalData ? RPC_IF_AUTOLISTEN : 0,
|
||
|
pTrkSvrConfig->GetSvrMaxRpcCalls(),
|
||
|
RpcSecurityEnabled(), // fSetAuthInfo
|
||
|
s_tszTrkSvrRpcProtocol );
|
||
|
|
||
|
TrkLog(( TRKDBG_RPC, TEXT("Registered TrkSvr RPC server %s (%d)"),
|
||
|
RpcSecurityEnabled() ? TEXT("") : TEXT("(without authorization)"),
|
||
|
pTrkSvrConfig->GetSvrMaxRpcCalls() ));
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
CTrkSvrRpcServer::UnInitialize( SVCHOST_GLOBAL_DATA * pSvcsGlobalData )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_RPC, TEXT("Unregistering TrkSvr RPC server") ));
|
||
|
CRpcServer::UnInitialize( );
|
||
|
TrkLog(( TRKDBG_RPC, TEXT("Unregistered TrkSvr RPC server") ));
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
CMachineId::CMachineId(handle_t ClientBinding)
|
||
|
{
|
||
|
RPC_STATUS RpcStatus;
|
||
|
NTSTATUS status;
|
||
|
BOOL f;
|
||
|
HANDLE hToken = NULL;
|
||
|
PTOKEN_USER pToken;
|
||
|
BYTE * pbToken = NULL;
|
||
|
PSID LocalSystemSid = NULL;
|
||
|
|
||
|
PUNICODE_STRING pUserName = NULL;
|
||
|
PUNICODE_STRING pUserDomainName = NULL;
|
||
|
|
||
|
NET_API_STATUS NetStatus;
|
||
|
|
||
|
WCHAR *pwszDomain = NULL;
|
||
|
BOOLEAN fIsWorkGroup;
|
||
|
LPBYTE pbAccountInfo = NULL;
|
||
|
BOOL fImpersonating = FALSE;
|
||
|
|
||
|
// Begin impersonating the user.
|
||
|
|
||
|
RpcStatus = RpcImpersonateClient(ClientBinding);
|
||
|
if (RpcStatus != RPC_S_OK)
|
||
|
TrkRaiseWin32Error(RpcStatus);
|
||
|
fImpersonating = TRUE;
|
||
|
|
||
|
__try
|
||
|
{
|
||
|
WCHAR wszCurrentUser[ UNLEN+1 ];
|
||
|
WCHAR wszDomainName[ CNLEN+1 ];
|
||
|
|
||
|
// Get the local domain name (so we can verify it against the user's).
|
||
|
|
||
|
NetStatus = NetpGetDomainNameEx(&pwszDomain, &fIsWorkGroup);
|
||
|
if (NetStatus != NO_ERROR)
|
||
|
{
|
||
|
pwszDomain = NULL;
|
||
|
TrkReportInternalError( THIS_FILE_NUMBER, __LINE__, HRESULT_FROM_WIN32(NetStatus),
|
||
|
TRKREPORT_LAST_PARAM );
|
||
|
TrkRaiseWin32Error(NetStatus);
|
||
|
}
|
||
|
TrkAssert( !fIsWorkGroup );
|
||
|
|
||
|
// Get the user's name & domain (possible because we're
|
||
|
// impersonating).
|
||
|
|
||
|
status = LsaGetUserName( &pUserName, &pUserDomainName );
|
||
|
if( !NT_SUCCESS(status) )
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get user name (%08x)"), status ));
|
||
|
TrkRaiseNtStatus( status );
|
||
|
}
|
||
|
|
||
|
memcpy( wszCurrentUser, pUserName->Buffer, pUserName->Length );
|
||
|
wszCurrentUser[ pUserName->Length / sizeof(WCHAR) ] = L'\0';
|
||
|
|
||
|
// Ensure this is a user in this domain
|
||
|
{
|
||
|
UNICODE_STRING LocalDomainName = { 0, 0, NULL };
|
||
|
RtlInitUnicodeString( &LocalDomainName, pwszDomain );
|
||
|
|
||
|
if( 0 != RtlCompareUnicodeString( pUserDomainName, &LocalDomainName, TRUE ))
|
||
|
{
|
||
|
TrkLog(( TRKDBG_WARNING, TEXT("User %s is from another domain"),
|
||
|
wszCurrentUser ));
|
||
|
TrkRaiseException( TRK_E_UNKNOWN_SID );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The user name should end in a '$', since it should be a
|
||
|
// machine account.
|
||
|
|
||
|
if (wszCurrentUser[ pUserName->Length / sizeof(WCHAR) - 1] != L'$')
|
||
|
{
|
||
|
// No dollar suffix, it's probably local system on the DC
|
||
|
|
||
|
DWORD SizeRequired;
|
||
|
|
||
|
// Get the user's SID
|
||
|
|
||
|
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken))
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't open thread token for %ws (%08x)"), wszCurrentUser, HRESULT_FROM_WIN32(GetLastError()) ));
|
||
|
TrkRaiseLastError();
|
||
|
}
|
||
|
|
||
|
if (!GetTokenInformation( hToken,
|
||
|
TokenUser,
|
||
|
NULL,
|
||
|
0,
|
||
|
&SizeRequired ) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get thread token information (1) for %ws (%08x)"), wszCurrentUser, HRESULT_FROM_WIN32(GetLastError()) ));
|
||
|
TrkRaiseLastError();
|
||
|
}
|
||
|
|
||
|
pbToken = new BYTE [SizeRequired];
|
||
|
if (pbToken == NULL)
|
||
|
TrkRaiseWin32Error(ERROR_NOT_ENOUGH_MEMORY);
|
||
|
|
||
|
pToken = (PTOKEN_USER) pbToken;
|
||
|
|
||
|
if (!GetTokenInformation( hToken,
|
||
|
TokenUser,
|
||
|
pToken,
|
||
|
SizeRequired,
|
||
|
&SizeRequired ))
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get thread token information (2) for %ws (%08x)"), wszCurrentUser, HRESULT_FROM_WIN32(GetLastError()) ));
|
||
|
TrkRaiseLastError();
|
||
|
}
|
||
|
|
||
|
// Get the local system SID
|
||
|
|
||
|
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
||
|
|
||
|
if (! AllocateAndInitializeSid(
|
||
|
&NtAuthority,
|
||
|
1,
|
||
|
SECURITY_LOCAL_SYSTEM_RID,
|
||
|
0, 0, 0, 0, 0, 0, 0,
|
||
|
&LocalSystemSid ))
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't Alloc/Init SID for %ws (%08x)"), wszCurrentUser, HRESULT_FROM_WIN32(GetLastError()) ));
|
||
|
TrkRaiseLastError();
|
||
|
}
|
||
|
|
||
|
// Verify that the user is local system.
|
||
|
|
||
|
BOOL fEqual = EqualSid( pToken->User.Sid, LocalSystemSid );
|
||
|
|
||
|
if (!fEqual)
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Unknown SID: %ws"), wszCurrentUser ));
|
||
|
TrkRaiseException(TRK_E_UNKNOWN_SID);
|
||
|
}
|
||
|
|
||
|
*this = CMachineId(MCID_LOCAL);
|
||
|
Normalize();
|
||
|
AssertValid();
|
||
|
|
||
|
} // if (wszCurrentUser[ pUserName->Length / sizeof(WCHAR) - 1] != L'$')
|
||
|
|
||
|
else
|
||
|
{
|
||
|
// We don't need to be impersonating any longer, potentially (though
|
||
|
// not likely) ACLs on the SAM could prevent the impersonated user
|
||
|
// from making this call. So we might as well revert now.
|
||
|
|
||
|
TrkAssert( fImpersonating );
|
||
|
if( fImpersonating )
|
||
|
{
|
||
|
RpcRevertToSelf();
|
||
|
fImpersonating = FALSE;
|
||
|
}
|
||
|
|
||
|
// Get account info for this user, so we can
|
||
|
// verify that it's a machine account.
|
||
|
|
||
|
NetStatus = NetUserGetInfo( NULL, // Check on this server
|
||
|
wszCurrentUser,
|
||
|
1, // Get USER_INFO_1
|
||
|
&pbAccountInfo );
|
||
|
|
||
|
|
||
|
if (NetStatus != 0)
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get user %s info (%08x)"),
|
||
|
wszCurrentUser,
|
||
|
NetStatus));
|
||
|
TrkRaiseWin32Error(NetStatus);
|
||
|
}
|
||
|
|
||
|
TrkLog(( TRKDBG_SVR, TEXT("\nUser is %s"), wszCurrentUser));
|
||
|
|
||
|
if ((((USER_INFO_1*)pbAccountInfo)->usri1_flags &
|
||
|
(UF_WORKSTATION_TRUST_ACCOUNT | UF_SERVER_TRUST_ACCOUNT)) == 0)
|
||
|
{
|
||
|
// if the account is not a workstation or backup dc account, fail
|
||
|
TrkRaiseException( TRK_E_CALLER_NOT_MACHINE_ACCOUNT );
|
||
|
}
|
||
|
|
||
|
// overwrite the $
|
||
|
wszCurrentUser[ pUserName->Length / sizeof(WCHAR) - 1] = L'\0';
|
||
|
|
||
|
if (_tcslen(wszCurrentUser) + 1 > sizeof(_szMachine))
|
||
|
TrkRaiseException(TRK_E_IMPERSONATED_COMPUTERNAME_TOO_LONG);
|
||
|
else if( TEXT('\0') == wszCurrentUser[0] )
|
||
|
TrkRaiseException( TRK_E_NULL_COMPUTERNAME );
|
||
|
|
||
|
memset(&_szMachine, 0, sizeof(_szMachine));
|
||
|
|
||
|
|
||
|
// Convert the Unicode computer name into Ansi, using
|
||
|
// the OEMCP codepage (NetBios/computer names are always
|
||
|
// in OEMCP, not Window/Ansi).
|
||
|
|
||
|
if( 0 == WideCharToMultiByte( CP_OEMCP, 0,
|
||
|
wszCurrentUser,
|
||
|
-1,
|
||
|
_szMachine,
|
||
|
sizeof(_szMachine),
|
||
|
NULL, NULL ))
|
||
|
{
|
||
|
TrkLog(( TRKDBG_ERROR,
|
||
|
TEXT("Couldn't convert machine name %s to multi-byte (%lu)"),
|
||
|
wszCurrentUser, GetLastError() ));
|
||
|
TrkRaiseLastError();
|
||
|
}
|
||
|
|
||
|
TrkLog(( TRKDBG_WARNING,
|
||
|
TEXT("Converted machine name: %hs (from %s, ")
|
||
|
MCID_BYTE_FORMAT_STRING,
|
||
|
_szMachine, wszCurrentUser,
|
||
|
(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] ));
|
||
|
|
||
|
Normalize();
|
||
|
AssertValid();
|
||
|
|
||
|
} // if (wszCurrentUser[ pUserName->Length / ... else
|
||
|
}
|
||
|
__finally
|
||
|
{
|
||
|
if( fImpersonating )
|
||
|
{
|
||
|
RpcRevertToSelf();
|
||
|
fImpersonating = FALSE;
|
||
|
}
|
||
|
|
||
|
if (hToken != NULL)
|
||
|
{
|
||
|
CloseHandle(hToken);
|
||
|
}
|
||
|
if (pbToken != NULL)
|
||
|
{
|
||
|
delete [] pbToken;
|
||
|
}
|
||
|
if (LocalSystemSid != NULL)
|
||
|
{
|
||
|
FreeSid( LocalSystemSid );
|
||
|
}
|
||
|
|
||
|
if (pUserName != NULL)
|
||
|
{
|
||
|
LsaFreeMemory(pUserName->Buffer);
|
||
|
LsaFreeMemory(pUserName);
|
||
|
}
|
||
|
if (pUserDomainName != NULL)
|
||
|
{
|
||
|
LsaFreeMemory(pUserDomainName->Buffer);
|
||
|
LsaFreeMemory(pUserDomainName);
|
||
|
}
|
||
|
|
||
|
if (pbAccountInfo != NULL)
|
||
|
{
|
||
|
NetApiBufferFree(pbAccountInfo);
|
||
|
}
|
||
|
|
||
|
if( NULL != pwszDomain )
|
||
|
NetApiBufferFree(pwszDomain);
|
||
|
}
|
||
|
}
|