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 TIME_ZONE_INFORMATION tzi; 151 OSVERSIONINFO ver; 152 int onlyMapID; 153 HANDLE hKey = NULL, hSubKey = NULL; 154 LONG ret; 155 DWORD nSubKeys, i; 156 ULONG valueType; 157 TCHAR subKeyName[MAX_ZONE_CHAR]; 158 TCHAR szValue[MAX_ZONE_CHAR]; 159 WCHAR stdNameInReg[MAX_ZONE_CHAR]; 160 TziValue tempTzi; 161 WCHAR *stdNamePtr = tzi.StandardName; 162 DWORD valueSize; 163 DWORD timeType; 164 int isVista; 165 166 /* 167 * Get the current time zone setting of the platform. 168 */ 169 timeType = GetTimeZoneInformation(&tzi); 170 if (timeType == TIME_ZONE_ID_INVALID) { 171 goto err; 172 } 173 174 /* 175 * Determine if this is an NT system. 176 */ 177 ver.dwOSVersionInfoSize = sizeof(ver); 178 GetVersionEx(&ver); 179 isVista = ver.dwMajorVersion >= 6; 180 181 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 182 KEY_READ, (PHKEY)&hKey); 183 if (ret == ERROR_SUCCESS) { 184 DWORD val; 185 DWORD bufSize; 186 187 /* 188 * Determine if auto-daylight time adjustment is turned off. 189 */ 190 valueType = 0; 191 bufSize = sizeof(val); 192 ret = RegQueryValueExA(hKey, "DisableAutoDaylightTimeSet", 193 NULL, &valueType, (LPBYTE) &val, &bufSize); 194 /* 195 * Vista uses the different key name. 196 */ 197 if (ret != ERROR_SUCCESS) { 198 bufSize = sizeof(val); 199 ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", 200 NULL, &valueType, (LPBYTE) &val, &bufSize); 201 } 202 203 if (ret == ERROR_SUCCESS) { 204 int daylightSavingsUpdateDisabledOther = val == 1 && tzi.DaylightDate.wMonth != 0; 205 int daylightSavingsUpdateDisabledVista = val == 1; 206 int daylightSavingsUpdateDisabled = isVista ? daylightSavingsUpdateDisabledVista : daylightSavingsUpdateDisabledOther; 207 208 if (daylightSavingsUpdateDisabled) { 209 (void) RegCloseKey(hKey); 210 customZoneName(tzi.Bias, winZoneName); 211 return VALUE_GMTOFFSET; 212 } 213 } 214 215 /* 216 * Vista has the key for the current "Time Zones" entry. 217 */ 218 if (isVista) { 219 valueType = 0; 220 bufSize = MAX_ZONE_CHAR; 221 ret = RegQueryValueExA(hKey, "TimeZoneKeyName", NULL, 222 &valueType, (LPBYTE) winZoneName, &bufSize); 223 if (ret != ERROR_SUCCESS) { 224 goto err; 225 } 226 (void) RegCloseKey(hKey); 227 return VALUE_KEY; 228 } 229 230 /* 231 * Win32 problem: If the length of the standard time name is equal 232 * to (or probably longer than) 32 in the registry, 233 * GetTimeZoneInformation() on NT returns a null string as its 234 * standard time name. We need to work around this problem by 235 * getting the same information from the TimeZoneInformation 236 * registry. The function on Win98 seems to return its key name. 237 * We can't do anything in that case. 238 */ 239 if (tzi.StandardName[0] == 0) { 240 bufSize = sizeof(stdNameInReg); 241 ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType, 242 (LPBYTE) stdNameInReg, &bufSize); 243 if (ret != ERROR_SUCCESS) { 244 goto err; 245 } 246 stdNamePtr = stdNameInReg; 247 } 248 (void) RegCloseKey(hKey); 249 } 250 251 /* 252 * Open the "Time Zones" registry. 253 */ 254 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey); 255 if (ret != ERROR_SUCCESS) { 256 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey); 257 /* 258 * If both failed, then give up. 259 */ 260 if (ret != ERROR_SUCCESS) { 261 return VALUE_UNKNOWN; 262 } 263 } 264 265 /* 266 * Get the number of subkeys of the "Time Zones" registry for 267 * enumeration. 268 */ 269 ret = RegQueryInfoKey(hKey, NULL, NULL, NULL, &nSubKeys, 270 NULL, NULL, NULL, NULL, NULL, NULL, NULL); 271 if (ret != ERROR_SUCCESS) { 272 goto err; 273 } 274 275 /* 276 * Compare to the "Std" value of each subkey and find the entry that 277 * matches the current control panel setting. 278 */ 279 onlyMapID = 0; 280 for (i = 0; i < nSubKeys; ++i) { 281 DWORD size = sizeof(subKeyName); 282 ret = RegEnumKeyEx(hKey, i, subKeyName, &size, NULL, NULL, NULL, NULL); 283 if (ret != ERROR_SUCCESS) { 284 goto err; 285 } 286 ret = RegOpenKeyEx(hKey, subKeyName, 0, KEY_READ, (PHKEY)&hSubKey); 287 if (ret != ERROR_SUCCESS) { 288 goto err; 289 } 290 291 size = sizeof(szValue); 292 ret = getValueInRegistry(hSubKey, STD_NAME, &valueType, 293 szValue, &size); 294 if (ret != ERROR_SUCCESS) { 295 /* 296 * NT 4.0 SP3 fails here since it doesn't have the "Std" 297 * entry in the Time Zones registry. 298 */ 299 RegCloseKey(hSubKey); 300 onlyMapID = 1; 301 ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, (PHKEY)&hSubKey); 302 if (ret != ERROR_SUCCESS) { 303 goto err; 304 } 305 break; 306 } 307 308 if (wcscmp((WCHAR *)szValue, stdNamePtr) == 0) { 309 /* 310 * Some localized Win32 platforms use a same name to 311 * different time zones. So, we can't rely only on the name 312 * here. We need to check GMT offsets and transition dates 313 * to make sure it's the registry of the current time 314 * zone. 315 */ 316 DWORD tziValueSize = sizeof(tempTzi); 317 ret = RegQueryValueEx(hSubKey, "TZI", NULL, &valueType, 318 (unsigned char *) &tempTzi, &tziValueSize); 319 if (ret == ERROR_SUCCESS) { 320 if ((tzi.Bias != tempTzi.bias) || 321 (memcmp((const void *) &tzi.StandardDate, 322 (const void *) &tempTzi.stdDate, 323 sizeof(SYSTEMTIME)) != 0)) { 324 goto out; 325 } 326 327 if (tzi.DaylightBias != 0) { 328 if ((tzi.DaylightBias != tempTzi.dstBias) || 329 (memcmp((const void *) &tzi.DaylightDate, 330 (const void *) &tempTzi.dstDate, 331 sizeof(SYSTEMTIME)) != 0)) { 332 goto out; 333 } 334 } 335 } 336 337 /* 338 * found matched record, terminate search 339 */ 340 strcpy(winZoneName, subKeyName); 341 break; 342 } 343 out: 344 (void) RegCloseKey(hSubKey); 345 } 346 347 /* 348 * Get the "MapID" value of the registry to be able to eliminate 349 * duplicated key names later. 350 */ 351 valueSize = MAX_MAPID_LENGTH; 352 ret = RegQueryValueExA(hSubKey, "MapID", NULL, &valueType, winMapID, &valueSize); 353 (void) RegCloseKey(hSubKey); 354 (void) RegCloseKey(hKey); 355 356 if (ret != ERROR_SUCCESS) { 357 /* 358 * Vista doesn't have mapID. VALUE_UNKNOWN should be returned 359 * only for Windows NT. 360 */ 361 if (onlyMapID == 1) { 362 return VALUE_UNKNOWN; 363 } 364 } 365 366 return VALUE_KEY; 367 368 err: 369 if (hKey != NULL) { 370 (void) RegCloseKey(hKey); 371 } 372 return VALUE_UNKNOWN; 373 } 374 375 /* 376 * The mapping table file name. 377 */ 378 #define MAPPINGS_FILE "\\lib\\tzmappings" 379 380 /* 381 * Index values for the mapping table. 382 */ 383 #define TZ_WIN_NAME 0 384 #define TZ_MAPID 1 385 #define TZ_REGION 2 386 #define TZ_JAVA_NAME 3 387 388 #define TZ_NITEMS 4 /* number of items (fields) */ 389 390 /* 391 * Looks up the mapping table (tzmappings) and returns a Java time 392 * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is 393 * returned. 394 * 395 * value_type is one of the following values: 396 * VALUE_KEY for exact key matching 397 * VALUE_MAPID for MapID (this is 398 * required for the old Windows, such as NT 4.0 SP3). 399 */ 400 static char *matchJavaTZ(const char *java_home_dir, int value_type, char *tzName, 401 char *mapID) 402 { 403 int line; 404 int IDmatched = 0; 405 FILE *fp; 406 char *javaTZName = NULL; 407 char *items[TZ_NITEMS]; 408 char *mapFileName; 409 char lineBuffer[MAX_ZONE_CHAR * 4]; 410 int noMapID = *mapID == '\0'; /* no mapID on Vista and later */ 411 412 mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1); 413 if (mapFileName == NULL) { 414 return NULL; 415 } 416 strcpy(mapFileName, java_home_dir); 417 strcat(mapFileName, MAPPINGS_FILE); 418 419 if ((fp = fopen(mapFileName, "r")) == NULL) { 420 jio_fprintf(stderr, "can't open %s.\n", mapFileName); 421 free((void *) mapFileName); 422 return NULL; 423 } 424 free((void *) mapFileName); 425 426 line = 0; 427 while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) { 428 char *start, *idx, *endp; 429 int itemIndex = 0; 430 431 line++; 432 start = idx = lineBuffer; 433 endp = &lineBuffer[sizeof(lineBuffer)]; 434 435 /* 436 * Ignore comment and blank lines. 437 */ 438 if (*idx == '#' || *idx == '\n') { 439 continue; 440 } 441 442 for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) { 443 items[itemIndex] = start; 444 while (*idx && *idx != ':') { 445 if (++idx >= endp) { 446 goto illegal_format; 447 } 448 } 449 if (*idx == '\0') { 450 goto illegal_format; 451 } 452 *idx++ = '\0'; 453 start = idx; 454 } 455 456 if (*idx != '\n') { 457 goto illegal_format; 458 } 459 460 if (noMapID || strcmp(mapID, items[TZ_MAPID]) == 0) { 461 /* 462 * When there's no mapID, we need to scan items until the 463 * exact match is found or the end of data is detected. 464 */ 465 if (!noMapID) { 466 IDmatched = 1; 467 } 468 if (strcmp(items[TZ_WIN_NAME], tzName) == 0) { 469 /* 470 * Found the time zone in the mapping table. 471 */ 472 javaTZName = _strdup(items[TZ_JAVA_NAME]); 473 break; 474 } 475 } else { 476 if (IDmatched == 1) { 477 /* 478 * No need to look up the mapping table further. 479 */ 480 break; 481 } 482 } 483 } 484 fclose(fp); 485 486 return javaTZName; 487 488 illegal_format: 489 (void) fclose(fp); 490 jio_fprintf(stderr, "tzmappings: Illegal format at line %d.\n", line); 491 return NULL; 492 } 493 494 /* 495 * Detects the platform time zone which maps to a Java time zone ID. 496 */ 497 char *findJavaTZ_md(const char *java_home_dir) 498 { 499 char winZoneName[MAX_ZONE_CHAR]; 500 char winMapID[MAX_MAPID_LENGTH]; 501 char *std_timezone = NULL; 502 int result; 503 504 winMapID[0] = 0; 505 result = getWinTimeZone(winZoneName, winMapID); 506 507 if (result != VALUE_UNKNOWN) { 508 if (result == VALUE_GMTOFFSET) { 509 std_timezone = _strdup(winZoneName); 510 } else { 511 std_timezone = matchJavaTZ(java_home_dir, result, 512 winZoneName, winMapID); 513 if (std_timezone == NULL) { 514 std_timezone = getGMTOffsetID(); 515 } 516 } 517 } 518 return std_timezone; 519 } 520 521 /** 522 * Returns a GMT-offset-based time zone ID. 523 */ 524 char * 525 getGMTOffsetID() 526 { 527 LONG bias = 0; 528 LONG ret; 529 HANDLE hKey = NULL; 530 char zonename[32]; 531 532 // Obtain the current GMT offset value of ActiveTimeBias. 533 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 534 KEY_READ, (PHKEY)&hKey); 535 if (ret == ERROR_SUCCESS) { 536 DWORD val; 537 DWORD bufSize = sizeof(val); 538 ULONG valueType = 0; 539 ret = RegQueryValueExA(hKey, "ActiveTimeBias", 540 NULL, &valueType, (LPBYTE) &val, &bufSize); 541 if (ret == ERROR_SUCCESS) { 542 bias = (LONG) val; 543 } 544 (void) RegCloseKey(hKey); 545 } 546 547 // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation. 548 // Note: Bias doesn't reflect current daylight saving. 549 if (ret != ERROR_SUCCESS) { 550 TIME_ZONE_INFORMATION tzi; 551 if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) { 552 bias = tzi.Bias; 553 } 554 } 555 556 customZoneName(bias, zonename); 557 return _strdup(zonename); 558 }