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 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 441 mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1); 442 if (mapFileName == NULL) { 443 return NULL; 444 } 445 strcpy(mapFileName, java_home_dir); 446 strcat(mapFileName, MAPPINGS_FILE); 447 448 if ((fp = fopen(mapFileName, "r")) == NULL) { 449 jio_fprintf(stderr, "can't open %s.\n", mapFileName); 450 free((void *) mapFileName); 451 return NULL; 452 } 453 free((void *) mapFileName); 454 455 line = 0; 456 while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) { 457 char *start, *idx, *endp; 458 int itemIndex = 0; 459 460 line++; 461 start = idx = lineBuffer; 462 endp = &lineBuffer[sizeof(lineBuffer)]; 463 464 /* 465 * Ignore comment and blank lines. 466 */ 467 if (*idx == '#' || *idx == '\n') { 468 continue; 469 } 470 471 for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) { 472 items[itemIndex] = start; 473 while (*idx && *idx != ':') { 474 if (++idx >= endp) { 475 goto illegal_format; 476 } 477 } 478 if (*idx == '\0') { 479 goto illegal_format; 480 } 481 *idx++ = '\0'; 482 start = idx; 483 } 484 485 if (*idx != '\n') { 486 goto illegal_format; 487 } 488 489 if (noMapID || strcmp(mapID, items[TZ_MAPID]) == 0) { 490 /* 491 * When there's no mapID, we need to scan items until the 492 * exact match is found or the end of data is detected. 493 */ 494 if (!noMapID) { 495 IDmatched = 1; 496 } 497 if (strcmp(items[TZ_WIN_NAME], tzName) == 0) { 498 /* 499 * Found the time zone in the mapping table. 500 */ 501 javaTZName = _strdup(items[TZ_JAVA_NAME]); 502 break; 503 } 504 } else { 505 if (IDmatched == 1) { 506 /* 507 * No need to look up the mapping table further. 508 */ 509 break; 510 } 511 } 512 } 513 fclose(fp); 514 515 return javaTZName; 516 517 illegal_format: 518 (void) fclose(fp); 519 jio_fprintf(stderr, "tzmappings: Illegal format at line %d.\n", line); 520 return NULL; 521 } 522 523 /* 524 * Detects the platform time zone which maps to a Java time zone ID. 525 */ 526 char *findJavaTZ_md(const char *java_home_dir) 527 { 528 char winZoneName[MAX_ZONE_CHAR]; 529 char winMapID[MAX_MAPID_LENGTH]; 530 char *std_timezone = NULL; 531 int result; 532 533 winMapID[0] = 0; 534 result = getWinTimeZone(winZoneName, winMapID); 535 536 if (result != VALUE_UNKNOWN) { 537 if (result == VALUE_GMTOFFSET) { 538 std_timezone = _strdup(winZoneName); 539 } else { 540 std_timezone = matchJavaTZ(java_home_dir, result, 541 winZoneName, winMapID); 542 if (std_timezone == NULL) { 543 std_timezone = getGMTOffsetID(); 544 } 545 } 546 } 547 return std_timezone; 548 } 549 550 /** 551 * Returns a GMT-offset-based time zone ID. 552 */ 553 char * 554 getGMTOffsetID() 555 { 556 LONG bias = 0; 557 LONG ret; 558 HANDLE hKey = NULL; 559 char zonename[32]; 560 561 // Obtain the current GMT offset value of ActiveTimeBias. 562 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 563 KEY_READ, (PHKEY)&hKey); 564 if (ret == ERROR_SUCCESS) { 565 DWORD val; 566 DWORD bufSize = sizeof(val); 567 ULONG valueType = 0; 568 ret = RegQueryValueExA(hKey, "ActiveTimeBias", 569 NULL, &valueType, (LPBYTE) &val, &bufSize); 570 if (ret == ERROR_SUCCESS) { 571 bias = (LONG) val; 572 } 573 (void) RegCloseKey(hKey); 574 } 575 576 // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation. 577 // Note: Bias doesn't reflect current daylight saving. 578 if (ret != ERROR_SUCCESS) { 579 TIME_ZONE_INFORMATION tzi; 580 if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) { 581 bias = tzi.Bias; 582 } 583 } 584 585 customZoneName(bias, zonename); 586 return _strdup(zonename); 587 }