root/usr/src/lib/libc/port/gen/localtime.c
/*
 * 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 2011 Nexenta Systems, Inc.  All rights reserved.
 * Copyright 2025 Oxide Computer Company
 * Copyright 2025 MNX Cloud, Inc.
 */

/*
 * 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(8), based
 * on zoneinfo rules "source" files.  This is all described on the zic(8)
 * 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>
#include <stdbool.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) \
            ((uchar_t)(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][MONSPERYEAR];
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 * SECSPERHOUR)
#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)).
 */
static time_t
mktime1(struct tm *tmptr, int usetz)
{
        struct tm _tm;
        long long t;            /* must hold more than 32-bit time_t */
        int     temp;
        int     mketimerrno;
        int     overflow;
        void    *unused = NULL;

        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];


        if (usetz) {
                /*
                 * If called from mktime(), then we need to do the TZ
                 * related transformations.
                 */

                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);
                }

        } else {        /* !usetz, i.e. using UTC */
                overflow = 0;
                /* Normalize the TM structure */
                (void) offtime_u((time_t)t, 0, &_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);
}

time_t
mktime(struct tm *tmptr)
{
        return (mktime1(tmptr, TRUE));
}

time_t
timegm(struct tm *tmptr)
{
        return (mktime1(tmptr, FALSE));
}


/*
 * 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 bool
state_is_posix(const state_t *state)
{
        return (state->zonerules == POSIX || state->zonerules == POSIX_USA);
}

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];
        bool posix = state_is_posix(lclzonep);

        if ((is_in_dst = ttisp->tt_isdst) == 0) { /* std. time */
                timezone = -ttisp->tt_gmtoff;
                newtzname[0] = &lclzonep->chars[ttisp->tt_abbrind];
                if (!posix && (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 (!posix && (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] = {
                DAYSPERNYEAR * SECSPERDAY,
                DAYSPERLYEAR * 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(8)
 * 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;
        struct  tzhead *tzhp;
        struct  stat64  stbuf;
        ttinfo_t        *most_recent_alt = NULL;
        ttinfo_t        *most_recent_std = NULL;
        ttinfo_t        *ttisp;


        if (name == NULL) {
                /* May TZDEFAULT be function call? */
                name = TZDEFAULT;
                if (name == 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 += offsetof(struct tzhead, tzh_ttisutcnt);

/* 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) {
                size_t tzl = strlen(tz) + 1;

                /* This is new timezone name */
                tzn = lmalloc(sizeof (tznmlist_t *) + tzl);
                (void) memcpy(tzn->name, tz, tzl);
                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;
        size_t tznl;

        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
         */
        tznl = strlen(name) + 1;
        tzn = lmalloc(sizeof (tznmlist_t *) + tznl);
        if (tzn == NULL)
                return (1);

        (void) memcpy(tzn->name, name, tznl);

        /* 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];
                }
        }
}