Path: blob/master/src/java.base/unix/native/libjava/TimeZone_md.c
41119 views
/*1* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425#include <stdlib.h>26#include <stdio.h>27#include <strings.h>28#include <time.h>29#include <limits.h>30#include <errno.h>31#include <stddef.h>32#include <sys/stat.h>33#include <sys/types.h>34#include <string.h>35#include <dirent.h>36#include <unistd.h>3738#include "jvm.h"39#include "TimeZone_md.h"4041static char *isFileIdentical(char* buf, size_t size, char *pathname);4243#define SKIP_SPACE(p) while (*p == ' ' || *p == '\t') p++;4445#define RESTARTABLE(_cmd, _result) do { \46do { \47_result = _cmd; \48} while((_result == -1) && (errno == EINTR)); \49} while(0)5051#define fileopen fopen52#define filegets fgets53#define fileclose fclose5455#if defined(_ALLBSD_SOURCE)56#define stat64 stat57#define lstat64 lstat58#define fstat64 fstat59#endif6061#if defined(__linux__) || defined(_ALLBSD_SOURCE)62static const char *ETC_TIMEZONE_FILE = "/etc/timezone";63static const char *ZONEINFO_DIR = "/usr/share/zoneinfo";64static const char *DEFAULT_ZONEINFO_FILE = "/etc/localtime";65#else66static const char *SYS_INIT_FILE = "/etc/default/init";67static const char *ZONEINFO_DIR = "/usr/share/lib/zoneinfo";68static const char *DEFAULT_ZONEINFO_FILE = "/usr/share/lib/zoneinfo/localtime";69#endif /* defined(__linux__) || defined(_ALLBSD_SOURCE) */7071static const char popularZones[][4] = {"UTC", "GMT"};7273#if defined(_AIX)74static const char *ETC_ENVIRONMENT_FILE = "/etc/environment";75#endif7677#if defined(__linux__) || defined(MACOSX)7879/*80* Returns a pointer to the zone ID portion of the given zoneinfo file81* name, or NULL if the given string doesn't contain "zoneinfo/".82*/83static char *84getZoneName(char *str)85{86static const char *zidir = "zoneinfo/";8788char *pos = strstr((const char *)str, zidir);89if (pos == NULL) {90return NULL;91}92return pos + strlen(zidir);93}9495/*96* Returns a path name created from the given 'dir' and 'name' under97* UNIX. This function allocates memory for the pathname calling98* malloc(). NULL is returned if malloc() fails.99*/100static char *101getPathName(const char *dir, const char *name) {102char *path;103104path = (char *) malloc(strlen(dir) + strlen(name) + 2);105if (path == NULL) {106return NULL;107}108return strcat(strcat(strcpy(path, dir), "/"), name);109}110111/*112* Scans the specified directory and its subdirectories to find a113* zoneinfo file which has the same content as /etc/localtime on Linux114* or /usr/share/lib/zoneinfo/localtime on Solaris given in 'buf'.115* If file is symbolic link, then the contents it points to are in buf.116* Returns a zone ID if found, otherwise, NULL is returned.117*/118static char *119findZoneinfoFile(char *buf, size_t size, const char *dir)120{121DIR *dirp = NULL;122struct dirent *dp = NULL;123char *pathname = NULL;124char *tz = NULL;125int res;126127if (strcmp(dir, ZONEINFO_DIR) == 0) {128/* fast path for 1st iteration */129for (unsigned int i = 0; i < sizeof (popularZones) / sizeof (popularZones[0]); i++) {130pathname = getPathName(dir, popularZones[i]);131if (pathname == NULL) {132continue;133}134tz = isFileIdentical(buf, size, pathname);135free((void *) pathname);136pathname = NULL;137if (tz != NULL) {138return tz;139}140}141}142143dirp = opendir(dir);144if (dirp == NULL) {145return NULL;146}147148while ((dp = readdir(dirp)) != NULL) {149/*150* Skip '.' and '..' (and possibly other .* files)151*/152if (dp->d_name[0] == '.') {153continue;154}155156/*157* Skip "ROC", "posixrules", and "localtime".158*/159if ((strcmp(dp->d_name, "ROC") == 0)160|| (strcmp(dp->d_name, "posixrules") == 0)161|| (strcmp(dp->d_name, "localtime") == 0)) {162continue;163}164165pathname = getPathName(dir, dp->d_name);166if (pathname == NULL) {167break;168}169170tz = isFileIdentical(buf, size, pathname);171free((void *) pathname);172pathname = NULL;173if (tz != NULL) {174break;175}176}177178if (dirp != NULL) {179(void) closedir(dirp);180}181return tz;182}183184/*185* Checks if the file pointed to by pathname matches186* the data contents in buf.187* Returns a representation of the timezone file name188* if file match is found, otherwise NULL.189*/190static char *191isFileIdentical(char *buf, size_t size, char *pathname)192{193char *possibleMatch = NULL;194struct stat64 statbuf;195char *dbuf = NULL;196int fd = -1;197int res;198199RESTARTABLE(stat64(pathname, &statbuf), res);200if (res == -1) {201return NULL;202}203204if (S_ISDIR(statbuf.st_mode)) {205possibleMatch = findZoneinfoFile(buf, size, pathname);206} else if (S_ISREG(statbuf.st_mode) && (size_t)statbuf.st_size == size) {207dbuf = (char *) malloc(size);208if (dbuf == NULL) {209return NULL;210}211RESTARTABLE(open(pathname, O_RDONLY), fd);212if (fd == -1) {213goto freedata;214}215RESTARTABLE(read(fd, dbuf, size), res);216if (res != (ssize_t) size) {217goto freedata;218}219if (memcmp(buf, dbuf, size) == 0) {220possibleMatch = getZoneName(pathname);221if (possibleMatch != NULL) {222possibleMatch = strdup(possibleMatch);223}224}225freedata:226free((void *) dbuf);227(void) close(fd);228}229return possibleMatch;230}231232/*233* Performs Linux specific mapping and returns a zone ID234* if found. Otherwise, NULL is returned.235*/236static char *237getPlatformTimeZoneID()238{239struct stat64 statbuf;240char *tz = NULL;241FILE *fp;242int fd;243char *buf;244size_t size;245int res;246247#if defined(__linux__)248/*249* Try reading the /etc/timezone file for Debian distros. There's250* no spec of the file format available. This parsing assumes that251* there's one line of an Olson tzid followed by a '\n', no252* leading or trailing spaces, no comments.253*/254if ((fp = fopen(ETC_TIMEZONE_FILE, "r")) != NULL) {255char line[256];256257if (fgets(line, sizeof(line), fp) != NULL) {258char *p = strchr(line, '\n');259if (p != NULL) {260*p = '\0';261}262if (strlen(line) > 0) {263tz = strdup(line);264}265}266(void) fclose(fp);267if (tz != NULL) {268return tz;269}270}271#endif /* defined(__linux__) */272273/*274* Next, try /etc/localtime to find the zone ID.275*/276RESTARTABLE(lstat64(DEFAULT_ZONEINFO_FILE, &statbuf), res);277if (res == -1) {278return NULL;279}280281/*282* If it's a symlink, get the link name and its zone ID part. (The283* older versions of timeconfig created a symlink as described in284* the Red Hat man page. It was changed in 1999 to create a copy285* of a zoneinfo file. It's no longer possible to get the zone ID286* from /etc/localtime.)287*/288if (S_ISLNK(statbuf.st_mode)) {289char linkbuf[PATH_MAX+1];290int len;291292if ((len = readlink(DEFAULT_ZONEINFO_FILE, linkbuf, sizeof(linkbuf)-1)) == -1) {293jio_fprintf(stderr, (const char *) "can't get a symlink of %s\n",294DEFAULT_ZONEINFO_FILE);295return NULL;296}297linkbuf[len] = '\0';298tz = getZoneName(linkbuf);299if (tz != NULL) {300tz = strdup(tz);301return tz;302}303}304305/*306* If it's a regular file, we need to find out the same zoneinfo file307* that has been copied as /etc/localtime.308* If initial symbolic link resolution failed, we should treat target309* file as a regular file.310*/311RESTARTABLE(open(DEFAULT_ZONEINFO_FILE, O_RDONLY), fd);312if (fd == -1) {313return NULL;314}315316RESTARTABLE(fstat64(fd, &statbuf), res);317if (res == -1) {318(void) close(fd);319return NULL;320}321size = (size_t) statbuf.st_size;322buf = (char *) malloc(size);323if (buf == NULL) {324(void) close(fd);325return NULL;326}327328RESTARTABLE(read(fd, buf, size), res);329if (res != (ssize_t) size) {330(void) close(fd);331free((void *) buf);332return NULL;333}334(void) close(fd);335336tz = findZoneinfoFile(buf, size, ZONEINFO_DIR);337free((void *) buf);338return tz;339}340341#elif defined(_AIX)342343static char *344getPlatformTimeZoneID()345{346FILE *fp;347char *tz = NULL;348char *tz_key = "TZ=";349char line[256];350size_t tz_key_len = strlen(tz_key);351352if ((fp = fopen(ETC_ENVIRONMENT_FILE, "r")) != NULL) {353while (fgets(line, sizeof(line), fp) != NULL) {354char *p = strchr(line, '\n');355if (p != NULL) {356*p = '\0';357}358if (0 == strncmp(line, tz_key, tz_key_len)) {359tz = strdup(line + tz_key_len);360break;361}362}363(void) fclose(fp);364}365366return tz;367}368369static char *370mapPlatformToJavaTimezone(const char *java_home_dir, const char *tz) {371FILE *tzmapf;372char mapfilename[PATH_MAX + 1];373char line[256];374int linecount = 0;375char *tz_buf = NULL;376char *temp_tz = NULL;377char *javatz = NULL;378size_t tz_len = 0;379380/* On AIX, the TZ environment variable may end with a comma381* followed by modifier fields. These are ignored here. */382temp_tz = strchr(tz, ',');383tz_len = (temp_tz == NULL) ? strlen(tz) : temp_tz - tz;384tz_buf = (char *)malloc(tz_len + 1);385memcpy(tz_buf, tz, tz_len);386tz_buf[tz_len] = 0;387388/* Open tzmappings file, with buffer overrun check */389if ((strlen(java_home_dir) + 15) > PATH_MAX) {390jio_fprintf(stderr, "Path %s/lib/tzmappings exceeds maximum path length\n", java_home_dir);391goto tzerr;392}393strcpy(mapfilename, java_home_dir);394strcat(mapfilename, "/lib/tzmappings");395if ((tzmapf = fopen(mapfilename, "r")) == NULL) {396jio_fprintf(stderr, "can't open %s\n", mapfilename);397goto tzerr;398}399400while (fgets(line, sizeof(line), tzmapf) != NULL) {401char *p = line;402char *sol = line;403char *java;404int result;405406linecount++;407/*408* Skip comments and blank lines409*/410if (*p == '#' || *p == '\n') {411continue;412}413414/*415* Get the first field, platform zone ID416*/417while (*p != '\0' && *p != '\t') {418p++;419}420if (*p == '\0') {421/* mapping table is broken! */422jio_fprintf(stderr, "tzmappings: Illegal format at near line %d.\n", linecount);423break;424}425426*p++ = '\0';427if ((result = strncmp(tz_buf, sol, tz_len)) == 0) {428/*429* If this is the current platform zone ID,430* take the Java time zone ID (2nd field).431*/432java = p;433while (*p != '\0' && *p != '\n') {434p++;435}436437if (*p == '\0') {438/* mapping table is broken! */439jio_fprintf(stderr, "tzmappings: Illegal format at line %d.\n", linecount);440break;441}442443*p = '\0';444javatz = strdup(java);445break;446} else if (result < 0) {447break;448}449}450(void) fclose(tzmapf);451452tzerr:453if (tz_buf != NULL ) {454free((void *) tz_buf);455}456457if (javatz == NULL) {458return getGMTOffsetID();459}460461return javatz;462}463464#endif /* defined(_AIX) */465466/*467* findJavaTZ_md() maps platform time zone ID to Java time zone ID468* using <java_home>/lib/tzmappings. If the TZ value is not found, it469* trys some libc implementation dependent mappings. If it still470* can't map to a Java time zone ID, it falls back to the GMT+/-hh:mm471* form.472*/473/*ARGSUSED1*/474char *475findJavaTZ_md(const char *java_home_dir)476{477char *tz;478char *javatz = NULL;479char *freetz = NULL;480481tz = getenv("TZ");482483if (tz == NULL || *tz == '\0') {484tz = getPlatformTimeZoneID();485freetz = tz;486}487488if (tz != NULL) {489/* Ignore preceding ':' */490if (*tz == ':') {491tz++;492}493#if defined(__linux__)494/* Ignore "posix/" prefix on Linux. */495if (strncmp(tz, "posix/", 6) == 0) {496tz += 6;497}498#endif499500#if defined(_AIX)501/* On AIX do the platform to Java mapping. */502javatz = mapPlatformToJavaTimezone(java_home_dir, tz);503if (freetz != NULL) {504free((void *) freetz);505}506#else507if (freetz == NULL) {508/* strdup if we are still working on getenv result. */509javatz = strdup(tz);510} else if (freetz != tz) {511/* strdup and free the old buffer, if we moved the pointer. */512javatz = strdup(tz);513free((void *) freetz);514} else {515/* we are good if we already work on a freshly allocated buffer. */516javatz = tz;517}518#endif519}520521return javatz;522}523524/**525* Returns a GMT-offset-based zone ID. (e.g., "GMT-08:00")526*/527528#if defined(MACOSX)529530char *531getGMTOffsetID()532{533time_t offset;534char sign, buf[32];535struct tm local_tm;536time_t clock;537538clock = time(NULL);539if (localtime_r(&clock, &local_tm) == NULL) {540return strdup("GMT");541}542offset = (time_t)local_tm.tm_gmtoff;543if (offset == 0) {544return strdup("GMT");545}546if (offset > 0) {547sign = '+';548} else {549offset = -offset;550sign = '-';551}552sprintf(buf, (const char *)"GMT%c%02d:%02d",553sign, (int)(offset/3600), (int)((offset%3600)/60));554return strdup(buf);555}556557#else558559char *560getGMTOffsetID()561{562time_t offset;563char sign, buf[32];564offset = timezone;565566if (offset == 0) {567return strdup("GMT");568}569570/* Note that the time offset direction is opposite. */571if (offset > 0) {572sign = '-';573} else {574offset = -offset;575sign = '+';576}577sprintf(buf, (const char *)"GMT%c%02d:%02d",578sign, (int)(offset/3600), (int)((offset%3600)/60));579return strdup(buf);580}581#endif /* MACOSX */582583584