1 /*
   2  * Copyright (c) 1999, 2020, 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 */