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.
5906 lines
228 KiB
5906 lines
228 KiB
//--------------------------------------------------------------------
|
|
// w32time - implementation
|
|
// Copyright (C) Microsoft Corporation, 1999
|
|
//
|
|
// Created by: Louis Thomas (louisth), 9-8-99
|
|
//
|
|
// Time service
|
|
//
|
|
|
|
#include "pch.h"
|
|
|
|
#include "AtomicInt64.inl"
|
|
|
|
#include "ErrToFileLog.h"
|
|
|
|
//--------------------------------------------------------------------
|
|
// structures
|
|
|
|
typedef HRESULT (__stdcall
|
|
TimeProvOpenFunc)(
|
|
IN WCHAR * wszName,
|
|
IN TimeProvSysCallbacks * pSysCallbacks,
|
|
OUT TimeProvHandle * phTimeProv);
|
|
|
|
typedef HRESULT (__stdcall
|
|
TimeProvCommandFunc)(
|
|
IN TimeProvHandle hTimeProv,
|
|
IN TimeProvCmd eCmd,
|
|
IN TimeProvArgs pvArgs);
|
|
|
|
typedef HRESULT (__stdcall
|
|
TimeProvCloseFunc)(
|
|
IN TimeProvHandle hTimeProv);
|
|
|
|
struct TimeProvider {
|
|
WCHAR * wszDllName;
|
|
WCHAR * wszProvName;
|
|
bool bInputProvider;
|
|
bool bStarted;
|
|
TimeProvider * ptpNext;
|
|
HINSTANCE hDllInst;
|
|
TimeProvHandle hTimeProv;
|
|
TimeProvCommandFunc * pfnTimeProvCommand;
|
|
TimeProvCloseFunc * pfnTimeProvClose;
|
|
DWORD dwStratum;
|
|
};
|
|
|
|
struct LocalClockConfigInfo {
|
|
DWORD dwLastClockRate;
|
|
DWORD dwMinClockRate;
|
|
DWORD dwMaxClockRate;
|
|
DWORD dwPhaseCorrectRate;
|
|
DWORD dwUpdateInterval;
|
|
DWORD dwFrequencyCorrectRate;
|
|
DWORD dwPollAdjustFactor;
|
|
DWORD dwLargePhaseOffset;
|
|
DWORD dwSpikeWatchPeriod;
|
|
DWORD dwHoldPeriod;
|
|
DWORD dwMinPollInterval;
|
|
DWORD dwMaxPollInterval;
|
|
DWORD dwLocalClockDispersion;
|
|
DWORD dwMaxNegPhaseCorrection;
|
|
DWORD dwMaxPosPhaseCorrection;
|
|
DWORD dwMaxAllowedPhaseOffset;
|
|
};
|
|
|
|
struct ConfigInfo {
|
|
TimeProvider * ptpProviderList;
|
|
LocalClockConfigInfo lcci;
|
|
DWORD dwAnnounceFlags;
|
|
DWORD dwEventLogFlags;
|
|
};
|
|
|
|
struct TimeSampleInfo {
|
|
TimeSample *pts;
|
|
TimeProvider *ptp; // The provider that provided this sample
|
|
};
|
|
|
|
struct EndpointEntry {
|
|
signed __int64 toEndpoint;
|
|
signed int nType;
|
|
};
|
|
struct CandidateEntry {
|
|
unsigned __int64 tpDistance;
|
|
unsigned int nSampleIndex;
|
|
};
|
|
|
|
enum LocalClockState {
|
|
e_Unset,
|
|
e_Hold,
|
|
e_Sync,
|
|
e_Spike,
|
|
};
|
|
|
|
enum ResyncResult {
|
|
e_Success=ResyncResult_Success,
|
|
e_NoData=ResyncResult_NoData,
|
|
e_StaleData=ResyncResult_StaleData,
|
|
e_ChangeTooBig=ResyncResult_ChangeTooBig,
|
|
e_Shutdown=ResyncResult_Shutdown
|
|
};
|
|
|
|
enum WaitTimeoutReason {
|
|
e_RegularPoll,
|
|
e_IrregularPoll,
|
|
e_LongTimeNoSync,
|
|
};
|
|
|
|
enum LocalClockCommand {
|
|
e_ParamChange,
|
|
e_TimeSlip,
|
|
e_RegularUpdate,
|
|
e_IrregularUpdate,
|
|
e_GoUnsyncd,
|
|
};
|
|
|
|
#define ClockFreqPredictErrBufSize 4
|
|
#define SysDispersionBufSize 4
|
|
#define SampleBufInitialSize 10
|
|
struct StateInfo {
|
|
// synchronization
|
|
BOOL bCSInitialized;
|
|
CRITICAL_SECTION csW32Time;
|
|
HANDLE hShutDownEvent;
|
|
HANDLE hClockDisplnThread;
|
|
HANDLE hClockCommandAvailEvent;
|
|
HANDLE hClockCommandCompleteEvent;
|
|
HANDLE hPollIntervalChangeEvent;
|
|
HANDLE hManagerGPUpdateEvent;
|
|
HANDLE hManagerParamChangeEvent;
|
|
HANDLE hTimeSlipEvent; // also, hard resync
|
|
HANDLE hRpcSyncCompleteAEvent;
|
|
HANDLE hRpcSyncCompleteBEvent;
|
|
HANDLE hNetTopoChangeEvent;
|
|
OVERLAPPED olNetTopoIOOverlapped;
|
|
HANDLE hNetTopoIOHandle;
|
|
HANDLE hNetTopoRpcEvent; // rediscover resync (can't overload hNetTopoChangeEvent because we need it to detect IO complete)
|
|
HANDLE hDomHierRoleChangeEvent;
|
|
HANDLE hSamplesAvailEvent;
|
|
|
|
// Wait handles used to de-register objects from the thread pool wait function:
|
|
HANDLE hRegisteredManagerParamChangeEvent;
|
|
HANDLE hRegisteredManagerGPUpdateEvent;
|
|
HANDLE hRegisteredTimeSlipEvent;
|
|
HANDLE hRegisteredNetTopoChangeEvent;
|
|
HANDLE hRegisteredClockDisplnThread;
|
|
HANDLE hRegisteredDomHierRoleChangeEvent;
|
|
HANDLE hRegisteredSamplesAvailEvent;
|
|
|
|
// Timer objects
|
|
HANDLE hTimer;
|
|
|
|
// NTP state
|
|
volatile NtpLeapIndicator eLeapIndicator;
|
|
volatile unsigned int nStratum;
|
|
volatile NtpRefId refidSource;
|
|
volatile signed int nPollInterval;
|
|
asint64 toSysPhaseOffset;
|
|
auint64 qwLastSyncTicks;
|
|
asint64 toRootDelay;
|
|
auint64 tpRootDispersion;
|
|
volatile DWORD dwTSFlags;
|
|
|
|
// transfer from manager to local clock
|
|
TimeSample tsNextClockUpdate;
|
|
TimeSampleInfo tsiNextClockUpdate;
|
|
NtTimePeriod tpSelectDispersion;
|
|
LocalClockCommand eLocalClockCommand;
|
|
// transfer from local clock to manager
|
|
bool bClockJumped;
|
|
NtTimeOffset toClockJump;
|
|
bool bPollIntervalChanged;
|
|
bool bStaleData;
|
|
bool bClockChangeTooBig;
|
|
NtTimeOffset toIgnoredChange;
|
|
WCHAR wszSourceName[256];
|
|
bool bSourceChanged;
|
|
bool bControlClockFromSoftware;
|
|
bool bPhaseSpike;
|
|
bool bFrequencySpike;
|
|
|
|
// local clock state
|
|
signed __int64 toKnownPhaseOffset;
|
|
unsigned __int64 qwPhaseCorrectStartTickCount;
|
|
unsigned __int64 qwLastUpdateTickCount;
|
|
DWORD dwClockRate;
|
|
signed __int32 nPhaseCorrectRateAdj;
|
|
signed __int32 nRateAdj;
|
|
signed __int32 nFllRateAdj;
|
|
signed __int32 nPllRateAdj;
|
|
unsigned int nErrorIndex;
|
|
double rgdFllError[ClockFreqPredictErrBufSize];
|
|
double rgdPllError[ClockFreqPredictErrBufSize];
|
|
DWORD dwPllLoopGain;
|
|
unsigned int nSysDispersionIndex;
|
|
unsigned __int64 rgtpSysDispersion[SysDispersionBufSize];
|
|
unsigned int nPollUpdateCounter;
|
|
LocalClockState lcState;
|
|
unsigned int nHoldCounter;
|
|
unsigned __int64 teSpikeStart;
|
|
WCHAR wszPreUnsyncSourceName[256];
|
|
WCHAR wszPreTimeSlipSourceName[256];
|
|
|
|
// manager state
|
|
ConfigInfo * pciConfig;
|
|
unsigned __int64 tpPollDelayRemaining;
|
|
unsigned __int64 teManagerWaitStart;
|
|
unsigned __int64 tpIrregularDelayRemaining;
|
|
unsigned __int64 tpTimeSinceLastSyncAttempt;
|
|
unsigned __int64 tpTimeSinceLastGoodSync;
|
|
unsigned __int64 tpWaitInterval;
|
|
signed int nClockPrecision;
|
|
TimeSample * rgtsSampleBuf;
|
|
TimeSampleInfo * rgtsiSampleInfoBuf;
|
|
EndpointEntry * rgeeEndpointList;
|
|
CandidateEntry * rgceCandidateList;
|
|
unsigned int nSampleBufAllocSize;
|
|
bool bTimeSlipNotificationStarted;
|
|
bool bNetTopoChangeNotificationStarted;
|
|
bool bGPNotificationStarted;
|
|
ResyncResult eLastRegSyncResult;
|
|
WaitTimeoutReason eTimeoutReason;
|
|
bool bDontLogClockChangeTooBig;
|
|
DWORD dwEventLogFlags;
|
|
bool bIsDomainRoot;
|
|
DSROLE_MACHINE_ROLE eMachineRole;
|
|
|
|
CRITICAL_SECTION csAPM;
|
|
bool bCSAPMInitialized;
|
|
bool bAPMStoppedFileLog;
|
|
bool bAPMAcquiredSystemClock;
|
|
|
|
CRITICAL_SECTION csTickCount;
|
|
bool bCSTickCountInitialized;
|
|
HANDLE hTickCountRefreshTimer;
|
|
|
|
// RPC state
|
|
bool bRpcServerStarted;
|
|
volatile DWORD dwNetlogonServiceBits;
|
|
volatile ResyncResult eLastSyncResult;
|
|
volatile HANDLE hRpcSyncCompleteEvent;
|
|
volatile bool bWaitingForResyncResult;
|
|
|
|
// RPC security info (used to ACL the RPC interface):
|
|
// NOTE: declare this buffer as a DWORD buffer!! This buffer must be DWORD-aligned or NtPrivilegeCheck will fail!
|
|
DWORD pbPrivsBuffer[((sizeof(PRIVILEGE_SET) + (1-ANYSIZE_ARRAY)*sizeof(LUID_AND_ATTRIBUTES))+sizeof(DWORD))/sizeof(DWORD)];
|
|
PRIVILEGE_SET *ppsRequiredPrivs;
|
|
};
|
|
|
|
#define SHUTDOWN_RESTART_ATTEMPTS 3
|
|
#define SHUTDOWN_RESTART_RESET_TIME 600000 // 600s == 10 mins
|
|
#define SHUTDOWN_RESTART_WAIT_TIME 60000 // 60s == 1 min
|
|
|
|
// Used to prevent multiple concurrent shutdown requests
|
|
struct ShutdownInfo {
|
|
BOOL bCSInitialized;
|
|
CRITICAL_SECTION cs;
|
|
HANDLE hShutdownReady;
|
|
DWORD dwNumRunning;
|
|
BOOL fShuttingDown;
|
|
|
|
unsigned __int64 rgu64RestartAttempts[SHUTDOWN_RESTART_ATTEMPTS];
|
|
};
|
|
|
|
//--------------------------------------------------------------------
|
|
// globals
|
|
|
|
#define W32TIME_ERROR_SHUTDOWN HRESULT_FROM_WIN32(ERROR_SERVICE_CANNOT_ACCEPT_CTRL)
|
|
|
|
#define WAITHINT_WAITFORMANAGER 1000 // 1 sec until the manager thread notices the stop event.
|
|
#define WAITHINT_WAITFORDISPLN 1000 // 1 sec until the clock discipline thread notices the stop event.
|
|
#define WAITHINT_WAITFORPROV 1000 // 1 sec until a time provider shuts down
|
|
#define WAITHINT_WAITFORNETLOGON 90000 // 90 sec for netlogon to start running
|
|
|
|
#define PLLLOOPGAINBASE 6368 // number of ticks in 64s
|
|
#define MINIMUMIRREGULARINTERVAL 160000000 // 16s in 10^-7s
|
|
#define TIMEZONEMAXBIAS 900 // 15hr in min
|
|
#define ONEDAYINMILLISECONDS (1000*60*60*24)
|
|
|
|
#define wszW32TimeUNLocalCmosClock L"Local CMOS Clock" // start
|
|
#define wszW32TimeUNFreeSysClock L"Free-running System Clock" // unsyncd
|
|
|
|
//
|
|
// Create a security descriptors to ACL named events:
|
|
//
|
|
// LocalSystem: "O:SYG:SYD:(A;;GA;;;SY)"
|
|
//
|
|
// O:SY -- owner == local system
|
|
// G:SY -- group == local system
|
|
// D: -- no dacl flags
|
|
// (A;;GA;;;SY) -- one ACE -- ACCESS_ALLOWED, GENERIC_ALL, trustee == LocalSystem
|
|
//
|
|
#define LOCAL_SYSTEM_SD L"O:SYG:SYD:(A;;GA;;;SY)"
|
|
|
|
MODULEPRIVATE StateInfo g_state;
|
|
MODULEPRIVATE ShutdownInfo g_shutdown;
|
|
|
|
// Keep the service handle separate from the g_state structure. We need to use it to shutdown the service,
|
|
// and we'll zero out the g_state structure before doing this.
|
|
SERVICE_STATUS g_servicestatus;
|
|
SERVICE_STATUS_HANDLE g_servicestatushandle;
|
|
|
|
// for running under svchost.exe
|
|
MODULEPRIVATE SVCHOST_GLOBAL_DATA * g_pSvcsGlobalData=NULL;
|
|
|
|
// externally modified function pointer table
|
|
SERVICE_STATUS_HANDLE (WINAPI * fnW32TmRegisterServiceCtrlHandlerEx)(LPCWSTR, LPHANDLER_FUNCTION_EX, LPVOID);
|
|
BOOL (WINAPI * fnW32TmSetServiceStatus)(SERVICE_STATUS_HANDLE, LPSERVICE_STATUS);
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
// function prototypes
|
|
MODULEPRIVATE unsigned int CountInputProvidersInList(TimeProvider * ptpHead);
|
|
MODULEPRIVATE HRESULT DumpRpcCaller(HANDLE hToken);
|
|
MODULEPRIVATE HRESULT HandleManagerApmResumeSuspend(void);
|
|
MODULEPRIVATE HRESULT HandleManagerApmSuspend(void);
|
|
MODULEPRIVATE HRESULT HandleManagerGoUnsyncd(void);
|
|
MODULEPRIVATE HRESULT HandleManagerHardResync(TimeProvCmd tpc, LPVOID pvArgs);
|
|
MODULEPRIVATE HRESULT HandleManagerNetTopoChange(bool bRpc);
|
|
MODULEPRIVATE void WINAPI HandleManagerNetTopoChangeNoRPC(LPVOID pvIgnored, BOOLEAN bIgnored);
|
|
MODULEPRIVATE void HandleManagerSystemShutdown(void);
|
|
MODULEPRIVATE void WINAPI HandleRefreshTickCount(PVOID pvIgnored, BOOLEAN bIgnored);
|
|
MODULEPRIVATE DWORD WINAPI HandleSetProviderStatus(PVOID pvSetProviderStatusInfo);
|
|
MODULEPRIVATE DWORD WINAPI SendServiceShutdownWorker(PVOID pvIgnored);
|
|
MODULEPRIVATE HRESULT ShutdownNetlogonServiceBits(void);
|
|
MODULEPRIVATE HRESULT StartOrStopTimeSlipNotification(bool bStart);
|
|
MODULEPRIVATE HRESULT StopNetTopoChangeNotification(void);
|
|
MODULEPRIVATE HRESULT StopProvider(TimeProvider * ptp);
|
|
MODULEPRIVATE HRESULT UpdateNetlogonServiceBits(bool bFullUpdate) ;
|
|
MODULEPRIVATE HRESULT UpdateTimerQueue1(void);
|
|
MODULEPRIVATE HRESULT UpdateTimerQueue2(void);
|
|
MODULEPRIVATE HRESULT W32TmStopRpcServer(void);
|
|
|
|
extern "C" void WINAPI W32TmServiceMain(unsigned int nArgs, WCHAR ** rgwszArgs);
|
|
|
|
//####################################################################
|
|
// module private functions
|
|
|
|
void __cdecl SeTransFunc(unsigned int u, EXCEPTION_POINTERS* pExp) {
|
|
throw SeException(u);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT AcquireControlOfSystemClock(bool bEnter, bool bBlock, bool *pbAcquired) {
|
|
BOOL bAcquired = TRUE;
|
|
HRESULT hr;
|
|
|
|
if (bEnter) {
|
|
if (bBlock) {
|
|
hr = myEnterCriticalSection(&g_state.csAPM);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bAcquired = TRUE;
|
|
} else {
|
|
hr = myTryEnterCriticalSection(&g_state.csAPM, &bAcquired);
|
|
_JumpIfError(hr, error, "myTryEnterCriticalSection");
|
|
}
|
|
} else {
|
|
hr = myLeaveCriticalSection(&g_state.csAPM);
|
|
_JumpIfError(hr, error, "myLeaveCriticalSection");
|
|
}
|
|
|
|
if (NULL != pbAcquired) {
|
|
*pbAcquired = bAcquired ? true : false;
|
|
}
|
|
hr = S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT AllowShutdown(BOOL fAllow) {
|
|
bool bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
|
|
_BeginTryWith(hr) {
|
|
//
|
|
// BUG 706393 When SCM sends service control to w32time when machine is shutting down the error might be mishandled.
|
|
// It is possible for the SCM to receive a service ctrl after w32time has shut down if the SCM handler is paged out
|
|
// while w32time is shutting down. Accessing the freed critsec *might* AV, which would be successfully handled
|
|
// by our EH. However, it might also deadlock.
|
|
//
|
|
// This check doesn't guarantee that we won't hit this case, but makes it less likely.
|
|
//
|
|
if (!g_shutdown.bCSInitialized) {
|
|
hr = HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_ACTIVE);
|
|
_JumpError(hr, error, "AllowShutdown: not active");
|
|
}
|
|
|
|
hr = myEnterCriticalSection(&g_shutdown.cs);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
if (fAllow) {
|
|
// This worker no longer needs to block shutdown.
|
|
// BUGBUG: note that if SetEvent() fails, the shutdown thread may
|
|
// never be woken up!
|
|
if (0 == --g_shutdown.dwNumRunning && g_shutdown.fShuttingDown) {
|
|
if (!SetEvent(g_shutdown.hShutdownReady)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
}
|
|
} else {
|
|
if (g_shutdown.fShuttingDown) {
|
|
hr = W32TIME_ERROR_SHUTDOWN;
|
|
_JumpError(hr, error, "AllowShutdown: g_shutdown.fShuttingDown==TRUE");
|
|
}
|
|
|
|
// We're not shutting down, increment the number of running
|
|
// shutdown-aware workers:
|
|
g_shutdown.dwNumRunning++;
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "AllowShutdown: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
HRESULT hr2 = myLeaveCriticalSection(&g_shutdown.cs);
|
|
_TeardownError(hr, hr2, "myLeaveCriticalSection");
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT StartShutdown() {
|
|
bool bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
|
|
hr = myEnterCriticalSection(&g_shutdown.cs);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
if (g_shutdown.fShuttingDown) {
|
|
hr = W32TIME_ERROR_SHUTDOWN;
|
|
_JumpError(hr, error, "StartShutdown: g_shutdown.fShuttingDown==TRUE");
|
|
}
|
|
|
|
g_shutdown.fShuttingDown = true;
|
|
|
|
if (g_shutdown.dwNumRunning) {
|
|
hr = myLeaveCriticalSection(&g_shutdown.cs);
|
|
if (SUCCEEDED(hr)) {
|
|
bEnteredCriticalSection = false;
|
|
} else {
|
|
_IgnoreError(hr, "myLeaveCriticalSection"); // Not much we can do if failed. Just hope for the best...
|
|
}
|
|
|
|
if (WAIT_FAILED == WaitForSingleObject(g_shutdown.hShutdownReady, INFINITE)) {
|
|
_JumpLastError(hr, error, "WaitForSingleObject");
|
|
}
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
HRESULT hr2 = myLeaveCriticalSection(&g_shutdown.cs);
|
|
_TeardownError(hr, hr2, "myLeaveCriticalSection");
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE bool IsEventSet(HANDLE hEvent) {
|
|
return (WAIT_OBJECT_0==WaitForSingleObject(hEvent,0));
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void FreeTimeProviderList(TimeProvider * ptpHead) {
|
|
while (NULL!=ptpHead) {
|
|
TimeProvider * ptpTemp=ptpHead;
|
|
ptpHead=ptpHead->ptpNext;
|
|
LocalFree(ptpTemp->wszDllName);
|
|
LocalFree(ptpTemp->wszProvName);
|
|
LocalFree(ptpTemp);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT RemoveProviderFromList(TimeProvider *ptp) {
|
|
bool bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
TimeProvider tpDummy;
|
|
TimeProvider *ptpHead;
|
|
TimeProvider *ptpPrev;
|
|
WCHAR *wszError=NULL;
|
|
|
|
_BeginTryWith(hr) {
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
FileLog1(FL_ServiceMainAnnounce, L"Removing provider from list: %s\n", ptp->wszProvName);
|
|
|
|
// Insert a dummy first element to simplify list operations:
|
|
ptpHead = g_state.pciConfig->ptpProviderList;
|
|
g_state.pciConfig->ptpProviderList = &tpDummy;
|
|
tpDummy.ptpNext = ptpHead;
|
|
ptpPrev = &tpDummy;
|
|
|
|
while (NULL != ptpHead) {
|
|
TimeProvider *ptpTemp = ptpHead;
|
|
if (ptp == ptpHead) { // We've found the provider to remove
|
|
// Unlink ptpHead from the list of providers:
|
|
ptpPrev->ptpNext = ptpHead->ptpNext;
|
|
// Now free it:
|
|
LocalFree(ptpHead->wszDllName);
|
|
LocalFree(ptpHead->wszProvName);
|
|
LocalFree(ptpHead);
|
|
break;
|
|
}
|
|
|
|
// Continue searching through the list ...
|
|
ptpPrev = ptpHead;
|
|
ptpHead = ptpPrev->ptpNext;
|
|
}
|
|
|
|
// Remove the dummy element we inserted at the beginning of the function:
|
|
g_state.pciConfig->ptpProviderList = tpDummy.ptpNext;
|
|
|
|
// If there are no input providers left, log an error
|
|
if (0==CountInputProvidersInList(g_state.pciConfig->ptpProviderList)) {
|
|
const WCHAR * rgwszStrings[1]={NULL};
|
|
|
|
FileLog0(FL_ServiceMainAnnounce, L"The time service has been configured to use one or more input providers, however, none of the input providers are still running. THE TIME SERVICE HAS NO SOURCE OF ACCURATE TIME.\n");
|
|
hr=MyLogEvent(EVENTLOG_ERROR_TYPE, MSG_NO_INPUT_PROVIDERS_STARTED, 2, rgwszStrings);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "RemoveProviderFromList: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
HRESULT hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT RemoveDefaultProvidersFromList() {
|
|
HRESULT hr;
|
|
|
|
for (TimeProvider *ptp = g_state.pciConfig->ptpProviderList; NULL != ptp; ptp = ptp->ptpNext) {
|
|
if (0 == wcscmp(ptp->wszDllName /*The provider's DLL*/, wszDLLNAME /*w32time.dll*/)) {
|
|
hr = RemoveProviderFromList(ptp);
|
|
_JumpIfError(hr, error, "RemoveProviderFromList");
|
|
}
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE unsigned int CountInputProvidersInList(TimeProvider * ptpHead) {
|
|
unsigned int nCount=0;
|
|
while (NULL!=ptpHead) {
|
|
if (ptpHead->bInputProvider) {
|
|
nCount++;
|
|
}
|
|
ptpHead=ptpHead->ptpNext;
|
|
}
|
|
|
|
return nCount;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void FreeConfigInfo(ConfigInfo * pci) {
|
|
FreeTimeProviderList(pci->ptpProviderList);
|
|
LocalFree(pci);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT InitShutdownState(void) {
|
|
static bool fFirstTime = true;
|
|
bool fSavedRestartAttempts = false;
|
|
unsigned __int64 rgu64Save[SHUTDOWN_RESTART_ATTEMPTS];
|
|
HRESULT hr;
|
|
|
|
// We want to save the rgu64RestartAttempts field accross restarts, but
|
|
// not the first time we've started the service:
|
|
if (!fFirstTime) {
|
|
_MyAssert(sizeof(rgu64Save) == sizeof(g_shutdown.rgu64RestartAttempts));
|
|
memcpy(&rgu64Save[0], &g_shutdown.rgu64RestartAttempts[0], sizeof(g_shutdown.rgu64RestartAttempts));
|
|
fSavedRestartAttempts = true;
|
|
} else {
|
|
fFirstTime = false;
|
|
}
|
|
|
|
ZeroMemory(&g_shutdown, sizeof(g_shutdown));
|
|
|
|
hr = myInitializeCriticalSection(&g_shutdown.cs);
|
|
_JumpIfError(hr, error, "myInitializeCriticalSection");
|
|
g_shutdown.bCSInitialized = true;
|
|
|
|
g_shutdown.hShutdownReady = CreateEvent(NULL, FALSE /*auto-reset*/, FALSE /*non-signaled*/, NULL /*no security*/);
|
|
if (NULL == g_shutdown.hShutdownReady) {
|
|
_JumpLastError(hr, error, "CreateEvent");
|
|
}
|
|
|
|
// Restore the saved ftRestartAttempts field
|
|
if (fSavedRestartAttempts) {
|
|
memcpy(&g_shutdown.rgu64RestartAttempts[0], &rgu64Save[0], sizeof(g_shutdown.rgu64RestartAttempts));
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT InitGlobalState(void) {
|
|
DWORD cbSD;
|
|
HRESULT hr;
|
|
PSECURITY_DESCRIPTOR pSD = NULL;
|
|
SECURITY_ATTRIBUTES SA;
|
|
|
|
ZeroMemory(&g_state, sizeof(g_state));
|
|
|
|
hr = myInitializeCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myInitializeCriticalSection");
|
|
g_state.bCSInitialized = true;
|
|
|
|
hr = myInitializeCriticalSection(&g_state.csAPM);
|
|
_JumpIfError(hr, error, "myInitializeCriticalSection");
|
|
g_state.bCSAPMInitialized = true;
|
|
|
|
hr = myInitializeCriticalSection(&g_state.csTickCount);
|
|
_JumpIfError(hr, error, "myInitializeCriticalSection");
|
|
g_state.bCSTickCountInitialized = true;
|
|
|
|
// Create all of the events used by the manager:
|
|
//
|
|
struct EventToCreate {
|
|
HANDLE *phEvent;
|
|
const WCHAR *pwszSD;
|
|
BOOL bManualReset;
|
|
BOOL bInitialState;
|
|
const WCHAR *pwszName;
|
|
} rgEvents[] = {
|
|
{ &g_state.hShutDownEvent, NULL, TRUE, FALSE, NULL },
|
|
{ &g_state.hClockCommandAvailEvent, NULL, FALSE, FALSE, NULL },
|
|
{ &g_state.hClockCommandCompleteEvent, NULL, FALSE, FALSE, NULL },
|
|
{ &g_state.hManagerParamChangeEvent, NULL, FALSE, FALSE, NULL },
|
|
{ &g_state.hManagerGPUpdateEvent, NULL, TRUE, FALSE, NULL },
|
|
{ &g_state.hTimeSlipEvent, LOCAL_SYSTEM_SD, FALSE, FALSE, W32TIME_NAMED_EVENT_SYSTIME_NOT_CORRECT},
|
|
{ &g_state.hNetTopoChangeEvent, NULL, TRUE, FALSE, NULL },
|
|
{ &g_state.hRpcSyncCompleteAEvent, NULL, TRUE, FALSE, NULL },
|
|
{ &g_state.hRpcSyncCompleteBEvent, NULL, TRUE, FALSE, NULL },
|
|
{ &g_state.hDomHierRoleChangeEvent, NULL, FALSE, FALSE, NULL },
|
|
{ &g_state.hSamplesAvailEvent, NULL, FALSE, FALSE, NULL }
|
|
};
|
|
|
|
for (DWORD dwIndex = 0; dwIndex < ARRAYSIZE(rgEvents); dwIndex++) {
|
|
EventToCreate etc = rgEvents[dwIndex];
|
|
PSECURITY_ATTRIBUTES pSA = NULL;
|
|
|
|
if (NULL != etc.pwszSD) {
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(etc.pwszSD, SDDL_REVISION_1, &pSD, &cbSD)) {
|
|
_JumpLastError(hr, error, "ConvertStringSecurityDescriptorToSecurityDescriptor");
|
|
}
|
|
|
|
SA.nLength = cbSD;
|
|
SA.lpSecurityDescriptor = pSD;
|
|
SA.bInheritHandle = FALSE;
|
|
pSA = &SA;
|
|
}
|
|
|
|
*(etc.phEvent) = CreateEvent(pSA, etc.bManualReset, etc.bInitialState, etc.pwszName);
|
|
if (NULL == *(etc.phEvent) || ERROR_ALREADY_EXISTS == GetLastError()) {
|
|
// Save the HRESULT (MyLogEvent will clobber it)
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
// POTENTIAL SECURITY RISK: Someone may have already created our named event before
|
|
// we have. Log an error and don't start the time service.
|
|
const WCHAR * rgwszStrings[1] = {etc.pwszName};
|
|
HRESULT hr2 = MyLogEvent(EVENTLOG_ERROR_TYPE, MSG_NAMED_EVENT_ALREADY_OPEN, 1, rgwszStrings);
|
|
_IgnoreIfError(hr2, "MyLogEvent");
|
|
|
|
// exit with an error
|
|
_JumpError(hr, error, "CreateEvent");
|
|
}
|
|
|
|
|
|
LocalFree(pSD);
|
|
pSD = NULL;
|
|
}
|
|
|
|
g_state.eLeapIndicator=e_ClockNotSynchronized;
|
|
g_state.nStratum=0;
|
|
g_state.refidSource.value=0;
|
|
//g_state.nPollInterval // OK =NtpConst::nMinPollInverval;
|
|
g_state.toRootDelay.setValue(0);
|
|
g_state.tpRootDispersion.setValue(0);
|
|
g_state.toSysPhaseOffset.setValue(0);
|
|
g_state.qwLastSyncTicks.setValue(0);
|
|
|
|
//g_state.tsNextClockUpdate // OK
|
|
//g_state.tpSelectDispersion // OK
|
|
|
|
//g_state.bClockJumped // OK
|
|
//g_state.toClockJump // OK
|
|
//g_state.bPollIntervalChanged // OK
|
|
//g_state.bStaleData // OK
|
|
//g_state.bClockChangeTooBig // OK
|
|
//g_state.toIgnoredChange //OK
|
|
|
|
// local clock state // OK
|
|
|
|
// manager state
|
|
g_state.pciConfig=NULL;
|
|
// g_state.tpPollDelayRemaining // OK
|
|
// g_state.teManagerWaitStart // OK
|
|
// g_state.tpIrregularDelayRemaining // OK
|
|
// g_state.tpTimeSinceLastSyncAttempt // OK
|
|
// g_state.tpTimeSinceLastGoodSync // OK
|
|
// g_state.tpWaitInterval // OK
|
|
g_state.nSampleBufAllocSize=SampleBufInitialSize;
|
|
g_state.rgtsSampleBuf=NULL;
|
|
g_state.rgtsiSampleInfoBuf=NULL;
|
|
g_state.rgeeEndpointList=NULL;
|
|
g_state.rgceCandidateList=NULL;
|
|
g_state.bTimeSlipNotificationStarted=false;
|
|
g_state.bNetTopoChangeNotificationStarted=false;
|
|
g_state.eLastRegSyncResult=e_NoData;
|
|
// g_state.eTimeoutReason // OK
|
|
g_state.bDontLogClockChangeTooBig=false;
|
|
// g_state.dwEventLogFlags // OK
|
|
|
|
// RPC State
|
|
g_state.bRpcServerStarted=false;
|
|
g_state.dwNetlogonServiceBits=0;
|
|
g_state.eLastSyncResult=e_NoData;
|
|
g_state.hRpcSyncCompleteEvent=g_state.hRpcSyncCompleteAEvent;
|
|
|
|
g_state.rgtsSampleBuf=(TimeSample *)LocalAlloc(LPTR, sizeof(TimeSample)*g_state.nSampleBufAllocSize);
|
|
_JumpIfOutOfMemory(hr, error, g_state.rgtsSampleBuf);
|
|
|
|
g_state.rgtsiSampleInfoBuf=(TimeSampleInfo *)LocalAlloc(LPTR, sizeof(TimeSampleInfo)*g_state.nSampleBufAllocSize);
|
|
_JumpIfOutOfMemory(hr, error, g_state.rgtsiSampleInfoBuf);
|
|
|
|
g_state.rgeeEndpointList=(EndpointEntry *)LocalAlloc(LPTR, sizeof(EndpointEntry)*3*g_state.nSampleBufAllocSize);
|
|
_JumpIfOutOfMemory(hr, error, g_state.rgeeEndpointList);
|
|
|
|
g_state.rgceCandidateList=(CandidateEntry *)LocalAlloc(LPTR, sizeof(CandidateEntry)*g_state.nSampleBufAllocSize);
|
|
_JumpIfOutOfMemory(hr, error, g_state.rgceCandidateList);
|
|
|
|
// Set up the information necessary to perform a privilege check on the
|
|
// client token accessing the RPC interface. We allow anyone with
|
|
// SeSystemtimePrivilege
|
|
g_state.ppsRequiredPrivs=(PRIVILEGE_SET *)(&g_state.pbPrivsBuffer[0]);
|
|
g_state.ppsRequiredPrivs->PrivilegeCount=1;
|
|
g_state.ppsRequiredPrivs->Control=PRIVILEGE_SET_ALL_NECESSARY;
|
|
g_state.ppsRequiredPrivs->Privilege[0].Attributes=0;
|
|
if (!LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &(g_state.ppsRequiredPrivs->Privilege[0].Luid))) {
|
|
_JumpLastError(hr, error, "LookupPrivilegeValue");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (NULL != pSD) {
|
|
LocalFree(pSD);
|
|
}
|
|
// on error, any succefully created objects will be freed in FreeGlobalState.
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void FreeGlobalState(void) {
|
|
if (g_state.bCSInitialized) {
|
|
DeleteCriticalSection(&g_state.csW32Time);
|
|
g_state.bCSInitialized = false;
|
|
}
|
|
|
|
if (g_state.bCSAPMInitialized) {
|
|
DeleteCriticalSection(&g_state.csAPM);
|
|
g_state.bCSAPMInitialized = false;
|
|
}
|
|
|
|
if (g_state.bCSTickCountInitialized) {
|
|
DeleteCriticalSection(&g_state.csTickCount);
|
|
g_state.bCSTickCountInitialized = false;
|
|
}
|
|
|
|
HANDLE rgEvents[] = {
|
|
g_state.hShutDownEvent,
|
|
g_state.hClockCommandAvailEvent,
|
|
g_state.hClockCommandCompleteEvent,
|
|
g_state.hManagerGPUpdateEvent,
|
|
g_state.hManagerParamChangeEvent,
|
|
g_state.hTimeSlipEvent,
|
|
g_state.hNetTopoChangeEvent,
|
|
g_state.hRpcSyncCompleteAEvent,
|
|
g_state.hRpcSyncCompleteBEvent,
|
|
g_state.hDomHierRoleChangeEvent,
|
|
g_state.hSamplesAvailEvent,
|
|
};
|
|
|
|
for (DWORD dwIndex = 0; dwIndex < ARRAYSIZE(rgEvents); dwIndex++) {
|
|
if (NULL != rgEvents[dwIndex]) {
|
|
CloseHandle(rgEvents[dwIndex]);
|
|
}
|
|
}
|
|
|
|
if (NULL!=g_state.hClockDisplnThread) {
|
|
CloseHandle(g_state.hClockDisplnThread);
|
|
}
|
|
|
|
//g_state.eLeapIndicator // OK
|
|
//g_state.nStratum // OK
|
|
//g_state.refidSource // OK
|
|
//g_state.nPollInterval // OK
|
|
//g_state.toRootDelay // OK
|
|
//g_state.tpRootDispersion // OK
|
|
//g_state.toSysPhaseOffset // OK
|
|
//g_state.qwLastSyncTicks // OK
|
|
|
|
//g_state.tsNextClockUpdate // OK
|
|
//g_state.tsNextClockUpdate // OK
|
|
//g_state.tpSelectDispersion // OK
|
|
|
|
//g_state.bClockJumped // OK
|
|
//g_state.toClockJump // OK
|
|
//g_state.bPollIntervalChanged // OK
|
|
//g_state.bStaleData // OK
|
|
//g_state.bClockChangeTooBig // OK
|
|
//g_state.toIgnoredChange //OK
|
|
|
|
// local clock state // OK
|
|
|
|
// manager state
|
|
if (NULL!=g_state.pciConfig) {
|
|
FreeConfigInfo(g_state.pciConfig);
|
|
}
|
|
// g_state.tpPollDelayRemaining // OK
|
|
// g_state.teManagerWaitStart // OK
|
|
// g_state.tpIrregularDelayRemaining // OK
|
|
// g_state.tpTimeSinceLastSyncAttempt // OK
|
|
// g_state.tpTimeSinceLastGoodSync // OK
|
|
// g_state.tpWaitInterval // OK
|
|
// g_state.nClockPrecision // OK
|
|
if (NULL!=g_state.rgtsSampleBuf) {
|
|
LocalFree(g_state.rgtsSampleBuf);
|
|
}
|
|
if (NULL!=g_state.rgtsiSampleInfoBuf) {
|
|
LocalFree(g_state.rgtsiSampleInfoBuf);
|
|
}
|
|
if (NULL!=g_state.rgeeEndpointList) {
|
|
LocalFree(g_state.rgeeEndpointList);
|
|
}
|
|
if (NULL!=g_state.rgceCandidateList) {
|
|
LocalFree(g_state.rgceCandidateList);
|
|
}
|
|
ZeroMemory(&g_state, sizeof(g_state));
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void FreeShutdownState(void) {
|
|
if (g_shutdown.bCSInitialized) {
|
|
DeleteCriticalSection(&g_shutdown.cs);
|
|
g_shutdown.bCSInitialized = false;
|
|
}
|
|
if (g_shutdown.hShutdownReady) {
|
|
CloseHandle(g_shutdown.hShutdownReady);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT AccurateGetTickCountSafe(unsigned __int64 *pqwTick, bool bGetInterruptCount) {
|
|
bool bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
|
|
hr = myEnterCriticalSection(&g_state.csTickCount);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
if (bGetInterruptCount) {
|
|
AccurateGetInterruptCount2(pqwTick);
|
|
} else {
|
|
AccurateGetTickCount2(pqwTick);
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
HRESULT hr2 = myLeaveCriticalSection(&g_state.csTickCount);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT EnlargeSampleBuf(unsigned int nSamples) {
|
|
HRESULT hr;
|
|
|
|
// must be cleaned up
|
|
TimeSample * rgtsNewSampleBuf=NULL;
|
|
TimeSampleInfo * rgtsiNewSampleInfoBuf=NULL;
|
|
EndpointEntry * rgeeNewEndpointList=NULL;
|
|
CandidateEntry * rgceNewCandidateList=NULL;
|
|
|
|
rgtsNewSampleBuf=(TimeSample *)LocalAlloc(LPTR, sizeof(TimeSample)*(g_state.nSampleBufAllocSize+nSamples));
|
|
_JumpIfOutOfMemory(hr, error, rgtsNewSampleBuf);
|
|
|
|
rgtsiNewSampleInfoBuf=(TimeSampleInfo *)LocalAlloc(LPTR, sizeof(TimeSampleInfo)*(g_state.nSampleBufAllocSize+nSamples));
|
|
_JumpIfOutOfMemory(hr, error, rgtsiNewSampleInfoBuf);
|
|
|
|
rgeeNewEndpointList=(EndpointEntry *)LocalAlloc(LPTR, sizeof(EndpointEntry)*3*(g_state.nSampleBufAllocSize+nSamples));
|
|
_JumpIfOutOfMemory(hr, error, rgeeNewEndpointList);
|
|
|
|
rgceNewCandidateList=(CandidateEntry *)LocalAlloc(LPTR, sizeof(CandidateEntry)*(g_state.nSampleBufAllocSize+nSamples));
|
|
_JumpIfOutOfMemory(hr, error, rgceNewCandidateList);
|
|
|
|
// we succeeded
|
|
|
|
// copy the current data and remeber our new allocated size.
|
|
// note the the endpoint and candidate lists do not need to be copied
|
|
memcpy(rgtsNewSampleBuf, g_state.rgtsSampleBuf, sizeof(TimeSample)*g_state.nSampleBufAllocSize);
|
|
memcpy(rgtsiNewSampleInfoBuf, g_state.rgtsiSampleInfoBuf, sizeof(TimeSampleInfo)*g_state.nSampleBufAllocSize);
|
|
g_state.nSampleBufAllocSize+=nSamples;
|
|
|
|
LocalFree(g_state.rgtsSampleBuf);
|
|
g_state.rgtsSampleBuf=rgtsNewSampleBuf;
|
|
rgtsNewSampleBuf=NULL;
|
|
|
|
LocalFree(g_state.rgtsiSampleInfoBuf);
|
|
g_state.rgtsiSampleInfoBuf=rgtsiNewSampleInfoBuf;
|
|
rgtsiNewSampleInfoBuf=NULL;
|
|
|
|
LocalFree(g_state.rgeeEndpointList);
|
|
g_state.rgeeEndpointList=rgeeNewEndpointList;
|
|
rgeeNewEndpointList=NULL;
|
|
|
|
LocalFree(g_state.rgceCandidateList);
|
|
g_state.rgceCandidateList=rgceNewCandidateList;
|
|
rgceNewCandidateList=NULL;
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (NULL!=rgtsNewSampleBuf) {
|
|
LocalFree(rgtsNewSampleBuf);
|
|
}
|
|
if (NULL!=rgtsiNewSampleInfoBuf) {
|
|
LocalFree(rgtsiNewSampleInfoBuf);
|
|
}
|
|
if (NULL!=rgeeNewEndpointList) {
|
|
LocalFree(rgeeNewEndpointList);
|
|
}
|
|
if (NULL!=rgceNewCandidateList) {
|
|
LocalFree(rgceNewCandidateList);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//====================================================================
|
|
// service control routines
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT MySetServiceStatus(DWORD dwCurrentState, DWORD dwCheckPoint, DWORD dwWaitHint, DWORD dwExitCode) {
|
|
HRESULT hr;
|
|
|
|
g_servicestatus.dwServiceType=SERVICE_WIN32_SHARE_PROCESS; // | SERVICE_INTERACTIVE_PROCESS;
|
|
g_servicestatus.dwCurrentState=dwCurrentState;
|
|
switch (dwCurrentState) {
|
|
case SERVICE_STOPPED:
|
|
case SERVICE_STOP_PENDING:
|
|
g_servicestatus.dwControlsAccepted=0;
|
|
break;
|
|
case SERVICE_RUNNING:
|
|
case SERVICE_PAUSED:
|
|
g_servicestatus.dwControlsAccepted=SERVICE_ACCEPT_STOP
|
|
//| SERVICE_ACCEPT_PAUSE_CONTINUE
|
|
| SERVICE_ACCEPT_SHUTDOWN
|
|
| SERVICE_ACCEPT_PARAMCHANGE
|
|
| SERVICE_ACCEPT_NETBINDCHANGE
|
|
| SERVICE_ACCEPT_HARDWAREPROFILECHANGE
|
|
| SERVICE_ACCEPT_POWEREVENT;
|
|
break;
|
|
case SERVICE_START_PENDING:
|
|
case SERVICE_CONTINUE_PENDING:
|
|
case SERVICE_PAUSE_PENDING:
|
|
g_servicestatus.dwControlsAccepted=0;
|
|
break;
|
|
}
|
|
g_servicestatus.dwWin32ExitCode = HRESULT_CODE(dwExitCode);
|
|
g_servicestatus.dwServiceSpecificExitCode = 0;
|
|
g_servicestatus.dwCheckPoint=dwCheckPoint;
|
|
g_servicestatus.dwWaitHint=dwWaitHint;
|
|
|
|
if (!fnW32TmSetServiceStatus(g_servicestatushandle, &g_servicestatus)) {
|
|
_JumpLastError(hr, error, "fnW32TmSetServiceStatus");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// for start/stop/pause pending
|
|
MODULEPRIVATE HRESULT MySetServicePending(DWORD dwCurrentState, DWORD dwCheckPoint, DWORD dwWaitHint) {
|
|
return MySetServiceStatus(dwCurrentState, dwCheckPoint, dwWaitHint, S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// for running/paused
|
|
MODULEPRIVATE HRESULT MySetServiceState(DWORD dwCurrentState) {
|
|
return MySetServiceStatus(dwCurrentState, 0, 0, S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// for stop
|
|
MODULEPRIVATE HRESULT MySetServiceStopped(HRESULT hr) {
|
|
|
|
return MySetServiceStatus(SERVICE_STOPPED, 0, 0, hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT SaveLastClockRate(void) {
|
|
HRESULT hr;
|
|
if (e_Sync==g_state.lcState && e_ClockNotSynchronized!=g_state.eLeapIndicator) {
|
|
HKEY hkKey;
|
|
hr=RegOpenKeyEx(HKEY_LOCAL_MACHINE, wszW32TimeRegKeyConfig, NULL, KEY_SET_VALUE, &hkKey);
|
|
hr=HRESULT_FROM_WIN32(hr);
|
|
_JumpIfError(hr, error, "RegOpenKeyEx");
|
|
|
|
hr=RegSetValueEx(hkKey, wszW32TimeRegValueLastClockRate, NULL, REG_DWORD, (BYTE *)&g_state.dwClockRate, sizeof(DWORD));
|
|
hr=HRESULT_FROM_WIN32(hr);
|
|
RegCloseKey(hkKey);
|
|
_JumpIfError(hr, error, "RegSetValueEx");
|
|
}
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
// Stops the time service.
|
|
//
|
|
MODULEPRIVATE void ServiceShutdown(DWORD dwExitCode) {
|
|
BOOL fResult;
|
|
HRESULT hr = dwExitCode;
|
|
HRESULT hr2;
|
|
int nCheckpoint = 2;
|
|
|
|
// Events registered with the thread pool:
|
|
HANDLE *rghRegistered[] = {
|
|
&g_state.hRegisteredManagerParamChangeEvent,
|
|
&g_state.hRegisteredManagerGPUpdateEvent,
|
|
&g_state.hRegisteredNetTopoChangeEvent,
|
|
&g_state.hRegisteredTimeSlipEvent,
|
|
&g_state.hRegisteredClockDisplnThread,
|
|
&g_state.hRegisteredDomHierRoleChangeEvent,
|
|
&g_state.hRegisteredSamplesAvailEvent
|
|
};
|
|
|
|
FileLog1(FL_ServiceMainAnnounce, L"Service shutdown initiated with exit code: %d.\n", dwExitCode);
|
|
|
|
hr2=MySetServicePending(SERVICE_STOP_PENDING, nCheckpoint++, WAITHINT_WAITFORDISPLN);
|
|
_TeardownError(hr, hr2, "MySetServicePending");
|
|
|
|
// Next, de-register all events in the thread pool:
|
|
for (int nIndex = 0; nIndex < ARRAYSIZE(rghRegistered); nIndex++) {
|
|
if (NULL != *(rghRegistered[nIndex])) {
|
|
if (!UnregisterWaitEx(*(rghRegistered[nIndex]) /*event to de-register*/, INVALID_HANDLE_VALUE /*wait forever*/)) {
|
|
hr2 = HRESULT_FROM_WIN32(GetLastError());
|
|
_TeardownError(hr, hr2, "UnregisterWaitEx");
|
|
}
|
|
// Don't want to unregister twice under any circumstances
|
|
*(rghRegistered[nIndex]) = NULL;
|
|
hr2=MySetServicePending(SERVICE_STOP_PENDING, nCheckpoint++, WAITHINT_WAITFORDISPLN);
|
|
_TeardownError(hr, hr2, "MySetServicePending");
|
|
}
|
|
}
|
|
|
|
// Delete the timer queue. This must be done before shutting down the
|
|
// clock discipline thread, as a timer queue timeout can block waiting
|
|
// for a "clock command complete" event, generated by the clock
|
|
// discipline thread.
|
|
if (NULL != g_state.hTimer) {
|
|
myDeleteTimerQueueTimer(NULL /*default queue*/, g_state.hTimer, INVALID_HANDLE_VALUE /*blocking*/);
|
|
g_state.hTimer = NULL;
|
|
}
|
|
|
|
if (NULL != g_state.hTickCountRefreshTimer) {
|
|
myDeleteTimerQueueTimer(NULL /*default queue*/, g_state.hTickCountRefreshTimer, INVALID_HANDLE_VALUE /*blocking*/);
|
|
g_state.hTickCountRefreshTimer = NULL;
|
|
}
|
|
|
|
// Set the shutdown event. This should stop the clock discipline thread.
|
|
if (NULL != g_state.hShutDownEvent) {
|
|
if (!SetEvent(g_state.hShutDownEvent)) {
|
|
hr2 = HRESULT_FROM_WIN32(GetLastError());
|
|
_TeardownError(hr, hr2, "SetEvent");
|
|
} else {
|
|
// wait for the clock discipline thread to finish
|
|
if (NULL != g_state.hClockDisplnThread) {
|
|
if (-1 == WaitForSingleObject(g_state.hClockDisplnThread, INFINITE)) {
|
|
hr2=HRESULT_FROM_WIN32(GetLastError());
|
|
_TeardownError(hr, hr2, "WaitForSingleObject");
|
|
}
|
|
else {
|
|
// we haven't errored out yet -- check that the clock discipline
|
|
// thread shut down correctly:
|
|
if (!GetExitCodeThread(g_state.hClockDisplnThread, (DWORD *)&hr)) {
|
|
hr2=HRESULT_FROM_WIN32(GetLastError());
|
|
_TeardownError(hr, hr2, "GetExitCodeThread");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// stash the last clock rate, if possible
|
|
hr2 = SaveLastClockRate();
|
|
_TeardownError(hr, hr2, "SaveLastClockRate");
|
|
|
|
// shutdown stage 2: wait for providers, if the provider list has been initialized
|
|
if (NULL != g_state.pciConfig) {
|
|
for (TimeProvider *ptpList=g_state.pciConfig->ptpProviderList; NULL!=ptpList; nCheckpoint++, ptpList=ptpList->ptpNext) {
|
|
hr2=MySetServicePending(SERVICE_STOP_PENDING, nCheckpoint, WAITHINT_WAITFORPROV);
|
|
_TeardownError(hr, hr2, "MySetServicePending");
|
|
|
|
// tell the provider to shut down.
|
|
hr2=StopProvider(ptpList);
|
|
_TeardownError(hr, hr2, "StopProvider");
|
|
|
|
} // <- end provider shutdown loop
|
|
// the timeprov list will be freed later
|
|
}
|
|
|
|
hr2 = LsaUnregisterPolicyChangeNotification(PolicyNotifyServerRoleInformation, g_state.hDomHierRoleChangeEvent);
|
|
if (ERROR_SUCCESS != hr2) {
|
|
hr2 = HRESULT_FROM_WIN32(LsaNtStatusToWinError(hr2));
|
|
_TeardownError(hr, hr2, "LsaUnegisterPolicyChangeNotification");
|
|
}
|
|
|
|
hr2=ShutdownNetlogonServiceBits();
|
|
_TeardownError(hr, hr2, "ShutdownNetlogonServiceBits");
|
|
|
|
if (true==g_state.bRpcServerStarted) {
|
|
hr2=W32TmStopRpcServer();
|
|
_TeardownError(hr, hr2, "W32TmStopRpcServer");
|
|
}
|
|
|
|
|
|
if (true==g_state.bNetTopoChangeNotificationStarted) {
|
|
hr2=StopNetTopoChangeNotification();
|
|
_TeardownError(hr, hr2, "StopNetTopoChangeNotification");
|
|
}
|
|
if (true==g_state.bGPNotificationStarted) {
|
|
if (!UnregisterGPNotification(g_state.hManagerGPUpdateEvent)) {
|
|
hr2 = HRESULT_FROM_WIN32(GetLastError());
|
|
_TeardownError(hr, hr2, "UnregisterGPNotification");
|
|
}
|
|
}
|
|
if (true==g_state.bTimeSlipNotificationStarted) {
|
|
hr2=StartOrStopTimeSlipNotification(false);
|
|
_TeardownError(hr, hr2, "StartOrStopTimeSlipNotification");
|
|
}
|
|
|
|
FileLog0(FL_ServiceMainAnnounce, L"Exiting ServiceShutdown\n");
|
|
FileLogEnd();
|
|
|
|
FreeGlobalState();
|
|
FreeShutdownState();
|
|
DebugWPrintfTerminate();
|
|
|
|
if (NULL!=g_servicestatushandle) {
|
|
// WARNING: we can't touch global data AFTER we call this method,
|
|
// as the SCM may start another instance of us which
|
|
// will cause a race condition.
|
|
MySetServiceStopped(hr);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// NOTE: this function should not be called directly. Rather, it should
|
|
// be invoked through SendServiceShutdown(), which protects against
|
|
// multiple concurrent shutdowns.
|
|
MODULEPRIVATE DWORD WINAPI SendServiceShutdownWorker(PVOID pvErr)
|
|
{
|
|
DWORD dwErr = (UINT_PTR)pvErr;
|
|
HRESULT hr;
|
|
|
|
_BeginTryWith(hr) {
|
|
// 1) On failure, log an event indicating that we are shutting down.
|
|
if (S_OK != dwErr) {
|
|
// Log an event indicating that the service is shutting down:
|
|
hr = MyLogErrorMessage(dwErr, EVENTLOG_ERROR_TYPE, MSG_ERROR_SHUTDOWN);
|
|
_IgnoreIfError(hr, "MyLogEvent");
|
|
}
|
|
|
|
// 2) Actually shut down the time service:
|
|
ServiceShutdown(dwErr);
|
|
} _TrapException(hr);
|
|
|
|
// BUG 620714: don't log after the service has shutdown:
|
|
// _IgnoreIfError(hr, "SendServiceShutdownWorker: HANDLED EXCEPTION");
|
|
return S_OK;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// NOTE: this function should not be called directly. Rather, it should
|
|
// be invoked through SendServiceShutdown(), which protects against
|
|
// multiple concurrent shutdowns.
|
|
MODULEPRIVATE DWORD WINAPI SendServiceRestartWorker(PVOID pvErr)
|
|
{
|
|
bool fRestartService = false;
|
|
DWORD dwInitialTickcount;
|
|
DWORD dwTimeout = 20000;
|
|
HRESULT hr;
|
|
SC_HANDLE hSCM = NULL;
|
|
SC_HANDLE hTimeService = NULL;
|
|
SERVICE_STATUS sSvcStatus;
|
|
unsigned __int64 qwNow;
|
|
unsigned __int64 qwTimeSinceRestart_0;
|
|
|
|
_BeginTryWith(hr) {
|
|
|
|
hr = SendServiceShutdownWorker(pvErr);
|
|
// BUG 620714: don't log after the service has shutdown:
|
|
// _IgnoreIfError(hr, "SendServiceShutdownWorker");
|
|
|
|
// Determine whether we want to restart the service. We'll restart
|
|
// if we haven't shutdown more than SHUTDOWN_RESTART_ATTEMPTS times
|
|
// in the last SHUTDOWN_RESTART_RESET_TIME milliseconds.
|
|
AccurateGetSystemTime(&qwNow);
|
|
|
|
// If the first attempt is 0, we've not restarted enough times to block
|
|
// restart.
|
|
if (0 == g_shutdown.rgu64RestartAttempts[0]) {
|
|
fRestartService = true;
|
|
} else {
|
|
// Get the time since the 0th restart in the list, and convert to millseconds.
|
|
qwTimeSinceRestart_0 = (qwNow - g_shutdown.rgu64RestartAttempts[0]) / 10000;
|
|
fRestartService = qwTimeSinceRestart_0 > SHUTDOWN_RESTART_RESET_TIME;
|
|
}
|
|
|
|
if (fRestartService) {
|
|
// Shift the list of restarts down by 1.
|
|
memmove(&g_shutdown.rgu64RestartAttempts[0], &g_shutdown.rgu64RestartAttempts[1], sizeof(g_shutdown.rgu64RestartAttempts) - sizeof(g_shutdown.rgu64RestartAttempts[0]));
|
|
|
|
// Add the current time to the list of restart times:
|
|
g_shutdown.rgu64RestartAttempts[ARRAYSIZE(g_shutdown.rgu64RestartAttempts)-1] = qwNow;
|
|
|
|
Sleep(SHUTDOWN_RESTART_WAIT_TIME);
|
|
|
|
// Restart the service
|
|
hSCM=OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT);
|
|
if (NULL==hSCM) {
|
|
// BUG 620714: don't log after the service has shutdown:
|
|
// _JumpLastError(hr, error, "OpenSCManager");
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto error;
|
|
}
|
|
|
|
hTimeService=OpenService(hSCM, L"w32time", SERVICE_START);
|
|
if (NULL==hTimeService) {
|
|
// BUG 620714: don't log after the service has shutdown:
|
|
// _JumpLastError(hr, error, "OpenService");
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto error;
|
|
}
|
|
|
|
if (!StartService(hTimeService, 0, NULL)) {
|
|
// BUG 620714: don't log after the service has shutdown:
|
|
// _JumpLastError(hr, error, "StartService");
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto error;
|
|
}
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
// BUG 620714: don't log after the service has shutdown:
|
|
// _JumpError(hr, error, "SendServiceRestartWorker: HANDLED EXCEPTION");
|
|
goto error;
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (NULL!=hSCM) {
|
|
CloseServiceHandle(hSCM);
|
|
}
|
|
if (NULL!=hTimeService) {
|
|
CloseServiceHandle(hTimeService);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// Asynchronously queues a shutdown request for the time service.
|
|
// NOTE: this should not be called when holding any critsecs!
|
|
MODULEPRIVATE HRESULT SendServiceShutdown(DWORD dwErr, BOOL bRestartService, BOOL bAsync) {
|
|
HRESULT hr;
|
|
LPTHREAD_START_ROUTINE pfnShutdownWorker;
|
|
|
|
// See if we're already shutting down:
|
|
hr = StartShutdown();
|
|
_JumpIfError(hr, error, "StartShutdown");
|
|
|
|
if (bRestartService) {
|
|
pfnShutdownWorker = SendServiceRestartWorker;
|
|
} else {
|
|
pfnShutdownWorker = SendServiceShutdownWorker;
|
|
}
|
|
|
|
if (bAsync) {
|
|
if (!QueueUserWorkItem(pfnShutdownWorker, UIntToPtr(dwErr), WT_EXECUTELONGFUNCTION)) {
|
|
_JumpLastError(hr, error, "QueueUserWorkItem");
|
|
}
|
|
} else {
|
|
hr = pfnShutdownWorker(UIntToPtr(dwErr));
|
|
// BUG 620714: don't log after the service has shutdown:
|
|
// _JumpIfError(hr, error, "pfnShutdownWorker");
|
|
goto error;
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE DWORD WINAPI W32TimeServiceCtrlHandler(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) {
|
|
bool bHandled = false;
|
|
bool bShutdownDisallowed = false;
|
|
HRESULT hr;
|
|
|
|
_BeginTryWith(hr) {
|
|
|
|
// We don't want to be shutdown while processing a service control
|
|
hr = AllowShutdown(false);
|
|
_JumpIfError(hr, error, "AllowShutdown");
|
|
bShutdownDisallowed = true;
|
|
|
|
FileLog0(FL_ServiceControl, L"W32TimeHandler called: ");
|
|
switch (dwControl) {
|
|
case SERVICE_CONTROL_STOP:
|
|
FileLogA0(FL_ServiceControl, L"SERVICE_CONTROL_STOP\n");
|
|
|
|
// Let the SCM know we're shutting down...
|
|
hr=MySetServicePending(SERVICE_STOP_PENDING, 1, WAITHINT_WAITFORMANAGER);
|
|
_JumpIfError(hr, error, "MySetServicePending");
|
|
|
|
// We can't attempt to shutdown the service is shutdown is disallowed!
|
|
hr = AllowShutdown(true);
|
|
_JumpIfError(hr, error, "AllowShutdown");
|
|
bShutdownDisallowed = false;
|
|
|
|
// Stop the service.
|
|
SendServiceShutdown(g_servicestatus.dwWin32ExitCode, FALSE /*don't restart*/, TRUE /*async*/);
|
|
bHandled=true;
|
|
break;
|
|
case SERVICE_CONTROL_PAUSE:
|
|
FileLogA0(FL_ServiceControl, L"SERVICE_CONTROL_PAUSE\n"); break;
|
|
case SERVICE_CONTROL_CONTINUE:
|
|
FileLogA0(FL_ServiceControl, L"SERVICE_CONTROL_CONTINUE\n"); break;
|
|
case SERVICE_CONTROL_INTERROGATE:
|
|
FileLogA0(FL_ServiceControl, L"SERVICE_CONTROL_INTERROGATE\n");
|
|
// our default handling is the right thing to do
|
|
break;
|
|
case SERVICE_CONTROL_SHUTDOWN:
|
|
FileLogA0(FL_ServiceControl, L"SERVICE_CONTROL_SHUTDOWN\n");
|
|
|
|
// We can't attempt to shutdown the service is shutdown is disallowed!
|
|
hr = AllowShutdown(true);
|
|
_JumpIfError(hr, error, "AllowShutdown");
|
|
bShutdownDisallowed = false;
|
|
|
|
// Perform minimal shutdown.
|
|
HandleManagerSystemShutdown();
|
|
bHandled=true;
|
|
break;
|
|
case SERVICE_CONTROL_PARAMCHANGE:
|
|
FileLogA0(FL_ServiceControl, L"SERVICE_CONTROL_PARAMCHANGE\n");
|
|
// We could handle this in the current thread, but this could take
|
|
// a while, so we use the thread pool instead.
|
|
if (!SetEvent(g_state.hManagerParamChangeEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
// our default handling is the right thing to do
|
|
break;
|
|
case SERVICE_CONTROL_NETBINDADD:
|
|
FileLogA0(FL_ServiceControl, L"SERVICE_CONTROL_NETBINDADD\n"); break;
|
|
case SERVICE_CONTROL_NETBINDREMOVE:
|
|
FileLogA0(FL_ServiceControl, L"SERVICE_CONTROL_NETBINDREMOVE\n"); break;
|
|
case SERVICE_CONTROL_NETBINDENABLE:
|
|
FileLogA0(FL_ServiceControl, L"SERVICE_CONTROL_NETBINDENABLE\n"); break;
|
|
case SERVICE_CONTROL_NETBINDDISABLE:
|
|
FileLogA0(FL_ServiceControl, L"SERVICE_CONTROL_NETBINDDISABLE\n"); break;
|
|
case SERVICE_CONTROL_DEVICEEVENT:
|
|
FileLogA2(FL_ServiceControl, L"SERVICE_CONTROL_DEVICEEVENT(0x%08X, 0x%p)\n", dwEventType, lpEventData); break;
|
|
case SERVICE_CONTROL_HARDWAREPROFILECHANGE:
|
|
FileLogA2(FL_ServiceControl, L"SERVICE_CONTROL_HARDWAREPROFILECHANGE(0x%08X, 0x%p)\n", dwEventType, lpEventData); break;
|
|
case SERVICE_CONTROL_POWEREVENT:
|
|
FileLogA2(FL_ServiceControl, L"SERVICE_CONTROL_POWEREVENT(0x%08X, 0x%p)\n", dwEventType, lpEventData);
|
|
|
|
switch (dwEventType)
|
|
{
|
|
case PBT_APMSUSPEND:
|
|
// System is suspending operation.
|
|
hr = HandleManagerApmSuspend();
|
|
_JumpIfError(hr, error, "HandleManagerApmSuspend");
|
|
break;
|
|
|
|
case PBT_APMRESUMECRITICAL:
|
|
case PBT_APMRESUMESUSPEND:
|
|
// NOTE: services will get APMRESUMESUSPEND regardless of whether we're recovering from
|
|
// a critical suspension. So, we need our code to handle a resume without knowing
|
|
//
|
|
|
|
// Operation resuming after suspension.
|
|
hr = HandleManagerApmResumeSuspend();
|
|
_JumpIfError(hr, error, "HandleManagerApmResumeSuspend");
|
|
break;
|
|
|
|
case PBT_APMQUERYSUSPENDFAILED: // Suspension request denied.
|
|
case PBT_APMQUERYSUSPEND: // Request for permission to suspend.
|
|
case PBT_APMBATTERYLOW: // Battery power is low.
|
|
case PBT_APMRESUMEAUTOMATIC: // Operation resuming automatically after event.
|
|
case PBT_APMOEMEVENT: // OEM-defined event occurred.
|
|
case PBT_APMPOWERSTATUSCHANGE: // Power status has changed.
|
|
// These power events don't need to be handled by w32time.
|
|
break;
|
|
default:
|
|
hr = E_INVALIDARG;
|
|
_JumpError(hr, error, "SERVICE_CONTROL_POWEREVENT: bad wparam.");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
FileLogA3(FL_ServiceControl, L"unknown service control (0x%08X, 0x%08X, 0x%p)\n", dwControl, dwEventType, lpEventData); break;
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "W32TimeServiceCtrlHandler: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (!bHandled) {
|
|
HRESULT hr2=MySetServiceStatus(g_servicestatus.dwCurrentState, g_servicestatus.dwCheckPoint,
|
|
g_servicestatus.dwWaitHint, g_servicestatus.dwServiceSpecificExitCode);
|
|
_TeardownError(hr, hr2, "MySetServiceStatus");
|
|
}
|
|
if (bShutdownDisallowed) {
|
|
HRESULT hr2 = AllowShutdown(true);
|
|
_TeardownError(hr, hr2, "AllowShutdown");
|
|
}
|
|
_IgnoreIfError(hr, "W32TimeServiceCtrlHandler");
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT GetPolicyEnabledProviderListp(TimeProvider ** pptpList, TimeProvider ** pptpListDisabled) {
|
|
HRESULT hr;
|
|
DWORD dwError;
|
|
unsigned int nKeyIndex;
|
|
WCHAR wszNameBuf[MAX_PATH];
|
|
DWORD dwNameLength;
|
|
FILETIME ftLastWrite;
|
|
DWORD dwType;
|
|
DWORD dwEnabled;
|
|
DWORD dwSize;
|
|
|
|
// must be cleaned up
|
|
HKEY hkTimeProvs=NULL;
|
|
HKEY hkCurProv = NULL;
|
|
|
|
TimeProvider * ptpList=NULL;
|
|
TimeProvider * ptpListDisabled=NULL;
|
|
TimeProvider * ptpNew=NULL;
|
|
|
|
// initialize out params
|
|
*pptpList=NULL;
|
|
|
|
// get the key with the time providers
|
|
dwError=RegOpenKeyEx(HKEY_LOCAL_MACHINE, wszW32TimeRegKeyPolicyTimeProviders, 0, KEY_READ, &hkTimeProvs);
|
|
if (ERROR_SUCCESS!=dwError) {
|
|
// We can't proceed, but we don't want to return an error -- policy just isn't configured.
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_JumpErrorStr(hr, done, "RegOpenKeyEx", wszW32TimeRegKeyPolicyTimeProviders);
|
|
}
|
|
|
|
// enumerate the subkeys
|
|
for (nKeyIndex=0; true; nKeyIndex++) {
|
|
// get the next key name
|
|
dwNameLength=MAX_PATH;
|
|
dwError=RegEnumKeyEx(hkTimeProvs, nKeyIndex, wszNameBuf, &dwNameLength, NULL, NULL, NULL, &ftLastWrite);
|
|
if (ERROR_NO_MORE_ITEMS==dwError) {
|
|
break;
|
|
} else if (ERROR_SUCCESS!=dwError) {
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_JumpError(hr, error, "RegEnumKeyEx");
|
|
}
|
|
|
|
FileLog1(FL_ReadConigAnnounceLow, L"ReadConfig (policy): Found provider '%s':\n", wszNameBuf);
|
|
|
|
// get the key of the current time provider
|
|
dwError=RegOpenKeyEx(hkTimeProvs, wszNameBuf, 0, KEY_READ, &hkCurProv);
|
|
if (ERROR_SUCCESS!=dwError) {
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_JumpErrorStr(hr, error, "RegOpenKeyEx", wszNameBuf);
|
|
}
|
|
|
|
// see if the provider is enabled
|
|
dwSize=sizeof(DWORD);
|
|
dwError=RegQueryValueEx(hkCurProv, wszW32TimeRegValueEnabled, NULL, &dwType, (BYTE *)&dwEnabled, &dwSize);
|
|
if (ERROR_SUCCESS!=dwError) {
|
|
// This isn't a fatal error, policy just isn't configured.
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_IgnoreErrorStr(hr, "RegQueryValueEx", wszW32TimeRegValueEnabled);
|
|
} else {
|
|
_Verify(REG_DWORD==dwType, hr, error);
|
|
FileLog2(FL_ReadConigAnnounceLow, L"ReadConfig (policy): '%s'=0x%08X\n", wszW32TimeRegValueEnabled, dwEnabled);
|
|
|
|
// create a new element
|
|
ptpNew=(TimeProvider *)LocalAlloc(LPTR, sizeof(TimeProvider));
|
|
_JumpIfOutOfMemory(hr, error, ptpNew);
|
|
|
|
// copy the provider name
|
|
ptpNew->wszProvName=(WCHAR *)LocalAlloc(LPTR, (wcslen(wszNameBuf)+1)*sizeof(WCHAR));
|
|
_JumpIfOutOfMemory(hr, error, ptpNew->wszProvName);
|
|
wcscpy(ptpNew->wszProvName, wszNameBuf);
|
|
|
|
// add it to one of our lists
|
|
if (0!=dwEnabled) {
|
|
// use this provider
|
|
ptpNew->ptpNext=ptpList;
|
|
ptpList=ptpNew;
|
|
} else {
|
|
ptpNew->ptpNext=ptpListDisabled;
|
|
ptpListDisabled=ptpNew;
|
|
}
|
|
ptpNew=NULL;
|
|
} // <- end if query value 'enabled' successful
|
|
|
|
// done with this key
|
|
RegCloseKey(hkCurProv);
|
|
hkCurProv=NULL;
|
|
|
|
} // <- end provider enumeration loop
|
|
|
|
// successful
|
|
done:
|
|
hr=S_OK;
|
|
*pptpList=ptpList;
|
|
*pptpListDisabled=ptpListDisabled;
|
|
ptpList=NULL;
|
|
ptpListDisabled=NULL;
|
|
|
|
error:
|
|
if (NULL!=ptpNew) {
|
|
if (NULL!=ptpNew->wszProvName) {
|
|
LocalFree(ptpNew->wszProvName);
|
|
}
|
|
LocalFree(ptpNew);
|
|
}
|
|
if (NULL!=hkCurProv) {
|
|
RegCloseKey(hkCurProv);
|
|
}
|
|
if (NULL!=hkTimeProvs) {
|
|
RegCloseKey(hkTimeProvs);
|
|
}
|
|
if (NULL!=ptpList) {
|
|
FreeTimeProviderList(ptpList);
|
|
}
|
|
if (NULL!=ptpListDisabled) {
|
|
FreeTimeProviderList(ptpListDisabled);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT GetPreferenceEnabledProviderListp(TimeProvider ** pptpList, TimeProvider ** pptpListDisabled) {
|
|
HRESULT hr;
|
|
DWORD dwError;
|
|
unsigned int nKeyIndex;
|
|
WCHAR wszNameBuf[MAX_PATH];
|
|
DWORD dwNameLength;
|
|
FILETIME ftLastWrite;
|
|
DWORD dwType;
|
|
DWORD dwEnabled;
|
|
DWORD dwSize;
|
|
WCHAR wszDllBuf[MAX_PATH];
|
|
DWORD dwInputProvider;
|
|
|
|
// must be cleaned up
|
|
HKEY hkTimeProvs=NULL;
|
|
HKEY hkCurProv = NULL;
|
|
|
|
TimeProvider * ptpList=NULL;
|
|
TimeProvider * ptpListDisabled=NULL;
|
|
TimeProvider * ptpNew=NULL;
|
|
|
|
// initialize out params
|
|
*pptpList=NULL;
|
|
|
|
// get the key with the time providers
|
|
dwError=RegOpenKeyEx(HKEY_LOCAL_MACHINE, wszW32TimeRegKeyTimeProviders, 0, KEY_READ, &hkTimeProvs);
|
|
if (ERROR_SUCCESS!=dwError) {
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_JumpErrorStr(hr, error, "RegOpenKeyEx", wszW32TimeRegKeyTimeProviders);
|
|
}
|
|
|
|
// enumerate the subkeys
|
|
for (nKeyIndex=0; true; nKeyIndex++) {
|
|
// get the next key name
|
|
dwNameLength=MAX_PATH;
|
|
dwError=RegEnumKeyEx(hkTimeProvs, nKeyIndex, wszNameBuf, &dwNameLength, NULL, NULL, NULL, &ftLastWrite);
|
|
if (ERROR_NO_MORE_ITEMS==dwError) {
|
|
break;
|
|
} else if (ERROR_SUCCESS!=dwError) {
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_JumpError(hr, error, "RegEnumKeyEx");
|
|
}
|
|
|
|
FileLog1(FL_ReadConigAnnounceLow, L"ReadConfig: Found provider '%s':\n", wszNameBuf);
|
|
|
|
// get the key of the current time provider
|
|
dwError=RegOpenKeyEx(hkTimeProvs, wszNameBuf, 0, KEY_READ, &hkCurProv);
|
|
if (ERROR_SUCCESS!=dwError) {
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_JumpErrorStr(hr, error, "RegOpenKeyEx", wszNameBuf);
|
|
}
|
|
|
|
// see if the provider is enabled
|
|
dwSize=sizeof(DWORD);
|
|
dwError=RegQueryValueEx(hkCurProv, wszW32TimeRegValueEnabled, NULL, &dwType, (BYTE *)&dwEnabled, &dwSize);
|
|
if (ERROR_SUCCESS!=dwError) {
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_JumpErrorStr(hr, error, "RegQueryValueEx", wszW32TimeRegValueEnabled);
|
|
} else {
|
|
_Verify(REG_DWORD==dwType, hr, error);
|
|
FileLog2(FL_ReadConigAnnounceLow, L"ReadConfig: '%s'=0x%08X\n", wszW32TimeRegValueEnabled, dwEnabled);
|
|
|
|
// get the dll name
|
|
dwSize=MAX_PATH*sizeof(WCHAR);
|
|
dwError=RegQueryValueEx(hkCurProv, wszW32TimeRegValueDllName, NULL, &dwType, (BYTE *)wszDllBuf, &dwSize);
|
|
if (ERROR_SUCCESS!=dwError) {
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_JumpErrorStr(hr, error, "RegQueryValueEx", wszW32TimeRegValueDllName);
|
|
}
|
|
_Verify(REG_SZ==dwType, hr, error);
|
|
FileLog2(FL_ReadConigAnnounceLow, L"ReadConfig: '%s'='%s'\n", wszW32TimeRegValueDllName, wszDllBuf);
|
|
|
|
// get the provider type
|
|
dwSize=sizeof(DWORD);
|
|
dwError=RegQueryValueEx(hkCurProv, wszW32TimeRegValueInputProvider, NULL, &dwType, (BYTE *)&dwInputProvider, &dwSize);
|
|
if (ERROR_SUCCESS!=dwError) {
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_JumpErrorStr(hr, error, "RegQueryValueEx", wszW32TimeRegValueInputProvider);
|
|
}
|
|
_Verify(REG_DWORD==dwType, hr, error);
|
|
FileLog2(FL_ReadConigAnnounceLow, L"ReadConfig: '%s'=0x%08X\n", wszW32TimeRegValueInputProvider, dwInputProvider);
|
|
|
|
// create a new element
|
|
ptpNew=(TimeProvider *)LocalAlloc(LPTR, sizeof(TimeProvider));
|
|
_JumpIfOutOfMemory(hr, error, ptpNew);
|
|
|
|
// copy the provider name
|
|
ptpNew->wszProvName=(WCHAR *)LocalAlloc(LPTR, (wcslen(wszNameBuf)+1)*sizeof(WCHAR));
|
|
_JumpIfOutOfMemory(hr, error, ptpNew->wszProvName);
|
|
wcscpy(ptpNew->wszProvName, wszNameBuf);
|
|
|
|
// copy the dll name
|
|
ptpNew->wszDllName=(WCHAR *)LocalAlloc(LPTR, (wcslen(wszDllBuf)+1)*sizeof(WCHAR));
|
|
_JumpIfOutOfMemory(hr, error, ptpNew->wszDllName);
|
|
wcscpy(ptpNew->wszDllName, wszDllBuf);
|
|
|
|
// set the provider type
|
|
ptpNew->bInputProvider=(dwInputProvider?true:false);
|
|
|
|
// add it to one of our lists
|
|
if (0!=dwEnabled) {
|
|
// use this provider
|
|
ptpNew->ptpNext=ptpList;
|
|
ptpList=ptpNew;
|
|
ptpNew=NULL;
|
|
|
|
} else {
|
|
ptpNew->ptpNext=ptpListDisabled;
|
|
ptpListDisabled=ptpNew;
|
|
ptpNew=NULL;
|
|
}
|
|
} // <- end if query value 'enabled' successful
|
|
|
|
// done with this key
|
|
RegCloseKey(hkCurProv);
|
|
hkCurProv=NULL;
|
|
|
|
} // <- end provider enumeration loop
|
|
|
|
// successful
|
|
hr=S_OK;
|
|
*pptpList=ptpList;
|
|
*pptpListDisabled=ptpListDisabled;
|
|
ptpList=NULL;
|
|
ptpListDisabled=NULL;
|
|
|
|
error:
|
|
if (NULL!=ptpNew) {
|
|
if (NULL!=ptpNew->wszDllName) {
|
|
LocalFree(ptpNew->wszDllName);
|
|
}
|
|
if (NULL!=ptpNew->wszProvName) {
|
|
LocalFree(ptpNew->wszProvName);
|
|
}
|
|
LocalFree(ptpNew);
|
|
}
|
|
if (NULL!=hkCurProv) {
|
|
RegCloseKey(hkCurProv);
|
|
}
|
|
if (NULL!=hkTimeProvs) {
|
|
RegCloseKey(hkTimeProvs);
|
|
}
|
|
if (NULL!=ptpList) {
|
|
FreeTimeProviderList(ptpList);
|
|
}
|
|
if (NULL!=ptpListDisabled) {
|
|
FreeTimeProviderList(ptpListDisabled);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
// read the provider list from the policy location of the registry
|
|
MODULEPRIVATE HRESULT GetEnabledProviderList(TimeProvider ** pptpList) {
|
|
HRESULT hr;
|
|
TimeProvider *ptpPolicy = NULL;
|
|
TimeProvider *ptpPolicyDisabled = NULL;
|
|
TimeProvider *ptpPreference = NULL;
|
|
TimeProvider *ptpPreferenceDisabled = NULL;
|
|
TimeProvider *ptpNew = NULL;
|
|
TimeProvider *ptpList = NULL;
|
|
TimeProvider *ptpNextSave;
|
|
|
|
WCHAR *pwszProvNameSave;
|
|
|
|
hr = GetPreferenceEnabledProviderListp(&ptpPreference, &ptpPreferenceDisabled);
|
|
_JumpIfError(hr, error, "GetPrefenceEnabledProviderListp");
|
|
|
|
hr = GetPolicyEnabledProviderListp(&ptpPolicy, &ptpPolicyDisabled);
|
|
_JumpIfError(hr, error, "GetPolicyEnabledProviderListp");
|
|
|
|
// Merge the policy and preference provider lists. The merge algorithm works as follows:
|
|
//
|
|
// FOR all providers enabled through preferences
|
|
// IF the provider is enabled through policy, copy fields from the preference entry, and continue
|
|
// ELSE IF the provider is disabled through policy, continue
|
|
// ELSE the provider is not configured through policy, add it to the enabled policy list
|
|
// FOR all providers disabled through preferences
|
|
// IF the provider is disabled through policy, continue
|
|
// ELSE IF the provider is not configured through policy, continue,
|
|
// ELSE the provider is enabled through policy, copy fields from the preference entry
|
|
//
|
|
// The result is stored in the policy-enabled time provider list.
|
|
//
|
|
for (TimeProvider *ptpTmp = ptpPreference; NULL != ptpTmp; ptpTmp = ptpTmp->ptpNext) {
|
|
bool bPolicyHasEnabledProvider = false;
|
|
bool bPolicyHasDisabledProvider = false;
|
|
bool bUseCurrentProvider = false;
|
|
|
|
// Is the provider enabled through policy?
|
|
for (TimeProvider *ptpPolicyTmp = ptpPolicy; NULL != ptpPolicyTmp; ptpPolicyTmp = ptpPolicyTmp->ptpNext) {
|
|
if (0 == _wcsicmp(ptpTmp->wszProvName, ptpPolicyTmp->wszProvName)) {
|
|
bUseCurrentProvider = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bPolicyHasEnabledProvider) {
|
|
// The provider isn't enabled through policy. Is it explicitly disabled?
|
|
for (TimeProvider *ptpPolicyTmp = ptpPolicyDisabled; NULL != ptpPolicyTmp; ptpPolicyTmp = ptpPolicyTmp->ptpNext) {
|
|
if (0 == _wcsicmp(ptpTmp->wszProvName, ptpPolicyTmp->wszProvName)) {
|
|
// The provider is explicitly disabled
|
|
bPolicyHasDisabledProvider = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bPolicyHasDisabledProvider) {
|
|
bUseCurrentProvider = true;
|
|
}
|
|
}
|
|
|
|
if (bUseCurrentProvider) {
|
|
ptpNew = (TimeProvider *)LocalAlloc(LPTR, sizeof(TimeProvider));
|
|
_JumpIfOutOfMemory(hr, error, ptpNew);
|
|
memcpy(ptpNew, ptpTmp, sizeof(TimeProvider));
|
|
ptpTmp->wszProvName = NULL; // prevent prov name from being double-freed
|
|
ptpTmp->wszDllName = NULL; // prevent dll name from being double-freed
|
|
|
|
ptpNew->ptpNext = ptpList;
|
|
ptpList = ptpNew;
|
|
ptpNew = NULL;
|
|
}
|
|
}
|
|
|
|
for (TimeProvider *ptpTmp = ptpPreferenceDisabled; NULL != ptpTmp; ptpTmp = ptpTmp->ptpNext) {
|
|
bool bPolicyHasEnabledProvider = false;
|
|
|
|
// Is the provider enabled through policy?
|
|
for (TimeProvider *ptpPolicyTmp = ptpPolicy; NULL != ptpPolicyTmp; ptpPolicyTmp = ptpPolicyTmp->ptpNext) {
|
|
if (0 == _wcsicmp(ptpTmp->wszProvName, ptpPolicyTmp->wszProvName)) {
|
|
// The provider disabled through preferences is actually enabled through policy. Copy over
|
|
// fields from the disabled preference list.
|
|
ptpNew = (TimeProvider *)LocalAlloc(LPTR, sizeof(TimeProvider));
|
|
_JumpIfOutOfMemory(hr, error, ptpNew);
|
|
|
|
memcpy(ptpNew, ptpTmp, sizeof(TimeProvider));
|
|
ptpTmp->wszProvName = NULL;
|
|
ptpTmp->wszDllName = NULL;
|
|
|
|
ptpNew->ptpNext = ptpList;
|
|
ptpList = ptpNew;
|
|
ptpNew = NULL;
|
|
|
|
break ;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify that all providers have a dll name:
|
|
for (TimeProvider *ptpTmp = ptpList; NULL != ptpTmp; ptpTmp = ptpTmp->ptpNext) {
|
|
if (NULL == ptpTmp->wszDllName) {
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
|
_JumpErrorStr(hr, error, "Verify configured providers: ", ptpTmp->wszDllName);
|
|
}
|
|
}
|
|
|
|
*pptpList = ptpList;
|
|
ptpList = NULL;
|
|
hr = S_OK;
|
|
error:
|
|
if (NULL != ptpPolicy) {
|
|
FreeTimeProviderList(ptpPolicy);
|
|
}
|
|
if (NULL != ptpPolicyDisabled) {
|
|
FreeTimeProviderList(ptpPolicyDisabled);
|
|
}
|
|
if (NULL != ptpPreference) {
|
|
FreeTimeProviderList(ptpPreference);
|
|
}
|
|
if (NULL != ptpPreferenceDisabled) {
|
|
FreeTimeProviderList(ptpPreferenceDisabled);
|
|
}
|
|
if (NULL != ptpNew) {
|
|
FreeTimeProviderList(ptpNew);
|
|
}
|
|
|
|
if (NULL != ptpList) {
|
|
FreeTimeProviderList(ptpList);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// read the current configuration. This does not modify the active
|
|
// configuration, so that changed can be detected.
|
|
MODULEPRIVATE HRESULT ReadConfig(ConfigInfo ** ppciConfig) {
|
|
HRESULT hr;
|
|
BOOL bSyncToCmosDisabled;
|
|
DWORD dwCurrentSecPerTick;
|
|
DWORD dwDefaultSecPerTick;
|
|
DWORD dwError;
|
|
DWORD dwSize;
|
|
DWORD dwType;
|
|
|
|
// must be cleaned up
|
|
ConfigInfo *pciConfig = NULL;
|
|
HKEY hkPolicyConfig = NULL;
|
|
HKEY hkPreferenceConfig = NULL;
|
|
|
|
// allocate a new config structure
|
|
pciConfig=(ConfigInfo *)LocalAlloc(LPTR, sizeof(ConfigInfo));
|
|
_JumpIfOutOfMemory(hr, error, pciConfig);
|
|
|
|
// get the list of providers
|
|
hr=GetEnabledProviderList(&pciConfig->ptpProviderList);
|
|
_JumpIfError(hr, error, "GetEnabledProviderList");
|
|
|
|
// get our preference config key
|
|
dwError=RegOpenKeyEx(HKEY_LOCAL_MACHINE, wszW32TimeRegKeyConfig, 0, KEY_READ, &hkPreferenceConfig);
|
|
if (ERROR_SUCCESS!=dwError) {
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
_JumpErrorStr(hr, error, "RegOpenKeyEx", wszW32TimeRegKeyConfig);
|
|
}
|
|
|
|
// get our policy config key
|
|
dwError=RegOpenKeyEx(HKEY_LOCAL_MACHINE, wszW32TimeRegKeyPolicyConfig, 0, KEY_READ, &hkPolicyConfig);
|
|
if (ERROR_SUCCESS!=dwError) {
|
|
hr=HRESULT_FROM_WIN32(dwError);
|
|
// May just not have policy settings on this machine -- that's OK.
|
|
_IgnoreErrorStr(hr, "RegOpenKeyEx", wszW32TimeRegKeyPolicyConfig);
|
|
}
|
|
|
|
// read all the values for the local clock configuration
|
|
// and service configuration
|
|
{
|
|
struct {
|
|
WCHAR * wszRegValue;
|
|
DWORD * pdwValue;
|
|
} rgRegParams[]={
|
|
{
|
|
wszW32TimeRegValuePhaseCorrectRate,
|
|
&pciConfig->lcci.dwPhaseCorrectRate
|
|
},{
|
|
wszW32TimeRegValueUpdateInterval,
|
|
&pciConfig->lcci.dwUpdateInterval
|
|
},{
|
|
wszW32TimeRegValueFrequencyCorrectRate,
|
|
&pciConfig->lcci.dwFrequencyCorrectRate
|
|
},{
|
|
wszW32TimeRegValuePollAdjustFactor,
|
|
&pciConfig->lcci.dwPollAdjustFactor
|
|
},{
|
|
wszW32TimeRegValueLargePhaseOffset,
|
|
&pciConfig->lcci.dwLargePhaseOffset
|
|
},{
|
|
wszW32TimeRegValueSpikeWatchPeriod,
|
|
&pciConfig->lcci.dwSpikeWatchPeriod
|
|
},{
|
|
wszW32TimeRegValueHoldPeriod,
|
|
&pciConfig->lcci.dwHoldPeriod
|
|
},{
|
|
wszW32TimeRegValueMinPollInterval,
|
|
&pciConfig->lcci.dwMinPollInterval
|
|
},{
|
|
wszW32TimeRegValueMaxPollInterval,
|
|
&pciConfig->lcci.dwMaxPollInterval
|
|
},{
|
|
wszW32TimeRegValueAnnounceFlags,
|
|
&pciConfig->dwAnnounceFlags
|
|
},{
|
|
wszW32TimeRegValueLocalClockDispersion,
|
|
&pciConfig->lcci.dwLocalClockDispersion
|
|
},{
|
|
wszW32TimeRegValueMaxNegPhaseCorrection,
|
|
&pciConfig->lcci.dwMaxNegPhaseCorrection
|
|
},{
|
|
wszW32TimeRegValueMaxPosPhaseCorrection,
|
|
&pciConfig->lcci.dwMaxPosPhaseCorrection
|
|
},{
|
|
wszW32TimeRegValueEventLogFlags,
|
|
&pciConfig->dwEventLogFlags
|
|
},{
|
|
wszW32TimeRegValueMaxAllowedPhaseOffset,
|
|
&pciConfig->lcci.dwMaxAllowedPhaseOffset
|
|
}
|
|
};
|
|
|
|
// Declare the reg values which we must guarantee not to be zero
|
|
// (we may divide by these values). These values will all be mapped
|
|
// to 1 if they are 0 in the registry. Otherwise, they will be untouched.
|
|
LPWSTR rgwszCantBeZero[] = {
|
|
wszW32TimeRegValuePhaseCorrectRate,
|
|
wszW32TimeRegValueUpdateInterval,
|
|
wszW32TimeRegValueFrequencyCorrectRate
|
|
};
|
|
|
|
// for each param
|
|
for (unsigned int nParamIndex=0; nParamIndex<ARRAYSIZE(rgRegParams); nParamIndex++) {
|
|
// Read in our preferences from the registry first.
|
|
dwSize=sizeof(DWORD);
|
|
hr=MyRegQueryPolicyValueEx(hkPreferenceConfig, hkPolicyConfig, rgRegParams[nParamIndex].wszRegValue, NULL, &dwType, (BYTE *)rgRegParams[nParamIndex].pdwValue, &dwSize);
|
|
_JumpIfErrorStr(hr, error, "MyRegQueryPolicyValueEx", rgRegParams[nParamIndex].wszRegValue);
|
|
_Verify(REG_DWORD==dwType, hr, error);
|
|
|
|
// for each value which must be mapped to zero
|
|
for (unsigned int nSubIndex=0; nSubIndex<ARRAYSIZE(rgwszCantBeZero); nSubIndex++) {
|
|
// see if the current value matches
|
|
if (0 == wcscmp(rgwszCantBeZero[nSubIndex], rgRegParams[nParamIndex].wszRegValue)) {
|
|
// there's a match -- see if this value is 0 in the registry.
|
|
if (0 == *(rgRegParams[nParamIndex].pdwValue)) {
|
|
// it's 0, map it to 1.
|
|
*(rgRegParams[nParamIndex].pdwValue) = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Log the value we've acquired:
|
|
FileLog2(FL_ReadConigAnnounceLow, L"ReadConfig: '%s'=0x%08X\n", rgRegParams[nParamIndex].wszRegValue, *rgRegParams[nParamIndex].pdwValue);
|
|
}
|
|
}
|
|
|
|
// BUG (fixed below): 550568 w32time: doesn't work when backed up and restored on different hardware which has a different timer interrupt rate
|
|
// We used to store these values in the registry, but this didn't work well with ntbackup (see bug).
|
|
//
|
|
// BUGBUG (not fixed): we may get into trouble here if someone else is already controling the time adjustment. We might want to revisit for
|
|
// longhorn.
|
|
if (!GetSystemTimeAdjustment(&dwCurrentSecPerTick, &dwDefaultSecPerTick, &bSyncToCmosDisabled)) {
|
|
_JumpLastError(hr, error, "GetSystemTimeAdjustment");
|
|
}
|
|
|
|
pciConfig->lcci.dwLastClockRate = dwDefaultSecPerTick;
|
|
pciConfig->lcci.dwMinClockRate = dwDefaultSecPerTick-(dwDefaultSecPerTick/400); // 1/4%
|
|
pciConfig->lcci.dwMaxClockRate = dwDefaultSecPerTick+(dwDefaultSecPerTick/400); // 1/4%
|
|
|
|
// success
|
|
hr=S_OK;
|
|
*ppciConfig=pciConfig;
|
|
pciConfig=NULL;
|
|
|
|
error:
|
|
if (NULL!=pciConfig) {
|
|
FreeConfigInfo(pciConfig);
|
|
}
|
|
if (NULL!=hkPreferenceConfig) {
|
|
RegCloseKey(hkPreferenceConfig);
|
|
}
|
|
if (NULL!=hkPolicyConfig) {
|
|
RegCloseKey(hkPolicyConfig);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// This function is passed to providers.
|
|
// No synchronization currently necessary.
|
|
MODULEPRIVATE HRESULT __stdcall MyLogTimeProvEvent(IN WORD wType, IN WCHAR * wszProvName, IN WCHAR * wszMessage) {
|
|
if (NULL==wszProvName || NULL==wszMessage) {
|
|
return E_INVALIDARG;
|
|
}
|
|
const WCHAR * rgwszStrings[2]={wszProvName, wszMessage};
|
|
|
|
switch (wType) {
|
|
case EVENTLOG_ERROR_TYPE:
|
|
return MyLogEvent(EVENTLOG_ERROR_TYPE, MSG_TIMEPROV_ERROR, 2, rgwszStrings);
|
|
case EVENTLOG_WARNING_TYPE:
|
|
return MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_TIMEPROV_WARNING, 2, rgwszStrings);
|
|
case EVENTLOG_INFORMATION_TYPE:
|
|
return MyLogEvent(EVENTLOG_INFORMATION_TYPE, MSG_TIMEPROV_INFORMATIONAL, 2, rgwszStrings);
|
|
default:
|
|
return E_INVALIDARG;
|
|
};
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT MyGetLastSyncTime(OUT unsigned __int64 * pqwLastSyncTime) {
|
|
HRESULT hr;
|
|
unsigned __int64 qwLastSyncTicks;
|
|
unsigned __int64 qwTicksNow;
|
|
unsigned __int64 qwNow;
|
|
unsigned __int64 qwLastSyncTime;
|
|
|
|
// Get the last sync time, expressed in ticks
|
|
qwLastSyncTime = g_state.qwLastSyncTicks.getValue();
|
|
|
|
// Get the current tick count
|
|
hr = AccurateGetTickCountSafe(&qwTicksNow, false /*get tick count*/);
|
|
_JumpIfError(hr, error, "AccurateGetTickCountSafe");
|
|
|
|
// Get the current system time
|
|
AccurateGetSystemTime(&qwNow);
|
|
|
|
// Subtract the number of 10^-7 s intervals that have passed to get
|
|
// the last sync time
|
|
qwLastSyncTime = qwNow - ((qwTicksNow-qwLastSyncTime)*10000);
|
|
|
|
// Success!
|
|
*pqwLastSyncTime = qwLastSyncTime;
|
|
hr = S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT __stdcall MyGetTimeSysInfo(IN TimeSysInfo eInfo, OUT void * pvInfo) {
|
|
if (NULL==pvInfo) {
|
|
return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
|
|
}
|
|
|
|
switch (eInfo) {
|
|
|
|
case TSI_LastSyncTime: // (unsigned __int64 *)pvInfo, NtTimeEpoch, in (10^-7)s
|
|
return MyGetLastSyncTime((unsigned __int64 *)pvInfo);
|
|
|
|
case TSI_ClockTickSize: // (unsigned __int64 *)pvInfo, NtTimePeriod, in (10^-7)s
|
|
*((unsigned __int64 *)pvInfo)=g_state.dwClockRate;
|
|
break;
|
|
|
|
case TSI_ClockPrecision: // ( signed __int32 *)pvInfo, ClockTickSize, in log2(s)
|
|
*((signed __int32 *)pvInfo)=g_state.nClockPrecision;
|
|
break;
|
|
|
|
case TSI_CurrentTime: // (unsigned __int64 *)pvInfo, NtTimeEpoch, in (10^-7)s
|
|
AccurateGetSystemTime(((unsigned __int64 *)pvInfo));
|
|
break;
|
|
|
|
case TSI_PhaseOffset: // ( signed __int64 *)pvInfo, opaque
|
|
*((signed __int64 *)pvInfo)=g_state.toSysPhaseOffset.getValue();
|
|
break;
|
|
|
|
case TSI_TickCount: // (unsigned __int64 *)pvInfo, opaque
|
|
return AccurateGetTickCountSafe((unsigned __int64 *)pvInfo, true /*get interrupt count*/);
|
|
|
|
case TSI_LeapFlags: // ( BYTE *)pvInfo, a warning of an impending leap second or loss of synchronization
|
|
*((BYTE *)pvInfo)=(BYTE)g_state.eLeapIndicator;
|
|
break;
|
|
|
|
case TSI_Stratum: // ( BYTE *)pvInfo, how far away the computer is from a reference source
|
|
*((BYTE *)pvInfo)=(BYTE)g_state.nStratum;
|
|
break;
|
|
|
|
case TSI_ReferenceIdentifier: // ( DWORD *)pvInfo, NtpRefId
|
|
*((DWORD *)pvInfo)=g_state.refidSource.value;
|
|
break;
|
|
|
|
case TSI_PollInterval: // ( signed __int32 *)pvInfo, poll interval, in log2(s)
|
|
*((signed __int32 *)pvInfo)=g_state.nPollInterval;
|
|
break;
|
|
|
|
case TSI_RootDelay: // ( signed __int64 *)pvInfo, NtTimeOffset, in (10^-7)s
|
|
*((signed __int64 *)pvInfo)=g_state.toRootDelay.getValue();
|
|
break;
|
|
|
|
case TSI_RootDispersion: // (unsigned __int64 *)pvInfo, NtTimePeriod, in (10^-7)s
|
|
*((unsigned __int64 *)pvInfo)=g_state.tpRootDispersion.getValue();
|
|
break;
|
|
|
|
case TSI_TSFlags: // ( DWORD *)pvInfo, Time source flags
|
|
*((DWORD *)pvInfo)=g_state.dwTSFlags;
|
|
break;
|
|
|
|
default:
|
|
return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT __stdcall MyAlertSamplesAvail(void) {
|
|
HRESULT hr;
|
|
|
|
if (!SetEvent(g_state.hSamplesAvailEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT __stdcall MySetProviderStatus(SetProviderStatusInfo *pspsi) {
|
|
HRESULT hr;
|
|
|
|
if (!QueueUserWorkItem(HandleSetProviderStatus, (LPVOID)pspsi, 0)) {
|
|
_JumpLastError(hr, error, "QueueUserWorkItem");
|
|
}
|
|
|
|
pspsi = NULL;
|
|
hr = S_OK;
|
|
error:
|
|
if (NULL != pspsi) {
|
|
pspsi->pfnFree(pspsi);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//====================================================================
|
|
// Provider control routines
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT StartProvider(TimeProvider * ptp) {
|
|
HRESULT hr;
|
|
TimeProvOpenFunc * pfnTimeProvOpen;
|
|
TimeProvSysCallbacks tpsc={
|
|
sizeof(TimeProvSysCallbacks),
|
|
MyGetTimeSysInfo,
|
|
MyLogTimeProvEvent,
|
|
MyAlertSamplesAvail,
|
|
MySetProviderStatus
|
|
};
|
|
|
|
FileLog2(FL_ControlProvAnnounce, L"Starting '%s', dll:'%s'\n", ptp->wszProvName, ptp->wszDllName);
|
|
ptp->hDllInst=LoadLibrary(ptp->wszDllName);
|
|
if (NULL==ptp->hDllInst) {
|
|
_JumpLastErrorStr(hr, error, "LoadLibrary", ptp->wszDllName);
|
|
}
|
|
|
|
ptp->pfnTimeProvClose=(TimeProvCloseFunc *)GetProcAddress(ptp->hDllInst, "TimeProvClose");
|
|
if (NULL==ptp->pfnTimeProvClose) {
|
|
_JumpLastError(hr, error, "GetProcAddress(TimeProvClose)");
|
|
}
|
|
|
|
ptp->pfnTimeProvCommand=(TimeProvCommandFunc *)GetProcAddress(ptp->hDllInst, "TimeProvCommand");
|
|
if (NULL==ptp->pfnTimeProvCommand) {
|
|
_JumpLastError(hr, error, "GetProcAddress(TimeProvCommand)");
|
|
}
|
|
|
|
pfnTimeProvOpen=(TimeProvOpenFunc *)GetProcAddress(ptp->hDllInst, "TimeProvOpen");
|
|
if (NULL==pfnTimeProvOpen) {
|
|
_JumpLastError(hr, error, "GetProcAddress(TimeProvOpen)");
|
|
}
|
|
|
|
// NOTE: this does not need to be synchronized because
|
|
// no other threads should be able to call into the time
|
|
// providers at the time of this call.
|
|
hr=pfnTimeProvOpen(ptp->wszProvName, &tpsc, &ptp->hTimeProv);
|
|
_JumpIfError(hr, error, "pfnTimeProvOpen");
|
|
|
|
ptp->bStarted = true;
|
|
hr=S_OK;
|
|
error:
|
|
if (FAILED(hr)) {
|
|
HRESULT hr2;
|
|
WCHAR * wszError;
|
|
const WCHAR * rgwszStrings[2]={
|
|
ptp->wszProvName,
|
|
NULL
|
|
};
|
|
|
|
hr2=GetSystemErrorString(hr, &wszError);
|
|
_JumpIfError(hr2, suberror, "GetSystemErrorString");
|
|
|
|
rgwszStrings[1]=wszError;
|
|
FileLog2(FL_ControlProvWarn, L"Logging error: Time provider '%s' failed to start due to the following error: %s\n", ptp->wszProvName, wszError);
|
|
hr2=MyLogEvent(EVENTLOG_ERROR_TYPE, MSG_TIMEPROV_FAILED_START, 2, rgwszStrings);
|
|
_JumpIfError(hr2, suberror, "MyLogEvent");
|
|
|
|
hr2=S_OK;
|
|
suberror:
|
|
if (NULL!=wszError) {
|
|
LocalFree(wszError);
|
|
}
|
|
if (NULL!=ptp->hDllInst) {
|
|
FreeLibrary(ptp->hDllInst);
|
|
}
|
|
_IgnoreIfError(hr2, "StartProvider");
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT StopProvider(TimeProvider * ptp) {
|
|
bool bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
HRESULT hr2;
|
|
|
|
// must be cleaned up
|
|
WCHAR * wszError=NULL;
|
|
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
_BeginTryWith(hr2) {
|
|
if (NULL != ptp->pfnTimeProvClose) {
|
|
// If we've initialized the provider callbacks, stop the provider
|
|
FileLog2(FL_ControlProvAnnounce, L"Stopping '%s', dll:'%s'\n", ptp->wszProvName, ptp->wszDllName);
|
|
hr2=ptp->pfnTimeProvClose(ptp->hTimeProv);
|
|
} else {
|
|
// We haven't initialized the callbacks: the provider is already stopped.
|
|
hr2 = S_OK;
|
|
}
|
|
} _TrapException(hr2);
|
|
|
|
// We've got to assume that the provider is stopped at this point
|
|
ptp->bStarted = false;
|
|
|
|
_BeginTryWith(hr) {
|
|
if (FAILED(hr2)) {
|
|
// log an event on failure, but otherwise ignore it.
|
|
const WCHAR * rgwszStrings[2]={
|
|
ptp->wszProvName,
|
|
NULL
|
|
};
|
|
|
|
// get the friendly error message
|
|
hr=GetSystemErrorString(hr2, &wszError);
|
|
_JumpIfError(hr, error, "GetSystemErrorString");
|
|
|
|
// log the event
|
|
rgwszStrings[1]=wszError;
|
|
FileLog2(FL_ControlProvWarn, L"Logging error: The time provider '%s' returned the following error during shutdown: %s\n", ptp->wszProvName, wszError);
|
|
hr=MyLogEvent(EVENTLOG_ERROR_TYPE, MSG_TIMEPROV_FAILED_STOP, 2, rgwszStrings);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
}
|
|
|
|
// release the dll
|
|
if (!FreeLibrary(ptp->hDllInst)) {
|
|
_JumpLastErrorStr(hr, error, "FreeLibrary", ptp->wszDllName);
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "StopProvider: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (NULL!=wszError) {
|
|
LocalFree(wszError);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT SendNotificationToProvider(TimeProvider * ptp, TimeProvCmd tpc, LPVOID pvArgs) {
|
|
bool bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
HRESULT hr2;
|
|
|
|
// must be cleaned up
|
|
WCHAR * wszError=NULL;
|
|
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
// send a "Param changed" message
|
|
_BeginTryWith(hr2) {
|
|
if (!ptp->bStarted) {
|
|
hr2=HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_ACTIVE);
|
|
} else {
|
|
hr2=ptp->pfnTimeProvCommand(ptp->hTimeProv, tpc, pvArgs);
|
|
}
|
|
} _TrapException(hr2);
|
|
|
|
_BeginTryWith(hr) {
|
|
if (FAILED(hr2)) {
|
|
// log an event on failure, but otherwise ignore it.
|
|
const WCHAR * rgwszStrings[2]={
|
|
ptp->wszProvName,
|
|
NULL
|
|
};
|
|
|
|
// get the friendly error message
|
|
hr=GetSystemErrorString(hr2, &wszError);
|
|
_JumpIfError(hr, error, "GetSystemErrorString");
|
|
|
|
// log the event
|
|
rgwszStrings[1]=wszError;
|
|
if (TPC_UpdateConfig==tpc) {
|
|
FileLog2(FL_ControlProvWarn, L"Logging warning: The time provider '%s' returned an error while updating its configuration. The error will be ignored. The error was: %s\n", ptp->wszProvName, wszError);
|
|
hr=MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_TIMEPROV_FAILED_UPDATE, 2, rgwszStrings);
|
|
} else if (TPC_PollIntervalChanged==tpc) {
|
|
FileLog2(FL_ControlProvWarn, L"Logging warning: The time provider '%s' returned an error when notified of a polling interval change. The error will be ignored. The error was: %s\n", ptp->wszProvName, wszError);
|
|
hr=MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_TIMEPROV_FAILED_POLLUPDATE, 2, rgwszStrings);
|
|
} else if (TPC_TimeJumped==tpc) {
|
|
FileLog2(FL_ControlProvWarn, L"Logging warning: The time provider '%s' returned an error when notified of a time jump. The error will be ignored. The error was: %s\n", ptp->wszProvName, wszError);
|
|
hr=MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_TIMEPROV_FAILED_TIMEJUMP, 2, rgwszStrings);
|
|
} else if (TPC_NetTopoChange==tpc) {
|
|
FileLog2(FL_ControlProvWarn, L"Logging warning: The time provider '%s' returned an error when notified of a net topography change. The error will be ignored. The error was: %s\n", ptp->wszProvName, wszError);
|
|
hr=MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_TIMEPROV_FAILED_NETTOPOCHANGE, 2, rgwszStrings);
|
|
}
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "SendNotificationToProvider: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (NULL!=wszError) {
|
|
LocalFree(wszError);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void StartAllProviders(void) {
|
|
HRESULT hr;
|
|
TimeProvider ** pptpPrev=&(g_state.pciConfig->ptpProviderList);
|
|
TimeProvider * ptpTravel=*pptpPrev;
|
|
unsigned int nStarted=0;
|
|
unsigned int nRequestedInputProviders=CountInputProvidersInList(g_state.pciConfig->ptpProviderList);
|
|
|
|
FileLog0(FL_ControlProvAnnounce, L"Starting Providers.\n");
|
|
while (NULL!=ptpTravel) {
|
|
hr=StartProvider(ptpTravel);
|
|
if (FAILED(hr)) {
|
|
FileLog1(FL_ControlProvWarn, L"Discarding provider '%s'.\n", ptpTravel->wszProvName);
|
|
*pptpPrev=ptpTravel->ptpNext;
|
|
ptpTravel->ptpNext=NULL;
|
|
FreeTimeProviderList(ptpTravel);
|
|
ptpTravel=*pptpPrev;
|
|
} else {
|
|
nStarted++;
|
|
pptpPrev=&ptpTravel->ptpNext;
|
|
ptpTravel=ptpTravel->ptpNext;
|
|
}
|
|
}
|
|
FileLog1(FL_ControlProvAnnounce, L"Successfully started %u providers.\n", nStarted);
|
|
|
|
// if we were supposed to have time providers, but NONE started, log a big warning
|
|
if (0==CountInputProvidersInList(g_state.pciConfig->ptpProviderList) && 0!=nRequestedInputProviders) {
|
|
FileLog0(FL_ParamChangeWarn, L"Logging error: The time service has been configured to use one or more input providers, however, none of the input providers could be started. THE TIME SERVICE HAS NO SOURCE OF ACCURATE TIME.\n");
|
|
hr=MyLogEvent(EVENTLOG_ERROR_TYPE, MSG_NO_INPUT_PROVIDERS_STARTED, 0, NULL);
|
|
_IgnoreIfError(hr, "MyLogEvent");
|
|
}
|
|
|
|
}
|
|
|
|
//====================================================================
|
|
// Local Clock
|
|
|
|
//--------------------------------------------------------------------
|
|
// Make sure that the system has a valid time zone. If not, log an
|
|
// error and set the time zone to a nice defualt (GMT)
|
|
MODULEPRIVATE HRESULT VerifyAndFixTimeZone(void) {
|
|
HRESULT hr;
|
|
TIME_ZONE_INFORMATION tzi;
|
|
DWORD dwRetval;
|
|
|
|
dwRetval=GetTimeZoneInformation(&tzi);
|
|
if (TIME_ZONE_ID_STANDARD==dwRetval || TIME_ZONE_ID_DAYLIGHT==dwRetval || TIME_ZONE_ID_UNKNOWN==dwRetval) {
|
|
// the system believes the time zone is valid
|
|
// do one more sanity check - I saw a computer with a time zone bias of +2 years.
|
|
// UTC = local time + bias
|
|
if (tzi.Bias<=TIMEZONEMAXBIAS && tzi.Bias>=-TIMEZONEMAXBIAS
|
|
&& tzi.DaylightBias<=TIMEZONEMAXBIAS && tzi.DaylightBias>=-TIMEZONEMAXBIAS
|
|
&& tzi.StandardBias<=TIMEZONEMAXBIAS && tzi.StandardBias>=-TIMEZONEMAXBIAS) {
|
|
// looks OK
|
|
FileLog0(FL_TimeZoneAnnounce, L"Time zone OK.\n");
|
|
goto done;
|
|
} else {
|
|
// fall through and fix
|
|
}
|
|
}
|
|
|
|
// set the time zone to GMT
|
|
ZeroMemory(&tzi, sizeof(tzi));
|
|
tzi.DaylightBias=-60;
|
|
tzi.StandardDate.wMonth=10;
|
|
tzi.StandardDate.wDay=5;
|
|
tzi.StandardDate.wHour=2;
|
|
tzi.DaylightDate.wMonth=3;
|
|
tzi.DaylightDate.wDay=5;
|
|
tzi.DaylightDate.wHour=1;
|
|
wcscpy(tzi.StandardName, L"GMT Standard Time (Recovered)");
|
|
wcscpy(tzi.DaylightName, L"GMT Daylight Time (Recovered)");
|
|
|
|
if (!SetTimeZoneInformation(&tzi)) {
|
|
hr=HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
// log an event on failure
|
|
WCHAR * rgwszStrings[1]={NULL};
|
|
HRESULT hr2=hr;
|
|
hr=GetSystemErrorString(hr2, &(rgwszStrings[0]));
|
|
_JumpIfError(hr, error, "GetSystemErrorString");
|
|
FileLog1(FL_TimeZoneWarn, L"Logging error: The time service discovered that the system time zone information was corrupted. The time service tried to reset the system time zone to GMT, but failed. The time service cannot start. The error was: %s\n", rgwszStrings[0]);
|
|
hr=MyLogEvent(EVENTLOG_ERROR_TYPE, MSG_TIME_ZONE_FIX_FAILED, 1, (const WCHAR **)rgwszStrings);
|
|
LocalFree(rgwszStrings[0]);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
hr=hr2;
|
|
|
|
_JumpError(hr, error, "SetTimeZoneInformation");
|
|
}
|
|
|
|
// Log the change
|
|
FileLog0(FL_TimeZoneWarn, L"Logging warning: The time service discovered that the system time zone information was corrupted. Because many system components require valid time zone information, the time service has reset the system time zone to GMT. Use the Date/Time control panel if you wish to change the system time zone.\n");
|
|
hr=MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_TIME_ZONE_FIXED, 0, NULL);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
|
|
done:
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// assert the time setting privilege
|
|
MODULEPRIVATE HRESULT GetPriveleges(void) {
|
|
HRESULT hr;
|
|
const unsigned int nPrivileges=2;
|
|
|
|
// must be cleaned up
|
|
HANDLE hProcToken=NULL;
|
|
TOKEN_PRIVILEGES * ptp=NULL;
|
|
|
|
// get the token for our process
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcToken)) {
|
|
_JumpLastError(hr, error, "OpenProcessToken");
|
|
}
|
|
|
|
// allocate the list of privileges
|
|
ptp=(TOKEN_PRIVILEGES *)LocalAlloc(LPTR, sizeof(TOKEN_PRIVILEGES)+(nPrivileges-ANYSIZE_ARRAY)*sizeof(LUID_AND_ATTRIBUTES));
|
|
_JumpIfOutOfMemory(hr, error, ptp);
|
|
|
|
// fill in the list of privileges
|
|
ptp->PrivilegeCount=nPrivileges;
|
|
|
|
// we want to change the system clock
|
|
if (!LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &(ptp->Privileges[0].Luid))) {
|
|
_JumpLastError(hr, error, "LookupPrivilegeValue");
|
|
}
|
|
ptp->Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
|
|
|
|
// we want increased priority
|
|
if (!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &(ptp->Privileges[1].Luid))) {
|
|
_JumpLastError(hr, error, "LookupPrivilegeValue");
|
|
}
|
|
ptp->Privileges[1].Attributes=SE_PRIVILEGE_ENABLED;
|
|
|
|
|
|
// make the requested privilege change
|
|
if (!AdjustTokenPrivileges(hProcToken, FALSE, ptp, 0, NULL, 0)) {
|
|
_JumpLastError(hr, error, "AdjustTokenPrivileges");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (NULL!=hProcToken) {
|
|
CloseHandle(hProcToken);
|
|
}
|
|
if (NULL!=ptp) {
|
|
LocalFree(ptp);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
//
|
|
// NOTE: This must be called in the clock discipline thread.
|
|
//
|
|
MODULEPRIVATE BOOL IsTimeServiceReliable() {
|
|
BOOL bIsReliable;
|
|
|
|
// If we've been manually configured to be a reliable timeserv,
|
|
// we want to provide time even when unsync'd.
|
|
// We are reliable if:
|
|
// a) We've been configured to always be reliable
|
|
// OR b) We've been configured to automatically decide if we're reliable, and
|
|
// we are the root DC in a domain tree.
|
|
//
|
|
switch (Reliable_Timeserv_Announce_Mask & g_state.pciConfig->dwAnnounceFlags)
|
|
{
|
|
case Reliable_Timeserv_Announce_Yes:
|
|
bIsReliable = TRUE;
|
|
break;
|
|
|
|
case Reliable_Timeserv_Announce_Auto:
|
|
bIsReliable = g_state.bIsDomainRoot;
|
|
break;
|
|
|
|
case Reliable_Timeserv_Announce_No:
|
|
bIsReliable = FALSE;
|
|
break;
|
|
|
|
default:
|
|
bIsReliable = TRUE;
|
|
}
|
|
|
|
return bIsReliable;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
//
|
|
// NOTE: This must be called in the clock discipline thread.
|
|
//
|
|
MODULEPRIVATE void SetClockUnsynchronized(LocalClockConfigInfo * plcci) {
|
|
if (IsTimeServiceReliable()) {
|
|
g_state.eLeapIndicator=(NtpLeapIndicator)g_state.tsNextClockUpdate.nLeapFlags;
|
|
|
|
// If our stratum is 0, set it to 1 so others can sync from us.
|
|
// NOTE: Do not reset our stratum if it is non-zero, as this will disrupt
|
|
// clients that are syncing from us:
|
|
//
|
|
// server (stratum n) <-- client (stratum n+1)
|
|
// Server goes unsync'd: server (stratum 1) <-- client (stratum 2)
|
|
// Server acquires source: server (stratum n) <-- client won't sync unless n < 2!!
|
|
//
|
|
// We also don't want to set our REFID unless we're stratum 0. Unless our stratum
|
|
// is set to 1, then using a LOCL refid indicates that we're syncing from a network
|
|
// peer with IP 76.79.67.76, which is much more wrong than maintaining our original
|
|
// refid.
|
|
|
|
if (0 == g_state.nStratum) {
|
|
g_state.nStratum=1;
|
|
g_state.refidSource.value=NtpConst::dwLocalRefId;
|
|
}
|
|
|
|
g_state.toRootDelay.setValue(0);
|
|
g_state.tpRootDispersion.setValue(((unsigned __int64)plcci->dwLocalClockDispersion)*10000000);
|
|
g_state.dwTSFlags=0;
|
|
|
|
// Remember when we last processed a sample
|
|
unsigned __int64 qwTicksNow;
|
|
AccurateGetTickCount(&qwTicksNow);
|
|
g_state.qwLastSyncTicks.setValue(qwTicksNow);
|
|
}
|
|
else {
|
|
// All other servers don't need this unconventional behavior,
|
|
// indicate that we are unsynchronized.
|
|
g_state.eLeapIndicator=e_ClockNotSynchronized;
|
|
g_state.nStratum=0;
|
|
g_state.refidSource.value=0; // Unsynchronized
|
|
}
|
|
|
|
FileLog5(FL_ClockDisThrdAnnounce, L" LI:%u S:%u RDl:%I64d RDs:%I64u TSF:0x%X",
|
|
g_state.eLeapIndicator,
|
|
g_state.nStratum,
|
|
g_state.toRootDelay.getValue(),
|
|
g_state.tpRootDispersion.getValue(),
|
|
g_state.dwTSFlags);
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
//
|
|
// N.B. the following code is required to keep the software clock in sync
|
|
// with the cmos clock. W32time controls the software clock using the
|
|
// system time adjustment. This never gets propagated to the software
|
|
// clock. GetSystemTime will read the software clock -- and SetSystemTime
|
|
// will actually write through to the CMOS clock.
|
|
//
|
|
//--------------------------------------------------------------------------------
|
|
|
|
// Only push the value from the software to the CMOS clock if we need
|
|
// to, otherwise we degrade the accuracy of the CMOS clock.
|
|
if (g_state.bControlClockFromSoftware) {
|
|
bool bAcquired;
|
|
|
|
HRESULT hr = AcquireControlOfSystemClock(true /*acquire?*/, false /*block?*/, &bAcquired /*success?*/);
|
|
if (SUCCEEDED(hr) && bAcquired) {
|
|
SYSTEMTIME stTime;
|
|
|
|
GetSystemTime(&stTime);
|
|
if (!SetSystemTime(&stTime)) {
|
|
_IgnoreLastError("SetSystemTime");
|
|
}
|
|
|
|
// Allow the interal CMOS clock to adjust time of day using its own internal mechanisms
|
|
if (!SetSystemTimeAdjustment(0 /*ignored*/, TRUE /*cmos*/)) {
|
|
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
|
|
_IgnoreError(hr, "SetSystemTimeAdjustment");
|
|
}
|
|
|
|
g_state.bControlClockFromSoftware = false;
|
|
// Release control of the system clock
|
|
hr = AcquireControlOfSystemClock(false /*acquire*/, false /*ignored*/, NULL /*ignored*/);
|
|
_IgnoreIfError(hr, "AcquireControlOfSystemClock");
|
|
}
|
|
}
|
|
|
|
{
|
|
// Don't want to advertise as a time service when we're unsynchronized
|
|
HRESULT hr = UpdateNetlogonServiceBits(true /*full update*/);
|
|
_IgnoreIfError(hr, "UpdateNetlogonServiceBits");
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// Issues remaining:
|
|
// * poll update - is current alg sufficient?
|
|
// * What are proper values for dwPllLoopGain and dwPhaseCorrectRate?
|
|
MODULEPRIVATE DWORD WINAPI ClockDisciplineThread(void * pvIgnored) {
|
|
HRESULT hr;
|
|
DWORD dwWaitResult;
|
|
DWORD dwError;
|
|
unsigned int nIndex;
|
|
LocalClockConfigInfo lcci;
|
|
|
|
HANDLE rghWait[2]={
|
|
g_state.hShutDownEvent,
|
|
g_state.hClockCommandAvailEvent,
|
|
};
|
|
|
|
_BeginTryWith(hr) {
|
|
|
|
// initialize time variables
|
|
g_state.toKnownPhaseOffset=0;
|
|
AccurateGetInterruptCount(&g_state.qwPhaseCorrectStartTickCount);
|
|
AccurateGetInterruptCount(&g_state.qwLastUpdateTickCount);
|
|
g_state.nPhaseCorrectRateAdj=0;
|
|
g_state.dwClockRate=g_state.pciConfig->lcci.dwLastClockRate; // special 'constant'
|
|
g_state.nRateAdj=0;
|
|
g_state.nFllRateAdj=0;
|
|
g_state.nPllRateAdj=0;
|
|
g_state.nErrorIndex=0;
|
|
for (nIndex=0; nIndex<ClockFreqPredictErrBufSize; nIndex++) {
|
|
g_state.rgdFllError[nIndex]=0;
|
|
g_state.rgdPllError[nIndex]=0;
|
|
};
|
|
g_state.nSysDispersionIndex=0;
|
|
for (nIndex=0; nIndex<SysDispersionBufSize; nIndex++) {
|
|
g_state.rgtpSysDispersion[nIndex]=0;
|
|
};
|
|
g_state.nPollUpdateCounter=0;
|
|
g_state.lcState=e_Unset;
|
|
|
|
// the current source
|
|
wcscpy(g_state.wszSourceName, wszW32TimeUNLocalCmosClock);
|
|
// use this to see if the source changed. Time slip can cause the source to change,
|
|
// but we don't want to log events if we go back to the same source after a time slip.
|
|
wcscpy(g_state.wszPreTimeSlipSourceName, wszW32TimeUNLocalCmosClock);
|
|
// just FYI, not used in a calculation
|
|
wcscpy(g_state.wszPreUnsyncSourceName, wszW32TimeUNLocalCmosClock);
|
|
|
|
// initialize 'constants'
|
|
memcpy(&lcci, &g_state.pciConfig->lcci, sizeof(LocalClockConfigInfo));
|
|
|
|
g_state.dwPllLoopGain=lcci.dwFrequencyCorrectRate*PLLLOOPGAINBASE; // number of ticks in 64s
|
|
|
|
// assert the time setting privilege
|
|
hr=GetPriveleges();
|
|
_JumpIfError(hr, error, "GetPriveleges");
|
|
|
|
// we need to be called at the right time.
|
|
// (highest of any non-realtime. should be in realtime class?)
|
|
if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) {
|
|
_JumpLastError(hr, error, "SetThreadPriority");
|
|
}
|
|
|
|
// initialize the state to unsynchronized
|
|
FileLog0(FL_ClockDisThrdAnnounce, L"ClockDisciplineThread: Starting:");
|
|
SetClockUnsynchronized(&lcci);
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L"\n");
|
|
|
|
// tell the manager we're initialized
|
|
if (!SetEvent(g_state.hClockCommandCompleteEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
|
|
// begin main event loop
|
|
while (true) {
|
|
// BUG #374491:
|
|
//
|
|
// If we're controlling the computer clock ourselves, we need to wake up to discipline it.
|
|
// If the computer clock is being controlled from CMOS, wake up only often enough to update
|
|
// our qwLastSyncTicks field. This is necessary for reliable time servers that trust
|
|
// their local clock.
|
|
|
|
DWORD dwWaitTime;
|
|
unsigned __int64 qwTicksNow;
|
|
AccurateGetTickCount(&qwTicksNow);
|
|
|
|
if (g_state.bControlClockFromSoftware) {
|
|
// We're disciplining the local clock -- use the update interval to determine when to wake up.
|
|
dwWaitTime = lcci.dwUpdateInterval*10;
|
|
} else {
|
|
if (IsTimeServiceReliable()) {
|
|
// We're not disciplining the local clock, but we're a reliable time service. We need to wake
|
|
// up when it is time to fudge our last sync time.
|
|
unsigned __int64 qwTimeSinceLastSync = qwTicksNow - g_state.qwLastSyncTicks.getValue();
|
|
|
|
if (qwTimeSinceLastSync > (NtpConst::tpMaxClockAge.qw/10000)) {
|
|
// Fudge our timestamp "immediately" (wait 5 seconds to be safe -- wouldn't want to fall into a
|
|
// CPU-hogging loop if there's a bug in our logic).
|
|
dwWaitTime = 5000;
|
|
} else {
|
|
dwWaitTime = static_cast<DWORD>(((NtpConst::tpMaxClockAge.qw/10000) - qwTimeSinceLastSync) & 0xFFFFFFFF);
|
|
}
|
|
|
|
FileLog3(FL_ClockDisThrdAnnounce, L"ClockDispln: we're a reliable time service with no time source: LS: %I64d, %TN: %I64d, WAIT: %d\n", qwTimeSinceLastSync, NtpConst::tpMaxClockAge.qw, dwWaitTime);
|
|
|
|
} else {
|
|
dwWaitTime = INFINITE;
|
|
}
|
|
}
|
|
|
|
dwWaitResult=WaitForMultipleObjects(ARRAYSIZE(rghWait), rghWait, false/*any*/, dwWaitTime);
|
|
if (WAIT_FAILED==dwWaitResult) {
|
|
_JumpLastError(hr, error, "WaitForMultipleObjects");
|
|
} else if (WAIT_OBJECT_0==dwWaitResult) {
|
|
// received stop request
|
|
FileLog0(FL_ClockDisThrdAnnounce, L"ClockDisciplineThread: hShutDownEvent signaled. Exiting.\n");
|
|
break;
|
|
} else if (WAIT_OBJECT_0+1==dwWaitResult && e_ParamChange==g_state.eLocalClockCommand) {
|
|
// Param change message
|
|
FileLog0(FL_ClockDisThrdAnnounce, L"ClockDisciplineThread: ParamChange. Reloading constants.\n");
|
|
|
|
// reinitialize 'constants'
|
|
memcpy(&lcci, &g_state.pciConfig->lcci, sizeof(LocalClockConfigInfo));
|
|
g_state.dwPllLoopGain=lcci.dwFrequencyCorrectRate*PLLLOOPGAINBASE; // number of ticks in 64s
|
|
// note, dwLastClockRate is ignored.
|
|
if (g_state.dwClockRate<lcci.dwMinClockRate) {
|
|
FileLog0(FL_ClockDisThrdAnnounce, L"ClockDispln: ClockRate adjusted to keep in bounds\n");
|
|
g_state.dwClockRate=lcci.dwMinClockRate;
|
|
} else if (g_state.dwClockRate>lcci.dwMaxClockRate) {
|
|
FileLog0(FL_ClockDisThrdAnnounce, L"ClockDispln: ClockRate adjusted to keep in bounds\n");
|
|
g_state.dwClockRate=lcci.dwMaxClockRate;
|
|
}
|
|
if (NtpConst::dwLocalRefId==g_state.refidSource.value
|
|
&& ((unsigned __int64)lcci.dwLocalClockDispersion)*10000000!=g_state.tpRootDispersion.getValue()) {
|
|
FileLog0(FL_ClockDisThrdAnnounce, L"ClockDispln: LocalClockDispersion adjusted.\n");
|
|
g_state.tpRootDispersion.setValue(((unsigned __int64)lcci.dwLocalClockDispersion)*10000000);
|
|
}
|
|
// toKnownPhaseOffset could be outside of dwMax(Neg/Pos)PhaseCorrection but it will be
|
|
// decreasing to 0 eventually. We will not worry about it.
|
|
} else if (WAIT_TIMEOUT==dwWaitResult) {
|
|
// See whether we're actually controlling the software clock ourselves:
|
|
if (!g_state.bControlClockFromSoftware) {
|
|
if (IsTimeServiceReliable()) {
|
|
// We're a reliable time service syncing from our local clock. Fake clock
|
|
// updates so our clients don't filter us out for having an old update time.
|
|
AccurateGetTickCount(&qwTicksNow);
|
|
g_state.qwLastSyncTicks.setValue(qwTicksNow);
|
|
}
|
|
// Nothing more to do, we're not disciplining the computer clock
|
|
continue;
|
|
} else {
|
|
// We're disciplining the computer clock. Continue on...
|
|
}
|
|
}
|
|
|
|
// finish the outstanding phase correction
|
|
unsigned __int64 qwPhaseCorrectionTicks;
|
|
AccurateGetInterruptCount(&qwPhaseCorrectionTicks);
|
|
qwPhaseCorrectionTicks-=g_state.qwPhaseCorrectStartTickCount;
|
|
signed __int64 toPhaseCorrection=g_state.nPhaseCorrectRateAdj*((signed __int64)qwPhaseCorrectionTicks);
|
|
g_state.toKnownPhaseOffset-=toPhaseCorrection;
|
|
g_state.qwPhaseCorrectStartTickCount+=qwPhaseCorrectionTicks;
|
|
g_state.toSysPhaseOffset.setValue(toPhaseCorrection+g_state.toSysPhaseOffset.getValue());
|
|
|
|
FileLog0(FL_ClockDisThrdAnnounceLow, L"ClockDispln:");
|
|
|
|
if (WAIT_OBJECT_0+1==dwWaitResult && (e_RegularUpdate==g_state.eLocalClockCommand || e_IrregularUpdate==g_state.eLocalClockCommand)) {
|
|
// process new update
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L"ClockDispln Update:");
|
|
|
|
// make sure return values are initialized
|
|
g_state.bPollIntervalChanged=false;
|
|
g_state.bClockJumped=false;
|
|
g_state.bStaleData=false;
|
|
g_state.bClockChangeTooBig=false;
|
|
g_state.bSourceChanged=false;
|
|
g_state.bPhaseSpike=false;
|
|
g_state.bFrequencySpike=false;
|
|
|
|
// only process this update if the sample is not older than the last processed sample
|
|
if (g_state.tsNextClockUpdate.nSysTickCount<=g_state.qwLastUpdateTickCount) {
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" *STALE*");
|
|
g_state.bStaleData=true;
|
|
} else {
|
|
|
|
// calculate the time between updates
|
|
unsigned __int64 qwUpdateTicks=g_state.tsNextClockUpdate.nSysTickCount-g_state.qwLastUpdateTickCount;
|
|
g_state.qwLastUpdateTickCount+=qwUpdateTicks;
|
|
|
|
// get the measured phase offset, accounting for known offset, and update known offset
|
|
signed __int64 toSampleOffset=g_state.tsNextClockUpdate.toOffset+g_state.tsNextClockUpdate.nSysPhaseOffset-g_state.toSysPhaseOffset.getValue();
|
|
signed __int64 toPhaseOffset=toSampleOffset-g_state.toKnownPhaseOffset;
|
|
|
|
bool bPhaseSpike = (toPhaseOffset<-((signed __int64)lcci.dwLargePhaseOffset) || toPhaseOffset>((signed __int64)lcci.dwLargePhaseOffset)); // default 128ms
|
|
bool bFrequencySpike = (toPhaseOffset<-((signed __int64)(qwUpdateTicks<<7)) || toPhaseOffset>((signed __int64)(qwUpdateTicks<<7))); // watch for frequency spikes as well.
|
|
bool bPossibleSpike = bPhaseSpike || bFrequencySpike;
|
|
|
|
FileLogA5(FL_ClockDisThrdAnnounce, L" SO:%I64d KPhO:%I64d %sPhO:%I64d uT:%I64u", toSampleOffset, g_state.toKnownPhaseOffset, (bPossibleSpike?L"*":L""), toPhaseOffset, qwUpdateTicks);
|
|
|
|
if (((lcci.dwMaxNegPhaseCorrection != PhaseCorrect_ANY) &&
|
|
(toSampleOffset<-(signed __int64)(((unsigned __int64)lcci.dwMaxNegPhaseCorrection)*10000000)))
|
|
||
|
|
((lcci.dwMaxPosPhaseCorrection != PhaseCorrect_ANY) &&
|
|
(toSampleOffset>(signed __int64)(((unsigned __int64)lcci.dwMaxPosPhaseCorrection)*10000000)))) {
|
|
g_state.bClockChangeTooBig=true;
|
|
g_state.toIgnoredChange.qw=toSampleOffset;
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" *TOO BIG*");
|
|
} else {
|
|
|
|
if (e_Unset==g_state.lcState) {
|
|
// we can't make frequency predictions until after the first update
|
|
// we believe this sample, so adjust the amount of phase offset we have left to correct.
|
|
g_state.toKnownPhaseOffset=toSampleOffset;
|
|
} else if (bPossibleSpike && (e_Spike==g_state.lcState || e_Sync==g_state.lcState)) {
|
|
// spike detector active - see if this large error is persistent
|
|
} else {
|
|
// we believe this sample, so adjust the amount of phase offset we have left to correct.
|
|
g_state.toKnownPhaseOffset=toSampleOffset;
|
|
|
|
// see how well FLL and PLL did at predicting this offset (zero==perfect frequency prediction)
|
|
double dFllPredictPhaseError=toPhaseOffset+(g_state.nRateAdj-g_state.nFllRateAdj)*((double)(signed __int64)qwUpdateTicks);
|
|
double dPllPredictPhaseError=toPhaseOffset+(g_state.nRateAdj-g_state.nPllRateAdj)*((double)(signed __int64)qwUpdateTicks);
|
|
FileLogA2(FL_ClockDisThrdAnnounce, L" FllPPE:%g PllPPE:%g", dFllPredictPhaseError, dPllPredictPhaseError);
|
|
|
|
// add these to our moving average buffer
|
|
g_state.rgdFllError[g_state.nErrorIndex]=dFllPredictPhaseError*dFllPredictPhaseError;
|
|
g_state.rgdPllError[g_state.nErrorIndex]=dPllPredictPhaseError*dPllPredictPhaseError;
|
|
g_state.nErrorIndex=(g_state.nErrorIndex+1)%ClockFreqPredictErrBufSize;
|
|
|
|
// calculate the root-means-squared error for the last few FLL & PLL predictions
|
|
dFllPredictPhaseError=0;
|
|
dPllPredictPhaseError=0;
|
|
for (nIndex=0; nIndex<ClockFreqPredictErrBufSize; nIndex++) {
|
|
dFllPredictPhaseError+=g_state.rgdFllError[nIndex];
|
|
dPllPredictPhaseError+=g_state.rgdPllError[nIndex];
|
|
};
|
|
dFllPredictPhaseError=sqrt(dFllPredictPhaseError/ClockFreqPredictErrBufSize);
|
|
dPllPredictPhaseError=sqrt(dPllPredictPhaseError/ClockFreqPredictErrBufSize);
|
|
FileLogA2(FL_ClockDisThrdAnnounce, L" FllPPrE:%g PllPPrE:%g", dFllPredictPhaseError, dPllPredictPhaseError);
|
|
|
|
// allow for perfection
|
|
if (0==dFllPredictPhaseError && 0==dPllPredictPhaseError) {
|
|
dFllPredictPhaseError=1;
|
|
}
|
|
|
|
// calculate the new frequency predictions
|
|
g_state.nFllRateAdj=(signed __int32)(toPhaseOffset/((signed __int64)qwUpdateTicks)/((signed __int32)lcci.dwFrequencyCorrectRate));
|
|
g_state.nPllRateAdj=(signed __int32)(toPhaseOffset*((signed __int64)qwUpdateTicks)/((signed __int32)g_state.dwPllLoopGain)/((signed __int32)g_state.dwPllLoopGain));
|
|
FileLogA2(FL_ClockDisThrdAnnounce, L" FllRA:%d PllRA:%d", g_state.nFllRateAdj, g_state.nPllRateAdj);
|
|
|
|
// calculate the combined frequency prediction
|
|
g_state.nRateAdj=(signed __int32)((g_state.nFllRateAdj*dPllPredictPhaseError+g_state.nPllRateAdj*dFllPredictPhaseError)
|
|
/(dPllPredictPhaseError+dFllPredictPhaseError));
|
|
|
|
// Keep the clock rate in bounds
|
|
if ((g_state.nRateAdj<0 && g_state.dwClockRate<(unsigned __int32)(-g_state.nRateAdj))
|
|
|| g_state.dwClockRate+g_state.nRateAdj<lcci.dwMinClockRate) {
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" [");
|
|
g_state.nRateAdj=lcci.dwMinClockRate-g_state.dwClockRate;
|
|
} else if ((g_state.nRateAdj>0 && g_state.dwClockRate<(unsigned __int32)(g_state.nRateAdj))
|
|
|| g_state.dwClockRate+g_state.nRateAdj>lcci.dwMaxClockRate) {
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" ]");
|
|
g_state.nRateAdj=lcci.dwMaxClockRate-g_state.dwClockRate;
|
|
}
|
|
|
|
// calculate the new frequency
|
|
g_state.dwClockRate+=g_state.nRateAdj;
|
|
FileLogA2(FL_ClockDisThrdAnnounce, L" RA:%d CR:%u", g_state.nRateAdj, g_state.dwClockRate);
|
|
|
|
// calculate phase error due to use of incorrect rate since sample was taken
|
|
unsigned __int64 qwNewTicks;
|
|
AccurateGetInterruptCount(&qwNewTicks);
|
|
qwNewTicks-=g_state.qwLastUpdateTickCount;
|
|
signed __int64 toRateAdjPhaseOffset=((signed __int64)qwNewTicks)*g_state.nRateAdj;
|
|
g_state.toKnownPhaseOffset+=toRateAdjPhaseOffset;
|
|
FileLogA2(FL_ClockDisThrdAnnounce,L" nT:%I64u RAPhO:%I64d", qwNewTicks, toRateAdjPhaseOffset);
|
|
|
|
} // <- end if not first update
|
|
|
|
|
|
// add these dispersions to our moving average buffer
|
|
g_state.rgtpSysDispersion[g_state.nSysDispersionIndex]=
|
|
g_state.tpSelectDispersion.qw*g_state.tpSelectDispersion.qw+
|
|
g_state.tsNextClockUpdate.tpDispersion*g_state.tsNextClockUpdate.tpDispersion;
|
|
g_state.nSysDispersionIndex=(g_state.nSysDispersionIndex+1)%SysDispersionBufSize;
|
|
|
|
// calculate the root-means-squared dispersion
|
|
unsigned __int64 tpSysDispersion=0;
|
|
for (nIndex=0; nIndex<SysDispersionBufSize; nIndex++) {
|
|
tpSysDispersion+=g_state.rgtpSysDispersion[nIndex];
|
|
};
|
|
tpSysDispersion=(unsigned __int64)sqrt(((double)(signed __int64)tpSysDispersion)/SysDispersionBufSize); //compiler error C2520: conversion from unsigned __int64 to double not implemented, use signed __int64
|
|
FileLogA1(FL_ClockDisThrdAnnounce, L" SD:%I64u", tpSysDispersion);
|
|
|
|
// see if we need to change the poll interval
|
|
unsigned __int64 tpAbsPhaseOffset;
|
|
if (toPhaseOffset<0) {
|
|
tpAbsPhaseOffset=(unsigned __int64)-toPhaseOffset;
|
|
} else {
|
|
tpAbsPhaseOffset=(unsigned __int64)toPhaseOffset;
|
|
}
|
|
if (e_IrregularUpdate==g_state.eLocalClockCommand) {
|
|
// do not adjust the poll because this update
|
|
// is irregular and not one of our scheduled updates. This
|
|
// prevents one hyperactive provider from driving
|
|
// the poll rate up for the other providers
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" (i)");
|
|
} else if (tpAbsPhaseOffset>lcci.dwPollAdjustFactor*tpSysDispersion) {
|
|
g_state.nPollUpdateCounter=0;
|
|
if (g_state.nPollInterval>((signed int)lcci.dwMinPollInterval)) {
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" Poll--");
|
|
g_state.nPollInterval--;
|
|
g_state.bPollIntervalChanged=true;
|
|
}
|
|
} else {
|
|
g_state.nPollUpdateCounter++;
|
|
if (SysDispersionBufSize==g_state.nPollUpdateCounter
|
|
&& g_state.nPollInterval<((signed int)lcci.dwMaxPollInterval)) {
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" Poll++");
|
|
g_state.nPollUpdateCounter=0;
|
|
g_state.nPollInterval++;
|
|
g_state.bPollIntervalChanged=true;
|
|
}
|
|
}
|
|
|
|
// update the other system parameters
|
|
g_state.eLeapIndicator=(NtpLeapIndicator)g_state.tsNextClockUpdate.nLeapFlags;
|
|
g_state.nStratum=g_state.tsNextClockUpdate.nStratum+1;
|
|
g_state.tsiNextClockUpdate.ptp->dwStratum=g_state.nStratum;
|
|
g_state.refidSource.value=g_state.tsNextClockUpdate.dwRefid;
|
|
g_state.toRootDelay.setValue(g_state.tsNextClockUpdate.toDelay);
|
|
tpSysDispersion=g_state.tpSelectDispersion.qw+tpAbsPhaseOffset;
|
|
if (tpSysDispersion<NtpConst::tpMinDispersion.qw) {
|
|
tpSysDispersion=NtpConst::tpMinDispersion.qw;
|
|
}
|
|
g_state.tpRootDispersion.setValue(g_state.tsNextClockUpdate.tpDispersion+tpSysDispersion);
|
|
g_state.dwTSFlags=g_state.tsNextClockUpdate.dwTSFlags;
|
|
FileLogA5(FL_ClockDisThrdAnnounce, L" LI:%u S:%u RDl:%I64d RDs:%I64u TSF:0x%X",
|
|
g_state.eLeapIndicator,
|
|
g_state.nStratum,
|
|
g_state.toRootDelay.getValue(),
|
|
g_state.tpRootDispersion.getValue(),
|
|
g_state.dwTSFlags);
|
|
|
|
// update our source
|
|
g_state.tsNextClockUpdate.wszUniqueName[255]=L'\0';
|
|
if (0!=wcscmp(g_state.tsNextClockUpdate.wszUniqueName, g_state.wszPreTimeSlipSourceName)) {
|
|
g_state.bSourceChanged=true;
|
|
wcscpy(g_state.wszPreTimeSlipSourceName, g_state.tsNextClockUpdate.wszUniqueName);
|
|
wcscpy(g_state.wszSourceName, g_state.tsNextClockUpdate.wszUniqueName);
|
|
}
|
|
// We've got time samples, so we're able to control the clock ourselves...
|
|
g_state.bControlClockFromSoftware = true;
|
|
|
|
// Remember when we last processed a sample
|
|
unsigned __int64 qwTicksNow;
|
|
AccurateGetTickCount(&qwTicksNow);
|
|
g_state.qwLastSyncTicks.setValue(qwTicksNow);
|
|
|
|
unsigned __int64 qwNow;
|
|
AccurateGetSystemTime(&qwNow);
|
|
|
|
// perform state transitions
|
|
if (e_Unset==g_state.lcState) {
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" Unset->Hold");
|
|
g_state.lcState=e_Hold;
|
|
g_state.nHoldCounter=0;
|
|
} else if (e_Hold==g_state.lcState) {
|
|
FileLogA1(FL_ClockDisThrdAnnounce, L" Hold(%u)", g_state.nHoldCounter);
|
|
g_state.nHoldCounter++;
|
|
if (g_state.nHoldCounter>=lcci.dwHoldPeriod && !bPossibleSpike) { // default HoldPeriod: 5 updates
|
|
g_state.lcState=e_Sync;
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L"->Sync");
|
|
}
|
|
} else if (e_Sync==g_state.lcState) {
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" Sync");
|
|
if (bPossibleSpike) {
|
|
g_state.lcState=e_Spike;
|
|
g_state.teSpikeStart=qwNow;
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L"->Spike");
|
|
}
|
|
} else if (e_Spike==g_state.lcState) {
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" Spike");
|
|
if (!bPossibleSpike) {
|
|
g_state.lcState=e_Sync;
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L"->Sync");
|
|
} else if (qwNow-g_state.teSpikeStart>(((unsigned __int64)lcci.dwSpikeWatchPeriod)*10000000)) { // default SpikeWatchPeriod: 900s
|
|
g_state.lcState=e_Unset;
|
|
g_state.eLeapIndicator=e_ClockNotSynchronized;
|
|
g_state.bPhaseSpike = bPhaseSpike;
|
|
g_state.bFrequencySpike = bFrequencySpike;
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L"->Unset");
|
|
}
|
|
}
|
|
|
|
}// <- end if not too big
|
|
} // <- end if not stale update
|
|
|
|
FileLogA0(FL_ClockDisThrdAnnounce,L"\n");
|
|
|
|
} // <- end if update available
|
|
if (WAIT_OBJECT_0+1==dwWaitResult && e_TimeSlip==g_state.eLocalClockCommand) {
|
|
|
|
FileLog0(FL_ClockDisThrdAnnounce, L"ClockDispln TimeSlip:");
|
|
g_state.bClockJumped=true;
|
|
|
|
// reinitialize pretty much everything
|
|
// internal state variables.
|
|
g_state.toKnownPhaseOffset=0;
|
|
AccurateGetInterruptCount(&g_state.qwPhaseCorrectStartTickCount);
|
|
AccurateGetInterruptCount(&g_state.qwLastUpdateTickCount);
|
|
g_state.nPhaseCorrectRateAdj=0;
|
|
g_state.nRateAdj=0;
|
|
g_state.nFllRateAdj=0;
|
|
g_state.nPllRateAdj=0;
|
|
g_state.nErrorIndex=0;
|
|
for (nIndex=0; nIndex<ClockFreqPredictErrBufSize; nIndex++) {
|
|
g_state.rgdFllError[nIndex]=0;
|
|
g_state.rgdPllError[nIndex]=0;
|
|
};
|
|
// keep the system error history
|
|
//g_state.nSysDispersionIndex=0;
|
|
//for (nIndex=0; nIndex<SysDispersionBufSize; nIndex++) {
|
|
// g_state.rgtpSysDispersion[nIndex]=0;
|
|
//};
|
|
g_state.nPollUpdateCounter=0;
|
|
g_state.lcState=e_Unset;
|
|
|
|
wcscpy(g_state.wszSourceName, wszW32TimeUNFreeSysClock);
|
|
|
|
// world visible state
|
|
if (g_state.nPollInterval>((signed int)lcci.dwMinPollInterval)) {
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L" [Poll");
|
|
g_state.nPollInterval=(signed int)lcci.dwMinPollInterval;
|
|
g_state.bPollIntervalChanged=true;
|
|
}
|
|
SetClockUnsynchronized(&lcci);
|
|
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L"\n");
|
|
} // <- end if time slip
|
|
|
|
|
|
if (WAIT_OBJECT_0+1==dwWaitResult && e_GoUnsyncd==g_state.eLocalClockCommand) {
|
|
// the manager says that it's been so long since the last sync that
|
|
// we should be telling the world that we're running of the local clock,
|
|
// not some other time source.
|
|
// this doesn't affect our calculations, just what we report to the outside world
|
|
FileLog0(FL_ClockDisThrdAnnounce, L"ClockDispln GoUnsyncd:");
|
|
g_state.bSourceChanged=false;
|
|
|
|
// set the source name, saving the old one
|
|
if (0!=wcscmp(wszW32TimeUNFreeSysClock, g_state.wszPreTimeSlipSourceName)) {
|
|
wcscpy(g_state.wszPreTimeSlipSourceName, wszW32TimeUNFreeSysClock);
|
|
wcscpy(g_state.wszSourceName, wszW32TimeUNFreeSysClock);
|
|
wcscpy(g_state.wszPreUnsyncSourceName, g_state.wszSourceName);
|
|
g_state.bSourceChanged=true;
|
|
}
|
|
|
|
SetClockUnsynchronized(&lcci);
|
|
FileLogA0(FL_ClockDisThrdAnnounce, L"\n");
|
|
}
|
|
|
|
// if we're controlling the clock ourselves, begin a new phase correction
|
|
// add a little to the rate
|
|
bool bAcquired = false;
|
|
hr = AcquireControlOfSystemClock(true /*acquire?*/, false /*block?*/, &bAcquired /*success?*/);
|
|
_IgnoreIfError(hr, "AcquireControlOfSystemClock");
|
|
bAcquired = SUCCEEDED(hr) && bAcquired;
|
|
if (g_state.bControlClockFromSoftware && bAcquired) {
|
|
toPhaseCorrection=g_state.toKnownPhaseOffset;
|
|
toPhaseCorrection/=(signed __int32)lcci.dwPhaseCorrectRate;
|
|
toPhaseCorrection/=(signed __int32)lcci.dwUpdateInterval; // won't correct really small phase errors
|
|
g_state.nPhaseCorrectRateAdj=(signed __int32)toPhaseCorrection;
|
|
if (toPhaseCorrection<0) {
|
|
toPhaseCorrection=-toPhaseCorrection;
|
|
}
|
|
|
|
// Used to compare against "dwMaxAllowedPhaseOffset"
|
|
signed __int64 toPhaseCorrectionInSeconds = g_state.toKnownPhaseOffset;
|
|
if (toPhaseCorrectionInSeconds < 0) {
|
|
toPhaseCorrectionInSeconds = -toPhaseCorrectionInSeconds;
|
|
}
|
|
toPhaseCorrectionInSeconds /= 10000000; // Convert from 100ns units, to 1s units
|
|
|
|
FileLog0(FL_ClockDisThrdAnnounceLow, L" ");
|
|
if ((((unsigned __int32)toPhaseCorrection)>g_state.dwClockRate/2) ||
|
|
((unsigned __int32)toPhaseCorrectionInSeconds > lcci.dwMaxAllowedPhaseOffset)) {
|
|
if (WAIT_OBJECT_0+1==dwWaitResult) {
|
|
// too far out of whack to slew - just jump to the correct time
|
|
unsigned __int64 teSysTime;
|
|
AccurateGetSystemTime(&teSysTime);
|
|
teSysTime+=g_state.toKnownPhaseOffset;
|
|
AccurateSetSystemTime(&teSysTime);
|
|
g_state.toClockJump.qw=g_state.toKnownPhaseOffset;
|
|
g_state.nPhaseCorrectRateAdj=0;
|
|
g_state.toSysPhaseOffset.setValue(g_state.toKnownPhaseOffset+g_state.toSysPhaseOffset.getValue());
|
|
g_state.toKnownPhaseOffset=0;
|
|
FileLogA1(FL_ClockDisThrdAnnounceLow, L" PhCRA:%I64d *SET*TIME*", toPhaseCorrection);
|
|
g_state.bClockJumped=true;
|
|
} else {
|
|
// This can only happen if we are making a large change and
|
|
// this thread is preempted for so long that we overshoot.
|
|
// This should be very rare.
|
|
if (g_state.toKnownPhaseOffset<0) {
|
|
toPhaseCorrection = -g_state.dwClockRate/2;
|
|
} else {
|
|
toPhaseCorrection = g_state.dwClockRate/2;
|
|
}
|
|
}
|
|
}
|
|
// slew to correct time.
|
|
SetSystemTimeAdjustment(g_state.nPhaseCorrectRateAdj+g_state.dwClockRate, false/*no cmos*/);
|
|
|
|
FileLogA3(FL_ClockDisThrdAnnounceLow, L" PhCRA:%d phcT:%I64u KPhO:%I64d\n", g_state.nPhaseCorrectRateAdj, qwPhaseCorrectionTicks, g_state.toKnownPhaseOffset);
|
|
}
|
|
|
|
if (bAcquired) {
|
|
// Release control of system clock:
|
|
hr = AcquireControlOfSystemClock(false /*acquire*/, false /*ignored*/, NULL /*ignored*/);
|
|
_IgnoreIfError(hr, "AcquireControlOfSystemClock");
|
|
}
|
|
|
|
if (WAIT_OBJECT_0+1==dwWaitResult) {
|
|
// ready for a new update
|
|
if (!SetEvent(g_state.hClockCommandCompleteEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
}
|
|
} // <- end main loop
|
|
|
|
// BUGBUG: should we put this in serviceshutdown code?
|
|
SetSystemTimeAdjustment(lcci.dwLastClockRate, true/*cmos*/);
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "ClockDisciplineThread: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT StartClockDiscipline(void) {
|
|
HRESULT hr;
|
|
DWORD dwThreadID;
|
|
|
|
g_state.hClockDisplnThread=CreateThread(NULL, NULL, ClockDisciplineThread, NULL, 0, &dwThreadID);
|
|
if (NULL==g_state.hClockDisplnThread) {
|
|
_JumpLastError(hr, error, "CreateThread");
|
|
}
|
|
|
|
{ // wait for clock discipline thread to read the initial config
|
|
HANDLE rghWait[2]={
|
|
g_state.hClockCommandCompleteEvent,
|
|
g_state.hClockDisplnThread
|
|
};
|
|
DWORD dwWaitResult;
|
|
|
|
dwWaitResult=WaitForMultipleObjects(ARRAYSIZE(rghWait), rghWait, false, INFINITE);
|
|
if (WAIT_FAILED==dwWaitResult) {
|
|
_JumpLastError(hr, error, "WaitForMultipleObjects");
|
|
} else if (WAIT_OBJECT_0==dwWaitResult) {
|
|
// Command acknowledged
|
|
} else {
|
|
// the ClockDiscipline thread shut down!
|
|
// fall outward to the manager thread main loop to analyze the issue.
|
|
}
|
|
}
|
|
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//====================================================================
|
|
// manager routines
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT ShutdownNetlogonServiceBits(void) {
|
|
HRESULT hr;
|
|
DWORD dwErr;
|
|
|
|
// stop announcing that we are a server
|
|
g_state.dwNetlogonServiceBits=0;
|
|
|
|
dwErr=NetLogonSetServiceBits(NULL, DS_TIMESERV_FLAG|DS_GOOD_TIMESERV_FLAG, 0);
|
|
if (0xC0020012==dwErr) { //RPC_NT_UNKNOWN_IF in ntstatus.h
|
|
// This happens if we are not joined to a domain. No problem, just ignore it.
|
|
_IgnoreError(dwErr, "NetLogonSetServiceBits")
|
|
} else if (S_OK!=dwErr) {
|
|
hr=HRESULT_FROM_WIN32(dwErr);
|
|
_JumpError(hr, error, "NetLogonSetServiceBits")
|
|
}
|
|
if (!I_ScSetServiceBits(g_servicestatushandle, SV_TYPE_TIME_SOURCE, FALSE, TRUE, NULL)) {
|
|
hr=HRESULT_FROM_WIN32(GetLastError());
|
|
if (HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE)==hr && (SERVICE_STATUS_HANDLE)3==g_servicestatushandle) {
|
|
// we are not really running as a service. just ignore this
|
|
} else {
|
|
_JumpError(hr, error, "I_ScSetServiceBits");
|
|
}
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT UpdateNetlogonServiceBits(bool bFullUpdate) {
|
|
HRESULT hr;
|
|
DWORD dwErr;
|
|
bool bTimeserv;
|
|
bool bReliableTimeserv;
|
|
DWORD dwNetlogonServiceBits;
|
|
|
|
if (false==bFullUpdate) {
|
|
// we only want to update the 'reliable' flag
|
|
// so keep the old timeserv flag
|
|
bTimeserv=(0!=(g_state.dwNetlogonServiceBits&DS_TIMESERV_FLAG));
|
|
|
|
} else {
|
|
// are we a time server? check the flags first
|
|
if (Timeserv_Announce_No==(Timeserv_Announce_Mask&g_state.pciConfig->dwAnnounceFlags)) {
|
|
bTimeserv=false;
|
|
} else if (Timeserv_Announce_Auto==(Timeserv_Announce_Mask&g_state.pciConfig->dwAnnounceFlags)) {
|
|
// autodetect
|
|
bool bWeAreADc=false;
|
|
bool bTimeOutputProvFound=false;
|
|
bool bWeAreSynchronized=false;
|
|
TimeProvider * ptpTravel;
|
|
|
|
if (DsRole_RoleStandaloneWorkstation==g_state.eMachineRole ||
|
|
DsRole_RoleStandaloneServer==g_state.eMachineRole) {
|
|
// We're a standalone machine -- netlogon is not started.
|
|
// Don't bother to set service bits, we'll just cause an RPC exception
|
|
hr = S_OK;
|
|
goto error;
|
|
}
|
|
|
|
if (DsRole_RoleBackupDomainController==g_state.eMachineRole
|
|
|| DsRole_RolePrimaryDomainController==g_state.eMachineRole) {
|
|
bWeAreADc=true;
|
|
}
|
|
|
|
// see if there are any provider running
|
|
ptpTravel=g_state.pciConfig->ptpProviderList;
|
|
while (NULL!=ptpTravel) {
|
|
if (false==ptpTravel->bInputProvider) {
|
|
bTimeOutputProvFound=true;
|
|
break;
|
|
}
|
|
ptpTravel=ptpTravel->ptpNext;
|
|
}
|
|
|
|
if (e_ClockNotSynchronized != g_state.eLeapIndicator) {
|
|
bWeAreSynchronized = true;
|
|
}
|
|
|
|
// We are a time service if we are a DC and there is a output provider running
|
|
bTimeserv=(bWeAreADc && bTimeOutputProvFound && bWeAreSynchronized);
|
|
|
|
} else {
|
|
// the Timeserv_Announce_Yes flag is set
|
|
bTimeserv=true;
|
|
}
|
|
}
|
|
|
|
// now see if we are a reliable time server
|
|
if (false==bTimeserv
|
|
|| Reliable_Timeserv_Announce_No==(Reliable_Timeserv_Announce_Mask&g_state.pciConfig->dwAnnounceFlags)) {
|
|
bReliableTimeserv=false;
|
|
} else if (Reliable_Timeserv_Announce_Auto==(Reliable_Timeserv_Announce_Mask&g_state.pciConfig->dwAnnounceFlags)) {
|
|
// autodetect
|
|
if (1==g_state.nStratum && NtpConst::dwLocalRefId==g_state.refidSource.value) {
|
|
bReliableTimeserv=true;
|
|
} else {
|
|
bReliableTimeserv=false;
|
|
}
|
|
} else {
|
|
// the Reliable_Timeserv_Announce_Yes flag is set
|
|
bReliableTimeserv=true;
|
|
}
|
|
|
|
// now see if we need to tell netlogon what our flags are
|
|
if (true==bFullUpdate
|
|
|| (true==bReliableTimeserv && 0==(g_state.dwNetlogonServiceBits&DS_GOOD_TIMESERV_FLAG))
|
|
|| (false==bReliableTimeserv && 0!=(g_state.dwNetlogonServiceBits&DS_GOOD_TIMESERV_FLAG))) {
|
|
|
|
// assume dword reads and writes are atomic
|
|
g_state.dwNetlogonServiceBits=(bTimeserv?DS_TIMESERV_FLAG:0)|(bReliableTimeserv?DS_GOOD_TIMESERV_FLAG:0);
|
|
|
|
dwErr=NetLogonSetServiceBits(NULL, DS_TIMESERV_FLAG|DS_GOOD_TIMESERV_FLAG, g_state.dwNetlogonServiceBits);
|
|
if (0xC0020012==dwErr) { //RPC_NT_UNKNOWN_IF in ntstatus.h
|
|
// This happens if we are not joined to a domain. No problem, just ignore it.
|
|
_IgnoreError(dwErr, "NetLogonSetServiceBits")
|
|
} else if (S_OK!=dwErr) {
|
|
hr=HRESULT_FROM_WIN32(dwErr);
|
|
_JumpError(hr, error, "NetLogonSetServiceBits")
|
|
}
|
|
if (!I_ScSetServiceBits(g_servicestatushandle, SV_TYPE_TIME_SOURCE, bTimeserv, TRUE, NULL)) {
|
|
hr=HRESULT_FROM_WIN32(GetLastError());
|
|
if (HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE)==hr && (SERVICE_STATUS_HANDLE)3==g_servicestatushandle) {
|
|
// we are not really running as a service. just ignore this
|
|
} else {
|
|
_JumpError(hr, error, "I_ScSetServiceBits");
|
|
}
|
|
}
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE int __cdecl CompareEndpointEntries(const void * pvElem1, const void * pvElem2) {
|
|
EndpointEntry * peeElem1=(EndpointEntry *)pvElem1;
|
|
EndpointEntry * peeElem2=(EndpointEntry *)pvElem2;
|
|
|
|
if (peeElem1->toEndpoint<peeElem2->toEndpoint) {
|
|
return -1;
|
|
} else if (peeElem1->toEndpoint>peeElem2->toEndpoint) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE int __cdecl CompareCandidateEntries(const void * pvElem1, const void * pvElem2) {
|
|
CandidateEntry * pceElem1=(CandidateEntry *)pvElem1;
|
|
CandidateEntry * pceElem2=(CandidateEntry *)pvElem2;
|
|
|
|
if (pceElem1->tpDistance<pceElem2->tpDistance) {
|
|
return -1;
|
|
} else if (pceElem1->tpDistance>pceElem2->tpDistance) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// NOTE: The method requires that nSamplesAvail > 0.
|
|
//
|
|
MODULEPRIVATE HRESULT SelectBestSample(unsigned int nSamplesAvail, bool * pbSuccessful) {
|
|
unsigned int nIndex;
|
|
signed __int64 toLow;
|
|
signed __int64 toHigh;
|
|
unsigned int nDroppedSamples; // f in RFC-1305
|
|
unsigned int nCandidates;
|
|
|
|
// note that the endpoint list and the candidate list will always be big enough to hold
|
|
// the entire sample buf, as ensured by EnlargeSampleBuf
|
|
|
|
//
|
|
// intersection algorithm
|
|
//
|
|
|
|
// create the list of endpoints
|
|
for (nIndex=0; nIndex<nSamplesAvail; nIndex++) {
|
|
unsigned __int64 tpSyncDistance;
|
|
if (g_state.rgtsSampleBuf[nIndex].toDelay<0) {
|
|
tpSyncDistance=(unsigned __int64)(-g_state.rgtsSampleBuf[nIndex].toDelay);
|
|
} else {
|
|
tpSyncDistance=(unsigned __int64)(g_state.rgtsSampleBuf[nIndex].toDelay);
|
|
}
|
|
tpSyncDistance/=2;
|
|
tpSyncDistance+=g_state.rgtsSampleBuf[nIndex].tpDispersion;
|
|
g_state.rgeeEndpointList[nIndex*3+0].toEndpoint=g_state.rgtsSampleBuf[nIndex].toOffset-tpSyncDistance;
|
|
g_state.rgeeEndpointList[nIndex*3+0].nType=-1;
|
|
g_state.rgeeEndpointList[nIndex*3+1].toEndpoint=g_state.rgtsSampleBuf[nIndex].toOffset;
|
|
g_state.rgeeEndpointList[nIndex*3+1].nType=0;
|
|
g_state.rgeeEndpointList[nIndex*3+2].toEndpoint=g_state.rgtsSampleBuf[nIndex].toOffset+tpSyncDistance;
|
|
g_state.rgeeEndpointList[nIndex*3+2].nType=1;
|
|
}
|
|
|
|
// sort the list
|
|
qsort(g_state.rgeeEndpointList, nSamplesAvail*3, sizeof(EndpointEntry), CompareEndpointEntries);
|
|
|
|
// determine the high and low of the range that at least half of the samples agree upon
|
|
for (nDroppedSamples=0; nDroppedSamples<=nSamplesAvail/2; nDroppedSamples++) {
|
|
unsigned int nIntersectionCount=0; // i in RFC-1305
|
|
unsigned int nFalseTickers=0; // c in RFC-1305
|
|
|
|
// find the lowest point including nSamplesAvail-nDroppedSamples samples
|
|
for (nIndex=0; nIndex<nSamplesAvail*3; nIndex++) {
|
|
nIntersectionCount-=g_state.rgeeEndpointList[nIndex].nType;
|
|
toLow=g_state.rgeeEndpointList[nIndex].toEndpoint;
|
|
if (nIntersectionCount>=nSamplesAvail-nDroppedSamples) {
|
|
break;
|
|
} else if (0==g_state.rgeeEndpointList[nIndex].nType) {
|
|
nFalseTickers++;
|
|
}
|
|
}
|
|
|
|
// find the highest point including nSamplesAvail-nDroppedSamples samples
|
|
nIntersectionCount=0;
|
|
for (nIndex=nSamplesAvail*3; nIndex>0; nIndex--) {
|
|
nIntersectionCount+=g_state.rgeeEndpointList[nIndex-1].nType;
|
|
toHigh=g_state.rgeeEndpointList[nIndex-1].toEndpoint;
|
|
if (nIntersectionCount>=nSamplesAvail-nDroppedSamples) {
|
|
break;
|
|
} else if (0==g_state.rgeeEndpointList[nIndex-1].nType) {
|
|
nFalseTickers++;
|
|
}
|
|
}
|
|
|
|
if (nFalseTickers<=nDroppedSamples) {
|
|
// we found all the falsetickers, so we can stop now.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Was there a range that the samples agreed upon?
|
|
if (toLow>toHigh) {
|
|
FileLog0(FL_SelectSampWarn, L"** No m/2 samples agreed upon range\n");
|
|
*pbSuccessful=false;
|
|
goto done;
|
|
}
|
|
|
|
FileLog1(FL_SelectSampAnnounceLow, L"Intersection successful with %u dropped samples.\n", nDroppedSamples);
|
|
|
|
|
|
//
|
|
// Clustering algorithm
|
|
//
|
|
|
|
// build the list of candidates that are in the intersection range
|
|
nCandidates=0;
|
|
for (nIndex=0; nIndex<nSamplesAvail; nIndex++) {
|
|
if (g_state.rgtsSampleBuf[nIndex].toOffset<=toHigh && g_state.rgtsSampleBuf[nIndex].toOffset>=toLow) {
|
|
unsigned __int64 tpSyncDistance;
|
|
if (g_state.rgtsSampleBuf[nIndex].toDelay<0) {
|
|
tpSyncDistance=(unsigned __int64)(-g_state.rgtsSampleBuf[nIndex].toDelay);
|
|
} else {
|
|
tpSyncDistance=(unsigned __int64)(g_state.rgtsSampleBuf[nIndex].toDelay);
|
|
}
|
|
tpSyncDistance/=2;
|
|
tpSyncDistance+=g_state.rgtsSampleBuf[nIndex].tpDispersion;
|
|
|
|
g_state.rgceCandidateList[nCandidates].nSampleIndex=nIndex;
|
|
g_state.rgceCandidateList[nCandidates].tpDistance=tpSyncDistance+NtpConst::tpMaxDispersion.qw*g_state.rgtsSampleBuf[nIndex].nStratum;
|
|
nCandidates++;
|
|
}
|
|
}
|
|
|
|
// sort the list
|
|
qsort(g_state.rgceCandidateList, nCandidates, sizeof(CandidateEntry), CompareCandidateEntries);
|
|
|
|
// just look at the top few
|
|
if (nCandidates>NtpConst::nMaxSelectClocks) {
|
|
nCandidates=NtpConst::nMaxSelectClocks;
|
|
}
|
|
|
|
// trim the candidate list to a small number
|
|
while (true) {
|
|
unsigned __int64 tpMaxSelectDispersion=0;;
|
|
unsigned int nMaxSelectDispersionIndex=0;
|
|
unsigned __int64 tpSelectDispersion=0;
|
|
TimeSample * ptsZero=&g_state.rgtsSampleBuf[g_state.rgceCandidateList[0].nSampleIndex];
|
|
unsigned __int64 tpMinDispersion=ptsZero->tpDispersion;
|
|
|
|
// we are looking for the maximum select dispersion and the minimum dispersion
|
|
for (nIndex=nCandidates; nIndex>0; nIndex--) {
|
|
// calculate the select dispersion for this candidate
|
|
signed __int64 toDelta=g_state.rgtsSampleBuf[g_state.rgceCandidateList[nIndex-1].nSampleIndex].toOffset-ptsZero->toOffset;
|
|
unsigned __int64 tpAbsDelta;
|
|
if (toDelta<0) {
|
|
tpAbsDelta=(unsigned __int64)(-toDelta);
|
|
} else {
|
|
tpAbsDelta=(unsigned __int64)(toDelta);
|
|
}
|
|
if (tpAbsDelta>NtpConst::tpMaxDispersion.qw) {
|
|
tpAbsDelta=NtpConst::tpMaxDispersion.qw;
|
|
}
|
|
tpSelectDispersion+=tpAbsDelta;
|
|
NtpConst::weightSelect(tpSelectDispersion);
|
|
|
|
if (FileLogAllowEntry(FL_SelectSampDump)) {
|
|
FileLogAdd(L" %u: Sample:%u SyncDist:%I64u SelectDisp:%I64u\n",
|
|
nIndex-1,
|
|
g_state.rgceCandidateList[nIndex-1].nSampleIndex,
|
|
g_state.rgceCandidateList[nIndex-1].tpDistance,
|
|
tpSelectDispersion);
|
|
}
|
|
|
|
// we are looking for the maximum select dispersion and the minimum dispersion
|
|
if (tpMaxSelectDispersion<tpSelectDispersion) {
|
|
tpMaxSelectDispersion=tpSelectDispersion;
|
|
nMaxSelectDispersionIndex=nIndex-1;
|
|
}
|
|
if (tpMinDispersion>g_state.rgtsSampleBuf[g_state.rgceCandidateList[nIndex-1].nSampleIndex].tpDispersion) {
|
|
tpMinDispersion=g_state.rgtsSampleBuf[g_state.rgceCandidateList[nIndex-1].nSampleIndex].tpDispersion;
|
|
}
|
|
} // <- end min/max calc loop
|
|
|
|
// did we eliminate enough outliers?
|
|
if (tpMaxSelectDispersion<=tpMinDispersion || nCandidates<=NtpConst::nMinSelectClocks) {
|
|
|
|
/*
|
|
// One last check - is it less that the maximum sync distance?
|
|
unsigned __int64 tpSyncDistance;
|
|
if (ptsZero->toDelay<0) {
|
|
tpSyncDistance=(unsigned __int64)(-ptsZero->toDelay);
|
|
} else {
|
|
tpSyncDistance=(unsigned __int64)(ptsZero->toDelay);
|
|
}
|
|
tpSyncDistance/=2;
|
|
tpSyncDistance+=ptsZero->tpDispersion;
|
|
if (tpSyncDistance>=NtpConst::tpMaxDistance.qw) {
|
|
FileLog0(FL_SelectSampWarn, L"** Chosen sample's sync distance is too big.\n");
|
|
*pbSuccessful=false;
|
|
goto done;
|
|
}
|
|
*/
|
|
|
|
// TODO: could do clock combining.
|
|
|
|
// save the answer
|
|
memcpy(&g_state.tsNextClockUpdate, ptsZero, sizeof(TimeSample));
|
|
g_state.tsiNextClockUpdate.ptp = g_state.rgtsiSampleInfoBuf[g_state.rgceCandidateList[0].nSampleIndex].ptp;
|
|
g_state.tsiNextClockUpdate.pts = &g_state.tsNextClockUpdate;
|
|
g_state.tpSelectDispersion.qw=tpSelectDispersion;
|
|
|
|
if (FileLogAllowEntry(FL_SelectSampDump)) {
|
|
FileLogAdd(L"Sample %u chosen. Select Dispersion:", g_state.rgceCandidateList[0].nSampleIndex);
|
|
FileLogNtTimePeriodEx(true /*append*/, g_state.tpSelectDispersion);
|
|
FileLogAppend(L"\n");
|
|
}
|
|
|
|
// All done! We are successful!
|
|
break;
|
|
|
|
} else {
|
|
|
|
FileLog1(FL_SelectSampDump, L"Discarding %u\n", nMaxSelectDispersionIndex);
|
|
|
|
// get rid of the worst offender
|
|
if (nMaxSelectDispersionIndex!=nCandidates-1) {
|
|
memmove(&g_state.rgceCandidateList[nMaxSelectDispersionIndex], &g_state.rgceCandidateList[nMaxSelectDispersionIndex+1], (nCandidates-1-nMaxSelectDispersionIndex)*sizeof(CandidateEntry));
|
|
}
|
|
nCandidates--;
|
|
}
|
|
|
|
} // <- end candidate list trimming
|
|
|
|
|
|
*pbSuccessful=true;
|
|
done:
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void WINAPI HandleClockDisplnThread(LPVOID pvIgnored, BOOLEAN bIgnored) {
|
|
HRESULT hr;
|
|
HRESULT hrThread = E_FAIL;
|
|
|
|
_BeginTryWith(hr) {
|
|
// Clock discipline thread has shut down!! Stop the service, if we're
|
|
// not already performing a shutdown:
|
|
if (!GetExitCodeThread(g_state.hClockDisplnThread, (DWORD *)&hrThread)) {
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
_IgnoreIfError(hr, "GetExitCodeThread");
|
|
}
|
|
|
|
// Can't shutdown the service from a registered callback function -- we'll deadlock!
|
|
// Queue shutdown asynchronously:
|
|
hr = SendServiceShutdown(hrThread, TRUE /*restart*/, TRUE /*async*/);
|
|
_IgnoreIfError(hr, "SendServiceShutdown");
|
|
} _TrapException(hr);
|
|
|
|
_IgnoreIfError(hr, "HandleClockDisplnThread: EXCEPTION HANDLED");
|
|
// return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT HandleManagerApmSuspend() {
|
|
HRESULT hr = S_OK;
|
|
HRESULT hr2;
|
|
|
|
FileLog0(FL_ResumeSuspendAnnounce, L"W32Time: Processing APM suspend notification. File logging will be disabled.\n");
|
|
|
|
// APM suspend requires that we close any open files:
|
|
hr2 = FileLogSuspend();
|
|
_TeardownError(hr, hr2, "FileLogSuspend");
|
|
if (SUCCEEDED(hr2)) {
|
|
g_state.bAPMStoppedFileLog = true;
|
|
}
|
|
|
|
// Let the CMOS clock take care of itself:
|
|
hr2 = HandleManagerGoUnsyncd();
|
|
_TeardownError(hr, hr2, "HandleManagerGoUnsynched");
|
|
|
|
// BUGBUG: should we propagate error to SCM?
|
|
hr2 = AcquireControlOfSystemClock(true /*acquire*/, true /*block*/, NULL /*assume acquired on success for blocking call*/);
|
|
_TeardownError(hr, hr2, "AllowSystemClockUpdates");
|
|
if (SUCCEEDED(hr2)) {
|
|
g_state.bAPMAcquiredSystemClock = true;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT HandleManagerApmResumeSuspend() {
|
|
HRESULT hr;
|
|
TpcTimeJumpedArgs tjArgs = { TJF_Default };
|
|
TpcNetTopoChangeArgs ntcArgs = { NTC_Default };
|
|
|
|
if (!g_state.bAPMStoppedFileLog) {
|
|
// We're resuming from a critical suspend, or stopping the filelog
|
|
// failed last time. Our file logging may be trashed at this point. Stop it and restart...
|
|
hr = FileLogSuspend();
|
|
_JumpIfError(hr, error, "FileLogSuspend");
|
|
g_state.bAPMStoppedFileLog = true;
|
|
}
|
|
|
|
if (g_state.bAPMAcquiredSystemClock) {
|
|
// We're resuming from a regular suspend, and we have the APM critsec locked.
|
|
// We must have the APM critsec locked -- free it and continue.
|
|
// BUGBUG: should we propagate error to SCM?
|
|
hr = AcquireControlOfSystemClock(false /*acquire*/, false /*ignored*/, NULL /*ignored*/);
|
|
_JumpIfError(hr, error, "AllowSystemClockUpdates");
|
|
g_state.bAPMAcquiredSystemClock = false;
|
|
}
|
|
|
|
hr = FileLogResume();
|
|
_JumpIfError(hr, error, "FileLogResume");
|
|
g_state.bAPMStoppedFileLog = false;
|
|
|
|
FileLog0(FL_ResumeSuspendAnnounce, L"Processing APM resume notification...\n");
|
|
|
|
// APM suspend doesn't preserve net connections. Rediscover network sources:
|
|
hr = HandleManagerNetTopoChange(true);
|
|
_JumpIfError(hr, error, "HandleManagerNetTopoChange"); // Fatal
|
|
|
|
// APM suspend almost certainly has caused a time slip.
|
|
hr = HandleManagerHardResync(TPC_TimeJumped, &tjArgs);
|
|
_JumpIfError(hr, error, "HandleManagerHardResync (TPC_TimeJumped) "); // Fatal
|
|
|
|
FileLog0(FL_ResumeSuspendAnnounce, L"APM resume complete!\n");
|
|
|
|
hr = S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT HandleManagerGetTimeSamples(bool bIrregular) {
|
|
HRESULT hr;
|
|
TimeProvider * ptp;
|
|
TpcGetSamplesArgs tgsa;
|
|
unsigned int nSamplesSoFar=0;
|
|
bool bBufferTooSmall;
|
|
bool bSuccessful;
|
|
bool bEnteredCriticalSection = false;
|
|
|
|
// must be cleaned up
|
|
WCHAR * wszError=NULL;
|
|
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection=true;
|
|
|
|
// loop over all the input providers and gather the samples.
|
|
ptp=g_state.pciConfig->ptpProviderList;
|
|
while (ptp!=NULL) {
|
|
if (true==ptp->bInputProvider) {
|
|
do {
|
|
// prepare the buffer for the next person to append to
|
|
tgsa.pbSampleBuf=(BYTE *)(&g_state.rgtsSampleBuf[nSamplesSoFar]);
|
|
tgsa.cbSampleBuf=sizeof(TimeSample)*(g_state.nSampleBufAllocSize-nSamplesSoFar);
|
|
tgsa.dwSamplesAvailable=0;
|
|
tgsa.dwSamplesReturned=0;
|
|
bBufferTooSmall=false;
|
|
|
|
// request the samples
|
|
_BeginTryWith(hr) {
|
|
if (!ptp->bStarted) {
|
|
hr=HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_ACTIVE);
|
|
} else {
|
|
hr=ptp->pfnTimeProvCommand(ptp->hTimeProv, TPC_GetSamples, &tgsa);
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
// was the buffer not big enough?
|
|
if (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)==hr) {
|
|
bBufferTooSmall=true;
|
|
hr=EnlargeSampleBuf(tgsa.dwSamplesAvailable-tgsa.dwSamplesReturned);
|
|
_JumpIfError(hr, error, "EnlargeSampleBuf");
|
|
}
|
|
} while (bBufferTooSmall);
|
|
|
|
if (FAILED(hr)) {
|
|
// log an event on failure, but otherwise ignore it.
|
|
const WCHAR * rgwszStrings[2]={
|
|
ptp->wszProvName,
|
|
NULL
|
|
};
|
|
|
|
// get the friendly error message
|
|
hr=GetSystemErrorString(hr, &wszError);
|
|
_JumpIfError(hr, error, "GetSystemErrorString");
|
|
|
|
// log the event
|
|
rgwszStrings[1]=wszError;
|
|
FileLog2(FL_ControlProvWarn, L"Logging warning: The time provider '%s' returned an error when asked for time samples. The error will be ignored. The error was: %s\n", ptp->wszProvName, wszError);
|
|
hr=MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_TIMEPROV_FAILED_GETSAMPLES, 2, rgwszStrings);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
|
|
LocalFree(wszError);
|
|
wszError=NULL;
|
|
} else {
|
|
// success. keep these samples and ask the next provider.
|
|
FileLog2(FL_CollectSampDump, L"%s returned %d samples.\n", ptp->wszProvName, tgsa.dwSamplesReturned);
|
|
|
|
// Maintain w32time-specific information:
|
|
for (unsigned int nIndex = nSamplesSoFar; nIndex < nSamplesSoFar+tgsa.dwSamplesReturned; nIndex++) {
|
|
g_state.rgtsiSampleInfoBuf[nIndex].pts = &g_state.rgtsSampleBuf[nIndex];
|
|
g_state.rgtsiSampleInfoBuf[nIndex].ptp = ptp; // Store the provider that provided this sample
|
|
}
|
|
|
|
nSamplesSoFar+=tgsa.dwSamplesReturned;
|
|
}
|
|
|
|
} // <- end if provider is an input provider
|
|
|
|
ptp=ptp->ptpNext;
|
|
} // <- end provider loop
|
|
|
|
{
|
|
HRESULT hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
|
|
if (FileLogAllowEntry(FL_CollectSampDump)) {
|
|
unsigned int nIndex;
|
|
for (nIndex=0; nIndex<nSamplesSoFar; nIndex++) {
|
|
NtTimeOffset to={g_state.rgtsSampleBuf[nIndex].toOffset};
|
|
FileLogAdd(L"Sample %d offset:", nIndex);
|
|
FileLogNtTimeOffsetEx(true /*append*/, to);
|
|
FileLogAppend(L" delay:");
|
|
to.qw=g_state.rgtsSampleBuf[nIndex].toDelay;
|
|
FileLogNtTimeOffsetEx(true /*append*/, to);
|
|
FileLogAppend(L" dispersion:");
|
|
NtTimePeriod tp={g_state.rgtsSampleBuf[nIndex].tpDispersion};
|
|
FileLogNtTimePeriodEx(true /*append*/, tp);
|
|
FileLogAppend(L"\n");
|
|
}
|
|
}
|
|
|
|
bSuccessful=false;
|
|
if (nSamplesSoFar>0) {
|
|
hr=SelectBestSample(nSamplesSoFar, &bSuccessful);
|
|
_JumpIfError(hr, error, "SelectBestSample");
|
|
}
|
|
|
|
if (bSuccessful) {
|
|
// we found someone to synchronize from!
|
|
|
|
HANDLE rghWait[2]={
|
|
g_state.hClockCommandCompleteEvent,
|
|
g_state.hClockDisplnThread
|
|
};
|
|
DWORD dwWaitResult;
|
|
|
|
g_state.eLocalClockCommand=bIrregular?e_IrregularUpdate:e_RegularUpdate;
|
|
if (!SetEvent(g_state.hClockCommandAvailEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
dwWaitResult=WaitForMultipleObjects(ARRAYSIZE(rghWait), rghWait, false, INFINITE);
|
|
if (WAIT_FAILED==dwWaitResult) {
|
|
_JumpLastError(hr, error, "WaitForMultipleObjects");
|
|
} else if (WAIT_OBJECT_0==dwWaitResult) {
|
|
// We may need to change our status with netlogon if
|
|
// 1) We've gone from unsynchronized --> synchronized,
|
|
// we can now advertise as a time source
|
|
// 2) We've become a reliable time source
|
|
hr=UpdateNetlogonServiceBits(true);
|
|
_JumpIfError(hr, error, "UpdateNetlogonServiceBits");
|
|
|
|
// save result for RPC requests
|
|
if (true==g_state.bStaleData) {
|
|
g_state.eLastSyncResult=e_StaleData;
|
|
} else if (true==g_state.bClockChangeTooBig) {
|
|
g_state.eLastSyncResult=e_ChangeTooBig;
|
|
} else {
|
|
g_state.eLastSyncResult=e_Success;
|
|
g_state.tpTimeSinceLastGoodSync=0;
|
|
}
|
|
|
|
// log a message if the time source changed
|
|
if (g_state.bSourceChanged && 0!=(EvtLog_SourceChange&g_state.dwEventLogFlags)) {
|
|
hr = MyLogSourceChangeEvent(g_state.wszSourceName);
|
|
_JumpIfError(hr, error, "MyLogSourceChangeEvent");
|
|
}
|
|
|
|
// log a message if the clock jumped
|
|
if (g_state.bClockJumped && 0!=(EvtLog_TimeJump&g_state.dwEventLogFlags)) {
|
|
WCHAR wszNumberBuf[35];
|
|
WCHAR * rgwszStrings[1]={wszNumberBuf};
|
|
if (g_state.toClockJump<gc_toZero) {
|
|
swprintf(wszNumberBuf, L"-%I64u", (-g_state.toClockJump.qw)/10000000);
|
|
} else {
|
|
swprintf(wszNumberBuf, L"+%I64u", g_state.toClockJump.qw/10000000);
|
|
}
|
|
FileLog1(FL_TimeAdjustWarn, L"Logging warning: The time service has made a discontinuous change in the system clock. The system time has been changed by %s seconds.\n", rgwszStrings[0]);
|
|
hr=MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_TIME_JUMPED, 1, (const WCHAR **)rgwszStrings);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
}
|
|
|
|
// log a message if we went into the "unset" state
|
|
if (g_state.bPhaseSpike) {
|
|
WCHAR wszNumberBuf1[35];
|
|
WCHAR wszNumberBuf2[35];
|
|
WCHAR * rgwszStrings[2]={wszNumberBuf1, wszNumberBuf2};
|
|
|
|
swprintf(wszNumberBuf1, L"%d", (g_state.pciConfig->lcci.dwLargePhaseOffset / 10000));
|
|
swprintf(wszNumberBuf2, L"%d", g_state.pciConfig->lcci.dwSpikeWatchPeriod);
|
|
|
|
FileLog2(FL_TimeAdjustWarn, L"Logging warning: The time service detected a time difference of greater than %s milliseconds for %s seconds. The system clock is unsynchronized. This is usually caused by synchronizing from low-accuracy time sources, or by poor network conditions.\n", wszNumberBuf1, wszNumberBuf2);
|
|
|
|
hr=MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_LOCALCLOCK_UNSET, 2, (const WCHAR **)rgwszStrings);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
}
|
|
|
|
if (g_state.bPhaseSpike || g_state.bFrequencySpike) {
|
|
// A phase or frequency spike has made us go unsynchronized.
|
|
// We want to update as soon as possible. Delay some, so as to avoid
|
|
// a frequency spike, and then repoll
|
|
g_state.tpPollDelayRemaining=((unsigned __int64)(((DWORD)1)<<g_state.nPollInterval))*10000000;
|
|
g_state.tpTimeSinceLastSyncAttempt=0;
|
|
g_state.tpTimeSinceLastGoodSync=0;
|
|
g_state.tpIrregularDelayRemaining=MINIMUMIRREGULARINTERVAL; // 16s
|
|
}
|
|
|
|
// log a message if the clock change was ignored
|
|
if (true==g_state.bClockChangeTooBig && false==g_state.bDontLogClockChangeTooBig) {
|
|
WCHAR wszNumberBuf1[35];
|
|
WCHAR wszNumberBuf2[35];
|
|
WCHAR * rgwszStrings[3]={wszNumberBuf1, wszNumberBuf2, NULL};
|
|
rgwszStrings[2]=g_state.tsNextClockUpdate.wszUniqueName;
|
|
if (g_state.toIgnoredChange<gc_toZero) {
|
|
swprintf(wszNumberBuf1, L"-%I64u", (-g_state.toIgnoredChange.qw)/10000000);
|
|
swprintf(wszNumberBuf2, L"-%u", g_state.pciConfig->lcci.dwMaxNegPhaseCorrection);
|
|
} else {
|
|
swprintf(wszNumberBuf1, L"+%I64u", g_state.toIgnoredChange.qw/10000000);
|
|
swprintf(wszNumberBuf2, L"+%u", g_state.pciConfig->lcci.dwMaxPosPhaseCorrection);
|
|
}
|
|
FileLog3(FL_TimeAdjustWarn, L"Logging error: The time service has detected that the system time need to be changed by %s seconds. For security reasons, the time service will not change the system time by more than %s seconds. Verify that your time and time zone are correct, and that the time source %s is working properly.\n", rgwszStrings[0], rgwszStrings[1], rgwszStrings[2]);
|
|
hr=MyLogEvent(EVENTLOG_ERROR_TYPE, MSG_TIME_CHANGE_TOO_BIG, 3, (const WCHAR **)rgwszStrings);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
g_state.bDontLogClockChangeTooBig=true;
|
|
}
|
|
if (false==g_state.bStaleData && false==g_state.bClockChangeTooBig && true==g_state.bDontLogClockChangeTooBig) {
|
|
g_state.bDontLogClockChangeTooBig=false;
|
|
}
|
|
|
|
// propogate the message to the providers
|
|
if (g_state.bPollIntervalChanged || g_state.bClockJumped) {
|
|
TimeProvider * ptpTravel;
|
|
for (ptpTravel=g_state.pciConfig->ptpProviderList; NULL!=ptpTravel; ptpTravel=ptpTravel->ptpNext) {
|
|
if (g_state.bClockJumped) {
|
|
TpcTimeJumpedArgs tjArgs = { TJF_Default };
|
|
hr=SendNotificationToProvider(ptpTravel, TPC_TimeJumped, &tjArgs);
|
|
_JumpIfError(hr, error, "SendNotificationToProvider");
|
|
}
|
|
if (g_state.bPollIntervalChanged) {
|
|
hr=SendNotificationToProvider(ptpTravel, TPC_PollIntervalChanged, NULL);
|
|
_JumpIfError(hr, error, "SendNotificationToProvider");
|
|
}
|
|
} // <- end provider loop
|
|
} // <- end if messages to propogate
|
|
} else {
|
|
// the ClockDiscipline thread shut down!
|
|
// fall outward to the manager thread main loop to analyze the issue.
|
|
}
|
|
|
|
} else {
|
|
// save result for RPC requests
|
|
g_state.eLastSyncResult=e_NoData;
|
|
}
|
|
|
|
// Allow any waiting RPC requests to finish
|
|
if (g_state.hRpcSyncCompleteEvent==g_state.hRpcSyncCompleteAEvent) {
|
|
if (!ResetEvent(g_state.hRpcSyncCompleteBEvent)) {
|
|
_JumpLastError(hr, error, "ResetEvent");
|
|
}
|
|
g_state.hRpcSyncCompleteEvent=g_state.hRpcSyncCompleteBEvent;
|
|
if (!SetEvent(g_state.hRpcSyncCompleteAEvent)) {
|
|
_JumpLastError(hr, error, "ResetEvent");
|
|
}
|
|
} else {
|
|
if (!ResetEvent(g_state.hRpcSyncCompleteAEvent)) {
|
|
_JumpLastError(hr, error, "ResetEvent");
|
|
}
|
|
g_state.hRpcSyncCompleteEvent=g_state.hRpcSyncCompleteAEvent;
|
|
if (!SetEvent(g_state.hRpcSyncCompleteBEvent)) {
|
|
_JumpLastError(hr, error, "ResetEvent");
|
|
}
|
|
}
|
|
|
|
// update the time remaining
|
|
if (!bIrregular) {
|
|
// start a new regular wait
|
|
g_state.tpPollDelayRemaining=((unsigned __int64)(((DWORD)1)<<g_state.nPollInterval))*10000000;
|
|
g_state.eLastRegSyncResult=g_state.eLastSyncResult;
|
|
}
|
|
// clear the irregular time, and contiue with the remaining regular wait
|
|
g_state.tpIrregularDelayRemaining=0;
|
|
g_state.tpTimeSinceLastSyncAttempt=0;
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
HRESULT hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (NULL!=wszError) {
|
|
LocalFree(wszError);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT HandleManagerGoUnsyncd(void) {
|
|
HRESULT hr;
|
|
HANDLE rghWait[2]={
|
|
g_state.hClockCommandCompleteEvent,
|
|
g_state.hClockDisplnThread
|
|
};
|
|
DWORD dwWaitResult;
|
|
|
|
g_state.eLocalClockCommand=e_GoUnsyncd;
|
|
if (!SetEvent(g_state.hClockCommandAvailEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
dwWaitResult=WaitForMultipleObjects(ARRAYSIZE(rghWait), rghWait, false, INFINITE);
|
|
if (WAIT_FAILED==dwWaitResult) {
|
|
_JumpLastError(hr, error, "WaitForMultipleObjects");
|
|
} else if (WAIT_OBJECT_0==dwWaitResult) {
|
|
|
|
// log a message if we went unsynced
|
|
if (g_state.bSourceChanged && 0!=(EvtLog_SourceChange&g_state.dwEventLogFlags)) {
|
|
WCHAR wszNumberBuf[35];
|
|
WCHAR * rgwszStrings[1]={wszNumberBuf};
|
|
DWORD dwLongTimeNoSync=((DWORD)3)<<(g_state.pciConfig->lcci.dwMaxPollInterval-1);
|
|
if (dwLongTimeNoSync < ((DWORD)((NtpConst::tpMaxClockAge.qw)/10000000))) {
|
|
dwLongTimeNoSync = ((DWORD)((NtpConst::tpMaxClockAge.qw)/10000000));
|
|
}
|
|
swprintf(wszNumberBuf, L"%u", dwLongTimeNoSync);
|
|
FileLog1(FL_SourceChangeWarn, L"Logging warning: The time service has not been able to synchronize the system time for %s seconds because none of the time providers has been able to provide a usable time stamp. The system clock is unsynchronized.\n", rgwszStrings[0]);
|
|
hr=MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_TIME_SOURCE_NONE, 1, (const WCHAR **)rgwszStrings);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
}
|
|
} else {
|
|
// the ClockDiscipline thread shut down!
|
|
// fall outward to the manager thread main loop to analyze the issue.
|
|
}
|
|
|
|
// update the time remaining
|
|
g_state.tpTimeSinceLastGoodSync=0;
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void WINAPI HandleManagerParamChange(PVOID pvIgnored, BOOLEAN bIgnored) {
|
|
bool bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
HRESULT hr2;
|
|
TimeProvider **pptpCurPrev;
|
|
TimeProvider *ptpCurTravel;
|
|
unsigned int nProvidersStopped = 0;
|
|
unsigned int nProvidersStarted = 0;
|
|
unsigned int nProvidersNotChanged = 0;
|
|
unsigned int nRequestedInputProviders = 0;
|
|
|
|
|
|
// Must be cleaned up
|
|
ConfigInfo * pciConfig=NULL;
|
|
WCHAR * rgwszStrings[1]={NULL};
|
|
|
|
_BeginTryWith(hr) {
|
|
|
|
FileLog0(FL_ParamChangeAnnounce, L"W32TmServiceMain: Param change notification\n");
|
|
|
|
// We've been called asynchronously by the SCM. Need to serialize this call:
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection=true;
|
|
|
|
hr = UpdateTimerQueue2();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue2");
|
|
|
|
// Propagate the message to the file log:
|
|
hr2 = UpdateFileLogConfig();
|
|
_IgnoreIfError(hr2, "UpdateFileLogConfig");
|
|
|
|
// get the configuration data
|
|
hr2=ReadConfig(&pciConfig);
|
|
if (FAILED(hr2)) {
|
|
// log an event on failure
|
|
hr=GetSystemErrorString(hr2, &(rgwszStrings[0]));
|
|
_JumpIfError(hr, error, "GetSystemErrorString");
|
|
FileLog1(FL_ParamChangeWarn, L"Logging warning: The time service encountered an error while reading its configuration from the registry, and will continue running with its previous configuration. The error was: %s\n", rgwszStrings[0]);
|
|
hr=MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_CONFIG_READ_FAILED_WARNING, 1, (const WCHAR **)rgwszStrings);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
|
|
// propogate the message to the providers at least
|
|
for (ptpCurTravel=g_state.pciConfig->ptpProviderList; NULL!=ptpCurTravel; ptpCurTravel=ptpCurTravel->ptpNext) {
|
|
hr=SendNotificationToProvider(ptpCurTravel, TPC_UpdateConfig, NULL);
|
|
_JumpIfError(hr, error, "SendNotificationToProvider");
|
|
}
|
|
|
|
} else {
|
|
// see if anything changed
|
|
|
|
// first, check the local clock config
|
|
if (0!=memcmp(&g_state.pciConfig->lcci, &pciConfig->lcci, sizeof(LocalClockConfigInfo))) {
|
|
FileLog0(FL_ParamChangeAnnounce, L" Updating params for local clock.\n");
|
|
|
|
// config is different. Grab it and tell the local clock.
|
|
memcpy(&g_state.pciConfig->lcci, &pciConfig->lcci, sizeof(LocalClockConfigInfo));
|
|
|
|
// fix the poll interval if necessary
|
|
// this is safe becuase the local clock only changes poll interval during an update, and we
|
|
// wait for the local clock to finish updates before proceding (so we're not updating now)
|
|
if (g_state.nPollInterval<((signed int)g_state.pciConfig->lcci.dwMinPollInterval)
|
|
|| g_state.nPollInterval>((signed int)g_state.pciConfig->lcci.dwMaxPollInterval)) {
|
|
if (g_state.nPollInterval<((signed int)g_state.pciConfig->lcci.dwMinPollInterval)) {
|
|
g_state.nPollInterval=((signed int)g_state.pciConfig->lcci.dwMinPollInterval);
|
|
} else {
|
|
g_state.nPollInterval=((signed int)g_state.pciConfig->lcci.dwMaxPollInterval);
|
|
if (g_state.tpPollDelayRemaining>((unsigned __int64)(((DWORD)1)<<g_state.nPollInterval))*10000000) {
|
|
g_state.tpPollDelayRemaining=((unsigned __int64)(((DWORD)1)<<g_state.nPollInterval))*10000000;
|
|
}
|
|
}
|
|
|
|
// propogate the message to the providers
|
|
for (ptpCurTravel=g_state.pciConfig->ptpProviderList; NULL!=ptpCurTravel; ptpCurTravel=ptpCurTravel->ptpNext) {
|
|
hr=SendNotificationToProvider(ptpCurTravel, TPC_PollIntervalChanged, NULL);
|
|
_JumpIfError(hr, error, "SendNotificationToProvider");
|
|
}
|
|
|
|
}
|
|
|
|
// now, tell the local clock.
|
|
{
|
|
HANDLE rghWait[2]={
|
|
g_state.hClockCommandCompleteEvent,
|
|
g_state.hClockDisplnThread
|
|
};
|
|
DWORD dwWaitResult;
|
|
|
|
g_state.eLocalClockCommand=e_ParamChange;
|
|
if (!SetEvent(g_state.hClockCommandAvailEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
dwWaitResult=WaitForMultipleObjects(ARRAYSIZE(rghWait), rghWait, false, INFINITE);
|
|
if (WAIT_FAILED==dwWaitResult) {
|
|
_JumpLastError(hr, error, "WaitForMultipleObjects");
|
|
} else if (WAIT_OBJECT_0==dwWaitResult) {
|
|
// Command acknowledged
|
|
} else {
|
|
// the ClockDiscipline thread shut down!
|
|
// fall outward to the manager thread main loop to analyze the issue.
|
|
}
|
|
}
|
|
|
|
} else {
|
|
FileLog0(FL_ParamChangeAnnounce, L" No params changed for local clock.\n");
|
|
} // <- end if config change for local clock
|
|
|
|
// second, check the provider list
|
|
// synchronization: currently, this thread (the manager thread)
|
|
// is the only thread that walks the provider list.
|
|
|
|
// check each provider in the current list against the new list
|
|
nRequestedInputProviders=CountInputProvidersInList(pciConfig->ptpProviderList);
|
|
pptpCurPrev=&(g_state.pciConfig->ptpProviderList);
|
|
ptpCurTravel=*pptpCurPrev;
|
|
while (NULL!=ptpCurTravel) {
|
|
|
|
// walk the new provider list
|
|
TimeProvider ** pptpNewPrev=&(pciConfig->ptpProviderList);
|
|
TimeProvider * ptpNewTravel=*pptpNewPrev;
|
|
while (NULL!=ptpNewTravel) {
|
|
// stop if this new provider matches the current provider
|
|
if (0==wcscmp(ptpNewTravel->wszDllName, ptpCurTravel->wszDllName)
|
|
&& 0==wcscmp(ptpNewTravel->wszProvName, ptpCurTravel->wszProvName)
|
|
&& ptpNewTravel->bInputProvider==ptpCurTravel->bInputProvider) {
|
|
break;
|
|
}
|
|
pptpNewPrev=&ptpNewTravel->ptpNext;
|
|
ptpNewTravel=ptpNewTravel->ptpNext;
|
|
}
|
|
if (NULL!=ptpNewTravel) {
|
|
// provider is in both lists, so we can drop the new one
|
|
nProvidersNotChanged++;
|
|
*pptpNewPrev=ptpNewTravel->ptpNext;
|
|
ptpNewTravel->ptpNext=NULL;
|
|
FreeTimeProviderList(ptpNewTravel);
|
|
|
|
// send a "Param changed" message
|
|
// do it here, so stopped and started providers don't get the update message
|
|
hr=SendNotificationToProvider(ptpCurTravel, TPC_UpdateConfig, NULL);
|
|
_JumpIfError(hr, error, "SendNotificationToProvider");
|
|
|
|
// procede to the next provider in the current list
|
|
pptpCurPrev=&ptpCurTravel->ptpNext;
|
|
ptpCurTravel=ptpCurTravel->ptpNext;
|
|
|
|
} else {
|
|
// provider is not in new list
|
|
// stop the privider
|
|
nProvidersStopped++;
|
|
hr=StopProvider(ptpCurTravel);
|
|
_JumpIfError(hr, error, "StopProvider");
|
|
|
|
// remove it from the list
|
|
*pptpCurPrev=ptpCurTravel->ptpNext;
|
|
ptpCurTravel->ptpNext=NULL;
|
|
FreeTimeProviderList(ptpCurTravel);
|
|
ptpCurTravel=*pptpCurPrev;
|
|
}
|
|
} // <- End list comparison loop
|
|
|
|
// Now, the only providers left in the new list are truly new providers.
|
|
// Append to our current list and start them.
|
|
*pptpCurPrev=pciConfig->ptpProviderList;
|
|
pciConfig->ptpProviderList=NULL;
|
|
ptpCurTravel=*pptpCurPrev;
|
|
while (NULL!=ptpCurTravel) {
|
|
hr=StartProvider(ptpCurTravel);
|
|
if (FAILED(hr)) {
|
|
FileLog1(FL_ParamChangeAnnounce, L"Discarding provider '%s'.\n", ptpCurTravel->wszProvName);
|
|
*pptpCurPrev=ptpCurTravel->ptpNext;
|
|
ptpCurTravel->ptpNext=NULL;
|
|
FreeTimeProviderList(ptpCurTravel);
|
|
ptpCurTravel=*pptpCurPrev;
|
|
} else {
|
|
nProvidersStarted++;
|
|
pptpCurPrev=&ptpCurTravel->ptpNext;
|
|
ptpCurTravel=ptpCurTravel->ptpNext;
|
|
}
|
|
} // <- end provider starting loop
|
|
|
|
FileLog3(FL_ParamChangeAnnounce, L" Provider list: %u stopped, %u started, %u not changed.\n",
|
|
nProvidersStopped, nProvidersStarted, nProvidersNotChanged);
|
|
|
|
// if we were supposed to have time providers, but NONE started, log a big warning
|
|
if (0==CountInputProvidersInList(g_state.pciConfig->ptpProviderList) && 0!=nRequestedInputProviders) {
|
|
FileLog0(FL_ParamChangeWarn, L"Logging error: The time service has been configured to use one or more input providers, however, none of the input providers could be started. THE TIME SERVICE HAS NO SOURCE OF ACCURATE TIME.\n");
|
|
hr=MyLogEvent(EVENTLOG_ERROR_TYPE, MSG_NO_INPUT_PROVIDERS_STARTED, 0, NULL);
|
|
_IgnoreIfError(hr, "MyLogEvent");
|
|
}
|
|
|
|
// now, check the announce flags
|
|
if (g_state.pciConfig->dwAnnounceFlags!=pciConfig->dwAnnounceFlags) {
|
|
FileLog2(FL_ParamChangeAnnounce, L" AnnounceFlags changed from 0x%08X to 0x%08X.\n", g_state.pciConfig->dwAnnounceFlags, pciConfig->dwAnnounceFlags);
|
|
g_state.pciConfig->dwAnnounceFlags=pciConfig->dwAnnounceFlags;
|
|
hr=UpdateNetlogonServiceBits(true);
|
|
_JumpIfError(hr, error, "UpdateNetlogonServiceBits");
|
|
} else if ((0!=nProvidersStopped || 0!=nProvidersStarted)
|
|
&& Timeserv_Announce_Auto==(Timeserv_Announce_Mask&g_state.pciConfig->dwAnnounceFlags)) {
|
|
FileLog0(FL_ParamChangeAnnounce, L" AnnounceFlags are auto. Updating announcement to match new provider list.\n");
|
|
hr=UpdateNetlogonServiceBits(true);
|
|
_JumpIfError(hr, error, "UpdateNetlogonServiceBits");
|
|
}
|
|
|
|
// check the EventLogFlags flag
|
|
if (g_state.dwEventLogFlags!=pciConfig->dwEventLogFlags) {
|
|
FileLog2(FL_ParamChangeAnnounce, L" EventLogFlags changed from 0x%08X to 0x%08X.\n",
|
|
g_state.dwEventLogFlags, pciConfig->dwEventLogFlags);
|
|
g_state.dwEventLogFlags=pciConfig->dwEventLogFlags;
|
|
}
|
|
|
|
// That's all the configuration parameters so far.
|
|
|
|
// log this again as well
|
|
g_state.bDontLogClockChangeTooBig=false;
|
|
|
|
} // <- end if configuration successfully read
|
|
|
|
hr = UpdateTimerQueue1();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue1");
|
|
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "HandleManagerParamChange: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (NULL!=pciConfig) {
|
|
FreeConfigInfo(pciConfig);
|
|
}
|
|
if (NULL!=rgwszStrings[0]) {
|
|
LocalFree(rgwszStrings[0]);
|
|
}
|
|
if (bEnteredCriticalSection) {
|
|
hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (S_OK != hr) { // The service should not continue if this function failed: stop the service on error.
|
|
hr2 = SendServiceShutdown(hr, TRUE /*restart*/, TRUE /*async*/);
|
|
_IgnoreIfError(hr2, "SendServiceShutdown");
|
|
}
|
|
// return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void WINAPI HandleManagerGPUpdate(PVOID pvIgnored, BOOLEAN bIgnored) {
|
|
bool bDisallowedShutdown = false;
|
|
HRESULT hr;
|
|
|
|
_BeginTryWith(hr) {
|
|
FileLog0(FL_GPUpdateAnnounce, L"W32TmServiceMain: Group Policy Update\n");
|
|
|
|
HandleManagerParamChange(NULL, FALSE);
|
|
|
|
// We can't mess with our registered callbacks FROM a callback
|
|
// if we're shutting down!
|
|
hr = AllowShutdown(false);
|
|
_JumpIfError(hr, error, "AllowShutdown");
|
|
bDisallowedShutdown = true;
|
|
|
|
if (!ResetEvent(g_state.hManagerGPUpdateEvent)) {
|
|
// If we can't reset the event, don't attempt to re-register for policy notification.
|
|
// We don't want to get caught in an infinite loop of policy updates.
|
|
_JumpLastError(hr, error, "ResetEvent");
|
|
}
|
|
|
|
if (NULL != g_state.hRegisteredManagerGPUpdateEvent) {
|
|
if (!UnregisterWaitEx(g_state.hRegisteredManagerGPUpdateEvent, 0 /*don't wait*/)) {
|
|
// Should just be a resource leak if we can't unregister this event.
|
|
_IgnoreLastError("UnregisterWait");
|
|
}
|
|
g_state.hRegisteredManagerGPUpdateEvent = NULL;
|
|
}
|
|
|
|
if (!RegisterWaitForSingleObject(&g_state.hRegisteredManagerGPUpdateEvent, g_state.hManagerGPUpdateEvent, HandleManagerGPUpdate, NULL, INFINITE, WT_EXECUTEONLYONCE)) {
|
|
_JumpLastError(hr, error, "RegisterWaitForSingleObject");
|
|
}
|
|
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "HandleManagerGPUpdate: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bDisallowedShutdown) {
|
|
hr = AllowShutdown(true);
|
|
_IgnoreIfError(hr, "AllowShutdown");
|
|
}
|
|
;
|
|
// BUGBUG: log event to indicate no more policy updates:
|
|
// return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// common code for time slip and net topo change
|
|
MODULEPRIVATE HRESULT HandleManagerHardResync(TimeProvCmd tpc, LPVOID pvArgs) {
|
|
HRESULT hr;
|
|
HANDLE rghWait[2]={
|
|
g_state.hClockCommandCompleteEvent,
|
|
g_state.hClockDisplnThread
|
|
};
|
|
DWORD dwWaitResult;
|
|
|
|
// send a slip message to the local clock
|
|
g_state.eLocalClockCommand=e_TimeSlip;
|
|
if (!SetEvent(g_state.hClockCommandAvailEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
|
|
dwWaitResult=WaitForMultipleObjects(ARRAYSIZE(rghWait), rghWait, false, INFINITE);
|
|
if (WAIT_FAILED==dwWaitResult) {
|
|
_JumpLastError(hr, error, "WaitForMultipleObjects");
|
|
} else if (WAIT_OBJECT_0==dwWaitResult) {
|
|
// propagate the message to the providers
|
|
TimeProvider * ptpTravel;
|
|
for (ptpTravel=g_state.pciConfig->ptpProviderList; NULL!=ptpTravel; ptpTravel=ptpTravel->ptpNext) {
|
|
|
|
hr=SendNotificationToProvider(ptpTravel, tpc, pvArgs);
|
|
_JumpIfError(hr, error, "SendNotificationToProvider");
|
|
|
|
if (g_state.bPollIntervalChanged) {
|
|
hr=SendNotificationToProvider(ptpTravel, TPC_PollIntervalChanged, NULL);
|
|
_JumpIfError(hr, error, "SendNotificationToProvider");
|
|
}
|
|
} // <- end provider loop
|
|
} else {
|
|
// the ClockDiscipline thread shut down!
|
|
// fall outward to the manager thread main loop to analyze the issue.
|
|
}
|
|
|
|
// log this again as well
|
|
g_state.bDontLogClockChangeTooBig=false;
|
|
|
|
// update the time remaining
|
|
// we want to update as soon as possible. Delay some, so providers can collect data.
|
|
g_state.tpPollDelayRemaining=((unsigned __int64)(((DWORD)1)<<g_state.nPollInterval))*10000000;
|
|
g_state.tpTimeSinceLastSyncAttempt=0;
|
|
g_state.tpTimeSinceLastGoodSync=0;
|
|
g_state.tpIrregularDelayRemaining=MINIMUMIRREGULARINTERVAL; // 16s
|
|
// If the minimum poll interval is small, use it the regular interval instead
|
|
if (g_state.tpPollDelayRemaining<g_state.tpIrregularDelayRemaining
|
|
|| (g_state.tpPollDelayRemaining-g_state.tpIrregularDelayRemaining)<=MINIMUMIRREGULARINTERVAL) {
|
|
g_state.tpIrregularDelayRemaining=0; // zero means no irregular sync
|
|
}
|
|
g_state.eLastSyncResult=e_NoData;
|
|
g_state.eLastRegSyncResult=e_NoData;
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
MODULEPRIVATE HRESULT UpdateTimerQueue2() {
|
|
BOOL bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
HRESULT hr2;
|
|
|
|
_BeginTryWith(hr) {
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
// keep track of how long we have left to wait
|
|
unsigned __int64 teManagerWaitStop;
|
|
AccurateGetSystemTime(&teManagerWaitStop);
|
|
if (teManagerWaitStop>g_state.teManagerWaitStart) {
|
|
unsigned __int64 tpManagerWait=teManagerWaitStop-g_state.teManagerWaitStart;
|
|
if (tpManagerWait<g_state.tpPollDelayRemaining) {
|
|
g_state.tpPollDelayRemaining-=tpManagerWait;
|
|
} else {
|
|
g_state.tpPollDelayRemaining=0;
|
|
}
|
|
if (0!=g_state.tpIrregularDelayRemaining) {
|
|
if (tpManagerWait<g_state.tpIrregularDelayRemaining) {
|
|
g_state.tpIrregularDelayRemaining-=tpManagerWait-1; // never goes to zero due to timeout
|
|
} else {
|
|
g_state.tpIrregularDelayRemaining=1; // never goes to zero due to timeout
|
|
}
|
|
}
|
|
g_state.tpTimeSinceLastSyncAttempt+=tpManagerWait;
|
|
g_state.tpTimeSinceLastGoodSync+=tpManagerWait;
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "UpdateTimerQueue2: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
MODULEPRIVATE void WINAPI HandleRefreshTickCount(PVOID pvIgnored, BOOLEAN bIgnored) {
|
|
HRESULT hr2;
|
|
unsigned __int64 qw;
|
|
|
|
// refresh the tick count
|
|
hr2 = AccurateGetTickCountSafe(&qw, false);
|
|
_IgnoreIfError(hr2, "AccurateGetTickCountSafe");
|
|
|
|
// refresh the interrupt count
|
|
hr2 = AccurateGetTickCountSafe(&qw, true);
|
|
_IgnoreIfError(hr2, "AccurateGetTickCountSafe");
|
|
}
|
|
|
|
MODULEPRIVATE void WINAPI HandleTimeout(PVOID pvIgnored, BOOLEAN bIgnored) {
|
|
BOOL bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
HRESULT hr2;
|
|
|
|
_BeginTryWith(hr) {
|
|
|
|
FileLog0(FL_ServiceMainAnnounce, L"W32TmServiceMain: timeout\n");
|
|
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
hr = UpdateTimerQueue2();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue2");
|
|
|
|
// wait time out.
|
|
if (e_LongTimeNoSync==g_state.eTimeoutReason) {
|
|
// this will handle most errors. returned errors are fatal
|
|
hr = HandleManagerGoUnsyncd();
|
|
_JumpIfError(hr, error, "HandleManagerGoUnsyncd");
|
|
} else {
|
|
// this will handle most errors. returned errors are fatal
|
|
hr = HandleManagerGetTimeSamples(0!=g_state.tpIrregularDelayRemaining && g_state.eLastRegSyncResult==e_Success);
|
|
_JumpIfError(hr, error, "HandleManagerGetTimeSamples");
|
|
}
|
|
|
|
hr = UpdateTimerQueue1();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue1");
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "HandleTimeout: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (S_OK != hr) {
|
|
// Errors in this function are fatal.
|
|
hr2 = SendServiceShutdown(hr, TRUE /*restart*/, TRUE /*async*/);
|
|
_IgnoreIfError(hr2, "SendServiceShutdown");
|
|
}
|
|
}
|
|
|
|
// 1) Disables timeout
|
|
// 2)
|
|
MODULEPRIVATE HRESULT UpdateTimerQueue1() {
|
|
BOOL bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
HRESULT hr2;
|
|
unsigned __int64 tpLongTimeNoSync;
|
|
|
|
_BeginTryWith(hr) {
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
// determine what timeout happens first
|
|
g_state.tpWaitInterval=g_state.tpPollDelayRemaining;
|
|
g_state.eTimeoutReason=e_RegularPoll;
|
|
if (0!=g_state.tpIrregularDelayRemaining) {
|
|
g_state.tpWaitInterval=g_state.tpIrregularDelayRemaining;
|
|
g_state.eTimeoutReason=e_IrregularPoll;
|
|
}
|
|
// if we don't synchronize for MAX(1.5 times the maximum interval, NTP.MAXAGE), go unsynchronized.
|
|
//
|
|
tpLongTimeNoSync=((unsigned __int64)(((DWORD)3)<<(g_state.pciConfig->lcci.dwMaxPollInterval-1)))*10000000;
|
|
if (tpLongTimeNoSync < (NtpConst::tpMaxClockAge.qw)) {
|
|
tpLongTimeNoSync = NtpConst::tpMaxClockAge.qw;
|
|
}
|
|
|
|
if (tpLongTimeNoSync<g_state.tpTimeSinceLastGoodSync) {
|
|
g_state.tpWaitInterval=0;
|
|
g_state.eTimeoutReason=e_LongTimeNoSync;
|
|
} else if (tpLongTimeNoSync-g_state.tpTimeSinceLastGoodSync<g_state.tpWaitInterval) {
|
|
g_state.tpWaitInterval=tpLongTimeNoSync-g_state.tpTimeSinceLastGoodSync;
|
|
g_state.eTimeoutReason=e_LongTimeNoSync;
|
|
}
|
|
|
|
// do the wait
|
|
if (e_RegularPoll==g_state.eTimeoutReason) {
|
|
FileLog2(FL_ServiceMainAnnounce, L"W32TmServiceMain: waiting %u.%03us\n",
|
|
(DWORD)(g_state.tpPollDelayRemaining/10000000),
|
|
(DWORD)((g_state.tpPollDelayRemaining/10000)%1000));
|
|
} else if (e_LongTimeNoSync==g_state.eTimeoutReason) {
|
|
FileLog4(FL_ServiceMainAnnounce, L"W32TmServiceMain: waiting ltns%u.%03us (%u.%03us)\n",
|
|
(DWORD)(g_state.tpWaitInterval/10000000),
|
|
(DWORD)((g_state.tpWaitInterval/10000)%1000),
|
|
(DWORD)(g_state.tpPollDelayRemaining/10000000),
|
|
(DWORD)((g_state.tpPollDelayRemaining/10000)%1000));
|
|
} else { //e_IrregularPoll==g_state.eTimeoutReason
|
|
FileLog4(FL_ServiceMainAnnounce, L"W32TmServiceMain: waiting i%u.%03us (%u.%03us)\n",
|
|
(DWORD)(g_state.tpIrregularDelayRemaining/10000000),
|
|
(DWORD)((g_state.tpIrregularDelayRemaining/10000)%1000),
|
|
(DWORD)(g_state.tpPollDelayRemaining/10000000),
|
|
(DWORD)((g_state.tpPollDelayRemaining/10000)%1000));
|
|
}
|
|
AccurateGetSystemTime(&g_state.teManagerWaitStart);
|
|
|
|
// Update the timer queue with the new wait time:
|
|
if (NULL != g_state.hTimer) {
|
|
hr = myChangeTimerQueueTimer(NULL, g_state.hTimer, (DWORD)(g_state.tpWaitInterval/10000), 0xFFFFFF /*shouldn't be used*/);
|
|
if (HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE) == hr) {
|
|
// Someone's modifying the timer now -- either we're shutting down or someone else is using the timer thread.
|
|
// We can ignore this error.
|
|
_IgnoreError(hr, "myChangeTimerQueueTimer");
|
|
} else {
|
|
_JumpIfError(hr, error, "myChangeTimerQueueTimer");
|
|
}
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "UpdateTimerQueue1: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void WINAPI HandleSamplesAvail(LPVOID pvIgnored, BOOLEAN bIgnored) {
|
|
bool bDisallowedShutdown = false;
|
|
bool bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
HRESULT hr2;
|
|
|
|
FileLog0(FL_ServiceMainAnnounce, L"W32TmServiceMain: resync req,");
|
|
|
|
_BeginTryWith(hr) {
|
|
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
hr = UpdateTimerQueue2();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue2");
|
|
|
|
if (g_state.bWaitingForResyncResult) {
|
|
FileLogA0(FL_ServiceMainAnnounce, L" user requested, get samples as soon as possible.\n");
|
|
|
|
// the user is waiting for a response -- get samples as soon as possible
|
|
g_state.tpIrregularDelayRemaining=1;
|
|
} else {
|
|
if (0!=g_state.tpIrregularDelayRemaining) {
|
|
FileLogA0(FL_ServiceMainAnnounce, L" irreg already pending.\n");
|
|
} else {
|
|
// we will never sync more often than every 16s
|
|
// get the minimum interval
|
|
g_state.tpIrregularDelayRemaining=MINIMUMIRREGULARINTERVAL; // 16s
|
|
|
|
// subtract any time we've already waited
|
|
if (g_state.tpTimeSinceLastSyncAttempt>g_state.tpIrregularDelayRemaining) {
|
|
g_state.tpIrregularDelayRemaining=1; // never goes to zero due to timeout
|
|
} else {
|
|
g_state.tpIrregularDelayRemaining-=g_state.tpTimeSinceLastSyncAttempt-1; // never goes to zero due to timeout
|
|
}
|
|
// if it's less than 16s until we do a regular sync,
|
|
// we don't have time to do an irregular sync, so just skip it
|
|
if (g_state.tpIrregularDelayRemaining>g_state.tpPollDelayRemaining
|
|
|| (g_state.tpPollDelayRemaining-g_state.tpIrregularDelayRemaining)<=MINIMUMIRREGULARINTERVAL) {
|
|
g_state.tpIrregularDelayRemaining=0; // zero means no irregular sync
|
|
FileLogA0(FL_ServiceMainAnnounce, L" reg too soon.\n");
|
|
} else {
|
|
FileLogA0(FL_ServiceMainAnnounce, L" irreg now pending.\n");
|
|
}
|
|
} // <- end if irregular update needs to be scheduled
|
|
}
|
|
|
|
hr = UpdateTimerQueue1();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue1");
|
|
|
|
hr = AllowShutdown(false);
|
|
_JumpIfError(hr, error, "AllowShutdown");
|
|
bDisallowedShutdown = true;
|
|
|
|
// We've got to deregister this callback, even though it's only
|
|
// a WT_EXECUTEONLYONCE callback.
|
|
if (NULL != g_state.hRegisteredSamplesAvailEvent) {
|
|
if (!UnregisterWaitEx(g_state.hRegisteredSamplesAvailEvent, 0 /*don't wait*/)) {
|
|
// Should just be a resource leak if we can't unregister this event.
|
|
_IgnoreLastError("UnregisterWait");
|
|
}
|
|
g_state.hRegisteredSamplesAvailEvent = NULL;
|
|
}
|
|
|
|
// Re-register the wait on our samples-avail event.
|
|
if (!RegisterWaitForSingleObject(&g_state.hRegisteredSamplesAvailEvent, g_state.hSamplesAvailEvent, HandleSamplesAvail, NULL, INFINITE, WT_EXECUTEONLYONCE)) {
|
|
_JumpLastError(hr, error, "RegisterWaitForSingleObject");
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "HandleSamplesAvail: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (bDisallowedShutdown) {
|
|
hr2 = AllowShutdown(true);
|
|
_TeardownError(hr, hr2, "AllowShutdown");
|
|
}
|
|
|
|
// return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE DWORD WINAPI HandleSetProviderStatus(PVOID pvSetProviderStatusInfo) {
|
|
bool bDisallowedShutdown = false;
|
|
bool bEnteredCriticalSection = false;
|
|
bool bUpdateSystemStratum;
|
|
HRESULT hr;
|
|
SetProviderStatusInfo *pspsi = static_cast<SetProviderStatusInfo *>(pvSetProviderStatusInfo);
|
|
TimeProvider *ptp = NULL;
|
|
|
|
_BeginTryWith(hr) {
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection=true;
|
|
|
|
hr = AllowShutdown(false);
|
|
_JumpIfError(hr, error, "AllowShutdown");
|
|
bDisallowedShutdown = true;
|
|
|
|
// BUG 631722: Don't log anything until we know the service isn't shutting down.
|
|
// BUGBUG: Note that in checked builds, the above _JumpIfError() statements could AV. Nice to fix,
|
|
// but it'll never AV in free builds.
|
|
FileLog0(FL_ServiceMainAnnounce, L"W32TmServiceMain: provider status update request: ");
|
|
|
|
// Search for the provider that requested a stratum change:
|
|
for (ptp = g_state.pciConfig->ptpProviderList; NULL != ptp; ptp = ptp->ptpNext) {
|
|
if (0 == wcscmp(pspsi->wszProvName, ptp->wszProvName)) {
|
|
// We've found the provider which made the callback
|
|
break;
|
|
}
|
|
}
|
|
|
|
// provider not found
|
|
if (NULL == ptp) {
|
|
FileLogA0(FL_ServiceMainAnnounce, L"provider not found.\n");
|
|
hr = E_INVALIDARG;
|
|
_JumpError(hr, error, "Provider not found");
|
|
}
|
|
|
|
if (TPS_Error == pspsi->tpsCurrentState) {
|
|
FileLogA2(FL_ServiceMainAnnounce, L" <%s, %d, TPS_Error>\n", ptp->wszProvName, pspsi->dwStratum);
|
|
|
|
// The provider has encountered an error it cannot recover from.
|
|
// 1) Stop the provider
|
|
hr = StopProvider(ptp);
|
|
if (FAILED(hr)) {
|
|
_IgnoreError(hr, "HandleSetProviderStatus: StopProvider");
|
|
FileLog1(FL_ServiceMainAnnounce, L"Couldn't stop provider: %s\n", ptp->wszProvName);
|
|
}
|
|
|
|
// 2) Delete it from our provider list, and report the error:
|
|
hr = RemoveProviderFromList(ptp);
|
|
if (FAILED(hr)) {
|
|
_IgnoreError(hr, "HandleSetProviderStatus: RemoveProviderFromList");
|
|
FileLog1(FL_ServiceMainAnnounce, L"Couldn't remove provider from list: %s\n", ptp->wszProvName);
|
|
}
|
|
} else if (TPS_Running == pspsi->tpsCurrentState) {
|
|
// The provider is still running, now set other status information for the provider:
|
|
|
|
// 1) Set the provider stratum
|
|
|
|
// Don't allow the provider to set its stratum to a value BETTER than the
|
|
// best sample it has provided.
|
|
if (0 != pspsi->dwStratum && ptp->dwStratum > pspsi->dwStratum) {
|
|
FileLogA1(FL_ServiceMainAnnounce, L"stratum too low (best provider stratum == %d).\n", ptp->dwStratum);
|
|
hr = E_INVALIDARG;
|
|
_JumpError(hr, error, "Stratum too low");
|
|
}
|
|
|
|
FileLogA2(FL_ServiceMainAnnounce, L"<%s, %d, TPS_Running>\n", ptp->wszProvName, pspsi->dwStratum);
|
|
|
|
// Update the provider with the new stratum information:
|
|
ptp->dwStratum = pspsi->dwStratum;
|
|
|
|
// Check if we need to update the system stratum.
|
|
// The system stratum will be updated iff the providers new stratum
|
|
// is superior all other provider's stratums, and inferior to the
|
|
// current system stratum.
|
|
//
|
|
if (e_ClockNotSynchronized == g_state.eLeapIndicator ||
|
|
(0 != pspsi->dwStratum && g_state.nStratum >= pspsi->dwStratum)) {
|
|
// The new stratum is superior to the system stratum --
|
|
// the system stratum will not be updated.
|
|
bUpdateSystemStratum = false;
|
|
} else {
|
|
bUpdateSystemStratum = true;
|
|
for (ptp = g_state.pciConfig->ptpProviderList; NULL != ptp; ptp = ptp->ptpNext) {
|
|
if (0 != ptp->dwStratum &&
|
|
(0 == pspsi->dwStratum || pspsi->dwStratum > ptp->dwStratum)) {
|
|
// The new stratum is NOT superior to this provider's stratum, do not update
|
|
// the system stratum.
|
|
bUpdateSystemStratum = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bUpdateSystemStratum) {
|
|
FileLog2(FL_ServiceMainAnnounce, L"***System stratum updated***, %d --> %d", g_state.nStratum, pspsi->dwStratum);
|
|
g_state.nStratum = pspsi->dwStratum;
|
|
if (0 == g_state.nStratum) {
|
|
// We've reset our system stratum to 0 -- this means we're not synchronized.
|
|
g_state.eLeapIndicator = e_ClockNotSynchronized;
|
|
}
|
|
} else {
|
|
FileLog1(FL_ServiceMainAnnounce, L"System stratum not updated: %d\n", g_state.nStratum);
|
|
}
|
|
} else {
|
|
FileLogA1(FL_ServiceMainAnnounce, L"bad provider status code, %d\n", pspsi->tpsCurrentState);
|
|
hr = E_INVALIDARG;
|
|
_JumpError(hr, error, "bad provider status code");
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "HandleSetProviderStatus: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
HRESULT hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (bDisallowedShutdown) {
|
|
HRESULT hr2 = AllowShutdown(true);
|
|
_TeardownError(hr, hr2, "AllowShutdown");
|
|
}
|
|
if (NULL != pspsi) {
|
|
// We're done, write the result of the operations,
|
|
// and signal completion if caller passed us an event handle:
|
|
if (NULL != pspsi->pHr) {
|
|
*(pspsi->pHr) = hr;
|
|
}
|
|
if (NULL != pspsi->pdwSysStratum) {
|
|
*(pspsi->pdwSysStratum) = g_state.nStratum;
|
|
}
|
|
if (NULL != pspsi->hWaitEvent) {
|
|
if (!SetEvent(pspsi->hWaitEvent)) {
|
|
_IgnoreError(HRESULT_FROM_WIN32(GetLastError()), "SetEvent");
|
|
}
|
|
}
|
|
// Use the callback deallocation function to free the input param
|
|
pspsi->pfnFree(pspsi);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void HandleDomHierRoleChangeEvent(LPVOID pvHR, BOOLEAN bIgnored) {
|
|
bool bEnteredCriticalSection = false;
|
|
bool bIsDomainRoot;
|
|
BOOL bPdcInSite;
|
|
DSROLE_PRIMARY_DOMAIN_INFO_BASIC *pDomInfo = NULL;
|
|
DWORD dwErr;
|
|
HRESULT hr;
|
|
LPWSTR pwszParentDomName = NULL;
|
|
NTSTATUS ntStatus;
|
|
|
|
FileLog0(FL_DomHierAnnounce, L" DomainHierarchy: LSA role change notification. Redetecting.\n");
|
|
|
|
_BeginTryWith(hr) {
|
|
dwErr = DsRoleGetPrimaryDomainInformation(NULL, DsRolePrimaryDomainInfoBasic, (BYTE **)&pDomInfo);
|
|
if (ERROR_SUCCESS != dwErr) {
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
_JumpError(hr, error, "DsRoleGetPrimaryDomainInformation");
|
|
}
|
|
|
|
// Update our domain role in the global state structure. Note that this is an atomic DWORD assignment:
|
|
g_state.eMachineRole = pDomInfo->MachineRole;
|
|
|
|
|
|
if (DsRole_RoleStandaloneWorkstation != pDomInfo->MachineRole &&
|
|
DsRole_RoleStandaloneServer != pDomInfo->MachineRole) {
|
|
|
|
// w32time depends on netlogon (if not in the standalone case). Wait 90 seconds for netlogon to start.
|
|
ntStatus = NlWaitForNetlogon(WAITHINT_WAITFORNETLOGON);
|
|
if (!NT_SUCCESS(ntStatus)) {
|
|
hr = HRESULT_FROM_WIN32(RtlNtStatusToDosError(ntStatus));
|
|
_JumpError(hr, error, "NlWaitForNetlogon");
|
|
}
|
|
}
|
|
|
|
// If we're a DC, determine if we're the domain root:
|
|
if (DsRole_RoleBackupDomainController == pDomInfo->MachineRole ||
|
|
DsRole_RolePrimaryDomainController == pDomInfo->MachineRole) {
|
|
|
|
dwErr = NetLogonGetTimeServiceParentDomain(NULL, &pwszParentDomName, &bPdcInSite);
|
|
if (ERROR_SUCCESS!=dwErr && ERROR_NO_SUCH_DOMAIN != dwErr) {
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
_JumpError(hr, error, "NetLogonGetTimeServiceParentDomain");
|
|
}
|
|
|
|
bIsDomainRoot = ((DsRole_RolePrimaryDomainController == pDomInfo->MachineRole) &&
|
|
(NULL == pwszParentDomName));
|
|
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
if (bIsDomainRoot != g_state.bIsDomainRoot) {
|
|
// The PDC role in the root domain has changed. Update whether we are a reliable time service.
|
|
g_state.bIsDomainRoot = bIsDomainRoot;
|
|
hr = UpdateNetlogonServiceBits(false /*reliable only*/);
|
|
_JumpIfError(hr, error, "UpdateNetlogonServiceBits");
|
|
|
|
if (bIsDomainRoot) {
|
|
FileLog0(FL_DomHierAnnounce, L" DomainHierarchy: we are now the domain root. Should be advertised as reliable\n");
|
|
} else {
|
|
FileLog0(FL_DomHierAnnounce, L" DomainHierarchy: we are no longer the domain root. Should NOT be advertised as reliable\n");
|
|
}
|
|
}
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "HandleDomHierRoleChangeEvent: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
HRESULT hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (NULL!=pDomInfo) { DsRoleFreeMemory(pDomInfo); }
|
|
if (NULL!=pwszParentDomName) { NetApiBufferFree(pwszParentDomName); }
|
|
|
|
// Couldn't process the role change, this is a fatal error
|
|
if (FAILED(hr)) {
|
|
// Called by the thread pool -- asynchronously shut down the service:
|
|
if (NULL == pvHR) {
|
|
HRESULT hr2 = SendServiceShutdown(hr, TRUE /*restart*/, TRUE /*async*/);
|
|
_IgnoreIfError(hr2, "SendServiceShutdown");
|
|
} else {
|
|
// called by the main thread -- can't call shutdown here. Just return the HR.
|
|
*((HRESULT *)pvHR) = hr;
|
|
}
|
|
}
|
|
// return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void HandleManagerTimeSlip(LPVOID pvIgnored, BOOLEAN bIgnored) {
|
|
BOOL bEnteredCriticalSection = false;
|
|
HRESULT hr;
|
|
HRESULT hr2;
|
|
TpcTimeJumpedArgs tjArgs;
|
|
|
|
_BeginTryWith(hr) {
|
|
FileLog0(FL_TimeSlipAnnounce, L"W32TmServiceMain: ********** Time Slip Notification **********\n");
|
|
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
hr = UpdateTimerQueue2();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue2");
|
|
|
|
// tell the local clock and the providers and update the timeouts
|
|
if (NULL == pvIgnored) {
|
|
tjArgs.tjfFlags = TJF_Default;
|
|
} else {
|
|
tjArgs = *((TpcTimeJumpedArgs *)pvIgnored);
|
|
}
|
|
hr=HandleManagerHardResync(TPC_TimeJumped, &tjArgs);
|
|
_JumpIfError(hr, error, "HandleManagerHardResync");
|
|
|
|
hr = UpdateTimerQueue1();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue1");
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "HandleManagerTimeSlip: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (S_OK != hr) {
|
|
// Errors in this function are fatal.
|
|
hr2 = SendServiceShutdown(hr, TRUE /*restart*/, TRUE /*async*/);
|
|
_IgnoreIfError(hr2, "SendServiceShutdown");
|
|
}
|
|
// return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// BUGBUG: this method requires that g_state.csW32time is held
|
|
// when called. Currently, this is always the case. Going forward,
|
|
// we should add code to ensure this continues to be the case.
|
|
//
|
|
MODULEPRIVATE HRESULT RequestNetTopoChangeNotification(void) {
|
|
HRESULT hr;
|
|
|
|
// get notified whenever a change occurs in the table that maps IP addresses to interfaces.
|
|
// Essentially, we're making an overlapped call to DeviceIORequest.
|
|
ZeroMemory(&g_state.olNetTopoIOOverlapped, sizeof(OVERLAPPED));
|
|
g_state.olNetTopoIOOverlapped.hEvent=g_state.hNetTopoChangeEvent;
|
|
hr=NotifyAddrChange(&g_state.hNetTopoIOHandle, &g_state.olNetTopoIOOverlapped);
|
|
_Verify(NO_ERROR!=hr, hr, error);
|
|
|
|
if (ERROR_OPEN_FAILED == hr) {
|
|
// Probably just don't have TCP/IP installed -- we should still be able to sync
|
|
// from a HW prov. Should we try to redected network?
|
|
HRESULT hr2 = MyLogEvent(EVENTLOG_WARNING_TYPE, MSG_TCP_NOT_INSTALLED, 0, NULL);
|
|
_IgnoreIfError(hr2, "MyLogEvent");
|
|
|
|
// No reason for our default network providers to run anymore -- shut them down:
|
|
RemoveDefaultProvidersFromList();
|
|
|
|
// Returned errors are fatal -- we can recover from this error, so log an event
|
|
// and move on.
|
|
hr = S_OK;
|
|
goto error;
|
|
}
|
|
|
|
if (ERROR_IO_PENDING!=hr) {
|
|
hr=HRESULT_FROM_WIN32(hr);
|
|
_JumpError(hr, error, "NotifyAddrChange");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT StopNetTopoChangeNotification(void) {
|
|
return S_OK;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT HandleManagerNetTopoChange(bool bRpc) {
|
|
bool bProcessNetTopoChange = false;
|
|
bool bDisallowedShutdown = false;
|
|
bool bEnteredCriticalSection = false;
|
|
DWORD dwIgnored;
|
|
HRESULT hr;
|
|
HRESULT hr2;
|
|
TpcNetTopoChangeArgs ntcArgs = { NTC_Default };
|
|
|
|
_BeginTryWith(hr) {
|
|
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
hr = UpdateTimerQueue2();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue2");
|
|
|
|
if (bRpc) {
|
|
FileLog0(FL_NetTopoChangeAnnounce, L"W32TmServiceMain: Network Topology Change (RPC)\n");
|
|
// The user requested this net topo change
|
|
ntcArgs.ntcfFlags = NTC_UserRequested;
|
|
// Always process user-requested changes
|
|
bProcessNetTopoChange = true;
|
|
} else {
|
|
// If GetOverlappedResult() returns TRUE, this indicates a change in the IP address table.
|
|
// (BUGBUG: verify behavior of GetOverlappedResult in all cases)
|
|
if (GetOverlappedResult(g_state.hNetTopoIOHandle, &g_state.olNetTopoIOOverlapped, &dwIgnored, TRUE)) {
|
|
FileLog0(FL_NetTopoChangeAnnounce, L"W32TmServiceMain: Network Topology Change\n");
|
|
bProcessNetTopoChange = true;
|
|
}
|
|
|
|
// We've received this message
|
|
if (!ResetEvent(g_state.hNetTopoChangeEvent)) {
|
|
_JumpLastError(hr, error, "ResetEvent");
|
|
}
|
|
|
|
// We can't mess with our registered callbacks FROM a callback
|
|
// if we're shutting down!
|
|
hr = AllowShutdown(false);
|
|
_JumpIfError(hr, error, "AllowShutdown");
|
|
bDisallowedShutdown = true;
|
|
|
|
// The registered event handle could be NULL if registering the
|
|
// net topo change handler failed:
|
|
if (NULL != g_state.hRegisteredNetTopoChangeEvent) {
|
|
if (!UnregisterWaitEx(g_state.hRegisteredNetTopoChangeEvent, 0 /*don't wait*/)) {
|
|
// Should just be a resource leak if we can't unregister this event.
|
|
_IgnoreLastError("UnregisterWait");
|
|
}
|
|
g_state.hRegisteredNetTopoChangeEvent = NULL;
|
|
}
|
|
|
|
if (!RegisterWaitForSingleObject(&g_state.hRegisteredNetTopoChangeEvent, g_state.hNetTopoChangeEvent, HandleManagerNetTopoChangeNoRPC, NULL, INFINITE, WT_EXECUTEONLYONCE)) {
|
|
_JumpLastError(hr, error, "RegisterWaitForSingleObject");
|
|
}
|
|
|
|
// We can't mess with our registered callbacks FROM a callback
|
|
// if we're shutting down!
|
|
hr = AllowShutdown(true);
|
|
_JumpIfError(hr, error, "AllowShutdown");
|
|
bDisallowedShutdown = false;
|
|
|
|
// request a notification of the next change
|
|
hr=RequestNetTopoChangeNotification();
|
|
_JumpIfError(hr, error, "RequestNetTopoChangeNotification");
|
|
}
|
|
|
|
if (bProcessNetTopoChange) {
|
|
// tell the local clock and the providers and update the timeouts.
|
|
// Errors in this function are fatal, as we won't be able to serve time
|
|
// if we can't process a net topo change.
|
|
TimeProvider * ptpTravel;
|
|
for (ptpTravel=g_state.pciConfig->ptpProviderList; NULL!=ptpTravel; ptpTravel=ptpTravel->ptpNext) {
|
|
hr=SendNotificationToProvider(ptpTravel, TPC_NetTopoChange, &ntcArgs);
|
|
_JumpIfError(hr, error, "SendNotificationToProvider");
|
|
}
|
|
|
|
// Since we're going to have to rediscover our network sources,
|
|
// clear our event log cache:
|
|
hr = MyResetSourceChangeLog();
|
|
_JumpIfError(hr, error, "MyResetSourceChangeLog");
|
|
}
|
|
|
|
hr = UpdateTimerQueue1();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue1");
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "HandleManagerNetTopoChange: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (bDisallowedShutdown) {
|
|
hr2 = AllowShutdown(true);
|
|
_TeardownError(hr, hr2, "AllowShutdown");
|
|
}
|
|
if (FAILED(hr) && W32TIME_ERROR_SHUTDOWN != hr) {
|
|
// Returned errors are fatal:
|
|
HRESULT hr2 = SendServiceShutdown(hr, TRUE /*restart*/, TRUE /*async*/);
|
|
_IgnoreIfError(hr2, "SendServiceShutdown");
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void WINAPI HandleManagerNetTopoChangeNoRPC(LPVOID pvIgnored, BOOLEAN bIgnored) {
|
|
HandleManagerNetTopoChange(FALSE);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE void HandleManagerSystemShutdown(void) {
|
|
HRESULT hr;
|
|
|
|
// Perform critical cleanup operations. We don't need to be in a good
|
|
// state after this method returns, as the system is shutting down.
|
|
|
|
// 1) See if we're already shutting down. We don't want multiple
|
|
// shutdowns to occur at the same time.
|
|
hr = StartShutdown();
|
|
_JumpIfError(hr, error, "StartShutdown");
|
|
|
|
FileLog0(FL_ServiceMainAnnounce, L"Beginning System Shutdown\n");
|
|
|
|
// 2) try our best to restore control to the cmos clock, by shutting
|
|
// down the clock discipline thread. This is important, as
|
|
// the software clock may have a very different reading than the
|
|
// cmos clock, and failing to do so may give us bad time on the next
|
|
// boot.
|
|
if (!SetEvent(g_state.hShutDownEvent)) {
|
|
_IgnoreLastError("SetEvent");
|
|
} else {
|
|
if (-1 == WaitForSingleObject(g_state.hClockDisplnThread, INFINITE)) {
|
|
_IgnoreLastError("WaitForSingleObject");
|
|
}
|
|
}
|
|
|
|
// 3) inform our providers that a system shutdown is occuring
|
|
if (NULL != g_state.pciConfig) {
|
|
for (TimeProvider *ptpList=g_state.pciConfig->ptpProviderList; NULL!=ptpList; ptpList=ptpList->ptpNext) {
|
|
// tell the provider to shut down.
|
|
HRESULT hr = ptpList->pfnTimeProvCommand(ptpList->hTimeProv, TPC_Shutdown, NULL);
|
|
_IgnoreIfError(hr, "ptpList->pfnTimeProvCommand: TPC_Shutdown");
|
|
}
|
|
}
|
|
|
|
FileLog0(FL_ServiceMainAnnounce, L"Exiting System Shutdown\n");
|
|
|
|
if (NULL!=g_servicestatushandle) {
|
|
// WARNING: The process may be killed after we report we are stopped
|
|
// even though this thread has not exited. Thus, the file log
|
|
// must be closed before this call.
|
|
MySetServiceStopped(0);
|
|
}
|
|
|
|
error:;
|
|
// return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT StartOrStopTimeSlipNotification(bool bStart) {
|
|
HRESULT hr;
|
|
const unsigned int nPrivileges=1;
|
|
|
|
// must be cleaned up
|
|
HANDLE hProcToken=NULL;
|
|
TOKEN_PRIVILEGES * ptp=NULL;
|
|
bool bPrivilegeChanged=false;
|
|
|
|
// get the token for our process
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcToken)) {
|
|
_JumpLastError(hr, error, "OpenProcessToken");
|
|
}
|
|
|
|
// allocate the list of privileges
|
|
ptp=(TOKEN_PRIVILEGES *)LocalAlloc(LPTR, sizeof(TOKEN_PRIVILEGES)+(nPrivileges-ANYSIZE_ARRAY)*sizeof(LUID_AND_ATTRIBUTES));
|
|
_JumpIfOutOfMemory(hr, error, ptp);
|
|
|
|
// fill in the list of privileges
|
|
ptp->PrivilegeCount=nPrivileges;
|
|
|
|
// we need the system clock changing privelege to change who will be notified of time slip events.
|
|
if (!LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &(ptp->Privileges[0].Luid))) {
|
|
_JumpLastError(hr, error, "LookupPrivilegeValue");
|
|
}
|
|
ptp->Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
|
|
|
|
// make the requested privilege change
|
|
if (!AdjustTokenPrivileges(hProcToken, FALSE, ptp, 0, NULL, 0)) {
|
|
_JumpLastError(hr, error, "AdjustTokenPrivileges");
|
|
}
|
|
bPrivilegeChanged=true;
|
|
|
|
if (true==bStart) {
|
|
hr=SetTimeSlipEvent(g_state.hTimeSlipEvent);
|
|
} else {
|
|
hr=SetTimeSlipEvent(NULL);
|
|
}
|
|
if (ERROR_SUCCESS!=hr) {
|
|
hr=HRESULT_FROM_WIN32(LsaNtStatusToWinError(hr));
|
|
_JumpError(hr, error, "SetTimeSlipEvent");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (true==bPrivilegeChanged) {
|
|
// don't need this special privilege any more
|
|
ptp->Privileges[0].Attributes=0;
|
|
|
|
// make the requested privilege change
|
|
if (!AdjustTokenPrivileges(hProcToken, FALSE, ptp, 0, NULL, 0)) {
|
|
HRESULT hr2=HRESULT_FROM_WIN32(GetLastError());
|
|
_TeardownError(hr, hr2, "AdjustTokenPrivileges");
|
|
}
|
|
}
|
|
if (NULL!=hProcToken) {
|
|
CloseHandle(hProcToken);
|
|
}
|
|
if (NULL!=ptp) {
|
|
LocalFree(ptp);
|
|
}
|
|
return hr;
|
|
|
|
}
|
|
|
|
//====================================================================
|
|
// RPC routines
|
|
|
|
//--------------------------------------------------------------------------------
|
|
//
|
|
// The security callback (called by the RPC runtime), determines whether
|
|
// or not an RPC client has access to call the w32time RPC interface.
|
|
// We'll allow the following groups to access the RPC interface:
|
|
//
|
|
// 1) anyone with SeSystemTimePrivilege
|
|
// 2) local administrators
|
|
// 3) domain administrators
|
|
// 4) the system account
|
|
//
|
|
// All others are denied access
|
|
//
|
|
long __stdcall W32TimeSecurityCallback(void * Interface, void *Context)
|
|
{
|
|
BOOL bAllowAccess = FALSE;
|
|
BOOL bImpersonatingClient = FALSE;
|
|
DWORD cbPrivilegeSet;
|
|
DWORD dwGrantedAccess;
|
|
HANDLE hClientToken = NULL;
|
|
HRESULT hr;
|
|
PRIVILEGE_SET privilegeSet;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
RpcStatus = RpcImpersonateClient(NULL);
|
|
if (RPC_S_OK != RpcStatus) {
|
|
hr = HRESULT_FROM_WIN32(RpcStatus);
|
|
_JumpError(hr, error, "RpcImpersonateClient");
|
|
}
|
|
bImpersonatingClient = TRUE;
|
|
|
|
// get our impersonated token
|
|
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hClientToken)) {
|
|
_JumpLastError(hr, error, "OpenThreadToken");
|
|
}
|
|
|
|
// log the caller.
|
|
// SECURITY: may move this call to AFTER the client has been validated
|
|
if (FileLogAllowEntry(FL_RpcAnnounce)) {
|
|
DumpRpcCaller(hClientToken);
|
|
}
|
|
|
|
// see if this caller has time setting privileges
|
|
if (!PrivilegeCheck(hClientToken, g_state.ppsRequiredPrivs, &bAllowAccess)) {
|
|
_JumpLastError(hr, error, "PrivilegeCheck");
|
|
}
|
|
|
|
if (!bAllowAccess) {
|
|
hr = E_ACCESSDENIED;
|
|
_JumpError(hr, error, "W32TimeSecurityCallback: access denied");
|
|
}
|
|
|
|
hr = RPC_S_OK;
|
|
error:
|
|
if (NULL != hClientToken) {
|
|
CloseHandle(hClientToken);
|
|
}
|
|
if (bImpersonatingClient) {
|
|
RpcRevertToSelf();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT W32TmStartRpcServer(void) {
|
|
HRESULT hr;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// must be cleaned up
|
|
WCHAR * wszServerPrincipalName=NULL;
|
|
|
|
// Tell the RPC runtime we want to serve requests over 1) LRPC and 2) named pipes
|
|
RpcStatus=RpcServerUseProtseqEp(L"ncalrpc", RPC_C_PROTSEQ_MAX_REQS_DEFAULT , wszW32TimeOwnProcRpcEndpointName, NULL);
|
|
if (RPC_S_OK!=RpcStatus && RPC_S_DUPLICATE_ENDPOINT!=RpcStatus) {
|
|
hr=HRESULT_FROM_WIN32(RpcStatus);
|
|
_JumpError(hr, error, "RpcServerUseProtseqEp");
|
|
}
|
|
|
|
RpcStatus=RpcServerUseProtseqEp(L"ncacn_np", RPC_C_PROTSEQ_MAX_REQS_DEFAULT , L"\\PIPE\\" wszW32TimeOwnProcRpcEndpointName, NULL);
|
|
if (RPC_S_OK!=RpcStatus && RPC_S_DUPLICATE_ENDPOINT!=RpcStatus) {
|
|
hr=HRESULT_FROM_WIN32(RpcStatus);
|
|
_JumpError(hr, error, "RpcServerUseProtseqEp");
|
|
}
|
|
|
|
// register our interface.
|
|
// NOTE: we must specify the AUTOLISTEN flag. Failing to do so may cause svchost to shut down our interface
|
|
// unexpectedly through RpcMgmtStopServerListening(). See MSDN.
|
|
RpcStatus = RpcServerRegisterIfEx(s_W32Time_v4_1_s_ifspec, NULL, NULL, RPC_IF_AUTOLISTEN | RPC_IF_ALLOW_SECURE_ONLY, RPC_C_PROTSEQ_MAX_REQS_DEFAULT, W32TimeSecurityCallback);
|
|
if (RPC_S_OK!=RpcStatus) {
|
|
hr=HRESULT_FROM_WIN32(RpcStatus);
|
|
_JumpError(hr, error, "RpcServerRegisterIf");
|
|
}
|
|
|
|
// The RPC server is now active.
|
|
g_state.bRpcServerStarted=true;
|
|
|
|
// allow clients to make authenticated requests
|
|
RpcStatus=RpcServerInqDefaultPrincName(RPC_C_AUTHN_GSS_NEGOTIATE, &wszServerPrincipalName);
|
|
if (RPC_S_OK!=RpcStatus) {
|
|
hr=HRESULT_FROM_WIN32(RpcStatus);
|
|
_JumpError(hr, error, "RpcServerListen");
|
|
}
|
|
RpcStatus=RpcServerRegisterAuthInfo(wszServerPrincipalName, RPC_C_AUTHN_GSS_NEGOTIATE, NULL, NULL);
|
|
if (RPC_S_OK!=RpcStatus) {
|
|
hr=HRESULT_FROM_WIN32(RpcStatus);
|
|
_JumpError(hr, error, "RpcServerListen");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (NULL!=wszServerPrincipalName) {
|
|
RpcStringFree(&wszServerPrincipalName);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT W32TmStopRpcServer(void) {
|
|
HRESULT hr;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
// shut down any pending resync requests
|
|
// open up both gates
|
|
g_state.eLastSyncResult=e_Shutdown;
|
|
if (!SetEvent(g_state.hRpcSyncCompleteAEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
if (!SetEvent(g_state.hRpcSyncCompleteBEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
|
|
// stop listening on our interface, and wait for calls to complete
|
|
RpcStatus=RpcServerUnregisterIf(s_W32Time_v4_1_s_ifspec, NULL, TRUE);
|
|
if (RPC_S_OK!=RpcStatus) {
|
|
hr=HRESULT_FROM_WIN32(RpcStatus);
|
|
_JumpError(hr, error, "RpcServerUnregisterIf");
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT DumpRpcCaller(HANDLE hToken) {
|
|
HRESULT hr;
|
|
WCHAR wszName[1024];
|
|
WCHAR wszDomain[1024];
|
|
DWORD dwSize;
|
|
DWORD dwSize2;
|
|
SID_NAME_USE SidType;
|
|
WCHAR * wszEnable;
|
|
|
|
// must be cleaned up
|
|
TOKEN_USER * pTokenUser=NULL;
|
|
WCHAR * wszSid=NULL;
|
|
|
|
// Call GetTokenInformation to get the buffer size.
|
|
_Verify(!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize), hr, error);
|
|
if (GetLastError()!=ERROR_INSUFFICIENT_BUFFER) {
|
|
_JumpLastError(hr, error, "GetTokenInformation");
|
|
}
|
|
|
|
// Allocate the buffer.
|
|
pTokenUser=(TOKEN_USER *)LocalAlloc(LPTR, dwSize);
|
|
_JumpIfOutOfMemory(hr, error, pTokenUser);
|
|
|
|
// Call GetTokenInformation again to get the group information.
|
|
if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize)) {
|
|
_JumpLastError(hr, error, "GetTokenInformation");
|
|
}
|
|
|
|
// Lookup the account name and print it.
|
|
dwSize=ARRAYSIZE(wszName);
|
|
dwSize2=ARRAYSIZE(wszDomain);
|
|
if (!LookupAccountSid(NULL, pTokenUser->User.Sid, wszName, &dwSize, wszDomain, &dwSize2, &SidType ) ) {
|
|
hr=GetLastError();
|
|
if (ERROR_NONE_MAPPED==hr) {
|
|
wcscpy(wszName, L"NONE_MAPPED");
|
|
} else {
|
|
_JumpLastError(hr, error, "LookupAccountSid");
|
|
}
|
|
}
|
|
|
|
if (!ConvertSidToStringSid(pTokenUser->User.Sid, &wszSid)) {
|
|
_JumpLastError(hr, error, "ConvertSidToStringSid");
|
|
}
|
|
|
|
FileLog3(FL_RpcAnnounce, L"RPC Caller is %s\\%s (%s)\n", wszDomain, wszName, wszSid);
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (NULL!=pTokenUser) {
|
|
LocalFree(pTokenUser);
|
|
}
|
|
if (NULL!=wszSid) {
|
|
LocalFree(wszSid);
|
|
}
|
|
if (FAILED(hr)) {
|
|
FileLog1(FL_RpcAnnounce, L"*** Couldn't dump RPC caller. The error was: %d\n", hr);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
extern "C" DWORD s_W32TimeSync(handle_t hHandle, ULONG ulWait, ULONG ulFlags) {
|
|
DWORD dwWaitTimeout = INFINITE;
|
|
HRESULT hr;
|
|
|
|
_BeginTryWith(hr) {
|
|
// alert the manager as to what type of resync we want to do
|
|
if (0!=(ulFlags&TimeSyncFlag_Rediscover)) {
|
|
FileLog0(FL_RpcAnnounce, L"RPC Call - Rediscover\n");
|
|
hr = HandleManagerNetTopoChange(TRUE /*rpc*/);
|
|
_JumpIfError(hr, error, "HandleManagerNetTopoChange");
|
|
|
|
// Let the manager know that we want samples as soon as possible
|
|
g_state.bWaitingForResyncResult = true;
|
|
TpcTimeJumpedArgs tjArgs = { TJF_UserRequested };
|
|
HandleManagerTimeSlip(&tjArgs, FALSE);
|
|
} else if (0!=(ulFlags&TimeSyncFlag_HardResync)) {
|
|
FileLog0(FL_RpcAnnounce, L"RPC Call - HardResync\n");
|
|
// Let the manager know that we want samples as soon as possible
|
|
g_state.bWaitingForResyncResult = true;
|
|
TpcTimeJumpedArgs tjArgs = { TJF_UserRequested };
|
|
HandleManagerTimeSlip(&tjArgs, FALSE);
|
|
} else if (0!=(ulFlags&TimeSyncFlag_UpdateAndResync)) {
|
|
FileLog0(FL_RpcAnnounce, L"RPC Call - UpdateAndResync\n");
|
|
// Let the manager know that we want samples as soon as possible
|
|
g_state.bWaitingForResyncResult = true;
|
|
HandleManagerParamChange(NULL /*pvIgnored*/, FALSE /*bIgnored*/);
|
|
dwWaitTimeout = MINIMUMIRREGULARINTERVAL / 10000;
|
|
} else {
|
|
FileLog0(FL_RpcAnnounce, L"RPC Call - SoftResync\n");
|
|
if (!SetEvent(g_state.hSamplesAvailEvent)) {
|
|
_JumpLastError(hr, error, "SetEvent");
|
|
}
|
|
}
|
|
|
|
// wait for resync to complete if so instructed
|
|
if (0!=ulWait) {
|
|
DWORD dwWaitResult = WaitForSingleObject(g_state.hRpcSyncCompleteEvent, dwWaitTimeout);
|
|
if (WAIT_FAILED==dwWaitResult) {
|
|
_JumpLastError(hr, error, "WaitForSingleObject");
|
|
} else if (WAIT_TIMEOUT==dwWaitResult) {
|
|
hr = ResyncResult_NoData;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
// successful
|
|
hr=S_OK;
|
|
if (0!=ulWait && 0!=(ulFlags&TimeSyncFlag_ReturnResult)) {
|
|
hr=g_state.eLastSyncResult;
|
|
}
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "s_W32TimeSync: HANDLED EXCEPTION");
|
|
}
|
|
|
|
error:
|
|
// Let the manager know that we know longer need samples in a timely fashion
|
|
g_state.bWaitingForResyncResult = false;
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
extern "C" unsigned long s_W32TimeQueryProviderStatus(/* [in] */ handle_t hRPCBinding,
|
|
/* [in] */ unsigned __int32 ulFlags,
|
|
/* [in, string] */ wchar_t *pwszProvider,
|
|
/* [in, out] */ PW32TIME_PROVIDER_INFO *pProviderInfo)
|
|
{
|
|
HRESULT hr;
|
|
RPC_STATUS rpcStatus;
|
|
TimeProvider *ptp = NULL;
|
|
|
|
_BeginTryWith(hr) {
|
|
// Search for the provider to query:
|
|
// BUGBUG: should we ensure somewhere that provider names are < 1024?
|
|
for (ptp = g_state.pciConfig->ptpProviderList; NULL != ptp; ptp = ptp->ptpNext) {
|
|
// Don't compare more than 1024 characters (prevents possible DoS attack for very long strings)
|
|
if (0 == _wcsnicmp(pwszProvider, ptp->wszProvName, 1024)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NULL == ptp) {
|
|
// Couldn't find a provider.
|
|
hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
|
|
_JumpError(hr, error, "W32TimeQueryProviderStatus_r: provider not found.");
|
|
}
|
|
|
|
// BUG 581116: check that the provider is started to reduce stress breaks (doesn't really matter,
|
|
// the worst that will happen is we'll catch the AV when we dereference the NULL callback).
|
|
if (!ptp->bStarted) {
|
|
hr = HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_ACTIVE);
|
|
_JumpIfError(hr, error, "s_W32TimeQueryProviderStatus");
|
|
}
|
|
|
|
// We've found a matching provider, dispatch the query command to it.
|
|
hr = ptp->pfnTimeProvCommand(ptp->hTimeProv, TPC_Query, pProviderInfo);
|
|
_JumpIfError(hr, error, "ptp->pfnTimeProvCommand");
|
|
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "s_W32TimeQueryProviderStatus: HANDLED EXCEPTION");
|
|
}
|
|
|
|
hr = S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// Netlogon can call this function and get our service bits if we start
|
|
// before they do. Note that we tell and they ask, and depending upon
|
|
// who started up first one of the two will be succesful. Either way,
|
|
// the flags will be set correctly.
|
|
extern "C" unsigned long s_W32TimeGetNetlogonServiceBits(handle_t hBinding) {
|
|
// assume dword reads and writes are atomic
|
|
return g_state.dwNetlogonServiceBits;
|
|
}
|
|
|
|
//####################################################################
|
|
// module public functions
|
|
|
|
//--------------------------------------------------------------------
|
|
extern "C" void WINAPI W32TmServiceMain(unsigned int nArgs, WCHAR ** rgwszArgs) {
|
|
bool bAllowedShutdown = false;
|
|
bool bBasicInitializationDone = false;
|
|
bool bCloseFileLog = false;
|
|
bool bEnteredCriticalSection = false;
|
|
bool bFreeGlobalState = false;
|
|
bool bFreeShutdownState = false;
|
|
DWORD dwWaitResult;
|
|
HANDLE rghWait[7];
|
|
HRESULT hr;
|
|
unsigned int nCheckpoint;
|
|
|
|
_BeginTryWith(hr) {
|
|
|
|
g_servicestatushandle=fnW32TmRegisterServiceCtrlHandlerEx(wszSERVICENAME, W32TimeServiceCtrlHandler, NULL);
|
|
if (NULL==g_servicestatushandle)
|
|
// Nothing we can do if we can't get a service status handle
|
|
return;
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// do the bare minimum initialization. Keep track of what we've already
|
|
// initialized so we can clean up if any of this fails
|
|
// (we can't call SendServiceShutdown() until at least the basic initialization
|
|
// is complete).
|
|
bFreeShutdownState = true;
|
|
hr=InitShutdownState();
|
|
_JumpIfError(hr, error, "InitShutdownState");
|
|
|
|
hr = AllowShutdown(false); // Don't allow shutdown during initialization
|
|
_JumpIfError(hr, error, "AllowShutdown");
|
|
bAllowedShutdown = true;
|
|
|
|
bFreeGlobalState = true;
|
|
hr=InitGlobalState();
|
|
_JumpIfError(hr, error, "InitGlobalState");
|
|
|
|
bCloseFileLog = true;
|
|
hr=FileLogBegin();
|
|
_JumpIfError(hr, error, "FileLogBegin");
|
|
|
|
bBasicInitializationDone = true;
|
|
// finished with basic initialization. We can now
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FileLog0(FL_ServiceMainAnnounce, L"Entered W32TmServiceMain\n");
|
|
|
|
// Prevent any threads which start up from accessing our global state until initialization finishes
|
|
hr = myEnterCriticalSection(&g_state.csW32Time);
|
|
_JumpIfError(hr, error, "myEnterCriticalSection");
|
|
bEnteredCriticalSection = true;
|
|
|
|
|
|
// tell the SCM we might take a while (we're waiting 90 seconds for netlogon to start).
|
|
// If we don't do this, we might get stopped while waiting for netlogon!
|
|
hr = MySetServicePending(SERVICE_START_PENDING, 1, WAITHINT_WAITFORDISPLN+10);
|
|
_JumpIfError(hr, error, "MySetServicePending");
|
|
|
|
{
|
|
DWORD dwAdj;
|
|
DWORD dwInc;
|
|
BOOL bDisabled;
|
|
GetSystemTimeAdjustment(&dwAdj, &dwInc, &bDisabled);
|
|
FileLog3(FL_ServiceMainAnnounce, L"CurSpc:%u00ns BaseSpc:%u00ns SyncToCmos:%s\n", dwAdj, dwInc, (bDisabled?L"Yes":L"No"));
|
|
LARGE_INTEGER nPerfFreq;
|
|
if (QueryPerformanceFrequency(&nPerfFreq)) {
|
|
FileLog1(FL_ServiceMainAnnounce, L"PerfFreq:%I64uc/s\n", nPerfFreq.QuadPart);
|
|
}
|
|
}
|
|
|
|
// if the time zone is invalid, fixing the time won't help any.
|
|
hr=VerifyAndFixTimeZone();
|
|
_JumpIfError(hr, error, "VerifyAndFixTimeZone");
|
|
|
|
// get the configuration data
|
|
hr=ReadConfig(&g_state.pciConfig);
|
|
if (FAILED(hr)) {
|
|
// log an event on failure
|
|
WCHAR * rgwszStrings[1]={NULL};
|
|
HRESULT hr2=hr;
|
|
hr=GetSystemErrorString(hr2, &(rgwszStrings[0]));
|
|
_JumpIfError(hr, error, "GetSystemErrorString");
|
|
FileLog1(FL_ParamChangeWarn, L"Logging error: The time service encountered an error while reading its configuration from the registry and cannot start. The error was: %s\n", rgwszStrings[0]);
|
|
hr=MyLogEvent(EVENTLOG_ERROR_TYPE, MSG_CONFIG_READ_FAILED, 1, (const WCHAR **)rgwszStrings);
|
|
LocalFree(rgwszStrings[0]);
|
|
_JumpIfError(hr, error, "MyLogEvent");
|
|
hr=hr2;
|
|
_JumpError(hr, error, "ReadConfig");
|
|
}
|
|
// other global state that must come from configuration
|
|
g_state.nPollInterval=g_state.pciConfig->lcci.dwMinPollInterval;
|
|
g_state.nClockPrecision=(signed int)ceil(log(1e-7*g_state.pciConfig->lcci.dwLastClockRate)/(0.69314718)); // just do this once
|
|
g_state.dwEventLogFlags=g_state.pciConfig->dwEventLogFlags;
|
|
|
|
// receive time slip notifications
|
|
hr=StartOrStopTimeSlipNotification(true);
|
|
_JumpIfError(hr, error, "StartOrStopTimeSlipNotification");
|
|
g_state.bTimeSlipNotificationStarted=true;
|
|
|
|
// receive network topology change notifications
|
|
hr=RequestNetTopoChangeNotification();
|
|
_JumpIfError(hr, error, "RequestNetTopoChangeNotification");
|
|
g_state.bNetTopoChangeNotificationStarted=true;
|
|
|
|
// start the rpc server
|
|
hr=W32TmStartRpcServer();
|
|
_JumpIfError(hr, error, "W32TmStartRpcServer");
|
|
|
|
// Register for policy change notification:
|
|
if (!RegisterGPNotification(g_state.hManagerGPUpdateEvent, TRUE /*machine*/)) {
|
|
_IgnoreLastError("RegisterGPNotification");
|
|
// BUGBUG: log event indicating no policy updates are coming:
|
|
} else {
|
|
g_state.bGPNotificationStarted = true;
|
|
}
|
|
|
|
// Initialize our domain role information
|
|
HandleDomHierRoleChangeEvent(&hr, FALSE /*ignored*/);
|
|
_JumpIfError(hr, error, "HandleDomHierRoleChangeEvent");
|
|
|
|
hr = LsaRegisterPolicyChangeNotification(PolicyNotifyServerRoleInformation, g_state.hDomHierRoleChangeEvent);
|
|
if (ERROR_SUCCESS != hr) {
|
|
hr = HRESULT_FROM_WIN32(LsaNtStatusToWinError(hr));
|
|
_JumpError(hr, error, "LsaRegisterPolicyChangeNotification");
|
|
}
|
|
|
|
// tell netlogon whether it needs to announce that we are a time server
|
|
// (at this stage, we will not be announced as a time server)
|
|
hr=UpdateNetlogonServiceBits(true);
|
|
_JumpIfError(hr, error, "UpdateNetlogonServiceBits");
|
|
|
|
// start the clock discipline thread
|
|
hr=StartClockDiscipline();
|
|
_JumpIfError(hr, error, "StartClockDiscipline");
|
|
|
|
// start the providers. We can no longer jump to the "error" label,
|
|
// as this does not shutdown the started providers.
|
|
StartAllProviders();
|
|
|
|
// Set up polling information
|
|
g_state.tpPollDelayRemaining = ((unsigned __int64)(1 << g_state.nPollInterval))*10000000;
|
|
g_state.tpIrregularDelayRemaining = MINIMUMIRREGULARINTERVAL; // HACK: make the first poll quick (2^5 seconds).
|
|
g_state.tpTimeSinceLastSyncAttempt = MINIMUMIRREGULARINTERVAL; // really, should be very large, but this is the only interesting value in use.
|
|
g_state.tpTimeSinceLastGoodSync = 0;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// The service must be configured to handle the following events:
|
|
//
|
|
// Event Received from Handled by Description
|
|
//
|
|
// shutdown SCM SCM thread SERVICE_CONTROL_SHUTDOWN received
|
|
// clock displn thread clock displn thread Thread pool handler Clock discipline thread has stopped
|
|
// param change SCM Thread pool handler SERVICE_CONTROL_PARAMCHANGE received
|
|
// time slip RequestTimeSlipNotification Thread pool handler Time slip event has occured
|
|
// samples available NTP provider Thread pool handler New samples are available from a provider
|
|
// net topo change NotifyAddr Thread pool handler A change in the IP address table has occured
|
|
// net topo RPC W32TimeSync RPC thread RPC Client requests time slip or net topo change
|
|
// timeout Timer Queue Thread pool handler We've gone too long without new time samples
|
|
// LSA role change LSA Thread poll handler Server role has changed -- need to update netlogon bits
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
{
|
|
struct EventsToRegister {
|
|
DWORD dwFlags;
|
|
HANDLE hObject;
|
|
HANDLE *phNewWaitObject;
|
|
WAITORTIMERCALLBACK Callback;
|
|
} rgEventsToRegister[] = {
|
|
{
|
|
WT_EXECUTEONLYONCE,
|
|
g_state.hManagerGPUpdateEvent,
|
|
&g_state.hRegisteredManagerGPUpdateEvent,
|
|
HandleManagerGPUpdate,
|
|
}, {
|
|
WT_EXECUTEDEFAULT,
|
|
g_state.hManagerParamChangeEvent,
|
|
&g_state.hRegisteredManagerParamChangeEvent,
|
|
HandleManagerParamChange
|
|
}, {
|
|
WT_EXECUTEDEFAULT,
|
|
g_state.hTimeSlipEvent,
|
|
&g_state.hRegisteredTimeSlipEvent,
|
|
HandleManagerTimeSlip
|
|
}, {
|
|
WT_EXECUTEONLYONCE,
|
|
g_state.hNetTopoChangeEvent,
|
|
&g_state.hRegisteredNetTopoChangeEvent,
|
|
HandleManagerNetTopoChangeNoRPC
|
|
}, {
|
|
WT_EXECUTEONLYONCE,
|
|
g_state.hClockDisplnThread,
|
|
&g_state.hRegisteredClockDisplnThread,
|
|
HandleClockDisplnThread
|
|
}, {
|
|
WT_EXECUTEDEFAULT,
|
|
g_state.hDomHierRoleChangeEvent,
|
|
&g_state.hRegisteredDomHierRoleChangeEvent,
|
|
HandleDomHierRoleChangeEvent
|
|
}, {
|
|
WT_EXECUTEONLYONCE,
|
|
g_state.hSamplesAvailEvent,
|
|
&g_state.hRegisteredSamplesAvailEvent,
|
|
HandleSamplesAvail
|
|
}
|
|
};
|
|
|
|
for (int nIndex = 0; nIndex < ARRAYSIZE(rgEventsToRegister); nIndex++) {
|
|
if (!RegisterWaitForSingleObject
|
|
(rgEventsToRegister[nIndex].phNewWaitObject, // BUGBUG: does this need to be freed?
|
|
rgEventsToRegister[nIndex].hObject,
|
|
rgEventsToRegister[nIndex].Callback,
|
|
NULL,
|
|
INFINITE,
|
|
rgEventsToRegister[nIndex].dwFlags)) {
|
|
_JumpLastError(hr, error, "RegisterWaitForSingleObject");
|
|
}
|
|
}
|
|
|
|
// Set up our timeout mechanism:
|
|
hr = myCreateTimerQueueTimer(&g_state.hTimer);
|
|
_JumpIfError(hr, error, "myCreateTimerQueueTimer");
|
|
|
|
hr = myStartTimerQueueTimer
|
|
(g_state.hTimer,
|
|
NULL /*default queue*/,
|
|
HandleTimeout,
|
|
NULL,
|
|
0xFFFF /*dummy value*/,
|
|
0xFFFF /*dummy value*/,
|
|
0 /*default execution*/
|
|
);
|
|
_JumpIfError(hr, error, "myStartTimerQueueTimer");
|
|
|
|
// Create the tickcount refresh timer:
|
|
hr = myCreateTimerQueueTimer(&g_state.hTickCountRefreshTimer);
|
|
_JumpIfError(hr, error, "myCreateTimerQueueTimer");
|
|
|
|
hr = myStartTimerQueueTimer
|
|
(g_state.hTickCountRefreshTimer,
|
|
NULL, /*default queue*/
|
|
HandleRefreshTickCount,
|
|
NULL,
|
|
ONEDAYINMILLISECONDS,
|
|
ONEDAYINMILLISECONDS,
|
|
0);
|
|
_JumpIfError(hr, error, "myStartTimerQueueTimer");
|
|
}
|
|
|
|
hr = UpdateTimerQueue1();
|
|
_JumpIfError(hr, error, "UpdateTimerQueue1");
|
|
|
|
// We're fully initialized -- now we're ready to receive controls from the SCM.
|
|
hr=MySetServiceState(SERVICE_RUNNING);
|
|
_JumpIfError(hr, error, "MySetServiceState");
|
|
} _TrapException(hr);
|
|
|
|
if (FAILED(hr)) {
|
|
_JumpError(hr, error, "W32TmServiceMain: HANDLED EXCEPTION");
|
|
}
|
|
|
|
// The service is now completely up and running.
|
|
hr = S_OK;
|
|
error:
|
|
if (bEnteredCriticalSection) {
|
|
HRESULT hr2 = myLeaveCriticalSection(&g_state.csW32Time);
|
|
_IgnoreIfError(hr2, "myLeaveCriticalSection");
|
|
bEnteredCriticalSection = false;
|
|
}
|
|
if (bAllowedShutdown) {
|
|
HRESULT hr2 = AllowShutdown(true);
|
|
_TeardownError(hr, hr2, "AllowShutdown");
|
|
}
|
|
if (FAILED(hr)) { // Didn't start the service successfully. Shutdown:
|
|
if (bBasicInitializationDone) {
|
|
// We've finished basic initialization, so we can shut down regularly
|
|
SendServiceShutdown(hr, FALSE /*don't restart*/, FALSE /*not async*/);
|
|
} else {
|
|
// We aren't yet initialized enough to use SendServiceShutdown.
|
|
// Just free anything we've already allocated.
|
|
if (bCloseFileLog) {
|
|
FileLogEnd();
|
|
}
|
|
if (bFreeGlobalState) {
|
|
FreeGlobalState();
|
|
}
|
|
if (bFreeShutdownState) {
|
|
FreeShutdownState();
|
|
}
|
|
MySetServiceStopped(hr);
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
extern "C" void WINAPI SvchostEntry_W32Time(unsigned int nArgs, WCHAR ** rgwszArgs) {
|
|
// this entry point is called by svchost.exe (base\screg\sc\svchost\svchost.c)
|
|
// adjust our function pointers, then procede as normal.
|
|
fnW32TmRegisterServiceCtrlHandlerEx=RegisterServiceCtrlHandlerExW;
|
|
fnW32TmSetServiceStatus=SetServiceStatus;
|
|
//g_pSvcsGlobalData=pGlobalData; // see SvchostPushServiceGlobals
|
|
W32TmServiceMain(0, NULL);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
extern "C" VOID SvchostPushServiceGlobals(PSVCHOST_GLOBAL_DATA pGlobalData) {
|
|
// this entry point is called by svchost.exe
|
|
g_pSvcsGlobalData=pGlobalData;
|
|
}
|
|
|