amiga-tz/library/time_tzload.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;
}
}