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
44typedef 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 */
55static 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 */
82static LONG
83getValueInRegistry(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 */
124static 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 */
148static 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 */
429static 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 */
526char *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 */
553char *
554getGMTOffsetID()
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}
588