diff --git a/winpr/libwinpr/timezone/CMakeLists.txt b/winpr/libwinpr/timezone/CMakeLists.txt index f037957b4..e52e3a0b8 100644 --- a/winpr/libwinpr/timezone/CMakeLists.txt +++ b/winpr/libwinpr/timezone/CMakeLists.txt @@ -22,7 +22,9 @@ set(SRCS ) if (NOT WIN32) list(APPEND SRCS - timezone.c + TimeZoneIanaAbbrevMap.c + TimeZoneIanaAbbrevMap.h + timezone.c ) endif() diff --git a/winpr/libwinpr/timezone/TimeZoneIanaAbbrevMap.c b/winpr/libwinpr/timezone/TimeZoneIanaAbbrevMap.c new file mode 100644 index 000000000..de31c6a17 --- /dev/null +++ b/winpr/libwinpr/timezone/TimeZoneIanaAbbrevMap.c @@ -0,0 +1,268 @@ +/** + * WinPR: Windows Portable Runtime + * Time Zone + * + * Copyright 2024 Armin Novak + * Copyright 2024 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TimeZoneIanaAbbrevMap.h" + +#include +#include +#include +#include +#include + +#include + +typedef struct +{ + const char* Iana; + const char* Abbrev; +} TimeZoneInanaAbbrevMapEntry; + +const static char* zonepath = "/usr/share/zoneinfo"; + +static TimeZoneInanaAbbrevMapEntry* TimeZoneIanaAbbrevMap = NULL; +static size_t TimeZoneIanaAbbrevMapSize = 0; + +static void append(const char* iana, const char* sname) +{ + const size_t size = TimeZoneIanaAbbrevMapSize + 1; + + TimeZoneInanaAbbrevMapEntry* tmp = + realloc(TimeZoneIanaAbbrevMap, size * sizeof(TimeZoneInanaAbbrevMapEntry)); + if (!tmp) + return; + TimeZoneIanaAbbrevMap = tmp; + TimeZoneIanaAbbrevMapSize = size; + + TimeZoneInanaAbbrevMapEntry* cur = &TimeZoneIanaAbbrevMap[size - 1]; + cur->Abbrev = _strdup(sname); + cur->Iana = _strdup(iana); +} + +static void append_timezone(const char* dir, const char* name) +{ + char* tz = NULL; + if (!dir && !name) + return; + if (!dir) + { + size_t len = 0; + winpr_asprintf(&tz, &len, "%s", name); + } + else + { + size_t len = 0; + winpr_asprintf(&tz, &len, "%s/%s", dir, name); + } + if (!tz) + return; + + const char* otz = getenv("TZ"); + char* oldtz = NULL; + if (otz) + oldtz = _strdup(otz); + setenv("TZ", tz, 1); + tzset(); + const time_t t = time(NULL); + struct tm lt = { 0 }; + localtime_r(&t, <); + append(tz, lt.tm_zone); + if (oldtz) + { + setenv("TZ", oldtz, 1); + free(oldtz); + } + else + unsetenv("TZ"); + free(tz); +} + +static void handle_link(const char* dir, const char* name, const char* base); + +static char* topath(const char* base, const char* bname, const char* name) +{ + size_t plen = 0; + char* path = NULL; + + if (!base && !bname && !name) + return NULL; + + if (!base && !name) + return _strdup(bname); + + if (!bname && !name) + return _strdup(base); + + if (!base && !bname) + return _strdup(bname); + + if (!base) + winpr_asprintf(&path, &plen, "%s/%s", bname, name); + else if (!bname) + winpr_asprintf(&path, &plen, "%s/%s", base, name); + else if (!name) + winpr_asprintf(&path, &plen, "%s/%s", base, bname); + else + winpr_asprintf(&path, &plen, "%s/%s/%s", base, bname, name); + return path; +} + +static void iterate_subdir_recursive(const char* base, const char* bname, const char* name) +{ + char* path = topath(base, bname, name); + if (!path) + return; + + struct DIR* d = opendir(path); + if (d) + { + struct dirent* dp = NULL; + while ((dp = readdir(d)) != NULL) + { + switch (dp->d_type) + { + case DT_DIR: + { + if (strcmp(dp->d_name, ".") == 0) + continue; + if (strcmp(dp->d_name, "..") == 0) + continue; + iterate_subdir_recursive(path, dp->d_name, NULL); + } + break; + case DT_LNK: + handle_link(base, bname, dp->d_name); + break; + case DT_REG: + append_timezone(bname, dp->d_name); + break; + default: + break; + } + } + closedir(d); + } + free(path); +} + +static char* get_link_target(const char* base, const char* dir, const char* name) +{ + char* apath = NULL; + char* path = topath(base, dir, name); + if (!path) + return NULL; + + SSIZE_T rc = -1; + size_t size = 0; + char* target = NULL; + do + { + size += 64; + char* tmp = realloc(target, size + 1); + if (!tmp) + goto fail; + + target = tmp; + + memset(target, 0, size + 1); + rc = readlink(path, target, size); + if (rc < 0) + goto fail; + } while (rc >= size); + + apath = topath(base, dir, target); +fail: + free(target); + free(path); + return apath; +} + +void handle_link(const char* base, const char* dir, const char* name) +{ + int isDir = -1; + + char* target = get_link_target(base, dir, name); + if (target) + { + struct stat s = { 0 }; + const int rc3 = stat(target, &s); + if (rc3 == 0) + isDir = S_ISDIR(s.st_mode); + + free(target); + } + + switch (isDir) + { + case 1: + iterate_subdir_recursive(base, dir, name); + break; + case 0: + append_timezone(dir, name); + break; + default: + break; + } +} + +static void TimeZoneIanaAbbrevCleanup(void) +{ + if (!TimeZoneIanaAbbrevMap) + return; + + for (size_t x = 0; x < TimeZoneIanaAbbrevMapSize; x++) + { + TimeZoneInanaAbbrevMapEntry* entry = &TimeZoneIanaAbbrevMap[x]; + free(entry->Iana); + free(entry->Abbrev); + } + free(TimeZoneIanaAbbrevMap); + TimeZoneIanaAbbrevMap = NULL; + TimeZoneIanaAbbrevMapSize = 0; +} + +static void TimeZoneIanaAbbrevInitialize(void) +{ + static BOOL initialized = FALSE; + if (initialized) + return; + + iterate_subdir_recursive(zonepath, NULL, NULL); + atexit(TimeZoneIanaAbbrevCleanup); + initialized = TRUE; +} + +size_t TimeZoneIanaAbbrevGet(const char* abbrev, const char** list, size_t listsize) +{ + TimeZoneIanaAbbrevInitialize(); + + size_t rc = 0; + for (size_t x = 0; x < TimeZoneIanaAbbrevMapSize; x++) + { + const TimeZoneInanaAbbrevMapEntry* entry = &TimeZoneIanaAbbrevMap[x]; + if (strcmp(abbrev, entry->Abbrev) == 0) + { + if (listsize > rc) + list[rc] = entry->Iana; + rc++; + } + } + + return rc; +} diff --git a/winpr/libwinpr/timezone/TimeZoneIanaAbbrevMap.h b/winpr/libwinpr/timezone/TimeZoneIanaAbbrevMap.h new file mode 100644 index 000000000..c331d1aa1 --- /dev/null +++ b/winpr/libwinpr/timezone/TimeZoneIanaAbbrevMap.h @@ -0,0 +1,36 @@ +/** + * WinPR: Windows Portable Runtime + * Time Zone + * + * Copyright 2024 Armin Novak + * Copyright 2024 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_TIMEZONE_IANA_ABBREV +#define WINPR_TIMEZONE_IANA_ABBREV + +#include + +/**! \brief returns a list of IANA names for a short timezone name + * + * \param abbrev The short name to look for + * \param list The list to hold the const IANA names + * \param listsize The size of the \b list. Set to 0 to only get the required size. + * + * \return The number of mappings found + */ +size_t TimeZoneIanaAbbrevGet(const char* abbrev, const char** list, size_t listsize); + +#endif diff --git a/winpr/libwinpr/timezone/timezone.c b/winpr/libwinpr/timezone/timezone.c index dc1a11efe..80f0430dc 100644 --- a/winpr/libwinpr/timezone/timezone.c +++ b/winpr/libwinpr/timezone/timezone.c @@ -3,6 +3,8 @@ * Time Zone * * Copyright 2012 Marc-Andre Moreau + * Copyright 2024 Armin Novak + * Copyright 2024 Thincast Technologies GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +36,7 @@ #endif #include "TimeZoneNameMap.h" +#include "TimeZoneIanaAbbrevMap.h" #ifndef _WIN32 @@ -465,8 +468,25 @@ static LONG get_bias(const struct tm* start, BOOL dstBias) return 0; } +static BOOL map_iana_id(const char* iana, LPTIME_ZONE_INFORMATION tz) +{ + const char* winId = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_ID); + if (!winId) + return FALSE; + + const char* winStd = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_STANDARD); + const char* winDst = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_DAYLIGHT); + + ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName)); + ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName)); + + return TRUE; +} + DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation) { + const char** list = NULL; + char* tzid = NULL; const char* defaultName = "Client Local Time"; DWORD res = TIME_ZONE_ID_UNKNOWN; const TIME_ZONE_INFORMATION empty = { 0 }; @@ -477,8 +497,6 @@ DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation) *tz = empty; ConvertUtf8ToWChar(defaultName, tz->StandardName, ARRAYSIZE(tz->StandardName)); - char* tzid = winpr_guess_time_zone(); - const time_t t = time(NULL); struct tm tres = { 0 }; struct tm* local_time = localtime_r(&t, &tres); @@ -495,22 +513,30 @@ DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation) get_transition_date(local_time, TRUE, &tz->DaylightDate); } - ConvertUtf8ToWChar(local_time->tm_zone, tz->StandardName, ARRAYSIZE(tz->StandardName)); - - const char* winId = TimeZoneIanaToWindows(tzid, TIME_ZONE_NAME_ID); - if (winId) + tzid = winpr_guess_time_zone(); + if (!map_iana_id(tzid, tz)) { - const char* winStd = TimeZoneIanaToWindows(tzid, TIME_ZONE_NAME_STANDARD); - const char* winDst = TimeZoneIanaToWindows(tzid, TIME_ZONE_NAME_DAYLIGHT); - - ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName)); - ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName)); - - res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD; + const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone, NULL, 0); + list = calloc(len, sizeof(char*)); + if (!list) + goto out_error; + const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len); + for (size_t x = 0; x < size; x++) + { + const char* id = list[x]; + if (map_iana_id(id, tz)) + { + res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD; + break; + } + } } + else + res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD; out_error: free(tzid); + free(list); switch (res) { case TIME_ZONE_ID_DAYLIGHT: