1 /* 2 * Copyright (c) 1999, 2019, 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 <stdlib.h> 27 #include <stdio.h> 28 #include <strings.h> 29 #include <time.h> 30 #include <limits.h> 31 #include <errno.h> 32 #include <stddef.h> 33 #include <sys/stat.h> 34 #include <sys/types.h> 35 #include <string.h> 36 #include <dirent.h> 37 #include <unistd.h> 38 39 #include "jvm.h" 40 #include "TimeZone_md.h" 41 42 static char *isFileIdentical(char* buf, size_t size, char *pathname); 43 44 #define SKIP_SPACE(p) while (*p == ' ' || *p == '\t') p++; 45 46 #define RESTARTABLE(_cmd, _result) do { \ 47 do { \ 48 _result = _cmd; \ 49 } while((_result == -1) && (errno == EINTR)); \ 50 } while(0) 51 52 #define fileopen fopen 53 #define filegets fgets 54 #define fileclose fclose 55 56 #if defined(_ALLBSD_SOURCE) 57 #define stat64 stat 58 #define lstat64 lstat 59 #define fstat64 fstat 60 #endif 61 62 #if defined(__linux__) || defined(_ALLBSD_SOURCE) 63 static const char *ETC_TIMEZONE_FILE = "/etc/timezone"; 64 static const char *ZONEINFO_DIR = "/usr/share/zoneinfo"; 65 static const char *DEFAULT_ZONEINFO_FILE = "/etc/localtime"; 66 #else 67 static const char *SYS_INIT_FILE = "/etc/default/init"; 68 static const char *ZONEINFO_DIR = "/usr/share/lib/zoneinfo"; 69 static const char *DEFAULT_ZONEINFO_FILE = "/usr/share/lib/zoneinfo/localtime"; 70 #endif /* defined(__linux__) || defined(_ALLBSD_SOURCE) */ 71 72 static const char popularZones[][4] = {"UTC", "GMT"}; 73 74 #if defined(_AIX) 75 static const char *ETC_ENVIRONMENT_FILE = "/etc/environment"; 76 #endif 77 78 #if defined(__linux__) || defined(MACOSX) 79 80 /* 81 * Returns a pointer to the zone ID portion of the given zoneinfo file 82 * name, or NULL if the given string doesn't contain "zoneinfo/". 83 */ 84 static char * 85 getZoneName(char *str) 86 { 87 static const char *zidir = "zoneinfo/"; 88 89 char *pos = strstr((const char *)str, zidir); 90 if (pos == NULL) { 91 return NULL; 92 } 93 return pos + strlen(zidir); 94 } 95 96 /* 97 * Returns a path name created from the given 'dir' and 'name' under 98 * UNIX. This function allocates memory for the pathname calling 99 * malloc(). NULL is returned if malloc() fails. 100 */ 101 static char * 102 getPathName(const char *dir, const char *name) { 103 char *path; 104 105 path = (char *) malloc(strlen(dir) + strlen(name) + 2); 106 if (path == NULL) { 107 return NULL; 108 } 109 return strcat(strcat(strcpy(path, dir), "/"), name); 110 } 111 112 /* 113 * Scans the specified directory and its subdirectories to find a 114 * zoneinfo file which has the same content as /etc/localtime on Linux 115 * or /usr/share/lib/zoneinfo/localtime on Solaris given in 'buf'. 116 * If file is symbolic link, then the contents it points to are in buf. 117 * Returns a zone ID if found, otherwise, NULL is returned. 118 */ 119 static char * 120 findZoneinfoFile(char *buf, size_t size, const char *dir) 121 { 122 DIR *dirp = NULL; 123 struct dirent *dp = NULL; 124 char *pathname = NULL; 125 char *tz = NULL; 126 int res; 127 128 if (strcmp(dir, ZONEINFO_DIR) == 0) { 129 /* fast path for 1st iteration */ 130 for (unsigned int i = 0; i < sizeof (popularZones) / sizeof (popularZones[0]); i++) { 131 pathname = getPathName(dir, popularZones[i]); 132 if (pathname == NULL) { 133 continue; 134 } 135 tz = isFileIdentical(buf, size, pathname); 136 free((void *) pathname); 137 pathname = NULL; 138 if (tz != NULL) { 139 return tz; 140 } 141 } 142 } 143 144 dirp = opendir(dir); 145 if (dirp == NULL) { 146 return NULL; 147 } 148 149 while ((dp = readdir(dirp)) != NULL) { 150 /* 151 * Skip '.' and '..' (and possibly other .* files) 152 */ 153 if (dp->d_name[0] == '.') { 154 continue; 155 } 156 157 /* 158 * Skip "ROC", "posixrules", and "localtime". 159 */ 160 if ((strcmp(dp->d_name, "ROC") == 0) 161 || (strcmp(dp->d_name, "posixrules") == 0) 162 || (strcmp(dp->d_name, "localtime") == 0)) { 163 continue; 164 } 165 166 pathname = getPathName(dir, dp->d_name); 167 if (pathname == NULL) { 168 break; 169 } 170 171 tz = isFileIdentical(buf, size, pathname); 172 free((void *) pathname); 173 pathname = NULL; 174 if (tz != NULL) { 175 break; 176 } 177 } 178 179 if (dirp != NULL) { 180 (void) closedir(dirp); 181 } 182 return tz; 183 } 184 185 /* 186 * Checks if the file pointed to by pathname matches 187 * the data contents in buf. 188 * Returns a representation of the timezone file name 189 * if file match is found, otherwise NULL. 190 */ 191 static char * 192 isFileIdentical(char *buf, size_t size, char *pathname) 193 { 194 char *possibleMatch = NULL; 195 struct stat64 statbuf; 196 char *dbuf = NULL; 197 int fd = -1; 198 int res; 199 200 RESTARTABLE(stat64(pathname, &statbuf), res); 201 if (res == -1) { 202 return NULL; 203 } 204 205 if (S_ISDIR(statbuf.st_mode)) { 206 possibleMatch = findZoneinfoFile(buf, size, pathname); 207 } else if (S_ISREG(statbuf.st_mode) && (size_t)statbuf.st_size == size) { 208 dbuf = (char *) malloc(size); 209 if (dbuf == NULL) { 210 return NULL; 211 } 212 RESTARTABLE(open(pathname, O_RDONLY), fd); 213 if (fd == -1) { 214 goto freedata; 215 } 216 RESTARTABLE(read(fd, dbuf, size), res); 217 if (res != (ssize_t) size) { 218 goto freedata; 219 } 220 if (memcmp(buf, dbuf, size) == 0) { 221 possibleMatch = getZoneName(pathname); 222 if (possibleMatch != NULL) { 223 possibleMatch = strdup(possibleMatch); 224 } 225 } 226 freedata: 227 free((void *) dbuf); 228 (void) close(fd); 229 } 230 return possibleMatch; 231 } 232 233 /* 234 * Performs Linux specific mapping and returns a zone ID 235 * if found. Otherwise, NULL is returned. 236 */ 237 static char * 238 getPlatformTimeZoneID() 239 { 240 struct stat64 statbuf; 241 char *tz = NULL; 242 FILE *fp; 243 int fd; 244 char *buf; 245 size_t size; 246 int res; 247 248 #if defined(__linux__) 249 /* 250 * Try reading the /etc/timezone file for Debian distros. There's 251 * no spec of the file format available. This parsing assumes that 252 * there's one line of an Olson tzid followed by a '\n', no 253 * leading or trailing spaces, no comments. 254 */ 255 if ((fp = fopen(ETC_TIMEZONE_FILE, "r")) != NULL) { 256 char line[256]; 257 258 if (fgets(line, sizeof(line), fp) != NULL) { 259 char *p = strchr(line, '\n'); 260 if (p != NULL) { 261 *p = '\0'; 262 } 263 if (strlen(line) > 0) { 264 tz = strdup(line); 265 } 266 } 267 (void) fclose(fp); 268 if (tz != NULL) { 269 return tz; 270 } 271 } 272 #endif /* defined(__linux__) */ 273 274 /* 275 * Next, try /etc/localtime to find the zone ID. 276 */ 277 RESTARTABLE(lstat64(DEFAULT_ZONEINFO_FILE, &statbuf), res); 278 if (res == -1) { 279 return NULL; 280 } 281 282 /* 283 * If it's a symlink, get the link name and its zone ID part. (The 284 * older versions of timeconfig created a symlink as described in 285 * the Red Hat man page. It was changed in 1999 to create a copy 286 * of a zoneinfo file. It's no longer possible to get the zone ID 287 * from /etc/localtime.) 288 */ 289 if (S_ISLNK(statbuf.st_mode)) { 290 char linkbuf[PATH_MAX+1]; 291 int len; 292 293 if ((len = readlink(DEFAULT_ZONEINFO_FILE, linkbuf, sizeof(linkbuf)-1)) == -1) { 294 jio_fprintf(stderr, (const char *) "can't get a symlink of %s\n", 295 DEFAULT_ZONEINFO_FILE); 296 return NULL; 297 } 298 linkbuf[len] = '\0'; 299 tz = getZoneName(linkbuf); 300 if (tz != NULL) { 301 tz = strdup(tz); 302 return tz; 303 } 304 } 305 306 /* 307 * If it's a regular file, we need to find out the same zoneinfo file 308 * that has been copied as /etc/localtime. 309 * If initial symbolic link resolution failed, we should treat target 310 * file as a regular file. 311 */ 312 RESTARTABLE(open(DEFAULT_ZONEINFO_FILE, O_RDONLY), fd); 313 if (fd == -1) { 314 return NULL; 315 } 316 317 RESTARTABLE(fstat64(fd, &statbuf), res); 318 if (res == -1) { 319 (void) close(fd); 320 return NULL; 321 } 322 size = (size_t) statbuf.st_size; 323 buf = (char *) malloc(size); 324 if (buf == NULL) { 325 (void) close(fd); 326 return NULL; 327 } 328 329 RESTARTABLE(read(fd, buf, size), res); 330 if (res != (ssize_t) size) { 331 (void) close(fd); 332 free((void *) buf); 333 return NULL; 334 } 335 (void) close(fd); 336 337 tz = findZoneinfoFile(buf, size, ZONEINFO_DIR); 338 free((void *) buf); 339 return tz; 340 } 341 342 #elif defined(_AIX) 343 344 static char * 345 getPlatformTimeZoneID() 346 { 347 FILE *fp; 348 char *tz = NULL; 349 char *tz_key = "TZ="; 350 char line[256]; 351 size_t tz_key_len = strlen(tz_key); 352 353 if ((fp = fopen(ETC_ENVIRONMENT_FILE, "r")) != NULL) { 354 while (fgets(line, sizeof(line), fp) != NULL) { 355 char *p = strchr(line, '\n'); 356 if (p != NULL) { 357 *p = '\0'; 358 } 359 if (0 == strncmp(line, tz_key, tz_key_len)) { 360 tz = strdup(line + tz_key_len); 361 break; 362 } 363 } 364 (void) fclose(fp); 365 } 366 367 return tz; 368 } 369 370 static char * 371 mapPlatformToJavaTimezone(const char *java_home_dir, const char *tz) { 372 FILE *tzmapf; 373 char mapfilename[PATH_MAX + 1]; 374 char line[256]; 375 int linecount = 0; 376 char *tz_buf = NULL; 377 char *temp_tz = NULL; 378 char *javatz = NULL; 379 size_t tz_len = 0; 380 381 /* On AIX, the TZ environment variable may end with a comma 382 * followed by modifier fields. These are ignored here. */ 383 temp_tz = strchr(tz, ','); 384 tz_len = (temp_tz == NULL) ? strlen(tz) : temp_tz - tz; 385 tz_buf = (char *)malloc(tz_len + 1); 386 memcpy(tz_buf, tz, tz_len); 387 tz_buf[tz_len] = 0; 388 389 /* Open tzmappings file, with buffer overrun check */ 390 if ((strlen(java_home_dir) + 15) > PATH_MAX) { 391 jio_fprintf(stderr, "Path %s/lib/tzmappings exceeds maximum path length\n", java_home_dir); 392 goto tzerr; 393 } 394 strcpy(mapfilename, java_home_dir); 395 strcat(mapfilename, "/lib/tzmappings"); 396 if ((tzmapf = fopen(mapfilename, "r")) == NULL) { 397 jio_fprintf(stderr, "can't open %s\n", mapfilename); 398 goto tzerr; 399 } 400 401 while (fgets(line, sizeof(line), tzmapf) != NULL) { 402 char *p = line; 403 char *sol = line; 404 char *java; 405 int result; 406 407 linecount++; 408 /* 409 * Skip comments and blank lines 410 */ 411 if (*p == '#' || *p == '\n') { 412 continue; 413 } 414 415 /* 416 * Get the first field, platform zone ID 417 */ 418 while (*p != '\0' && *p != '\t') { 419 p++; 420 } 421 if (*p == '\0') { 422 /* mapping table is broken! */ 423 jio_fprintf(stderr, "tzmappings: Illegal format at near line %d.\n", linecount); 424 break; 425 } 426 427 *p++ = '\0'; 428 if ((result = strncmp(tz_buf, sol, tz_len)) == 0) { 429 /* 430 * If this is the current platform zone ID, 431 * take the Java time zone ID (2nd field). 432 */ 433 java = p; 434 while (*p != '\0' && *p != '\n') { 435 p++; 436 } 437 438 if (*p == '\0') { 439 /* mapping table is broken! */ 440 jio_fprintf(stderr, "tzmappings: Illegal format at line %d.\n", linecount); 441 break; 442 } 443 444 *p = '\0'; 445 javatz = strdup(java); 446 break; 447 } else if (result < 0) { 448 break; 449 } 450 } 451 (void) fclose(tzmapf); 452 453 tzerr: 454 if (tz_buf != NULL ) { 455 free((void *) tz_buf); 456 } 457 458 if (javatz == NULL) { 459 return getGMTOffsetID(); 460 } 461 462 return javatz; 463 } 464 465 #endif /* defined(_AIX) */ 466 467 /* 468 * findJavaTZ_md() maps platform time zone ID to Java time zone ID 469 * using <java_home>/lib/tzmappings. If the TZ value is not found, it 470 * trys some libc implementation dependent mappings. If it still 471 * can't map to a Java time zone ID, it falls back to the GMT+/-hh:mm 472 * form. 473 */ 474 /*ARGSUSED1*/ 475 char * 476 findJavaTZ_md(const char *java_home_dir) 477 { 478 char *tz; 479 char *javatz = NULL; 480 char *freetz = NULL; 481 482 tz = getenv("TZ"); 483 484 if (tz == NULL || *tz == '\0') { 485 tz = getPlatformTimeZoneID(); 486 freetz = tz; 487 } 488 489 if (tz != NULL) { 490 /* Ignore preceding ':' */ 491 if (*tz == ':') { 492 tz++; 493 } 494 #if defined(__linux__) 495 /* Ignore "posix/" prefix on Linux. */ 496 if (strncmp(tz, "posix/", 6) == 0) { 497 tz += 6; 498 } 499 #endif 500 501 #if defined(_AIX) 502 /* On AIX do the platform to Java mapping. */ 503 javatz = mapPlatformToJavaTimezone(java_home_dir, tz); 504 if (freetz != NULL) { 505 free((void *) freetz); 506 } 507 #else 508 if (freetz == NULL) { 509 /* strdup if we are still working on getenv result. */ 510 javatz = strdup(tz); 511 } else if (freetz != tz) { 512 /* strdup and free the old buffer, if we moved the pointer. */ 513 javatz = strdup(tz); 514 free((void *) freetz); 515 } else { 516 /* we are good if we already work on a freshly allocated buffer. */ 517 javatz = tz; 518 } 519 #endif 520 } 521 522 return javatz; 523 } 524 525 /** 526 * Returns a GMT-offset-based zone ID. (e.g., "GMT-08:00") 527 */ 528 529 #if defined(MACOSX) 530 531 char * 532 getGMTOffsetID() 533 { 534 time_t offset; 535 char sign, buf[32]; 536 struct tm local_tm; 537 time_t clock; 538 539 clock = time(NULL); 540 if (localtime_r(&clock, &local_tm) == NULL) { 541 return strdup("GMT"); 542 } 543 offset = (time_t)local_tm.tm_gmtoff; 544 if (offset == 0) { 545 return strdup("GMT"); 546 } 547 if (offset > 0) { 548 sign = '+'; 549 } else { 550 offset = -offset; 551 sign = '-'; 552 } 553 sprintf(buf, (const char *)"GMT%c%02d:%02d", 554 sign, (int)(offset/3600), (int)((offset%3600)/60)); 555 return strdup(buf); 556 } 557 558 #else 559 560 char * 561 getGMTOffsetID() 562 { 563 time_t offset; 564 char sign, buf[32]; 565 offset = timezone; 566 567 if (offset == 0) { 568 return strdup("GMT"); 569 } 570 571 /* Note that the time offset direction is opposite. */ 572 if (offset > 0) { 573 sign = '-'; 574 } else { 575 offset = -offset; 576 sign = '+'; 577 } 578 sprintf(buf, (const char *)"GMT%c%02d:%02d", 579 sign, (int)(offset/3600), (int)((offset%3600)/60)); 580 return strdup(buf); 581 } 582 #endif /* MACOSX */