/** * 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 #include #include "timezone.h" typedef struct { char* Iana; char* Abbrev; } TimeZoneInanaAbbrevMapEntry; const static char* zonepath = "/usr/share/zoneinfo"; static TimeZoneInanaAbbrevMapEntry* TimeZoneIanaAbbrevMap = nullptr; 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 = nullptr; 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; char* oldtz = setNewAndSaveOldTZ(tz); const time_t t = time(nullptr); struct tm lt = WINPR_C_ARRAY_INIT; (void)localtime_r(&t, <); append(tz, lt.tm_zone); restoreSavedTZ(oldtz); free(tz); } static void handle_link(const char* base, const char* dir, const char* name); static char* topath(const char* base, const char* bname, const char* name) { size_t plen = 0; char* path = nullptr; if (!base && !bname && !name) return nullptr; if (!base && !name) return _strdup(bname); if (!bname && !name) return _strdup(base); if (!base && !bname) return _strdup(name); 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; DIR* d = opendir(path); if (d) { struct dirent* dp = nullptr; // NOLINTNEXTLINE(concurrency-mt-unsafe) while ((dp = readdir(d)) != nullptr) { 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, nullptr); } 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 = nullptr; char* path = topath(base, dir, name); if (!path) return nullptr; SSIZE_T rc = -1; size_t size = 0; char* target = nullptr; 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 ((size_t)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 = WINPR_C_ARRAY_INIT; 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 = nullptr; TimeZoneIanaAbbrevMapSize = 0; } static BOOL CALLBACK TimeZoneIanaAbbrevInitialize(WINPR_ATTR_UNUSED PINIT_ONCE once, WINPR_ATTR_UNUSED PVOID param, WINPR_ATTR_UNUSED PVOID* context) { iterate_subdir_recursive(zonepath, nullptr, nullptr); (void)atexit(TimeZoneIanaAbbrevCleanup); return TRUE; } size_t TimeZoneIanaAbbrevGet(const char* abbrev, const char** list, size_t listsize) { static INIT_ONCE init_guard = INIT_ONCE_STATIC_INIT; if (!InitOnceExecuteOnce(&init_guard, TimeZoneIanaAbbrevInitialize, nullptr, nullptr)) return 0; 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; }