localtime.c revision 06e1a7147edd272b7296f208141627a5b1191731
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/* Copyright (c) 1988 AT&T */
/* All Rights Reserved */
/*
* A part of this file comes from public domain source, so
* clarified as of June 5, 1996 by Arthur David Olson
* (arthur_david_olson@nih.gov).
*/
/*
* localtime.c
*
* This file contains routines to convert struct tm to time_t and
* back as well as adjust time values based on their timezone, which
* is a local offset from GMT (Greenwich Mean Time).
*
* Many timezones actually consist of more than one offset from GMT.
* The GMT offset that is considered the normal offset is referred
* to as standard time. The other offset is referred to as alternate
* time, but is better known as daylight savings time or summer time.
*
* The current timezone for an application is derived from the TZ
* environment variable either as defined in the environment or in
* /etc/default/init. As defined by IEEE 1003.1-1990 (POSIX), the
* TZ variable can either be:
* :<characters>
* or
* <std><offset1>[<dst>[<offset2>]][,<start>[/<time>],<end>[/<time>]
*
* <characters> is an implementation-defined string that somehow describes
* a timezone. The implementation-defined description of a timezone used
* in Solaris is based on the public domain zoneinfo code available from
* elsie.nci.nih.gov and a timezone that is specified in this way is
* referred to as a zoneinfo timezone. An example of this is ":US/Pacific".
*
* The precise definition of the second format can be found in POSIX,
* but, basically, <std> is the abbreviation for the timezone in standard
* (not daylight savings time), <offset1> is the standard offset from GMT,
* <dst> is the abbreviation for the timezone in daylight savings time and
* <offset2> is the daylight savings time offset from GMT. The remainder
* specifies when daylight savings time begins and ends. A timezone
* specified in this way is referred to as a POSIX timezone. An example
* of this is "PST7PDT".
*
* In Solaris, there is an extension to this. If the timezone is not
* preceded by a ":" and it does not parse as a POSIX timezone, then it
* will be treated as a zoneinfo timezone. Much usage of zoneinfo
* timezones in Solaris is done without the leading ":".
*
* A zoneinfo timezone is a reference to a file that contains a set of
* rules that describe the timezone. In Solaris, the file is in
* /usr/share/lib/zoneinfo. The file is generated by zic(1M), based
* on zoneinfo rules "source" files. This is all described on the zic(1M)
* man page.
*/
/*
* Functions that are common to ctime(3C) and cftime(3C)
*/
#pragma weak tzset = _tzset
#pragma weak localtime_r = _localtime_r
#pragma weak gmtime_r = _gmtime_r
#include "synonyms.h"
#include "libc.h"
#include "tsd.h"
#include <stdarg.h>
#include <mtlib.h>
#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
#include <limits.h>
#include <sys/param.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <tzfile.h>
#include <thread.h>
#include <synch.h>
#include <fcntl.h>
#include <errno.h>
#include <deflt.h>
#include <sys/stat.h>
/* JAN_01_1902 cast to (int) - negative number of seconds from 1970 */
#define JAN_01_1902 (int)0x8017E880
#define LEN_TZDIR (sizeof (TZDIR) - 1)
#define TIMEZONE "/etc/default/init"
#define TZSTRING "TZ="
#define HASHTABLE 109
#define LEAPS_THRU_END_OF(y) ((y) / 4 - (y) / 100 + (y) / 400)
/* Days since 1/1/70 to 12/31/(1900 + Y - 1) */
#define DAYS_SINCE_70(Y) (YR((Y)-1L) - YR(70-1))
#define YR(X) /* Calc # days since 0 A.D. X = curr. yr - 1900 */ \
((1900L + (X)) * 365L + (1900L + (X)) / 4L - \
(1900L + (X)) / 100L + ((1900L + (X)) - 1600L) / 400L)
/*
* The following macros are replacements for detzcode(), which has
* been in the public domain versions of the localtime.c code for
* a long time. The primatives supporting the CVTZCODE macro are
* implemented differently for different endianness (ie. little
* vs. big endian) out of necessity, to account for the different
* byte ordering of the quantities being fetched. Both versions
* are substantially faster than the detzcode() macro. The big
* endian version is approx. 6.8x faster than detzcode(), the
* little endian version is approximately 3x faster, due to the
* extra shifting requiring to change byte order. The micro
* benchmarks used to compare were based on the SUNWSpro SC6.1
* (and later) compilers.
*/
#if defined(__sparc) || defined(__sparcv9) /* big endian */
#define GET_LONG(p) \
*(uint_t *)(p)
#define GET_SHORTS(p) \
*(ushort_t *)(p) << 16 |\
*(ushort_t *)((p) + 2)
#define GET_CHARS(p) \
*(uchar_t *)(p) << 24 |\
*(uchar_t *)((p) + 1) << 16 |\
*(uchar_t *)((p) + 2) << 8 |\
*(uchar_t *)((p) + 3)
#else /* little endian */
#define GET_BYTE(x) \
((x) & 0xff)
#define SWAP_BYTES(x) ((\
GET_BYTE(x) << 8) |\
GET_BYTE((x) >> 8))
#define SWAP_WORDS(x) ((\
SWAP_BYTES(x) << 16) |\
SWAP_BYTES((x) >> 16))
#define GET_LONG(p) \
SWAP_WORDS(*(uint_t *)(p))
#define GET_SHORTS(p) \
SWAP_BYTES(*(ushort_t *)(p)) << 16 |\
SWAP_BYTES(*(ushort_t *)((p) + 2))
#define GET_CHARS(p) \
GET_BYTE(*(uchar_t *)(p)) << 24 |\
GET_BYTE(*(uchar_t *)((p) + 1)) << 16 |\
GET_BYTE(*(uchar_t *)((p) + 2)) << 8 |\
GET_BYTE(*(uchar_t *)((p) + 3))
#endif
#define IF_ALIGNED(ptr, byte_alignment) \
!((uintptr_t)(ptr) & (byte_alignment - 1))
#define CVTZCODE(p) (int)(\
IF_ALIGNED(p, 4) ? GET_LONG(p) :\
IF_ALIGNED(p, 2) ? GET_SHORTS(p) : GET_CHARS(p));\
p += 4;
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE (1)
#endif
extern mutex_t _time_lock;
extern const int __lyday_to_month[];
extern const int __yday_to_month[];
extern const int __mon_lengths[2][MONS_PER_YEAR];
extern const int __year_lengths[2];
const char _tz_gmt[4] = "GMT"; /* "GMT" */
const char _tz_spaces[4] = " "; /* " " */
static const char _posix_gmt0[5] = "GMT0"; /* "GMT0" */
typedef struct ttinfo { /* Time type information */
long tt_gmtoff; /* GMT offset in seconds */
int tt_isdst; /* used to set tm_isdst */
int tt_abbrind; /* abbreviation list index */
int tt_ttisstd; /* TRUE if trans is std time */
int tt_ttisgmt; /* TRUE if transition is GMT */
} ttinfo_t;
typedef struct lsinfo { /* Leap second information */
time_t ls_trans; /* transition time */
long ls_corr; /* correction to apply */
} lsinfo_t;
typedef struct previnfo { /* Info about *prev* trans */
ttinfo_t *std; /* Most recent std type */
ttinfo_t *alt; /* Most recent alt type */
} prev_t;
typedef enum {
MON_WEEK_DOW, /* Mm.n.d - month, week, day of week */
JULIAN_DAY, /* Jn - Julian day */
DAY_OF_YEAR /* n - day of year */
} posrule_type_t;
typedef struct {
posrule_type_t r_type; /* type of rule */
int r_day; /* day number of rule */
int r_week; /* week number of rule */
int r_mon; /* month number of rule */
long r_time; /* transition time of rule */
} rule_t;
typedef struct {
rule_t *rules[2];
long offset[2];
long long rtime[2];
} posix_daylight_t;
/*
* Note: ZONERULES_INVALID used for global curr_zonerules variable, but not
* for zonerules field of state_t.
*/
typedef enum {
ZONERULES_INVALID, POSIX, POSIX_USA, ZONEINFO
} zone_rules_t;
/*
* The following members are allocated from the libc-internal malloc:
*
* zonename
* chars
*/
typedef struct state {
const char *zonename; /* Timezone */
struct state *next; /* next state */
zone_rules_t zonerules; /* Type of zone */
int daylight; /* daylight global */
long default_timezone; /* Def. timezone val */
long default_altzone; /* Def. altzone val */
const char *default_tzname0; /* Def tz..[0] val */
const char *default_tzname1; /* Def tz..[1] val */
int leapcnt; /* # leap sec trans */
int timecnt; /* # transitions */
int typecnt; /* # zone types */
int charcnt; /* # zone abbv. chars */
char *chars; /* Zone abbv. chars */
size_t charsbuf_size; /* malloc'ed buflen */
prev_t prev[TZ_MAX_TIMES]; /* Pv. trans info */
time_t ats[TZ_MAX_TIMES]; /* Trans. times */
uchar_t types[TZ_MAX_TIMES]; /* Type indices */
ttinfo_t ttis[TZ_MAX_TYPES]; /* Zone types */
lsinfo_t lsis[TZ_MAX_LEAPS]; /* Leap sec trans */
rule_t start_rule; /* For POSIX w/rules */
rule_t end_rule; /* For POSIX w/rules */
} state_t;
typedef struct systemtz {
const char *tz;
state_t *entry;
int flag;
} systemtz_t;
static const char *namecache;
static state_t *tzcache[HASHTABLE];
static state_t *lclzonep;
static struct tm tm; /* For non-reentrant use */
static int is_in_dst; /* Set if t is in DST */
static zone_rules_t curr_zonerules = ZONERULES_INVALID;
static int cached_year; /* mktime() perf. enhancement */
static long long cached_secs_since_1970; /* mktime() perf. */
static int year_is_cached = FALSE; /* mktime() perf. */
#define _2AM (2 * SECS_PER_HOUR)
#define FIRSTWEEK 1
#define LASTWEEK 5
enum wks {
_1st_week = 1,
_2nd_week,
_3rd_week,
_4th_week,
_Last_week
};
enum dwk {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat
};
enum mth {
Jan = 1,
Feb,
Mar,
Apr,
May,
Jun,
Jul,
Aug,
Sep,
Oct,
Nov,
Dec
};
/*
* The following table defines standard USA DST transitions
* as they have been declared throughout history, disregarding
* the legally sanctioned local variants.
*
* Note: At some point, this table may be supplanted by
* more popular 'posixrules' logic.
*/
typedef struct {
int s_year;
int e_year;
rule_t start;
rule_t end;
} __usa_rules_t;
static const __usa_rules_t __usa_rules[] = {
{
2007, 2037,
{ MON_WEEK_DOW, Sun, _2nd_week, Mar, _2AM },
{ MON_WEEK_DOW, Sun, _1st_week, Nov, _2AM },
},
{
1987, 2006,
{ MON_WEEK_DOW, Sun, _1st_week, Apr, _2AM },
{ MON_WEEK_DOW, Sun, _Last_week, Oct, _2AM },
},
{
1976, 1986,
{ MON_WEEK_DOW, Sun, _Last_week, Apr, _2AM },
{ MON_WEEK_DOW, Sun, _Last_week, Oct, _2AM },
},
{
1975, 1975,
{ MON_WEEK_DOW, Sun, _Last_week, Feb, _2AM },
{ MON_WEEK_DOW, Sun, _Last_week, Oct, _2AM },
},
{
1974, 1974,
{ MON_WEEK_DOW, Sun, _1st_week, Jan, _2AM },
{ MON_WEEK_DOW, Sun, _Last_week, Nov, _2AM },
},
/*
* The entry below combines two previously separate entries for
* 1969-1973 and 1902-1968
*/
{
1902, 1973,
{ MON_WEEK_DOW, Sun, _Last_week, Apr, _2AM },
{ MON_WEEK_DOW, Sun, _Last_week, Oct, _2AM },
}
};
#define MAX_RULE_TABLE (sizeof (__usa_rules) / sizeof (__usa_rules_t) - 1)
/*
* Prototypes for static functions.
*/
static systemtz_t *getsystemTZ(systemtz_t *);
static const char *getzname(const char *, int);
static const char *getnum(const char *, int *, int, int);
static const char *getsecs(const char *, long *);
static const char *getoffset(const char *, long *);
static const char *getrule(const char *, rule_t *, int);
static int load_posixinfo(const char *, state_t *);
static int load_zoneinfo(const char *, state_t *);
static void ltzset_u(time_t, systemtz_t *);
static struct tm *offtime_u(time_t, long, struct tm *);
static int posix_check_dst(long long, state_t *);
static int posix_daylight(long long *, int, posix_daylight_t *);
static void set_zone_context(time_t);
/*
* definition of difftime
*
* This code assumes time_t is type long. Note the difference of two
* longs in absolute value is representable as an unsigned long. So,
* compute the absolute value of the difference, cast the result to
* double and attach the sign back on.
*
* Note this code assumes 2's complement arithmetic. The subtraction
* operation may overflow when using signed operands, but when the
* result is cast to unsigned long, it yields the desired value
* (ie, the absolute value of the difference). The cast to unsigned
* long is done using pointers to avoid undefined behavior if casting
* a negative value to unsigned.
*/
double
difftime(time_t time1, time_t time0)
{
if (time1 < time0) {
time0 -= time1;
return (-(double)*(unsigned long *) &time0);
} else {
time1 -= time0;
return ((double)*(unsigned long *) &time1);
}
}
/*
* Accepts a time_t, returns a tm struct based on it, with
* no local timezone adjustment.
*
* This routine is the thread-safe variant of gmtime(), and
* requires that the call provide the address of their own tm
* struct.
*
* Locking is not done here because set_zone_context()
* is not called, thus timezone, altzone, and tzname[] are not
* accessed, no memory is allocated, and no common dynamic
* data is accessed.
*
* See ctime(3C)
*/
struct tm *
_gmtime_r(const time_t *timep, struct tm *p_tm)
{
return (offtime_u((time_t)*timep, 0L, p_tm));
}
/*
* Accepts a time_t, returns a tm struct based on it, with
* no local timezone adjustment.
*
* This function is explicitly NOT THREAD-SAFE. The standards
* indicate it should provide its results in its own statically
* allocated tm struct that gets overwritten. The thread-safe
* variant is gmtime_r(). We make it mostly thread-safe by
* allocating its buffer in thread-specific data.
*
* See ctime(3C)
*/
struct tm *
gmtime(const time_t *timep)
{
struct tm *p_tm = tsdalloc(_T_STRUCT_TM, sizeof (struct tm), NULL);
if (p_tm == NULL) /* memory allocation failure */
p_tm = &tm; /* use static buffer and hope for the best */
return (_gmtime_r(timep, p_tm));
}
/*
* This is the hashing function, based on the input timezone name.
*/
static int
get_hashid(const char *id)
{
const unsigned char *s = (const unsigned char *)id;
unsigned char c;
unsigned int h;
h = *s++;
while ((c = *s++) != '\0') {
h = (h << 5) - h + c;
}
return ((int)(h % HASHTABLE));
}
/*
* find_zone() gets the hashid for zonename, then uses the hashid
* to search the hash table for the appropriate timezone entry. If
* the entry for zonename is found in the hash table, return a pointer
* to the entry. Otherwise, update the input link_prev and link_next
* to the addresses of pointers for the caller to update to add the new
* entry to the hash table.
*/
static state_t *
find_zone(const char *zonename, state_t ***link_prev, state_t **link_next)
{
int hashid;
state_t *cur, *prv;
hashid = get_hashid(zonename);
cur = tzcache[hashid];
prv = NULL;
while (cur) {
int res;
res = strcmp(cur->zonename, zonename);
if (res == 0) {
return (cur);
} else if (res > 0) {
break;
}
prv = cur;
cur = cur->next;
}
if (prv) {
*link_prev = &prv->next;
*link_next = cur;
} else {
*link_prev = &tzcache[hashid];
*link_next = NULL;
}
return (NULL);
}
/*
* Returns tm struct based on input time_t argument, correcting
* for the local timezone, producing documented side-effects
* to extern global state, timezone, altzone, daylight and tzname[].
*
* localtime_r() is the thread-safe variant of localtime().
*
* IMPLEMENTATION NOTE:
*
* Locking slows multithreaded access and is probably ultimately
* unnecessary here. The POSIX specification is a bit vague
* as to whether the extern variables set by tzset() need to
* set as a result of a call to localtime_r()
*
* Currently, the spec only mentions that tzname[] doesn't
* need to be set. As soon as it becomes unequivocal
* that the external zone state doesn't need to be asserted
* for this call, and it really doesn't make much sense
* to set common state from multi-threaded calls made to this
* function, locking can be dispensed with here.
*
* local zone state would still need to be aquired for the
* time in question in order for calculations elicited here
* to be correct, but that state wouldn't need to be shared,
* thus no multi-threaded synchronization would be required.
*
* It would be nice if POSIX would approve an ltzset_r()
* function, but if not, it wouldn't stop us from making one
* privately.
*
* localtime_r() can now return NULL if overflow is detected.
* offtime_u() is the function that detects overflow, and sets
* errno appropriately. We unlock before the call to offtime_u(),
* so that lmutex_unlock() does not reassign errno. The function
* offtime_u() is MT-safe and does not have to be locked. Use
* my_is_in_dst to reference local copy of is_in_dst outside locks.
*
* See ctime(3C)
*/
struct tm *
_localtime_r(const time_t *timep, struct tm *p_tm)
{
long offset;
struct tm *rt;
int my_is_in_dst;
systemtz_t stz;
systemtz_t *tzp;
tzp = getsystemTZ(&stz);
lmutex_lock(&_time_lock);
ltzset_u(*timep, tzp);
if (lclzonep == NULL) {
lmutex_unlock(&_time_lock);
if (tzp->flag)
free(tzp->entry);
return (offtime_u(*timep, 0L, p_tm));
}
my_is_in_dst = is_in_dst;
offset = (my_is_in_dst) ? -altzone : -timezone;
lmutex_unlock(&_time_lock);
rt = offtime_u(*timep, offset, p_tm);
p_tm->tm_isdst = my_is_in_dst;
if (tzp->flag)
free(tzp->entry);
return (rt);
}
/*
* Accepts a time_t, returns a tm struct based on it, correcting
* for the local timezone. Produces documented side-effects to
* extern global timezone state data.
*
* This function is explicitly NOT THREAD-SAFE. The standards
* indicate it should provide its results in its own statically
* allocated tm struct that gets overwritten. The thread-safe
* variant is localtime_r(). We make it mostly thread-safe by
* allocating its buffer in thread-specific data.
*
* localtime() can now return NULL if overflow is detected.
* offtime_u() is the function that detects overflow, and sets
* errno appropriately.
*
* See ctime(3C)
*/
struct tm *
localtime(const time_t *timep)
{
struct tm *p_tm = tsdalloc(_T_STRUCT_TM, sizeof (struct tm), NULL);
if (p_tm == NULL) /* memory allocation failure */
p_tm = &tm; /* use static buffer and hope for the best */
return (_localtime_r(timep, p_tm));
}
/*
* This function takes a pointer to a tm struct and returns a
* normalized time_t, also inducing documented side-effects in
* extern global zone state variables. (See mktime(3C)).
*/
time_t
mktime(struct tm *tmptr)
{
struct tm _tm;
long long t; /* must hold more than 32-bit time_t */
int temp;
int mketimerrno;
int overflow;
systemtz_t stz;
systemtz_t *tzp;
mketimerrno = errno;
tzp = getsystemTZ(&stz);
/* mktime leaves errno unchanged if no error is encountered */
lmutex_lock(&_time_lock);
/* Calculate time_t from tm arg. tm may need to be normalized. */
t = tmptr->tm_sec + SECSPERMIN * tmptr->tm_min +
SECSPERHOUR * tmptr->tm_hour +
SECSPERDAY * (tmptr->tm_mday - 1);
if (tmptr->tm_mon >= 12) {
tmptr->tm_year += tmptr->tm_mon / 12;
tmptr->tm_mon %= 12;
} else if (tmptr->tm_mon < 0) {
temp = -tmptr->tm_mon;
tmptr->tm_mon = 0; /* If tm_mon divides by 12. */
tmptr->tm_year -= (temp / 12);
if (temp %= 12) { /* Remainder... */
tmptr->tm_year--;
tmptr->tm_mon = 12 - temp;
}
}
/* Avoid numerous calculations embedded in macro if possible */
if (!year_is_cached || (cached_year != tmptr->tm_year)) {
cached_year = tmptr->tm_year;
year_is_cached = TRUE;
/* For boundry values of tm_year, typecasting required */
cached_secs_since_1970 =
(long long)SECSPERDAY * DAYS_SINCE_70(cached_year);
}
t += cached_secs_since_1970;
if (isleap(tmptr->tm_year + TM_YEAR_BASE))
t += SECSPERDAY * __lyday_to_month[tmptr->tm_mon];
else
t += SECSPERDAY * __yday_to_month[tmptr->tm_mon];
ltzset_u((time_t)t, tzp);
/* Attempt to convert time to GMT based on tm_isdst setting */
t += (tmptr->tm_isdst > 0) ? altzone : timezone;
#ifdef _ILP32
overflow = t > LONG_MAX || t < LONG_MIN ||
tmptr->tm_year < 1 || tmptr->tm_year > 138;
#else
overflow = t > LONG_MAX || t < LONG_MIN;
#endif
set_zone_context((time_t)t);
if (tmptr->tm_isdst < 0) {
long dst_delta = timezone - altzone;
switch (curr_zonerules) {
case ZONEINFO:
if (is_in_dst) {
t -= dst_delta;
set_zone_context((time_t)t);
if (is_in_dst) {
(void) offtime_u((time_t)t,
-altzone, &_tm);
_tm.tm_isdst = 1;
} else {
(void) offtime_u((time_t)t,
-timezone, &_tm);
}
} else {
(void) offtime_u((time_t)t, -timezone, &_tm);
}
break;
case POSIX_USA:
case POSIX:
if (is_in_dst) {
t -= dst_delta;
set_zone_context((time_t)t);
if (is_in_dst) {
(void) offtime_u((time_t)t,
-altzone, &_tm);
_tm.tm_isdst = 1;
} else {
(void) offtime_u((time_t)t,
-timezone, &_tm);
}
} else { /* check for ambiguous 'fallback' transition */
set_zone_context((time_t)t - dst_delta);
if (is_in_dst) { /* In fallback, force DST */
t -= dst_delta;
(void) offtime_u((time_t)t,
-altzone, &_tm);
_tm.tm_isdst = 1;
} else {
(void) offtime_u((time_t)t,
-timezone, &_tm);
}
}
break;
case ZONERULES_INVALID:
(void) offtime_u((time_t)t, 0L, &_tm);
break;
}
} else if (is_in_dst) {
(void) offtime_u((time_t)t, -altzone, &_tm);
_tm.tm_isdst = 1;
} else {
(void) offtime_u((time_t)t, -timezone, &_tm);
}
if (overflow || t > LONG_MAX || t < LONG_MIN) {
mketimerrno = EOVERFLOW;
t = -1;
} else {
*tmptr = _tm;
}
lmutex_unlock(&_time_lock);
if (tzp->flag)
free(tzp->entry);
errno = mketimerrno;
return ((time_t)t);
}
/*
* Sets extern global zone state variables based on the current
* time. Specifically, tzname[], timezone, altzone, and daylight
* are updated. See ctime(3C) manpage.
*/
void
_tzset(void)
{
systemtz_t stz;
systemtz_t *tzp;
tzp = getsystemTZ(&stz);
lmutex_lock(&_time_lock);
ltzset_u(time(NULL), tzp);
lmutex_unlock(&_time_lock);
if (tzp->flag)
free(tzp->entry);
}
void
_ltzset(time_t tim)
{
systemtz_t stz;
systemtz_t *tzp;
tzp = getsystemTZ(&stz);
lmutex_lock(&_time_lock);
ltzset_u(tim, tzp);
lmutex_unlock(&_time_lock);
if (tzp->flag)
free(tzp->entry);
}
/*
* Loads local zone information if TZ changed since last time zone
* information was loaded, or if this is the first time thru.
* We already hold _time_lock; no further locking is required.
*/
static void
ltzset_u(time_t t, systemtz_t *tzp)
{
const char *zonename = tzp->tz;
state_t *entry, **p, *q;
if (zonename == NULL || *zonename == '\0')
zonename = _posix_gmt0;
if (curr_zonerules != ZONERULES_INVALID &&
strcmp(namecache, zonename) == 0) {
set_zone_context(t);
return;
}
entry = find_zone(zonename, &p, &q);
if (entry == NULL) {
/*
* No timezone entry found in hash table, so load it,
* and create a new timezone entry.
*/
char *newzonename, *charsbuf;
/* Invalidate the current timezone */
curr_zonerules = ZONERULES_INVALID;
newzonename = libc_strdup(zonename);
daylight = 0;
entry = tzp->entry;
if (entry == NULL || newzonename == NULL) {
/* something wrong happened. */
if (newzonename != NULL)
libc_free(newzonename);
timezone = altzone = 0;
is_in_dst = 0;
tzname[0] = (char *)_tz_gmt;
tzname[1] = (char *)_tz_spaces;
return;
}
/*
* Builds transition cache and sets up zone state data for zone
* specified in TZ, which can be specified as a POSIX zone or an
* Olson zoneinfo file reference.
*
* If local data cannot be parsed or loaded, the local zone
* tables are set up for GMT.
*
* Unless a leading ':' is prepended to TZ, TZ is initially
* parsed as a POSIX zone; failing that, it reverts to
* a zoneinfo check.
* However, if a ':' is prepended, the zone will *only* be
* parsed as zoneinfo. If any failure occurs parsing or
* loading a zoneinfo TZ, GMT data is loaded for the local zone.
*
* Example: There is a zoneinfo file in the standard
* distribution called 'PST8PDT'. The only way the user can
* specify that file under Solaris is to set TZ to ":PST8PDT".
* Otherwise the initial parse of PST8PDT as a POSIX zone will
* succeed and be used.
*/
if ((charsbuf = libc_malloc(TZ_MAX_CHARS)) == NULL) {
libc_free(newzonename);
timezone = altzone = 0;
is_in_dst = 0;
tzname[0] = (char *)_tz_gmt;
tzname[1] = (char *)_tz_spaces;
return;
}
entry->charsbuf_size = TZ_MAX_CHARS;
entry->chars = charsbuf;
entry->default_tzname0 = _tz_gmt;
entry->default_tzname1 = _tz_spaces;
entry->zonename = newzonename;
if (*zonename == ':') {
if (load_zoneinfo(zonename + 1, entry) != 0) {
(void) load_posixinfo(_posix_gmt0, entry);
}
} else if (load_posixinfo(zonename, entry) != 0) {
if (load_zoneinfo(zonename, entry) != 0) {
(void) load_posixinfo(_posix_gmt0, entry);
}
}
/*
* The pre-allocated buffer is used; reset the free flag
* so the buffer won't be freed.
*/
tzp->flag = 0;
entry->next = q;
*p = entry;
}
curr_zonerules = entry->zonerules;
namecache = entry->zonename;
daylight = entry->daylight;
lclzonep = entry;
set_zone_context(t);
}
/*
* Sets timezone, altzone, tzname[], extern globals, to represent
* disposition of t with respect to TZ; See ctime(3C). is_in_dst,
* internal global is also set. daylight is set at zone load time.
*
* Issues:
*
* In this function, any time_t not located in the cache is handled
* as a miss. To build/update transition cache, load_zoneinfo()
* must be called prior to this routine.
*
* If POSIX zone, cache miss penalty is slightly degraded
* performance. For zoneinfo, penalty is decreased is_in_dst
* accuracy.
*
* POSIX, despite its chicken/egg problem, ie. not knowing DST
* until time known, and not knowing time until DST known, at
* least uses the same algorithm for 64-bit time as 32-bit.
*
* The fact that zoneinfo files only contain transistions for 32-bit
* time space is a well known problem, as yet unresolved.
* Without an official standard for coping with out-of-range
* zoneinfo times, assumptions must be made. For now
* the assumption is: If t exceeds 32-bit boundries and local zone
* is zoneinfo type, is_in_dst is set to to 0 for negative values
* of t, and set to the same DST state as the highest ordered
* transition in cache for positive values of t.
*/
static void
set_zone_context(time_t t)
{
prev_t *prevp;
int lo, hi, tidx;
ttinfo_t *ttisp, *std, *alt;
/* If state data not loaded or TZ busted, just use GMT */
if (lclzonep == NULL || curr_zonerules == ZONERULES_INVALID) {
timezone = altzone = 0;
daylight = is_in_dst = 0;
tzname[0] = (char *)_tz_gmt;
tzname[1] = (char *)_tz_spaces;
return;
}
/* Retrieve suitable defaults for this zone */
altzone = lclzonep->default_altzone;
timezone = lclzonep->default_timezone;
tzname[0] = (char *)lclzonep->default_tzname0;
tzname[1] = (char *)lclzonep->default_tzname1;
is_in_dst = 0;
if (lclzonep->timecnt <= 0 || lclzonep->typecnt < 2)
/* Loaded zone incapable of transitioning. */
return;
/*
* At least one alt. zone and one transistion exist. Locate
* state for 't' quickly as possible. Use defaults as necessary.
*/
lo = 0;
hi = lclzonep->timecnt - 1;
if (t < lclzonep->ats[0] || t >= lclzonep->ats[hi]) {
/* CACHE MISS. Calculate DST as best as possible */
if (lclzonep->zonerules == POSIX_USA ||
lclzonep->zonerules == POSIX) {
/* Must nvoke calculations to determine DST */
is_in_dst = (daylight) ?
posix_check_dst(t, lclzonep) : 0;
return;
} else if (t < lclzonep->ats[0]) { /* zoneinfo... */
/* t precedes 1st transition. Use defaults */
return;
} else { /* zoneinfo */
/* t follows final transistion. Use final */
tidx = hi;
}
} else {
/* CACHE HIT. Locate transition using binary search. */
while (lo <= hi) {
tidx = (lo + hi) / 2;
if (t == lclzonep->ats[tidx])
break;
else if (t < lclzonep->ats[tidx])
hi = tidx - 1;
else
lo = tidx + 1;
}
if (lo > hi)
tidx = hi;
}
/*
* Set extern globals based on located transition and summary of
* its previous state, which were cached when zone was loaded
*/
ttisp = &lclzonep->ttis[lclzonep->types[tidx]];
prevp = &lclzonep->prev[tidx];
if ((is_in_dst = ttisp->tt_isdst) == 0) { /* std. time */
timezone = -ttisp->tt_gmtoff;
tzname[0] = &lclzonep->chars[ttisp->tt_abbrind];
if ((alt = prevp->alt) != NULL) {
altzone = -alt->tt_gmtoff;
tzname[1] = &lclzonep->chars[alt->tt_abbrind];
}
} else { /* alt. time */
altzone = -ttisp->tt_gmtoff;
tzname[1] = &lclzonep->chars[ttisp->tt_abbrind];
if ((std = prevp->std) != NULL) {
timezone = -std->tt_gmtoff;
tzname[0] = &lclzonep->chars[std->tt_abbrind];
}
}
}
/*
* This function takes a time_t and gmt offset and produces a
* tm struct based on specified time.
*
* The the following fields are calculated, based entirely
* on the offset-adjusted value of t:
*
* tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec
* tm_yday. tm_wday. (tm_isdst is ALWAYS set to 0).
*/
static struct tm *
offtime_u(time_t t, long offset, struct tm *tmptr)
{
long days;
long rem;
long y;
int yleap;
const int *ip;
days = t / SECSPERDAY;
rem = t % SECSPERDAY;
rem += offset;
while (rem < 0) {
rem += SECSPERDAY;
--days;
}
while (rem >= SECSPERDAY) {
rem -= SECSPERDAY;
++days;
}
tmptr->tm_hour = (int)(rem / SECSPERHOUR);
rem = rem % SECSPERHOUR;
tmptr->tm_min = (int)(rem / SECSPERMIN);
tmptr->tm_sec = (int)(rem % SECSPERMIN);
tmptr->tm_wday = (int)((EPOCH_WDAY + days) % DAYSPERWEEK);
if (tmptr->tm_wday < 0)
tmptr->tm_wday += DAYSPERWEEK;
y = EPOCH_YEAR;
while (days < 0 || days >= (long)__year_lengths[yleap = isleap(y)]) {
long newy;
newy = y + days / DAYSPERNYEAR;
if (days < 0)
--newy;
days -= ((long)newy - (long)y) * DAYSPERNYEAR +
LEAPS_THRU_END_OF(newy > 0 ? newy - 1L : newy) -
LEAPS_THRU_END_OF(y > 0 ? y - 1L : y);
y = newy;
}
tmptr->tm_year = (int)(y - TM_YEAR_BASE);
tmptr->tm_yday = (int)days;
ip = __mon_lengths[yleap];
for (tmptr->tm_mon = 0; days >=
(long)ip[tmptr->tm_mon]; ++(tmptr->tm_mon))
days = days - (long)ip[tmptr->tm_mon];
tmptr->tm_mday = (int)(days + 1);
tmptr->tm_isdst = 0;
#ifdef _LP64
/* do as much as possible before checking for error. */
if ((y > (long)INT_MAX + TM_YEAR_BASE) ||
(y < (long)INT_MIN + TM_YEAR_BASE)) {
errno = EOVERFLOW;
return (NULL);
}
#endif
return (tmptr);
}
/*
* Check whether DST is set for time in question. Only applies to
* POSIX timezones. If explicit POSIX transition rules were provided
* for the current zone, use those, otherwise use default USA POSIX
* transitions.
*/
static int
posix_check_dst(long long t, state_t *sp)
{
struct tm gmttm;
long long jan01;
int year, i, idx, ridx;
posix_daylight_t pdaylight;
(void) offtime_u(t, 0L, &gmttm);
year = gmttm.tm_year + 1900;
jan01 = t - ((gmttm.tm_yday * SECSPERDAY) +
(gmttm.tm_hour * SECSPERHOUR) +
(gmttm.tm_min * SECSPERMIN) + gmttm.tm_sec);
/*
* If transition rules were provided for this zone,
* use them, otherwise, default to USA daylight rules,
* which are historically correct for the continental USA,
* excluding local provisions. (This logic may be replaced
* at some point in the future with "posixrules" to offer
* more flexibility to the system administrator).
*/
if (sp->zonerules == POSIX) { /* POSIX rules */
pdaylight.rules[0] = &sp->start_rule;
pdaylight.rules[1] = &sp->end_rule;
} else { /* POSIX_USA: USA */
i = 0;
while (year < __usa_rules[i].s_year && i < MAX_RULE_TABLE) {
i++;
}
pdaylight.rules[0] = (rule_t *)&__usa_rules[i].start;
pdaylight.rules[1] = (rule_t *)&__usa_rules[i].end;
}
pdaylight.offset[0] = timezone;
pdaylight.offset[1] = altzone;
idx = posix_daylight(&jan01, year, &pdaylight);
ridx = !idx;
/*
* Note: t, rtime[0], and rtime[1] are all bounded within 'year'
* beginning on 'jan01'
*/
if (t >= pdaylight.rtime[idx] && t < pdaylight.rtime[ridx]) {
return (ridx);
} else {
return (idx);
}
}
/*
* Given January 1, 00:00:00 GMT for a year as an Epoch-relative time,
* along with the integer year #, a posix_daylight_t that is composed
* of two rules, and two GMT offsets (timezone and altzone), calculate
* the two Epoch-relative times the two rules take effect, and return
* them in the two rtime fields of the posix_daylight_t structure.
* Also update janfirst by a year, by adding the appropriate number of
* seconds depending on whether the year is a leap year or not. (We take
* advantage that this routine knows the leap year status.)
*/
static int
posix_daylight(long long *janfirst, int year, posix_daylight_t *pdaylightp)
{
rule_t *rulep;
long offset;
int idx;
int i, d, m1, yy0, yy1, yy2, dow;
long leapyear;
long long value;
static const int __secs_year_lengths[2] = {
DAYS_PER_NYEAR * SECSPERDAY,
DAYS_PER_LYEAR * SECSPERDAY
};
leapyear = isleap(year);
for (idx = 0; idx < 2; idx++) {
rulep = pdaylightp->rules[idx];
offset = pdaylightp->offset[idx];
switch (rulep->r_type) {
case MON_WEEK_DOW:
/*
* Mm.n.d - nth "dth day" of month m.
*/
value = *janfirst;
for (i = 0; i < rulep->r_mon - 1; ++i)
value += __mon_lengths[leapyear][i] *
SECSPERDAY;
/*
* Use Zeller's Congruence to get day-of-week of first
* day of month.
*/
m1 = (rulep->r_mon + 9) % 12 + 1;
yy0 = (rulep->r_mon <= 2) ? (year - 1) : year;
yy1 = yy0 / 100;
yy2 = yy0 % 100;
dow = ((26 * m1 - 2) / 10 +
1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
if (dow < 0)
dow += DAYSPERWEEK;
/*
* Following heuristic increases accuracy of USA rules
* for negative years.
*/
if (year < 1 && leapyear)
++dow;
/*
* "dow" is the day-of-week of the first day of the
* month. Get the day-of-month, zero-origin, of the
* first "dow" day of the month.
*/
d = rulep->r_day - dow;
if (d < 0)
d += DAYSPERWEEK;
for (i = 1; i < rulep->r_week; ++i) {
if (d + DAYSPERWEEK >=
__mon_lengths[leapyear][rulep->r_mon - 1])
break;
d += DAYSPERWEEK;
}
/*
* "d" is the day-of-month, zero-origin, of the day
* we want.
*/
value += d * SECSPERDAY;
break;
case JULIAN_DAY:
/*
* Jn - Julian day, 1 == Jan 1, 60 == March 1 even
* in leap yrs.
*/
value = *janfirst + (rulep->r_day - 1) * SECSPERDAY;
if (leapyear && rulep->r_day >= 60)
value += SECSPERDAY;
break;
case DAY_OF_YEAR:
/*
* n - day of year.
*/
value = *janfirst + rulep->r_day * SECSPERDAY;
break;
}
pdaylightp->rtime[idx] = value + rulep->r_time + offset;
}
*janfirst += __secs_year_lengths[leapyear];
return ((pdaylightp->rtime[0] > pdaylightp->rtime[1]) ? 1 : 0);
}
/*
* Try to load zoneinfo file into internal transition tables using name
* indicated in TZ, and do validity checks. The format of zic(1M)
* compiled zoneinfo files isdescribed in tzfile.h
*/
static int
load_zoneinfo(const char *name, state_t *sp)
{
char *cp;
char *cp2;
int i;
long cnt;
int fid;
int ttisstdcnt;
int ttisgmtcnt;
char *fullname;
size_t namelen;
char *bufp;
size_t flen;
prev_t *prevp;
/* LINTED */
struct tzhead *tzhp;
struct stat64 stbuf;
ttinfo_t *most_recent_alt = NULL;
ttinfo_t *most_recent_std = NULL;
ttinfo_t *ttisp;
if (name == NULL && (name = TZDEFAULT) == NULL)
return (-1);
if ((name[0] == '/') || strstr(name, "../"))
return (-1);
/*
* We allocate fullname this way to avoid having
* a PATH_MAX size buffer in our stack frame.
*/
namelen = LEN_TZDIR + 1 + strlen(name) + 1;
if ((fullname = lmalloc(namelen)) == NULL)
return (-1);
(void) strcpy(fullname, TZDIR "/");
(void) strcpy(fullname + LEN_TZDIR + 1, name);
if ((fid = open(fullname, O_RDONLY)) == -1) {
lfree(fullname, namelen);
return (-1);
}
lfree(fullname, namelen);
if (fstat64(fid, &stbuf) == -1) {
(void) close(fid);
return (-1);
}
flen = (size_t)stbuf.st_size;
if (flen < sizeof (struct tzhead)) {
(void) close(fid);
return (-1);
}
/*
* It would be nice to use alloca() to allocate bufp but,
* as above, we wish to avoid allocating a big buffer in
* our stack frame, and also because alloca() gives us no
* opportunity to fail gracefully on allocation failure.
*/
cp = bufp = lmalloc(flen);
if (bufp == NULL) {
(void) close(fid);
return (-1);
}
if ((cnt = read(fid, bufp, flen)) != flen) {
lfree(bufp, flen);
(void) close(fid);
return (-1);
}
if (close(fid) != 0) {
lfree(bufp, flen);
return (-1);
}
cp += (sizeof (tzhp->tzh_magic)) + (sizeof (tzhp->tzh_reserved));
/* LINTED: alignment */
ttisstdcnt = CVTZCODE(cp);
/* LINTED: alignment */
ttisgmtcnt = CVTZCODE(cp);
/* LINTED: alignment */
sp->leapcnt = CVTZCODE(cp);
/* LINTED: alignment */
sp->timecnt = CVTZCODE(cp);
/* LINTED: alignment */
sp->typecnt = CVTZCODE(cp);
/* LINTED: alignment */
sp->charcnt = CVTZCODE(cp);
if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS ||
sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES ||
sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES ||
sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS ||
(ttisstdcnt != sp->typecnt && ttisstdcnt != 0) ||
(ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0)) {
lfree(bufp, flen);
return (-1);
}
if (cnt - (cp - bufp) < (long)(sp->timecnt * 4 + /* ats */
sp->timecnt + /* types */
sp->typecnt * (4 + 2) + /* ttinfos */
sp->charcnt + /* chars */
sp->leapcnt * (4 + 4) + /* lsinfos */
ttisstdcnt + /* ttisstds */
ttisgmtcnt)) { /* ttisgmts */
lfree(bufp, flen);
return (-1);
}
for (i = 0; i < sp->timecnt; ++i) {
/* LINTED: alignment */
sp->ats[i] = CVTZCODE(cp);
}
/*
* Skip over types[] for now and load ttis[] so that when
* types[] are loaded we can check for transitions to STD & DST.
* This allows us to shave cycles in ltzset_u(), including
* eliminating the need to check set 'daylight' later.
*/
cp2 = (char *)((uintptr_t)cp + sp->timecnt);
for (i = 0; i < sp->typecnt; ++i) {
ttisp = &sp->ttis[i];
/* LINTED: alignment */
ttisp->tt_gmtoff = CVTZCODE(cp2);
ttisp->tt_isdst = (uchar_t)*cp2++;
if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1) {
lfree(bufp, flen);
return (-1);
}
ttisp->tt_abbrind = (uchar_t)*cp2++;
if (ttisp->tt_abbrind < 0 ||
ttisp->tt_abbrind > sp->charcnt) {
lfree(bufp, flen);
return (-1);
}
}
/*
* Since ttis were loaded ahead of types, it is possible to
* detect whether daylight is ever set for this zone now, and
* also preload other information to avoid repeated lookups later.
* This logic facilitates keeping a running tab on the state of
* std zone and alternate zone transitions such that timezone,
* altzone and tzname[] can be determined quickly via an
* index to any transition.
*
* For transition #0 there are no previous transitions,
* so prev->std and prev->alt will be null, but that's OK,
* because null prev->std/prev->alt effectively
* indicates none existed prior.
*/
prevp = &sp->prev[0];
for (i = 0; i < sp->timecnt; ++i) {
sp->types[i] = (uchar_t)*cp++;
ttisp = &sp->ttis[sp->types[i]];
prevp->std = most_recent_std;
prevp->alt = most_recent_alt;
if (ttisp->tt_isdst == 1) {
most_recent_alt = ttisp;
} else {
most_recent_std = ttisp;
}
if ((int)sp->types[i] >= sp->typecnt) {
lfree(bufp, flen);
return (-1);
}
++prevp;
}
if (most_recent_alt == NULL)
sp->daylight = 0;
else
sp->daylight = 1;
/*
* Set pointer ahead to where it would have been if we
* had read types[] and ttis[] in the same order they
* occurred in the file.
*/
cp = cp2;
for (i = 0; i < sp->charcnt; ++i)
sp->chars[i] = *cp++;
sp->chars[i] = '\0'; /* ensure '\0' at end */
for (i = 0; i < sp->leapcnt; ++i) {
struct lsinfo *lsisp;
lsisp = &sp->lsis[i];
/* LINTED: alignment */
lsisp->ls_trans = CVTZCODE(cp);
/* LINTED: alignment */
lsisp->ls_corr = CVTZCODE(cp);
}
for (i = 0; i < sp->typecnt; ++i) {
ttisp = &sp->ttis[i];
if (ttisstdcnt == 0) {
ttisp->tt_ttisstd = FALSE;
} else {
ttisp->tt_ttisstd = *cp++;
if (ttisp->tt_ttisstd != TRUE &&
ttisp->tt_ttisstd != FALSE) {
lfree(bufp, flen);
return (-1);
}
}
}
for (i = 0; i < sp->typecnt; ++i) {
ttisp = &sp->ttis[i];
if (ttisgmtcnt == 0) {
ttisp->tt_ttisgmt = FALSE;
} else {
ttisp->tt_ttisgmt = *cp++;
if (ttisp->tt_ttisgmt != TRUE &&
ttisp->tt_ttisgmt != FALSE) {
lfree(bufp, flen);
return (-1);
}
}
}
/*
* Other defaults set at beginning of this routine
* to cover case where zoneinfo file cannot be loaded
*/
sp->default_timezone = -sp->ttis[0].tt_gmtoff;
sp->default_altzone = 0;
sp->default_tzname0 = &sp->chars[0];
sp->default_tzname1 = _tz_spaces;
lfree(bufp, flen);
sp->zonerules = ZONEINFO;
return (0);
}
/*
* Given a POSIX section 8-style TZ string, fill in transition tables.
*
* Examples:
*
* TZ = PST8 or GMT0
* Timecnt set to 0 and typecnt set to 1, reflecting std time only.
*
* TZ = PST8PDT or PST8PDT7
* Create transition times by applying USA transitions from
* Jan 1 of each year covering 1902-2038. POSIX offsets
* as specified in the TZ are used to calculate the tt_gmtoff
* for each of the two zones. If ommitted, DST defaults to
* std. time minus one hour.
*
* TZ = <PST8>8PDT or <PST8>8<PDT9>
* Quoted transition. The values in angled brackets are treated
* as zone name text, not parsed as offsets. The offsets
* occuring following the zonename section. In this way,
* instead of PST being displayed for standard time, it could
* be displayed as PST8 to give an indication of the offset
* of that zone to GMT.
*
* TZ = GMT0BST, M3.5.0/1, M10.5.0/2 or GMT0BST, J23953, J23989
* Create transition times based on the application new-year
* relative POSIX transitions, parsed from TZ, from Jan 1
* for each year covering 1902-2038. POSIX offsets specified
* in TZ are used to calculate tt_gmtoff for each of the two
* zones.
*
*/
static int
load_posixinfo(const char *name, state_t *sp)
{
const char *stdname;
const char *dstname = 0;
size_t stdlen;
size_t dstlen;
long stdoff = 0;
long dstoff = 0;
time_t *tranp;
uchar_t *typep;
prev_t *prevp;
char *cp;
int year;
int i;
long long janfirst;
ttinfo_t *dst;
ttinfo_t *std;
int quoted;
zone_rules_t zonetype;
posix_daylight_t pdaylight;
zonetype = POSIX_USA;
stdname = name;
if ((quoted = (*stdname == '<')) != 0)
++stdname;
/* Parse/extract STD zone name, len and GMT offset */
if (*name != '\0') {
if ((name = getzname(name, quoted)) == NULL)
return (-1);
stdlen = name - stdname;
if (*name == '>')
++name;
if (*name == '\0' || stdlen < 1) {
return (-1);
} else {
if ((name = getoffset(name, &stdoff)) == NULL)
return (-1);
}
}
/* If DST specified in TZ, extract DST zone details */
if (*name != '\0') {
dstname = name;
if ((quoted = (*dstname == '<')) != 0)
++dstname;
if ((name = getzname(name, quoted)) == NULL)
return (-1);
dstlen = name - dstname;
if (dstlen < 1)
return (-1);
if (*name == '>')
++name;
if (*name != '\0' && *name != ',' && *name != ';') {
if ((name = getoffset(name, &dstoff)) == NULL)
return (-1);
} else {
dstoff = stdoff - SECSPERHOUR;
}
/* If any present, extract POSIX transitions from TZ */
if (*name == ',' || *name == ';') {
/* Backward compatibility using ';' separator */
int compat_flag = (*name == ';');
++name;
if ((name = getrule(name, &sp->start_rule, compat_flag))
== NULL)
return (-1);
if (*name++ != ',')
return (-1);
if ((name = getrule(name, &sp->end_rule, compat_flag))
== NULL)
return (-1);
if (*name != '\0')
return (-1);
zonetype = POSIX;
}
/*
* We know STD and DST zones are specified with this timezone
* therefore the cache will be set up with 2 transitions per
* year transitioning to their respective std and dst zones.
*/
sp->daylight = 1;
sp->typecnt = 2;
sp->timecnt = 272;
/*
* Insert zone data from POSIX TZ into state table
* The Olson public domain POSIX code sets up ttis[0] to be DST,
* as we are doing here. It seems to be the correct behavior.
* The US/Pacific zoneinfo file also lists DST as first type.
*/
dst = &sp->ttis[0];
dst->tt_gmtoff = -dstoff;
dst->tt_isdst = 1;
std = &sp->ttis[1];
std->tt_gmtoff = -stdoff;
std->tt_isdst = 0;
sp->prev[0].std = NULL;
sp->prev[0].alt = NULL;
/* Create transition data based on POSIX TZ */
tranp = sp->ats;
prevp = &sp->prev[1];
typep = sp->types;
/*
* We only cache from 1902 to 2037 to avoid transistions
* that wrap at the 32-bit boundries, since 1901 and 2038
* are not full years in 32-bit time. The rough edges
* will be handled as transition cache misses.
*/
janfirst = JAN_01_1902;
pdaylight.rules[0] = &sp->start_rule;
pdaylight.rules[1] = &sp->end_rule;
pdaylight.offset[0] = stdoff;
pdaylight.offset[1] = dstoff;
for (i = MAX_RULE_TABLE; i >= 0; i--) {
if (zonetype == POSIX_USA) {
pdaylight.rules[0] =
(rule_t *)&__usa_rules[i].start;
pdaylight.rules[1] =
(rule_t *)&__usa_rules[i].end;
}
for (year = __usa_rules[i].s_year;
year <= __usa_rules[i].e_year;
year++) {
int idx, ridx;
idx =
posix_daylight(&janfirst, year, &pdaylight);
ridx = !idx;
/*
* Two transitions per year. Since there are
* only two zone types for this POSIX zone,
* previous std and alt are always set to
* &ttis[0] and &ttis[1].
*/
*tranp++ = (time_t)pdaylight.rtime[idx];
*typep++ = idx;
prevp->std = std;
prevp->alt = dst;
++prevp;
*tranp++ = (time_t)pdaylight.rtime[ridx];
*typep++ = ridx;
prevp->std = std;
prevp->alt = dst;
++prevp;
}
}
} else { /* DST wasn't specified in POSIX TZ */
/* Since we only have STD time, there are no transitions */
dstlen = 0;
sp->daylight = 0;
sp->typecnt = 1;
sp->timecnt = 0;
std = &sp->ttis[0];
std->tt_gmtoff = -stdoff;
std->tt_isdst = 0;
}
/* Setup zone name character data for state table */
sp->charcnt = (int)(stdlen + 1);
if (dstlen != 0)
sp->charcnt += dstlen + 1;
/* If bigger than zone name abbv. buffer, grow it */
if ((size_t)sp->charcnt > sp->charsbuf_size) {
if ((cp = libc_realloc(sp->chars, sp->charcnt)) == NULL)
return (-1);
sp->chars = cp;
sp->charsbuf_size = sp->charcnt;
}
/*
* Copy zone name text null-terminatedly into state table.
* By doing the copy once during zone loading, setting
* tzname[] subsequently merely involves setting pointer
*
* If either or both std. or alt. zone name < 3 chars,
* space pad the deficient name(s) to right.
*/
std->tt_abbrind = 0;
cp = sp->chars;
(void) strncpy(cp, stdname, stdlen);
while (stdlen < 3)
cp[stdlen++] = ' ';
cp[stdlen] = '\0';
i = (int)(stdlen + 1);
if (dstlen != 0) {
dst->tt_abbrind = i;
cp += i;
(void) strncpy(cp, dstname, dstlen);
while (dstlen < 3)
cp[dstlen++] = ' ';
cp[dstlen] = '\0';
}
/* Save default values */
if (sp->typecnt == 1) {
sp->default_timezone = stdoff;
sp->default_altzone = stdoff;
sp->default_tzname0 = &sp->chars[0];
sp->default_tzname1 = _tz_spaces;
} else {
sp->default_timezone = -std->tt_gmtoff;
sp->default_altzone = -dst->tt_gmtoff;
sp->default_tzname0 = &sp->chars[std->tt_abbrind];
sp->default_tzname1 = &sp->chars[dst->tt_abbrind];
}
sp->zonerules = zonetype;
return (0);
}
/*
* Given a pointer into a time zone string, scan until a character that is not
* a valid character in a zone name is found. Return ptr to that character.
* Return NULL if error (ie. non-printable character located in name)
*/
static const char *
getzname(const char *strp, int quoted)
{
char c;
if (quoted) {
while ((c = *strp) != '\0' && c != '>' &&
isgraph((unsigned char)c))
++strp;
} else {
while ((c = *strp) != '\0' && isgraph((unsigned char)c) &&
!isdigit((unsigned char)c) && c != ',' && c != '-' &&
c != '+')
++strp;
}
/* Found an excessively invalid character. Discredit whole name */
if (c != '\0' && !isgraph((unsigned char)c))
return (NULL);
return (strp);
}
/*
* Given pointer into time zone string, extract first
* number pointed to. Validate number within range specified,
* Return ptr to first char following valid numeric sequence.
*/
static const char *
getnum(const char *strp, int *nump, int min, int max)
{
char c;
int num;
if (strp == NULL || !isdigit((unsigned char)(c = *strp)))
return (NULL);
num = 0;
do {
num = num * 10 + (c - '0');
if (num > max)
return (NULL); /* illegal value */
c = *++strp;
} while (isdigit((unsigned char)c));
if (num < min)
return (NULL); /* illegal value */
*nump = num;
return (strp);
}
/*
* Given a pointer into a time zone string, extract a number of seconds,
* in hh[:mm[:ss]] form, from the string. If an error occurs, return NULL,
* otherwise, return a pointer to the first character not part of the number
* of seconds.
*/
static const char *
getsecs(const char *strp, long *secsp)
{
int num;
/*
* `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
* "M10.4.6/26", which does not conform to Posix,
* but which specifies the equivalent of
* ``02:00 on the first Sunday on or after 23 Oct''.
*/
strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1);
if (strp == NULL)
return (NULL);
*secsp = num * (long)SECSPERHOUR;
if (*strp == ':') {
++strp;
strp = getnum(strp, &num, 0, MINSPERHOUR - 1);
if (strp == NULL)
return (NULL);
*secsp += num * SECSPERMIN;
if (*strp == ':') {
++strp;
/* `SECSPERMIN' allows for leap seconds. */
strp = getnum(strp, &num, 0, SECSPERMIN);
if (strp == NULL)
return (NULL);
*secsp += num;
}
}
return (strp);
}
/*
* Given a pointer into a time zone string, extract an offset, in
* [+-]hh[:mm[:ss]] form, from the string.
* If any error occurs, return NULL.
* Otherwise, return a pointer to the first character not part of the time.
*/
static const char *
getoffset(const char *strp, long *offsetp)
{
int neg = 0;
if (*strp == '-') {
neg = 1;
++strp;
} else if (*strp == '+') {
++strp;
}
strp = getsecs(strp, offsetp);
if (strp == NULL)
return (NULL); /* illegal time */
if (neg)
*offsetp = -*offsetp;
return (strp);
}
/*
* Given a pointer into a time zone string, extract a rule in the form
* date[/time]. See POSIX section 8 for the format of "date" and "time".
* If a valid rule is not found, return NULL.
* Otherwise, return a pointer to the first character not part of the rule.
*
* If compat_flag is set, support old 1-based day of year values.
*/
static const char *
getrule(const char *strp, rule_t *rulep, int compat_flag)
{
if (compat_flag == 0 && *strp == 'M') {
/*
* Month, week, day.
*/
rulep->r_type = MON_WEEK_DOW;
++strp;
strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR);
if (strp == NULL)
return (NULL);
if (*strp++ != '.')
return (NULL);
strp = getnum(strp, &rulep->r_week, 1, 5);
if (strp == NULL)
return (NULL);
if (*strp++ != '.')
return (NULL);
strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1);
} else if (compat_flag == 0 && *strp == 'J') {
/*
* Julian day.
*/
rulep->r_type = JULIAN_DAY;
++strp;
strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR);
} else if (isdigit((unsigned char)*strp)) {
/*
* Day of year.
*/
rulep->r_type = DAY_OF_YEAR;
if (compat_flag == 0) {
/* zero-based day of year */
strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1);
} else {
/* one-based day of year */
strp = getnum(strp, &rulep->r_day, 1, DAYSPERLYEAR);
rulep->r_day--;
}
} else {
return (NULL); /* ZONERULES_INVALID format */
}
if (strp == NULL)
return (NULL);
if (*strp == '/') {
/*
* Time specified.
*/
++strp;
strp = getsecs(strp, &rulep->r_time);
} else {
rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */
}
return (strp);
}
/*
* Returns default value for TZ as specified in /etc/default/init file, if
* a default value for TZ is provided there.
*/
static char *
get_default_tz(void)
{
char *tz = NULL;
uchar_t *tzp, *tzq;
int flags;
if (defopen(TIMEZONE) == 0) {
flags = defcntl(DC_GETFLAGS, 0);
TURNON(flags, DC_STRIP_QUOTES);
(void) defcntl(DC_SETFLAGS, flags);
if ((tzp = (uchar_t *)defread(TZSTRING)) != NULL) {
while (isspace(*tzp))
tzp++;
tzq = tzp;
while (!isspace(*tzq) &&
*tzq != ';' &&
*tzq != '#' &&
*tzq != '\0')
tzq++;
*tzq = '\0';
if (*tzp != '\0')
tz = strdup((char *)tzp);
}
(void) defopen(NULL);
}
return (tz);
}
static state_t *
get_zone(systemtz_t *tzp)
{
int hashid;
state_t *m, *p;
const char *zonename = tzp->tz;
hashid = get_hashid(zonename);
m = tzcache[hashid];
while (m) {
int r;
r = strcmp(m->zonename, zonename);
if (r == 0) {
/* matched */
return (NULL);
} else if (r > 0) {
break;
}
m = m->next;
}
/* malloc() return value is also checked for NULL in ltzset_u() */
p = malloc(sizeof (state_t));
/* ltzset_u() resets the free flag to 0 if it uses the p buffer */
if (p != NULL)
tzp->flag = 1;
return (p);
}
/*
* getsystemTZ() returns the TZ value if it is set in the environment, or
* it returns the system TZ; if the systemTZ has not yet been set,
* get_default_tz() is called to read the /etc/default/init file to get
* the value.
*
* getsystemTZ() also calls get_zone() to do an initial check to see if the
* timezone is the current timezone, or one that is already loaded in the
* hash table. If get_zone() determines the timezone has not yet been loaded,
* it pre-allocates a buffer for a state_t struct, which ltzset_u() can use
* later to load the timezone and add to the hash table.
*
* The large state_t buffer is allocated here to avoid calls to malloc()
* within mutex_locks.
*/
static systemtz_t *
getsystemTZ(systemtz_t *stzp)
{
static const char *systemTZ = NULL;
char *tz;
assert_no_libc_locks_held();
stzp->flag = 0;
tz = getenv("TZ");
if (tz != NULL && *tz != '\0') {
stzp->tz = (const char *)tz;
goto get_entry;
}
if (systemTZ != NULL) {
stzp->tz = systemTZ;
goto get_entry;
}
tz = get_default_tz();
lmutex_lock(&_time_lock);
if (systemTZ == NULL) {
if ((systemTZ = tz) != NULL) /* found TZ entry in the file */
tz = NULL;
else
systemTZ = _posix_gmt0; /* no TZ entry in the file */
}
lmutex_unlock(&_time_lock);
if (tz != NULL) /* someone beat us to it; free our entry */
free(tz);
stzp->tz = systemTZ;
get_entry:
/*
* The object referred to by the 1st 'namecache'
* may be different from the one by the 2nd 'namecache' below.
* But, it does not matter. The bottomline is at this point
* 'namecache' points to non-NULL and whether the string pointed
* to by 'namecache' is equivalent to stzp->tz or not.
*/
if (namecache != NULL && strcmp(namecache, stzp->tz) == 0) {
/*
* At this point, we found the entry having the same
* zonename as stzp->tz exists. Later we will find
* the exact one, so we don't need to allocate
* the memory here.
*/
stzp->entry = NULL;
} else {
/*
* At this point, we could not get the evidence that this
* zonename had been cached. We will look into the cache
* further.
*/
stzp->entry = get_zone(stzp);
}
return (stzp);
}