1 /* 2 * Copyright (c) 1999, 2015, 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 #include "jdk_util.h" 32 33 #define VALUE_UNKNOWN 0 34 #define VALUE_KEY 1 35 #define VALUE_MAPID 2 36 #define VALUE_GMTOFFSET 3 37 38 #define MAX_ZONE_CHAR 256 39 #define MAX_MAPID_LENGTH 32 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 #if _WIN32_WINNT < 0x0600 /* < _WIN32_WINNT_VISTA */ 54 typedef struct _TIME_DYNAMIC_ZONE_INFORMATION { 55 LONG Bias; 56 WCHAR StandardName[32]; 57 SYSTEMTIME StandardDate; 58 LONG StandardBias; 59 WCHAR DaylightName[32]; 60 SYSTEMTIME DaylightDate; 61 LONG DaylightBias; 62 WCHAR TimeZoneKeyName[128]; 63 BOOLEAN DynamicDaylightTimeDisabled; 64 } DYNAMIC_TIME_ZONE_INFORMATION, *PDYNAMIC_TIME_ZONE_INFORMATION; 65 #endif 66 67 /* 68 * Registry key names 69 */ 70 static void *keyNames[] = { 71 (void *) L"StandardName", 72 (void *) "StandardName", 73 (void *) L"Std", 74 (void *) "Std" 75 }; 76 77 /* 78 * Indices to keyNames[] 79 */ 80 #define STANDARD_NAME 0 81 #define STD_NAME 2 82 83 /* 84 * Calls RegQueryValueEx() to get the value for the specified key. If 85 * the platform is NT, 2000 or XP, it calls the Unicode 86 * version. Otherwise, it calls the ANSI version and converts the 87 * value to Unicode. In this case, it assumes that the current ANSI 88 * Code Page is the same as the native platform code page (e.g., Code 89 * Page 932 for the Japanese Windows systems. 90 * 91 * `keyIndex' is an index value to the keyNames in Unicode 92 * (WCHAR). `keyIndex' + 1 points to its ANSI value. 93 * 94 * Returns the status value. ERROR_SUCCESS if succeeded, a 95 * non-ERROR_SUCCESS value otherwise. 96 */ 97 static LONG 98 getValueInRegistry(HKEY hKey, 99 int keyIndex, 100 LPDWORD typePtr, 101 LPBYTE buf, 102 LPDWORD bufLengthPtr) 103 { 104 LONG ret; 105 DWORD bufLength = *bufLengthPtr; 106 char val[MAX_ZONE_CHAR]; 107 DWORD valSize; 108 int len; 109 110 *typePtr = 0; 111 ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL, 112 typePtr, buf, bufLengthPtr); 113 if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) { 114 return ret; 115 } 116 117 valSize = sizeof(val); 118 ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL, 119 typePtr, val, &valSize); 120 if (ret != ERROR_SUCCESS) { 121 return ret; 122 } 123 if (*typePtr != REG_SZ) { 124 return ERROR_BADKEY; 125 } 126 127 len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, 128 (LPCSTR) val, -1, 129 (LPWSTR) buf, bufLength/sizeof(WCHAR)); 130 if (len <= 0) { 131 return ERROR_BADKEY; 132 } 133 return ERROR_SUCCESS; 134 } 135 136 /* 137 * Produces custom name "GMT+hh:mm" from the given bias in buffer. 138 */ 139 static void customZoneName(LONG bias, char *buffer) { 140 LONG gmtOffset; 141 int sign; 142 143 if (bias > 0) { 144 gmtOffset = bias; 145 sign = -1; 146 } else { 147 gmtOffset = -bias; 148 sign = 1; 149 } 150 if (gmtOffset != 0) { 151 sprintf(buffer, "GMT%c%02d:%02d", 152 ((sign >= 0) ? '+' : '-'), 153 gmtOffset / 60, 154 gmtOffset % 60); 155 } else { 156 strcpy(buffer, "GMT"); 157 } 158 } 159 160 /* 161 * Use NO_DYNAMIC_TIME_ZONE_INFO as the return value indicating that no 162 * dynamic time zone information is available. 163 */ 164 #define NO_DYNAMIC_TIME_ZONE_INFO (-128) 165 166 static int getDynamicTimeZoneInfo(PDYNAMIC_TIME_ZONE_INFORMATION pdtzi) { 167 DWORD timeType = NO_DYNAMIC_TIME_ZONE_INFO; 168 HMODULE dllHandle; 169 170 /* 171 * Dynamically load the dll to call GetDynamicTimeZoneInformation. 172 */ 173 dllHandle = JDK_LoadSystemLibrary("Kernel32.dll"); 174 if (dllHandle != NULL) { 175 typedef DWORD (WINAPI *GetDynamicTimezoneInfoType)(PDYNAMIC_TIME_ZONE_INFORMATION); 176 GetDynamicTimezoneInfoType getDynamicTimeZoneInfo = 177 (GetDynamicTimezoneInfoType) GetProcAddress(dllHandle, 178 "GetDynamicTimeZoneInformation"); 179 180 if (getDynamicTimeZoneInfo != NULL) { 181 timeType = getDynamicTimeZoneInfo(pdtzi); 182 } 183 } 184 return timeType; 185 } 186 187 /* 188 * Gets the current time zone entry in the "Time Zones" registry. 189 */ 190 static int getWinTimeZone(char *winZoneName, char *winMapID) 191 { 192 TIME_ZONE_INFORMATION tzi; 193 OSVERSIONINFO ver; 194 int onlyMapID; 195 HANDLE hKey = NULL, hSubKey = NULL; 196 LONG ret; 197 DWORD nSubKeys, i; 198 ULONG valueType; 199 TCHAR subKeyName[MAX_ZONE_CHAR]; 200 TCHAR szValue[MAX_ZONE_CHAR]; 201 WCHAR stdNameInReg[MAX_ZONE_CHAR]; 202 TziValue tempTzi; 203 WCHAR *stdNamePtr = tzi.StandardName; 204 DWORD valueSize; 205 DWORD timeType; 206 int isVistaOrLater; 207 208 /* 209 * Determine if this is a Vista or later. 210 */ 211 ver.dwOSVersionInfoSize = sizeof(ver); 212 GetVersionEx(&ver); 213 isVistaOrLater = (ver.dwMajorVersion >= 6); 214 215 if (isVistaOrLater) { 216 DYNAMIC_TIME_ZONE_INFORMATION dtzi; 217 DWORD bufSize; 218 DWORD val; 219 220 /* 221 * Get the dynamic time zone information, if available, so that time 222 * zone redirection can be supported. (see JDK-7044727) 223 */ 224 timeType = getDynamicTimeZoneInfo(&dtzi); 225 if (timeType == TIME_ZONE_ID_INVALID) { 226 goto err; 227 } 228 229 if (timeType != NO_DYNAMIC_TIME_ZONE_INFO) { 230 /* 231 * Make sure TimeZoneKeyName is available from the API call. If 232 * DynamicDaylightTime is disabled, return a custom time zone name 233 * based on the GMT offset. Otherwise, return the TimeZoneKeyName 234 * value. 235 */ 236 if (dtzi.TimeZoneKeyName[0] != 0) { 237 if (dtzi.DynamicDaylightTimeDisabled) { 238 customZoneName(dtzi.Bias, winZoneName); 239 return VALUE_GMTOFFSET; 240 } 241 wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR); 242 return VALUE_KEY; 243 } 244 245 /* 246 * If TimeZoneKeyName is not available, check whether StandardName 247 * is available to fall back to the older API GetTimeZoneInformation. 248 * If not, directly read the value from registry keys. 249 */ 250 if (dtzi.StandardName[0] == 0) { 251 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 252 KEY_READ, (PHKEY)&hKey); 253 if (ret != ERROR_SUCCESS) { 254 goto err; 255 } 256 257 /* 258 * Determine if auto-daylight time adjustment is turned off. 259 */ 260 bufSize = sizeof(val); 261 ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL, 262 &valueType, (LPBYTE) &val, &bufSize); 263 if (ret != ERROR_SUCCESS) { 264 goto err; 265 } 266 /* 267 * Return a custom time zone name if auto-daylight time 268 * adjustment is disabled. 269 */ 270 if (val == 1) { 271 customZoneName(dtzi.Bias, winZoneName); 272 (void) RegCloseKey(hKey); 273 return VALUE_GMTOFFSET; 274 } 275 276 bufSize = MAX_ZONE_CHAR; 277 ret = RegQueryValueExA(hKey, "TimeZoneKeyName",NULL, 278 &valueType, (LPBYTE)winZoneName, &bufSize); 279 if (ret != ERROR_SUCCESS) { 280 goto err; 281 } 282 (void) RegCloseKey(hKey); 283 return VALUE_KEY; 284 } 285 } 286 } 287 288 /* 289 * Fall back to GetTimeZoneInformation 290 */ 291 timeType = GetTimeZoneInformation(&tzi); 292 if (timeType == TIME_ZONE_ID_INVALID) { 293 goto err; 294 } 295 296 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 297 KEY_READ, (PHKEY)&hKey); 298 if (ret == ERROR_SUCCESS) { 299 DWORD val; 300 DWORD bufSize; 301 302 /* 303 * Determine if auto-daylight time adjustment is turned off. 304 */ 305 bufSize = sizeof(val); 306 ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL, 307 &valueType, (LPBYTE) &val, &bufSize); 308 if (ret != ERROR_SUCCESS) { 309 /* 310 * Try the old key name. 311 */ 312 bufSize = sizeof(val); 313 ret = RegQueryValueExA(hKey, "DisableAutoDaylightTimeSet", NULL, 314 &valueType, (LPBYTE) &val, &bufSize); 315 } 316 317 if (ret == ERROR_SUCCESS) { 318 int daylightSavingsUpdateDisabledOther = (val == 1 && tzi.DaylightDate.wMonth != 0); 319 int daylightSavingsUpdateDisabledVista = (val == 1); 320 int daylightSavingsUpdateDisabled 321 = (isVistaOrLater ? daylightSavingsUpdateDisabledVista : daylightSavingsUpdateDisabledOther); 322 323 if (daylightSavingsUpdateDisabled) { 324 (void) RegCloseKey(hKey); 325 customZoneName(tzi.Bias, winZoneName); 326 return VALUE_GMTOFFSET; 327 } 328 } 329 330 /* 331 * Win32 problem: If the length of the standard time name is equal 332 * to (or probably longer than) 32 in the registry, 333 * GetTimeZoneInformation() on NT returns a null string as its 334 * standard time name. We need to work around this problem by 335 * getting the same information from the TimeZoneInformation 336 * registry. 337 */ 338 if (tzi.StandardName[0] == 0) { 339 bufSize = sizeof(stdNameInReg); 340 ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType, 341 (LPBYTE) stdNameInReg, &bufSize); 342 if (ret != ERROR_SUCCESS) { 343 goto err; 344 } 345 stdNamePtr = stdNameInReg; 346 } 347 (void) RegCloseKey(hKey); 348 } 349 350 /* 351 * Open the "Time Zones" registry. 352 */ 353 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey); 354 if (ret != ERROR_SUCCESS) { 355 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey); 356 /* 357 * If both failed, then give up. 358 */ 359 if (ret != ERROR_SUCCESS) { 360 return VALUE_UNKNOWN; 361 } 362 } 363 364 /* 365 * Get the number of subkeys of the "Time Zones" registry for 366 * enumeration. 367 */ 368 ret = RegQueryInfoKey(hKey, NULL, NULL, NULL, &nSubKeys, 369 NULL, NULL, NULL, NULL, NULL, NULL, NULL); 370 if (ret != ERROR_SUCCESS) { 371 goto err; 372 } 373 374 /* 375 * Compare to the "Std" value of each subkey and find the entry that 376 * matches the current control panel setting. 377 */ 378 onlyMapID = 0; 379 for (i = 0; i < nSubKeys; ++i) { 380 DWORD size = sizeof(subKeyName); 381 ret = RegEnumKeyEx(hKey, i, subKeyName, &size, NULL, NULL, NULL, NULL); 382 if (ret != ERROR_SUCCESS) { 383 goto err; 384 } 385 ret = RegOpenKeyEx(hKey, subKeyName, 0, KEY_READ, (PHKEY)&hSubKey); 386 if (ret != ERROR_SUCCESS) { 387 goto err; 388 } 389 390 size = sizeof(szValue); 391 ret = getValueInRegistry(hSubKey, STD_NAME, &valueType, 392 szValue, &size); 393 if (ret != ERROR_SUCCESS) { 394 /* 395 * NT 4.0 SP3 fails here since it doesn't have the "Std" 396 * entry in the Time Zones registry. 397 */ 398 RegCloseKey(hSubKey); 399 onlyMapID = 1; 400 ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, (PHKEY)&hSubKey); 401 if (ret != ERROR_SUCCESS) { 402 goto err; 403 } 404 break; 405 } 406 407 if (wcscmp((WCHAR *)szValue, stdNamePtr) == 0) { 408 /* 409 * Some localized Win32 platforms use a same name to 410 * different time zones. So, we can't rely only on the name 411 * here. We need to check GMT offsets and transition dates 412 * to make sure it's the registry of the current time 413 * zone. 414 */ 415 DWORD tziValueSize = sizeof(tempTzi); 416 ret = RegQueryValueEx(hSubKey, "TZI", NULL, &valueType, 417 (unsigned char *) &tempTzi, &tziValueSize); 418 if (ret == ERROR_SUCCESS) { 419 if ((tzi.Bias != tempTzi.bias) || 420 (memcmp((const void *) &tzi.StandardDate, 421 (const void *) &tempTzi.stdDate, 422 sizeof(SYSTEMTIME)) != 0)) { 423 goto out; 424 } 425 426 if (tzi.DaylightBias != 0) { 427 if ((tzi.DaylightBias != tempTzi.dstBias) || 428 (memcmp((const void *) &tzi.DaylightDate, 429 (const void *) &tempTzi.dstDate, 430 sizeof(SYSTEMTIME)) != 0)) { 431 goto out; 432 } 433 } 434 } 435 436 /* 437 * found matched record, terminate search 438 */ 439 strcpy(winZoneName, subKeyName); 440 break; 441 } 442 out: 443 (void) RegCloseKey(hSubKey); 444 } 445 446 /* 447 * Get the "MapID" value of the registry to be able to eliminate 448 * duplicated key names later. 449 */ 450 valueSize = MAX_MAPID_LENGTH; 451 ret = RegQueryValueExA(hSubKey, "MapID", NULL, &valueType, winMapID, &valueSize); 452 (void) RegCloseKey(hSubKey); 453 (void) RegCloseKey(hKey); 454 455 if (ret != ERROR_SUCCESS) { 456 /* 457 * Vista doesn't have mapID. VALUE_UNKNOWN should be returned 458 * only for Windows NT. 459 */ 460 if (onlyMapID == 1) { 461 return VALUE_UNKNOWN; 462 } 463 } 464 465 return VALUE_KEY; 466 467 err: 468 if (hKey != NULL) { 469 (void) RegCloseKey(hKey); 470 } 471 return VALUE_UNKNOWN; 472 } 473 474 /* 475 * The mapping table file name. 476 */ 477 #define MAPPINGS_FILE "\\lib\\tzmappings" 478 479 /* 480 * Index values for the mapping table. 481 */ 482 #define TZ_WIN_NAME 0 483 #define TZ_MAPID 1 484 #define TZ_REGION 2 485 #define TZ_JAVA_NAME 3 486 487 #define TZ_NITEMS 4 /* number of items (fields) */ 488 489 /* 490 * Looks up the mapping table (tzmappings) and returns a Java time 491 * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is 492 * returned. 493 * 494 * value_type is one of the following values: 495 * VALUE_KEY for exact key matching 496 * VALUE_MAPID for MapID (this is 497 * required for the old Windows, such as NT 4.0 SP3). 498 */ 499 static char *matchJavaTZ(const char *java_home_dir, int value_type, char *tzName, 500 char *mapID) 501 { 502 int line; 503 int IDmatched = 0; 504 FILE *fp; 505 char *javaTZName = NULL; 506 char *items[TZ_NITEMS]; 507 char *mapFileName; 508 char lineBuffer[MAX_ZONE_CHAR * 4]; 509 int noMapID = *mapID == '\0'; /* no mapID on Vista and later */ 510 511 mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1); 512 if (mapFileName == NULL) { 513 return NULL; 514 } 515 strcpy(mapFileName, java_home_dir); 516 strcat(mapFileName, MAPPINGS_FILE); 517 518 if ((fp = fopen(mapFileName, "r")) == NULL) { 519 jio_fprintf(stderr, "can't open %s.\n", mapFileName); 520 free((void *) mapFileName); 521 return NULL; 522 } 523 free((void *) mapFileName); 524 525 line = 0; 526 while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) { 527 char *start, *idx, *endp; 528 int itemIndex = 0; 529 530 line++; 531 start = idx = lineBuffer; 532 endp = &lineBuffer[sizeof(lineBuffer)]; 533 534 /* 535 * Ignore comment and blank lines. 536 */ 537 if (*idx == '#' || *idx == '\n') { 538 continue; 539 } 540 541 for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) { 542 items[itemIndex] = start; 543 while (*idx && *idx != ':') { 544 if (++idx >= endp) { 545 goto illegal_format; 546 } 547 } 548 if (*idx == '\0') { 549 goto illegal_format; 550 } 551 *idx++ = '\0'; 552 start = idx; 553 } 554 555 if (*idx != '\n') { 556 goto illegal_format; 557 } 558 559 if (noMapID || strcmp(mapID, items[TZ_MAPID]) == 0) { 560 /* 561 * When there's no mapID, we need to scan items until the 562 * exact match is found or the end of data is detected. 563 */ 564 if (!noMapID) { 565 IDmatched = 1; 566 } 567 if (strcmp(items[TZ_WIN_NAME], tzName) == 0) { 568 /* 569 * Found the time zone in the mapping table. 570 */ 571 javaTZName = _strdup(items[TZ_JAVA_NAME]); 572 break; 573 } 574 } else { 575 if (IDmatched == 1) { 576 /* 577 * No need to look up the mapping table further. 578 */ 579 break; 580 } 581 } 582 } 583 fclose(fp); 584 585 return javaTZName; 586 587 illegal_format: 588 (void) fclose(fp); 589 jio_fprintf(stderr, "tzmappings: Illegal format at line %d.\n", line); 590 return NULL; 591 } 592 593 /* 594 * Detects the platform time zone which maps to a Java time zone ID. 595 */ 596 char *findJavaTZ_md(const char *java_home_dir) 597 { 598 char winZoneName[MAX_ZONE_CHAR]; 599 char winMapID[MAX_MAPID_LENGTH]; 600 char *std_timezone = NULL; 601 int result; 602 603 winMapID[0] = 0; 604 result = getWinTimeZone(winZoneName, winMapID); 605 606 if (result != VALUE_UNKNOWN) { 607 if (result == VALUE_GMTOFFSET) { 608 std_timezone = _strdup(winZoneName); 609 } else { 610 std_timezone = matchJavaTZ(java_home_dir, result, 611 winZoneName, winMapID); 612 if (std_timezone == NULL) { 613 std_timezone = getGMTOffsetID(); 614 } 615 } 616 } 617 return std_timezone; 618 } 619 620 /** 621 * Returns a GMT-offset-based time zone ID. 622 */ 623 char * 624 getGMTOffsetID() 625 { 626 LONG bias = 0; 627 LONG ret; 628 HANDLE hKey = NULL; 629 char zonename[32]; 630 631 // Obtain the current GMT offset value of ActiveTimeBias. 632 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 633 KEY_READ, (PHKEY)&hKey); 634 if (ret == ERROR_SUCCESS) { 635 DWORD val; 636 DWORD bufSize = sizeof(val); 637 ULONG valueType = 0; 638 ret = RegQueryValueExA(hKey, "ActiveTimeBias", 639 NULL, &valueType, (LPBYTE) &val, &bufSize); 640 if (ret == ERROR_SUCCESS) { 641 bias = (LONG) val; 642 } 643 (void) RegCloseKey(hKey); 644 } 645 646 // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation. 647 // Note: Bias doesn't reflect current daylight saving. 648 if (ret != ERROR_SUCCESS) { 649 TIME_ZONE_INFORMATION tzi; 650 if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) { 651 bias = tzi.Bias; 652 } 653 } 654 655 customZoneName(bias, zonename); 656 return _strdup(zonename); 657 }