mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
585 lines
16 KiB
C
585 lines
16 KiB
C
/*
|
|
* Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <strings.h>
|
|
#include <time.h>
|
|
#include <limits.h>
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <string.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
|
|
#include "jvm.h"
|
|
#include "jni_util.h"
|
|
#include "TimeZone_md.h"
|
|
#include "path_util.h"
|
|
|
|
#define fileopen fopen
|
|
#define filegets fgets
|
|
#define fileclose fclose
|
|
|
|
#if defined(__linux__) || defined(_ALLBSD_SOURCE)
|
|
static const char *ETC_TIMEZONE_FILE = "/etc/timezone";
|
|
static const char *ZONEINFO_DIR = "/usr/share/zoneinfo";
|
|
static const char *DEFAULT_ZONEINFO_FILE = "/etc/localtime";
|
|
#else
|
|
static const char *SYS_INIT_FILE = "/etc/default/init";
|
|
static const char *ZONEINFO_DIR = "/usr/share/lib/zoneinfo";
|
|
static const char *DEFAULT_ZONEINFO_FILE = "/usr/share/lib/zoneinfo/localtime";
|
|
#endif /* defined(__linux__) || defined(_ALLBSD_SOURCE) */
|
|
|
|
static const char popularZones[][4] = {"UTC", "GMT"};
|
|
|
|
#if defined(__linux__) || defined(MACOSX)
|
|
static char *isFileIdentical(char* buf, size_t size, char *pathname);
|
|
|
|
/*
|
|
* remove repeated path separators ('/') in the given 'path'.
|
|
*/
|
|
static void
|
|
removeDuplicateSlashes(char *path)
|
|
{
|
|
char *left = path;
|
|
char *right = path;
|
|
char *end = path + strlen(path);
|
|
|
|
for (; right < end; right++) {
|
|
// Skip sequence of multiple path-separators.
|
|
while (*right == '/' && *(right + 1) == '/') {
|
|
right++;
|
|
}
|
|
|
|
while (*right != '\0' && !(*right == '/' && *(right + 1) == '/')) {
|
|
*left++ = *right++;
|
|
}
|
|
|
|
if (*right == '\0') {
|
|
*left = '\0';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns a pointer to the zone ID portion of the given zoneinfo file
|
|
* name, or NULL if the given string doesn't contain "zoneinfo/".
|
|
*/
|
|
static char *
|
|
getZoneName(char *str)
|
|
{
|
|
static const char *zidir = "zoneinfo/";
|
|
|
|
char *pos = strstr((const char *)str, zidir);
|
|
if (pos == NULL) {
|
|
return NULL;
|
|
}
|
|
return pos + strlen(zidir);
|
|
}
|
|
|
|
/*
|
|
* Returns a path name created from the given 'dir' and 'name' under
|
|
* UNIX. This function allocates memory for the pathname calling
|
|
* malloc(). NULL is returned if malloc() fails.
|
|
*/
|
|
static char *
|
|
getPathName(const char *dir, const char *name) {
|
|
char *path;
|
|
|
|
path = (char *) malloc(strlen(dir) + strlen(name) + 2);
|
|
if (path == NULL) {
|
|
return NULL;
|
|
}
|
|
return strcat(strcat(strcpy(path, dir), "/"), name);
|
|
}
|
|
|
|
/*
|
|
* Scans the specified directory and its subdirectories to find a
|
|
* zoneinfo file which has the same content as /etc/localtime on Linux
|
|
* or /usr/share/lib/zoneinfo/localtime on Solaris given in 'buf'.
|
|
* If file is symbolic link, then the contents it points to are in buf.
|
|
* Returns a zone ID if found, otherwise, NULL is returned.
|
|
*/
|
|
static char *
|
|
findZoneinfoFile(char *buf, size_t size, const char *dir)
|
|
{
|
|
DIR *dirp = NULL;
|
|
struct dirent *dp = NULL;
|
|
char *pathname = NULL;
|
|
char *tz = NULL;
|
|
|
|
if (strcmp(dir, ZONEINFO_DIR) == 0) {
|
|
/* fast path for 1st iteration */
|
|
for (unsigned int i = 0; i < sizeof (popularZones) / sizeof (popularZones[0]); i++) {
|
|
pathname = getPathName(dir, popularZones[i]);
|
|
if (pathname == NULL) {
|
|
continue;
|
|
}
|
|
tz = isFileIdentical(buf, size, pathname);
|
|
free((void *) pathname);
|
|
pathname = NULL;
|
|
if (tz != NULL) {
|
|
return tz;
|
|
}
|
|
}
|
|
}
|
|
|
|
dirp = opendir(dir);
|
|
if (dirp == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
while ((dp = readdir(dirp)) != NULL) {
|
|
/*
|
|
* Skip '.' and '..' (and possibly other .* files)
|
|
*/
|
|
if (dp->d_name[0] == '.') {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Skip "ROC", "posixrules", and "localtime".
|
|
*/
|
|
if ((strcmp(dp->d_name, "ROC") == 0)
|
|
|| (strcmp(dp->d_name, "posixrules") == 0)
|
|
|| (strcmp(dp->d_name, "localtime") == 0)) {
|
|
continue;
|
|
}
|
|
|
|
pathname = getPathName(dir, dp->d_name);
|
|
if (pathname == NULL) {
|
|
break;
|
|
}
|
|
|
|
tz = isFileIdentical(buf, size, pathname);
|
|
free((void *) pathname);
|
|
pathname = NULL;
|
|
if (tz != NULL) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dirp != NULL) {
|
|
(void) closedir(dirp);
|
|
}
|
|
return tz;
|
|
}
|
|
|
|
/*
|
|
* Checks if the file pointed to by pathname matches
|
|
* the data contents in buf.
|
|
* Returns a representation of the timezone file name
|
|
* if file match is found, otherwise NULL.
|
|
*/
|
|
static char *
|
|
isFileIdentical(char *buf, size_t size, char *pathname)
|
|
{
|
|
char *possibleMatch = NULL;
|
|
struct stat statbuf;
|
|
char *dbuf = NULL;
|
|
int fd = -1;
|
|
int res;
|
|
|
|
RESTARTABLE(stat(pathname, &statbuf), res);
|
|
if (res == -1) {
|
|
return NULL;
|
|
}
|
|
|
|
if (S_ISDIR(statbuf.st_mode)) {
|
|
possibleMatch = findZoneinfoFile(buf, size, pathname);
|
|
} else if (S_ISREG(statbuf.st_mode) && (size_t)statbuf.st_size == size) {
|
|
dbuf = (char *) malloc(size);
|
|
if (dbuf == NULL) {
|
|
return NULL;
|
|
}
|
|
RESTARTABLE(open(pathname, O_RDONLY), fd);
|
|
if (fd == -1) {
|
|
goto freedata;
|
|
}
|
|
RESTARTABLE(read(fd, dbuf, size), res);
|
|
if (res != (ssize_t) size) {
|
|
goto freedata;
|
|
}
|
|
if (memcmp(buf, dbuf, size) == 0) {
|
|
possibleMatch = getZoneName(pathname);
|
|
if (possibleMatch != NULL) {
|
|
possibleMatch = strdup(possibleMatch);
|
|
}
|
|
}
|
|
freedata:
|
|
free((void *) dbuf);
|
|
(void) close(fd);
|
|
}
|
|
return possibleMatch;
|
|
}
|
|
|
|
/*
|
|
* Performs Linux specific mapping and returns a zone ID
|
|
* if found. Otherwise, NULL is returned.
|
|
*/
|
|
static char *
|
|
getPlatformTimeZoneID()
|
|
{
|
|
struct stat statbuf;
|
|
char *tz = NULL;
|
|
FILE *fp;
|
|
int fd;
|
|
char *buf;
|
|
size_t size;
|
|
int res;
|
|
|
|
#if defined(__linux__)
|
|
/*
|
|
* Try reading the /etc/timezone file for Debian distros. There's
|
|
* no spec of the file format available. This parsing assumes that
|
|
* there's one line of an Olson tzid followed by a '\n', no
|
|
* leading or trailing spaces, no comments.
|
|
*/
|
|
if ((fp = fopen(ETC_TIMEZONE_FILE, "r")) != NULL) {
|
|
char line[256];
|
|
|
|
if (fgets(line, sizeof(line), fp) != NULL) {
|
|
char *p = strchr(line, '\n');
|
|
if (p != NULL) {
|
|
*p = '\0';
|
|
}
|
|
if (strlen(line) > 0) {
|
|
tz = strdup(line);
|
|
}
|
|
}
|
|
(void) fclose(fp);
|
|
if (tz != NULL) {
|
|
return tz;
|
|
}
|
|
}
|
|
#endif /* defined(__linux__) */
|
|
|
|
/*
|
|
* Next, try /etc/localtime to find the zone ID.
|
|
*/
|
|
RESTARTABLE(lstat(DEFAULT_ZONEINFO_FILE, &statbuf), res);
|
|
if (res == -1) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* If it's a symlink, get the link name and its zone ID part. (The
|
|
* older versions of timeconfig created a symlink as described in
|
|
* the Red Hat man page. It was changed in 1999 to create a copy
|
|
* of a zoneinfo file. It's no longer possible to get the zone ID
|
|
* from /etc/localtime.)
|
|
*/
|
|
if (S_ISLNK(statbuf.st_mode)) {
|
|
char linkbuf[PATH_MAX+1];
|
|
int len;
|
|
|
|
if ((len = readlink(DEFAULT_ZONEINFO_FILE, linkbuf, sizeof(linkbuf)-1)) == -1) {
|
|
jio_fprintf(stderr, (const char *) "can't get a symlink of %s\n",
|
|
DEFAULT_ZONEINFO_FILE);
|
|
return NULL;
|
|
}
|
|
linkbuf[len] = '\0';
|
|
removeDuplicateSlashes(linkbuf);
|
|
collapse(linkbuf);
|
|
tz = getZoneName(linkbuf);
|
|
if (tz != NULL) {
|
|
tz = strdup(tz);
|
|
return tz;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If it's a regular file, we need to find out the same zoneinfo file
|
|
* that has been copied as /etc/localtime.
|
|
* If initial symbolic link resolution failed, we should treat target
|
|
* file as a regular file.
|
|
*/
|
|
RESTARTABLE(open(DEFAULT_ZONEINFO_FILE, O_RDONLY), fd);
|
|
if (fd == -1) {
|
|
return NULL;
|
|
}
|
|
|
|
RESTARTABLE(fstat(fd, &statbuf), res);
|
|
if (res == -1) {
|
|
(void) close(fd);
|
|
return NULL;
|
|
}
|
|
size = (size_t) statbuf.st_size;
|
|
buf = (char *) malloc(size);
|
|
if (buf == NULL) {
|
|
(void) close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
RESTARTABLE(read(fd, buf, size), res);
|
|
if (res != (ssize_t) size) {
|
|
(void) close(fd);
|
|
free((void *) buf);
|
|
return NULL;
|
|
}
|
|
(void) close(fd);
|
|
|
|
tz = findZoneinfoFile(buf, size, ZONEINFO_DIR);
|
|
free((void *) buf);
|
|
return tz;
|
|
}
|
|
|
|
#elif defined(_AIX)
|
|
static const char *ETC_ENVIRONMENT_FILE = "/etc/environment";
|
|
|
|
static char *
|
|
getPlatformTimeZoneID()
|
|
{
|
|
FILE *fp;
|
|
char *tz = NULL;
|
|
char *tz_key = "TZ=";
|
|
char line[256];
|
|
size_t tz_key_len = strlen(tz_key);
|
|
|
|
if ((fp = fopen(ETC_ENVIRONMENT_FILE, "r")) != NULL) {
|
|
while (fgets(line, sizeof(line), fp) != NULL) {
|
|
char *p = strchr(line, '\n');
|
|
if (p != NULL) {
|
|
*p = '\0';
|
|
}
|
|
if (0 == strncmp(line, tz_key, tz_key_len)) {
|
|
tz = strdup(line + tz_key_len);
|
|
break;
|
|
}
|
|
}
|
|
(void) fclose(fp);
|
|
}
|
|
|
|
return tz;
|
|
}
|
|
|
|
static char *
|
|
mapPlatformToJavaTimezone(const char *java_home_dir, const char *tz) {
|
|
FILE *tzmapf;
|
|
char mapfilename[PATH_MAX + 1];
|
|
char line[256];
|
|
int linecount = 0;
|
|
char *tz_buf = NULL;
|
|
char *temp_tz = NULL;
|
|
char *javatz = NULL;
|
|
size_t tz_len = 0;
|
|
|
|
/* On AIX, the TZ environment variable may end with a comma
|
|
* followed by modifier fields until early AIX6.1.
|
|
* This restriction has been removed from AIX7. */
|
|
|
|
tz_buf = strdup(tz);
|
|
tz_len = strlen(tz_buf);
|
|
|
|
/* Open tzmappings file, with buffer overrun check */
|
|
if ((strlen(java_home_dir) + 15) > PATH_MAX) {
|
|
jio_fprintf(stderr, "Path %s/lib/tzmappings exceeds maximum path length\n", java_home_dir);
|
|
goto tzerr;
|
|
}
|
|
strcpy(mapfilename, java_home_dir);
|
|
strcat(mapfilename, "/lib/tzmappings");
|
|
if ((tzmapf = fopen(mapfilename, "r")) == NULL) {
|
|
jio_fprintf(stderr, "can't open %s\n", mapfilename);
|
|
goto tzerr;
|
|
}
|
|
|
|
while (fgets(line, sizeof(line), tzmapf) != NULL) {
|
|
char *p = line;
|
|
char *sol = line;
|
|
char *java;
|
|
int result;
|
|
|
|
linecount++;
|
|
/*
|
|
* Skip comments and blank lines
|
|
*/
|
|
if (*p == '#' || *p == '\n') {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Get the first field, platform zone ID
|
|
*/
|
|
while (*p != '\0' && *p != '\t') {
|
|
p++;
|
|
}
|
|
if (*p == '\0') {
|
|
/* mapping table is broken! */
|
|
jio_fprintf(stderr, "tzmappings: Illegal format at near line %d.\n", linecount);
|
|
break;
|
|
}
|
|
|
|
*p++ = '\0';
|
|
if ((result = strncmp(tz_buf, sol, tz_len)) == 0) {
|
|
/*
|
|
* If this is the current platform zone ID,
|
|
* take the Java time zone ID (2nd field).
|
|
*/
|
|
java = p;
|
|
while (*p != '\0' && *p != '\n') {
|
|
p++;
|
|
}
|
|
|
|
if (*p == '\0') {
|
|
/* mapping table is broken! */
|
|
jio_fprintf(stderr, "tzmappings: Illegal format at line %d.\n", linecount);
|
|
break;
|
|
}
|
|
|
|
*p = '\0';
|
|
javatz = strdup(java);
|
|
break;
|
|
} else if (result < 0) {
|
|
break;
|
|
}
|
|
}
|
|
(void) fclose(tzmapf);
|
|
|
|
tzerr:
|
|
if (tz_buf != NULL ) {
|
|
free((void *) tz_buf);
|
|
}
|
|
|
|
if (javatz == NULL) {
|
|
return getGMTOffsetID();
|
|
}
|
|
|
|
return javatz;
|
|
}
|
|
|
|
#endif /* defined(_AIX) */
|
|
|
|
/*
|
|
* findJavaTZ_md() maps platform time zone ID to Java time zone ID
|
|
* using <java_home>/lib/tzmappings. If the TZ value is not found, it
|
|
* tries some libc implementation dependent mappings. If it still
|
|
* can't map to a Java time zone ID, it falls back to the GMT+/-hh:mm
|
|
* form.
|
|
*/
|
|
/*ARGSUSED1*/
|
|
char *
|
|
findJavaTZ_md(const char *java_home_dir)
|
|
{
|
|
char *tz;
|
|
char *javatz = NULL;
|
|
char *freetz = NULL;
|
|
|
|
tz = getenv("TZ");
|
|
|
|
if (tz == NULL || *tz == '\0') {
|
|
tz = getPlatformTimeZoneID();
|
|
freetz = tz;
|
|
}
|
|
|
|
if (tz != NULL) {
|
|
/* Ignore preceding ':' */
|
|
if (*tz == ':') {
|
|
tz++;
|
|
}
|
|
#if defined(__linux__)
|
|
/* Ignore "posix/" prefix on Linux. */
|
|
if (strncmp(tz, "posix/", 6) == 0) {
|
|
tz += 6;
|
|
}
|
|
#endif
|
|
|
|
#if defined(_AIX)
|
|
/* On AIX do the platform to Java mapping. */
|
|
javatz = mapPlatformToJavaTimezone(java_home_dir, tz);
|
|
if (freetz != NULL) {
|
|
free((void *) freetz);
|
|
}
|
|
#else
|
|
if (freetz == NULL) {
|
|
/* strdup if we are still working on getenv result. */
|
|
javatz = strdup(tz);
|
|
} else if (freetz != tz) {
|
|
/* strdup and free the old buffer, if we moved the pointer. */
|
|
javatz = strdup(tz);
|
|
free((void *) freetz);
|
|
} else {
|
|
/* we are good if we already work on a freshly allocated buffer. */
|
|
javatz = tz;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return javatz;
|
|
}
|
|
|
|
/**
|
|
* Returns a GMT-offset-based zone ID. (e.g., "GMT-08:00")
|
|
*/
|
|
char *
|
|
getGMTOffsetID()
|
|
{
|
|
char buf[32];
|
|
char offset[6];
|
|
struct tm localtm;
|
|
time_t clock = time(NULL);
|
|
if (localtime_r(&clock, &localtm) == NULL) {
|
|
return strdup("GMT");
|
|
}
|
|
|
|
#if defined(MACOSX)
|
|
time_t gmt_offset;
|
|
gmt_offset = (time_t)localtm.tm_gmtoff;
|
|
if (gmt_offset == 0) {
|
|
return strdup("GMT");
|
|
}
|
|
#else
|
|
struct tm gmt;
|
|
if (gmtime_r(&clock, &gmt) == NULL) {
|
|
return strdup("GMT");
|
|
}
|
|
|
|
if(localtm.tm_hour == gmt.tm_hour && localtm.tm_min == gmt.tm_min) {
|
|
return strdup("GMT");
|
|
}
|
|
#endif
|
|
|
|
#if defined(_AIX)
|
|
// strftime() with "%z" does not return ISO 8601 format by AIX default.
|
|
// XPG_SUS_ENV=ON environment variable is required.
|
|
// But Hotspot does not support XPG_SUS_ENV=ON.
|
|
// Ignore daylight saving settings to calculate current time difference
|
|
localtm.tm_isdst = 0;
|
|
int gmt_off = (int)(difftime(mktime(&localtm), mktime(&gmt)) / 60.0);
|
|
snprintf(buf, sizeof(buf), (const char *)"GMT%c%02.2d:%02.2d",
|
|
gmt_off < 0 ? '-' : '+' , abs(gmt_off / 60), gmt_off % 60);
|
|
#else
|
|
if (strftime(offset, 6, "%z", &localtm) != 5) {
|
|
return strdup("GMT");
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf), (const char *)"GMT%c%c%c:%c%c", offset[0], offset[1], offset[2],
|
|
offset[3], offset[4]);
|
|
#endif
|
|
return strdup(buf);
|
|
}
|