/*- * Copyright (c) 2017-2021 Carsten Sonne Larsen * 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 "notify.h" #include "global.h" #include "message.h" #include "setting.h" #include "conv.h" #include "text.h" #include "ptz.h" #include "timer.h" #include "mem.h" #include "tz.h" #include #include #include "logmod.h" #define MODULENAME "Timezone" #define EnvTZTimezone 1 #define EnvTZOneTimezone 2 #define SetTZEnvTimezone 3 #define SetTZValTimezone 4 #define PrefsTimezone 5 #define TIMEZONE_STD 1 #define TIMEZONE_DST 2 #define ENV_TZ "TZ" #define ENV_TZONE "TZONE" #define ENV_TZ_FILE "ENV:TZ" #define ENV_TZONE_FILE "ENV:TZONE" #define ENV_PREFS_FILE "ENV:Sys/locale.prefs" /* * 2922 is the number of days between 1.1.1970 and 1.1.1978 * (2 leap years and 6 normal) * 2922 * 24 * 60 * 60 = 252460800 */ #define AMIGA_OFFSET 252460800 static volatile bool TimezoneCheckerRunning = false; static volatile bool TimezoneCheckerRestart = false; static volatile bool TimezoneCheckerShutdown = false; static volatile long TimezoneSource = 0; static volatile long unixEpochOffset = 0; static void StartTimezoneCheck(void); static void BuildTimezone(struct PosixLocalTimezone *timezone, int offset, bool std) { long sign = (offset < 0 ? -1 : +1); long offsetPositive = (offset < 0 ? -offset : offset); long hoursOffset = (offsetPositive / 60L); long minutsOffset = (offsetPositive % 60L); if (Settings->TimeZoneName != NULL && *Settings->TimeZoneName != '\0' && ((Settings->TimeZoneDst <= 0 && std) || (Settings->TimeZoneDst == 1 && !std))) SNPrintf(timezone->abbreviation, MAX_ABBR - 1, "%s", Settings->TimeZoneName); else if (minutsOffset == 0) { SNPrintf(timezone->abbreviation, MAX_ABBR - 1, "GMT%s%ld", (sign <= 0 ? "+" : "-"), hoursOffset); } else { SNPrintf(timezone->abbreviation, MAX_ABBR - 1, "GMT%s%ld:%02ld", (sign <= 0 ? "+" : "-"), hoursOffset, minutsOffset); } timezone->offset.hours = hoursOffset; timezone->offset.minutes = minutsOffset; timezone->offset.seconds = 0; timezone->offset.sign = -sign; } static void FindCurrentTimezone(struct PosixTimezone *timezone, long opt) { if (opt == TIMEZONE_STD) { StrCopy(timezone->current.abbreviation, timezone->std.abbreviation); timezone->current.offset = timezone->std.offset; } else if (opt == TIMEZONE_DST) { StrCopy(timezone->current.abbreviation, timezone->dst.abbreviation); timezone->current.offset = timezone->dst.offset; } } static void SetCurrentTimezone(struct PosixTimezone *timezone) { if (Settings->TimeZoneDst == 0) FindCurrentTimezone(timezone, TIMEZONE_STD); else if (Settings->TimeZoneDst == 1) FindCurrentTimezone(timezone, TIMEZONE_DST); else if (timezone->source == PosixTypeTimezone && timezone->dst.abbreviation == NULL) FindCurrentTimezone(timezone, TIMEZONE_DST); else FindCurrentTimezone(timezone, TIMEZONE_STD); } static struct PosixTimezone *BuildAmigaTimezone(struct Locale *locale) { struct PosixTimezone *tz = AllocStructSafe(struct PosixTimezone); tz->source = CustomTypeTimezone; long offset = (locale != NULL ? locale->loc_GMTOffset : 0); BuildTimezone(&tz->std, offset, true); BuildTimezone(&tz->dst, offset - 60, false); SetCurrentTimezone(tz); return tz; } static struct PosixTimezone *BuildSettingsTimezone(void) { struct PosixTimezone *tz = AllocStructSafe(struct PosixTimezone); tz->source = CustomTypeTimezone; long value = Settings->TimeZoneValue; long hours = (value != TZVALUE_DEF ? (value / -100) : 0); long minutes = (value != TZVALUE_DEF ? (value % -100) : 0); long offset = hours * 60 + minutes; BuildTimezone(&tz->std, offset, true); BuildTimezone(&tz->dst, offset - 60, false); SetCurrentTimezone(tz); return tz; } static void ShowTimezones(void) { char timezone[TIMEZONE_TEXT_LEN]; GetTimezoneText(timezone, TZD_STD); LogInfo(timezoneStd, timezone); GetTimezoneText(timezone, TZD_DST); LogInfo(timezoneDst, timezone); GetTimezoneText(timezone, TZD_OFFSET_IN_PARENS); LogNotice(timezoneCur, timezone); } static char *GetVariable(const char *variable) { unsigned long memsize = 256; STRPTR var = (STRPTR)variable; STRPTR buf = (STRPTR)AllocStringSafe(memsize); if (!buf) { return NULL; } if (GetVar(var, buf, memsize - 1, GVF_GLOBAL_ONLY) > 0) { return (char *)buf; } FreeMemSafe(buf); return NULL; } static struct PosixTimezone *ParseSettingsTZ() { struct PosixTimezone *timezone = NULL; if (Settings->TZ != NULL && *Settings->TZ != '\0') { timezone = InitPosixTimezone(Settings->TZ); if (timezone != NULL) LogInfo(tzSettingValid, tzFromSet, SettingKeys->TZ); else LogInfo(tzSettingInvalid, tzFromSet, SettingKeys->TZ); LogDebug(settingValueString, SettingKeys->TZ, Settings->TZ); } return timezone; } static struct PosixTimezone *ParseEnvTZ(const char *tz) { struct PosixTimezone *timezone = NULL; char *var = GetVariable(tz); if (var != NULL) { timezone = InitPosixTimezone(var); if (timezone != NULL) LogInfo(tzSettingValid, tzFromEnv, tz); else LogInfo(tzSettingInvalid, tzFromEnv, tz); LogDebug(settingValueString, tz, var); FreeMemSafe(var); } return timezone; } static void ShowTimezoneSource(void) { switch (TimezoneSource) { case SetTZValTimezone: LogNotice(applyTimeZone, tzFromSet, SettingKeys->TimeZoneValue); break; case SetTZEnvTimezone: LogNotice(applyTimeZone, tzFromSet, SettingKeys->TZ); break; case EnvTZTimezone: LogNotice(applyTimeZone, tzFromEnv, ENV_TZ); break; case EnvTZOneTimezone: LogNotice(applyTimeZone, tzFromEnv, ENV_TZONE); break; case PrefsTimezone: LogNotice(applyTimeZone, tzWorkbench, tzPreferences); break; default: break; } } static void ShowNextTransition(void) { if (NextTransition != NULL) { char *nextTime = GetTimeText((ULONG)NextTransition->time); const char *nextTrans = NextTransition->isdst ? tzDstAbbr : tzStdAbbr; LogWarn(tzChange, nextTrans, nextTime); FreeMemSafe(nextTime); } } void InitTimezone(void) { CleanupNotifications(); struct Locale *locale = OpenLocale(NULL); struct PosixTimezone *set1Timezone = ParseSettingsTZ(); struct PosixTimezone *set2Timezone = BuildSettingsTimezone(); struct PosixTimezone *env1Timezone = ParseEnvTZ(ENV_TZ); struct PosixTimezone *env2Timezone = ParseEnvTZ(ENV_TZONE); struct PosixTimezone *prefTimezone = BuildAmigaTimezone(locale); CloseLocale(locale); struct timeval tv; GetSysTime(&tv); SetPosixTimezone(set1Timezone, &tv); SetPosixTimezone(env1Timezone, &tv); SetPosixTimezone(env2Timezone, &tv); long source = 0; struct PosixTimezone *timezone = NULL; if (Settings->TimeZoneValue != TZVALUE_DEF) { source = SetTZValTimezone; timezone = set2Timezone; } else if (set1Timezone != NULL) { source = SetTZEnvTimezone; timezone = set1Timezone; } else if (env1Timezone != NULL) { source = EnvTZTimezone; timezone = env1Timezone; } else if (env2Timezone != NULL) { source = EnvTZOneTimezone; timezone = env2Timezone; } else { source = PrefsTimezone; timezone = prefTimezone; } struct PosixTimezone *activeTimezone = AllocStructSafe(struct PosixTimezone); CopyMem(timezone, activeTimezone, sizeof(struct PosixTimezone)); FreePosixTimezone(set1Timezone); FreePosixTimezone(set2Timezone); FreePosixTimezone(env1Timezone); FreePosixTimezone(env2Timezone); FreePosixTimezone(prefTimezone); struct PosixTransitionTime *activeTransition = FindNextTransition(activeTimezone, &tv); long offset = (activeTimezone->current.offset.sign < 0 ? +1 : -1) * (activeTimezone->current.offset.hours * 60 * 60 + activeTimezone->current.offset.minutes * 60 + activeTimezone->current.offset.seconds); offset += AMIGA_OFFSET; // Switch global time zone variables Forbid(); struct PosixTimezone *lastTimezone = Timezone; Timezone = activeTimezone; TimezoneSource = source; NextTransition = activeTransition; unixEpochOffset = offset; Permit(); switch (source) { case EnvTZTimezone: WatchFile(ENV_TZ_FILE, ATK_TZ_CHANGED); break; case EnvTZOneTimezone: WatchFile(ENV_TZONE_FILE, ATK_TZONE_CHANGED); break; case PrefsTimezone: WatchFile(ENV_PREFS_FILE, ATK_LOCALE_CHANGED); break; default: break; } ShowTimezoneSource(); ShowTimezones(); ShowNextTransition(); StartTimezoneCheck(); FreePosixTimezone(lastTimezone); } void Unix2Amiga(struct timeval *utc, struct timeval *tv) { assert(utc != NULL); assert(tv != NULL); tv->tv_secs = utc->tv_secs - AMIGA_OFFSET; tv->tv_micro = utc->tv_micro; } void UnixUtc2AmigaLocal(struct timeval *utc, struct timeval *tv) { assert(utc != NULL); assert(tv != NULL); tv->tv_secs = utc->tv_secs - unixEpochOffset; tv->tv_micro = utc->tv_micro; } void GetTimeOfDay(struct timeval *tv) { assert(tv != NULL); GetSysTime(tv); tv->tv_secs = tv->tv_secs + unixEpochOffset; } void GetLocalTimeOfDay(struct timeval *tv) { assert(tv != NULL); GetSysTime(tv); } long GetUtcOffsetValue(void) { return (unixEpochOffset - AMIGA_OFFSET); } void SetTimeOfDay(const struct TimerInfo *info, const struct timeval *tv) { struct timeval t; assert(tv != NULL); t.tv_secs = tv->tv_secs - unixEpochOffset; t.tv_micro = tv->tv_micro; SetTime(&t); } void SaveTimeOfDay(const struct timeval *tv) { assert(tv != NULL); WriteBattClock((long)tv->tv_secs - unixEpochOffset); } static long GetNextTransition(void) { time_t next = (NextTransition != NULL ? NextTransition->time : 2147483647); return (long)next; } static long GetNextTransitionCheck(long now) { long diff = GetNextTransition() - now; diff = (diff > 60 ? 60 : diff); return diff; } static void CheckTransition(void) { TimezoneCheckerShutdown = false; TimezoneCheckerRestart = false; if (NextTransition == NULL) { LogNotice(timezoneNoTrans); LogInfo(Text2P, tzChecker, TextStopped); TimezoneCheckerRunning = false; return; } LogInfo(Text2P, tzChecker, TextStarted); struct timeval tv; GetSysTime(&tv); ULONG next, now = (ULONG)(tv.tv_secs); long diff = GetNextTransitionCheck(now); while (!TimezoneCheckerShutdown) { if (diff < 0) { LogTrace(tzCheck); GetSysTime(&tv); now = (ULONG)(tv.tv_secs); next = GetNextTransition(); if (now > next) { ShowTimezones(); LogNotice(tzTrans); Forbid(); SetPosixTimezone(Timezone, &tv); NextTransition = FindNextTransition(Timezone, &tv); Permit(); ShowTimezones(); SendWindowMessage(ATK_TZ_CHANGED); } diff = GetNextTransitionCheck(now); } diff--; Delay(50); if (TimezoneCheckerRestart) { LogInfo(Text2P, TextRestarting, tzChecker); TimezoneCheckerRestart = false; diff = -1; } } LogInfo(Text2P, tzChecker, TextStopped); TimezoneCheckerRunning = false; TimezoneCheckerShutdown = false; } void StopTimezoneCheck(void) { TimezoneCheckerShutdown = true; while (TimezoneCheckerRunning) Delay(10); } static void StartTimezoneCheck(void) { if (TimezoneCheckerRunning) { LogTrace(Text2P, tzChecker, TextAlreadyRunning); TimezoneCheckerRestart = true; return; } TimezoneCheckerRunning = true; LogNotice(Text2P, TextStarting, tzChecker); struct Task *task = (struct Task *)CreateNewProcTags( NP_Entry, (IPTR)CheckTransition, NP_StackSize, 32 * 1024, NP_Name, 0, 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); if (task == NULL) { LogError(Text2P, TextNoStart, tzChecker); TimezoneCheckerRunning = false; } }