AmiTimeKeeper/sync.c

586 lines
14 KiB
C

/*-
* Copyright (c) 2017-2020 Carsten Sonne Larsen <cs@innolan.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <stddef.h>
#include <stdbool.h>
#include "sync.h"
#include "config.h"
#include "global.h"
#include "setting.h"
#include "message.h"
#include "timer.h"
#include "sntp.h"
#include "ptz.h"
#include "mem.h"
#include "net.h"
#include "tz.h"
#include "logmod.h"
#define MODULENAME "Syncer"
#define SYNCPROCNAME APP_SHORT_NAME " Synchronizer"
#define POOLSUFFIX "pool.ntp.org"
#define POOLPORT "123"
struct AppCom
{
struct TimerInfo *TimerInfo;
struct sntp *Client;
int TimerSigBit;
bool Restart;
bool FirstRun;
bool SocketOpen;
int RunCount;
int FailCount;
int PoolServerNumber;
char *ServerName;
char *ServerPort;
};
static void SyncProc(void);
static void ComOpen(void);
static void ComInit(void);
static int SyncClock(void);
static void ShowStats(void);
static void CheckTimezone(void);
static const char *SntpErrorText(enum sntp_err error);
static struct AppCom Synchronizer;
volatile bool SynchronizerRunning = false;
volatile struct timeval LastSync = {0, 0};
void StartSynchronizer(void)
{
bool running;
struct Task *task;
Forbid();
running = SynchronizerRunning;
if (!running)
{
task = (struct Task *)CreateNewProcTags(
NP_Entry, (IPTR)SyncProc,
NP_Name, (IPTR)SYNCPROCNAME,
NP_StackSize, 64 * 1024,
NP_Input, 0,
NP_Output, 0,
NP_Error, 0,
NP_CloseInput, FALSE,
NP_CloseOutput, FALSE,
NP_CloseError, FALSE,
NP_WindowPtr, 0,
NP_ConsoleTask, 0,
NP_Cli, FALSE,
TAG_DONE);
SynchronizerRunning = (task != NULL);
}
Permit();
if (running)
{
LogDebug("Synchronizer process is already running");
}
else if (task != NULL)
{
LogDebug("Created synchronizer process");
}
else
{
LogError("Failed to create synchronizer process");
}
}
static void ComDestroy(void)
{
if (Synchronizer.Client != NULL)
{
sntp_destroy(Synchronizer.Client);
Synchronizer.Client = NULL;
}
}
static void ShowLocalTime(void)
{
char *time = GetTimeText(AppLocale, LOCAL_TIME_NOW);
LogInfo("Local time is %s", time);
FreeMemSafe(time);
}
static void MsgLoop(void)
{
bool loop = true;
bool restart = false;
ULONG userPortSigMask = (1 << Ports.SyncerPort->mp_SigBit);
ULONG timerSigMask = (1 << Synchronizer.TimerSigBit);
ULONG sigMask = userPortSigMask | timerSigMask;
ComOpen();
do
{
ULONG sigrcvd = Wait(sigMask);
if (sigrcvd & timerSigMask)
{
ComOpen();
ShowStats();
}
if (sigrcvd & userPortSigMask)
{
struct ApplicationMesage *msg;
while ((msg = (struct ApplicationMesage *)GetMsg(Ports.SyncerPort)))
{
switch (msg->MsgId)
{
case ATK_SHUTDOWN:
loop = false;
break;
case ATK_RESTART:
restart = true;
break;
default:
break;
}
ReplyMsg((struct Message *)msg);
}
}
if (loop && restart)
{
struct timeval tv;
tv.tv_secs = Settings->Interval / 1000;
tv.tv_micro = Settings->Interval % 1000;
SetInterruptTimerInterval(Synchronizer.TimerInfo, &tv);
ComDestroy();
Synchronizer.Restart = true;
ComOpen();
ShowStats();
restart = false;
}
} while (loop);
ComDestroy();
}
static void SyncProc(void)
{
struct timeval tv;
Synchronizer.TimerInfo = NULL;
Synchronizer.Client = NULL;
Synchronizer.TimerSigBit = -1;
Synchronizer.Restart = false;
Synchronizer.FirstRun = true;
Synchronizer.SocketOpen = false;
Synchronizer.RunCount = 0;
Synchronizer.FailCount = 0;
Synchronizer.PoolServerNumber = -1;
Synchronizer.ServerName = NULL;
Synchronizer.ServerPort = NULL;
Synchronizer.TimerSigBit = AllocSignal(-1);
if (Synchronizer.TimerSigBit == -1)
{
LogError("Could not allocate signal for synchronizer");
return;
}
Ports.SyncerPort = CreateMsgPort();
if (Ports.SyncerPort == NULL)
{
LogError("Could not allocate port for synchronizer");
FreeSignal(Synchronizer.TimerSigBit);
return;
}
Synchronizer.TimerInfo = CreateInterruptTimer(
FindTask(NULL), Synchronizer.TimerSigBit);
if (Synchronizer.TimerInfo == NULL)
{
LogError("Could not create timer for synchronizer");
FreeSignal(Synchronizer.TimerSigBit);
DeleteMsgPort(Ports.SyncerPort);
Ports.SyncerPort = NULL;
return;
}
tv.tv_secs = Settings->Interval / 1000;
tv.tv_micro = Settings->Interval % 1000;
SetInterruptTimerInterval(Synchronizer.TimerInfo, &tv);
StartInterruptTimer(Synchronizer.TimerInfo);
MsgLoop();
LogTrace("Loop exiting");
DeleteTimer(Synchronizer.TimerInfo);
FreeSignal(Synchronizer.TimerSigBit);
CloseSocketLibrary();
CleanupVolMsgPort(&Ports.SyncerPort);
if (Synchronizer.ServerName != NULL)
{
FreeMemSafe(Synchronizer.ServerName);
Synchronizer.ServerName = NULL;
LogTrace("ServerName cleared");
}
if (Synchronizer.ServerPort != NULL)
{
FreeMemSafe(Synchronizer.ServerPort);
Synchronizer.ServerPort = NULL;
LogTrace("ServerPort cleared");
}
Synchronizer.TimerInfo = NULL;
LogTrace("Loop exited");
SynchronizerRunning = false;
}
static void ComOpen(void)
{
if (OpenSocketLibrary() != LIB_OK)
{
Synchronizer.SocketOpen = false;
return;
}
Synchronizer.SocketOpen = true;
if (Synchronizer.FirstRun)
{
LogInfo("Starting SNTP client");
ComInit();
Synchronizer.FirstRun = false;
}
if (Synchronizer.Restart)
{
LogInfo("Restart SNTP client");
ComInit();
Synchronizer.Restart = false;
}
if (SyncClock() == COM_OK)
{
Synchronizer.FailCount = 0;
}
else
{
Synchronizer.FailCount++;
}
if (Synchronizer.FailCount != 0 && Synchronizer.FailCount % 5 == 0)
{
ComDestroy();
Synchronizer.Restart = true;
}
if (Synchronizer.FailCount == 10)
{
LogInfo("Reinitialize socket library");
ComDestroy();
CloseSocketLibrary();
Synchronizer.FailCount = 0;
}
}
static void ShowStats(void)
{
if (Synchronizer.SocketOpen)
{
LogDebug("Sending next NTP request in %ld milliseconds",
Settings->Interval);
}
else
{
LogDebug("Retry in %ld milliseconds", Settings->Interval);
}
Synchronizer.RunCount++;
if (Synchronizer.RunCount % 10 == 0)
{
ShowLocalTime();
}
if (Synchronizer.RunCount % 25 == 0)
{
long blocks, size;
MemUsage(&blocks, &size, NULL);
LogInfo("Currently using %ld bytes in %ld blocks on heap", size, blocks);
}
}
static void ChooseServer(void)
{
int i;
char *server = Settings->DestinationAddress;
int len = StrLen(server);
bool isPool = EndsWith(server, POOLSUFFIX) &&
!StartsWith(server, "0.") &&
!StartsWith(server, "1.") &&
!StartsWith(server, "2.") &&
!StartsWith(server, "3.");
if (Synchronizer.ServerName != NULL)
{
FreeMemSafe(Synchronizer.ServerName);
}
if (Synchronizer.ServerPort != NULL)
{
FreeMemSafe(Synchronizer.ServerPort);
}
if (!isPool)
{
Synchronizer.ServerName = StrDupSafe(server);
Synchronizer.ServerPort = StrDupSafe(Settings->DestinationPort);
return;
}
Synchronizer.ServerName = AllocStringSafe(len + 3);
Synchronizer.ServerPort = StrDupSafe(POOLPORT);
do
{
i = RandomFast() % 4;
} while (i < 0 || i == Synchronizer.PoolServerNumber);
Synchronizer.PoolServerNumber = i;
SNPrintf(Synchronizer.ServerName, len + 3, "%ld.%s", i, server);
LogWarn("Choosing pool server %s", Synchronizer.ServerName);
}
static void ComInit(void)
{
if (Synchronizer.Client != NULL)
{
sntp_destroy(Synchronizer.Client);
Synchronizer.Client = NULL;
}
ChooseServer();
Synchronizer.Client = sntp_create(
Synchronizer.ServerName,
Synchronizer.ServerPort,
NULL,
NULL);
if (Synchronizer.Client == NULL)
{
LogWarn("Could not create SNTP client");
return;
}
}
static void UpdateWindow(void)
{
bool send = false;
struct TimeMessage *message = AllocStructSafe(struct TimeMessage);
Forbid();
if (Ports.WindowPort != NULL && Ports.MemoryPort != NULL)
{
message->AppMsg.Msg.mn_Node.ln_Type = NT_MESSAGE;
message->AppMsg.Msg.mn_Length = sizeof(struct TimeMessage);
message->AppMsg.Msg.mn_ReplyPort = Ports.MemoryPort;
message->AppMsg.MsgType = MSGTYPE_TIMEVAL;
message->TimeVal = LastSync;
PutMsg(Ports.WindowPort, (struct Message *)message);
send = true;
}
Permit();
if (!send)
{
FreeMemSafe(message);
}
}
static int SyncClock(void)
{
struct timeval rtv, ltv, tv;
long long lt, rt, dt, adt, th;
struct ntptime nt;
bool readOnly;
int ret;
int stratum;
th = Settings->Threshold;
readOnly = Settings->Readonly;
LogInfo("Sending request to %s:%s",
Synchronizer.ServerName,
Synchronizer.ServerPort);
ret = sntp_send(Synchronizer.Client);
if (ret != SNTP_OK)
{
LogInfo("SNTP send failed: %s", SntpErrorText(ret));
if (ret == SNTP_DNSERR || ret == SNTP_SYSERR)
{
Synchronizer.Restart = true;
}
return COM_ERROR;
}
ret = sntp_poll(Synchronizer.Client, Settings->Timeout);
if (ret != SNTP_OK)
{
LogWarn("SNTP poll failed: %s", SntpErrorText(ret));
return COM_ERROR;
}
ret = sntp_recv(Synchronizer.Client, &nt, &stratum);
if (ret != SNTP_OK)
{
LogWarn("SNTP recieve failed: %s", SntpErrorText(ret));
if (ret == SNTP_BACKOFF)
{
LogWarn("Increasing polling interval: %ld -> %ld",
Settings->Interval,
Settings->Interval * 2);
Settings->Interval *= 2;
}
return COM_ERROR;
}
nt2tv(&nt, &rtv);
GetTimeOfDay(&ltv);
lt = 1000000LL * ltv.tv_secs + ltv.tv_micro;
rt = 1000000LL * rtv.tv_secs + rtv.tv_micro;
dt = rt - lt;
adt = dt < 0 ? -dt : dt;
if (!readOnly && adt < th)
{
static char out[MAXLONGLONGCHARSIZE];
static char out2[MAXLONGLONGCHARSIZE];
LogDebug("Stratum %ld server", (unsigned long)stratum);
LongLongToStr(adt, out);
LongLongToStr(th, out2);
LogInfo("%s us < %s us, not setting clock", out, out2);
}
else if (!readOnly)
{
LogDebug("Stratum %ld server", (unsigned long)stratum);
tv.tv_secs = rt / 1000000;
tv.tv_micro = rt % 1000000;
SetTimeOfDay(Synchronizer.TimerInfo, &tv);
{ // Log after setting clock
static char out[MAXLONGLONGCHARSIZE];
static char out2[MAXLONGLONGCHARSIZE];
LongLongToStr(adt, out);
LongLongToStr(th, out2);
LogWarn("%s us > %s us, setting software clock", out, out2);
if (BattClockBase != NULL)
{
LogWarn("Setting hardware clock");
SaveTimeOfDay(&tv);
}
}
}
{
static char out[MAXLONGLONGCHARSIZE];
LongLongToStr(rt, out);
LogDebug("True time %s", out);
LongLongToStr(lt, out);
LogDebug("Kernel time %s", out);
LongLongToStr(dt, out);
LogDebug("Delta %s", out);
}
LastSync = rtv;
UpdateWindow();
CheckTimezone();
return COM_OK;
}
static void CheckTimezone(void)
{
struct timeval tv;
ULONG now;
if (NextTransition == NULL)
{
return;
}
GetSysTime(&tv);
now = (ULONG)(tv.tv_secs);
if (now > NextTransition->time)
{
char Timezone[TIMEZONE_TEXT_LEN];
GetTimezoneText(AppLocale, Timezone, OFFSET_IN_PARENS);
ShowLocalTime();
LogWarn("Current Timezone is %s", Timezone);
LogWarn("Changing Timezone");
// TODO: Make thread safe
ShowLocalTime();
InitTimezoneShift();
SendMessageTo(Ports.WindowPort, Ports.MemoryPort, ATK_TZ_CHANGED);
}
}
static const char *SntpErrorText(enum sntp_err error)
{
switch (error)
{
case SNTP_OK:
return "OK";
case SNTP_SYSERR:
return GetErrorText();
case SNTP_DNSERR:
return GetHostErrorText();
case SNTP_NOREQ:
return "No request sent";
case SNTP_NORESP:
return "No response received";
case SNTP_BADRESP:
return "Invalid response received";
case SNTP_LAME:
return "Server is lame / unsynchronized";
case SNTP_BACKOFF:
return "Polling too frequently";
default:
return "Unknown error";
}
return "Unknown error";
}