/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* 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 #include "lint.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> #include <sys/mman.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 31 #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 */ int last_ats_idx; /* last ats index */ rule_t start_rule; /* For POSIX w/rules */ rule_t end_rule; /* For POSIX w/rules */ } state_t; typedef struct tznmlist { struct tznmlist *link; char name[1]; } tznmlist_t; static const char *systemTZ; static tznmlist_t *systemTZrec; static const char *namecache; static state_t *tzcache[HASHTABLE]; #define TZNMC_SZ 43 static tznmlist_t *tznmhash[TZNMC_SZ]; static const char *last_tzname[2]; 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 TZSYNC_FILE "/var/run/tzsync" static uint32_t zoneinfo_seqno; static uint32_t zoneinfo_seqno_init = 1; static uint32_t *zoneinfo_seqadr = &zoneinfo_seqno_init; #define RELOAD_INFO() (zoneinfo_seqno != *zoneinfo_seqadr) #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 const char *getsystemTZ(void); 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 load_posix_transitions(state_t *, long, long, zone_rules_t); static void adjust_posix_default(state_t *, long, long); static void *ltzset_u(time_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); static void reload_counter(void); static void purge_zone_cache(void); static void set_tzname(const char **); /* * 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) { unsigned char c; unsigned int h; h = *id++; while ((c = *id++) != '\0') 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. */ static state_t * find_zone(const char *zonename) { int hashid; state_t *cur; hashid = get_hashid(zonename); cur = tzcache[hashid]; while (cur) { int res; res = strcmp(cur->zonename, zonename); if (res == 0) { return (cur); } else if (res > 0) { break; } cur = cur->next; } return (NULL); } /* * Register new state in the cache. */ static void reg_zone(state_t *new) { int hashid, res; state_t *cur, *prv; hashid = get_hashid(new->zonename); cur = tzcache[hashid]; prv = NULL; while (cur != NULL) { res = strcmp(cur->zonename, new->zonename); if (res == 0) { /* impossible, but just in case */ return; } else if (res > 0) { break; } prv = cur; cur = cur->next; } if (prv != NULL) { new->next = prv->next; prv->next = new; } else { new->next = tzcache[hashid]; tzcache[hashid] = new; } } /* * 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; void *unused; int my_is_in_dst; lmutex_lock(&_time_lock); unused = ltzset_u(*timep); if (lclzonep == NULL) { lmutex_unlock(&_time_lock); if (unused != NULL) free(unused); 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); if (unused != NULL) free(unused); rt = offtime_u(*timep, offset, p_tm); p_tm->tm_isdst = my_is_in_dst; 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; void *unused; mketimerrno = errno; /* mktime leaves errno unchanged if no error is encountered */ /* 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; } } lmutex_lock(&_time_lock); /* 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]; unused = ltzset_u((time_t)t); /* 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 (unused != NULL) free(unused); 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) { void *unused; lmutex_lock(&_time_lock); unused = ltzset_u(time(NULL)); lmutex_unlock(&_time_lock); if (unused != NULL) free(unused); } void _ltzset(time_t tim) { void *unused; lmutex_lock(&_time_lock); unused = ltzset_u(tim); lmutex_unlock(&_time_lock); if (unused != NULL) free(unused); } /* * 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. * Return a memory block which can be free'd at safe place. */ static void * ltzset_u(time_t t) { const char *zonename; state_t *entry, *new_entry; const char *newtzname[2]; if (RELOAD_INFO()) { reload_counter(); purge_zone_cache(); } if ((zonename = getsystemTZ()) == NULL || *zonename == '\0') zonename = _posix_gmt0; if (namecache != NULL && strcmp(namecache, zonename) == 0) { set_zone_context(t); return (NULL); } entry = find_zone(zonename); if (entry == NULL) { /* * We need to release _time_lock to call out malloc(). * We can release _time_lock as far as global variables * can remain consistent. Here, we haven't touch any * variables, so it's okay to release lock. */ lmutex_unlock(&_time_lock); new_entry = malloc(sizeof (state_t)); lmutex_lock(&_time_lock); /* * check it again, since zone may have been loaded while * time_lock was unlocked. */ entry = find_zone(zonename); } else { new_entry = NULL; goto out; } /* * We are here because the 1st attemp failed. * new_entry points newly allocated entry. If it was NULL, it * indicates that the memory allocation also failed. */ if (entry == NULL) { /* * 2nd attemp also failed. * No timezone entry found in hash table, so load it, * and create a new timezone entry. */ char *newzonename, *charsbuf; newzonename = libc_strdup(zonename); daylight = 0; entry = new_entry; if (entry == NULL || newzonename == NULL) { /* something wrong happened. */ failed: if (newzonename != NULL) libc_free(newzonename); /* Invalidate the current timezone */ curr_zonerules = ZONERULES_INVALID; namecache = NULL; timezone = altzone = 0; is_in_dst = 0; newtzname[0] = (char *)_tz_gmt; newtzname[1] = (char *)_tz_spaces; set_tzname(newtzname); return (entry); } /* * 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) goto failed; entry->zonerules = ZONERULES_INVALID; 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); } } entry->last_ats_idx = -1; /* * The pre-allocated buffer is used; reset the free flag * so the buffer won't be freed. */ reg_zone(entry); new_entry = NULL; } out: curr_zonerules = entry->zonerules; namecache = entry->zonename; daylight = entry->daylight; lclzonep = entry; set_zone_context(t); /* * We shouldn't release lock beyond this point since lclzonep * can refer to invalid address if cache is invalidated. * We defer the call to free till it can be done safely. */ return (new_entry); } /* * 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_default_context(void) { const char *newtzname[2]; /* Retrieve suitable defaults for this zone */ altzone = lclzonep->default_altzone; timezone = lclzonep->default_timezone; newtzname[0] = (char *)lclzonep->default_tzname0; newtzname[1] = (char *)lclzonep->default_tzname1; is_in_dst = 0; set_tzname(newtzname); } static void set_zone_context(time_t t) { prev_t *prevp; int lo, hi, tidx, lidx; ttinfo_t *ttisp, *std, *alt; const char *newtzname[2]; /* 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; newtzname[0] = (char *)_tz_gmt; newtzname[1] = (char *)_tz_spaces; set_tzname(newtzname); return; } if (lclzonep->timecnt <= 0 || lclzonep->typecnt < 2) { /* Loaded zone incapable of transitioning. */ set_zone_default_context(); 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]) { /* * Date which is out of definition. * Calculate DST as best as possible */ if (lclzonep->zonerules == POSIX_USA || lclzonep->zonerules == POSIX) { /* Must invoke calculations to determine DST */ set_zone_default_context(); is_in_dst = (daylight) ? posix_check_dst(t, lclzonep) : 0; return; } else if (t < lclzonep->ats[0]) { /* zoneinfo... */ /* t precedes 1st transition. Use defaults */ set_zone_default_context(); return; } else { /* zoneinfo */ /* t follows final transistion. Use final */ tidx = hi; } } else { if ((lidx = lclzonep->last_ats_idx) != -1 && lidx != hi && t >= lclzonep->ats[lidx] && t < lclzonep->ats[lidx + 1]) { /* CACHE HIT. Nothing needs to be done */ tidx = lidx; } else { /* * CACHE MISS. 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; newtzname[0] = &lclzonep->chars[ttisp->tt_abbrind]; if ((alt = prevp->alt) != NULL) { altzone = -alt->tt_gmtoff; newtzname[1] = &lclzonep->chars[alt->tt_abbrind]; } else { altzone = lclzonep->default_altzone; newtzname[1] = (char *)lclzonep->default_tzname1; } } else { /* alt. time */ altzone = -ttisp->tt_gmtoff; newtzname[1] = &lclzonep->chars[ttisp->tt_abbrind]; if ((std = prevp->std) != NULL) { timezone = -std->tt_gmtoff; newtzname[0] = &lclzonep->chars[std->tt_abbrind]; } else { timezone = lclzonep->default_timezone; newtzname[0] = (char *)lclzonep->default_tzname0; } } lclzonep->last_ats_idx = tidx; set_tzname(newtzname); } /* * 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); } #ifdef _TZ_DEBUG static void print_state(state_t *sp) { struct tm tmp; int i, c; (void) fprintf(stderr, "=========================================\n"); (void) fprintf(stderr, "zonename: \"%s\"\n", sp->zonename); (void) fprintf(stderr, "next: 0x%p\n", (void *)sp->next); (void) fprintf(stderr, "zonerules: %s\n", sp->zonerules == ZONERULES_INVALID ? "ZONERULES_INVALID" : sp->zonerules == POSIX ? "POSIX" : sp->zonerules == POSIX_USA ? "POSIX_USA" : sp->zonerules == ZONEINFO ? "ZONEINFO" : "UNKNOWN"); (void) fprintf(stderr, "daylight: %d\n", sp->daylight); (void) fprintf(stderr, "default_timezone: %ld\n", sp->default_timezone); (void) fprintf(stderr, "default_altzone: %ld\n", sp->default_altzone); (void) fprintf(stderr, "default_tzname0: \"%s\"\n", sp->default_tzname0); (void) fprintf(stderr, "default_tzname1: \"%s\"\n", sp->default_tzname1); (void) fprintf(stderr, "leapcnt: %d\n", sp->leapcnt); (void) fprintf(stderr, "timecnt: %d\n", sp->timecnt); (void) fprintf(stderr, "typecnt: %d\n", sp->typecnt); (void) fprintf(stderr, "charcnt: %d\n", sp->charcnt); (void) fprintf(stderr, "chars: \"%s\"\n", sp->chars); (void) fprintf(stderr, "charsbuf_size: %u\n", sp->charsbuf_size); (void) fprintf(stderr, "prev: skipping...\n"); (void) fprintf(stderr, "ats = {\n"); for (c = 0, i = 0; i < sp->timecnt; i++) { char buf[64]; size_t len; if (c != 0) { (void) fprintf(stderr, ", "); } (void) asctime_r(gmtime_r(&sp->ats[i], &tmp), buf, sizeof (buf)); len = strlen(buf); buf[len-1] = '\0'; (void) fprintf(stderr, "%s", buf); if (c == 1) { (void) fprintf(stderr, "\n"); c = 0; } else { c++; } } (void) fprintf(stderr, "}\n"); (void) fprintf(stderr, "types = {\n"); for (c = 0, i = 0; i < sp->timecnt; i++) { if (c == 0) { (void) fprintf(stderr, "\t"); } else { (void) fprintf(stderr, ", "); } (void) fprintf(stderr, "%d", sp->types[i]); if (c == 7) { (void) fprintf(stderr, "\n"); c = 0; } else { c++; } } (void) fprintf(stderr, "}\n"); (void) fprintf(stderr, "ttis = {\n"); for (i = 0; i < sp->typecnt; i++) { (void) fprintf(stderr, "\t{\n"); (void) fprintf(stderr, "\t\ttt_gmtoff: %ld\n", sp->ttis[i].tt_gmtoff); (void) fprintf(stderr, "\t\ttt_ttisdst: %d\n", sp->ttis[i].tt_isdst); (void) fprintf(stderr, "\t\ttt_abbrind: %d\n", sp->ttis[i].tt_abbrind); (void) fprintf(stderr, "\t\ttt_tt_isstd: %d\n", sp->ttis[i].tt_ttisstd); (void) fprintf(stderr, "\t\ttt_ttisgmt: %d\n", sp->ttis[i].tt_ttisgmt); (void) fprintf(stderr, "\t}\n"); } (void) fprintf(stderr, "}\n"); } #endif /* * 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; char *cp; int i; ttinfo_t *dst; ttinfo_t *std; int quoted; zone_rules_t zonetype; 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 (*name != ',' && *name != ';') { /* no transtition specified; using default rule */ if (load_zoneinfo(TZDEFRULES, sp) == 0 && sp->daylight == 1) { /* loading TZDEFRULES zoneinfo succeeded */ adjust_posix_default(sp, stdoff, dstoff); } else { /* loading TZDEFRULES zoneinfo failed */ load_posix_transitions(sp, stdoff, dstoff, zonetype); } } else { /* extract POSIX transitions from TZ */ /* 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; load_posix_transitions(sp, stdoff, dstoff, zonetype); } dst = &sp->ttis[0]; std = &sp->ttis[1]; } 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); } /* * We loaded the TZDEFAULT which usually the one in US zones. We * adjust the GMT offset for the zone which has stdoff/dstoff * offset. */ static void adjust_posix_default(state_t *sp, long stdoff, long dstoff) { long zone_stdoff = 0; long zone_dstoff = 0; int zone_stdoff_flag = 0; int zone_dstoff_flag = 0; int isdst; int i; /* * Initial values of zone_stdoff and zone_dstoff */ for (i = 0; (zone_stdoff_flag == 0 || zone_dstoff_flag == 0) && i < sp->timecnt; i++) { ttinfo_t *zone; zone = &sp->ttis[sp->types[i]]; if (zone_stdoff_flag == 0 && zone->tt_isdst == 0) { zone_stdoff = -zone->tt_gmtoff; zone_stdoff_flag = 1; } else if (zone_dstoff_flag == 0 && zone->tt_isdst != 0) { zone_dstoff = -zone->tt_gmtoff; zone_dstoff_flag = 1; } } if (zone_dstoff_flag == 0) zone_dstoff = zone_stdoff; /* * Initially we're assumed to be in standard time. */ isdst = 0; for (i = 0; i < sp->timecnt; i++) { ttinfo_t *zone; int next_isdst; zone = &sp->ttis[sp->types[i]]; next_isdst = zone->tt_isdst; sp->types[i] = next_isdst ? 0 : 1; if (zone->tt_ttisgmt == 0) { /* * If summer time is in effect, and the transition time * was not specified as standard time, add the summer * time offset to the transition time; * otherwise, add the standard time offset to the * transition time. */ /* * Transitions from DST to DDST will effectively * disappear since POSIX provides for only one DST * offset. */ if (isdst != 0 && zone->tt_ttisstd == 0) sp->ats[i] += dstoff - zone_dstoff; else sp->ats[i] += stdoff - zone_stdoff; } if (next_isdst != 0) zone_dstoff = -zone->tt_gmtoff; else zone_stdoff = -zone->tt_gmtoff; isdst = next_isdst; } /* * Finally, fill in ttis. * ttisstd and ttisgmt need not be handled. */ sp->ttis[0].tt_gmtoff = -dstoff; sp->ttis[0].tt_isdst = 1; sp->ttis[1].tt_gmtoff = -stdoff; sp->ttis[1].tt_isdst = 0; sp->typecnt = 2; sp->daylight = 1; } /* * */ static void load_posix_transitions(state_t *sp, long stdoff, long dstoff, zone_rules_t zonetype) { ttinfo_t *std, *dst; time_t *tranp; uchar_t *typep; prev_t *prevp; int year; int i; long long janfirst; posix_daylight_t pdaylight; /* * 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; } } } /* * 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; void *defp; assert_no_libc_locks_held(); if ((defp = defopen_r(TIMEZONE)) != NULL) { flags = defcntl_r(DC_GETFLAGS, 0, defp); TURNON(flags, DC_STRIP_QUOTES); (void) defcntl_r(DC_SETFLAGS, flags, defp); if ((tzp = (uchar_t *)defread_r(TZSTRING, defp)) != NULL) { while (isspace(*tzp)) tzp++; tzq = tzp; while (!isspace(*tzq) && *tzq != ';' && *tzq != '#' && *tzq != '\0') tzq++; *tzq = '\0'; if (*tzp != '\0') tz = libc_strdup((char *)tzp); } defclose_r(defp); } return (tz); } /* * Purge all cache'd state_t */ static void purge_zone_cache(void) { int hashid; state_t *p, *n, *r; /* * Create a single list of caches which are detached * from hash table. */ r = NULL; for (hashid = 0; hashid < HASHTABLE; hashid++) { for (p = tzcache[hashid]; p != NULL; p = n) { n = p->next; p->next = r; r = p; } tzcache[hashid] = NULL; } namecache = NULL; /* last_tzname[] may point cache being freed */ last_tzname[0] = NULL; last_tzname[1] = NULL; /* We'll reload system TZ as well */ systemTZ = NULL; /* * Hash table has been cleared, and all elements are detached from * the hash table. Now we are safe to release _time_lock. * We need to unlock _time_lock because we need to call out to * free(). */ lmutex_unlock(&_time_lock); assert_no_libc_locks_held(); while (r != NULL) { n = r->next; libc_free((char *)r->zonename); libc_free((char *)r->chars); free(r); r = n; } lmutex_lock(&_time_lock); } /* * When called first time, open the counter device and load * the initial value. If counter is updated, copy value to * private memory. */ static void reload_counter(void) { int fd; caddr_t addr; if (zoneinfo_seqadr != &zoneinfo_seqno_init) { zoneinfo_seqno = *zoneinfo_seqadr; return; } if ((fd = open(TZSYNC_FILE, O_RDONLY)) < 0) return; addr = mmap(0, sizeof (uint32_t), PROT_READ, MAP_SHARED, fd, 0); (void) close(fd); if (addr == MAP_FAILED) return; /*LINTED*/ zoneinfo_seqadr = (uint32_t *)addr; zoneinfo_seqno = *zoneinfo_seqadr; } /* * 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, or * cleared by tzreload, get_default_tz() is called to read the * /etc/default/init file to get the value. */ static const char * getsystemTZ() { tznmlist_t *tzn; char *tz; tz = getenv("TZ"); if (tz != NULL && *tz != '\0') return ((const char *)tz); if (systemTZ != NULL) return (systemTZ); /* * get_default_tz calls out stdio functions via defread. */ lmutex_unlock(&_time_lock); tz = get_default_tz(); lmutex_lock(&_time_lock); if (tz == NULL) { /* no TZ entry in the file */ systemTZ = _posix_gmt0; return (systemTZ); } /* * look up timezone used previously. We will not free the * old timezone name, because ltzset_u() can release _time_lock * while it has references to systemTZ (via zonename). If we * free the systemTZ, the reference via zonename can access * invalid memory when systemTZ is reset. */ for (tzn = systemTZrec; tzn != NULL; tzn = tzn->link) { if (strcmp(tz, tzn->name) == 0) break; } if (tzn == NULL) { /* This is new timezone name */ tzn = lmalloc(sizeof (tznmlist_t *) + strlen(tz) + 1); (void) strcpy(tzn->name, tz); tzn->link = systemTZrec; systemTZrec = tzn; } libc_free(tz); return (systemTZ = tzn->name); } /* * tzname[] is the user visible string which applications may have * references. Even though TZ was changed, references to the old tzname * may continue to remain in the application, and those references need * to be valid. They were valid by our implementation because strings being * pointed by tzname were never be freed nor altered by the change of TZ. * However, this will no longer be the case. * * state_t is now freed when cache is purged. Therefore, reading string * from old tzname[] addr may end up with accessing a stale data(freed area). * To avoid this, we maintain a copy of all timezone name strings which will * never be freed, and tzname[] will point those copies. * */ static int set_one_tzname(const char *name, int idx) { const unsigned char *nm; int hashid, i; char *s; tznmlist_t *tzn; if (name == _tz_gmt || name == _tz_spaces) { tzname[idx] = (char *)name; return (0); } nm = (const unsigned char *)name; hashid = (nm[0] * 29 + nm[1] * 3) % TZNMC_SZ; for (tzn = tznmhash[hashid]; tzn != NULL; tzn = tzn->link) { s = tzn->name; /* do the strcmp() */ for (i = 0; s[i] == name[i]; i++) { if (s[i] == '\0') { tzname[idx] = tzn->name; return (0); } } } /* * allocate new entry. This entry is never freed, so use lmalloc */ tzn = lmalloc(sizeof (tznmlist_t *) + strlen(name) + 1); if (tzn == NULL) return (1); (void) strcpy(tzn->name, name); /* link it */ tzn->link = tznmhash[hashid]; tznmhash[hashid] = tzn; tzname[idx] = tzn->name; return (0); } /* * Set tzname[] after testing parameter to see if we are setting * same zone name. If we got same address, it should be same zone * name as tzname[], unless cache have been purged. * Note, purge_zone_cache() resets last_tzname[]. */ static void set_tzname(const char **namep) { if (namep[0] != last_tzname[0]) { if (set_one_tzname(namep[0], 0)) { tzname[0] = (char *)_tz_gmt; last_tzname[0] = NULL; } else { last_tzname[0] = namep[0]; } } if (namep[1] != last_tzname[1]) { if (set_one_tzname(namep[1], 1)) { tzname[1] = (char *)_tz_spaces; last_tzname[1] = NULL; } else { last_tzname[1] = namep[1]; } } }