1 /* 2 * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #include <windows.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include "jvm.h" 30 #include "TimeZone_md.h" 31 32 #define VALUE_UNKNOWN 0 33 #define VALUE_KEY 1 34 #define VALUE_MAPID 2 35 #define VALUE_GMTOFFSET 3 36 37 #define MAX_ZONE_CHAR 256 38 #define MAX_MAPID_LENGTH 32 39 #define MAX_REGION_LENGTH 4 40 41 #define NT_TZ_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones" 42 #define WIN_TZ_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones" 43 #define WIN_CURRENT_TZ_KEY "System\\CurrentControlSet\\Control\\TimeZoneInformation" 44 45 typedef struct _TziValue { 46 LONG bias; 47 LONG stdBias; 48 LONG dstBias; 49 SYSTEMTIME stdDate; 50 SYSTEMTIME dstDate; 51 } TziValue; 52 53 /* 54 * Registry key names 55 */ 56 static void *keyNames[] = { 57 (void *) L"StandardName", 58 (void *) "StandardName", 59 (void *) L"Std", 60 (void *) "Std" 61 }; 62 63 /* 64 * Indices to keyNames[] 65 */ 66 #define STANDARD_NAME 0 67 #define STD_NAME 2 68 69 /* 70 * Calls RegQueryValueEx() to get the value for the specified key. If 71 * the platform is NT, 2000 or XP, it calls the Unicode 72 * version. Otherwise, it calls the ANSI version and converts the 73 * value to Unicode. In this case, it assumes that the current ANSI 74 * Code Page is the same as the native platform code page (e.g., Code 75 * Page 932 for the Japanese Windows systems. 76 * 77 * `keyIndex' is an index value to the keyNames in Unicode 78 * (WCHAR). `keyIndex' + 1 points to its ANSI value. 79 * 80 * Returns the status value. ERROR_SUCCESS if succeeded, a 81 * non-ERROR_SUCCESS value otherwise. 82 */ 83 static LONG 84 getValueInRegistry(HKEY hKey, 85 int keyIndex, 86 LPDWORD typePtr, 87 LPBYTE buf, 88 LPDWORD bufLengthPtr) 89 { 90 LONG ret; 91 DWORD bufLength = *bufLengthPtr; 92 char val[MAX_ZONE_CHAR]; 93 DWORD valSize; 94 int len; 95 96 *typePtr = 0; 97 ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL, 98 typePtr, buf, bufLengthPtr); 99 if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) { 100 return ret; 101 } 102 103 valSize = sizeof(val); 104 ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL, 105 typePtr, val, &valSize); 106 if (ret != ERROR_SUCCESS) { 107 return ret; 108 } 109 if (*typePtr != REG_SZ) { 110 return ERROR_BADKEY; 111 } 112 113 len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, 114 (LPCSTR) val, -1, 115 (LPWSTR) buf, bufLength/sizeof(WCHAR)); 116 if (len <= 0) { 117 return ERROR_BADKEY; 118 } 119 return ERROR_SUCCESS; 120 } 121 122 /* 123 * Produces custom name "GMT+hh:mm" from the given bias in buffer. 124 */ 125 static void customZoneName(LONG bias, char *buffer) { 126 LONG gmtOffset; 127 int sign; 128 129 if (bias > 0) { 130 gmtOffset = bias; 131 sign = -1; 132 } else { 133 gmtOffset = -bias; 134 sign = 1; 135 } 136 if (gmtOffset != 0) { 137 sprintf(buffer, "GMT%c%02d:%02d", 138 ((sign >= 0) ? '+' : '-'), 139 gmtOffset / 60, 140 gmtOffset % 60); 141 } else { 142 strcpy(buffer, "GMT"); 143 } 144 } 145 146 /* 147 * Gets the current time zone entry in the "Time Zones" registry. 148 */ 149 static int getWinTimeZone(char *winZoneName) 150 { 151 DYNAMIC_TIME_ZONE_INFORMATION dtzi; 152 DWORD timeType; 153 DWORD bufSize; 154 DWORD val; 155 HANDLE hKey = NULL; 156 LONG ret; 157 ULONG valueType; 158 159 /* 160 * Get the dynamic time zone information so that time zone redirection 161 * can be supported. (see JDK-7044727) 162 */ 163 timeType = GetDynamicTimeZoneInformation(&dtzi); 164 if (timeType == TIME_ZONE_ID_INVALID) { 165 goto err; 166 } 167 168 /* 169 * Make sure TimeZoneKeyName is available from the API call. If 170 * DynamicDaylightTime is disabled, return a custom time zone name 171 * based on the GMT offset. Otherwise, return the TimeZoneKeyName 172 * value. 173 */ 174 if (dtzi.TimeZoneKeyName[0] != 0) { 175 if (dtzi.DynamicDaylightTimeDisabled) { 176 customZoneName(dtzi.Bias, winZoneName); 177 return VALUE_GMTOFFSET; 178 } 179 wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR); 180 return VALUE_KEY; 181 } 182 183 /* 184 * If TimeZoneKeyName is not available, check whether StandardName 185 * is available to fall back to the older API GetTimeZoneInformation. 186 * If not, directly read the value from registry keys. 187 */ 188 if (dtzi.StandardName[0] == 0) { 189 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 190 KEY_READ, (PHKEY)&hKey); 191 if (ret != ERROR_SUCCESS) { 192 goto err; 193 } 194 195 /* 196 * Determine if auto-daylight time adjustment is turned off. 197 */ 198 bufSize = sizeof(val); 199 ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL, 200 &valueType, (LPBYTE) &val, &bufSize); 201 if (ret != ERROR_SUCCESS) { 202 goto err; 203 } 204 /* 205 * Return a custom time zone name if auto-daylight time adjustment 206 * is disabled. 207 */ 208 if (val == 1) { 209 customZoneName(dtzi.Bias, winZoneName); 210 (void) RegCloseKey(hKey); 211 return VALUE_GMTOFFSET; 212 } 213 214 bufSize = MAX_ZONE_CHAR; 215 ret = RegQueryValueExA(hKey, "TimeZoneKeyName", NULL, 216 &valueType, (LPBYTE) winZoneName, &bufSize); 217 if (ret != ERROR_SUCCESS) { 218 goto err; 219 } 220 (void) RegCloseKey(hKey); 221 return VALUE_KEY; 222 } else { 223 /* 224 * Fall back to GetTimeZoneInformation 225 */ 226 TIME_ZONE_INFORMATION tzi; 227 HANDLE hSubKey = NULL; 228 DWORD nSubKeys, i; 229 ULONG valueType; 230 TCHAR subKeyName[MAX_ZONE_CHAR]; 231 TCHAR szValue[MAX_ZONE_CHAR]; 232 WCHAR stdNameInReg[MAX_ZONE_CHAR]; 233 TziValue tempTzi; 234 WCHAR *stdNamePtr = tzi.StandardName; 235 int onlyMapID; 236 237 timeType = GetTimeZoneInformation(&tzi); 238 if (timeType == TIME_ZONE_ID_INVALID) { 239 goto err; 240 } 241 242 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 243 KEY_READ, (PHKEY)&hKey); 244 if (ret == ERROR_SUCCESS) { 245 /* 246 * Determine if auto-daylight time adjustment is turned off. 247 */ 248 bufSize = sizeof(val); 249 ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL, 250 &valueType, (LPBYTE) &val, &bufSize); 251 if (ret == ERROR_SUCCESS) { 252 if (val == 1 && tzi.DaylightDate.wMonth != 0) { 253 (void) RegCloseKey(hKey); 254 customZoneName(tzi.Bias, winZoneName); 255 return VALUE_GMTOFFSET; 256 } 257 } 258 259 /* 260 * Win32 problem: If the length of the standard time name is equal 261 * to (or probably longer than) 32 in the registry, 262 * GetTimeZoneInformation() on NT returns a null string as its 263 * standard time name. We need to work around this problem by 264 * getting the same information from the TimeZoneInformation 265 * registry. 266 */ 267 if (tzi.StandardName[0] == 0) { 268 bufSize = sizeof(stdNameInReg); 269 ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType, 270 (LPBYTE) stdNameInReg, &bufSize); 271 if (ret != ERROR_SUCCESS) { 272 goto err; 273 } 274 stdNamePtr = stdNameInReg; 275 } 276 (void) RegCloseKey(hKey); 277 } 278 279 /* 280 * Open the "Time Zones" registry. 281 */ 282 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey); 283 if (ret != ERROR_SUCCESS) { 284 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey); 285 /* 286 * If both failed, then give up. 287 */ 288 if (ret != ERROR_SUCCESS) { 289 return VALUE_UNKNOWN; 290 } 291 } 292 293 /* 294 * Get the number of subkeys of the "Time Zones" registry for 295 * enumeration. 296 */ 297 ret = RegQueryInfoKey(hKey, NULL, NULL, NULL, &nSubKeys, 298 NULL, NULL, NULL, NULL, NULL, NULL, NULL); 299 if (ret != ERROR_SUCCESS) { 300 goto err; 301 } 302 303 /* 304 * Compare to the "Std" value of each subkey and find the entry that 305 * matches the current control panel setting. 306 */ 307 onlyMapID = 0; 308 for (i = 0; i < nSubKeys; ++i) { 309 DWORD size = sizeof(subKeyName); 310 ret = RegEnumKeyEx(hKey, i, subKeyName, &size, NULL, NULL, NULL, NULL); 311 if (ret != ERROR_SUCCESS) { 312 goto err; 313 } 314 ret = RegOpenKeyEx(hKey, subKeyName, 0, KEY_READ, (PHKEY)&hSubKey); 315 if (ret != ERROR_SUCCESS) { 316 goto err; 317 } 318 319 size = sizeof(szValue); 320 ret = getValueInRegistry(hSubKey, STD_NAME, &valueType, 321 szValue, &size); 322 if (ret != ERROR_SUCCESS) { 323 /* 324 * NT 4.0 SP3 fails here since it doesn't have the "Std" 325 * entry in the Time Zones registry. 326 */ 327 RegCloseKey(hSubKey); 328 onlyMapID = 1; 329 ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, (PHKEY)&hSubKey); 330 if (ret != ERROR_SUCCESS) { 331 goto err; 332 } 333 break; 334 } 335 336 if (wcscmp((WCHAR *)szValue, stdNamePtr) == 0) { 337 /* 338 * Some localized Win32 platforms use a same name to 339 * different time zones. So, we can't rely only on the name 340 * here. We need to check GMT offsets and transition dates 341 * to make sure it's the registry of the current time 342 * zone. 343 */ 344 DWORD tziValueSize = sizeof(tempTzi); 345 ret = RegQueryValueEx(hSubKey, "TZI", NULL, &valueType, 346 (unsigned char *) &tempTzi, &tziValueSize); 347 if (ret == ERROR_SUCCESS) { 348 if ((tzi.Bias != tempTzi.bias) || 349 (memcmp((const void *) &tzi.StandardDate, 350 (const void *) &tempTzi.stdDate, 351 sizeof(SYSTEMTIME)) != 0)) { 352 goto out; 353 } 354 355 if (tzi.DaylightBias != 0) { 356 if ((tzi.DaylightBias != tempTzi.dstBias) || 357 (memcmp((const void *) &tzi.DaylightDate, 358 (const void *) &tempTzi.dstDate, 359 sizeof(SYSTEMTIME)) != 0)) { 360 goto out; 361 } 362 } 363 } 364 365 /* 366 * found matched record, terminate search 367 */ 368 strcpy(winZoneName, subKeyName); 369 break; 370 } 371 out: 372 (void) RegCloseKey(hSubKey); 373 } 374 375 (void) RegCloseKey(hKey); 376 } 377 378 return VALUE_KEY; 379 380 err: 381 if (hKey != NULL) { 382 (void) RegCloseKey(hKey); 383 } 384 return VALUE_UNKNOWN; 385 } 386 387 /* 388 * The mapping table file name. 389 */ 390 #define MAPPINGS_FILE "\\lib\\tzmappings" 391 392 /* 393 * Index values for the mapping table. 394 */ 395 #define TZ_WIN_NAME 0 396 #define TZ_REGION 1 397 #define TZ_JAVA_NAME 2 398 399 #define TZ_NITEMS 3 /* number of items (fields) */ 400 401 /* 402 * Looks up the mapping table (tzmappings) and returns a Java time 403 * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is 404 * returned. 405 */ 406 static char *matchJavaTZ(const char *java_home_dir, char *tzName) 407 { 408 int line; 409 int IDmatched = 0; 410 FILE *fp; 411 char *javaTZName = NULL; 412 char *items[TZ_NITEMS]; 413 char *mapFileName; 414 char lineBuffer[MAX_ZONE_CHAR * 4]; 415 int offset = 0; 416 const char* errorMessage = "unknown error"; 417 char region[MAX_REGION_LENGTH]; 418 419 // Get the user's location 420 if (GetGeoInfo(GetUserGeoID(GEOCLASS_NATION), 421 GEO_ISO2, region, MAX_REGION_LENGTH, 0) == 0) { 422 // If GetGeoInfo fails, fallback to LCID's country 423 LCID lcid = GetUserDefaultLCID(); 424 if (GetLocaleInfo(lcid, 425 LOCALE_SISO3166CTRYNAME, region, MAX_REGION_LENGTH) == 0 && 426 GetLocaleInfo(lcid, 427 LOCALE_SISO3166CTRYNAME2, region, MAX_REGION_LENGTH) == 0) { 428 region[0] = '\0'; 429 } 430 } 431 432 mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1); 433 if (mapFileName == NULL) { 434 return NULL; 435 } 436 strcpy(mapFileName, java_home_dir); 437 strcat(mapFileName, MAPPINGS_FILE); 438 439 if ((fp = fopen(mapFileName, "r")) == NULL) { 440 jio_fprintf(stderr, "can't open %s.\n", mapFileName); 441 free((void *) mapFileName); 442 return NULL; 443 } 444 free((void *) mapFileName); 445 446 line = 0; 447 while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) { 448 char *start, *idx, *endp; 449 int itemIndex = 0; 450 451 line++; 452 start = idx = lineBuffer; 453 endp = &lineBuffer[sizeof(lineBuffer)]; 454 455 /* 456 * Ignore comment and blank lines. 457 */ 458 if (*idx == '#' || *idx == '\n') { 459 continue; 460 } 461 462 for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) { 463 items[itemIndex] = start; 464 while (*idx && *idx != ':') { 465 if (++idx >= endp) { 466 errorMessage = "premature end of line"; 467 offset = (int)(idx - lineBuffer); 468 goto illegal_format; 469 } 470 } 471 if (*idx == '\0') { 472 errorMessage = "illegal null character found"; 473 offset = (int)(idx - lineBuffer); 474 goto illegal_format; 475 } 476 *idx++ = '\0'; 477 start = idx; 478 } 479 480 if (*idx != '\n') { 481 errorMessage = "illegal non-newline character found"; 482 offset = (int)(idx - lineBuffer); 483 goto illegal_format; 484 } 485 486 /* 487 * We need to scan items until the 488 * exact match is found or the end of data is detected. 489 */ 490 if (strcmp(items[TZ_WIN_NAME], tzName) == 0) { 491 /* 492 * Found the time zone in the mapping table. 493 * Check the region code and select the appropriate entry 494 */ 495 if (strcmp(items[TZ_REGION], region) == 0 || 496 strcmp(items[TZ_REGION], "001") == 0) { 497 javaTZName = _strdup(items[TZ_JAVA_NAME]); 498 break; 499 } 500 } 501 } 502 fclose(fp); 503 504 return javaTZName; 505 506 illegal_format: 507 (void) fclose(fp); 508 jio_fprintf(stderr, "Illegal format in tzmappings file: %s at line %d, offset %d.\n", 509 errorMessage, line, offset); 510 return NULL; 511 } 512 513 /* 514 * Detects the platform time zone which maps to a Java time zone ID. 515 */ 516 char *findJavaTZ_md(const char *java_home_dir) 517 { 518 char winZoneName[MAX_ZONE_CHAR]; 519 char *std_timezone = NULL; 520 int result; 521 522 result = getWinTimeZone(winZoneName); 523 524 if (result != VALUE_UNKNOWN) { 525 if (result == VALUE_GMTOFFSET) { 526 std_timezone = _strdup(winZoneName); 527 } else { 528 std_timezone = matchJavaTZ(java_home_dir, winZoneName); 529 if (std_timezone == NULL) { 530 std_timezone = getGMTOffsetID(); 531 } 532 } 533 } 534 return std_timezone; 535 } 536 537 /** 538 * Returns a GMT-offset-based time zone ID. 539 */ 540 char * 541 getGMTOffsetID() 542 { 543 LONG bias = 0; 544 LONG ret; 545 HANDLE hKey = NULL; 546 char zonename[32]; 547 548 // Obtain the current GMT offset value of ActiveTimeBias. 549 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 550 KEY_READ, (PHKEY)&hKey); 551 if (ret == ERROR_SUCCESS) { 552 DWORD val; 553 DWORD bufSize = sizeof(val); 554 ULONG valueType = 0; 555 ret = RegQueryValueExA(hKey, "ActiveTimeBias", 556 NULL, &valueType, (LPBYTE) &val, &bufSize); 557 if (ret == ERROR_SUCCESS) { 558 bias = (LONG) val; 559 } 560 (void) RegCloseKey(hKey); 561 } 562 563 // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation. 564 // Note: Bias doesn't reflect current daylight saving. 565 if (ret != ERROR_SUCCESS) { 566 TIME_ZONE_INFORMATION tzi; 567 if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) { 568 bias = tzi.Bias; 569 } 570 } 571 572 customZoneName(bias, zonename); 573 return _strdup(zonename); 574 }