AmiTimeKeeper/com.c

563 lines
15 KiB
C

/*-
* Copyright (c) 2017-2019 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 "config.h"
#include "message.h"
#include "state.h"
#include "time.h"
#include "sntp.h"
#include "mem.h"
#define POOLSUFFIX "pool.ntp.org"
#define POOLPORT "123"
struct AppCom
{
bool Run;
bool Restart;
bool Exited;
short TimerSigBit;
short RestartSigBit;
struct Process *Task;
struct TimerInfo *TimerInfo;
struct sntp *Client;
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 ULONG GetSntpRestartSigMask(void);
static const char *SntpErrorText(enum sntp_err error);
void RestartSntpAsync(void)
{
if (Globals->Syncer->Task != NULL)
{
Signal((struct Task *)Globals->Syncer->Task, GetSntpRestartSigMask());
}
}
void StopComAsync(void)
{
if (Globals->Syncer->Task != NULL)
{
Signal((struct Task *)Globals->Syncer->Task, SIGBREAKF_CTRL_C);
}
}
bool CheckComClosed(void)
{
return Globals->Syncer->Exited;
}
void StartCom(void)
{
if (Globals->Syncer != NULL && Globals->Syncer->Task != NULL)
{
LogTrace("Synchronizer process already running");
return;
}
if (Globals->Syncer == NULL)
{
Globals->Syncer = (struct AppCom *)AllocMemSafe(sizeof(struct AppCom));
}
Globals->Syncer->Run = true;
Globals->Syncer->Restart = false;
Globals->Syncer->Exited = false;
Globals->Syncer->FirstRun = true;
Globals->Syncer->SocketOpen = false;
Globals->Syncer->RunCount = 0;
Globals->Syncer->FailCount = 0;
Globals->Syncer->PoolServerNumber = -1;
Globals->Syncer->ServerName = NULL;
Globals->Syncer->ServerPort = NULL;
Globals->Syncer->TimerInfo = NULL;
Forbid();
Globals->Syncer->Task = CreateNewProcTags(
NP_Entry, (IPTR)SyncProc,
NP_Name, (IPTR)APP_SHORT_NAME " Synchronizer",
NP_StackSize, 64 * 1024,
TAG_DONE);
Permit();
if (Globals->Syncer->Task != NULL)
{
LogTrace("Created synchronizer process");
}
else
{
LogError("Failed to create synchronizer process");
}
}
static ULONG GetTimerSigMask(void)
{
return (1 << Globals->Syncer->TimerSigBit);
}
static ULONG GetSntpRestartSigMask(void)
{
return (1 << Globals->Syncer->RestartSigBit);
}
static void ComDestroy(void)
{
if (Globals->Syncer->Client != NULL)
{
sntp_destroy(Globals->Syncer->Client);
Globals->Syncer->Client = NULL;
}
}
static void SyncProc(void)
{
Globals->Syncer->TimerSigBit = AllocSignal(-1);
if (Globals->Syncer->TimerSigBit == -1)
{
SendErrorMessage("Could not allocate signal for synchronizer");
Globals->Syncer->Exited = true;
return;
}
Globals->Syncer->RestartSigBit = AllocSignal(-1);
if (Globals->Syncer->RestartSigBit == -1)
{
SendErrorMessage("Could not allocate signal for synchronizer");
Globals->Syncer->Exited = true;
FreeSignal(Globals->Syncer->TimerSigBit);
return;
}
Globals->Syncer->TimerInfo = CreateInterruptTimer(
(struct Task *)Globals->Syncer->Task,
Globals->Syncer->TimerSigBit);
if (Globals->Syncer->TimerInfo == NULL)
{
SendErrorMessage("Could not create timer for synchronizer");
Globals->Syncer->Exited = true;
FreeSignal(Globals->Syncer->TimerSigBit);
FreeSignal(Globals->Syncer->RestartSigBit);
return;
}
ComOpen();
StartInterruptTimer(Globals->Syncer->TimerInfo);
do
{
ULONG sigtime = GetTimerSigMask();
ULONG sigrest = GetSntpRestartSigMask();
ULONG sigmask = SIGBREAKF_CTRL_C | sigtime | sigrest;
ULONG sigrcvd = Wait(sigmask);
if (sigrcvd & sigtime)
{
ComOpen();
ShowStats();
}
if (sigrcvd & sigrest)
{
ComDestroy();
Globals->Syncer->Restart = true;
ComOpen();
ShowStats();
}
if (sigrcvd & SIGBREAKF_CTRL_C)
{
Globals->Syncer->Run = false;
}
} while (Globals->Syncer->Run);
ComDestroy();
StopInterruptTimer(Globals->Syncer->TimerInfo);
FreeSignal(Globals->Syncer->TimerSigBit);
FreeSignal(Globals->Syncer->RestartSigBit);
DeleteTimer(Globals->Syncer->TimerInfo);
CloseSocketLibrary();
if (!Globals->ShuttingDown)
{
SendWarningMessage("Exited synchronizer process");
}
if (Globals->Syncer->ServerName != NULL)
{
FreeMemSafe(Globals->Syncer->ServerName);
Globals->Syncer->ServerName = NULL;
}
if (Globals->Syncer->ServerPort != NULL)
{
FreeMemSafe(Globals->Syncer->ServerPort);
Globals->Syncer->ServerPort = NULL;
}
Globals->Syncer->TimerInfo = NULL;
Globals->Syncer->Task = NULL;
Globals->Syncer->Exited = true;
}
static void ComOpen(void)
{
if (OpenSocketLibrary() != LIB_OK)
{
Globals->Syncer->SocketOpen = false;
return;
}
Globals->Syncer->SocketOpen = true;
if (Globals->Syncer->FirstRun)
{
SendInfoMessage("Starting SNTP client");
ComInit();
Globals->Syncer->FirstRun = false;
}
if (Globals->Syncer->Restart)
{
SendInfoMessage("Restart SNTP client");
ComInit();
Globals->Syncer->Restart = false;
}
if (SyncClock() == COM_OK)
{
Globals->Syncer->FailCount = 0;
}
else
{
Globals->Syncer->FailCount++;
}
if (Globals->Syncer->FailCount != 0 && Globals->Syncer->FailCount % 5 == 0)
{
ComDestroy();
Globals->Syncer->Restart = true;
}
if (Globals->Syncer->FailCount == 10)
{
SendInfoMessage("Reinitialize socket library");
ComDestroy();
CloseSocketLibrary();
Globals->Syncer->FailCount = 0;
}
}
static void ShowStats(void)
{
if (Globals->Syncer->SocketOpen)
{
char message[SETTINGMESSAGELEN];
SNPrintf(message, SETTINGMESSAGELEN,
"Sending next NTP request in %ld milliseconds",
Globals->Settings->Interval);
SendTraceMessage(message);
}
else
{
char message[SETTINGMESSAGELEN];
SNPrintf(message, SETTINGMESSAGELEN,
"Retry in %ld milliseconds",
Globals->Settings->Interval);
SendTraceMessage(message);
}
Globals->Syncer->RunCount++;
if (Globals->Syncer->RunCount % 10 == 0)
{
char message[SETTINGMESSAGELEN];
char timeString[10], dateString[10], dayString[10], zoneString[10];
SystemTimeString(timeString, dateString, dayString, zoneString);
SNPrintf(message, SETTINGMESSAGELEN,
"Local time is %s %s %s%s",
dayString, dateString, timeString, zoneString);
SendInfoMessage(message);
}
if (Globals->Syncer->RunCount % 25 == 0)
{
char message[SETTINGMESSAGELEN];
long blocks, size;
MemUsage(&blocks, &size, NULL);
SNPrintf(message, SETTINGMESSAGELEN,
"Currently using %ld bytes in %ld blocks on heap",
size, blocks);
SendInfoMessage(message);
}
}
static void ChooseServer(void)
{
char message[SETTINGMESSAGELEN];
int i;
char *server = Globals->Settings->DestinationAddress;
int len = StrLen(server);
bool isPool = EndsWith(server, POOLSUFFIX) &&
!StartsWith(server, "0.") &&
!StartsWith(server, "1.") &&
!StartsWith(server, "2.") &&
!StartsWith(server, "3.");
if (Globals->Syncer->ServerName != NULL)
{
FreeMemSafe(Globals->Syncer->ServerName);
}
if (Globals->Syncer->ServerPort != NULL)
{
FreeMemSafe(Globals->Syncer->ServerPort);
}
if (!isPool)
{
Globals->Syncer->ServerName = StrDupSafe(server);
Globals->Syncer->ServerPort = StrDupSafe(Globals->Settings->DestinationPort);
return;
}
Globals->Syncer->ServerName = (char *)AllocMemSafe(len + 3);
Globals->Syncer->ServerPort = StrDupSafe(POOLPORT);
do
{
i = RandomFast() % 4;
} while (i < 0 || i == Globals->Syncer->PoolServerNumber);
Globals->Syncer->PoolServerNumber = i;
SNPrintf(Globals->Syncer->ServerName, len + 3,
"%ld.%s", i, server);
SNPrintf(message, SETTINGMESSAGELEN,
"Choosing pool server %s", Globals->Syncer->ServerName);
SendWarningMessage(message);
}
static void ComInit(void)
{
if (Globals->Syncer->Client != NULL)
{
sntp_destroy(Globals->Syncer->Client);
Globals->Syncer->Client = NULL;
}
ChooseServer();
Globals->Syncer->Client = sntp_create(
Globals->Syncer->ServerName,
Globals->Syncer->ServerPort,
NULL,
NULL);
if (Globals->Syncer->Client == NULL)
{
SendWarningMessage("Could not create SNTP client");
return;
}
}
static int SyncClock(void)
{
char message[SETTINGMESSAGELEN];
struct timeval rtv, ltv, tv;
long long lt, rt, dt, adt, th;
struct ntptime nt;
bool readOnly;
int trace, ret;
th = Globals->Settings->Threshold;
readOnly = Globals->Settings->Readonly;
trace = TraceLogging();
SNPrintf(message, SETTINGMESSAGELEN,
"Sending request to %s:%s",
Globals->Syncer->ServerName,
Globals->Syncer->ServerPort);
SendInfoMessage(message);
ret = sntp_send(Globals->Syncer->Client);
if (ret != SNTP_OK)
{
SNPrintf(message, SETTINGMESSAGELEN,
"SNTP send failed: %s",
SntpErrorText(ret));
SendWarningMessage(message);
return COM_ERROR;
}
if (trace)
{
SendTraceMessage("Waiting for response");
}
ret = sntp_poll(Globals->Syncer->Client, Globals->Settings->Timeout);
if (ret != SNTP_OK)
{
SNPrintf(message, SETTINGMESSAGELEN,
"SNTP poll failed: %s",
SntpErrorText(ret));
SendWarningMessage(message);
return COM_ERROR;
}
if (trace)
{
SendTraceMessage("Processing response");
}
ret = sntp_recv(Globals->Syncer->Client, &nt);
if (ret != SNTP_OK)
{
SNPrintf(message, SETTINGMESSAGELEN,
"SNTP recieve failed: %s",
SntpErrorText(ret));
SendWarningMessage(message);
if (ret == SNTP_BACKOFF)
{
SNPrintf(message, SETTINGMESSAGELEN,
"Increasing polling interval: %ld -> %ld",
Globals->Settings->Interval,
Globals->Settings->Interval * 2);
SendWarningMessage(message);
Globals->Settings->Interval *= 2;
}
return COM_ERROR;
}
nt2tv(&nt, &rtv);
if (trace)
{
SNPrintf(message, SETTINGMESSAGELEN,
"Got time %lu.%06lu",
(unsigned long)rtv.tv_secs,
(unsigned long)rtv.tv_micro);
SendTraceMessage(message);
}
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 (trace)
{
static char out[MAXLONGLONGCHARSIZE];
LongLongToStr(rt, out);
SNPrintf(message, SETTINGMESSAGELEN, "True time %s", out);
SendTraceMessage(message);
LongLongToStr(lt, out);
SNPrintf(message, SETTINGMESSAGELEN, "Kernel time %s", out);
SendTraceMessage(message);
LongLongToStr(dt, out);
SNPrintf(message, SETTINGMESSAGELEN, "Delta %s", out);
SendTraceMessage(message);
}
if (!readOnly && adt < th)
{
static char out[MAXLONGLONGCHARSIZE];
static char out2[MAXLONGLONGCHARSIZE];
LongLongToStr(adt, out);
LongLongToStr(th, out2);
SNPrintf(message, SETTINGMESSAGELEN,
"%s us < %s us, not setting clock",
out, out2);
SendInfoMessage(message);
}
else if (!readOnly)
{
tv.tv_secs = rt / 1000000;
tv.tv_micro = rt % 1000000;
SetTimeOfDay(Globals->Syncer->TimerInfo, &tv);
{ // Log after setting clock
static char out[MAXLONGLONGCHARSIZE];
static char out2[MAXLONGLONGCHARSIZE];
LongLongToStr(adt, out);
LongLongToStr(th, out2);
SNPrintf(message, SETTINGMESSAGELEN,
"%s us > %s us, setting software clock",
out, out2);
SendWarningMessage(message);
SendWarningMessage("Setting hardware clock");
SaveTimeOfDay(&tv);
}
}
Globals->LastNtpSync = rtv;
Forbid();
if (Globals->Window->Task != NULL && !Globals->ShuttingDown)
{
SendMessageTo(Globals->Window->UserPort, Globals->Broker->ReplyPort, ATK_REFRESH);
}
Permit();
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";
}