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 40 #define NT_TZ_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones" 41 #define WIN_TZ_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones" 42 #define WIN_CURRENT_TZ_KEY "System\\CurrentControlSet\\Control\\TimeZoneInformation" 43 44 typedef struct _TziValue { 45 LONG bias; 46 LONG stdBias; 47 LONG dstBias; 48 SYSTEMTIME stdDate; 49 SYSTEMTIME dstDate; 50 } TziValue; 51 52 /* 53 * Registry key names 54 */ 55 static void *keyNames[] = { 56 (void *) L"StandardName", 57 (void *) "StandardName", 58 (void *) L"Std", 59 (void *) "Std" 60 }; 61 62 /* 63 * Indices to keyNames[] 64 */ 65 #define STANDARD_NAME 0 66 #define STD_NAME 2 67 68 /* 69 * Calls RegQueryValueEx() to get the value for the specified key. If 70 * the platform is NT, 2000 or XP, it calls the Unicode 71 * version. Otherwise, it calls the ANSI version and converts the 72 * value to Unicode. In this case, it assumes that the current ANSI 73 * Code Page is the same as the native platform code page (e.g., Code 74 * Page 932 for the Japanese Windows systems. 75 * 76 * `keyIndex' is an index value to the keyNames in Unicode 77 * (WCHAR). `keyIndex' + 1 points to its ANSI value. 78 * 79 * Returns the status value. ERROR_SUCCESS if succeeded, a 80 * non-ERROR_SUCCESS value otherwise. 81 */ 82 static LONG 83 getValueInRegistry(HKEY hKey, 84 int keyIndex, 85 LPDWORD typePtr, 86 LPBYTE buf, 87 LPDWORD bufLengthPtr) 88 { 89 LONG ret; 90 DWORD bufLength = *bufLengthPtr; 91 char val[MAX_ZONE_CHAR]; 92 DWORD valSize; 93 int len; 94 95 *typePtr = 0; 96 ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL, 97 typePtr, buf, bufLengthPtr); 98 if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) { 99 return ret; 100 } 101 102 valSize = sizeof(val); 103 ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL, 104 typePtr, val, &valSize); 105 if (ret != ERROR_SUCCESS) { 106 return ret; 107 } 108 if (*typePtr != REG_SZ) { 109 return ERROR_BADKEY; 110 } 111 112 len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, 113 (LPCSTR) val, -1, 114 (LPWSTR) buf, bufLength/sizeof(WCHAR)); 115 if (len <= 0) { 116 return ERROR_BADKEY; 117 } 118 return ERROR_SUCCESS; 119 } 120 121 /* 122 * Produces custom name "GMT+hh:mm" from the given bias in buffer. 123 */ 124 static void customZoneName(LONG bias, char *buffer) { 125 LONG gmtOffset; 126 int sign; 127 128 if (bias > 0) { 129 gmtOffset = bias; 130 sign = -1; 131 } else { 132 gmtOffset = -bias; 133 sign = 1; 134 } 135 if (gmtOffset != 0) { 136 sprintf(buffer, "GMT%c%02d:%02d", 137 ((sign >= 0) ? '+' : '-'), 138 gmtOffset / 60, 139 gmtOffset % 60); 140 } else { 141 strcpy(buffer, "GMT"); 142 } 143 } 144 145 /* 146 * Gets the current time zone entry in the "Time Zones" registry. 147 */ 148 static int getWinTimeZone(char *winZoneName, char *winMapID) 149 { 150 DYNAMIC_TIME_ZONE_INFORMATION dtzi; 151 DWORD timeType; 152 DWORD bufSize; 153 DWORD val; 154 HANDLE hKey = NULL; 155 LONG ret; 156 ULONG valueType; 157 158 /* 159 * Get the dynamic time zone information so that time zone redirection 160 * can be supported. (see JDK-7044727) 161 */ 162 timeType = GetDynamicTimeZoneInformation(&dtzi); 163 if (timeType == TIME_ZONE_ID_INVALID) { 164 goto err; 165 } 166 167 /* 168 * Make sure TimeZoneKeyName is available from the API call. If 169 * DynamicDaylightTime is disabled, return a custom time zone name 170 * based on the GMT offset. Otherwise, return the TimeZoneKeyName 171 * value. 172 */ 173 if (dtzi.TimeZoneKeyName[0] != 0) { 174 if (dtzi.DynamicDaylightTimeDisabled) { 175 customZoneName(dtzi.Bias, winZoneName); 176 return VALUE_GMTOFFSET; 177 } 178 wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR); 179 return VALUE_KEY; 180 } 181 182 /* 183 * If TimeZoneKeyName is not available, check whether StandardName 184 * is available to fall back to the older API GetTimeZoneInformation. 185 * If not, directly read the value from registry keys. 186 */ 187 if (dtzi.StandardName[0] == 0) { 188 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 189 KEY_READ, (PHKEY)&hKey); 190 if (ret != ERROR_SUCCESS) { 191 goto err; 192 } 193 194 /* 195 * Determine if auto-daylight time adjustment is turned off. 196 */ 197 bufSize = sizeof(val); 198 ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL, 199 &valueType, (LPBYTE) &val, &bufSize); 200 if (ret != ERROR_SUCCESS) { 201 goto err; 202 } 203 /* 204 * Return a custom time zone name if auto-daylight time adjustment 205 * is disabled. 206 */ 207 if (val == 1) { 208 customZoneName(dtzi.Bias, winZoneName); 209 (void) RegCloseKey(hKey); 210 return VALUE_GMTOFFSET; 211 } 212 213 bufSize = MAX_ZONE_CHAR; 214 ret = RegQueryValueExA(hKey, "TimeZoneKeyName", NULL, 215 &valueType, (LPBYTE) winZoneName, &bufSize); 216 if (ret != ERROR_SUCCESS) { 217 goto err; 218 } 219 (void) RegCloseKey(hKey); 220 return VALUE_KEY; 221 } else { 222 /* 223 * Fall back to GetTimeZoneInformation 224 */ 225 TIME_ZONE_INFORMATION tzi; 226 HANDLE hSubKey = NULL; 227 DWORD nSubKeys, i; 228 ULONG valueType; 229 TCHAR subKeyName[MAX_ZONE_CHAR]; 230 TCHAR szValue[MAX_ZONE_CHAR]; 231 WCHAR stdNameInReg[MAX_ZONE_CHAR]; 232 TziValue tempTzi; 233 WCHAR *stdNamePtr = tzi.StandardName; 234 DWORD valueSize; 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 /* 376 * Get the "MapID" value of the registry to be able to eliminate 377 * duplicated key names later. 378 */ 379 valueSize = MAX_MAPID_LENGTH; 380 ret = RegQueryValueExA(hSubKey, "MapID", NULL, &valueType, winMapID, &valueSize); 381 (void) RegCloseKey(hSubKey); 382 (void) RegCloseKey(hKey); 383 384 if (ret != ERROR_SUCCESS) { 385 /* 386 * Vista doesn't have mapID. VALUE_UNKNOWN should be returned 387 * only for Windows NT. 388 */ 389 if (onlyMapID == 1) { 390 return VALUE_UNKNOWN; 391 } 392 } 393 } 394 395 return VALUE_KEY; 396 397 err: 398 if (hKey != NULL) { 399 (void) RegCloseKey(hKey); 400 } 401 return VALUE_UNKNOWN; 402 } 403 404 /* 405 * The mapping table file name. 406 */ 407 #define MAPPINGS_FILE "\\lib\\tzmappings" 408 409 /* 410 * Index values for the mapping table. 411 */ 412 #define TZ_WIN_NAME 0 413 #define TZ_MAPID 1 414 #define TZ_REGION 2 415 #define TZ_JAVA_NAME 3 416 417 #define TZ_NITEMS 4 /* number of items (fields) */ 418 419 /* 420 * Looks up the mapping table (tzmappings) and returns a Java time 421 * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is 422 * returned. 423 * 424 * value_type is one of the following values: 425 * VALUE_KEY for exact key matching 426 * VALUE_MAPID for MapID (this is 427 * required for the old Windows, such as NT 4.0 SP3). 428 */ 429 static char *matchJavaTZ(const char *java_home_dir, int value_type, char *tzName, 430 char *mapID) 431 { 432 int line; 433 int IDmatched = 0; 434 FILE *fp; 435 char *javaTZName = NULL; 436 char *items[TZ_NITEMS]; 437 char *mapFileName; 438 char lineBuffer[MAX_ZONE_CHAR * 4]; 439 int noMapID = *mapID == '\0'; /* no mapID on Vista and later */ 440 int offset = 0; 441 const char* errorMessage = "unknown error"; 442 443 mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1); 444 if (mapFileName == NULL) { 445 return NULL; 446 } 447 strcpy(mapFileName, java_home_dir); 448 strcat(mapFileName, MAPPINGS_FILE); 449 450 if ((fp = fopen(mapFileName, "r")) == NULL) { 451 jio_fprintf(stderr, "can't open %s.\n", mapFileName); 452 free((void *) mapFileName); 453 return NULL; 454 } 455 free((void *) mapFileName); 456 457 line = 0; 458 while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) { 459 char *start, *idx, *endp; 460 int itemIndex = 0; 461 462 line++; 463 start = idx = lineBuffer; 464 endp = &lineBuffer[sizeof(lineBuffer)]; 465 466 /* 467 * Ignore comment and blank lines. 468 */ 469 if (*idx == '#' || *idx == '\n') { 470 continue; 471 } 472 473 for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) { 474 items[itemIndex] = start; 475 while (*idx && *idx != ':') { 476 if (++idx >= endp) { 477 errorMessage = "premature end of line"; 478 offset = (int)(idx - lineBuffer); 479 goto illegal_format; 480 } 481 } 482 if (*idx == '\0') { 483 errorMessage = "illegal character \0 found"; 484 offset = (int)(idx - lineBuffer); 485 goto illegal_format; 486 } 487 *idx++ = '\0'; 488 start = idx; 489 } 490 491 if (*idx != '\n') { 492 errorMessage = "illegal non-newline character found"; 493 offset = (int)(idx - lineBuffer); 494 goto illegal_format; 495 } 496 497 if (noMapID || strcmp(mapID, items[TZ_MAPID]) == 0) { 498 /* 499 * When there's no mapID, we need to scan items until the 500 * exact match is found or the end of data is detected. 501 */ 502 if (!noMapID) { 503 IDmatched = 1; 504 } 505 if (strcmp(items[TZ_WIN_NAME], tzName) == 0) { 506 /* 507 * Found the time zone in the mapping table. 508 */ 509 javaTZName = _strdup(items[TZ_JAVA_NAME]); 510 break; 511 } 512 } else { 513 if (IDmatched == 1) { 514 /* 515 * No need to look up the mapping table further. 516 */ 517 break; 518 } 519 } 520 } 521 fclose(fp); 522 523 return javaTZName; 524 525 illegal_format: 526 (void) fclose(fp); 527 jio_fprintf(stderr, "tzmappings: Illegal format at line %d (%s at offset %d, file: %s).\n", 528 line, message, offset, mapFileName); 529 return NULL; 530 } 531 532 /* 533 * Detects the platform time zone which maps to a Java time zone ID. 534 */ 535 char *findJavaTZ_md(const char *java_home_dir) 536 { 537 char winZoneName[MAX_ZONE_CHAR]; 538 char winMapID[MAX_MAPID_LENGTH]; 539 char *std_timezone = NULL; 540 int result; 541 542 winMapID[0] = 0; 543 result = getWinTimeZone(winZoneName, winMapID); 544 545 if (result != VALUE_UNKNOWN) { 546 if (result == VALUE_GMTOFFSET) { 547 std_timezone = _strdup(winZoneName); 548 } else { 549 std_timezone = matchJavaTZ(java_home_dir, result, 550 winZoneName, winMapID); 551 if (std_timezone == NULL) { 552 std_timezone = getGMTOffsetID(); 553 } 554 } 555 } 556 return std_timezone; 557 } 558 559 /** 560 * Returns a GMT-offset-based time zone ID. 561 */ 562 char * 563 getGMTOffsetID() 564 { 565 LONG bias = 0; 566 LONG ret; 567 HANDLE hKey = NULL; 568 char zonename[32]; 569 570 // Obtain the current GMT offset value of ActiveTimeBias. 571 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 572 KEY_READ, (PHKEY)&hKey); 573 if (ret == ERROR_SUCCESS) { 574 DWORD val; 575 DWORD bufSize = sizeof(val); 576 ULONG valueType = 0; 577 ret = RegQueryValueExA(hKey, "ActiveTimeBias", 578 NULL, &valueType, (LPBYTE) &val, &bufSize); 579 if (ret == ERROR_SUCCESS) { 580 bias = (LONG) val; 581 } 582 (void) RegCloseKey(hKey); 583 } 584 585 // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation. 586 // Note: Bias doesn't reflect current daylight saving. 587 if (ret != ERROR_SUCCESS) { 588 TIME_ZONE_INFORMATION tzi; 589 if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) { 590 bias = tzi.Bias; 591 } 592 } 593 594 customZoneName(bias, zonename); 595 return _strdup(zonename); 596 }