1 /*
2 * Copyright (c) 1999, 2018, 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 #define MAX_REGION_LENGTH 4
40
41 #define NT_TZ_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"
42 #define WIN_TZ_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones"
43 #define WIN_CURRENT_TZ_KEY "System\\CurrentControlSet\\Control\\TimeZoneInformation"
44
45 typedef struct _TziValue {
46 LONG bias;
47 LONG stdBias;
48 LONG dstBias;
49 SYSTEMTIME stdDate;
50 SYSTEMTIME dstDate;
51 } TziValue;
52
53 /*
54 * Registry key names
55 */
56 static void *keyNames[] = {
57 (void *) L"StandardName",
58 (void *) "StandardName",
59 (void *) L"Std",
60 (void *) "Std"
61 };
62
63 /*
64 * Indices to keyNames[]
65 */
66 #define STANDARD_NAME 0
67 #define STD_NAME 2
68
69 /*
70 * Calls RegQueryValueEx() to get the value for the specified key. If
71 * the platform is NT, 2000 or XP, it calls the Unicode
72 * version. Otherwise, it calls the ANSI version and converts the
73 * value to Unicode. In this case, it assumes that the current ANSI
74 * Code Page is the same as the native platform code page (e.g., Code
75 * Page 932 for the Japanese Windows systems.
76 *
77 * `keyIndex' is an index value to the keyNames in Unicode
78 * (WCHAR). `keyIndex' + 1 points to its ANSI value.
79 *
80 * Returns the status value. ERROR_SUCCESS if succeeded, a
81 * non-ERROR_SUCCESS value otherwise.
82 */
83 static LONG
getValueInRegistry(HKEY hKey,int keyIndex,LPDWORD typePtr,LPBYTE buf,LPDWORD bufLengthPtr)84 getValueInRegistry(HKEY hKey,
85 int keyIndex,
86 LPDWORD typePtr,
87 LPBYTE buf,
88 LPDWORD bufLengthPtr)
89 {
90 LONG ret;
91 DWORD bufLength = *bufLengthPtr;
92 char val[MAX_ZONE_CHAR];
93 DWORD valSize;
94 int len;
95
96 *typePtr = 0;
97 ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL,
98 typePtr, buf, bufLengthPtr);
99 if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) {
100 return ret;
101 }
102
103 valSize = sizeof(val);
104 ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL,
105 typePtr, val, &valSize);
106 if (ret != ERROR_SUCCESS) {
107 return ret;
108 }
109 if (*typePtr != REG_SZ) {
110 return ERROR_BADKEY;
111 }
112
113 len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
114 (LPCSTR) val, -1,
115 (LPWSTR) buf, bufLength/sizeof(WCHAR));
116 if (len <= 0) {
117 return ERROR_BADKEY;
118 }
119 return ERROR_SUCCESS;
120 }
121
122 /*
123 * Produces custom name "GMT+hh:mm" from the given bias in buffer.
124 */
customZoneName(LONG bias,char * buffer)125 static void customZoneName(LONG bias, char *buffer) {
126 LONG gmtOffset;
127 int sign;
128
129 if (bias > 0) {
130 gmtOffset = bias;
131 sign = -1;
132 } else {
133 gmtOffset = -bias;
134 sign = 1;
135 }
136 if (gmtOffset != 0) {
137 sprintf(buffer, "GMT%c%02d:%02d",
138 ((sign >= 0) ? '+' : '-'),
139 gmtOffset / 60,
140 gmtOffset % 60);
141 } else {
142 strcpy(buffer, "GMT");
143 }
144 }
145
146 /*
147 * Gets the current time zone entry in the "Time Zones" registry.
148 */
getWinTimeZone(char * winZoneName)149 static int getWinTimeZone(char *winZoneName)
150 {
151 DYNAMIC_TIME_ZONE_INFORMATION dtzi;
152 DWORD timeType;
153 DWORD bufSize;
154 DWORD val;
155 HANDLE hKey = NULL;
156 LONG ret;
157 ULONG valueType;
158
159 /*
160 * Get the dynamic time zone information so that time zone redirection
161 * can be supported. (see JDK-7044727)
162 */
163 timeType = GetDynamicTimeZoneInformation(&dtzi);
164 if (timeType == TIME_ZONE_ID_INVALID) {
165 goto err;
166 }
167
168 /*
169 * Make sure TimeZoneKeyName is available from the API call. If
170 * DynamicDaylightTime is disabled, return a custom time zone name
171 * based on the GMT offset. Otherwise, return the TimeZoneKeyName
172 * value.
173 */
174 if (dtzi.TimeZoneKeyName[0] != 0) {
175 if (dtzi.DynamicDaylightTimeDisabled) {
176 customZoneName(dtzi.Bias, winZoneName);
177 return VALUE_GMTOFFSET;
178 }
179 wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR);
180 return VALUE_KEY;
181 }
182
183 /*
184 * If TimeZoneKeyName is not available, check whether StandardName
185 * is available to fall back to the older API GetTimeZoneInformation.
186 * If not, directly read the value from registry keys.
187 */
188 if (dtzi.StandardName[0] == 0) {
189 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
190 KEY_READ, (PHKEY)&hKey);
191 if (ret != ERROR_SUCCESS) {
192 goto err;
193 }
194
195 /*
196 * Determine if auto-daylight time adjustment is turned off.
197 */
198 bufSize = sizeof(val);
199 ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL,
200 &valueType, (LPBYTE) &val, &bufSize);
201 if (ret != ERROR_SUCCESS) {
202 goto err;
203 }
204 /*
205 * Return a custom time zone name if auto-daylight time adjustment
206 * is disabled.
207 */
208 if (val == 1) {
209 customZoneName(dtzi.Bias, winZoneName);
210 (void) RegCloseKey(hKey);
211 return VALUE_GMTOFFSET;
212 }
213
214 bufSize = MAX_ZONE_CHAR;
215 ret = RegQueryValueExA(hKey, "TimeZoneKeyName", NULL,
216 &valueType, (LPBYTE) winZoneName, &bufSize);
217 if (ret != ERROR_SUCCESS) {
218 goto err;
219 }
220 (void) RegCloseKey(hKey);
221 return VALUE_KEY;
222 } else {
223 /*
224 * Fall back to GetTimeZoneInformation
225 */
226 TIME_ZONE_INFORMATION tzi;
227 HANDLE hSubKey = NULL;
228 DWORD nSubKeys, i;
229 ULONG valueType;
230 TCHAR subKeyName[MAX_ZONE_CHAR];
231 TCHAR szValue[MAX_ZONE_CHAR];
232 WCHAR stdNameInReg[MAX_ZONE_CHAR];
233 TziValue tempTzi;
234 WCHAR *stdNamePtr = tzi.StandardName;
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 (void) RegCloseKey(hKey);
376 }
377
378 return VALUE_KEY;
379
380 err:
381 if (hKey != NULL) {
382 (void) RegCloseKey(hKey);
383 }
384 return VALUE_UNKNOWN;
385 }
386
387 /*
388 * The mapping table file name.
389 */
390 #define MAPPINGS_FILE "\\lib\\tzmappings"
391
392 /*
393 * Index values for the mapping table.
394 */
395 #define TZ_WIN_NAME 0
396 #define TZ_REGION 1
397 #define TZ_JAVA_NAME 2
398
399 #define TZ_NITEMS 3 /* number of items (fields) */
400
401 /*
402 * Looks up the mapping table (tzmappings) and returns a Java time
403 * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is
404 * returned.
405 */
matchJavaTZ(const char * java_home_dir,char * tzName)406 static char *matchJavaTZ(const char *java_home_dir, char *tzName)
407 {
408 int line;
409 int IDmatched = 0;
410 FILE *fp;
411 char *javaTZName = NULL;
412 char *items[TZ_NITEMS];
413 char *mapFileName;
414 char lineBuffer[MAX_ZONE_CHAR * 4];
415 int offset = 0;
416 const char* errorMessage = "unknown error";
417 char region[MAX_REGION_LENGTH];
418
419 // Get the user's location
420 if (GetGeoInfo(GetUserGeoID(GEOCLASS_NATION),
421 GEO_ISO2, region, MAX_REGION_LENGTH, 0) == 0) {
422 // If GetGeoInfo fails, fallback to LCID's country
423 LCID lcid = GetUserDefaultLCID();
424 if (GetLocaleInfo(lcid,
425 LOCALE_SISO3166CTRYNAME, region, MAX_REGION_LENGTH) == 0 &&
426 GetLocaleInfo(lcid,
427 LOCALE_SISO3166CTRYNAME2, region, MAX_REGION_LENGTH) == 0) {
428 region[0] = '\0';
429 }
430 }
431
432 mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1);
433 if (mapFileName == NULL) {
434 return NULL;
435 }
436 strcpy(mapFileName, java_home_dir);
437 strcat(mapFileName, MAPPINGS_FILE);
438
439 if ((fp = fopen(mapFileName, "r")) == NULL) {
440 jio_fprintf(stderr, "can't open %s.\n", mapFileName);
441 free((void *) mapFileName);
442 return NULL;
443 }
444 free((void *) mapFileName);
445
446 line = 0;
447 while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) {
448 char *start, *idx, *endp;
449 int itemIndex = 0;
450
451 line++;
452 start = idx = lineBuffer;
453 endp = &lineBuffer[sizeof(lineBuffer)];
454
455 /*
456 * Ignore comment and blank lines.
457 */
458 if (*idx == '#' || *idx == '\n') {
459 continue;
460 }
461
462 for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) {
463 items[itemIndex] = start;
464 while (*idx && *idx != ':') {
465 if (++idx >= endp) {
466 errorMessage = "premature end of line";
467 offset = (int)(idx - lineBuffer);
468 goto illegal_format;
469 }
470 }
471 if (*idx == '\0') {
472 errorMessage = "illegal null character found";
473 offset = (int)(idx - lineBuffer);
474 goto illegal_format;
475 }
476 *idx++ = '\0';
477 start = idx;
478 }
479
480 if (*idx != '\n') {
481 errorMessage = "illegal non-newline character found";
482 offset = (int)(idx - lineBuffer);
483 goto illegal_format;
484 }
485
486 /*
487 * We need to scan items until the
488 * exact match is found or the end of data is detected.
489 */
490 if (strcmp(items[TZ_WIN_NAME], tzName) == 0) {
491 /*
492 * Found the time zone in the mapping table.
493 * Check the region code and select the appropriate entry
494 */
495 if (strcmp(items[TZ_REGION], region) == 0 ||
496 strcmp(items[TZ_REGION], "001") == 0) {
497 javaTZName = _strdup(items[TZ_JAVA_NAME]);
498 break;
499 }
500 }
501 }
502 fclose(fp);
503
504 return javaTZName;
505
506 illegal_format:
507 (void) fclose(fp);
508 jio_fprintf(stderr, "Illegal format in tzmappings file: %s at line %d, offset %d.\n",
509 errorMessage, line, offset);
510 return NULL;
511 }
512
513 /*
514 * Detects the platform time zone which maps to a Java time zone ID.
515 */
findJavaTZ_md(const char * java_home_dir)516 char *findJavaTZ_md(const char *java_home_dir)
517 {
518 char winZoneName[MAX_ZONE_CHAR];
519 char *std_timezone = NULL;
520 int result;
521
522 result = getWinTimeZone(winZoneName);
523
524 if (result != VALUE_UNKNOWN) {
525 if (result == VALUE_GMTOFFSET) {
526 std_timezone = _strdup(winZoneName);
527 } else {
528 std_timezone = matchJavaTZ(java_home_dir, winZoneName);
529 if (std_timezone == NULL) {
530 std_timezone = getGMTOffsetID();
531 }
532 }
533 }
534 return std_timezone;
535 }
536
537 /**
538 * Returns a GMT-offset-based time zone ID.
539 */
540 char *
getGMTOffsetID()541 getGMTOffsetID()
542 {
543 LONG bias = 0;
544 LONG ret;
545 HANDLE hKey = NULL;
546 char zonename[32];
547
548 // Obtain the current GMT offset value of ActiveTimeBias.
549 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
550 KEY_READ, (PHKEY)&hKey);
551 if (ret == ERROR_SUCCESS) {
552 DWORD val;
553 DWORD bufSize = sizeof(val);
554 ULONG valueType = 0;
555 ret = RegQueryValueExA(hKey, "ActiveTimeBias",
556 NULL, &valueType, (LPBYTE) &val, &bufSize);
557 if (ret == ERROR_SUCCESS) {
558 bias = (LONG) val;
559 }
560 (void) RegCloseKey(hKey);
561 }
562
563 // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation.
564 // Note: Bias doesn't reflect current daylight saving.
565 if (ret != ERROR_SUCCESS) {
566 TIME_ZONE_INFORMATION tzi;
567 if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) {
568 bias = tzi.Bias;
569 }
570 }
571
572 customZoneName(bias, zonename);
573 return _strdup(zonename);
574 }
575