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 }