AmiTimeKeeper/sync.c

460 lines
12 KiB
C

/*-
* Copyright (c) 2017-2021 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 "conv.h"
#include "sntp.h"
#include "text.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;
int PoolServerNumber;
char *ServerName;
char *ServerPort;
};
static void SyncProc(void);
static int SyncClock(void);
static void ChooseServer(void);
static const char *SntpErrorText(enum sntp_err error);
static struct AppCom Synchronizer;
volatile bool SynchronizerRunning = false;
volatile struct timeval LastSync = {0, 0};
volatile struct timeval LastAdjust = {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(Text2P, "Synchronizer", TextAlreadyRunning);
}
else if (task != NULL)
{
LogDebug("Created synchronizer process");
}
else
{
LogError("Failed to create synchronizer process");
}
}
static int ComInit(void)
{
if (OpenSocketLibrary() != LIB_OK)
{
return COM_ERROR;
}
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 COM_ERROR;
}
return COM_OK;
}
static void ComDestroy(void)
{
if (Synchronizer.Client != NULL)
{
sntp_destroy(Synchronizer.Client);
Synchronizer.Client = NULL;
}
CloseSocketLibrary();
}
static void SendSync(void)
{
if (ComInit() == COM_OK)
{
if (SyncClock() == COM_OK)
LogDebug("Sending next NTP request in %ld milliseconds", Settings->Interval);
else
LogDebug("Retry in %ld milliseconds", Settings->Interval);
}
ComDestroy();
}
static void MsgLoop(void)
{
bool loop = true;
bool restart = false;
ULONG procPortSigMask = GetPortSignalMask(MSGPORT_SYNCER);
ULONG timerSigMask = (1 << Synchronizer.TimerSigBit);
ULONG sigMask = procPortSigMask | timerSigMask;
SendSync();
do
{
ULONG sigrcvd = Wait(sigMask);
if (sigrcvd & timerSigMask)
{
SendSync();
}
if (sigrcvd & procPortSigMask)
{
struct ApplicationMesage *msg;
while ((msg = (struct ApplicationMesage *)GetNewMessage(MSGPORT_SYNCER)))
{
switch (msg->MsgId)
{
case ATK_DISABLE:
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);
SendSync();
restart = false;
}
} while (loop);
}
static void SyncProc(void)
{
struct timeval tv;
bool success;
Synchronizer.TimerInfo = NULL;
Synchronizer.Client = NULL;
Synchronizer.TimerSigBit = -1;
Synchronizer.Restart = false;
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;
}
success = CreateMessagePort(MSGPORT_SYNCER);
if (!success)
{
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);
DestroyMessagePort(MSGPORT_SYNCER);
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();
DestroyMessagePort(MSGPORT_SYNCER);
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 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, "%d.%s", i, server);
LogNotice("Choosing pool server %s", Synchronizer.ServerName);
}
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 receive 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);
LastAdjust = rtv;
{ // Log after setting clock
static char out[MAXLONGLONGCHARSIZE];
static char out2[MAXLONGLONGCHARSIZE];
LongLongToStr(adt, out);
LongLongToStr(th, out2);
LogNotice("%s us > %s us, setting software clock", out, out2);
if (BattClockBase != NULL)
{
LogNotice("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;
SendWindowMessage(ATK_LASTSYNC);
return COM_OK;
}
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";
}