blob: 9a2a0b437b98e12a28ffc4093541d8ba628775fb [file] [log] [blame]
/*
* Copyright (c) 1999, 2004, 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>
#ifdef __linux__
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include "jvm.h"
#define SKIP_SPACE(p) while (*p == ' ' || *p == '\t') p++;
#if !defined(__solaris__) || defined(__sparcv9) || defined(amd64)
#define fileopen fopen
#define filegets fgets
#define fileclose fclose
#endif
#ifdef __linux__
static const char *ETC_TIMEZONE_FILE = "/etc/timezone";
static const char *ZONEINFO_DIR = "/usr/share/zoneinfo";
static const char *DEFAULT_ZONEINFO_FILE = "/etc/localtime";
/*
* Returns a point to the zone ID portion of the given zoneinfo file
* name.
*/
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().
*/
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 given 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 stat statbuf;
union {
struct dirent d;
char b[offsetof (struct dirent, d_name) + NAME_MAX + 1];
} entry;
struct dirent *dp;
char *pathname = NULL;
int fd = -1;
char *dbuf = NULL;
char *tz = NULL;
dirp = opendir(dir);
if (dirp == NULL) {
return NULL;
}
while (readdir_r(dirp, &entry.d, &dp) == 0 && dp != NULL) {
/*
* Skip '.' and '..' (and possibly other .* files)
*/
if (dp->d_name[0] == '.') {
continue;
}
/*
* Skip "ROC", "posixrules", and "localtime" since Java doesn't
* support them.
*/
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;
}
if (stat(pathname, &statbuf) == -1) {
break;
}
if (S_ISDIR(statbuf.st_mode)) {
tz = findZoneinfoFile(buf, size, pathname);
if (tz != NULL) {
break;
}
} else if (S_ISREG(statbuf.st_mode) && (size_t)statbuf.st_size == size) {
dbuf = (char *) malloc(size);
if (dbuf == NULL) {
break;
}
if ((fd = open(pathname, O_RDONLY)) == -1) {
fd = 0;
break;
}
if (read(fd, dbuf, size) != (ssize_t) size) {
break;
}
if (memcmp(buf, dbuf, size) == 0) {
tz = getZoneName(pathname);
if (tz != NULL) {
tz = strdup(tz);
}
break;
}
free((void *) dbuf);
dbuf = NULL;
(void) close(fd);
fd = 0;
}
free((void *) pathname);
pathname = NULL;
}
if (dirp != NULL) {
(void) closedir(dirp);
}
if (pathname != NULL) {
free((void *) pathname);
}
if (fd != 0) {
(void) close(fd);
}
if (dbuf != NULL) {
free((void *) dbuf);
}
return tz;
}
/*
* Performs libc implementation 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;
/*
* 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;
}
}
/*
* Next, try /etc/localtime to find the zone ID.
*/
if (lstat(DEFAULT_ZONEINFO_FILE, &statbuf) == -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';
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.
*/
size = (size_t) statbuf.st_size;
buf = (char *) malloc(size);
if (buf == NULL) {
return NULL;
}
if ((fd = open(DEFAULT_ZONEINFO_FILE, O_RDONLY)) == -1) {
free((void *) buf);
return NULL;
}
if (read(fd, buf, size) != (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;
}
#else
#ifdef __solaris__
#if !defined(__sparcv9) && !defined(amd64)
/*
* Those file* functions mimic the UNIX stream io functions. This is
* because of the limitation of the number of open files on Solaris
* (32-bit mode only) due to the System V ABI.
*/
#define BUFFER_SIZE 4096
static struct iobuffer {
int magic; /* -1 to distinguish from the real FILE */
int fd; /* file descriptor */
char *buffer; /* pointer to buffer */
char *ptr; /* current read pointer */
char *endptr; /* end pointer */
};
static int
fileclose(FILE *stream)
{
struct iobuffer *iop = (struct iobuffer *) stream;
if (iop->magic != -1) {
return fclose(stream);
}
if (iop == NULL) {
return 0;
}
close(iop->fd);
free((void *)iop->buffer);
free((void *)iop);
return 0;
}
static FILE *
fileopen(const char *fname, const char *fmode)
{
FILE *fp;
int fd;
struct iobuffer *iop;
if ((fp = fopen(fname, fmode)) != NULL) {
return fp;
}
/*
* It assumes read open.
*/
if ((fd = open(fname, O_RDONLY)) == -1) {
return NULL;
}
/*
* Allocate struct iobuffer and its buffer
*/
iop = malloc(sizeof(struct iobuffer));
if (iop == NULL) {
(void) close(fd);
errno = ENOMEM;
return NULL;
}
iop->magic = -1;
iop->fd = fd;
iop->buffer = malloc(BUFFER_SIZE);
if (iop->buffer == NULL) {
(void) close(fd);
free((void *) iop);
errno = ENOMEM;
return NULL;
}
iop->ptr = iop->buffer;
iop->endptr = iop->buffer;
return (FILE *)iop;
}
/*
* This implementation assumes that n is large enough and the line
* separator is '\n'.
*/
static char *
filegets(char *s, int n, FILE *stream)
{
struct iobuffer *iop = (struct iobuffer *) stream;
char *p;
if (iop->magic != -1) {
return fgets(s, n, stream);
}
p = s;
for (;;) {
char c;
if (iop->ptr == iop->endptr) {
ssize_t len;
if ((len = read(iop->fd, (void *)iop->buffer, BUFFER_SIZE)) == -1) {
return NULL;
}
if (len == 0) {
*p = 0;
if (s == p) {
return NULL;
}
return s;
}
iop->ptr = iop->buffer;
iop->endptr = iop->buffer + len;
}
c = *iop->ptr++;
*p++ = c;
if ((p - s) == (n - 1)) {
*p = 0;
return s;
}
if (c == '\n') {
*p = 0;
return s;
}
}
/*NOTREACHED*/
}
#endif /* not __sparcv9 */
static const char *sys_init_file = "/etc/default/init";
/*
* Performs libc implementation dependent mapping. Returns a zone ID
* if found. Otherwise, NULL is returned. Solaris libc looks up
* "/etc/default/init" to get a default TZ value if TZ is not defined
* as an environment variable.
*/
static char *
getPlatformTimeZoneID()
{
char *tz = NULL;
FILE *fp;
/*
* Try the TZ entry in /etc/default/init.
*/
if ((fp = fileopen(sys_init_file, "r")) != NULL) {
char line[256];
char quote = '\0';
while (filegets(line, sizeof(line), fp) != NULL) {
char *p = line;
char *s;
char c;
/* quick check for comment lines */
if (*p == '#') {
continue;
}
if (strncmp(p, "TZ=", 3) == 0) {
p += 3;
SKIP_SPACE(p);
c = *p;
if (c == '"' || c == '\'') {
quote = c;
p++;
}
/*
* PSARC/2001/383: quoted string support
*/
for (s = p; (c = *s) != '\0' && c != '\n'; s++) {
/* No '\\' is supported here. */
if (c == quote) {
quote = '\0';
break;
}
if (c == ' ' && quote == '\0') {
break;
}
}
if (quote != '\0') {
jio_fprintf(stderr, "ZoneInfo: unterminated time zone name in /etc/TIMEZONE\n");
}
*s = '\0';
tz = strdup(p);
break;
}
}
(void) fileclose(fp);
}
return tz;
}
#endif
#endif
/*
* 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
* trys 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. `country', which can be null, is not used for UNIX platforms.
*/
/*ARGSUSED1*/
char *
findJavaTZ_md(const char *java_home_dir, const char *country)
{
char *tz;
char *javatz = NULL;
char *freetz = NULL;
tz = getenv("TZ");
#ifdef __linux__
if (tz == NULL) {
#else
#ifdef __solaris__
if (tz == NULL || *tz == '\0') {
#endif
#endif
tz = getPlatformTimeZoneID();
freetz = tz;
}
if (tz != NULL) {
if (*tz == ':') {
tz++;
}
#ifdef __linux__
/*
* Ignore "posix/" prefix.
*/
if (strncmp(tz, "posix/", 6) == 0) {
tz += 6;
}
#endif
javatz = strdup(tz);
if (freetz != NULL) {
free((void *) freetz);
}
}
return javatz;
}
/**
* Returns a GMT-offset-based time zone ID. (e.g., "GMT-08:00")
*/
char *
getGMTOffsetID()
{
time_t offset;
char sign, buf[16];
if (timezone == 0) {
return strdup("GMT");
}
/* Note that the time offset direction is opposite. */
if (timezone > 0) {
offset = timezone;
sign = '-';
} else {
offset = -timezone;
sign = '+';
}
sprintf(buf, (const char *)"GMT%c%02d:%02d",
sign, (int)(offset/3600), (int)((offset%3600)/60));
return strdup(buf);
}