498 lines
15 KiB
C
498 lines
15 KiB
C
#include "time_header.h"
|
|
|
|
static int_fast32_t detzcode(const char *const codep)
|
|
{
|
|
register int_fast32_t result;
|
|
register int i;
|
|
int_fast32_t one = 1;
|
|
int_fast32_t halfmaxval = one << (32 - 2);
|
|
int_fast32_t maxval = halfmaxval - 1 + halfmaxval;
|
|
int_fast32_t minval = -1 - maxval;
|
|
|
|
result = codep[0] & 0x7f;
|
|
for (i = 1; i < 4; ++i)
|
|
result = (result << 8) | (codep[i] & 0xff);
|
|
|
|
if (codep[0] & 0x80) {
|
|
/* Do two's-complement negation even on non-two's-complement machines.
|
|
If the result would be minval - 1, return minval. */
|
|
result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0;
|
|
result += minval;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int_fast64_t detzcode64(const char *const codep)
|
|
{
|
|
register int_fast64_t result;
|
|
register int i;
|
|
int_fast64_t one = 1;
|
|
int_fast64_t halfmaxval = one << (64 - 2);
|
|
int_fast64_t maxval = halfmaxval - 1 + halfmaxval;
|
|
int_fast64_t minval = -TWOS_COMPLEMENT(int_fast64_t) - maxval;
|
|
|
|
result = codep[0] & 0x7f;
|
|
for (i = 1; i < 8; ++i)
|
|
result = (result << 8) | (codep[i] & 0xff);
|
|
|
|
if (codep[0] & 0x80) {
|
|
/* Do two's-complement negation even on non-two's-complement machines.
|
|
If the result would be minval - 1, return minval. */
|
|
result -= !TWOS_COMPLEMENT(int_fast64_t) && result != 0;
|
|
result += minval;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool typesequiv(const struct state *sp, int a, int b)
|
|
{
|
|
register bool result;
|
|
|
|
if (sp == NULL ||
|
|
a < 0 || a >= sp->typecnt ||
|
|
b < 0 || b >= sp->typecnt)
|
|
result = false;
|
|
else {
|
|
const struct ttinfo * ap = &sp->ttis[a];
|
|
const struct ttinfo * bp = &sp->ttis[b];
|
|
result = ap->tt_gmtoff == bp->tt_gmtoff &&
|
|
ap->tt_isdst == bp->tt_isdst &&
|
|
ap->tt_ttisstd == bp->tt_ttisstd &&
|
|
ap->tt_ttisgmt == bp->tt_ttisgmt &&
|
|
strcmp(&sp->chars[ap->tt_abbrind],
|
|
&sp->chars[bp->tt_abbrind]) == 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool differ_by_repeat(const time_t t1, const time_t t0)
|
|
{
|
|
if (TYPE_BIT(time_t) - TYPE_SIGNED(time_t) < SECSPERREPEAT_BITS)
|
|
return 0;
|
|
return (int_fast64_t)t1 - (int_fast64_t)t0 == SECSPERREPEAT;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Input buffer for data read from a compiled tz file.
|
|
*/
|
|
union input_buffer {
|
|
/* The first part of the buffer, interpreted as a header. */
|
|
struct tzhead tzhead;
|
|
|
|
/* The entire buffer. */
|
|
char buf[2 * sizeof(struct tzhead) + 2 * sizeof (struct state)
|
|
+ 4 * TZ_MAX_TIMES];
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* Local storage needed for 'tzloadbody'.
|
|
*/
|
|
union local_storage {
|
|
/* The file name to be opened. */
|
|
char fullname[FILENAME_MAX + 1];
|
|
|
|
/* The results of analyzing the file's contents after it is opened. */
|
|
struct {
|
|
/* The input buffer. */
|
|
union input_buffer u;
|
|
|
|
/* A temporary state used for parsing a TZ string in the file. */
|
|
struct state st;
|
|
} u;
|
|
};
|
|
|
|
int openzonefile(char *name, BPTR *file)
|
|
{
|
|
BPTR lock;
|
|
struct FileInfoBlock *fib;
|
|
int error = 0;
|
|
|
|
fib = AllocDosObject(DOS_FIB, NULL);
|
|
if (fib == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
lock = Lock((STRPTR)name, ACCESS_READ);
|
|
if (lock == 0) {
|
|
FreeDosObject(DOS_FIB, fib);
|
|
return 2;
|
|
}
|
|
|
|
if (Examine(lock, fib) == 0) {
|
|
error = 3;
|
|
}
|
|
|
|
if (error == 0 && fib->fib_DirEntryType > 0) {
|
|
error = 4;
|
|
}
|
|
|
|
if (error == 0 && fib->fib_Size < sizeof(struct tzhead)) {
|
|
error = 5;
|
|
}
|
|
|
|
// No buffer overflow
|
|
if (error == 0 && fib->fib_Size > sizeof(union local_storage)) {
|
|
error = 6;
|
|
}
|
|
|
|
FreeDosObject(DOS_FIB, fib);
|
|
|
|
if (error == 0) {
|
|
*file = OpenFromLock(lock);
|
|
if (*file == 0) {
|
|
error = 7;
|
|
} else {
|
|
lock = 0;
|
|
}
|
|
}
|
|
|
|
UnLock(lock);
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Load tz data from the file named NAME into *SP. Read extended
|
|
* format if DOEXTEND. Use *LSP for temporary storage. Return 0 on
|
|
* success, an errno value on failure.
|
|
*/
|
|
int tzloadbody(
|
|
char const *name,
|
|
struct state *sp,
|
|
bool doextend,
|
|
union local_storage *lsp
|
|
)
|
|
{
|
|
BPTR file;
|
|
int err;
|
|
long c;
|
|
int i;
|
|
int stored;
|
|
int nread;
|
|
char *bufptr;
|
|
char const *p = TZDIR;
|
|
char *fullname = lsp->fullname;
|
|
union input_buffer *up = &lsp->u.u;
|
|
int tzheadsize = sizeof(struct tzhead);
|
|
|
|
sp->goback = sp->goahead = false;
|
|
|
|
if (name == NULL || name[0] == '\0') {
|
|
name = TZDEFAULT;
|
|
}
|
|
|
|
if (sizeof(lsp->fullname) - 1 <= strlen(p) + strlen(name))
|
|
return ENAMETOOLONG;
|
|
|
|
strcpy(fullname, p);
|
|
strcat(fullname, name);
|
|
name = fullname;
|
|
|
|
if ((err = openzonefile(fullname, &file)) != 0) {
|
|
return -(10 + err);
|
|
}
|
|
|
|
nread = 0;
|
|
bufptr = up->buf;
|
|
while ((c = FGetC(file)) != -1) {
|
|
*bufptr = c;
|
|
bufptr++;
|
|
nread++;
|
|
}
|
|
|
|
if (nread < tzheadsize) {
|
|
// File too short
|
|
Close(file);
|
|
return -2;
|
|
}
|
|
|
|
if (Close(file) == 0) {
|
|
// Something went really wrong
|
|
return -3;
|
|
}
|
|
|
|
for (stored = 4; stored <= 8; stored *= 2) {
|
|
int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt);
|
|
int_fast32_t ttisgmtcnt = detzcode(up->tzhead.tzh_ttisgmtcnt);
|
|
int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt);
|
|
int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt);
|
|
int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt);
|
|
int_fast32_t charcnt = detzcode(up->tzhead.tzh_charcnt);
|
|
char const *p = up->buf + tzheadsize;
|
|
if (! (0 <= leapcnt && leapcnt < TZ_MAX_LEAPS
|
|
&& 0 < typecnt && typecnt < TZ_MAX_TYPES
|
|
&& 0 <= timecnt && timecnt < TZ_MAX_TIMES
|
|
&& 0 <= charcnt && charcnt < TZ_MAX_CHARS
|
|
&& (ttisstdcnt == typecnt || ttisstdcnt == 0)
|
|
&& (ttisgmtcnt == typecnt || ttisgmtcnt == 0)))
|
|
return EINVAL;
|
|
if (nread
|
|
< (tzheadsize /* struct tzhead */
|
|
+ timecnt * stored /* ats */
|
|
+ timecnt /* types */
|
|
+ typecnt * 6 /* ttinfos */
|
|
+ charcnt /* chars */
|
|
+ leapcnt * (stored + 4) /* lsinfos */
|
|
+ ttisstdcnt /* ttisstds */
|
|
+ ttisgmtcnt)) /* ttisgmts */
|
|
return EINVAL;
|
|
sp->leapcnt = leapcnt;
|
|
sp->timecnt = timecnt;
|
|
sp->typecnt = typecnt;
|
|
sp->charcnt = charcnt;
|
|
|
|
/* Read transitions, discarding those out of time_t range.
|
|
But pretend the last transition before time_t_min
|
|
occurred at time_t_min. */
|
|
timecnt = 0;
|
|
for (i = 0; i < sp->timecnt; ++i) {
|
|
int_fast64_t at
|
|
= stored == 4 ? detzcode(p) : detzcode64(p);
|
|
sp->types[i] = at <= time_t_max;
|
|
if (sp->types[i]) {
|
|
time_t attime
|
|
= ((TYPE_SIGNED(time_t) ? at < time_t_min : at < 0)
|
|
? time_t_min : at);
|
|
if (timecnt && attime <= sp->ats[timecnt - 1]) {
|
|
if (attime < sp->ats[timecnt - 1])
|
|
return EINVAL;
|
|
sp->types[i - 1] = 0;
|
|
timecnt--;
|
|
}
|
|
sp->ats[timecnt++] = attime;
|
|
}
|
|
p += stored;
|
|
}
|
|
|
|
timecnt = 0;
|
|
for (i = 0; i < sp->timecnt; ++i) {
|
|
unsigned char typ = *p++;
|
|
if (sp->typecnt <= typ)
|
|
return EINVAL;
|
|
if (sp->types[i])
|
|
sp->types[timecnt++] = typ;
|
|
}
|
|
sp->timecnt = timecnt;
|
|
for (i = 0; i < sp->typecnt; ++i) {
|
|
struct ttinfo *ttisp;
|
|
unsigned char isdst, abbrind;
|
|
|
|
ttisp = &sp->ttis[i];
|
|
ttisp->tt_gmtoff = detzcode(p);
|
|
p += 4;
|
|
isdst = *p++;
|
|
if (! (isdst < 2))
|
|
return EINVAL;
|
|
ttisp->tt_isdst = isdst;
|
|
abbrind = *p++;
|
|
if (! (abbrind < sp->charcnt))
|
|
return EINVAL;
|
|
ttisp->tt_abbrind = abbrind;
|
|
}
|
|
for (i = 0; i < sp->charcnt; ++i)
|
|
sp->chars[i] = *p++;
|
|
sp->chars[i] = '\0'; /* ensure '\0' at end */
|
|
|
|
/* Read leap seconds, discarding those out of time_t range. */
|
|
leapcnt = 0;
|
|
for (i = 0; i < sp->leapcnt; ++i) {
|
|
int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p);
|
|
int_fast32_t corr = detzcode(p + stored);
|
|
p += stored + 4;
|
|
if (tr <= time_t_max) {
|
|
time_t trans
|
|
= ((TYPE_SIGNED(time_t) ? tr < time_t_min : tr < 0)
|
|
? time_t_min : tr);
|
|
if (leapcnt && trans <= sp->lsis[leapcnt - 1].ls_trans) {
|
|
if (trans < sp->lsis[leapcnt - 1].ls_trans)
|
|
return EINVAL;
|
|
leapcnt--;
|
|
}
|
|
sp->lsis[leapcnt].ls_trans = trans;
|
|
sp->lsis[leapcnt].ls_corr = corr;
|
|
leapcnt++;
|
|
}
|
|
}
|
|
sp->leapcnt = leapcnt;
|
|
|
|
for (i = 0; i < sp->typecnt; ++i) {
|
|
struct ttinfo *ttisp;
|
|
|
|
ttisp = &sp->ttis[i];
|
|
if (ttisstdcnt == 0)
|
|
ttisp->tt_ttisstd = false;
|
|
else {
|
|
if (*p != true && *p != false)
|
|
return EINVAL;
|
|
ttisp->tt_ttisstd = *p++;
|
|
}
|
|
}
|
|
for (i = 0; i < sp->typecnt; ++i) {
|
|
struct ttinfo *ttisp;
|
|
|
|
ttisp = &sp->ttis[i];
|
|
if (ttisgmtcnt == 0)
|
|
ttisp->tt_ttisgmt = false;
|
|
else {
|
|
if (*p != true && *p != false)
|
|
return EINVAL;
|
|
ttisp->tt_ttisgmt = *p++;
|
|
}
|
|
}
|
|
/*
|
|
** If this is an old file, we're done.
|
|
*/
|
|
if (up->tzhead.tzh_version[0] == '\0')
|
|
break;
|
|
nread -= p - up->buf;
|
|
memmove(up->buf, p, nread);
|
|
}
|
|
|
|
if (doextend && nread > 2 &&
|
|
up->buf[0] == '\n' && up->buf[nread - 1] == '\n' &&
|
|
sp->typecnt + 2 <= TZ_MAX_TYPES) {
|
|
|
|
struct state *ts = &lsp->u.st;
|
|
|
|
up->buf[nread - 1] = '\0';
|
|
if (tzparse(&up->buf[1], ts, false) && ts->typecnt == 2) {
|
|
|
|
/* Attempt to reuse existing abbreviations.
|
|
* Without this, America/Anchorage would stop
|
|
* working after 2037 when TZ_MAX_CHARS is 50, as
|
|
* sp->charcnt equals 42 (for LMT CAT CAWT CAPT AHST
|
|
* AHDT YST AKDT AKST) and ts->charcnt equals 10
|
|
* (for AKST AKDT). Reusing means sp->charcnt can
|
|
* stay 42 in this example. */
|
|
int gotabbr = 0;
|
|
int charcnt = sp->charcnt;
|
|
for (i = 0; i < 2; i++) {
|
|
char *tsabbr = ts->chars + ts->ttis[i].tt_abbrind;
|
|
int j;
|
|
for (j = 0; j < charcnt; j++)
|
|
if (strcmp(sp->chars + j, tsabbr) == 0) {
|
|
ts->ttis[i].tt_abbrind = j;
|
|
gotabbr++;
|
|
break;
|
|
}
|
|
if (! (j < charcnt)) {
|
|
int tsabbrlen = strlen(tsabbr);
|
|
if (j + tsabbrlen < TZ_MAX_CHARS) {
|
|
strcpy(sp->chars + j, tsabbr);
|
|
charcnt = j + tsabbrlen + 1;
|
|
ts->ttis[i].tt_abbrind = j;
|
|
gotabbr++;
|
|
}
|
|
}
|
|
}
|
|
if (gotabbr == 2) {
|
|
sp->charcnt = charcnt;
|
|
for (i = 0; i < ts->timecnt; i++)
|
|
if (sp->ats[sp->timecnt - 1] < ts->ats[i])
|
|
break;
|
|
while (i < ts->timecnt
|
|
&& sp->timecnt < TZ_MAX_TIMES) {
|
|
sp->ats[sp->timecnt] = ts->ats[i];
|
|
sp->types[sp->timecnt] = (sp->typecnt
|
|
+ ts->types[i]);
|
|
sp->timecnt++;
|
|
i++;
|
|
}
|
|
sp->ttis[sp->typecnt++] = ts->ttis[0];
|
|
sp->ttis[sp->typecnt++] = ts->ttis[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sp->timecnt > 1) {
|
|
for (i = 1; i < sp->timecnt; ++i)
|
|
if (typesequiv(sp, sp->types[i], sp->types[0]) &&
|
|
differ_by_repeat(sp->ats[i], sp->ats[0])) {
|
|
sp->goback = true;
|
|
break;
|
|
}
|
|
for (i = sp->timecnt - 2; i >= 0; --i)
|
|
if (typesequiv(sp, sp->types[sp->timecnt - 1],
|
|
sp->types[i]) &&
|
|
differ_by_repeat(sp->ats[sp->timecnt - 1],
|
|
sp->ats[i])) {
|
|
sp->goahead = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** If type 0 is is unused in transitions,
|
|
** it's the type to use for early times.
|
|
*/
|
|
for (i = 0; i < sp->timecnt; ++i)
|
|
if (sp->types[i] == 0)
|
|
break;
|
|
i = i < sp->timecnt ? -1 : 0;
|
|
|
|
/*
|
|
** Absent the above,
|
|
** if there are transition times
|
|
** and the first transition is to a daylight time
|
|
** find the standard type less than and closest to
|
|
** the type of the first transition.
|
|
*/
|
|
if (i < 0 && sp->timecnt > 0 && sp->ttis[sp->types[0]].tt_isdst) {
|
|
i = sp->types[0];
|
|
while (--i >= 0)
|
|
if (!sp->ttis[i].tt_isdst)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
** If no result yet, find the first standard type.
|
|
** If there is none, punt to type zero.
|
|
*/
|
|
if (i < 0) {
|
|
i = 0;
|
|
while (sp->ttis[i].tt_isdst)
|
|
if (++i >= sp->typecnt) {
|
|
i = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
sp->defaulttype = i;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Load tz data from the file named NAME into *SP. Read extended
|
|
* format if DOEXTEND. Return 0 on success, an errno value on failure.
|
|
*/
|
|
int tzload(char const *name, struct state *sp, bool doextend)
|
|
{
|
|
unsigned long memsize = sizeof(union local_storage);
|
|
union local_storage *lsp =
|
|
(union local_storage*)
|
|
AllocMem(memsize, MEMF_ANY | MEMF_CLEAR);
|
|
|
|
if (!lsp) {
|
|
return errno;
|
|
} else {
|
|
int err = tzloadbody(name, sp, doextend, lsp);
|
|
FreeMem(lsp, memsize);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|