| /* | |
| * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| #include <windows.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include "jvm.h" | |
| #include "TimeZone_md.h" | |
| #define VALUE_UNKNOWN 0 | |
| #define VALUE_KEY 1 | |
| #define VALUE_MAPID 2 | |
| #define VALUE_GMTOFFSET 3 | |
| #define MAX_ZONE_CHAR 256 | |
| #define MAX_MAPID_LENGTH 32 | |
| #define MAX_REGION_LENGTH 4 | |
| #define NT_TZ_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones" | |
| #define WIN_TZ_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones" | |
| #define WIN_CURRENT_TZ_KEY "System\\CurrentControlSet\\Control\\TimeZoneInformation" | |
| typedef struct _TziValue { | |
| LONG bias; | |
| LONG stdBias; | |
| LONG dstBias; | |
| SYSTEMTIME stdDate; | |
| SYSTEMTIME dstDate; | |
| } TziValue; | |
| /* | |
| * Registry key names | |
| */ | |
| static void *keyNames[] = { | |
| (void *) L"StandardName", | |
| (void *) "StandardName", | |
| (void *) L"Std", | |
| (void *) "Std" | |
| }; | |
| /* | |
| * Indices to keyNames[] | |
| */ | |
| #define STANDARD_NAME 0 | |
| #define STD_NAME 2 | |
| /* | |
| * Calls RegQueryValueEx() to get the value for the specified key. If | |
| * the platform is NT, 2000 or XP, it calls the Unicode | |
| * version. Otherwise, it calls the ANSI version and converts the | |
| * value to Unicode. In this case, it assumes that the current ANSI | |
| * Code Page is the same as the native platform code page (e.g., Code | |
| * Page 932 for the Japanese Windows systems. | |
| * | |
| * `keyIndex' is an index value to the keyNames in Unicode | |
| * (WCHAR). `keyIndex' + 1 points to its ANSI value. | |
| * | |
| * Returns the status value. ERROR_SUCCESS if succeeded, a | |
| * non-ERROR_SUCCESS value otherwise. | |
| */ | |
| static LONG | |
| getValueInRegistry(HKEY hKey, | |
| int keyIndex, | |
| LPDWORD typePtr, | |
| LPBYTE buf, | |
| LPDWORD bufLengthPtr) | |
| { | |
| LONG ret; | |
| DWORD bufLength = *bufLengthPtr; | |
| char val[MAX_ZONE_CHAR]; | |
| DWORD valSize; | |
| int len; | |
| *typePtr = 0; | |
| ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL, | |
| typePtr, buf, bufLengthPtr); | |
| if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) { | |
| return ret; | |
| } | |
| valSize = sizeof(val); | |
| ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL, | |
| typePtr, val, &valSize); | |
| if (ret != ERROR_SUCCESS) { | |
| return ret; | |
| } | |
| if (*typePtr != REG_SZ) { | |
| return ERROR_BADKEY; | |
| } | |
| len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, | |
| (LPCSTR) val, -1, | |
| (LPWSTR) buf, bufLength/sizeof(WCHAR)); | |
| if (len <= 0) { | |
| return ERROR_BADKEY; | |
| } | |
| return ERROR_SUCCESS; | |
| } | |
| /* | |
| * Produces custom name "GMT+hh:mm" from the given bias in buffer. | |
| */ | |
| static void customZoneName(LONG bias, char *buffer, size_t bufSize) { | |
| LONG gmtOffset; | |
| int sign; | |
| if (bias > 0) { | |
| gmtOffset = bias; | |
| sign = -1; | |
| } else { | |
| gmtOffset = -bias; | |
| sign = 1; | |
| } | |
| if (gmtOffset != 0) { | |
| snprintf(buffer, bufSize, "GMT%c%02d:%02d", | |
| ((sign >= 0) ? '+' : '-'), | |
| gmtOffset / 60, | |
| gmtOffset % 60); | |
| } else { | |
| strcpy(buffer, "GMT"); | |
| } | |
| } | |
| /* | |
| * Gets the current time zone entry in the "Time Zones" registry. | |
| */ | |
| static int getWinTimeZone(char *winZoneName, size_t winZoneNameBufSize) | |
| { | |
| DYNAMIC_TIME_ZONE_INFORMATION dtzi; | |
| DWORD timeType; | |
| DWORD bufSize; | |
| DWORD val; | |
| HANDLE hKey = NULL; | |
| LONG ret; | |
| ULONG valueType; | |
| /* | |
| * Get the dynamic time zone information so that time zone redirection | |
| * can be supported. (see JDK-7044727) | |
| */ | |
| timeType = GetDynamicTimeZoneInformation(&dtzi); | |
| if (timeType == TIME_ZONE_ID_INVALID) { | |
| goto err; | |
| } | |
| /* | |
| * Make sure TimeZoneKeyName is available from the API call. If | |
| * DynamicDaylightTime is disabled, return a custom time zone name | |
| * based on the GMT offset. Otherwise, return the TimeZoneKeyName | |
| * value. | |
| */ | |
| if (dtzi.TimeZoneKeyName[0] != 0) { | |
| if (dtzi.DynamicDaylightTimeDisabled) { | |
| customZoneName(dtzi.Bias, winZoneName, winZoneNameBufSize); | |
| return VALUE_GMTOFFSET; | |
| } | |
| wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR); | |
| return VALUE_KEY; | |
| } | |
| /* | |
| * If TimeZoneKeyName is not available, check whether StandardName | |
| * is available to fall back to the older API GetTimeZoneInformation. | |
| * If not, directly read the value from registry keys. | |
| */ | |
| if (dtzi.StandardName[0] == 0) { | |
| ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, | |
| KEY_READ, (PHKEY)&hKey); | |
| if (ret != ERROR_SUCCESS) { | |
| goto err; | |
| } | |
| /* | |
| * Determine if auto-daylight time adjustment is turned off. | |
| */ | |
| bufSize = sizeof(val); | |
| ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL, | |
| &valueType, (LPBYTE) &val, &bufSize); | |
| if (ret != ERROR_SUCCESS) { | |
| goto err; | |
| } | |
| /* | |
| * Return a custom time zone name if auto-daylight time adjustment | |
| * is disabled. | |
| */ | |
| if (val == 1) { | |
| customZoneName(dtzi.Bias, winZoneName, winZoneNameBufSize); | |
| (void) RegCloseKey(hKey); | |
| return VALUE_GMTOFFSET; | |
| } | |
| bufSize = MAX_ZONE_CHAR; | |
| ret = RegQueryValueExA(hKey, "TimeZoneKeyName", NULL, | |
| &valueType, (LPBYTE) winZoneName, &bufSize); | |
| if (ret != ERROR_SUCCESS) { | |
| goto err; | |
| } | |
| (void) RegCloseKey(hKey); | |
| return VALUE_KEY; | |
| } else { | |
| /* | |
| * Fall back to GetTimeZoneInformation | |
| */ | |
| TIME_ZONE_INFORMATION tzi; | |
| HANDLE hSubKey = NULL; | |
| DWORD nSubKeys, i; | |
| ULONG valueType; | |
| TCHAR subKeyName[MAX_ZONE_CHAR]; | |
| TCHAR szValue[MAX_ZONE_CHAR]; | |
| WCHAR stdNameInReg[MAX_ZONE_CHAR]; | |
| TziValue tempTzi; | |
| WCHAR *stdNamePtr = tzi.StandardName; | |
| int onlyMapID; | |
| timeType = GetTimeZoneInformation(&tzi); | |
| if (timeType == TIME_ZONE_ID_INVALID) { | |
| goto err; | |
| } | |
| ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, | |
| KEY_READ, (PHKEY)&hKey); | |
| if (ret == ERROR_SUCCESS) { | |
| /* | |
| * Determine if auto-daylight time adjustment is turned off. | |
| */ | |
| bufSize = sizeof(val); | |
| ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL, | |
| &valueType, (LPBYTE) &val, &bufSize); | |
| if (ret == ERROR_SUCCESS) { | |
| if (val == 1 && tzi.DaylightDate.wMonth != 0) { | |
| (void) RegCloseKey(hKey); | |
| customZoneName(tzi.Bias, winZoneName, winZoneNameBufSize); | |
| return VALUE_GMTOFFSET; | |
| } | |
| } | |
| /* | |
| * Win32 problem: If the length of the standard time name is equal | |
| * to (or probably longer than) 32 in the registry, | |
| * GetTimeZoneInformation() on NT returns a null string as its | |
| * standard time name. We need to work around this problem by | |
| * getting the same information from the TimeZoneInformation | |
| * registry. | |
| */ | |
| if (tzi.StandardName[0] == 0) { | |
| bufSize = sizeof(stdNameInReg); | |
| ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType, | |
| (LPBYTE) stdNameInReg, &bufSize); | |
| if (ret != ERROR_SUCCESS) { | |
| goto err; | |
| } | |
| stdNamePtr = stdNameInReg; | |
| } | |
| (void) RegCloseKey(hKey); | |
| } | |
| /* | |
| * Open the "Time Zones" registry. | |
| */ | |
| ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey); | |
| if (ret != ERROR_SUCCESS) { | |
| ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey); | |
| /* | |
| * If both failed, then give up. | |
| */ | |
| if (ret != ERROR_SUCCESS) { | |
| return VALUE_UNKNOWN; | |
| } | |
| } | |
| /* | |
| * Get the number of subkeys of the "Time Zones" registry for | |
| * enumeration. | |
| */ | |
| ret = RegQueryInfoKey(hKey, NULL, NULL, NULL, &nSubKeys, | |
| NULL, NULL, NULL, NULL, NULL, NULL, NULL); | |
| if (ret != ERROR_SUCCESS) { | |
| goto err; | |
| } | |
| /* | |
| * Compare to the "Std" value of each subkey and find the entry that | |
| * matches the current control panel setting. | |
| */ | |
| onlyMapID = 0; | |
| for (i = 0; i < nSubKeys; ++i) { | |
| DWORD size = sizeof(subKeyName); | |
| ret = RegEnumKeyEx(hKey, i, subKeyName, &size, NULL, NULL, NULL, NULL); | |
| if (ret != ERROR_SUCCESS) { | |
| goto err; | |
| } | |
| ret = RegOpenKeyEx(hKey, subKeyName, 0, KEY_READ, (PHKEY)&hSubKey); | |
| if (ret != ERROR_SUCCESS) { | |
| goto err; | |
| } | |
| size = sizeof(szValue); | |
| ret = getValueInRegistry(hSubKey, STD_NAME, &valueType, | |
| szValue, &size); | |
| if (ret != ERROR_SUCCESS) { | |
| /* | |
| * NT 4.0 SP3 fails here since it doesn't have the "Std" | |
| * entry in the Time Zones registry. | |
| */ | |
| RegCloseKey(hSubKey); | |
| onlyMapID = 1; | |
| ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, (PHKEY)&hSubKey); | |
| if (ret != ERROR_SUCCESS) { | |
| goto err; | |
| } | |
| break; | |
| } | |
| if (wcscmp((WCHAR *)szValue, stdNamePtr) == 0) { | |
| /* | |
| * Some localized Win32 platforms use a same name to | |
| * different time zones. So, we can't rely only on the name | |
| * here. We need to check GMT offsets and transition dates | |
| * to make sure it's the registry of the current time | |
| * zone. | |
| */ | |
| DWORD tziValueSize = sizeof(tempTzi); | |
| ret = RegQueryValueEx(hSubKey, "TZI", NULL, &valueType, | |
| (unsigned char *) &tempTzi, &tziValueSize); | |
| if (ret == ERROR_SUCCESS) { | |
| if ((tzi.Bias != tempTzi.bias) || | |
| (memcmp((const void *) &tzi.StandardDate, | |
| (const void *) &tempTzi.stdDate, | |
| sizeof(SYSTEMTIME)) != 0)) { | |
| goto out; | |
| } | |
| if (tzi.DaylightBias != 0) { | |
| if ((tzi.DaylightBias != tempTzi.dstBias) || | |
| (memcmp((const void *) &tzi.DaylightDate, | |
| (const void *) &tempTzi.dstDate, | |
| sizeof(SYSTEMTIME)) != 0)) { | |
| goto out; | |
| } | |
| } | |
| } | |
| /* | |
| * found matched record, terminate search | |
| */ | |
| strcpy(winZoneName, subKeyName); | |
| break; | |
| } | |
| out: | |
| (void) RegCloseKey(hSubKey); | |
| } | |
| (void) RegCloseKey(hKey); | |
| } | |
| return VALUE_KEY; | |
| err: | |
| if (hKey != NULL) { | |
| (void) RegCloseKey(hKey); | |
| } | |
| return VALUE_UNKNOWN; | |
| } | |
| /* | |
| * The mapping table file name. | |
| */ | |
| #define MAPPINGS_FILE "\\lib\\tzmappings" | |
| /* | |
| * Index values for the mapping table. | |
| */ | |
| #define TZ_WIN_NAME 0 | |
| #define TZ_REGION 1 | |
| #define TZ_JAVA_NAME 2 | |
| #define TZ_NITEMS 3 /* number of items (fields) */ | |
| /* | |
| * Looks up the mapping table (tzmappings) and returns a Java time | |
| * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is | |
| * returned. | |
| */ | |
| static char *matchJavaTZ(const char *java_home_dir, char *tzName) | |
| { | |
| int line; | |
| FILE *fp; | |
| char *javaTZName = NULL; | |
| char *items[TZ_NITEMS]; | |
| char *mapFileName; | |
| char lineBuffer[MAX_ZONE_CHAR * 4]; | |
| int offset = 0; | |
| const char* errorMessage = "unknown error"; | |
| char region[MAX_REGION_LENGTH]; | |
| // Get the user's location | |
| if (GetGeoInfo(GetUserGeoID(GEOCLASS_NATION), | |
| GEO_ISO2, region, MAX_REGION_LENGTH, 0) == 0) { | |
| // If GetGeoInfo fails, fallback to LCID's country | |
| LCID lcid = GetUserDefaultLCID(); | |
| if (GetLocaleInfo(lcid, | |
| LOCALE_SISO3166CTRYNAME, region, MAX_REGION_LENGTH) == 0 && | |
| GetLocaleInfo(lcid, | |
| LOCALE_SISO3166CTRYNAME2, region, MAX_REGION_LENGTH) == 0) { | |
| region[0] = '\0'; | |
| } | |
| } | |
| mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1); | |
| if (mapFileName == NULL) { | |
| return NULL; | |
| } | |
| strcpy(mapFileName, java_home_dir); | |
| strcat(mapFileName, MAPPINGS_FILE); | |
| if (fopen_s(&fp, mapFileName, "rt") != 0) { | |
| jio_fprintf(stderr, "can't open %s.\n", mapFileName); | |
| free((void *) mapFileName); | |
| return NULL; | |
| } | |
| free((void *) mapFileName); | |
| line = 0; | |
| while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) { | |
| char *start, *idx, *endp; | |
| int itemIndex = 0; | |
| line++; | |
| start = idx = lineBuffer; | |
| endp = &lineBuffer[sizeof(lineBuffer)]; | |
| /* | |
| * Ignore comment and blank lines. | |
| */ | |
| if (*idx == '#' || *idx == '\n') { | |
| continue; | |
| } | |
| for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) { | |
| items[itemIndex] = start; | |
| while (*idx && *idx != ':') { | |
| if (++idx >= endp) { | |
| errorMessage = "premature end of line"; | |
| offset = (int)(idx - lineBuffer); | |
| goto illegal_format; | |
| } | |
| } | |
| if (*idx == '\0') { | |
| errorMessage = "illegal null character found"; | |
| offset = (int)(idx - lineBuffer); | |
| goto illegal_format; | |
| } | |
| *idx++ = '\0'; | |
| start = idx; | |
| } | |
| if (*idx != '\n') { | |
| errorMessage = "illegal non-newline character found"; | |
| offset = (int)(idx - lineBuffer); | |
| goto illegal_format; | |
| } | |
| /* | |
| * We need to scan items until the | |
| * exact match is found or the end of data is detected. | |
| */ | |
| if (strcmp(items[TZ_WIN_NAME], tzName) == 0) { | |
| /* | |
| * Found the time zone in the mapping table. | |
| * Check the region code and select the appropriate entry | |
| */ | |
| if (strcmp(items[TZ_REGION], region) == 0 || | |
| strcmp(items[TZ_REGION], "001") == 0) { | |
| javaTZName = _strdup(items[TZ_JAVA_NAME]); | |
| break; | |
| } | |
| } | |
| } | |
| fclose(fp); | |
| return javaTZName; | |
| illegal_format: | |
| (void) fclose(fp); | |
| jio_fprintf(stderr, "Illegal format in tzmappings file: %s at line %d, offset %d.\n", | |
| errorMessage, line, offset); | |
| return NULL; | |
| } | |
| /* | |
| * Detects the platform time zone which maps to a Java time zone ID. | |
| */ | |
| char *findJavaTZ_md(const char *java_home_dir) | |
| { | |
| char winZoneName[MAX_ZONE_CHAR]; | |
| char *std_timezone = NULL; | |
| int result; | |
| result = getWinTimeZone(winZoneName, sizeof(winZoneName)); | |
| if (result != VALUE_UNKNOWN) { | |
| if (result == VALUE_GMTOFFSET) { | |
| std_timezone = _strdup(winZoneName); | |
| } else { | |
| std_timezone = matchJavaTZ(java_home_dir, winZoneName); | |
| if (std_timezone == NULL) { | |
| std_timezone = getGMTOffsetID(); | |
| } | |
| } | |
| } | |
| return std_timezone; | |
| } | |
| /** | |
| * Returns a GMT-offset-based time zone ID. | |
| */ | |
| char * | |
| getGMTOffsetID() | |
| { | |
| LONG bias = 0; | |
| LONG ret; | |
| HANDLE hKey = NULL; | |
| char zonename[32]; | |
| // Obtain the current GMT offset value of ActiveTimeBias. | |
| ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, | |
| KEY_READ, (PHKEY)&hKey); | |
| if (ret == ERROR_SUCCESS) { | |
| DWORD val; | |
| DWORD bufSize = sizeof(val); | |
| ULONG valueType = 0; | |
| ret = RegQueryValueExA(hKey, "ActiveTimeBias", | |
| NULL, &valueType, (LPBYTE) &val, &bufSize); | |
| if (ret == ERROR_SUCCESS) { | |
| bias = (LONG) val; | |
| } | |
| (void) RegCloseKey(hKey); | |
| } | |
| // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation. | |
| // Note: Bias doesn't reflect current daylight saving. | |
| if (ret != ERROR_SUCCESS) { | |
| TIME_ZONE_INFORMATION tzi; | |
| if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) { | |
| bias = tzi.Bias; | |
| } | |
| } | |
| customZoneName(bias, zonename, sizeof(zonename)); | |
| return _strdup(zonename); | |
| } |