1 /*
   2  * Copyright (c) 1999, 2006, 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 static int isNT = FALSE;        /* TRUE if it is NT. */
  69 
  70 /*
  71  * Calls RegQueryValueEx() to get the value for the specified key. If
  72  * the platform is NT, 2000 or XP, it calls the Unicode
  73  * version. Otherwise, it calls the ANSI version and converts the
  74  * value to Unicode. In this case, it assumes that the current ANSI
  75  * Code Page is the same as the native platform code page (e.g., Code
  76  * Page 932 for the Japanese Windows systems.
  77  *
  78  * `keyIndex' is an index value to the keyNames in Unicode
  79  * (WCHAR). `keyIndex' + 1 points to its ANSI value.
  80  *
  81  * Returns the status value. ERROR_SUCCESS if succeeded, a
  82  * non-ERROR_SUCCESS value otherwise.
  83  */
  84 static LONG
  85 getValueInRegistry(HKEY hKey,
  86                    int keyIndex,
  87                    LPDWORD typePtr,
  88                    LPBYTE buf,
  89                    LPDWORD bufLengthPtr)
  90 {
  91     LONG ret;
  92     DWORD bufLength = *bufLengthPtr;
  93     char val[MAX_ZONE_CHAR];
  94     DWORD valSize;
  95     int len;
  96 
  97     *typePtr = 0;
  98     if (isNT) {
  99         ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL,
 100                                typePtr, buf, bufLengthPtr);
 101         if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) {
 102             return ret;
 103         }
 104     }
 105 
 106     valSize = sizeof(val);
 107     ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL,
 108                            typePtr, val, &valSize);
 109     if (ret != ERROR_SUCCESS) {
 110         return ret;
 111     }
 112     if (*typePtr != REG_SZ) {
 113         return ERROR_BADKEY;
 114     }
 115 
 116     len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
 117                               (LPCSTR) val, -1,
 118                               (LPWSTR) buf, bufLength/sizeof(WCHAR));
 119     if (len <= 0) {
 120         return ERROR_BADKEY;
 121     }
 122     return ERROR_SUCCESS;
 123 }
 124 
 125 /*
 126  * Produces custom name "GMT+hh:mm" from the given bias in buffer.
 127  */
 128 static void customZoneName(LONG bias, char *buffer) {
 129     LONG gmtOffset;
 130     int sign;
 131 
 132     if (bias > 0) {
 133         gmtOffset = bias;
 134         sign = -1;
 135     } else {
 136         gmtOffset = -bias;
 137         sign = 1;
 138     }
 139     if (gmtOffset != 0) {
 140         sprintf(buffer, "GMT%c%02d:%02d",
 141                 ((sign >= 0) ? '+' : '-'),
 142                 gmtOffset / 60,
 143                 gmtOffset % 60);
 144     } else {
 145         strcpy(buffer, "GMT");
 146     }
 147 }
 148 
 149 /*
 150  * Gets the current time zone entry in the "Time Zones" registry.
 151  */
 152 static int getWinTimeZone(char *winZoneName, char *winMapID)
 153 {
 154     TIME_ZONE_INFORMATION tzi;
 155     OSVERSIONINFO ver;
 156     int onlyMapID;
 157     HANDLE hKey = NULL, hSubKey = NULL;
 158     LONG ret;
 159     DWORD nSubKeys, i;
 160     ULONG valueType;
 161     TCHAR subKeyName[MAX_ZONE_CHAR];
 162     TCHAR szValue[MAX_ZONE_CHAR];
 163     WCHAR stdNameInReg[MAX_ZONE_CHAR];
 164     TziValue tempTzi;
 165     WCHAR *stdNamePtr = tzi.StandardName;
 166     DWORD valueSize;
 167     DWORD timeType;
 168 
 169     /*
 170      * Get the current time zone setting of the platform.
 171      */
 172     timeType = GetTimeZoneInformation(&tzi);
 173     if (timeType == TIME_ZONE_ID_INVALID) {
 174         goto err;
 175     }
 176 
 177     /*
 178      * Determine if this is an NT system.
 179      */
 180     ver.dwOSVersionInfoSize = sizeof(ver);
 181     GetVersionEx(&ver);
 182     isNT = ver.dwPlatformId == VER_PLATFORM_WIN32_NT;
 183 
 184     ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
 185                        KEY_READ, (PHKEY)&hKey);
 186     if (ret == ERROR_SUCCESS) {
 187         DWORD val;
 188         DWORD bufSize;
 189 
 190         /*
 191          * Determine if auto-daylight time adjustment is turned off.
 192          */
 193         valueType = 0;
 194         bufSize = sizeof(val);
 195         ret = RegQueryValueExA(hKey, "DisableAutoDaylightTimeSet",
 196                                NULL, &valueType, (LPBYTE) &val, &bufSize);
 197         /*
 198          * Vista uses the different key name.
 199          */
 200         if (ret != ERROR_SUCCESS) {
 201           bufSize = sizeof(val);
 202             ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled",
 203                                    NULL, &valueType, (LPBYTE) &val, &bufSize);
 204         }
 205         if (ret == ERROR_SUCCESS) {
 206             if (val == 1) {
 207                 (void) RegCloseKey(hKey);
 208                 customZoneName(tzi.Bias, winZoneName);
 209                 return VALUE_GMTOFFSET;
 210             }
 211         }
 212 
 213         /*
 214          * Vista has the key for the current "Time Zones" entry.
 215          */
 216         if (isNT && ver.dwMajorVersion >= 6) {
 217             valueType = 0;
 218             bufSize = MAX_ZONE_CHAR;
 219             ret = RegQueryValueExA(hKey, "TimeZoneKeyName", NULL,
 220                                    &valueType, (LPBYTE) winZoneName, &bufSize);
 221             if (ret != ERROR_SUCCESS) {
 222                 goto err;
 223             }
 224             (void) RegCloseKey(hKey);
 225             return VALUE_KEY;
 226         }
 227 
 228         /*
 229          * Win32 problem: If the length of the standard time name is equal
 230          * to (or probably longer than) 32 in the registry,
 231          * GetTimeZoneInformation() on NT returns a null string as its
 232          * standard time name. We need to work around this problem by
 233          * getting the same information from the TimeZoneInformation
 234          * registry. The function on Win98 seems to return its key name.
 235          * We can't do anything in that case.
 236          */
 237         if (tzi.StandardName[0] == 0) {
 238             bufSize = sizeof(stdNameInReg);
 239             ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType,
 240                                      (LPBYTE) stdNameInReg, &bufSize);
 241             if (ret != ERROR_SUCCESS) {
 242                 goto err;
 243             }
 244             stdNamePtr = stdNameInReg;
 245         }
 246         (void) RegCloseKey(hKey);
 247     }
 248 
 249     /*
 250      * Open the "Time Zones" registry.
 251      */
 252     ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey);
 253     if (ret != ERROR_SUCCESS) {
 254         ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey);
 255         /*
 256          * If both failed, then give up.
 257          */
 258         if (ret != ERROR_SUCCESS) {
 259             return VALUE_UNKNOWN;
 260         }
 261     }
 262 
 263     /*
 264      * Get the number of subkeys of the "Time Zones" registry for
 265      * enumeration.
 266      */
 267     ret = RegQueryInfoKey(hKey, NULL, NULL, NULL, &nSubKeys,
 268                           NULL, NULL, NULL, NULL, NULL, NULL, NULL);
 269     if (ret != ERROR_SUCCESS) {
 270         goto err;
 271     }
 272 
 273     /*
 274      * Compare to the "Std" value of each subkey and find the entry that
 275      * matches the current control panel setting.
 276      */
 277     onlyMapID = 0;
 278     for (i = 0; i < nSubKeys; ++i) {
 279         DWORD size = sizeof(subKeyName);
 280         ret = RegEnumKeyEx(hKey, i, subKeyName, &size, NULL, NULL, NULL, NULL);
 281         if (ret != ERROR_SUCCESS) {
 282             goto err;
 283         }
 284         ret = RegOpenKeyEx(hKey, subKeyName, 0, KEY_READ, (PHKEY)&hSubKey);
 285         if (ret != ERROR_SUCCESS) {
 286             goto err;
 287         }
 288 
 289         size = sizeof(szValue);
 290         ret = getValueInRegistry(hSubKey, STD_NAME, &valueType,
 291                                  szValue, &size);
 292         if (ret != ERROR_SUCCESS) {
 293             /*
 294              * NT 4.0 SP3 fails here since it doesn't have the "Std"
 295              * entry in the Time Zones registry.
 296              */
 297             RegCloseKey(hSubKey);
 298             onlyMapID = 1;
 299             ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, (PHKEY)&hSubKey);
 300             if (ret != ERROR_SUCCESS) {
 301                 goto err;
 302             }
 303             break;
 304         }
 305 
 306         if (wcscmp((WCHAR *)szValue, stdNamePtr) == 0) {
 307             /*
 308              * Some localized Win32 platforms use a same name to
 309              * different time zones. So, we can't rely only on the name
 310              * here. We need to check GMT offsets and transition dates
 311              * to make sure it's the registry of the current time
 312              * zone.
 313              */
 314             DWORD tziValueSize = sizeof(tempTzi);
 315             ret = RegQueryValueEx(hSubKey, "TZI", NULL, &valueType,
 316                                   (unsigned char *) &tempTzi, &tziValueSize);
 317             if (ret == ERROR_SUCCESS) {
 318                 if ((tzi.Bias != tempTzi.bias) ||
 319                     (memcmp((const void *) &tzi.StandardDate,
 320                             (const void *) &tempTzi.stdDate,
 321                             sizeof(SYSTEMTIME)) != 0)) {
 322                         goto out;
 323                 }
 324 
 325                 if (tzi.DaylightBias != 0) {
 326                     if ((tzi.DaylightBias != tempTzi.dstBias) ||
 327                         (memcmp((const void *) &tzi.DaylightDate,
 328                                 (const void *) &tempTzi.dstDate,
 329                                 sizeof(SYSTEMTIME)) != 0)) {
 330                         goto out;
 331                     }
 332                 }
 333             }
 334 
 335             /*
 336              * found matched record, terminate search
 337              */
 338             strcpy(winZoneName, subKeyName);
 339             break;
 340         }
 341     out:
 342         (void) RegCloseKey(hSubKey);
 343     }
 344 
 345     /*
 346      * Get the "MapID" value of the registry to be able to eliminate
 347      * duplicated key names later.
 348      */
 349     valueSize = MAX_MAPID_LENGTH;
 350     ret = RegQueryValueExA(hSubKey, "MapID", NULL, &valueType, winMapID, &valueSize);
 351     (void) RegCloseKey(hSubKey);
 352     (void) RegCloseKey(hKey);
 353 
 354     if (ret != ERROR_SUCCESS) {
 355         /*
 356          * Vista doesn't have mapID. VALUE_UNKNOWN should be returned
 357          * only for Windows NT.
 358          */
 359         if (onlyMapID == 1) {
 360             return VALUE_UNKNOWN;
 361         }
 362     }
 363 
 364     return VALUE_KEY;
 365 
 366  err:
 367     if (hKey != NULL) {
 368         (void) RegCloseKey(hKey);
 369     }
 370     return VALUE_UNKNOWN;
 371 }
 372 
 373 /*
 374  * The mapping table file name.
 375  */
 376 #define MAPPINGS_FILE "\\lib\\tzmappings"
 377 
 378 /*
 379  * Index values for the mapping table.
 380  */
 381 #define TZ_WIN_NAME     0
 382 #define TZ_MAPID        1
 383 #define TZ_REGION       2
 384 #define TZ_JAVA_NAME    3
 385 
 386 #define TZ_NITEMS       4       /* number of items (fields) */
 387 
 388 /*
 389  * Looks up the mapping table (tzmappings) and returns a Java time
 390  * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is
 391  * returned.
 392  *
 393  * value_type is one of the following values:
 394  *      VALUE_KEY for exact key matching
 395  *      VALUE_MAPID for MapID and country-based mapping (this is
 396  *      required for the old Windows, such as NT 4.0 SP3).
 397  */
 398 static char *matchJavaTZ(const char *java_home_dir, int value_type, char *tzName,
 399                          char *mapID, const char *country)
 400 {
 401     int line;
 402     int IDmatched = 0;
 403     FILE *fp;
 404     char *javaTZName = NULL;
 405     char *items[TZ_NITEMS];
 406     char mapFileName[_MAX_PATH + 1];
 407     char lineBuffer[MAX_ZONE_CHAR * 4];
 408     char bestMatch[MAX_ZONE_CHAR];
 409     int noMapID = *mapID == '\0';       /* no mapID on Vista */
 410 
 411     bestMatch[0] = '\0';
 412 
 413     strcpy(mapFileName, java_home_dir);
 414     strcat(mapFileName, MAPPINGS_FILE);
 415 
 416     if ((fp = fopen(mapFileName, "r")) == NULL) {
 417         jio_fprintf(stderr, "can't open %s.\n", mapFileName);
 418         return NULL;
 419     }
 420 
 421     line = 0;
 422     while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) {
 423         char *start, *idx, *endp;
 424         int itemIndex = 0;
 425 
 426         line++;
 427         start = idx = lineBuffer;
 428         endp = &lineBuffer[sizeof(lineBuffer)];
 429 
 430         /*
 431          * Ignore comment and blank lines.
 432          */
 433         if (*idx == '#' || *idx == '\n') {
 434             continue;
 435         }
 436 
 437         for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) {
 438             items[itemIndex] = start;
 439             while (*idx && *idx != ':') {
 440                 if (++idx >= endp) {
 441                     goto illegal_format;
 442                 }
 443             }
 444             if (*idx == '\0') {
 445                 goto illegal_format;
 446             }
 447             *idx++ = '\0';
 448             start = idx;
 449         }
 450 
 451         if (*idx != '\n') {
 452             goto illegal_format;
 453         }
 454 
 455         if (noMapID || strcmp(mapID, items[TZ_MAPID]) == 0) {
 456             /*
 457              * When there's no mapID, we need to scan items until the
 458              * exact match is found or the end of data is detected.
 459              */
 460             if (!noMapID) {
 461                 IDmatched = 1;
 462             }
 463             if (strcmp(items[TZ_WIN_NAME], tzName) == 0) {
 464                 /*
 465                  * Found the time zone in the mapping table.
 466                  */
 467                 javaTZName = _strdup(items[TZ_JAVA_NAME]);
 468                 break;
 469             }
 470             /*
 471              * Try to find the most likely time zone.
 472              */
 473             if (*items[TZ_REGION] == '\0') {
 474                 strncpy(bestMatch, items[TZ_JAVA_NAME], MAX_ZONE_CHAR);
 475             } else if (country != NULL && strcmp(items[TZ_REGION], country) == 0) {
 476                 if (value_type == VALUE_MAPID) {
 477                     javaTZName = _strdup(items[TZ_JAVA_NAME]);
 478                     break;
 479                 }
 480                 strncpy(bestMatch, items[TZ_JAVA_NAME], MAX_ZONE_CHAR);
 481             }
 482         } else {
 483             if (IDmatched == 1) {
 484                 /*
 485                  * No need to look up the mapping table further.
 486                  */
 487                 break;
 488             }
 489         }
 490     }
 491     fclose(fp);
 492 
 493     if (javaTZName == NULL && bestMatch[0] != '\0') {
 494         javaTZName = _strdup(bestMatch);
 495     }
 496     return javaTZName;
 497 
 498  illegal_format:
 499     (void) fclose(fp);
 500     jio_fprintf(stderr, "tzmappings: Illegal format at line %d.\n", line);
 501     return NULL;
 502 }
 503 
 504 /*
 505  * Detects the platform time zone which maps to a Java time zone ID.
 506  */
 507 char *findJavaTZ_md(const char *java_home_dir, const char *country)
 508 {
 509     char winZoneName[MAX_ZONE_CHAR];
 510     char winMapID[MAX_MAPID_LENGTH];
 511     char *std_timezone = NULL;
 512     int  result;
 513 
 514     winMapID[0] = 0;
 515     result = getWinTimeZone(winZoneName, winMapID);
 516 
 517     if (result != VALUE_UNKNOWN) {
 518         if (result == VALUE_GMTOFFSET) {
 519             std_timezone = _strdup(winZoneName);
 520         } else {
 521             std_timezone = matchJavaTZ(java_home_dir, result,
 522                                        winZoneName, winMapID, country);
 523         }
 524     }
 525 
 526     return std_timezone;
 527 }
 528 
 529 /**
 530  * Returns a GMT-offset-based time zone ID. On Win32, it always return
 531  * NULL since the fall back is performed in getWinTimeZone().
 532  */
 533 char *
 534 getGMTOffsetID()
 535 {
 536     return NULL;
 537 }