/*
* Leap second handling from Bradley White.
- * POSIX-style TZ environment variable handling from Guy Harris.
+ * POSIX.1-1988 style TZ environment variable handling from Guy Harris.
*/
/* this file needs to build in both frontend and backend contexts */
#include "tzfile.h"
+/*
+ * Pacify gcc -Wcast-qual on char const * exprs.
+ * Use this carefully, as the casts disable type checking.
+ * This is a macro so that it can be used in static initializers.
+ */
+#define UNCONST(a) unconstify(char *, a)
+
#ifndef WILDABBR
/*
* Someone might make incorrect use of a time zone abbreviation:
* in which Daylight Saving Time is never observed.
* 4. They might reference tzname[0] after setting to a time zone
* in which Standard Time is never observed.
- * 5. They might reference tm.tm_zone after calling offtime.
+ * 5. They might reference tm.TM_ZONE after calling offtime.
* What's best to do in the above cases is open to debate;
* for now, we just set things up so that in any of the five cases
* WILDABBR is used. Another possibility: initialize tzname[0] to the
* manual page of what this "time zone abbreviation" means (doing this so
* that tzname[0] has the "normal" length of three characters).
*/
-#define WILDABBR " "
+#define WILDABBR " "
#endif /* !defined WILDABBR */
static const char wildabbr[] = WILDABBR;
-static const char gmt[] = "GMT";
-
/*
- * The DST rules to use if a POSIX TZ string has no rules.
+ * The DST rules to use if TZ has no rules.
* Default to US rules as of 2017-05-07.
* POSIX does not specify the default DST rules;
* for historical reasons, US rules are a common default.
*/
+#ifndef TZDEFRULESTRING
#define TZDEFRULESTRING ",M3.2.0,M11.1.0"
+#endif
+
+/* TZNAME_MAXIMUM and types ttinfo, lsinfo, state have been moved to pgtz.h */
-/* structs ttinfo, lsinfo, state have been moved to pgtz.h */
+static int
+leapcount(ATTRIBUTE_MAYBE_UNUSED struct state const *sp)
+{
+#if TZ_RUNTIME_LEAPS
+ return sp->leapcnt;
+#else
+ return 0;
+#endif
+}
+static void
+set_leapcount(ATTRIBUTE_MAYBE_UNUSED struct state *sp,
+ ATTRIBUTE_MAYBE_UNUSED int leapcnt)
+{
+#if TZ_RUNTIME_LEAPS
+ sp->leapcnt = leapcnt;
+#endif
+}
+static struct lsinfo
+lsinfo(ATTRIBUTE_MAYBE_UNUSED struct state const *sp,
+ ATTRIBUTE_MAYBE_UNUSED int i)
+{
+#if TZ_RUNTIME_LEAPS
+ return sp->lsis[i];
+#else
+ unreachable();
+#endif
+}
+static void
+set_lsinfo(ATTRIBUTE_MAYBE_UNUSED struct state *sp,
+ ATTRIBUTE_MAYBE_UNUSED int i,
+ ATTRIBUTE_MAYBE_UNUSED struct lsinfo lsinfo)
+{
+#if TZ_RUNTIME_LEAPS
+ sp->lsis[i] = lsinfo;
+#endif
+}
enum r_type
{
static struct pg_tm *gmtsub(pg_time_t const *timep, int_fast32_t offset,
struct pg_tm *tmp);
static bool increment_overflow(int *ip, int j);
-static bool increment_overflow_time(pg_time_t *tp, int_fast32_t j);
-static int_fast64_t leapcorr(struct state const *sp, pg_time_t t);
+static bool increment_overflow_time(pg_time_t *tp, int_fast32_2s j);
+static int_fast32_2s leapcorr(struct state const *sp, pg_time_t t);
static struct pg_tm *timesub(pg_time_t const *timep,
int_fast32_t offset, struct state const *sp,
struct pg_tm *tmp);
-static bool typesequiv(struct state const *sp, int a, int b);
+static bool tzparse(const char *name, struct state *sp, struct state const *basep);
/*
/* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */
static void
-init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, int desigidx)
+init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst,
+ desigidx_type desigidx)
{
s->tt_utoff = utoff;
s->tt_isdst = isdst;
s->tt_ttisut = false;
}
-static int_fast32_t
+static int_fast32_2s
detzcode(const char *const codep)
{
- int_fast32_t result;
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;
+ int_fast32_2s
+ maxval = TWO_31_MINUS_1,
+ minval = -1 - maxval,
+ result;
result = codep[0] & 0x7f;
for (i = 1; i < 4; ++i)
{
/*
* Do two's-complement negation even on non-two's-complement machines.
- * If the result would be minval - 1, return minval.
+ * This cannot overflow, as int_fast32_2s is wide enough.
*/
- result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0;
result += minval;
}
return result;
static int_fast64_t
detzcode64(const char *const codep)
{
- uint_fast64_t result;
+ int_fast64_t result;
int i;
int_fast64_t one = 1;
int_fast64_t halfmaxval = one << (64 - 2);
return result;
}
-static bool
-differ_by_repeat(const pg_time_t t1, const pg_time_t t0)
-{
- if (TYPE_BIT(pg_time_t) - TYPE_SIGNED(pg_time_t) < SECSPERREPEAT_BITS)
- return 0;
- return t1 - t0 == SECSPERREPEAT;
-}
-
/* 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. */
+ /*
+ * The entire buffer. Ideally this would have no size limits; the
+ * following should suffice for practical use.
+ */
char buf[2 * sizeof(struct tzhead) + 2 * sizeof(struct state)
+ 4 * TZ_MAX_TIMES];
};
struct state st;
} u;
- /* We don't need the "fullname" member */
+ /* PG: we don't need the "fullname" member */
};
+/* These tzload flags can be ORed together, and fit into 'char'. */
+enum
+{
+TZLOAD_FROMENV = 1}; /* The TZ string came from the environment. */
+enum
+{
+TZLOAD_TZSTRING = 2}; /* Read any newline-surrounded TZ string. */
+enum
+{
+TZLOAD_TZDIR_SUB = 4}; /* TZ should be a file under TZDIR. */
+
/*
- * Load tz data from the file named NAME into *SP. Read extended
- * format if DOEXTEND. Use *LSP for temporary storage. Return 0 on
+ * Load tz data from the file named NAME into *SP. Respect TZLOADFLAGS.
+ * Use **LSPP for temporary storage. Return 0 on
* success, an errno value on failure.
* PG: If "canonname" is not NULL, then on success the canonical spelling of
* given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
*/
static int
-tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
- union local_storage *lsp)
+tzloadbody(char const *name, char *canonname,
+ struct state *sp, char tzloadflags,
+ union local_storage **lspp)
{
int i;
int fid;
int stored;
ssize_t nread;
- union input_buffer *up = &lsp->u.u;
+ union local_storage *lsp = *lspp;
+ union input_buffer *up;
int tzheadsize = sizeof(struct tzhead);
sp->goback = sp->goahead = false;
if (name[0] == ':')
++name;
+ /*
+ * The IANA code goes to a great deal of trouble here to try to prevent
+ * inappropriate file accesses. That seems unnecessary for PG since we
+ * won't run as root. pg_open_tzfile() does go to some effort to prevent
+ * accesses outside the designated zoneinfo tree, though.
+ */
fid = pg_open_tzfile(name, canonname);
if (fid < 0)
return ENOENT; /* pg_open_tzfile may not set errno */
+ up = &lsp->u.u;
nread = read(fid, up->buf, sizeof up->buf);
if (nread < tzheadsize)
{
}
if (close(fid) < 0)
return errno;
+
for (stored = 4; stored <= 8; stored *= 2)
{
- int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt);
- int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt);
- int_fast64_t prevtr = 0;
- int_fast32_t prevcorr = 0;
- 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 version = up->tzhead.tzh_version[0];
+ bool skip_datablock = stored == 4 && version;
+ int_fast32_t datablock_size;
+ int_fast32_2s
+ ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt),
+ ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt),
+ leapcnt = detzcode(up->tzhead.tzh_leapcnt),
+ timecnt = detzcode(up->tzhead.tzh_timecnt),
+ typecnt = detzcode(up->tzhead.tzh_typecnt),
+ charcnt = detzcode(up->tzhead.tzh_charcnt);
char const *p = up->buf + tzheadsize;
/*
* support future formats that may allow zero typecnt in files that
* have a TZ string and no transitions.
*/
- 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)
- && (ttisutcnt == typecnt || ttisutcnt == 0)))
+ if (!(0 <= leapcnt
+ && leapcnt <= (TZ_RUNTIME_LEAPS ? TZ_MAX_LEAPS : 0)
+ && 0 <= typecnt && typecnt <= TZ_MAX_TYPES
+ && 0 <= timecnt && timecnt <= TZ_MAX_TIMES
+ && 0 <= charcnt && charcnt <= TZ_MAX_CHARS
+ && 0 <= ttisstdcnt && ttisstdcnt <= TZ_MAX_TYPES
+ && 0 <= ttisutcnt && ttisutcnt <= TZ_MAX_TYPES))
return EINVAL;
- if (nread
- < (tzheadsize /* struct tzhead */
- + timecnt * stored /* ats */
+ datablock_size
+ = (timecnt * stored /* ats */
+ timecnt /* types */
+ typecnt * 6 /* ttinfos */
+ charcnt /* chars */
+ leapcnt * (stored + 4) /* lsinfos */
+ ttisstdcnt /* ttisstds */
- + ttisutcnt)) /* ttisuts */
+ + ttisutcnt); /* ttisuts */
+ if (nread < tzheadsize + datablock_size)
return EINVAL;
- sp->leapcnt = leapcnt;
- sp->timecnt = timecnt;
- sp->typecnt = typecnt;
- sp->charcnt = charcnt;
-
- /*
- * Read transitions, discarding those out of pg_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)
+ if (skip_datablock)
+ p += datablock_size;
+ else if (!((ttisstdcnt == typecnt || ttisstdcnt == 0)
+ && (ttisutcnt == typecnt || ttisutcnt == 0)))
+ return EINVAL;
+ else
{
- int_fast64_t at
- = stored == 4 ? detzcode(p) : detzcode64(p);
+ int_fast64_t prevtr = -1;
+ int_fast32_2s prevcorr = -1;
+
+ set_leapcount(sp, leapcnt);
+ sp->timecnt = timecnt;
+ sp->typecnt = typecnt;
+ sp->charcnt = charcnt;
- sp->types[i] = at <= TIME_T_MAX;
- if (sp->types[i])
+ /*
+ * Read transitions, discarding those out of pg_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)
{
- pg_time_t attime
- = ((TYPE_SIGNED(pg_time_t) ? at < TIME_T_MIN : at < 0)
- ? TIME_T_MIN : at);
+ int_fast64_t at
+ = stored == 4 ? detzcode(p) : detzcode64(p);
- if (timecnt && attime <= sp->ats[timecnt - 1])
+ sp->types[i] = at <= TIME_T_MAX;
+ if (sp->types[i])
{
- if (attime < sp->ats[timecnt - 1])
- return EINVAL;
- sp->types[i - 1] = 0;
- timecnt--;
+ pg_time_t attime
+ = ((TYPE_SIGNED(pg_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;
}
- sp->ats[timecnt++] = attime;
+ p += stored;
}
- 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,
- desigidx;
-
- ttisp = &sp->ttis[i];
- ttisp->tt_utoff = detzcode(p);
- p += 4;
- isdst = *p++;
- if (!(isdst < 2))
- return EINVAL;
- ttisp->tt_isdst = isdst;
- desigidx = *p++;
- if (!(desigidx < sp->charcnt))
- return EINVAL;
- ttisp->tt_desigidx = desigidx;
- }
- for (i = 0; i < sp->charcnt; ++i)
- sp->chars[i] = *p++;
- sp->chars[i] = '\0'; /* ensure '\0' at end */
+ timecnt = 0;
+ for (i = 0; i < sp->timecnt; ++i)
+ {
+ unsigned char typ = *p++;
- /* Read leap seconds, discarding those out of pg_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;
- /* Leap seconds cannot occur before the Epoch. */
- if (tr < 0)
- return EINVAL;
- if (tr <= TIME_T_MAX)
+ 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,
+ desigidx;
+ int_fast32_2s utoff = detzcode(p);
+
/*
- * Leap seconds cannot occur more than once per UTC month, and
- * UTC months are at least 28 days long (minus 1 second for a
- * negative leap second). Each leap second's correction must
- * differ from the previous one's by 1 second.
+ * Reject a UT offset equal to -2**31, as it might cause
+ * trouble both in this file and in callers. Also, it violates
+ * RFC 9636 section 3.2.
*/
- if (tr - prevtr < 28 * SECSPERDAY - 1
- || (corr != prevcorr - 1 && corr != prevcorr + 1))
+ if (utoff < -TWO_31_MINUS_1)
return EINVAL;
- sp->lsis[leapcnt].ls_trans = prevtr = tr;
- sp->lsis[leapcnt].ls_corr = prevcorr = corr;
- leapcnt++;
+
+ ttisp = &sp->ttis[i];
+ ttisp->tt_utoff = utoff;
+ p += 4;
+ isdst = *p++;
+ if (!(isdst < 2))
+ return EINVAL;
+ ttisp->tt_isdst = isdst;
+ desigidx = *p++;
+ if (!(desigidx < sp->charcnt))
+ return EINVAL;
+ ttisp->tt_desigidx = desigidx;
}
- }
- sp->leapcnt = leapcnt;
+ for (i = 0; i < sp->charcnt; ++i)
+ sp->chars[i] = *p++;
- for (i = 0; i < sp->typecnt; ++i)
- {
- struct ttinfo *ttisp;
+ /*
+ * Ensure '\0'-terminated, and make it safe to call ttunspecified
+ * later.
+ */
+ memset(&sp->chars[i], 0, CHARS_EXTRA);
- ttisp = &sp->ttis[i];
- if (ttisstdcnt == 0)
- ttisp->tt_ttisstd = false;
- else
+ /* Read leap seconds, discarding those out of pg_time_t range. */
+ leapcnt = 0;
+ for (i = 0; i < leapcount(sp); i++)
{
- if (*p != true && *p != false)
+ int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p);
+ int_fast32_2s corr = detzcode(p + stored);
+
+ p += stored + 4;
+
+ /*
+ * Leap seconds cannot occur before the Epoch, or out of
+ * order.
+ */
+ if (tr <= prevtr)
+ return EINVAL;
+
+ /*
+ * To avoid other botches in this code, each leap second's
+ * correction must differ from the previous one's by 1 second
+ * or less, except that the first correction can be any value;
+ * these requirements are more generous than RFC 9636, to
+ * allow future RFC extensions.
+ */
+ if (!(i == 0
+ || (prevcorr < corr
+ ? corr == prevcorr + 1
+ : (corr == prevcorr
+ || corr == prevcorr - 1))))
return EINVAL;
- ttisp->tt_ttisstd = *p++;
+ prevtr = tr;
+ prevcorr = corr;
+
+ if (tr <= TIME_T_MAX)
+ {
+ struct lsinfo ls;
+
+ ls.ls_trans = tr;
+ ls.ls_corr = corr;
+ set_lsinfo(sp, leapcnt, ls);
+ leapcnt++;
+ }
}
- }
- for (i = 0; i < sp->typecnt; ++i)
- {
- struct ttinfo *ttisp;
+ set_leapcount(sp, leapcnt);
- ttisp = &sp->ttis[i];
- if (ttisutcnt == 0)
- ttisp->tt_ttisut = false;
- else
+ for (i = 0; i < sp->typecnt; ++i)
{
- if (*p != true && *p != false)
- return EINVAL;
- ttisp->tt_ttisut = *p++;
+ 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 (ttisutcnt == 0)
+ ttisp->tt_ttisut = false;
+ else
+ {
+ if (*p != true && *p != false)
+ return EINVAL;
+ ttisp->tt_ttisut = *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 this is an old file, we're done. */
+ if (!version)
+ break;
}
- if (doextend && nread > 2 &&
+ if ((tzloadflags & TZLOAD_TZSTRING) && 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))
+ if (tzparse(&up->buf[1], ts, sp))
{
/*
* Attempt to reuse existing abbreviations. Without this,
- * America/Anchorage would be right on the edge after 2037 when
- * TZ_MAX_CHARS is 50, as sp->charcnt equals 40 (for LMT AST AWT
- * APT AHST AHDT YST AKDT AKST) and ts->charcnt equals 10 (for
- * AKST AKDT). Reusing means sp->charcnt can stay 40 in this
- * example.
+ * America/Anchorage would consume 50 bytes for abbreviations, as
+ * sp->charcnt equals 40 (for LMT AST AWT APT AHST AHDT YST AKDT
+ * AKST) and ts->charcnt equals 10 (for AKST AKDT). Reusing means
+ * sp->charcnt can stay 40 in this example.
*/
int gotabbr = 0;
int charcnt = sp->charcnt;
}
if (!(j < charcnt))
{
- int tsabbrlen = strlen(tsabbr);
+ int tsabbrlen = strnlen(tsabbr, TZ_MAX_CHARS - j);
if (j + tsabbrlen < TZ_MAX_CHARS)
{
- strcpy(sp->chars + j, tsabbr);
+ char *cp = sp->chars + j;
+
+ memcpy(cp, tsabbr, tsabbrlen);
+ cp += tsabbrlen;
+ *cp = '\0';
charcnt = j + tsabbrlen + 1;
ts->ttis[i].tt_desigidx = j;
gotabbr++;
== sp->types[sp->timecnt - 2]))
sp->timecnt--;
+ sp->goahead = ts->goahead;
+
for (i = 0; i < ts->timecnt; i++)
- if (sp->timecnt == 0
- || (sp->ats[sp->timecnt - 1]
- < ts->ats[i] + leapcorr(sp, ts->ats[i])))
- break;
- while (i < ts->timecnt
- && sp->timecnt < TZ_MAX_TIMES)
{
- sp->ats[sp->timecnt]
- = ts->ats[i] + leapcorr(sp, ts->ats[i]);
+ pg_time_t t = ts->ats[i];
+
+ if (increment_overflow_time(&t, leapcorr(sp, t))
+ || (0 < sp->timecnt
+ && t <= sp->ats[sp->timecnt - 1]))
+ continue;
+ if (TZ_MAX_TIMES <= sp->timecnt)
+ {
+ sp->goahead = false;
+ break;
+ }
+ sp->ats[sp->timecnt] = t;
sp->types[sp->timecnt] = (sp->typecnt
+ ts->types[i]);
sp->timecnt++;
- i++;
}
for (i = 0; i < ts->typecnt; i++)
sp->ttis[sp->typecnt++] = ts->ttis[i];
}
if (sp->typecnt == 0)
return EINVAL;
- 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;
- }
- }
-
- /*
- * Infer sp->defaulttype from the data. Although this default type is
- * always zero for data from recent tzdb releases, things are trickier for
- * data from tzdb 2018e or earlier.
- *
- * The first set of heuristics work around bugs in 32-bit data generated
- * by tzdb 2013c or earlier. The workaround is for zones like
- * Australia/Macquarie where timestamps before the first transition have a
- * time type that is not the earliest standard-time type. See:
- * https://mm.icann.org/pipermail/tz/2013-May/019368.html
- */
-
- /*
- * If type 0 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;
- }
-
- /*
- * The next heuristics are for data generated by tzdb 2018e or earlier,
- * for zones like EST5EDT where the first transition is to DST.
- */
-
- /*
- * 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;
- }
- }
-
- /*
- * A simple 'sp->defaulttype = 0;' would suffice here if we didn't have to
- * worry about 2018e-or-earlier data. Even simpler would be to remove the
- * defaulttype member and just use 0 in its place.
- */
- sp->defaulttype = i;
return 0;
}
/*
- * Load tz data from the file named NAME into *SP. Read extended
- * format if DOEXTEND. Return 0 on success, an errno value on failure.
+ * Load tz data from the file named NAME into *SP. Respect TZLOADFLAGS.
+ * Return 0 on success, an errno value on failure.
* PG: If "canonname" is not NULL, then on success the canonical spelling of
* given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
*/
-int
-tzload(char const *name, char *canonname, struct state *sp, bool doextend)
-{
- union local_storage *lsp = malloc(sizeof *lsp);
-
- if (!lsp)
- return errno;
- else
- {
- int err = tzloadbody(name, canonname, sp, doextend, lsp);
-
- free(lsp);
- return err;
- }
-}
-
-static bool
-typesequiv(const struct state *sp, int a, int b)
+static int
+tzload(char const *name, char *canonname, struct state *sp, char tzloadflags)
{
- bool result;
+ /* PG: our version of tzloadbody never reallocates *lspp */
+ union local_storage *lsp;
+ union local_storage ls;
- 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_utoff == bp->tt_utoff
- && ap->tt_isdst == bp->tt_isdst
- && ap->tt_ttisstd == bp->tt_ttisstd
- && ap->tt_ttisut == bp->tt_ttisut
- && (strcmp(&sp->chars[ap->tt_desigidx],
- &sp->chars[bp->tt_desigidx])
- == 0));
- }
- return result;
+ lsp = &ls;
+ return tzloadbody(name, canonname, sp, tzloadflags, &lsp);
}
static const int mon_lengths[2][MONSPERYEAR] = {
DAYSPERNYEAR, DAYSPERLYEAR
};
+/* Is C an ASCII digit? */
+static bool
+is_digit(char c)
+{
+ return '0' <= c && c <= '9';
+}
+
/*
* Given a pointer into a timezone string, scan until a character that is not
* a valid character in a time zone abbreviation is found.
* Return a pointer to that character.
*/
-static const char *
+ATTRIBUTE_PURE_114833 static const char *
getzname(const char *strp)
{
char c;
* We don't do any checking here; checking is done later in common-case code.
*/
-static const char *
+ATTRIBUTE_PURE_114833 static const char *
getqzname(const char *strp, const int delim)
{
int c;
getsecs(const char *strp, int_fast32_t *const secsp)
{
int num;
+ int_fast32_t secsperhour = SECSPERHOUR;
/*
- * 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
- * "M10.4.6/26", which does not conform to Posix, but which specifies the
+ * '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 * (int_fast32_t) SECSPERHOUR;
+ *secsp = num * secsperhour;
if (*strp == ':')
{
++strp;
/*
* Given a pointer into a timezone string, extract a rule in the form
- * date[/time]. See POSIX section 8 for the format of "date" and "time".
+ * date[/time]. See POSIX Base Definitions section 8.3 variable TZ
+ * 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.
*/
yy2,
dow;
- INITIALIZE(value);
leapyear = isleap(year);
switch (rulep->r_type)
{
for (i = 0; i < rulep->r_mon - 1; ++i)
value += mon_lengths[leapyear][i] * SECSPERDAY;
break;
+
+ default:
+ unreachable();
}
/*
}
/*
- * Given a POSIX section 8-style TZ string, fill in the rule tables as
+ * Given a POSIX.1 proleptic TZ string, fill in the rule tables as
* appropriate.
- * Returns true on success, false on failure.
*/
-bool
-tzparse(const char *name, struct state *sp, bool lastditch)
+
+static bool
+tzparse(const char *name, struct state *sp, struct state const *basep)
{
const char *stdname;
const char *dstname = NULL;
- size_t stdlen;
- size_t dstlen;
- size_t charcnt;
int_fast32_t stdoffset;
int_fast32_t dstoffset;
char *cp;
- bool load_ok;
+ ptrdiff_t stdlen,
+ dstlen,
+ charcnt;
+ pg_time_t atlo = TIME_T_MIN,
+ leaplo = TIME_T_MIN;
stdname = name;
- if (lastditch)
+ if (*name == '<')
{
- /* Unlike IANA, don't assume name is exactly "GMT" */
- stdlen = strlen(name); /* length of standard zone name */
- name += stdlen;
- stdoffset = 0;
+ name++;
+ stdname = name;
+ name = getqzname(name, '>');
+ if (*name != '>')
+ return false;
+ stdlen = name - stdname;
+ name++;
}
else
{
- if (*name == '<')
- {
- name++;
- stdname = name;
- name = getqzname(name, '>');
- if (*name != '>')
- return false;
- stdlen = name - stdname;
- name++;
- }
- else
- {
- name = getzname(name);
- stdlen = name - stdname;
- }
- if (*name == '\0') /* we allow empty STD abbrev, unlike IANA */
- return false;
- name = getoffset(name, &stdoffset);
- if (name == NULL)
- return false;
+ name = getzname(name);
+ stdlen = name - stdname;
}
- charcnt = stdlen + 1;
- if (sizeof sp->chars < charcnt)
+ if (stdlen > TZNAME_MAXIMUM) /* allow empty STD abbrev, unlike IANA */
return false;
+ name = getoffset(name, &stdoffset);
+ if (name == NULL)
+ return false;
+ charcnt = stdlen + 1;
+ if (basep)
+ {
+ if (0 < basep->timecnt)
+ atlo = basep->ats[basep->timecnt - 1];
+ set_leapcount(sp, leapcount(basep));
+ if (0 < leapcount(sp))
+ {
+ int i;
- /*
- * The IANA code always tries to tzload(TZDEFRULES) here. We do not want
- * to do that; it would be bad news in the lastditch case, where we can't
- * assume pg_open_tzfile() is sane yet. Moreover, if we did load it and
- * it contains leap-second-dependent info, that would cause problems too.
- * Finally, IANA has deprecated the TZDEFRULES feature, so it presumably
- * will die at some point. Desupporting it now seems like good
- * future-proofing.
- */
- load_ok = false;
- sp->goback = sp->goahead = false; /* simulate failed tzload() */
- sp->leapcnt = 0; /* intentionally assume no leap seconds */
-
+ for (i = 0; i < leapcount(sp); i++)
+ set_lsinfo(sp, i, lsinfo(basep, i));
+ leaplo = lsinfo(sp, leapcount(sp) - 1).ls_trans;
+ }
+ }
+ else
+ set_leapcount(sp, 0); /* So, we're off a little. */
+ sp->goback = sp->goahead = false;
if (*name != '\0')
{
+ struct rule start,
+ end;
+ int year,
+ yearbeg,
+ yearlim,
+ timecnt;
+ pg_time_t janfirst;
+ int_fast32_t janoffset = 0;
+
if (*name == '<')
{
dstname = ++name;
name = getzname(name);
dstlen = name - dstname; /* length of DST abbr. */
}
- if (!dstlen)
+ if (!(0 < dstlen && dstlen <= TZNAME_MAXIMUM))
return false;
charcnt += dstlen + 1;
- if (sizeof sp->chars < charcnt)
- return false;
if (*name != '\0' && *name != ',' && *name != ';')
{
name = getoffset(name, &dstoffset);
}
else
dstoffset = stdoffset - SECSPERHOUR;
- if (*name == '\0' && !load_ok)
- name = TZDEFRULESTRING;
- if (*name == ',' || *name == ';')
- {
- struct rule start;
- struct rule end;
- int year;
- int yearlim;
- int timecnt;
- pg_time_t janfirst;
- int_fast32_t janoffset = 0;
- int yearbeg;
-
- ++name;
- if ((name = getrule(name, &start)) == NULL)
- return false;
- if (*name++ != ',')
- return false;
- if ((name = getrule(name, &end)) == NULL)
- return false;
- if (*name != '\0')
- return false;
- sp->typecnt = 2; /* standard time and DST */
- /*
- * Two transitions per year, from EPOCH_YEAR forward.
- */
- init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
- init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
- sp->defaulttype = 0;
- timecnt = 0;
- janfirst = 0;
- yearbeg = EPOCH_YEAR;
+ if (*name == '\0')
+ name = TZDEFRULESTRING;
+ if (!(*name == ',' || *name == ';'))
+ return false;
- do
- {
- int_fast32_t yearsecs
- = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY;
+ name = getrule(name + 1, &start);
+ if (!name)
+ return false;
+ if (*name++ != ',')
+ return false;
+ name = getrule(name, &end);
+ if (!name || *name)
+ return false;
+ sp->typecnt = 2; /* standard time and DST */
- yearbeg--;
- if (increment_overflow_time(&janfirst, -yearsecs))
- {
- janoffset = -yearsecs;
- break;
- }
- } while (EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg);
+ /*
+ * Two transitions per year, from EPOCH_YEAR forward.
+ */
+ init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
+ init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
+ timecnt = 0;
+ janfirst = 0;
+ yearbeg = EPOCH_YEAR;
- yearlim = yearbeg + YEARSPERREPEAT + 1;
- for (year = yearbeg; year < yearlim; year++)
- {
- int_fast32_t
- starttime = transtime(year, &start, stdoffset),
- endtime = transtime(year, &end, dstoffset);
- int_fast32_t
- yearsecs = (year_lengths[isleap(year)]
- * SECSPERDAY);
- bool reversed = endtime < starttime;
-
- if (reversed)
- {
- int_fast32_t swap = starttime;
+ do
+ {
+ int_fast32_t yearsecs
+ = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY;
+ pg_time_t janfirst1 = janfirst;
- starttime = endtime;
- endtime = swap;
- }
- if (reversed
- || (starttime < endtime
- && (endtime - starttime
- < (yearsecs
- + (stdoffset - dstoffset)))))
- {
- if (TZ_MAX_TIMES - 2 < timecnt)
- break;
- sp->ats[timecnt] = janfirst;
- if (!increment_overflow_time
- (&sp->ats[timecnt],
- janoffset + starttime))
- sp->types[timecnt++] = !reversed;
- sp->ats[timecnt] = janfirst;
- if (!increment_overflow_time
- (&sp->ats[timecnt],
- janoffset + endtime))
- {
- sp->types[timecnt++] = reversed;
- yearlim = year + YEARSPERREPEAT + 1;
- }
- }
- if (increment_overflow_time
- (&janfirst, janoffset + yearsecs))
- break;
- janoffset = 0;
- }
- sp->timecnt = timecnt;
- if (!timecnt)
+ yearbeg--;
+ if (increment_overflow_time(&janfirst1, -yearsecs))
{
- sp->ttis[0] = sp->ttis[1];
- sp->typecnt = 1; /* Perpetual DST. */
+ janoffset = -yearsecs;
+ break;
}
- else if (YEARSPERREPEAT < year - yearbeg)
- sp->goback = sp->goahead = true;
- }
- else
+ janfirst = janfirst1;
+ } while (atlo < janfirst
+ && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg);
+
+ while (true)
{
- int_fast32_t theirstdoffset;
- int_fast32_t theirdstoffset;
- int_fast32_t theiroffset;
- bool isdst;
- int i;
- int j;
+ int_fast32_t yearsecs
+ = year_lengths[isleap(yearbeg)] * SECSPERDAY;
+ int yearbeg1 = yearbeg;
+ pg_time_t janfirst1 = janfirst;
+
+ if (increment_overflow_time(&janfirst1, yearsecs)
+ || increment_overflow(&yearbeg1, 1)
+ || atlo <= janfirst1)
+ break;
+ yearbeg = yearbeg1;
+ janfirst = janfirst1;
+ }
- if (*name != '\0')
- return false;
+ yearlim = yearbeg;
+ if (increment_overflow(&yearlim, years_of_observations))
+ yearlim = INT_MAX;
+ for (year = yearbeg; year < yearlim; year++)
+ {
+ int_fast32_t
+ starttime = transtime(year, &start, stdoffset),
+ endtime = transtime(year, &end, dstoffset),
+ yearsecs = year_lengths[isleap(year)] * SECSPERDAY;
+ bool reversed = endtime < starttime;
- /*
- * Initial values of theirstdoffset and theirdstoffset.
- */
- theirstdoffset = 0;
- for (i = 0; i < sp->timecnt; ++i)
+ if (reversed)
{
- j = sp->types[i];
- if (!sp->ttis[j].tt_isdst)
- {
- theirstdoffset =
- -sp->ttis[j].tt_utoff;
- break;
- }
+ int_fast32_t swap = starttime;
+
+ starttime = endtime;
+ endtime = swap;
}
- theirdstoffset = 0;
- for (i = 0; i < sp->timecnt; ++i)
+ if (reversed
+ || (starttime < endtime
+ && endtime - starttime < yearsecs))
{
- j = sp->types[i];
- if (sp->ttis[j].tt_isdst)
- {
- theirdstoffset =
- -sp->ttis[j].tt_utoff;
+ if (TZ_MAX_TIMES - 2 < timecnt)
break;
+ sp->ats[timecnt] = janfirst;
+ if (!increment_overflow_time(&sp->ats[timecnt],
+ janoffset + starttime)
+ && atlo <= sp->ats[timecnt])
+ sp->types[timecnt++] = !reversed;
+ sp->ats[timecnt] = janfirst;
+ if (!increment_overflow_time(&sp->ats[timecnt],
+ janoffset + endtime)
+ && atlo <= sp->ats[timecnt])
+ {
+ sp->types[timecnt++] = reversed;
}
}
-
- /*
- * Initially we're assumed to be in standard time.
- */
- isdst = false;
- theiroffset = theirstdoffset;
-
- /*
- * Now juggle transition times and types tracking offsets as you
- * do.
- */
- for (i = 0; i < sp->timecnt; ++i)
+ if (endtime < leaplo)
{
- j = sp->types[i];
- sp->types[i] = sp->ttis[j].tt_isdst;
- if (sp->ttis[j].tt_ttisut)
- {
- /* No adjustment to transition time */
- }
- else
- {
- /*
- * If daylight saving time is in effect, and the
- * transition time was not specified as standard time, add
- * the daylight saving 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 && !sp->ttis[j].tt_ttisstd)
- {
- sp->ats[i] += dstoffset -
- theirdstoffset;
- }
- else
- {
- sp->ats[i] += stdoffset -
- theirstdoffset;
- }
- }
- theiroffset = -sp->ttis[j].tt_utoff;
- if (sp->ttis[j].tt_isdst)
- theirdstoffset = theiroffset;
- else
- theirstdoffset = theiroffset;
+ yearlim = year;
+ if (increment_overflow(&yearlim, years_of_observations))
+ yearlim = INT_MAX;
}
-
- /*
- * Finally, fill in ttis.
- */
- init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
- init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
- sp->typecnt = 2;
- sp->defaulttype = 0;
+ if (increment_overflow_time(&janfirst, janoffset + yearsecs))
+ break;
+ janoffset = 0;
+ }
+ sp->timecnt = timecnt;
+ if (!timecnt)
+ {
+ sp->ttis[0] = sp->ttis[1];
+ sp->typecnt = 1; /* Perpetual DST. */
}
+ else if (years_of_observations <= year - yearbeg)
+ sp->goback = sp->goahead = true;
}
else
{
sp->typecnt = 1; /* only standard time */
sp->timecnt = 0;
init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
- sp->defaulttype = 0;
}
sp->charcnt = charcnt;
cp = sp->chars;
if (dstlen != 0)
{
memcpy(cp, dstname, dstlen);
- *(cp + dstlen) = '\0';
+ cp += dstlen;
+ *cp = '\0';
}
return true;
}
static void
gmtload(struct state *const sp)
{
- if (tzload(gmt, NULL, sp, true) != 0)
- tzparse(gmt, sp, true);
+ /* PG: for historical compatibility, use "GMT" not "UTC" as TZ abbrev */
+ tzparse("GMT0", sp, NULL);
}
if ((sp->goback && t < sp->ats[0]) ||
(sp->goahead && t > sp->ats[sp->timecnt - 1]))
{
- pg_time_t newt = t;
+ pg_time_t newt;
pg_time_t seconds;
pg_time_t years;
else
seconds = t - sp->ats[sp->timecnt - 1];
--seconds;
- years = (seconds / SECSPERREPEAT + 1) * YEARSPERREPEAT;
+
+ /*
+ * Beware integer overflow, as SECONDS might be close to the maximum
+ * pg_time_t.
+ */
+ years = seconds / SECSPERREPEAT * YEARSPERREPEAT;
seconds = years * AVGSECSPERYEAR;
+ years += YEARSPERREPEAT;
if (t < sp->ats[0])
- newt += seconds;
+ newt = t + seconds + SECSPERREPEAT;
else
- newt -= seconds;
+ newt = t - seconds - SECSPERREPEAT;
+
if (newt < sp->ats[0] ||
newt > sp->ats[sp->timecnt - 1])
return NULL; /* "cannot happen" */
result = localsub(sp, &newt, tmp);
if (result)
{
+#if defined ckd_add && defined ckd_sub
+ if (t < sp->ats[0]
+ ? ckd_sub(&result->tm_year,
+ result->tm_year, years)
+ : ckd_add(&result->tm_year,
+ result->tm_year, years))
+ return NULL;
+#else
int_fast64_t newy;
newy = result->tm_year;
if (!(INT_MIN <= newy && newy <= INT_MAX))
return NULL;
result->tm_year = newy;
+#endif
}
return result;
}
if (sp->timecnt == 0 || t < sp->ats[0])
{
- i = sp->defaulttype;
+ i = 0;
}
else
{
else
lo = mid + 1;
}
- i = (int) sp->types[lo - 1];
+ i = sp->types[lo - 1];
}
ttisp = &sp->ttis[i];
/*
* To get (wrong) behavior that's compatible with System V Release 2.0
* you'd replace the statement below with t += ttisp->tt_utoff;
- * timesub(&t, 0L, sp, tmp);
+ * timesub(&t, 0, sp, tmp);
*/
result = timesub(&t, ttisp->tt_utoff, sp, tmp);
if (result)
{
result->tm_isdst = ttisp->tt_isdst;
- result->tm_zone = unconstify(char *, &sp->chars[ttisp->tt_desigidx]);
+#ifdef TM_ZONE
+ result->TM_ZONE = UNCONST(&sp->chars[ttisp->tt_desigidx]);
+#endif
}
return result;
}
/*
* gmtsub is to gmtime as localsub is to localtime.
*
- * Except we have a private "struct state" for GMT, so no sp is passed in.
+ * PG: except we have a private "struct state" for GMT, so no sp is passed in.
*/
static struct pg_tm *
-gmtsub(pg_time_t const *timep, int_fast32_t offset,
- struct pg_tm *tmp)
+gmtsub(pg_time_t const *timep,
+ int_fast32_t offset, struct pg_tm *tmp)
{
struct pg_tm *result;
}
result = timesub(timep, offset, gmtptr, tmp);
+#ifdef TM_ZONE
/*
* Could get fancy here and deliver something such as "+xx" or "-xx" if
* offset is non-zero, but this is no time for a treasure hunt.
*/
- if (offset != 0)
- tmp->tm_zone = wildabbr;
- else
- tmp->tm_zone = gmtptr->chars;
-
+ tmp->TM_ZONE = UNCONST(offset ? wildabbr
+ : gmtptr->chars);
+#endif /* defined TM_ZONE */
return result;
}
* where, to make the math easy, the answer for year zero is defined as zero.
*/
-static int
-leaps_thru_end_of_nonneg(int y)
+static pg_time_t
+leaps_thru_end_of_nonneg(pg_time_t y)
{
return y / 4 - y / 100 + y / 400;
}
-static int
-leaps_thru_end_of(const int y)
+static pg_time_t
+leaps_thru_end_of(pg_time_t y)
{
return (y < 0
? -1 - leaps_thru_end_of_nonneg(-1 - y)
timesub(const pg_time_t *timep, int_fast32_t offset,
const struct state *sp, struct pg_tm *tmp)
{
- const struct lsinfo *lp;
pg_time_t tdays;
- int idays; /* unsigned would be so 2003 */
- int_fast64_t rem;
- int y;
const int *ip;
- int_fast64_t corr;
- bool hit;
+ int_fast32_2s corr;
int i;
+ int_fast32_t idays,
+ rem,
+ dayoff,
+ dayrem;
+ pg_time_t y;
+
+ /*
+ * If less than SECSPERMIN, the number of seconds since the most recent
+ * positive leap second; otherwise, do not add 1 to localtime tm_sec
+ * because of leap seconds.
+ */
+ pg_time_t secs_since_posleap = SECSPERMIN;
corr = 0;
- hit = false;
- i = (sp == NULL) ? 0 : sp->leapcnt;
+ i = sp ? leapcount(sp) : 0;
while (--i >= 0)
{
- lp = &sp->lsis[i];
- if (*timep >= lp->ls_trans)
+ struct lsinfo ls = lsinfo(sp, i);
+
+ if (ls.ls_trans <= *timep)
{
- corr = lp->ls_corr;
- hit = (*timep == lp->ls_trans
- && (i == 0 ? 0 : lp[-1].ls_corr) < corr);
+ corr = ls.ls_corr;
+ if ((i == 0 ? 0 : lsinfo(sp, i - 1).ls_corr) < corr)
+ secs_since_posleap = *timep - ls.ls_trans;
break;
}
}
- y = EPOCH_YEAR;
+
+ /*
+ * Calculate the year, avoiding integer overflow even if pg_time_t is
+ * unsigned.
+ */
tdays = *timep / SECSPERDAY;
rem = *timep % SECSPERDAY;
- while (tdays < 0 || tdays >= year_lengths[isleap(y)])
+ rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY;
+ dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3;
+ rem %= SECSPERDAY;
+
+ /*
+ * y = (EPOCH_YEAR + floor((tdays + dayoff) / DAYSPERREPEAT) *
+ * YEARSPERREPEAT), sans overflow. But calculate against 1570 (EPOCH_YEAR
+ * - YEARSPERREPEAT) instead of against 1970 so that things work for
+ * localtime values before 1970 when pg_time_t is unsigned.
+ */
+ dayrem = tdays % DAYSPERREPEAT;
+ dayrem += dayoff % DAYSPERREPEAT;
+ y = (EPOCH_YEAR - YEARSPERREPEAT
+ + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT
+ - ((dayrem % DAYSPERREPEAT) < 0)
+ + tdays / DAYSPERREPEAT)
+ * YEARSPERREPEAT));
+ /* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */
+ idays = tdays % DAYSPERREPEAT;
+ idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT;
+ idays %= DAYSPERREPEAT;
+ /* Increase Y and decrease IDAYS until IDAYS is in range for Y. */
+ while (year_lengths[isleap(y)] <= idays)
{
- int newy;
- pg_time_t tdelta;
- int idelta;
+ int tdelta = idays / DAYSPERLYEAR;
+ int_fast32_t ydelta = tdelta + !tdelta;
+ pg_time_t newy = y + ydelta;
int leapdays;
- tdelta = tdays / DAYSPERLYEAR;
- if (!((!TYPE_SIGNED(pg_time_t) || INT_MIN <= tdelta)
- && tdelta <= INT_MAX))
- goto out_of_range;
- idelta = tdelta;
- if (idelta == 0)
- idelta = (tdays < 0) ? -1 : 1;
- newy = y;
- if (increment_overflow(&newy, idelta))
- goto out_of_range;
leapdays = leaps_thru_end_of(newy - 1) -
leaps_thru_end_of(y - 1);
- tdays -= ((pg_time_t) newy - y) * DAYSPERNYEAR;
- tdays -= leapdays;
+ idays -= ydelta * DAYSPERNYEAR;
+ idays -= leapdays;
y = newy;
}
- /*
- * Given the range, we can now fearlessly cast...
- */
- idays = tdays;
- rem += offset - corr;
- while (rem < 0)
- {
- rem += SECSPERDAY;
- --idays;
- }
- while (rem >= SECSPERDAY)
+#ifdef ckd_add
+ if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE))
{
- rem -= SECSPERDAY;
- ++idays;
+ errno = EOVERFLOW;
+ return NULL;
}
- while (idays < 0)
+#else
+ if (!TYPE_SIGNED(pg_time_t) && y < TM_YEAR_BASE)
{
- if (increment_overflow(&y, -1))
- goto out_of_range;
- idays += year_lengths[isleap(y)];
+ int signed_y = y;
+
+ tmp->tm_year = signed_y - TM_YEAR_BASE;
}
- while (idays >= year_lengths[isleap(y)])
+ else if ((!TYPE_SIGNED(pg_time_t) || INT_MIN + TM_YEAR_BASE <= y)
+ && y - TM_YEAR_BASE <= INT_MAX)
+ tmp->tm_year = y - TM_YEAR_BASE;
+ else
{
- idays -= year_lengths[isleap(y)];
- if (increment_overflow(&y, 1))
- goto out_of_range;
+ errno = EOVERFLOW;
+ return NULL;
}
- tmp->tm_year = y;
- if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
- goto out_of_range;
+#endif
tmp->tm_yday = idays;
/*
* The "extra" mods below avoid overflow problems.
*/
- tmp->tm_wday = EPOCH_WDAY +
- ((y - EPOCH_YEAR) % DAYSPERWEEK) *
- (DAYSPERNYEAR % DAYSPERWEEK) +
- leaps_thru_end_of(y - 1) -
- leaps_thru_end_of(EPOCH_YEAR - 1) +
- idays;
+ tmp->tm_wday = (TM_WDAY_BASE
+ + ((tmp->tm_year % DAYSPERWEEK)
+ * (DAYSPERNYEAR % DAYSPERWEEK))
+ + leaps_thru_end_of(y - 1)
+ - leaps_thru_end_of(TM_YEAR_BASE - 1)
+ + idays);
tmp->tm_wday %= DAYSPERWEEK;
if (tmp->tm_wday < 0)
tmp->tm_wday += DAYSPERWEEK;
- tmp->tm_hour = (int) (rem / SECSPERHOUR);
+ tmp->tm_hour = rem / SECSPERHOUR;
rem %= SECSPERHOUR;
- tmp->tm_min = (int) (rem / SECSPERMIN);
+ tmp->tm_min = rem / SECSPERMIN;
+ tmp->tm_sec = rem % SECSPERMIN;
/*
- * A positive leap second requires a special representation. This uses
- * "... ??:59:60" et seq.
+ * Use "... ??:??:60" at the end of the localtime minute containing the
+ * second just before the positive leap second.
*/
- tmp->tm_sec = (int) (rem % SECSPERMIN) + hit;
+ tmp->tm_sec += secs_since_posleap <= tmp->tm_sec;
+
ip = mon_lengths[isleap(y)];
for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon))
idays -= ip[tmp->tm_mon];
- tmp->tm_mday = (int) (idays + 1);
+ tmp->tm_mday = idays + 1;
tmp->tm_isdst = 0;
- tmp->tm_gmtoff = offset;
+#ifdef TM_GMTOFF
+ tmp->TM_GMTOFF = offset;
+#endif /* defined TM_GMTOFF */
return tmp;
-
-out_of_range:
- errno = EOVERFLOW;
- return NULL;
}
+/*
+ * Adapted from code provided by Robert Elz, who writes:
+ * The "best" way to do mktime I think is based on an idea of Bob
+ * Kridle's (so its said...) from a long time ago.
+ * It does a binary search of the pg_time_t space. Since pg_time_t's are
+ * just 32 bits, its a max of 32 iterations (even at 64 bits it
+ * would still be very reasonable).
+ */
+
+#ifndef WRONG
+#define WRONG (-1)
+#endif /* !defined WRONG */
+
/*
* Normalize logic courtesy Paul Eggert.
*/
static bool
increment_overflow(int *ip, int j)
{
+#ifdef ckd_add
+ return ckd_add(ip, *ip, j);
+#else
int const i = *ip;
/*----------
return true;
*ip += j;
return false;
+#endif
}
static bool
-increment_overflow_time(pg_time_t *tp, int_fast32_t j)
+increment_overflow_time(pg_time_t *tp, int_fast32_2s j)
{
+#ifdef ckd_add
+ return ckd_add(tp, *tp, j);
+#else
/*----------
* This is like
* 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...',
return true;
*tp += j;
return false;
+#endif
}
-static int_fast64_t
+static int_fast32_2s
leapcorr(struct state const *sp, pg_time_t t)
{
- struct lsinfo const *lp;
int i;
- i = sp->leapcnt;
+ i = leapcount(sp);
while (--i >= 0)
{
- lp = &sp->lsis[i];
- if (t >= lp->ls_trans)
- return lp->ls_corr;
+ struct lsinfo ls = lsinfo(sp, i);
+
+ if (ls.ls_trans <= t)
+ return ls.ls_corr;
}
return 0;
}
+/*
+ * Postgres-specific functions begin here.
+ */
+
+/*
+ * Load the definition of the given time zone name into *sp.
+ * Return true if successful, false if not.
+ * If "canonname" is not NULL, then on success the canonical spelling of
+ * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
+ *
+ * "GMT" is always interpreted as the gmtload() definition, without attempting
+ * to load a definition from the filesystem. This has a number of benefits:
+ * 1. It's guaranteed to succeed, so we don't have the failure mode wherein
+ * the bootstrap default timezone setting doesn't work (as could happen if
+ * the OS attempts to supply a leap-second-aware version of "GMT").
+ * 2. Because we aren't accessing the filesystem, we can safely initialize
+ * the "GMT" zone definition before my_exec_path is known.
+ * 3. It's quick enough that we don't waste much time when the bootstrap
+ * default timezone setting is later overridden from postgresql.conf.
+ */
+bool
+pg_tzload(const char *name, char *canonname, struct state *sp)
+{
+ if (strcmp(name, "GMT") == 0)
+ {
+ gmtload(sp);
+ /* Use given name as canonical */
+ if (canonname)
+ strcpy(canonname, name);
+ }
+ else if (tzload(name, canonname, sp, TZLOAD_TZSTRING) != 0)
+ {
+ if (name[0] == ':' || !tzparse(name, sp, NULL))
+ {
+ /* Unknown timezone. Fail our call instead of loading GMT! */
+ return false;
+ }
+ /* For POSIX timezone specs, use given name as canonical */
+ if (canonname)
+ strcpy(canonname, name);
+ }
+ return true;
+}
+
/*
* Find the next DST transition time in the given zone after the given time
*
sp = &tz->state;
if (sp->timecnt == 0)
{
- /* non-DST zone, use the defaulttype */
- ttisp = &sp->ttis[sp->defaulttype];
+ /* non-DST zone, use the defaulttype (now always 0) */
+ ttisp = &sp->ttis[0];
*before_gmtoff = ttisp->tt_utoff;
*before_isdst = ttisp->tt_isdst;
return 0;
}
if (t < sp->ats[0])
{
- /* For "before", use the defaulttype */
- ttisp = &sp->ttis[sp->defaulttype];
+ /* For "before", use the defaulttype (now always 0) */
+ ttisp = &sp->ttis[0];
*before_gmtoff = ttisp->tt_utoff;
*before_isdst = ttisp->tt_isdst;
*boundary = sp->ats[0];
* Not found yet; check the defaulttype, which is notionally the era
* before any of the entries in sp->types[].
*/
- ttisp = &sp->ttis[sp->defaulttype];
+ ttisp = &sp->ttis[0];
if (ttisp->tt_desigidx == abbrind)
{
*gmtoff = ttisp->tt_utoff;
#include "postgres_fe.h"
#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
#include <sys/stat.h>
#include <time.h>
#include "private.h"
#include "tzfile.h"
-#define ZIC_VERSION_PRE_2013 '2'
-#define ZIC_VERSION '3'
+#ifndef O_BINARY
+#define O_BINARY 0 /* MS-Windows */
+#endif
typedef int_fast64_t zic_t;
-#define ZIC_MIN INT_FAST64_MIN
-#define ZIC_MAX INT_FAST64_MAX
-#define PRIdZIC PRIdFAST64
+static zic_t const
+ ZIC_MIN = INT_FAST64_MIN,
+ ZIC_MAX = INT_FAST64_MAX,
+ ZIC32_MIN = -1 - (zic_t) TWO_31_MINUS_1,
+ ZIC32_MAX = TWO_31_MINUS_1;
#define SCNdZIC SCNdFAST64
#ifndef ZIC_MAX_ABBR_LEN_WO_WARN
-#define ZIC_MAX_ABBR_LEN_WO_WARN 6
+#define ZIC_MAX_ABBR_LEN_WO_WARN 6
#endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */
-#ifndef WIN32
-#ifdef S_IRUSR
-#define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
-#else
-#define MKDIR_UMASK 0755
+/* Minimum and maximum years, assuming signed 32-bit pg_time_t. */
+enum
+{
+YEAR_32BIT_MIN = 1901, YEAR_32BIT_MAX = 2038};
+
+/* An upper bound on how much a format might grow due to concatenation. */
+enum
+{
+FORMAT_LEN_GROWTH_BOUND = 5};
+
+/* All file permission bits. */
+#define ALL_PERMS (S_IRWXU | S_IRWXG | S_IRWXO)
+
+/* Troublesome file permission bits. */
+#define TROUBLE_PERMS (S_IWGRP | S_IWOTH)
+
+/*
+ * File permission bits for making directories.
+ * The umask modifies these bits.
+ */
+#define MKDIR_PERMS (ALL_PERMS & ~TROUBLE_PERMS)
+
+/*
+ * File permission bits for making regular files.
+ * The umask modifies these bits.
+ */
+#define CREAT_PERMS (MKDIR_PERMS & ~(S_IXUSR | S_IXGRP | S_IXOTH))
+static mode_t creat_perms = CREAT_PERMS;
+
+static gid_t const no_gid = -1;
+static uid_t const no_uid = -1;
+static gid_t output_group = -1;
+static uid_t output_owner = -1;
+#ifndef GID_T_MAX
+#define GID_T_MAX MAXVAL(gid_t, TYPE_BIT(gid_t))
#endif
+#ifndef UID_T_MAX
+#define UID_T_MAX MAXVAL(uid_t, TYPE_BIT(uid_t))
#endif
-/* Port to native MS-Windows and to ancient UNIX. */
-#if !defined S_ISDIR && defined S_IFDIR && defined S_IFMT
-#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
+
+/*
+ * The minimum alignment of a type, for pre-C23 platforms.
+ * The __SUNPRO_C test is because Oracle Developer Studio 12.6 lacks
+ * <stdalign.h> even though __STDC_VERSION__ == 201112.
+ */
+#if __STDC_VERSION__ < 201112 || defined __SUNPRO_C
+#define alignof(type) offsetof(struct { char a; type b; }, b)
+#elif __STDC_VERSION__ < 202311
+#include <stdalign.h>
#endif
-/* The maximum ptrdiff_t value, for pre-C99 platforms. */
-#ifndef PTRDIFF_MAX
-static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t));
+/* The name used for the file implementing the obsolete -p option. */
+#ifndef TZDEFRULES
+#define TZDEFRULES "posixrules"
#endif
-/* The minimum alignment of a type, for pre-C11 platforms. */
-#if __STDC_VERSION__ < 201112
-#define _Alignof(type) offsetof(struct { char a; type b; }, b)
+/* The maximum length of a text line, including the trailing newline. */
+#ifndef _POSIX2_LINE_MAX
+#define _POSIX2_LINE_MAX 2048
#endif
/*
struct rule
{
- const char *r_filename;
+ int r_filenum;
lineno_t r_linenum;
const char *r_name;
zic_t r_loyear; /* for example, 1986 */
zic_t r_hiyear; /* for example, 1986 */
- bool r_lowasnum;
bool r_hiwasnum;
int r_month; /* 0..11 */
};
/*
- * r_dycode r_dayofmonth r_wday
+ * r_dycode r_dayofmonth r_wday
*/
-
-#define DC_DOM 0 /* 1..31 */ /* unused */
-#define DC_DOWGEQ 1 /* 1..31 */ /* 0..6 (Sun..Sat) */
-#define DC_DOWLEQ 2 /* 1..31 */ /* 0..6 (Sun..Sat) */
+enum
+{
+ DC_DOM, /* 1..31 */ /* unused */
+ DC_DOWGEQ, /* 1..31 */ /* 0..6 (Sun..Sat) */
+ DC_DOWLEQ /* 1..31 */ /* 0..6 (Sun..Sat) */
+};
struct zone
{
- const char *z_filename;
+ int z_filenum;
lineno_t z_linenum;
const char *z_name;
zic_t z_untiltime;
};
-extern int link(const char *target, const char *linkname);
#ifndef AT_SYMLINK_FOLLOW
#define linkat(targetdir, target, linknamedir, linkname, flag) \
- (itssymlink(target) ? (errno = ENOTSUP, -1) : link(target, linkname))
+ (errno = ENOTSUP, -1)
#endif
static void verror(const char *const string, va_list args) pg_attribute_printf(1, 0);
static void error(const char *const string, ...) pg_attribute_printf(1, 2);
static void warning(const char *const string, ...) pg_attribute_printf(1, 2);
+static int addabbr(char chs[TZ_MAX_CHARS], int *pnchs, char const *abbr);
static void addtt(zic_t starttime, int type);
static int addtype(zic_t utoff, char const *abbr,
bool isdst, bool ttisstd, bool ttisut);
-static void leapadd(zic_t t, int correction, int rolling);
static void adjleap(void);
static void associate(void);
-static void dolink(const char *target, const char *linkname,
- bool staysymlink);
-static char **getfields(char *cp);
+static void checkabbr(char const *string);
+static void check_for_signal(void);
+static void dolink(char const *target, char const *linkname, bool staysymlink);
+static int getfields(char *cp, char **array, int arrayelts);
static zic_t gethms(const char *string, const char *errstring);
static zic_t getsave(char *field, bool *isdst);
static void inexpires(char **fields, int nfields);
-static void infile(const char *name);
+static void infile(int fnum, char const *name);
static void inleap(char **fields, int nfields);
static void inlink(char **fields, int nfields);
static void inrule(char **fields, int nfields);
static bool inzcont(char **fields, int nfields);
static bool inzone(char **fields, int nfields);
static bool inzsub(char **fields, int nfields, bool iscont);
-static bool itsdir(char const *name);
-static bool itssymlink(char const *name);
static bool is_alpha(char a);
+static int itssymlink(char const *name, int *cache);
+static void leapadd(zic_t t, int correction, int rolling);
static char lowerit(char a);
static void mkdirs(char const *argname, bool ancestors);
-static void newabbr(const char *string);
static zic_t oadd(zic_t t1, zic_t t2);
+static zic_t omul(zic_t t1, zic_t t2);
static void outzone(const struct zone *zpfirst, ptrdiff_t zonecount);
static zic_t rpytime(const struct rule *rp, zic_t wantedy);
-static void rulesub(struct rule *rp,
+static bool rulesub(struct rule *rp,
const char *loyearp, const char *hiyearp,
const char *typep, const char *monthp,
const char *dayp, const char *timep);
static zic_t tadd(zic_t t1, zic_t t2);
-/* Bound on length of what %z can expand to. */
-enum
+/* Is C an ASCII digit? */
+static bool
+is_digit(char c)
{
-PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1};
+ return '0' <= c && c <= '9';
+}
-/*
- * If true, work around a bug in Qt 5.6.1 and earlier, which mishandles
- * TZif files whose POSIX-TZ-style strings contain '<'; see
- * QTBUG-53071 <https://bugreports.qt.io/browse/QTBUG-53071>. This
- * workaround will no longer be needed when Qt 5.6.1 and earlier are
- * obsolete, say in the year 2021.
- */
-#ifndef WORK_AROUND_QTBUG_53071
+/* Bound on length of what %z can expand to. */
enum
{
-WORK_AROUND_QTBUG_53071 = true};
-#endif
+PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1};
static int charcnt;
static bool errors;
static bool warnings;
-static const char *filename;
-static int leapcnt;
+static int filenum;
+static ptrdiff_t leapcnt;
+static ptrdiff_t leap_alloc;
static bool leapseen;
static zic_t leapminyear;
static zic_t leapmaxyear;
static bool noise;
static bool print_abbrevs;
static zic_t print_cutoff;
-static const char *rfilename;
+static bool skip_mkdir;
+static int rfilenum;
static lineno_t rlinenum;
static const char *progname;
+static char const *leapsec;
+static char *const *main_argv;
static ptrdiff_t timecnt;
static ptrdiff_t timecnt_alloc;
static int typecnt;
+static int unspecifiedtype;
/*
* Line codes.
*/
-#define LC_RULE 0
-#define LC_ZONE 1
-#define LC_LINK 2
-#define LC_LEAP 3
-#define LC_EXPIRES 4
+enum
+{
+ LC_RULE,
+ LC_ZONE,
+ LC_LINK,
+ LC_LEAP,
+ LC_EXPIRES
+};
/*
* Which fields are which on a Zone line.
*/
-#define ZF_NAME 1
-#define ZF_STDOFF 2
-#define ZF_RULE 3
-#define ZF_FORMAT 4
-#define ZF_TILYEAR 5
-#define ZF_TILMONTH 6
-#define ZF_TILDAY 7
-#define ZF_TILTIME 8
-#define ZONE_MINFIELDS 5
-#define ZONE_MAXFIELDS 9
+enum
+{
+ ZF_NAME = 1,
+ ZF_STDOFF,
+ ZF_RULE,
+ ZF_FORMAT,
+ ZF_TILYEAR,
+ ZF_TILMONTH,
+ ZF_TILDAY,
+ ZF_TILTIME,
+ ZONE_MAXFIELDS,
+ ZONE_MINFIELDS = ZF_TILYEAR
+};
/*
* Which fields are which on a Zone continuation line.
*/
-#define ZFC_STDOFF 0
-#define ZFC_RULE 1
-#define ZFC_FORMAT 2
-#define ZFC_TILYEAR 3
-#define ZFC_TILMONTH 4
-#define ZFC_TILDAY 5
-#define ZFC_TILTIME 6
-#define ZONEC_MINFIELDS 3
-#define ZONEC_MAXFIELDS 7
+enum
+{
+ ZFC_STDOFF,
+ ZFC_RULE,
+ ZFC_FORMAT,
+ ZFC_TILYEAR,
+ ZFC_TILMONTH,
+ ZFC_TILDAY,
+ ZFC_TILTIME,
+ ZONEC_MAXFIELDS,
+ ZONEC_MINFIELDS = ZFC_TILYEAR
+};
/*
* Which files are which on a Rule line.
*/
-#define RF_NAME 1
-#define RF_LOYEAR 2
-#define RF_HIYEAR 3
-#define RF_COMMAND 4
-#define RF_MONTH 5
-#define RF_DAY 6
-#define RF_TOD 7
-#define RF_SAVE 8
-#define RF_ABBRVAR 9
-#define RULE_FIELDS 10
+enum
+{
+ RF_NAME = 1,
+ RF_LOYEAR,
+ RF_HIYEAR,
+ RF_COMMAND,
+ RF_MONTH,
+ RF_DAY,
+ RF_TOD,
+ RF_SAVE,
+ RF_ABBRVAR,
+ RULE_FIELDS
+};
/*
* Which fields are which on a Link line.
*/
-#define LF_TARGET 1
-#define LF_LINKNAME 2
-#define LINK_FIELDS 3
+enum
+{
+ LF_TARGET = 1,
+ LF_LINKNAME,
+ LINK_FIELDS
+};
/*
* Which fields are which on a Leap line.
*/
-#define LP_YEAR 1
-#define LP_MONTH 2
-#define LP_DAY 3
-#define LP_TIME 4
-#define LP_CORR 5
-#define LP_ROLL 6
-#define LEAP_FIELDS 7
+enum
+{
+ LP_YEAR = 1,
+ LP_MONTH,
+ LP_DAY,
+ LP_TIME,
+ LP_CORR,
+ LP_ROLL,
+ LEAP_FIELDS,
-/* Expires lines are like Leap lines, except without CORR and ROLL fields. */
-#define EXPIRES_FIELDS 5
+ /*
+ * Expires lines are like Leap lines, except without CORR and ROLL fields.
+ */
+ EXPIRES_FIELDS = LP_TIME + 1
+};
+
+/*
+ * The maximum number of fields on any of the above lines.
+ * (The "+"s pacify gcc -Wenum-compare.)
+ */
+enum
+{
+ MAX_FIELDS = max(max(+RULE_FIELDS, +LINK_FIELDS),
+ max(+LEAP_FIELDS, +EXPIRES_FIELDS))
+};
/*
* Year synonyms.
*/
-#define YR_MINIMUM 0
-#define YR_MAXIMUM 1
-#define YR_ONLY 2
+enum
+{
+ YR_MINIMUM, /* "minimum" is for backward compatibility
+ * only */
+ YR_MAXIMUM,
+ YR_ONLY
+};
static struct rule *rules;
static ptrdiff_t nrules; /* number of rules */
struct link
{
- const char *l_filename;
+ int l_filenum;
lineno_t l_linenum;
const char *l_target;
const char *l_linkname;
static struct lookup const begin_years[] = {
{"minimum", YR_MINIMUM},
- {"maximum", YR_MAXIMUM},
{NULL, 0}
};
static struct lookup const end_years[] = {
- {"minimum", YR_MINIMUM},
{"maximum", YR_MAXIMUM},
{"only", YR_ONLY},
{NULL, 0}
static bool ttisstds[TZ_MAX_TYPES];
static bool ttisuts[TZ_MAX_TYPES];
static char chars[TZ_MAX_CHARS];
-static zic_t trans[TZ_MAX_LEAPS];
-static zic_t corr[TZ_MAX_LEAPS];
-static char roll[TZ_MAX_LEAPS];
+static struct
+{
+ zic_t trans;
+ zic_t corr;
+ char roll;
+} *leap;
/*
* Memory allocation.
*/
-static _Noreturn void
+ATTRIBUTE_NORETURN static void
memory_exhausted(const char *msg)
{
fprintf(stderr, _("%s: Memory exhausted: %s\n"), progname, msg);
exit(EXIT_FAILURE);
}
-static size_t
-size_product(size_t nitems, size_t itemsize)
+ATTRIBUTE_NORETURN static void
+size_overflow(void)
{
- if (SIZE_MAX / itemsize < nitems)
- memory_exhausted(_("size overflow"));
- return nitems * itemsize;
+ memory_exhausted(_("size overflow"));
}
-static size_t
-align_to(size_t size, size_t alignment)
+ATTRIBUTE_PURE_114833_HACK
+static ptrdiff_t
+size_sum(size_t a, size_t b)
{
- size_t aligned_size = size + alignment - 1;
+#ifdef ckd_add
+ ptrdiff_t sum;
- aligned_size -= aligned_size % alignment;
- if (aligned_size < size)
- memory_exhausted(_("alignment overflow"));
- return aligned_size;
+ if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX)
+ return sum;
+#else
+ if (a <= INDEX_MAX && b <= INDEX_MAX - a)
+ return a + b;
+#endif
+ size_overflow();
+}
+
+ATTRIBUTE_PURE_114833_HACK
+static ptrdiff_t
+size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
+{
+#ifdef ckd_mul
+ ptrdiff_t product;
+
+ if (!ckd_mul(&product, nitems, itemsize) && product <= INDEX_MAX)
+ return product;
+#else
+ ptrdiff_t nitems_max = INDEX_MAX / itemsize;
+
+ if (nitems <= nitems_max)
+ return nitems * itemsize;
+#endif
+ size_overflow();
+}
+
+ATTRIBUTE_PURE_114833_HACK
+static ptrdiff_t
+align_to(ptrdiff_t size, ptrdiff_t alignment)
+{
+ ptrdiff_t lo_bits = alignment - 1,
+ sum = size_sum(size, lo_bits);
+
+ return sum & ~lo_bits;
}
static void *
}
static void *
-emalloc(size_t size)
+xmalloc(size_t size)
{
return memcheck(malloc(size));
}
static void *
-erealloc(void *ptr, size_t size)
+xrealloc(void *ptr, size_t size)
{
return memcheck(realloc(ptr, size));
}
static char *
-ecpyalloc(char const *str)
+xstrdup(char const *str)
{
return memcheck(strdup(str));
}
-static void *
-growalloc(void *ptr, size_t itemsize, ptrdiff_t nitems, ptrdiff_t *nitems_alloc)
+static ptrdiff_t
+grow_nitems_alloc(ptrdiff_t *nitems_alloc, ptrdiff_t itemsize)
{
- if (nitems < *nitems_alloc)
- return ptr;
- else
- {
- ptrdiff_t nitems_max = PTRDIFF_MAX - WORK_AROUND_QTBUG_53071;
- ptrdiff_t amax = nitems_max < SIZE_MAX ? nitems_max : SIZE_MAX;
+ ptrdiff_t addend = (*nitems_alloc >> 1) + 1;
+#if defined ckd_add && defined ckd_mul
+ ptrdiff_t product;
- if ((amax - 1) / 3 * 2 < *nitems_alloc)
- memory_exhausted(_("integer overflow"));
- *nitems_alloc += (*nitems_alloc >> 1) + 1;
- return erealloc(ptr, size_product(*nitems_alloc, itemsize));
+ if (!ckd_add(nitems_alloc, *nitems_alloc, addend)
+ && !ckd_mul(&product, *nitems_alloc, itemsize) && product <= INDEX_MAX)
+ return product;
+#else
+ if (*nitems_alloc <= ((INDEX_MAX - 1) / 3 * 2) / itemsize)
+ {
+ *nitems_alloc += addend;
+ return *nitems_alloc * itemsize;
}
+#endif
+ memory_exhausted(_("integer overflow"));
+}
+
+static void *
+growalloc(void *ptr, ptrdiff_t itemsize, ptrdiff_t nitems,
+ ptrdiff_t *nitems_alloc)
+{
+ return (nitems < *nitems_alloc
+ ? ptr
+ : xrealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
}
/*
* Error handling.
*/
+/*
+ * In most of the code, an input file name is represented by its index
+ * into the main argument vector, except that LEAPSEC_FILENUM stands
+ * for leapsec and COMMAND_LINE_FILENUM stands for the command line.
+ */
+enum
+{
+LEAPSEC_FILENUM = -2, COMMAND_LINE_FILENUM = -1};
+
+/* Return the name of the Ith input file, for diagnostics. */
+static char const *
+filename(int i)
+{
+ if (i == COMMAND_LINE_FILENUM)
+ return _("command line");
+ else
+ {
+ char const *fname = i == LEAPSEC_FILENUM ? leapsec : main_argv[i];
+
+ return strcmp(fname, "-") == 0 ? _("standard input") : fname;
+ }
+}
+
static void
-eats(char const *name, lineno_t num, char const *rname, lineno_t rnum)
+eats(int fnum, lineno_t num, int rfnum, lineno_t rnum)
{
- filename = name;
+ filenum = fnum;
linenum = num;
- rfilename = rname;
+ rfilenum = rfnum;
rlinenum = rnum;
}
static void
-eat(char const *name, lineno_t num)
+eat(int fnum, lineno_t num)
{
- eats(name, num, NULL, -1);
+ eats(fnum, num, 0, -1);
}
static void
verror(const char *const string, va_list args)
{
+ check_for_signal();
+
/*
* Match the format of "cc" to allow sh users to zic ... 2>&1 | error -t
* "*" -v on BSD systems.
*/
- if (filename)
- fprintf(stderr, _("\"%s\", line %" PRIdMAX ": "), filename, linenum);
+ if (filenum)
+ fprintf(stderr, _("\"%s\", line %" PRIdMAX ": "),
+ filename(filenum), linenum);
vfprintf(stderr, string, args);
- if (rfilename != NULL)
+ if (rfilenum)
fprintf(stderr, _(" (rule from \"%s\", line %" PRIdMAX ")"),
- rfilename, rlinenum);
+ filename(rfilenum), rlinenum);
fprintf(stderr, "\n");
}
warnings = true;
}
+/*
+ * Convert ARG, a string in base BASE, to an unsigned long value no
+ * greater than MAXVAL. On failure, diagnose with MSGID and exit.
+ */
+static unsigned long
+arg2num(char const *arg, int base, unsigned long maxval, char const *msgid)
+{
+ unsigned long n;
+ char *ep;
+
+ errno = 0;
+ n = strtoul(arg, &ep, base);
+ if (ep == arg || *ep || maxval < n || errno)
+ {
+ fprintf(stderr, _(msgid), progname, arg);
+ exit(EXIT_FAILURE);
+ }
+ return n;
+}
+
+#ifndef MODE_T_MAX
+#define MODE_T_MAX MAXVAL(mode_t, TYPE_BIT(mode_t))
+#endif
+
+#ifndef HAVE_SETMODE
+#if (defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
+ || (defined __APPLE__ && defined __MACH__))
+#define HAVE_SETMODE 1
+#else
+#define HAVE_SETMODE 0
+#endif
+#endif
+
+static mode_t const no_mode = -1;
+static mode_t output_mode = -1;
+
+static mode_t
+mode_option(char const *arg)
+{
+#if HAVE_SETMODE
+ void *set = setmode(arg);
+
+ if (set)
+ {
+ mode_t mode = getmode(set, CREAT_PERMS);
+
+ free(set);
+ return mode;
+ }
+#endif
+ return arg2num(arg, 8, min(MODE_T_MAX, ULONG_MAX),
+ N_("%s: -m '%s': invalid mode\n"));
+}
+
+static int
+chmetadata(FILE *stream)
+{
+#ifndef WIN32
+ if (output_owner != no_uid || output_group != no_gid)
+ {
+ int r = fchown(fileno(stream), output_owner, output_group);
+
+ if (r < 0)
+ return r;
+ }
+ return output_mode == no_mode ? 0 : fchmod(fileno(stream), output_mode);
+#else
+ return 0;
+#endif
+}
+
+/*
+ * Close STREAM.
+ * If it had an I/O error, report it against DIR/NAME,
+ * remove TEMPNAME if nonnull, and then exit.
+ * If TEMPNAME is nonnull, and if requested,
+ * change the stream's metadata before closing.
+ */
static void
-close_file(FILE *stream, char const *dir, char const *name)
+close_file(FILE *stream, char const *dir, char const *name,
+ char const *tempname)
{
char const *e = (ferror(stream) ? _("I/O error")
- : fclose(stream) != 0 ? strerror(errno) : NULL);
+ : ((tempname
+ && (fflush(stream) < 0 || chmetadata(stream) < 0))
+ || fclose(stream) < 0)
+ ? strerror(errno) : NULL);
if (e)
{
+ if (name && *name == '/')
+ dir = NULL;
fprintf(stderr, "%s: %s%s%s%s%s\n", progname,
dir ? dir : "", dir ? "/" : "",
name ? name : "", name ? ": " : "",
e);
+ if (tempname)
+ remove(tempname);
exit(EXIT_FAILURE);
}
}
-static _Noreturn void
+ATTRIBUTE_NORETURN static void
+duplicate_options(char const *opt)
+{
+ fprintf(stderr, _("%s: More than one %s option specified\n"), progname, opt);
+ exit(EXIT_FAILURE);
+}
+
+ATTRIBUTE_NORETURN static void
usage(FILE *stream, int status)
{
fprintf(stream,
_("%s: usage is %s [ --version ] [ --help ] [ -v ] [ -P ] \\\n"
- "\t[ -b {slim|fat} ] [ -d directory ] [ -l localtime ]"
- " [ -L leapseconds ] \\\n"
- "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -t localtime-link ] \\\n"
+ "\t[ -b {slim|fat} ] [ -d directory ] [ -D ] \\\n"
+ "\t[ -l localtime ] [ -L leapseconds ] [ -m mode ] \\\n"
+ "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -R @hi ] \\\n"
+ "\t[ -t localtime-link ] [ -u 'owner[:group]' ] \\\n"
"\t[ filename ... ]\n\n"
"Report bugs to %s.\n"),
progname, progname, PACKAGE_BUGREPORT);
if (status == EXIT_SUCCESS)
- close_file(stream, NULL, NULL);
+ close_file(stream, NULL, NULL, NULL);
exit(status);
}
+static void
+group_option(char const *arg)
+{
+#ifndef WIN32
+ if (*arg)
+ {
+ if (output_group != no_gid)
+ {
+ fprintf(stderr, _("multiple groups specified"));
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ struct group *gr = getgrnam(arg);
+
+ output_group = (gr ? gr->gr_gid
+ : arg2num(arg, 10, min(GID_T_MAX, ULONG_MAX),
+ N_("%s: invalid group: %s\n")));
+ }
+ }
+#endif
+}
+
+static void
+owner_option(char const *arg)
+{
+#ifndef WIN32
+ if (*arg)
+ {
+ if (output_owner != no_uid)
+ {
+ fprintf(stderr, _("multiple owners specified"));
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ struct passwd *pw = getpwnam(arg);
+
+ output_owner = (pw ? pw->pw_uid
+ : arg2num(arg, 10, min(UID_T_MAX, ULONG_MAX),
+ N_("%s: invalid owner: %s\n")));
+ }
+ }
+#endif
+}
+
+/*
+ * If setting owner or group, use temp file permissions that avoid
+ * security races before the fchmod at the end.
+ */
+static void
+use_safe_temp_permissions(void)
+{
+ if (output_owner != no_uid || output_group != no_gid)
+ {
+
+ /* The mode when done with the file. */
+ mode_t omode;
+
+ if (output_mode == no_mode)
+ {
+ mode_t cmask = umask(0);
+
+ umask(cmask);
+ omode = CREAT_PERMS & ~cmask;
+ }
+ else
+ omode = output_mode;
+
+ /*
+ * The mode passed to open+O_CREAT. Do not bother with executable
+ * permissions, as they should not be used and this mode is merely a
+ * nicety (even a mode of 0 still work).
+ */
+ creat_perms = ((((omode & (S_IRUSR | S_IRGRP | S_IROTH))
+ == (S_IRUSR | S_IRGRP | S_IROTH))
+ ? S_IRUSR | S_IRGRP | S_IROTH : 0)
+ | (((omode & (S_IWUSR | S_IWGRP | S_IWOTH))
+ == (S_IWUSR | S_IWGRP | S_IWOTH))
+ ? S_IWUSR | S_IWGRP | S_IWOTH : 0));
+
+ /*
+ * If creat_perms is not the final mode, arrange to run fchmod later,
+ * even if -m was not used.
+ */
+ if (creat_perms != omode)
+ output_mode = omode;
+ }
+}
+
/*
* Change the working directory to DIR, possibly creating DIR and its
* ancestors. After this is done, all files are accessed with names
}
}
-#define TIME_T_BITS_IN_FILE 64
+/* Compare the two links A and B, for a stable sort by link name. */
+static int
+qsort_linkcmp(void const *a, void const *b)
+{
+ struct link const *l = a;
+ struct link const *m = b;
+ int cmp = strcmp(l->l_linkname, m->l_linkname);
+
+ if (cmp)
+ return cmp;
+
+ /*
+ * The link names are the same. Make the sort stable by comparing file
+ * numbers (where subtraction cannot overflow) and possibly line numbers
+ * (where it can).
+ */
+ cmp = l->l_filenum - m->l_filenum;
+ if (cmp)
+ return cmp;
+ return (l->l_linenum > m->l_linenum) - (l->l_linenum < m->l_linenum);
+}
+
+/* Compare the string KEY to the link B, for bsearch. */
+static int
+bsearch_linkcmp(void const *key, void const *b)
+{
+ struct link const *m = b;
+
+ return strcmp(key, m->l_linkname);
+}
+
+/* Make the links specified by the Link lines. */
+static void
+make_links(void)
+{
+ ptrdiff_t i,
+ j,
+ nalinks,
+ pass_size;
+
+ if (1 < nlinks)
+ qsort(links, nlinks, sizeof *links, qsort_linkcmp);
+
+ /* Ignore each link superseded by a later link with the same name. */
+ j = 0;
+ for (i = 0; i < nlinks; i++)
+ {
+ while (i + 1 < nlinks
+ && strcmp(links[i].l_linkname, links[i + 1].l_linkname) == 0)
+ i++;
+ links[j++] = links[i];
+ }
+ nlinks = pass_size = j;
+
+ /*
+ * Walk through the link array making links. However, if a link's target
+ * has not been made yet, append a copy to the end of the array. The end
+ * of the array will gradually fill up with a small sorted subsequence of
+ * not-yet-made links. nalinks counts all the links in the array,
+ * including copies. When we reach the copied subsequence, it may still
+ * contain a link to a not-yet-made link, so the process repeats. At any
+ * given point in time, the link array consists of the following
+ * subregions, where 0 <= i <= j <= nalinks and 0 <= nlinks <= nalinks:
+ *
+ * 0 .. (i - 1): links that either have been made, or have been copied to
+ * a later point point in the array (this later point can be in any of the
+ * three subregions) i .. (j - 1): not-yet-made links for this pass j ..
+ * (nalinks - 1): not-yet-made links that this pass has skipped because
+ * they were links to not-yet-made links
+ *
+ * The first subregion might not be sorted if nlinks < i; the other two
+ * subregions are sorted. This algorithm does not alter entries 0 ..
+ * (nlinks - 1), which remain sorted.
+ *
+ * If there are L links, this algorithm is O(C*L*log(L)) where C is the
+ * length of the longest link chain. Usually C is short (e.g., 3) though
+ * its worst-case value is L.
+ */
+
+ j = nalinks = nlinks;
+
+ for (i = 0; i < nalinks; i++)
+ {
+ struct link *l;
+
+ eat(links[i].l_filenum, links[i].l_linenum);
+
+ /* If this pass examined all its links, start the next pass. */
+ if (i == j)
+ {
+ if (nalinks - i == pass_size)
+ {
+ error(_("\"Link %s %s\" is part of a link cycle"),
+ links[i].l_target, links[i].l_linkname);
+ break;
+ }
+ j = nalinks;
+ pass_size = nalinks - i;
+ }
+
+ /*
+ * Diagnose self links, which the cycle detection algorithm would not
+ * otherwise catch.
+ */
+ if (strcmp(links[i].l_target, links[i].l_linkname) == 0)
+ {
+ error(_("link %s targets itself"), links[i].l_target);
+ continue;
+ }
+
+ /* Make this link unless its target has not been made yet. */
+ l = bsearch(links[i].l_target, &links[i + 1], j - (i + 1),
+ sizeof *links, bsearch_linkcmp);
+ if (!l)
+ l = bsearch(links[i].l_target, &links[j], nalinks - j,
+ sizeof *links, bsearch_linkcmp);
+ if (!l)
+ dolink(links[i].l_target, links[i].l_linkname, false);
+ else
+ {
+ /*
+ * The link target has not been made yet; copy the link to the
+ * end.
+ */
+ links = growalloc(links, sizeof *links, nalinks, &nlinks_alloc);
+ links[nalinks++] = links[i];
+ }
+
+ if (noise && i < nlinks)
+ {
+ if (l)
+ warning(_("link %s targeting link %s mishandled by pre-2023 zic"),
+ links[i].l_linkname, links[i].l_target);
+ else if (bsearch(links[i].l_target, links, nlinks, sizeof *links,
+ bsearch_linkcmp))
+ warning(_("link %s targeting link %s"),
+ links[i].l_linkname, links[i].l_target);
+ }
+ check_for_signal();
+ }
+}
+
+/*
+ * Simple signal handling: just set a flag that is checked
+ * periodically outside critical sections. To set up the handler,
+ * prefer sigaction if available to close a signal race.
+ */
+
+static sig_atomic_t got_signal;
+
+static void
+signal_handler(int sig)
+{
+#ifndef SA_SIGINFO
+ signal(sig, signal_handler);
+#endif
+ got_signal = sig;
+}
+
+/* Arrange for SIGINT etc. to be caught by the handler. */
+static void
+catch_signals(void)
+{
+ static int const signals[] = {
+#ifdef SIGHUP
+ SIGHUP,
+#endif
+ SIGINT,
+#ifdef SIGPIPE
+ SIGPIPE,
+#endif
+ SIGTERM
+ };
+ int i;
+
+ for (i = 0; i < sizeof signals / sizeof signals[0]; i++)
+ {
+#ifdef SA_SIGINFO
+ struct sigaction act0,
+ act;
+
+ act.sa_handler = signal_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(signals[i], &act, &act0) == 0
+ && !(act0.sa_flags & SA_SIGINFO) && act0.sa_handler == SIG_IGN)
+ {
+ sigaction(signals[i], &act0, NULL);
+ got_signal = 0;
+ }
+#else
+ if (signal(signals[i], signal_handler) == SIG_IGN)
+ {
+ signal(signals[i], SIG_IGN);
+ got_signal = 0;
+ }
+#endif
+ }
+}
+
+/* If a signal has arrived, terminate zic with appropriate status. */
+static void
+check_for_signal(void)
+{
+ int sig = got_signal;
+
+ if (sig)
+ {
+ signal(sig, SIG_DFL);
+ raise(sig);
+ abort(); /* A bug in 'raise'. */
+ }
+}
+
+enum
+{
+TIME_T_BITS_IN_FILE = 64};
/* The minimum and maximum values representable in a TZif file. */
static zic_t const min_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
static zic_t lo_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
static zic_t hi_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
+/*
+ * The time specified by the -R option, defaulting to MIN_TIME;
+ * or lo_time, whichever is greater.
+ */
+static zic_t redundant_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
+
/* The time specified by an Expires line, or negative if no such line. */
static zic_t leapexpires = -1;
-/* The time specified by an #expires comment, or negative if no such line. */
-static zic_t comment_leapexpires = -1;
-
/*
* Set the time range of the output to TIMERANGE.
* Return true if successful.
}
if (*hi_end || hi < lo || max_time < lo || hi < min_time)
return false;
- lo_time = lo < min_time ? min_time : lo;
- hi_time = max_time < hi ? max_time : hi;
+ lo_time = max(lo, min_time);
+ hi_time = min(hi, max_time);
return true;
}
+/* Generate redundant time stamps up to OPT. Return true if successful. */
+static bool
+redundant_time_option(char *opt)
+{
+ if (*opt == '@')
+ {
+ intmax_t redundant;
+ char *opt_end;
+
+ redundant = strtoimax(opt + 1, &opt_end, 10);
+ if (opt_end != opt + 1 && !*opt_end)
+ {
+ redundant_time = max(redundant_time, redundant);
+ return true;
+ }
+ }
+ return false;
+}
+
static const char *psxrules;
static const char *lcltime;
static const char *directory;
-static const char *leapsec;
static const char *tzdefault;
+/* True if DIRECTORY ends in '/'. */
+static bool directory_ends_in_slash;
+
/*
* -1 if the TZif output file should be slim, 0 if default, 1 if the
* output should be fat for backward compatibility. ZIC_BLOAT_DEFAULT
j;
bool timerange_given = false;
-#ifndef WIN32
- umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
-#endif
- progname = argv[0];
+ main_argv = argv;
+ progname = argv[0] ? argv[0] : "zic";
if (TYPE_BIT(zic_t) < 64)
{
fprintf(stderr, "%s: %s\n", progname,
if (strcmp(argv[k], "--version") == 0)
{
printf("zic %s\n", PG_VERSION);
- close_file(stdout, NULL, NULL);
+ close_file(stdout, NULL, NULL, NULL);
return EXIT_SUCCESS;
}
else if (strcmp(argv[k], "--help") == 0)
{
usage(stdout, EXIT_SUCCESS);
}
- while ((c = getopt(argc, argv, "b:d:l:L:p:Pr:st:vy:")) != EOF && c != -1)
+ while ((c = getopt(argc, argv, "b:d:Dg:l:L:m:p:Pr:R:st:u:vy:")) != -1)
switch (c)
{
default:
error(_("invalid option: -b '%s'"), optarg);
break;
case 'd':
- if (directory == NULL)
- directory = strdup(optarg);
- else
- {
- fprintf(stderr,
- _("%s: More than one -d option specified\n"),
- progname);
- return EXIT_FAILURE;
- }
+ if (directory)
+ duplicate_options("-d");
+ directory = strdup(optarg);
+ break;
+ case 'D':
+ skip_mkdir = true;
+ break;
+ case 'g':
+
+ /*
+ * This undocumented option is present for compatibility with
+ * FreeBSD 14.
+ */
+ group_option(optarg);
break;
case 'l':
- if (lcltime == NULL)
- lcltime = strdup(optarg);
- else
- {
- fprintf(stderr,
- _("%s: More than one -l option specified\n"),
- progname);
- return EXIT_FAILURE;
- }
+ if (lcltime)
+ duplicate_options("-l");
+ lcltime = strdup(optarg);
+ break;
+ case 'm':
+ if (output_mode != no_mode)
+ duplicate_options("-m");
+ output_mode = mode_option(optarg);
break;
case 'p':
- if (psxrules == NULL)
- psxrules = strdup(optarg);
- else
- {
- fprintf(stderr,
- _("%s: More than one -p option specified\n"),
- progname);
- return EXIT_FAILURE;
- }
+ if (psxrules)
+ duplicate_options("-p");
+ if (strcmp(optarg, "-") != 0)
+ warning(_("-p is obsolete"
+ " and likely ineffective"));
+ psxrules = strdup(optarg);
break;
case 't':
- if (tzdefault != NULL)
+ if (tzdefault)
+ duplicate_options("-t");
+ tzdefault = strdup(optarg);
+ break;
+ case 'u':
{
- fprintf(stderr,
- _("%s: More than one -t option"
- " specified\n"),
- progname);
- return EXIT_FAILURE;
+ char *colon = strchr(optarg, ':');
+
+ if (colon)
+ *colon = '\0';
+ owner_option(optarg);
+ if (colon)
+ group_option(colon + 1);
}
- tzdefault = optarg;
break;
case 'y':
warning(_("-y ignored"));
break;
case 'L':
- if (leapsec == NULL)
- leapsec = strdup(optarg);
- else
- {
- fprintf(stderr,
- _("%s: More than one -L option specified\n"),
- progname);
- return EXIT_FAILURE;
- }
+ if (leapsec)
+ duplicate_options("-L");
+ leapsec = strdup(optarg);
break;
case 'v':
noise = true;
break;
case 'r':
if (timerange_given)
- {
- fprintf(stderr,
- _("%s: More than one -r option specified\n"),
- progname);
- return EXIT_FAILURE;
- }
+ duplicate_options("-r");
if (!timerange_option(optarg))
{
fprintf(stderr,
}
timerange_given = true;
break;
+ case 'R':
+ if (!redundant_time_option(optarg))
+ {
+ fprintf(stderr, _("%s: invalid time: %s\n"),
+ progname, optarg);
+ return EXIT_FAILURE;
+ }
+ break;
case 's':
warning(_("-s ignored"));
break;
}
if (optind == argc - 1 && strcmp(argv[optind], "=") == 0)
usage(stderr, EXIT_FAILURE); /* usage message by request */
+ if (hi_time + (hi_time < ZIC_MAX) < redundant_time)
+ {
+ fprintf(stderr, _("%s: -R time exceeds -r cutoff\n"), progname);
+ return EXIT_FAILURE;
+ }
+ if (redundant_time < lo_time)
+ redundant_time = lo_time;
if (bloat == 0)
{
static char const bloat_default[] = ZIC_BLOAT_DEFAULT;
if (optind < argc && leapsec != NULL)
{
- infile(leapsec);
+ infile(LEAPSEC_FILENUM, leapsec);
adjleap();
}
for (k = optind; k < argc; k++)
- infile(argv[k]);
+ infile(k, argv[k]);
if (errors)
return EXIT_FAILURE;
associate();
+ use_safe_temp_permissions();
change_directory(directory);
+ directory_ends_in_slash = directory[strlen(directory) - 1] == '/';
+ catch_signals();
for (i = 0; i < nzones; i = j)
{
/*
for (j = i + 1; j < nzones && zones[j].z_name == NULL; ++j)
continue;
outzone(&zones[i], j - i);
+ check_for_signal();
}
-
- /*
- * Make links.
- */
- for (i = 0; i < nlinks; ++i)
- {
- eat(links[i].l_filename, links[i].l_linenum);
- dolink(links[i].l_target, links[i].l_linkname, false);
- if (noise)
- for (j = 0; j < nlinks; ++j)
- if (strcmp(links[i].l_linkname,
- links[j].l_target) == 0)
- warning(_("link to link"));
- }
+ make_links();
if (lcltime != NULL)
{
- eat(_("command line"), 1);
+ eat(COMMAND_LINE_FILENUM, 1);
dolink(lcltime, tzdefault, true);
}
if (psxrules != NULL)
{
- eat(_("command line"), 1);
+ eat(COMMAND_LINE_FILENUM, 1);
dolink(psxrules, TZDEFRULES, true);
}
if (warnings && (ferror(stderr) || fclose(stderr) != 0))
{
int len = component_len;
- error(_("file name '%s' contains '%.*s' component"),
- name, len, component);
- return false;
+ error(_("file name '%s' contains '%.*s' component"),
+ name, len, component);
+ return false;
+ }
+ if (noise)
+ {
+ if (0 < component_len && component[0] == '-')
+ warning(_("file name '%s' component contains leading '-'"),
+ name);
+ if (component_len_max < component_len)
+ warning(_("file name '%s' contains overlength component"
+ " '%.*s...'"),
+ name, component_len_max, component);
+ }
+ return true;
+}
+
+static bool
+namecheck(const char *name)
+{
+ char const *cp;
+
+ /* Benign characters in a portable file name. */
+ static char const benign[] =
+ "-/_"
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ /*
+ * Non-control chars in the POSIX portable character set, excluding the
+ * benign characters.
+ */
+ static char const printable_and_not_benign[] =
+ " !\"#$%&'()*+,.0123456789:;<=>?@[\\]^`{|}~";
+
+ char const *component = name;
+
+ for (cp = name; *cp; cp++)
+ {
+ unsigned char c = *cp;
+
+ if (noise && !strchr(benign, c))
+ {
+ warning((strchr(printable_and_not_benign, c)
+ ? _("file name '%s' contains byte '%c'")
+ : _("file name '%s' contains byte '\\%o'")),
+ name, c);
+ }
+ if (c == '/')
+ {
+ if (!componentcheck(name, component, cp))
+ return false;
+ component = cp + 1;
+ }
+ }
+ return componentcheck(name, component, cp);
+}
+
+/* Return a random uint_fast64_t. */
+static uint_fast64_t
+get_rand_u64(void)
+{
+#if HAVE_GETRANDOM
+ static uint_fast64_t entropy_buffer[max(1, 256 / sizeof(uint_fast64_t))];
+ static int nwords;
+
+ if (!nwords)
+ {
+ ssize_t s;
+
+ for (;; check_for_signal())
+ {
+ s = getrandom(entropy_buffer, sizeof entropy_buffer, 0);
+ if (!(s < 0 && errno == EINTR))
+ break;
+ }
+
+ nwords = s < 0 ? -1 : s / sizeof *entropy_buffer;
+ }
+ if (0 < nwords)
+ return entropy_buffer[--nwords];
+#endif
+
+ /*
+ * getrandom didn't work, so fall back on portable code that is not the
+ * best because the seed isn't cryptographically random and 'rand' might
+ * not be cryptographically secure.
+ */
+ {
+ static bool initialized;
+
+ if (!initialized)
+ {
+ srand(time(NULL));
+ initialized = true;
+ }
+ }
+
+ /*
+ * Return a random number if rand() yields a random number and in the
+ * typical case where RAND_MAX is one less than a power of two. In other
+ * cases this code yields a sort-of-random number.
+ */
+ {
+ uint_fast64_t rand_max = RAND_MAX,
+ nrand = rand_max < UINT_FAST64_MAX ? rand_max + 1 : 0,
+ rmod = INT_MAX < UINT_FAST64_MAX ? 0 : UINT_FAST64_MAX / nrand + 1,
+ r = 0,
+ rmax = 0;
+
+ for (;; check_for_signal())
+ {
+ uint_fast64_t rmax1 = rmax;
+
+ if (rmod)
+ {
+ /*
+ * Avoid signed integer overflow on theoretical platforms
+ * where uint_fast64_t promotes to int.
+ */
+ rmax1 %= rmod;
+ r %= rmod;
+ }
+ rmax1 = nrand * rmax1 + rand_max;
+ r = nrand * r + rand();
+ rmax = rmax < rmax1 ? rmax1 : UINT_FAST64_MAX;
+ if (UINT_FAST64_MAX <= rmax)
+ break;
+ }
+
+ return r;
+ }
+}
+
+/*
+ * Generate a randomish name in the same directory as *NAME. If
+ * *NAMEALLOC, put the name into *NAMEALLOC which is assumed to be
+ * that returned by a previous call and is thus already almost set up
+ * and equal to *NAME; otherwise, allocate a new name and put its
+ * address into both *NAMEALLOC and *NAME.
+ */
+static void
+random_dirent(char const **name, char **namealloc)
+{
+ char const *src = *name;
+ char *dst = *namealloc;
+ static char const prefix[] = ".zic";
+ static char const alphabet[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ enum
+ {
+ prefixlen = sizeof prefix - 1, alphabetlen = sizeof alphabet - 1};
+ int suffixlen = 6;
+ char const *lastslash = strrchr(src, '/');
+ ptrdiff_t dirlen = lastslash ? lastslash + 1 - src : 0;
+ int i;
+ uint_fast64_t r;
+ uint_fast64_t base = alphabetlen;
+
+ /* BASE**6 */
+ uint_fast64_t base__6 = base * base * base * base * base * base;
+
+ /*
+ * The largest uintmax_t that is a multiple of BASE**6. Any random
+ * uintmax_t value that is this value or greater, yields a biased
+ * remainder when divided by BASE**6. UNFAIR_MIN equals the mathematical
+ * value of ((UINTMAX_MAX + 1) - (UINTMAX_MAX + 1) % BASE**6) computed
+ * without overflow.
+ */
+ uint_fast64_t unfair_min = -((UINTMAX_MAX % base__6 + 1) % base__6);
+
+ if (!dst)
+ {
+ char *cp = dst = xmalloc(size_sum(dirlen, prefixlen + suffixlen + 1));
+
+ memcpy(cp, src, dirlen);
+ cp += dirlen;
+ memcpy(cp, prefix, prefixlen);
+ cp += prefixlen;
+ cp[suffixlen] = '\0';
+ *name = *namealloc = dst;
+ }
+
+ for (;; check_for_signal())
+ {
+ r = get_rand_u64();
+ if (r < unfair_min)
+ break;
}
- if (noise)
+
+ for (i = 0; i < suffixlen; i++)
{
- if (0 < component_len && component[0] == '-')
- warning(_("file name '%s' component contains leading '-'"),
- name);
- if (component_len_max < component_len)
- warning(_("file name '%s' contains overlength component"
- " '%.*s...'"),
- name, component_len_max, component);
+ dst[dirlen + prefixlen + i] = alphabet[r % alphabetlen];
+ r /= alphabetlen;
}
- return true;
}
-static bool
-namecheck(const char *name)
+/*
+ * For diagnostics the directory, and file name relative to that
+ * directory, respectively. A diagnostic routine can name FILENAME by
+ * outputting diagdir(FILENAME), then diagslash(FILENAME), then FILENAME.
+ */
+static char const *
+diagdir(char const *filename)
{
- char const *cp;
-
- /* Benign characters in a portable file name. */
- static char const benign[] =
- "-/_"
- "abcdefghijklmnopqrstuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ return *filename == '/' ? "" : directory;
+}
+static char const *
+diagslash(char const *filename)
+{
+ return &"/"[*filename == '/' || directory_ends_in_slash];
+}
- /*
- * Non-control chars in the POSIX portable character set, excluding the
- * benign characters.
- */
- static char const printable_and_not_benign[] =
- " !\"#$%&'()*+,.0123456789:;<=>?@[\\]^`{|}~";
+/*
+ * Prepare to write to the file *OUTNAME, using *TEMPNAME to store the
+ * name of the temporary file that will eventually be renamed to
+ * *OUTNAME. Assign the temporary file's name to both *OUTNAME and
+ * *TEMPNAME. If *TEMPNAME is null, allocate the name of any such
+ * temporary file; otherwise, reuse *TEMPNAME's storage, which is
+ * already set up and only needs its trailing suffix updated.
+ */
+static FILE *
+open_outfile(char const **outname, char **tempname)
+{
+ bool dirs_made = false;
- char const *component = name;
+ if (!*tempname)
+ random_dirent(outname, tempname);
- for (cp = name; *cp; cp++)
+ for (;; check_for_signal())
{
- unsigned char c = *cp;
+ int oflags = O_WRONLY | O_BINARY | O_CREAT | O_EXCL;
+ int fd = open(*outname, oflags, creat_perms);
+ int err;
- if (noise && !strchr(benign, c))
+ if (fd < 0)
+ err = errno;
+ else
{
- warning((strchr(printable_and_not_benign, c)
- ? _("file name '%s' contains byte '%c'")
- : _("file name '%s' contains byte '\\%o'")),
- name, c);
+ FILE *fp = fdopen(fd, "wb");
+
+ if (fp)
+ return fp;
+ err = errno;
+ close(fd);
}
- if (c == '/')
+ if (err == ENOENT && !dirs_made)
{
- if (!componentcheck(name, component, cp))
- return false;
- component = cp + 1;
+ mkdirs(*outname, true);
+ dirs_made = true;
+ }
+ else if (err == EEXIST)
+ random_dirent(outname, tempname);
+ else
+ {
+ fprintf(stderr, _("%s: Can't create %s%s%s: %s\n"),
+ progname, diagdir(*outname), diagslash(*outname), *outname,
+ strerror(err));
+ exit(EXIT_FAILURE);
}
}
- return componentcheck(name, component, cp);
}
/*
- * Create symlink contents suitable for symlinking FROM to TO, as a
- * freshly allocated string. FROM should be a relative file name, and
- * is relative to the global variable DIRECTORY. TO can be either
- * relative or absolute.
+ * If TEMPNAME, the result is in the temporary file TEMPNAME even
+ * though the user wanted it in NAME, so rename TEMPNAME to NAME.
+ * Report an error and exit if there is trouble. Also, free TEMPNAME.
+ */
+static void
+rename_dest(char *tempname, char const *name)
+{
+ if (tempname)
+ {
+ if (rename(tempname, name) != 0)
+ {
+ int rename_errno = errno;
+
+ remove(tempname);
+ fprintf(stderr, _("%s: rename to %s%s%s: %s\n"),
+ progname, diagdir(name), diagslash(name), name,
+ strerror(rename_errno));
+ exit(EXIT_FAILURE);
+ }
+ free(tempname);
+ }
+}
+
+/*
+ * Create symlink contents suitable for symlinking TARGET to LINKNAME, as a
+ * freshly allocated string. TARGET should be a relative file name, and
+ * is relative to the global variable DIRECTORY. LINKNAME can be either
+ * relative or absolute. Return a null pointer if the symlink contents
+ * was not computed because LINKNAME is absolute but DIRECTORY is not.
*/
#ifdef HAVE_SYMLINK
static char *
{
size_t i,
taillen,
- dotdotetcsize;
- size_t dir_len = 0,
- dotdots = 0,
- linksize = SIZE_MAX;
+ dir_len = 0,
+ dotdots = 0;
+ ptrdiff_t dotdotetcsize,
+ linksize = INDEX_MAX;
char const *f = target;
char *result = NULL;
{
/* Make F absolute too. */
size_t len = strlen(directory);
- bool needslash = len && directory[len - 1] != '/';
-
- linksize = len + needslash + strlen(target) + 1;
- f = result = emalloc(linksize);
- strcpy(result, directory);
- result[len] = '/';
- strcpy(result + len + needslash, target);
+ bool needs_slash = len && directory[len - 1] != '/';
+ size_t lenslash = len + needs_slash;
+ size_t targetsize = strlen(target) + 1;
+ char *cp;
+
+ if (*directory != '/')
+ return NULL;
+ linksize = size_sum(lenslash, targetsize);
+ f = cp = result = xmalloc(linksize);
+ memcpy(cp, directory, len);
+ cp += len;
+ *cp = '/';
+ memcpy(cp + needs_slash, target, targetsize);
}
for (i = 0; f[i] && f[i] == linkname[i]; i++)
if (f[i] == '/')
for (; linkname[i]; i++)
dotdots += linkname[i] == '/' && linkname[i - 1] != '/';
taillen = strlen(f + dir_len);
- dotdotetcsize = 3 * dotdots + taillen + 1;
+ dotdotetcsize = size_sum(size_product(dotdots, 3), taillen + 1);
if (dotdotetcsize <= linksize)
{
+ char *cp;
+
if (!result)
- result = emalloc(dotdotetcsize);
+ result = xmalloc(dotdotetcsize);
+ cp = result;
for (i = 0; i < dotdots; i++)
- memcpy(result + 3 * i, "../", 3);
- memmove(result + 3 * dotdots, f + dir_len, taillen + 1);
+ {
+ memcpy(cp, "../", 3);
+ cp += 3;
+ }
+ memmove(cp, f + dir_len, taillen + 1);
}
return result;
}
#endif /* HAVE_SYMLINK */
/*
- * Hard link FROM to TO, following any symbolic links.
- * Return 0 if successful, an error number otherwise.
+ * Return true if A and B must have the same parent dir if A and B exist.
+ * Return false if this is not necessarily true (though it might be true).
+ * Keep it simple, and do not inspect the file system.
*/
-static int
-hardlinkerr(char const *target, char const *linkname)
+ATTRIBUTE_PURE_114833
+static bool
+same_parent_dirs(char const *a, char const *b)
{
- int r = linkat(AT_FDCWD, target, AT_FDCWD, linkname, AT_SYMLINK_FOLLOW);
-
- return r == 0 ? 0 : errno;
+ for (; *a == *b; a++, b++)
+ if (!*a)
+ return true;
+ return !(strchr(a, '/') || strchr(b, '/'));
}
static void
dolink(char const *target, char const *linkname, bool staysymlink)
{
- bool remove_only = strcmp(target, "-") == 0;
bool linkdirs_made = false;
int link_errno;
+ char *tempname = NULL;
+ char const *outname = linkname;
+ int targetissym = -2,
+ linknameissym = -2;
- /*
- * We get to be careful here since there's a fair chance of root running
- * us.
- */
- if (!remove_only && itsdir(target))
- {
- fprintf(stderr, _("%s: linking target %s/%s failed: %s\n"),
- progname, directory, target, strerror(EPERM));
- exit(EXIT_FAILURE);
- }
- if (staysymlink)
- staysymlink = itssymlink(linkname);
- if (remove(linkname) == 0)
- linkdirs_made = true;
- else if (errno != ENOENT)
+ if (strcmp(target, "-") == 0)
{
- char const *e = strerror(errno);
+ if (remove(linkname) == 0 || errno == ENOENT || errno == ENOTDIR)
+ return;
+ else
+ {
+ char const *e = strerror(errno);
- fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"),
- progname, directory, linkname, e);
- exit(EXIT_FAILURE);
+ fprintf(stderr, _("%s: Can't remove %s%s%s: %s\n"),
+ progname, diagdir(linkname), diagslash(linkname), linkname,
+ e);
+ exit(EXIT_FAILURE);
+ }
}
- if (remove_only)
- return;
- link_errno = staysymlink ? ENOTSUP : hardlinkerr(target, linkname);
- if (link_errno == ENOENT && !linkdirs_made)
+
+ for (;; check_for_signal())
{
- mkdirs(linkname, true);
- linkdirs_made = true;
- link_errno = hardlinkerr(target, linkname);
+ if (linkat(AT_FDCWD, target, AT_FDCWD, outname, AT_SYMLINK_FOLLOW)
+ == 0)
+ {
+ link_errno = 0;
+ break;
+ }
+ link_errno = errno;
+ /* Linux 2.6.16 and 2.6.17 mishandle AT_SYMLINK_FOLLOW. */
+ if (link_errno == EINVAL)
+ link_errno = ENOTSUP;
+
+ /*
+ * If linkat is not supported, fall back on link(A, B). However, skip
+ * this if A is a relative symlink and A and B might not have the same
+ * parent directory. On some platforms link(A, B) does not follow a
+ * symlink A, and if A is relative it might misbehave elsewhere.
+ */
+ if (link_errno == ENOTSUP
+ && (same_parent_dirs(target, outname)
+ || 0 <= itssymlink(target, &targetissym)))
+ {
+ if (link(target, outname) == 0)
+ {
+ link_errno = 0;
+ break;
+ }
+ link_errno = errno;
+ }
+ if (link_errno == EXDEV || link_errno == ENOTSUP)
+ break;
+
+ if (link_errno == EEXIST)
+ {
+ staysymlink &= !tempname;
+ random_dirent(&outname, &tempname);
+ if (staysymlink && itssymlink(linkname, &linknameissym))
+ break;
+ }
+ else if (link_errno == ENOENT && !linkdirs_made)
+ {
+ mkdirs(linkname, true);
+ linkdirs_made = true;
+ }
+ else
+ {
+ fprintf(stderr, _("%s: Can't link %s%s%s to %s%s%s: %s\n"),
+ progname, diagdir(target), diagslash(target), target,
+ diagdir(outname), diagslash(outname), outname,
+ strerror(link_errno));
+ exit(EXIT_FAILURE);
+ }
}
if (link_errno != 0)
{
bool absolute = *target == '/';
char *linkalloc = absolute ? NULL : relname(target, linkname);
char const *contents = absolute ? target : linkalloc;
- int symlink_errno = symlink(contents, linkname) == 0 ? 0 : errno;
+ int symlink_errno = -1;
- if (!linkdirs_made
- && (symlink_errno == ENOENT || symlink_errno == ENOTSUP))
+ if (contents)
{
- mkdirs(linkname, true);
- if (symlink_errno == ENOENT)
- symlink_errno = symlink(contents, linkname) == 0 ? 0 : errno;
+ for (;; check_for_signal())
+ {
+ if (symlink(contents, outname) == 0)
+ {
+ symlink_errno = 0;
+ break;
+ }
+ symlink_errno = errno;
+ if (symlink_errno == EEXIST)
+ random_dirent(&outname, &tempname);
+ else if (symlink_errno == ENOENT && !linkdirs_made)
+ {
+ mkdirs(linkname, true);
+ linkdirs_made = true;
+ }
+ else
+ break;
+ }
}
free(linkalloc);
if (symlink_errno == 0)
{
- if (link_errno != ENOTSUP)
+ if (link_errno != ENOTSUP && link_errno != EEXIST)
warning(_("symbolic link used because hard link failed: %s"),
strerror(link_errno));
}
{
char const *e = strerror(errno);
- fprintf(stderr, _("%s: Can't read %s/%s: %s\n"),
- progname, directory, target, e);
- exit(EXIT_FAILURE);
- }
- tp = fopen(linkname, "wb");
- if (!tp)
- {
- char const *e = strerror(errno);
-
- fprintf(stderr, _("%s: Can't create %s/%s: %s\n"),
- progname, directory, linkname, e);
+ fprintf(stderr, _("%s: Can't read %s%s%s: %s\n"),
+ progname, diagdir(target), diagslash(target), target, e);
exit(EXIT_FAILURE);
}
- while ((c = getc(fp)) != EOF)
+ tp = open_outfile(&outname, &tempname);
+ for (; (c = getc(fp)) != EOF; check_for_signal())
putc(c, tp);
- close_file(fp, directory, target);
- close_file(tp, directory, linkname);
+ close_file(tp, directory, linkname, tempname);
+ close_file(fp, directory, target, NULL);
if (link_errno != ENOTSUP)
warning(_("copy used because hard link failed: %s"),
strerror(link_errno));
#ifdef HAVE_SYMLINK
+ else if (symlink_errno < 0)
+ warning(_("copy used because symbolic link not obvious"));
else if (symlink_errno != ENOTSUP)
warning(_("copy used because symbolic link failed: %s"),
strerror(symlink_errno));
#endif
}
}
+ rename_dest(tempname, linkname);
}
-/* Return true if NAME is a directory. */
-static bool
-itsdir(char const *name)
+/*
+ * Return 1 if NAME is an absolute symbolic link, -1 if it is relative,
+ * 0 if it is not a symbolic link. If *CACHE is not -2, it is the
+ * cached result of a previous call to this function with the same NAME.
+ */
+static int
+itssymlink(char const *name, int *cache)
{
- struct stat st;
- int res = stat(name, &st);
-#ifdef S_ISDIR
- if (res == 0)
- return S_ISDIR(st.st_mode) != 0;
-#endif
- if (res == 0 || errno == EOVERFLOW)
+#ifdef HAVE_SYMLINK
+ if (*cache == -2)
{
- size_t n = strlen(name);
- char *nameslashdot = emalloc(n + 3);
- bool dir;
+ char c = '\0';
- memcpy(nameslashdot, name, n);
- strcpy(&nameslashdot[n], &"/."[!(n && name[n - 1] != '/')]);
- dir = stat(nameslashdot, &st) == 0 || errno == EOVERFLOW;
- free(nameslashdot);
- return dir;
+ *cache = readlink(name, &c, 1) < 0 ? 0 : c == '/' ? 1 : -1;
}
- return false;
-}
-
-/* Return true if NAME is a symbolic link. */
-static bool
-itssymlink(char const *name)
-{
-#ifdef HAVE_SYMLINK
- char c;
-
- return 0 <= readlink(name, &c, 1);
+ return *cache;
#else
return false;
#endif
static int
rcomp(const void *cp1, const void *cp2)
{
- return strcmp(((const struct rule *) cp1)->r_name,
- ((const struct rule *) cp2)->r_name);
+ struct rule const *r1 = cp1,
+ *r2 = cp2;
+
+ return strcmp(r1->r_name, r2->r_name);
}
static void
base,
out;
- if (nrules != 0)
+ if (1 < nrules)
{
qsort(rules, nrules, sizeof *rules, rcomp);
for (i = 0; i < nrules - 1; ++i)
if (strcmp(rules[i].r_name,
rules[i + 1].r_name) != 0)
continue;
- if (strcmp(rules[i].r_filename,
- rules[i + 1].r_filename) == 0)
+ if (rules[i].r_filenum == rules[i + 1].r_filenum)
continue;
- eat(rules[i].r_filename, rules[i].r_linenum);
+ eat(rules[i].r_filenum, rules[i].r_linenum);
warning(_("same rule name in multiple files"));
- eat(rules[i + 1].r_filename, rules[i + 1].r_linenum);
+ eat(rules[i + 1].r_filenum, rules[i + 1].r_linenum);
warning(_("same rule name in multiple files"));
for (j = i + 2; j < nrules; ++j)
{
if (strcmp(rules[i].r_name,
rules[j].r_name) != 0)
break;
- if (strcmp(rules[i].r_filename,
- rules[j].r_filename) == 0)
+ if (rules[i].r_filenum == rules[j].r_filenum)
continue;
- if (strcmp(rules[i + 1].r_filename,
- rules[j].r_filename) == 0)
+ if (rules[i + 1].r_filenum
+ == rules[j].r_filenum)
continue;
break;
}
/*
* Maybe we have a local standard time offset.
*/
- eat(zp->z_filename, zp->z_linenum);
+ eat(zp->z_filenum, zp->z_linenum);
zp->z_save = getsave(zp->z_rule, &zp->z_isdst);
/*
exit(EXIT_FAILURE);
}
+/*
+ * Read a text line from FP into BUF, which is of size BUFSIZE.
+ * Terminate it with a NUL byte instead of a newline.
+ * Return true if successful, false if EOF.
+ * On error, report the error and exit.
+ */
+static bool
+inputline(FILE *fp, char *buf, ptrdiff_t bufsize)
+{
+ ptrdiff_t linelen = 0,
+ ch;
+
+ for (; (ch = getc(fp)) != '\n'; check_for_signal())
+ {
+ if (ch < 0)
+ {
+ if (ferror(fp))
+ {
+ error(_("input error"));
+ exit(EXIT_FAILURE);
+ }
+ if (linelen == 0)
+ return false;
+ error(_("unterminated line"));
+ exit(EXIT_FAILURE);
+ }
+ if (!ch)
+ {
+ error(_("NUL input byte"));
+ exit(EXIT_FAILURE);
+ }
+ buf[linelen++] = ch;
+ if (linelen == bufsize)
+ {
+ error(_("line too long"));
+ exit(EXIT_FAILURE);
+ }
+ }
+ buf[linelen] = '\0';
+ return true;
+}
+
static void
-infile(const char *name)
+infile(int fnum, char const *name)
{
FILE *fp;
- char **fields;
- char *cp;
const struct lookup *lp;
- int nfields;
bool wantcont;
lineno_t num;
- char buf[BUFSIZ];
if (strcmp(name, "-") == 0)
{
- name = _("standard input");
fp = stdin;
}
else if ((fp = fopen(name, "r")) == NULL)
wantcont = false;
for (num = 1;; ++num)
{
- eat(name, num);
- if (fgets(buf, sizeof buf, fp) != buf)
- break;
- cp = strchr(buf, '\n');
- if (cp == NULL)
- {
- error(_("line too long"));
- exit(EXIT_FAILURE);
- }
- *cp = '\0';
- fields = getfields(buf);
- nfields = 0;
- while (fields[nfields] != NULL)
+ enum
{
- static char nada;
-
- if (strcmp(fields[nfields], "-") == 0)
- fields[nfields] = &nada;
- ++nfields;
- }
+ bufsize_bound
+ = (min(INT_MAX, INDEX_MAX) / FORMAT_LEN_GROWTH_BOUND)};
+ char buf[min(_POSIX2_LINE_MAX, bufsize_bound)];
+ int nfields;
+ char *fields[MAX_FIELDS];
+
+ eat(fnum, num);
+ if (!inputline(fp, buf, sizeof buf))
+ break;
+ nfields = getfields(buf, fields,
+ sizeof fields / sizeof *fields);
if (nfields == 0)
{
- if (name == leapsec && *buf == '#')
- sscanf(buf, "#expires %" SCNdZIC, &comment_leapexpires);
+ /* nothing to do */
}
else if (wantcont)
{
else
{
struct lookup const *line_codes
- = name == leapsec ? leap_line_codes : zi_line_codes;
+ = fnum < 0 ? leap_line_codes : zi_line_codes;
lp = byword(fields[0], line_codes);
if (lp == NULL)
inexpires(fields, nfields);
wantcont = false;
break;
- default: /* "cannot happen" */
- fprintf(stderr,
- _("%s: panic: Invalid l_value %d\n"),
- progname, lp->l_value);
- exit(EXIT_FAILURE);
+ default:
+ unreachable();
}
}
- free(fields);
+ check_for_signal();
}
- close_file(fp, NULL, filename);
+ close_file(fp, NULL, filename(fnum), NULL);
if (wantcont)
error(_("expected continuation line not found"));
}
ok = false;
break;
case 8:
- ok = '0' <= xr && xr <= '9';
- pg_fallthrough;
+ ok = is_digit(xr);
+ ATTRIBUTE_FALLTHROUGH;
case 7:
ok &= ssx == '.';
if (ok && noise)
warning(_("fractional seconds rejected by"
" pre-2018 versions of zic"));
- pg_fallthrough;
+ ATTRIBUTE_FALLTHROUGH;
case 5:
ok &= mmx == ':';
- pg_fallthrough;
+ ATTRIBUTE_FALLTHROUGH;
case 3:
ok &= hhx == ':';
- pg_fallthrough;
+ ATTRIBUTE_FALLTHROUGH;
case 1:
break;
}
error("%s", errstring);
return 0;
}
- if (ZIC_MAX / SECSPERHOUR < hh)
- {
- error(_("time overflow"));
- return 0;
- }
ss += 5 + ((ss ^ 1) & (xr == '0')) <= tenths; /* Round to even. */
if (noise && (hh > HOURSPERDAY ||
(hh == HOURSPERDAY && (mm != 0 || ss != 0))))
warning(_("values over 24 hours not handled by pre-2007 versions of zic"));
- return oadd(sign * hh * SECSPERHOUR,
+ return oadd(omul(hh, sign * SECSPERHOUR),
sign * (mm * SECSPERMIN + ss));
}
{
int dst = -1;
zic_t save;
- size_t fieldlen = strlen(field);
+ ptrdiff_t fieldlen = strlen(field);
if (fieldlen != 0)
{
static void
inrule(char **fields, int nfields)
{
- static struct rule r;
+ struct rule r;
if (nfields != RULE_FIELDS)
{
error(_("Invalid rule name \"%s\""), fields[RF_NAME]);
return;
}
- r.r_filename = filename;
+ r.r_filenum = filenum;
r.r_linenum = linenum;
r.r_save = getsave(fields[RF_SAVE], &r.r_isdst);
- rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR], fields[RF_COMMAND],
- fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]);
- r.r_name = ecpyalloc(fields[RF_NAME]);
- r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]);
+ if (!rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR],
+ fields[RF_COMMAND], fields[RF_MONTH], fields[RF_DAY],
+ fields[RF_TOD]))
+ return;
+ r.r_name = xstrdup(fields[RF_NAME]);
+ r.r_abbrvar = xstrdup(fields[RF_ABBRVAR]);
if (max_abbrvar_len < strlen(r.r_abbrvar))
max_abbrvar_len = strlen(r.r_abbrvar);
rules = growalloc(rules, sizeof *rules, nrules, &nrules_alloc);
}
if (lcltime != NULL && strcmp(fields[ZF_NAME], tzdefault) == 0)
{
- error(
- _("\"Zone %s\" line and -l option are mutually exclusive"),
+ error(_("\"Zone %s\" line and -l option are mutually exclusive"),
tzdefault);
return false;
}
if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL)
{
- error(
- _("\"Zone %s\" line and -p option are mutually exclusive"),
+ error(_("\"Zone %s\" line and -p option are mutually exclusive"),
TZDEFRULES);
return false;
}
error(_("duplicate zone name %s"
" (file \"%s\", line %" PRIdMAX ")"),
fields[ZF_NAME],
- zones[i].z_filename,
+ filename(zones[i].z_filenum),
zones[i].z_linenum);
return false;
}
{
char *cp;
char *cp1;
- static struct zone z;
+ struct zone z;
+ int format_len;
int i_stdoff,
i_rule,
i_format;
i_untilmonth = ZFC_TILMONTH;
i_untilday = ZFC_TILDAY;
i_untiltime = ZFC_TILTIME;
- z.z_name = NULL;
}
else if (!namecheck(fields[ZF_NAME]))
return false;
i_untilmonth = ZF_TILMONTH;
i_untilday = ZF_TILDAY;
i_untiltime = ZF_TILTIME;
- z.z_name = ecpyalloc(fields[ZF_NAME]);
}
- z.z_filename = filename;
+ z.z_filenum = filenum;
z.z_linenum = linenum;
z.z_stdoff = gethms(fields[i_stdoff], _("invalid UT offset"));
- if ((cp = strchr(fields[i_format], '%')) != NULL)
+ cp = strchr(fields[i_format], '%');
+ if (cp)
{
if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%')
|| strchr(fields[i_format], '/'))
return false;
}
}
- z.z_rule = ecpyalloc(fields[i_rule]);
- z.z_format = cp1 = ecpyalloc(fields[i_format]);
z.z_format_specifier = cp ? *cp : '\0';
- if (z.z_format_specifier == 'z')
- {
- if (noise)
- warning(_("format '%s' not handled by pre-2015 versions of zic"),
- z.z_format);
- cp1[cp - fields[i_format]] = 's';
- }
- if (max_format_len < strlen(z.z_format))
- max_format_len = strlen(z.z_format);
+ format_len = strlen(fields[i_format]);
+ if (max_format_len < format_len)
+ max_format_len = format_len;
hasuntil = nfields > i_untilyear;
if (hasuntil)
{
- z.z_untilrule.r_filename = filename;
+ z.z_untilrule.r_filenum = filenum;
z.z_untilrule.r_linenum = linenum;
- rulesub(&z.z_untilrule,
- fields[i_untilyear],
- "only",
- "",
- (nfields > i_untilmonth) ?
- fields[i_untilmonth] : "Jan",
- (nfields > i_untilday) ? fields[i_untilday] : "1",
- (nfields > i_untiltime) ? fields[i_untiltime] : "0");
+ if (!rulesub(
+ &z.z_untilrule,
+ fields[i_untilyear],
+ "only",
+ "",
+ (nfields > i_untilmonth) ?
+ fields[i_untilmonth] : "Jan",
+ (nfields > i_untilday) ? fields[i_untilday] : "1",
+ (nfields > i_untiltime) ? fields[i_untiltime] : "0"))
+ return false;
z.z_untiltime = rpytime(&z.z_untilrule,
z.z_untilrule.r_loyear);
if (iscont && nzones > 0 &&
- z.z_untiltime > min_time &&
- z.z_untiltime < max_time &&
- zones[nzones - 1].z_untiltime > min_time &&
- zones[nzones - 1].z_untiltime < max_time &&
zones[nzones - 1].z_untiltime >= z.z_untiltime)
{
- error(_("Zone continuation line end time is not after end time of previous line"));
+ error(_("Zone continuation line end time is"
+ " not after end time of previous line"));
return false;
}
}
+ z.z_name = iscont ? NULL : xstrdup(fields[ZF_NAME]);
+ z.z_rule = xstrdup(fields[i_rule]);
+ z.z_format = cp1 = xstrdup(fields[i_format]);
+ if (z.z_format_specifier == 'z')
+ {
+ cp1[cp - fields[i_format]] = 's';
+ if (noise)
+ warning(_("format '%s' not handled by pre-2015 versions of zic"),
+ fields[i_format]);
+ }
zones = growalloc(zones, sizeof *zones, nzones, &nzones_alloc);
zones[nzones++] = z;
}
static zic_t
-getleapdatetime(char **fields, int nfields, bool expire_line)
+getleapdatetime(char **fields, bool expire_line)
{
const char *cp;
const struct lookup *lp;
return -1;
}
dayoff = oadd(dayoff, day - 1);
- if (dayoff < min_time / SECSPERDAY)
- {
- error(_("time too small"));
- return -1;
- }
- if (dayoff > max_time / SECSPERDAY)
- {
- error(_("time too large"));
- return -1;
- }
- t = dayoff * SECSPERDAY;
+ t = omul(dayoff, SECSPERDAY);
tod = gethms(fields[LP_TIME], _("invalid time of day"));
t = tadd(t, tod);
if (t < 0)
error(_("wrong number of fields on Leap line"));
else
{
- zic_t t = getleapdatetime(fields, nfields, false);
+ zic_t t = getleapdatetime(fields, false);
if (0 <= t)
{
else if (0 <= leapexpires)
error(_("multiple Expires lines"));
else
- leapexpires = getleapdatetime(fields, nfields, true);
+ leapexpires = getleapdatetime(fields, true);
}
static void
}
if (!namecheck(fields[LF_LINKNAME]))
return;
- l.l_filename = filename;
+ l.l_filenum = filenum;
l.l_linenum = linenum;
- l.l_target = ecpyalloc(fields[LF_TARGET]);
- l.l_linkname = ecpyalloc(fields[LF_LINKNAME]);
+ l.l_target = xstrdup(fields[LF_TARGET]);
+ l.l_linkname = xstrdup(fields[LF_LINKNAME]);
links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc);
links[nlinks++] = l;
}
-static void
+static bool
rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
const char *typep, const char *monthp, const char *dayp,
const char *timep)
if ((lp = byword(monthp, mon_names)) == NULL)
{
error(_("invalid month name"));
- return;
+ return false;
}
rp->r_month = lp->l_value;
rp->r_todisstd = false;
rp->r_todisut = false;
- dp = ecpyalloc(timep);
+ dp = xstrdup(timep);
if (*dp != '\0')
{
ep = dp + strlen(dp) - 1;
*/
cp = loyearp;
lp = byword(cp, begin_years);
- rp->r_lowasnum = lp == NULL;
- if (!rp->r_lowasnum)
+ if (lp)
switch (lp->l_value)
{
case YR_MINIMUM:
- rp->r_loyear = ZIC_MIN;
- break;
- case YR_MAXIMUM:
- rp->r_loyear = ZIC_MAX;
+ warning(_("FROM year \"%s\" is obsolete;"
+ " treated as %d"),
+ cp, YEAR_32BIT_MIN - 1);
+ rp->r_loyear = YEAR_32BIT_MIN - 1;
break;
- default: /* "cannot happen" */
- fprintf(stderr,
- _("%s: panic: Invalid l_value %d\n"),
- progname, lp->l_value);
- exit(EXIT_FAILURE);
+ default:
+ unreachable();
}
else if (sscanf(cp, "%" SCNdZIC "%c", &rp->r_loyear, &xs) != 1)
{
error(_("invalid starting year"));
- return;
+ return false;
}
cp = hiyearp;
lp = byword(cp, end_years);
if (!rp->r_hiwasnum)
switch (lp->l_value)
{
- case YR_MINIMUM:
- rp->r_hiyear = ZIC_MIN;
- break;
case YR_MAXIMUM:
rp->r_hiyear = ZIC_MAX;
break;
case YR_ONLY:
rp->r_hiyear = rp->r_loyear;
break;
- default: /* "cannot happen" */
- fprintf(stderr,
- _("%s: panic: Invalid l_value %d\n"),
- progname, lp->l_value);
- exit(EXIT_FAILURE);
+ default:
+ unreachable();
}
else if (sscanf(cp, "%" SCNdZIC "%c", &rp->r_hiyear, &xs) != 1)
{
error(_("invalid ending year"));
- return;
+ return false;
}
if (rp->r_loyear > rp->r_hiyear)
{
error(_("starting year greater than ending year"));
- return;
+ return false;
}
if (*typep != '\0')
{
error(_("year type \"%s\" is unsupported; use \"-\" instead"),
typep);
- return;
+ return false;
}
/*
* Day work. Accept things such as: 1 lastSunday last-Sunday
* (undocumented; warn about this) Sun<=20 Sun>=7
*/
- dp = ecpyalloc(dayp);
+ dp = xstrdup(dayp);
if ((lp = byword(dp, lasts)) != NULL)
{
rp->r_dycode = DC_DOWLEQ;
}
else
{
- if ((ep = strchr(dp, '<')) != NULL)
+ ep = strchr(dp, '<');
+ if (ep)
rp->r_dycode = DC_DOWLEQ;
- else if ((ep = strchr(dp, '>')) != NULL)
- rp->r_dycode = DC_DOWGEQ;
else
{
- ep = dp;
- rp->r_dycode = DC_DOM;
+ ep = strchr(dp, '>');
+ if (ep)
+ rp->r_dycode = DC_DOWGEQ;
+ else
+ {
+ ep = dp;
+ rp->r_dycode = DC_DOM;
+ }
}
if (rp->r_dycode != DC_DOM)
{
{
error(_("invalid day of month"));
free(dp);
- return;
+ return false;
}
if ((lp = byword(dp, wday_names)) == NULL)
{
error(_("invalid weekday name"));
free(dp);
- return;
+ return false;
}
rp->r_wday = lp->l_value;
}
{
error(_("invalid day of month"));
free(dp);
- return;
+ return false;
}
}
free(dp);
+ return true;
}
static void
-convert(const int_fast32_t val, char *const buf)
+convert(uint_fast32_t val, char *buf)
{
int i;
int shift;
unsigned char *const b = (unsigned char *) buf;
for (i = 0, shift = 24; i < 4; ++i, shift -= 8)
- b[i] = val >> shift;
+ b[i] = (val >> shift) & 0xff;
}
static void
-convert64(const zic_t val, char *const buf)
+convert64(uint_fast64_t val, char *buf)
{
int i;
int shift;
unsigned char *const b = (unsigned char *) buf;
for (i = 0, shift = 56; i < 8; ++i, shift -= 8)
- b[i] = val >> shift;
+ b[i] = (val >> shift) & 0xff;
}
static void
-puttzcode(const int_fast32_t val, FILE *const fp)
+puttzcode(zic_t val, FILE *fp)
{
char buf[4];
static int
atcomp(const void *avp, const void *bvp)
{
- const zic_t a = ((const struct attype *) avp)->at;
- const zic_t b = ((const struct attype *) bvp)->at;
+ struct attype const *ap = avp,
+ *bp = bvp;
+ zic_t a = ap->at,
+ b = bp->at;
- return (a < b) ? -1 : (a > b);
+ return a < b ? -1 : a > b;
}
struct timerange
int defaulttype;
ptrdiff_t base,
count;
- int leapbase,
+ ptrdiff_t leapbase,
leapcount;
+ bool leapexpiry;
};
static struct timerange
limitrange(struct timerange r, zic_t lo, zic_t hi,
zic_t const *ats, unsigned char const *types)
{
+ /* Omit ordinary transitions < LO. */
while (0 < r.count && ats[r.base] < lo)
{
r.defaulttype = types[r.base];
r.count--;
r.base++;
}
- while (0 < r.leapcount && trans[r.leapbase] < lo)
+
+ /*
+ * Omit as many initial leap seconds as possible, such that the first leap
+ * second in the truncated list is <= LO, and is a positive leap second if
+ * and only if it has a positive correction. This supports common TZif
+ * readers that assume that the first leap second is positive if and only
+ * if its correction is positive.
+ */
+ while (1 < r.leapcount && leap[r.leapbase + 1].trans <= lo)
{
r.leapcount--;
r.leapbase++;
}
+ while (0 < r.leapbase
+ && ((leap[r.leapbase - 1].corr < leap[r.leapbase].corr)
+ != (0 < leap[r.leapbase].corr)))
+ {
+ r.leapcount++;
+ r.leapbase--;
+ }
- if (hi < ZIC_MAX)
+
+ /* Omit ordinary and leap second transitions greater than HI + 1. */
+ if (hi < max_time)
{
while (0 < r.count && hi + 1 < ats[r.base + r.count - 1])
r.count--;
- while (0 < r.leapcount && hi + 1 < trans[r.leapbase + r.leapcount - 1])
+ while (0 < r.leapcount && hi + 1 < leap[r.leapbase + r.leapcount - 1].trans)
r.leapcount--;
}
+ /* Determine whether to append an expiration to the leap second table. */
+ r.leapexpiry = 0 <= leapexpires && leapexpires - 1 <= hi;
+
return r;
}
ptrdiff_t i,
j;
int pass;
- static const struct tzhead tzh0;
- static struct tzhead tzh;
- bool dir_checked = false;
- zic_t one = 1;
- zic_t y2038_boundary = one << 31;
- ptrdiff_t nats = timecnt + WORK_AROUND_QTBUG_53071;
+ char *tempname = NULL;
+ char const *outname = name;
/*
* Allocate the ATS and TYPES arrays via a single malloc, as this is a bit
- * faster.
+ * faster. Do not malloc(0) if !timecnt, as that might return NULL even
+ * on success.
*/
- zic_t *ats = emalloc(align_to(size_product(nats, sizeof *ats + 1),
- _Alignof(zic_t)));
- void *typesptr = ats + nats;
+ zic_t *ats = xmalloc(align_to(size_product(timecnt + !timecnt,
+ sizeof *ats + 1),
+ alignof(zic_t)));
+ void *typesptr = ats + timecnt;
unsigned char *types = typesptr;
- struct timerange rangeall,
- range32,
- range64;
+ struct timerange rangeall = {0}, range32, range64;
/*
* Sort.
qsort(attypes, timecnt, sizeof *attypes, atcomp);
/*
- * Optimize.
+ * Optimize and skip unwanted transitions.
*/
{
ptrdiff_t fromi,
fromi = 0;
for (; fromi < timecnt; ++fromi)
{
- if (toi != 0
- && ((attypes[fromi].at
- + utoffs[attypes[toi - 1].type])
- <= (attypes[toi - 1].at
- + utoffs[toi == 1 ? 0
- : attypes[toi - 2].type])))
+ if (toi != 0)
{
- attypes[toi - 1].type =
- attypes[fromi].type;
- continue;
+ /*
+ * Skip the previous transition if it is unwanted because its
+ * local time is not earlier. The UT offset additions can't
+ * overflow because of how the times were calculated.
+ */
+ unsigned char type_2 =
+ toi == 1 ? 0 : attypes[toi - 2].type;
+
+ if ((attypes[fromi].at
+ + utoffs[attypes[toi - 1].type])
+ <= attypes[toi - 1].at + utoffs[type_2])
+ {
+ if (attypes[fromi].type == type_2)
+ toi--;
+ else
+ attypes[toi - 1].type =
+ attypes[fromi].type;
+ continue;
+ }
}
+
+ /*
+ * Use a transition if it is the first one, or if it cannot be
+ * merged for other reasons, or if it transitions to different
+ * timekeeping.
+ */
if (toi == 0
|| attypes[fromi].dontmerge
|| (utoffs[attypes[toi - 1].type]
timecnt = toi;
}
- if (noise && timecnt > 1200)
+ if (noise)
{
- if (timecnt > TZ_MAX_TIMES)
- warning(_("reference clients mishandle"
- " more than %d transition times"),
- TZ_MAX_TIMES);
- else
- warning(_("pre-2014 clients may mishandle"
- " more than 1200 transition times"));
+ if (1200 < timecnt)
+ {
+ if (TZ_MAX_TIMES < timecnt)
+ warning(_("reference clients mishandle"
+ " more than %d transition times"),
+ TZ_MAX_TIMES);
+ else
+ warning(_("pre-2014 clients may mishandle"
+ " more than 1200 transition times"));
+ }
+ if (TZ_MAX_LEAPS < leapcnt)
+ warning(_("reference clients mishandle more than %d leap seconds"),
+ TZ_MAX_LEAPS);
}
/*
{
j = leapcnt;
while (--j >= 0)
- if (ats[i] > trans[j] - corr[j])
+ if (leap[j].trans - leap[j].corr < ats[i])
{
- ats[i] = tadd(ats[i], corr[j]);
+ ats[i] = tadd(ats[i], leap[j].corr);
break;
}
}
- /*
- * Work around QTBUG-53071 for timestamps less than y2038_boundary - 1, by
- * inserting a no-op transition at time y2038_boundary - 1. This works
- * only for timestamps before the boundary, which should be good enough in
- * practice as QTBUG-53071 should be long-dead by 2038. Do this after
- * correcting for leap seconds, as the idea is to insert a transition just
- * before 32-bit pg_time_t rolls around, and this occurs at a slightly
- * different moment if transitions are leap-second corrected.
- */
- if (WORK_AROUND_QTBUG_53071 && timecnt != 0 && want_bloat()
- && ats[timecnt - 1] < y2038_boundary - 1 && strchr(string, '<'))
- {
- ats[timecnt] = y2038_boundary - 1;
- types[timecnt] = types[timecnt - 1];
- timecnt++;
- }
-
rangeall.defaulttype = defaulttype;
- rangeall.base = rangeall.leapbase = 0;
rangeall.count = timecnt;
rangeall.leapcount = leapcnt;
- range64 = limitrange(rangeall, lo_time, hi_time, ats, types);
- range32 = limitrange(range64, INT32_MIN, INT32_MAX, ats, types);
+ range64 = limitrange(rangeall, lo_time,
+ max(hi_time,
+ redundant_time - (ZIC_MIN < redundant_time)),
+ ats, types);
+ range32 = limitrange(range64, ZIC32_MIN, ZIC32_MAX, ats, types);
/*
- * Remove old file, if any, to snap links.
+ * TZif version 4 is needed if a no-op transition is appended to indicate
+ * the expiration of the leap second table, or if the first leap second
+ * transition is not to a +1 or -1 correction.
*/
- if (remove(name) == 0)
- dir_checked = true;
- else if (errno != ENOENT)
- {
- const char *e = strerror(errno);
-
- fprintf(stderr, _("%s: Cannot remove %s/%s: %s\n"),
- progname, directory, name, e);
- exit(EXIT_FAILURE);
- }
- fp = fopen(name, "wb");
- if (!fp)
+ for (pass = 1; pass <= 2; pass++)
{
- int fopen_errno = errno;
+ struct timerange const *r = pass == 1 ? &range32 : &range64;
- if (fopen_errno == ENOENT && !dir_checked)
+ if (pass == 1 && !want_bloat())
+ continue;
+ if (r->leapexpiry)
{
- mkdirs(name, true);
- fp = fopen(name, "wb");
- fopen_errno = errno;
+ if (noise)
+ warning(_("%s: pre-2021b clients may mishandle"
+ " leap second expiry"),
+ name);
+ version = '4';
}
- if (!fp)
+ if (0 < r->leapcount
+ && leap[r->leapbase].corr != 1 && leap[r->leapbase].corr != -1)
{
- fprintf(stderr, _("%s: Cannot create %s/%s: %s\n"),
- progname, directory, name, strerror(fopen_errno));
- exit(EXIT_FAILURE);
+ if (noise)
+ warning(_("%s: pre-2021b clients may mishandle"
+ " leap second table truncation"),
+ name);
+ version = '4';
}
+ if (version == '4')
+ break;
}
+
+ fp = open_outfile(&outname, &tempname);
+
for (pass = 1; pass <= 2; ++pass)
{
ptrdiff_t thistimei,
thistimecnt,
thistimelim;
- int thisleapi,
+ ptrdiff_t thisleapi,
thisleapcnt,
thisleaplim;
- int currenttype,
+ struct tzhead tzh;
+ int pretranstype = -1,
thisdefaulttype;
bool locut,
- hicut;
- zic_t lo;
+ hicut,
+ thisleapexpiry;
+ zic_t lo,
+ thismin,
+ thismax;
int old0;
char omittype[TZ_MAX_TYPES];
int typemap[TZ_MAX_TYPES];
if (pass == 1)
{
- /*
- * Arguably the default time type in the 32-bit data should be
- * range32.defaulttype, which is suited for timestamps just before
- * INT32_MIN. However, zic traditionally used the time type of
- * the indefinite past instead. Internet RFC 8532 says readers
- * should ignore 32-bit data, so this discrepancy matters only to
- * obsolete readers where the traditional type might be more
- * appropriate even if it's "wrong". So, use the historical zic
- * value, unless -r specifies a low cutoff that excludes some
- * 32-bit timestamps.
- */
- thisdefaulttype = (lo_time <= INT32_MIN
- ? range64.defaulttype
- : range32.defaulttype);
-
+ thisdefaulttype = range32.defaulttype;
thistimei = range32.base;
thistimecnt = range32.count;
toomanytimes = thistimecnt >> 31 >> 1 != 0;
thisleapi = range32.leapbase;
thisleapcnt = range32.leapcount;
- locut = INT32_MIN < lo_time;
- hicut = hi_time < INT32_MAX;
+ thisleapexpiry = range32.leapexpiry;
+ thismin = ZIC32_MIN;
+ thismax = ZIC32_MAX;
}
else
{
toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0;
thisleapi = range64.leapbase;
thisleapcnt = range64.leapcount;
- locut = min_time < lo_time;
- hicut = hi_time < max_time;
+ thisleapexpiry = range64.leapexpiry;
+ thismin = min_time;
+ thismax = max_time;
}
if (toomanytimes)
error(_("too many transition times"));
+ locut = thismin < lo_time && lo_time <= thismax;
+ hicut = thismin <= hi_time && hi_time < thismax;
+ thistimelim = thistimei + thistimecnt;
+ memset(omittype, true, typecnt);
+
/*
- * Keep the last too-low transition if no transition is exactly at LO.
- * The kept transition will be output as a LO "transition"; see
- * "Output a LO_TIME transition" below. This is needed when the
- * output is truncated at the start, and is also useful when catering
- * to buggy 32-bit clients that do not use time type 0 for timestamps
- * before the first transition.
+ * Determine whether to output a transition before the first
+ * transition in range. This is needed when the output is truncated
+ * at the start, and is also useful when catering to buggy 32-bit
+ * clients that do not use time type 0 for timestamps before the first
+ * transition.
*/
- if (0 < thistimei && ats[thistimei] != lo_time)
+ if ((locut || (pass == 1 && thistimei))
+ && !(thistimecnt && ats[thistimei] == lo_time))
{
- thistimei--;
- thistimecnt++;
- locut = false;
+ pretranstype = thisdefaulttype;
+ omittype[pretranstype] = false;
}
- thistimelim = thistimei + thistimecnt;
- thisleaplim = thisleapi + thisleapcnt;
- if (thistimecnt != 0)
- {
- if (ats[thistimei] == lo_time)
- locut = false;
- if (hi_time < ZIC_MAX && ats[thistimelim - 1] == hi_time + 1)
- hicut = false;
- }
- memset(omittype, true, typecnt);
+ /*
+ * Arguably the default time type in the 32-bit data should be
+ * range32.defaulttype, which is suited for timestamps just before
+ * ZIC32_MIN. However, zic traditionally used the time type of the
+ * indefinite past instead. Internet RFC 8532 says readers should
+ * ignore 32-bit data, so this discrepancy matters only to obsolete
+ * readers where the traditional type might be more appropriate even
+ * if it's "wrong". So, use the historical zic value, unless -r
+ * specifies a low cutoff that excludes some 32-bit timestamps.
+ */
+ if (pass == 1 && lo_time <= thismin)
+ thisdefaulttype = range64.defaulttype;
+
+ if (locut)
+ thisdefaulttype = unspecifiedtype;
omittype[thisdefaulttype] = false;
for (i = thistimei; i < thistimelim; i++)
omittype[types[i]] = false;
+ if (hicut)
+ omittype[unspecifiedtype] = false;
/*
* Reorder types to make THISDEFAULTTYPE type 0. Use TYPEMAP to swap
type;
hidst = histd = mrudst = mrustd = -1;
- for (i = thistimei; i < thistimelim; ++i)
+ if (0 <= pretranstype)
+ {
+ if (isdsts[pretranstype])
+ mrudst = pretranstype;
+ else
+ mrustd = pretranstype;
+ }
+ for (i = thistimei; i < thistimelim; i++)
if (isdsts[types[i]])
mrudst = types[i];
else
: i == thisdefaulttype ? old0 : i]
= thistypecnt++;
- for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i)
- indmap[i] = -1;
thischarcnt = stdcnt = utcnt = 0;
for (i = old0; i < typecnt; i++)
{
- char *thisabbr;
-
if (omittype[i])
continue;
if (ttisstds[i])
stdcnt = thistypecnt;
if (ttisuts[i])
utcnt = thistypecnt;
- if (indmap[desigidx[i]] >= 0)
- continue;
- thisabbr = &chars[desigidx[i]];
- for (j = 0; j < thischarcnt; ++j)
- if (strcmp(&thischars[j], thisabbr) == 0)
- break;
- if (j == thischarcnt)
- {
- strcpy(&thischars[thischarcnt], thisabbr);
- thischarcnt += strlen(thisabbr) + 1;
- }
- indmap[desigidx[i]] = j;
+ addabbr(thischars, &thischarcnt, &chars[desigidx[i]]);
}
+
+ /*
+ * Now that all abbrevs have been added to THISCHARS, it is safe to
+ * set INDMAP without worrying about whether the abbrevs might move
+ * later.
+ */
+ for (i = 0; i < TZ_MAX_CHARS; i++)
+ indmap[i] = -1;
+ for (i = old0; i < typecnt; i++)
+ if (!omittype[i] && indmap[desigidx[i]] < 0)
+ indmap[desigidx[i]] = addabbr(thischars, &thischarcnt,
+ &chars[desigidx[i]]);
+
if (pass == 1 && !want_bloat())
{
- utcnt = stdcnt = thisleapcnt = 0;
- thistimecnt = -(locut + hicut);
+ hicut = thisleapexpiry = false;
+ pretranstype = -1;
+ thistimecnt = thisleapcnt = 0;
thistypecnt = thischarcnt = 1;
- thistimelim = thistimei;
}
#define DO(field) fwrite(tzh.field, sizeof tzh.field, 1, fp)
- tzh = tzh0;
+ memset(&tzh, 0, sizeof tzh);
memcpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic);
tzh.tzh_version[0] = version;
convert(utcnt, tzh.tzh_ttisutcnt);
convert(stdcnt, tzh.tzh_ttisstdcnt);
- convert(thisleapcnt, tzh.tzh_leapcnt);
- convert(locut + thistimecnt + hicut, tzh.tzh_timecnt);
+ convert(thisleapcnt + thisleapexpiry, tzh.tzh_leapcnt);
+ convert((0 <= pretranstype) + thistimecnt + hicut,
+ tzh.tzh_timecnt);
convert(thistypecnt, tzh.tzh_typecnt);
convert(thischarcnt, tzh.tzh_charcnt);
DO(tzh_magic);
}
}
+ if (pass == 2 && noise && 50 < thischarcnt)
+ warning(_("%s: pre-2026 reference clients mishandle"
+ " more than 50 bytes of abbreviations"),
+ name);
+
/*
* Output a LO_TIME transition if needed; see limitrange. But do not
* go below the minimum representable value for this pass.
*/
- lo = pass == 1 && lo_time < INT32_MIN ? INT32_MIN : lo_time;
+ lo = pass == 1 && lo_time < ZIC32_MIN ? ZIC32_MIN : lo_time;
- if (locut)
+ if (0 <= pretranstype)
puttzcodepass(lo, fp, pass);
for (i = thistimei; i < thistimelim; ++i)
{
- zic_t at = ats[i] < lo ? lo : ats[i];
-
- puttzcodepass(at, fp, pass);
+ puttzcodepass(ats[i], fp, pass);
}
if (hicut)
puttzcodepass(hi_time + 1, fp, pass);
- currenttype = 0;
- if (locut)
- putc(currenttype, fp);
- for (i = thistimei; i < thistimelim; ++i)
- {
- currenttype = typemap[types[i]];
- putc(currenttype, fp);
- }
+ if (0 <= pretranstype)
+ putc(typemap[pretranstype], fp);
+ for (i = thistimei; i < thistimelim; i++)
+ putc(typemap[types[i]], fp);
if (hicut)
- putc(currenttype, fp);
+ putc(typemap[unspecifiedtype], fp);
for (i = old0; i < typecnt; i++)
{
if (thischarcnt != 0)
fwrite(thischars, sizeof thischars[0],
thischarcnt, fp);
+ thisleaplim = thisleapi + thisleapcnt;
for (i = thisleapi; i < thisleaplim; ++i)
{
zic_t todo;
- if (roll[i])
+ if (leap[i].roll)
{
- if (timecnt == 0 || trans[i] < ats[0])
+ if (timecnt == 0 || leap[i].trans < ats[0])
{
j = 0;
while (isdsts[j])
{
j = 1;
while (j < timecnt &&
- trans[i] >= ats[j])
+ ats[j] <= leap[i].trans)
++j;
j = types[j - 1];
}
- todo = tadd(trans[i], -utoffs[j]);
+ todo = tadd(leap[i].trans, -utoffs[j]);
}
else
- todo = trans[i];
+ todo = leap[i].trans;
puttzcodepass(todo, fp, pass);
- puttzcode(corr[i], fp);
+ puttzcode(leap[i].corr, fp);
+ }
+ if (thisleapexpiry)
+ {
+ /*
+ * Append a no-op leap correction indicating when the leap second
+ * table expires. Although this does not conform to Internet RFC
+ * 9636, most clients seem to accept this and the plan is to amend
+ * the RFC to allow this in version 4 TZif files.
+ */
+ puttzcodepass(leapexpires, fp, pass);
+ puttzcode(thisleaplim ? leap[thisleaplim - 1].corr : 0, fp);
}
if (stdcnt != 0)
for (i = old0; i < typecnt; i++)
putc(ttisuts[i], fp);
}
fprintf(fp, "\n%s\n", string);
- close_file(fp, directory, name);
+ close_file(fp, directory, name, tempname);
+ rename_dest(tempname, name);
free(ats);
}
}
}
-static size_t
+static char const disable_percent_s[] = "";
+
+static ptrdiff_t
doabbr(char *abbr, struct zone const *zp, char const *letters,
bool isdst, zic_t save, bool doquotes)
{
char *cp;
- char const *slashp;
- size_t len;
+ ptrdiff_t len;
char const *format = zp->z_format;
+ char const *slashp = strchr(format, '/');
- slashp = strchr(format, '/');
if (slashp == NULL)
{
char letterbuf[PERCENT_Z_LEN_BOUND + 1];
letters = abbroffset(letterbuf, zp->z_stdoff + save);
else if (!letters)
letters = "%s";
+ else if (letters == disable_percent_s)
+ return 0;
sprintf(abbr, format, letters);
}
else if (isdst)
- {
strcpy(abbr, slashp + 1);
- }
else
{
memcpy(abbr, format, slashp - format);
offset /= SECSPERMIN;
minutes = offset % MINSPERHOUR;
offset /= MINSPERHOUR;
- hours = offset;
- if (hours >= HOURSPERDAY * DAYSPERWEEK)
+ if (offset >= HOURSPERDAY * DAYSPERWEEK)
{
result[0] = '\0';
return 0;
}
+ hours = offset;
len += sprintf(result + len, "%d", hours);
if (minutes != 0 || seconds != 0)
{
return 1;
if (a->r_hiyear != b->r_hiyear)
return a->r_hiyear < b->r_hiyear ? -1 : 1;
+ if (a->r_hiyear == ZIC_MAX)
+ return 0;
if (a->r_month - b->r_month != 0)
return a->r_month - b->r_month;
return a->r_dayofmonth - b->r_dayofmonth;
}
+/*
+ * Store into RESULT a proleptic TZ string that represent the future
+ * predictions for the zone ZPFIRST with ZONECOUNT entries. Return a
+ * compatibility indicator (a TZDB release year) if successful, a
+ * negative integer if no such TZ string exists.
+ */
static int
stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
{
struct rule *stdrp;
struct rule *dstrp;
ptrdiff_t i;
- const char *abbrvar;
int compat = 0;
int c;
- size_t len;
int offsetlen;
struct rule stdr,
dstr;
+ ptrdiff_t len;
+ int dstcmp;
+ struct rule *lastrp[2] = {NULL, NULL};
+ struct zone zstr[2];
+ struct zone const *stdzp;
+ struct zone const *dstzp;
result[0] = '\0';
/*
- * Internet RFC 8536 section 5.1 says to use an empty TZ string if future
+ * Internet RFC 9636 section 6.1 says to use an empty TZ string if future
* timestamps are truncated.
*/
if (hi_time < max_time)
return -1;
zp = zpfirst + zonecount - 1;
- stdrp = dstrp = NULL;
for (i = 0; i < zp->z_nrules; ++i)
{
+ struct rule **last;
+ int cmp;
+
rp = &zp->z_rules[i];
- if (rp->r_hiwasnum || rp->r_hiyear != ZIC_MAX)
- continue;
- if (!rp->r_isdst)
- {
- if (stdrp == NULL)
- stdrp = rp;
- else
- return -1;
- }
- else
- {
- if (dstrp == NULL)
- dstrp = rp;
- else
- return -1;
- }
+ last = &lastrp[rp->r_isdst];
+ cmp = rule_cmp(*last, rp);
+ if (cmp < 0)
+ *last = rp;
+ else if (cmp == 0)
+ return -1;
+ }
+ stdrp = lastrp[false];
+ dstrp = lastrp[true];
+ dstcmp = zp->z_nrules ? rule_cmp(dstrp, stdrp) : zp->z_isdst ? 1 : -1;
+ stdzp = dstzp = zp;
+
+ if (dstcmp < 0)
+ {
+ /* Standard time all year. */
+ dstrp = NULL;
}
- if (stdrp == NULL && dstrp == NULL)
+ else if (0 < dstcmp)
{
/*
- * There are no rules running through "max". Find the latest std rule
- * in stdabbrrp and latest rule of any type in stdrp.
+ * DST all year. Use an abbreviation like "XXX3EDT4,0/0,J365/23" for
+ * EDT (-04) all year.
*/
- struct rule *stdabbrrp = NULL;
-
- for (i = 0; i < zp->z_nrules; ++i)
- {
- rp = &zp->z_rules[i];
- if (!rp->r_isdst && rule_cmp(stdabbrrp, rp) < 0)
- stdabbrrp = rp;
- if (rule_cmp(stdrp, rp) < 0)
- stdrp = rp;
- }
- if (stdrp != NULL && stdrp->r_isdst)
- {
- /* Perpetual DST. */
- dstr.r_month = TM_JANUARY;
- dstr.r_dycode = DC_DOM;
- dstr.r_dayofmonth = 1;
- dstr.r_tod = 0;
- dstr.r_todisstd = dstr.r_todisut = false;
- dstr.r_isdst = stdrp->r_isdst;
- dstr.r_save = stdrp->r_save;
- dstr.r_abbrvar = stdrp->r_abbrvar;
- stdr.r_month = TM_DECEMBER;
- stdr.r_dycode = DC_DOM;
- stdr.r_dayofmonth = 31;
- stdr.r_tod = SECSPERDAY + stdrp->r_save;
- stdr.r_todisstd = stdr.r_todisut = false;
- stdr.r_isdst = false;
- stdr.r_save = 0;
- stdr.r_abbrvar
- = (stdabbrrp ? stdabbrrp->r_abbrvar : "");
- dstrp = &dstr;
- stdrp = &stdr;
- }
- }
- if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_isdst))
- return -1;
- abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar;
- len = doabbr(result, zp, abbrvar, false, 0, true);
- offsetlen = stringoffset(result + len, -zp->z_stdoff);
+ zic_t save = dstrp ? dstrp->r_save : zp->z_save;
+
+ if (0 <= save)
+ {
+ /*
+ * Positive DST, the typical case for all-year DST. Fake a
+ * timezone with negative DST.
+ */
+ stdzp = &zstr[0];
+ dstzp = &zstr[1];
+ zstr[0].z_stdoff = zp->z_stdoff + 2 * save;
+ zstr[0].z_format = "XXX"; /* Any 3 letters will do. */
+ zstr[0].z_format_specifier = 0;
+ zstr[1].z_stdoff = zstr[0].z_stdoff;
+ zstr[1].z_format = zp->z_format;
+ zstr[1].z_format_specifier = zp->z_format_specifier;
+ }
+ dstr.r_month = TM_JANUARY;
+ dstr.r_dycode = DC_DOM;
+ dstr.r_dayofmonth = 1;
+ dstr.r_tod = 0;
+ dstr.r_todisstd = dstr.r_todisut = false;
+ dstr.r_isdst = true;
+ dstr.r_save = save < 0 ? save : -save;
+ dstr.r_abbrvar = dstrp ? dstrp->r_abbrvar : NULL;
+ stdr.r_month = TM_DECEMBER;
+ stdr.r_dycode = DC_DOM;
+ stdr.r_dayofmonth = 31;
+ stdr.r_tod = SECSPERDAY + dstr.r_save;
+ stdr.r_todisstd = stdr.r_todisut = false;
+ stdr.r_isdst = false;
+ stdr.r_save = 0;
+ stdr.r_abbrvar = save < 0 && stdrp ? stdrp->r_abbrvar : NULL;
+ dstrp = &dstr;
+ stdrp = &stdr;
+ }
+ len = doabbr(result, stdzp, stdrp ? stdrp->r_abbrvar : NULL,
+ false, 0, true);
+ offsetlen = stringoffset(result + len, -stdzp->z_stdoff);
if (!offsetlen)
{
result[0] = '\0';
len += offsetlen;
if (dstrp == NULL)
return compat;
- len += doabbr(result + len, zp, dstrp->r_abbrvar,
+ len += doabbr(result + len, dstzp, dstrp->r_abbrvar,
dstrp->r_isdst, dstrp->r_save, true);
if (dstrp->r_save != SECSPERMIN * MINSPERHOUR)
{
offsetlen = stringoffset(result + len,
- -(zp->z_stdoff + dstrp->r_save));
+ -(dstzp->z_stdoff + dstrp->r_save));
if (!offsetlen)
{
result[0] = '\0';
len += offsetlen;
}
result[len++] = ',';
- c = stringrule(result + len, dstrp, dstrp->r_save, zp->z_stdoff);
+ c = stringrule(result + len, dstrp, dstrp->r_save, stdzp->z_stdoff);
if (c < 0)
{
result[0] = '\0';
compat = c;
len += strlen(result + len);
result[len++] = ',';
- c = stringrule(result + len, stdrp, dstrp->r_save, zp->z_stdoff);
+ c = stringrule(result + len, stdrp, dstrp->r_save, stdzp->z_stdoff);
if (c < 0)
{
result[0] = '\0';
static void
outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
{
- const struct zone *zp;
- struct rule *rp;
ptrdiff_t i,
j;
- bool usestart,
- useuntil;
zic_t starttime,
untiltime;
- zic_t stdoff;
- zic_t save;
- zic_t year;
- zic_t startoff;
bool startttisstd;
bool startttisut;
- int type;
char *startbuf;
char *ab;
char *envvar;
int max_abbr_len;
int max_envvar_len;
- bool prodstic; /* all rules are min to max */
int compat;
bool do_extend;
char version;
- ptrdiff_t lastatmax = -1;
- zic_t one = 1;
- zic_t y2038_boundary = one << 31;
+ zic_t nonTZlimtime = ZIC_MIN;
+ int nonTZlimtype = -1;
zic_t max_year0;
int defaulttype = -1;
+ int max_stringoffset_len = sizeof "-167:59:59" - 1;
+ int max_comma_stringrule_len = (sizeof ",M12.5.6/" - 1
+ + max_stringoffset_len);
+ check_for_signal();
+
+ /* This cannot overflow; see FORMAT_LEN_GROWTH_BOUND. */
max_abbr_len = 2 + max_format_len + max_abbrvar_len;
- max_envvar_len = 2 * max_abbr_len + 5 * 9;
- startbuf = emalloc(max_abbr_len + 1);
- ab = emalloc(max_abbr_len + 1);
- envvar = emalloc(max_envvar_len + 1);
+ max_envvar_len = 2 * (max_abbr_len + max_stringoffset_len
+ + max_comma_stringrule_len);
+
+ startbuf = xmalloc(max_abbr_len + 1);
+ ab = xmalloc(max_abbr_len + 1);
+ envvar = xmalloc(max_envvar_len + 1);
INITIALIZE(untiltime);
INITIALIZE(starttime);
timecnt = 0;
typecnt = 0;
charcnt = 0;
- prodstic = zonecount == 1;
/*
* Thanks to Earl Chew for noting the need to unconditionally initialize
}
for (i = 0; i < zonecount; ++i)
{
- zp = &zpfirst[i];
+ struct zone const *zp = &zpfirst[i];
+
if (i < zonecount - 1)
updateminmax(zp->z_untilrule.r_loyear);
for (j = 0; j < zp->z_nrules; ++j)
{
- rp = &zp->z_rules[j];
- if (rp->r_lowasnum)
- updateminmax(rp->r_loyear);
+ struct rule *rp = &zp->z_rules[j];
+
+ updateminmax(rp->r_loyear);
if (rp->r_hiwasnum)
updateminmax(rp->r_hiyear);
- if (rp->r_lowasnum || rp->r_hiwasnum)
- prodstic = false;
}
}
* Generate lots of data if a rule can't cover all future times.
*/
compat = stringzone(envvar, zpfirst, zonecount);
- version = compat < 2013 ? ZIC_VERSION_PRE_2013 : ZIC_VERSION;
+ version = compat < 2013 ? '2' : '3';
do_extend = compat < 0;
if (noise)
{
if (!*envvar)
warning("%s %s",
- _("no POSIX environment variable for zone"),
+ _("no proleptic TZ string for zone"),
zpfirst->z_name);
else if (compat != 0)
{
}
if (do_extend)
{
- /*
- * Search through a couple of extra years past the obvious 400, to
- * avoid edge cases. For example, suppose a non-POSIX rule applies
- * from 2012 onwards and has transitions in March and September, plus
- * some one-off transitions in November 2013. If zic looked only at
- * the last 400 years, it would set max_year=2413, with the intent
- * that the 400 years 2014 through 2413 will be repeated. The last
- * transition listed in the tzfile would be in 2413-09, less than 400
- * years after the last one-off transition in 2013-11. Two years
- * might be overkill, but with the kind of edge cases available we're
- * not sure that one year would suffice.
- */
- enum
- {
- years_of_observations = YEARSPERREPEAT + 2};
-
if (min_year >= ZIC_MIN + years_of_observations)
min_year -= years_of_observations;
else
max_year += years_of_observations;
else
max_year = ZIC_MAX;
-
- /*
- * Regardless of any of the above, for a "proDSTic" zone which
- * specifies that its rules always have and always will be in effect,
- * we only need one cycle to define the zone.
- */
- if (prodstic)
- {
- min_year = 1900;
- max_year = min_year + years_of_observations;
- }
}
+ max_year = max(max_year, (redundant_time / (SECSPERDAY * DAYSPERNYEAR)
+ + EPOCH_YEAR + 1));
max_year0 = max_year;
if (want_bloat())
{
* For the benefit of older systems, generate data from 1900 through
* 2038.
*/
- if (min_year > 1900)
- min_year = 1900;
- if (max_year < 2038)
- max_year = 2038;
+ if (min_year > YEAR_32BIT_MIN - 1)
+ min_year = YEAR_32BIT_MIN - 1;
+ if (max_year < YEAR_32BIT_MAX)
+ max_year = YEAR_32BIT_MAX;
}
+ if (min_time < lo_time || hi_time < max_time)
+ unspecifiedtype = addtype(0, "-00", false, false, false);
+
for (i = 0; i < zonecount; ++i)
{
- struct rule *prevrp = NULL;
-
/*
* A guess that may well be corrected later.
*/
- save = 0;
- zp = &zpfirst[i];
- usestart = i > 0 && (zp - 1)->z_untiltime > min_time;
- useuntil = i < (zonecount - 1);
+ zic_t save = 0;
+ struct zone const *zp = &zpfirst[i];
+ bool usestart = i > 0 && (zp - 1)->z_untiltime > min_time;
+ bool useuntil = i < (zonecount - 1);
+ zic_t stdoff = zp->z_stdoff;
+ zic_t startoff = stdoff;
+
if (useuntil && zp->z_untiltime <= min_time)
continue;
- stdoff = zp->z_stdoff;
- eat(zp->z_filename, zp->z_linenum);
+ eat(zp->z_filenum, zp->z_linenum);
*startbuf = '\0';
- startoff = zp->z_stdoff;
if (zp->z_nrules == 0)
{
+ int type;
+
save = zp->z_save;
doabbr(startbuf, zp, NULL, zp->z_isdst, save, false);
type = addtype(oadd(zp->z_stdoff, save),
if (usestart)
{
addtt(starttime, type);
+ if (nonTZlimtime < starttime)
+ {
+ nonTZlimtime = starttime;
+ nonTZlimtype = type;
+ }
usestart = false;
}
else
defaulttype = type;
}
else
+ {
+ zic_t year;
+
for (year = min_year; year <= max_year; ++year)
{
if (useuntil && year > zp->z_untilrule.r_hiyear)
*/
for (j = 0; j < zp->z_nrules; ++j)
{
- rp = &zp->z_rules[j];
- eats(zp->z_filename, zp->z_linenum,
- rp->r_filename, rp->r_linenum);
+ zic_t one = 1;
+ zic_t y2038_boundary = one << 31;
+ struct rule *rp = &zp->z_rules[j];
+
+ eats(zp->z_filenum, zp->z_linenum,
+ rp->r_filenum, rp->r_linenum);
rp->r_todo = year >= rp->r_loyear &&
year <= rp->r_hiyear;
if (rp->r_todo)
zic_t jtime,
ktime;
zic_t offset;
+ struct rule *rp;
+ int type;
INITIALIZE(ktime);
if (useuntil)
k = -1;
for (j = 0; j < zp->z_nrules; ++j)
{
- rp = &zp->z_rules[j];
- if (!rp->r_todo)
+ struct rule *r = &zp->z_rules[j];
+
+ if (!r->r_todo)
continue;
- eats(zp->z_filename, zp->z_linenum,
- rp->r_filename, rp->r_linenum);
- offset = rp->r_todisut ? 0 : stdoff;
- if (!rp->r_todisstd)
+ eats(zp->z_filenum, zp->z_linenum,
+ r->r_filenum, r->r_linenum);
+ offset = r->r_todisut ? 0 : stdoff;
+ if (!r->r_todisstd)
offset = oadd(offset, save);
- jtime = rp->r_temp;
- if (jtime == min_time ||
- jtime == max_time)
- continue;
+ jtime = r->r_temp;
jtime = tadd(jtime, -offset);
if (k < 0 || jtime < ktime)
{
char const *dup_rules_msg =
_("two rules for same instant");
- eats(zp->z_filename, zp->z_linenum,
- rp->r_filename, rp->r_linenum);
+ eats(zp->z_filenum, zp->z_linenum,
+ r->r_filenum, r->r_linenum);
warning("%s", dup_rules_msg);
- rp = &zp->z_rules[k];
- eats(zp->z_filename, zp->z_linenum,
- rp->r_filename, rp->r_linenum);
+ r = &zp->z_rules[k];
+ eats(zp->z_filenum, zp->z_linenum,
+ r->r_filenum, r->r_linenum);
error("%s", dup_rules_msg);
}
}
rp = &zp->z_rules[k];
rp->r_todo = false;
if (useuntil && ktime >= untiltime)
+ {
+ if (!*startbuf
+ && (oadd(zp->z_stdoff, rp->r_save)
+ == startoff))
+ doabbr(startbuf, zp, rp->r_abbrvar,
+ rp->r_isdst, rp->r_save,
+ false);
break;
+ }
save = rp->r_save;
if (usestart && ktime == starttime)
usestart = false;
false);
}
}
- eats(zp->z_filename, zp->z_linenum,
- rp->r_filename, rp->r_linenum);
+ eats(zp->z_filenum, zp->z_linenum,
+ rp->r_filenum, rp->r_linenum);
doabbr(ab, zp, rp->r_abbrvar,
rp->r_isdst, rp->r_save, false);
offset = oadd(zp->z_stdoff, rp->r_save);
- if (!want_bloat() && !useuntil && !do_extend
- && prevrp
- && rp->r_hiyear == ZIC_MAX
- && prevrp->r_hiyear == ZIC_MAX)
- break;
type = addtype(offset, ab, rp->r_isdst,
rp->r_todisstd, rp->r_todisut);
if (defaulttype < 0 && !rp->r_isdst)
defaulttype = type;
- if (rp->r_hiyear == ZIC_MAX
- && !(0 <= lastatmax
- && ktime < attypes[lastatmax].at))
- lastatmax = timecnt;
addtt(ktime, type);
- prevrp = rp;
+ if (nonTZlimtime < ktime
+ && (useuntil || rp->r_hiyear != ZIC_MAX))
+ {
+ nonTZlimtime = ktime;
+ nonTZlimtype = type;
+ }
}
}
+ }
if (usestart)
{
- if (*startbuf == '\0' &&
- zp->z_format != NULL &&
- strchr(zp->z_format, '%') == NULL &&
- strchr(zp->z_format, '/') == NULL)
- strcpy(startbuf, zp->z_format);
- eat(zp->z_filename, zp->z_linenum);
+ bool isdst = startoff != zp->z_stdoff;
+
+ if (*startbuf == '\0' && zp->z_format)
+ doabbr(startbuf, zp, disable_percent_s,
+ isdst, save, false);
+ eat(zp->z_filenum, zp->z_linenum);
if (*startbuf == '\0')
- error(_("cannot determine time zone abbreviation to use just after until time"));
+ error(_("cannot determine time zone abbreviation"
+ " to use just after until time"));
else
{
- bool isdst = startoff != zp->z_stdoff;
+ int type = addtype(startoff, startbuf, isdst,
+ startttisstd, startttisut);
- type = addtype(startoff, startbuf, isdst,
- startttisstd, startttisut);
if (defaulttype < 0 && !isdst)
defaulttype = type;
addtt(starttime, type);
}
if (defaulttype < 0)
defaulttype = 0;
- if (0 <= lastatmax)
- attypes[lastatmax].dontmerge = true;
+ if (!do_extend && !want_bloat())
+ {
+ /* Keep trailing transitions that are no greater than this. */
+ zic_t keep_at_max;
+
+ /* The earliest transition into a time governed by the TZ string. */
+ zic_t TZstarttime = ZIC_MAX;
+
+ for (i = 0; i < timecnt; i++)
+ {
+ zic_t at = attypes[i].at;
+
+ if (nonTZlimtime < at && at < TZstarttime)
+ TZstarttime = at;
+ }
+ if (TZstarttime == ZIC_MAX)
+ TZstarttime = nonTZlimtime;
+
+ /*
+ * Omit trailing transitions deducible from the TZ string, and not
+ * needed for -r or -R.
+ */
+ keep_at_max = max(TZstarttime, redundant_time);
+ for (i = j = 0; i < timecnt; i++)
+ if (attypes[i].at <= keep_at_max)
+ {
+ attypes[j].at = attypes[i].at;
+ attypes[j].dontmerge = (attypes[i].at == TZstarttime
+ && (nonTZlimtype != attypes[i].type
+ || strchr(envvar, ',')));
+ attypes[j].type = attypes[i].type;
+ j++;
+ }
+ timecnt = j;
+ }
if (do_extend)
{
/*
* If we're extending the explicitly listed observations for 400 years
- * because we can't fill the POSIX-TZ field, check whether we actually
- * ended up explicitly listing observations through that period. If
- * there aren't any near the end of the 400-year period, add a
- * redundant one at the end of the final year, to make it clear that
- * we are claiming to have definite knowledge of the lack of
+ * because we can't fill the proleptic TZ field, check whether we
+ * actually ended up explicitly listing observations through that
+ * period. If there aren't any near the end of the 400-year period,
+ * add a redundant one at the end of the final year, to make it clear
+ * that we are claiming to have definite knowledge of the lack of
* transitions up to that point.
*/
struct rule xr;
{
int i,
j;
+ int charcnt0;
- if (!(-1L - 2147483647L <= utoff && utoff <= 2147483647L))
+ /* RFC 9636 section 3.2 specifies this range for utoff. */
+ if (!(-TWO_31_MINUS_1 <= utoff && utoff <= TWO_31_MINUS_1))
{
error(_("UT offset out of range"));
exit(EXIT_FAILURE);
if (!want_bloat())
ttisstd = ttisut = false;
- for (j = 0; j < charcnt; ++j)
- if (strcmp(&chars[j], abbr) == 0)
- break;
- if (j == charcnt)
- newabbr(abbr);
+ checkabbr(abbr);
+
+ charcnt0 = charcnt;
+ j = addabbr(chars, &charcnt, abbr);
+ if (charcnt0 < charcnt)
+ {
+ /*
+ * If an abbreviation was inserted, increment indexes no earlier than
+ * the insert by the size of the insertion, so that they continue to
+ * point to the same contents.
+ */
+ for (i = 0; i < typecnt; i++)
+ if (j <= desigidx[i])
+ desigidx[i] += charcnt - charcnt0;
+ }
else
{
/* If there's already an entry, return its index. */
static void
leapadd(zic_t t, int correction, int rolling)
{
- int i;
+ ptrdiff_t i;
- if (TZ_MAX_LEAPS <= leapcnt)
+ if (rolling && (lo_time != min_time || hi_time != max_time))
{
- error(_("too many leap seconds"));
+ error(_("Rolling leap seconds not supported with -r"));
exit(EXIT_FAILURE);
}
+ leap = growalloc(leap, sizeof *leap, leapcnt, &leap_alloc);
for (i = 0; i < leapcnt; ++i)
- if (t <= trans[i])
+ if (t <= leap[i].trans)
break;
- memmove(&trans[i + 1], &trans[i], (leapcnt - i) * sizeof *trans);
- memmove(&corr[i + 1], &corr[i], (leapcnt - i) * sizeof *corr);
- memmove(&roll[i + 1], &roll[i], (leapcnt - i) * sizeof *roll);
- trans[i] = t;
- corr[i] = correction;
- roll[i] = rolling;
+ memmove(&leap[i + 1], &leap[i], (leapcnt - i) * sizeof *leap);
+ leap[i].trans = t;
+ leap[i].corr = correction;
+ leap[i].roll = rolling;
++leapcnt;
}
static void
adjleap(void)
{
- int i;
+ ptrdiff_t i;
zic_t last = 0;
zic_t prevtrans = 0;
*/
for (i = 0; i < leapcnt; ++i)
{
- if (trans[i] - prevtrans < 28 * SECSPERDAY)
+ if (leap[i].trans - prevtrans < 28 * SECSPERDAY)
{
error(_("Leap seconds too close together"));
exit(EXIT_FAILURE);
}
- prevtrans = trans[i];
- trans[i] = tadd(trans[i], last);
- last = corr[i] += last;
- }
-
- if (leapexpires < 0)
- {
- leapexpires = comment_leapexpires;
- if (0 <= leapexpires)
- warning(_("\"#expires\" is obsolescent; use \"Expires\""));
+ prevtrans = leap[i].trans;
+ leap[i].trans = tadd(prevtrans, last);
+ last = leap[i].corr += last;
}
if (0 <= leapexpires)
{
leapexpires = oadd(leapexpires, last);
- if (!(leapcnt == 0 || (trans[leapcnt - 1] < leapexpires)))
+ if (!(leapcnt == 0 || (leap[leapcnt - 1].trans < leapexpires)))
{
error(_("last Leap time does not precede Expires time"));
exit(EXIT_FAILURE);
}
- if (leapexpires <= hi_time)
- hi_time = leapexpires - 1;
}
}
}
/* case-insensitive equality */
+ATTRIBUTE_PURE_114833
static bool
ciequal(const char *ap, const char *bp)
{
return false;
}
+ATTRIBUTE_PURE_114833
static bool
itsabbr(const char *abbr, const char *word)
{
/* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case. */
+ATTRIBUTE_PURE_114833
static bool
ciprefix(char const *abbr, char const *word)
{
return foundlp;
}
-static char **
-getfields(char *cp)
+static int
+getfields(char *cp, char **array, int arrayelts)
{
char *dp;
- char **array;
int nsubs;
- if (cp == NULL)
- return NULL;
- array = emalloc(size_product(strlen(cp) + 1, sizeof *array));
nsubs = 0;
for (;;)
{
+ char *dstart;
+
while (is_space(*cp))
++cp;
if (*cp == '\0' || *cp == '#')
break;
- array[nsubs++] = dp = cp;
+ dstart = dp = cp;
do
{
if ((*dp = *cp++) != '"')
if (is_space(*cp))
++cp;
*dp = '\0';
+ if (nsubs == arrayelts)
+ {
+ error(_("Too many input fields"));
+ exit(EXIT_FAILURE);
+ }
+ array[nsubs++] = dstart + (*dstart == '-' && dp == dstart + 1);
}
- array[nsubs] = NULL;
- return array;
+ return nsubs;
}
-static _Noreturn void
+ATTRIBUTE_NORETURN static void
time_overflow(void)
{
error(_("time overflow"));
exit(EXIT_FAILURE);
}
+/* Return T1 + T2, but diagnose any overflow and exit. */
+ATTRIBUTE_PURE_114833_HACK
static zic_t
oadd(zic_t t1, zic_t t2)
{
- if (t1 < 0 ? t2 < ZIC_MIN - t1 : ZIC_MAX - t1 < t2)
- time_overflow();
- return t1 + t2;
+#ifdef ckd_add
+ zic_t sum;
+
+ if (!ckd_add(&sum, t1, t2))
+ return sum;
+#else
+ if (t1 < 0 ? ZIC_MIN - t1 <= t2 : t2 <= ZIC_MAX - t1)
+ return t1 + t2;
+#endif
+ time_overflow();
}
+/*
+ * Return T1 + T2, but diagnose any overflow and exit.
+ * This is like oadd, except the result must fit in min_time..max_time range,
+ * which on oddball machines can be a smaller range than ZIC_MIN..ZIC_MAX.
+ */
+ATTRIBUTE_PURE_114833_HACK
static zic_t
tadd(zic_t t1, zic_t t2)
{
- if (t1 < 0)
- {
- if (t2 < min_time - t1)
- {
- if (t1 != min_time)
- time_overflow();
- return min_time;
- }
- }
- else
- {
- if (max_time - t1 < t2)
- {
- if (t1 != max_time)
- time_overflow();
- return max_time;
- }
- }
- return t1 + t2;
+ zic_t sum = oadd(t1, t2);
+
+ if (min_time <= sum && sum <= max_time)
+ return sum;
+ time_overflow();
+}
+
+/* Return T1 * T2, but diagnose any overflow and exit. */
+ATTRIBUTE_PURE_114833_HACK
+static zic_t
+omul(zic_t t1, zic_t t2)
+{
+#ifdef ckd_mul
+ zic_t product;
+
+ if (!ckd_mul(&product, t1, t2))
+ return product;
+#else
+ if (t2 < 0
+ ? ZIC_MAX / t2 <= t1 && (t2 == -1 || t1 <= ZIC_MIN / t2)
+ : t2 == 0 || (ZIC_MIN / t2 <= t1 && t1 <= ZIC_MAX / t2))
+ return t1 * t2;
+#endif
+ time_overflow();
}
/*
* Given a rule, and a year, compute the date (in seconds since January 1,
* 1970, 00:00 LOCAL time) in that year that the rule refers to.
+ * Do not count leap seconds. On error, diagnose and exit.
*/
static zic_t
zic_t dayoff; /* with a nod to Margaret O. */
zic_t t,
y;
+ int yrem;
- if (wantedy == ZIC_MIN)
- return min_time;
- if (wantedy == ZIC_MAX)
- return max_time;
- dayoff = 0;
m = TM_JANUARY;
y = EPOCH_YEAR;
- if (y < wantedy)
- {
- wantedy -= y;
- dayoff = (wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY);
- wantedy %= YEARSPERREPEAT;
- wantedy += y;
- }
- else if (wantedy < 0)
- {
- dayoff = (wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY);
- wantedy %= YEARSPERREPEAT;
- }
+
+ /*
+ * dayoff = floor((wantedy - y) / YEARSPERREPEAT) * DAYSPERREPEAT, sans
+ * overflow.
+ */
+ yrem = wantedy % YEARSPERREPEAT - y % YEARSPERREPEAT;
+ dayoff = ((wantedy / YEARSPERREPEAT - y / YEARSPERREPEAT
+ + yrem / YEARSPERREPEAT - (yrem % YEARSPERREPEAT < 0))
+ * DAYSPERREPEAT);
+ /* wantedy = y + ((wantedy - y) mod YEARSPERREPEAT), sans overflow. */
+ wantedy = y + (yrem + 2 * YEARSPERREPEAT) % YEARSPERREPEAT;
+
while (wantedy != y)
{
- if (wantedy > y)
- {
- i = len_years[isleap(y)];
- ++y;
- }
- else
- {
- --y;
- i = -len_years[isleap(y)];
- }
+ i = len_years[isleap(y)];
dayoff = oadd(dayoff, i);
+ y++;
}
while (m != rp->r_month)
{
dayoff = oadd(dayoff, i);
if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ)
{
- zic_t wday;
-
-#define LDAYSPERWEEK ((zic_t) DAYSPERWEEK)
- wday = EPOCH_WDAY;
-
/*
* Don't trust mod of negative numbers.
*/
- if (dayoff >= 0)
- wday = (wday + dayoff) % LDAYSPERWEEK;
- else
- {
- wday -= ((-dayoff) % LDAYSPERWEEK);
- if (wday < 0)
- wday += LDAYSPERWEEK;
- }
+ zic_t wday = ((EPOCH_WDAY + dayoff % DAYSPERWEEK + DAYSPERWEEK)
+ % DAYSPERWEEK);
+
while (wday != rp->r_wday)
if (rp->r_dycode == DC_DOWGEQ)
{
dayoff = oadd(dayoff, 1);
- if (++wday >= LDAYSPERWEEK)
+ if (++wday >= DAYSPERWEEK)
wday = 0;
++i;
}
{
dayoff = oadd(dayoff, -1);
if (--wday < 0)
- wday = LDAYSPERWEEK - 1;
+ wday = DAYSPERWEEK - 1;
--i;
}
if (i < 0 || i >= len_months[isleap(y)][m])
will not work with pre-2004 versions of zic"));
}
}
- if (dayoff < min_time / SECSPERDAY)
- return min_time;
- if (dayoff > max_time / SECSPERDAY)
- return max_time;
- t = (zic_t) dayoff * SECSPERDAY;
+ t = omul(dayoff, SECSPERDAY);
return tadd(t, rp->r_tod);
}
static void
-newabbr(const char *string)
+checkabbr(char const *string)
{
- int i;
-
if (strcmp(string, GRANDPARENTED) != 0)
{
const char *cp;
cp = string;
mp = NULL;
- while (is_alpha(*cp) || ('0' <= *cp && *cp <= '9')
+ while (is_alpha(*cp) || is_digit(*cp)
|| *cp == '-' || *cp == '+')
++cp;
if (noise && cp - string < 3)
if (mp != NULL)
warning("%s (%s)", mp, string);
}
- i = strlen(string) + 1;
- if (charcnt + i > TZ_MAX_CHARS)
+}
+
+/*
+ * Put into CHS, which currently contains *PNCHS bytes containing
+ * NUL-terminated abbreviations none of which are suffixes of another,
+ * the abbreviation ABBR including its trailing NUL.
+ * If ABBR does not already appear in CHS,
+ * possibly as a suffix of an existing abbreviation,
+ * add ABBR to CHS, remove from CHS any abbreviation
+ * that is a suffix of ABBR, and increment *PNCHS accordingly.
+ * Return the index of ABBR after any modifications to CHS are made.
+ *
+ * If all abbreviations have already been added, this function
+ * lets the caller look up the index of an existing abbreviation.
+ */
+static int
+addabbr(char chs[TZ_MAX_CHARS], int *pnchs, char const *abbr)
+{
+ int nchs = *pnchs;
+ int alen = strlen(abbr),
+ nchs_incr = alen + 1;
+ int i;
+
+ for (i = 0; i < nchs;)
+ {
+ int clen = strlen(&chs[i]);
+
+ if (alen <= clen)
+ {
+ /*
+ * If ABBR is a suffix of an abbreviation in CHS, return the index
+ * of ABBR in CHS.
+ */
+ int isuff = i + (clen - alen);
+
+ if (memcmp(&chs[isuff], abbr, alen) == 0)
+ return isuff;
+ }
+ else if (memcmp(&chs[i], &abbr[alen - clen], clen) == 0)
+ {
+ /*
+ * An abbreviation in CHS is a substring of ABBR. Replace it with
+ * ABBR, instead of the more-common actions of appending ABBR or
+ * doing nothing.
+ */
+ nchs_incr = alen - clen;
+ break;
+ }
+ i += clen + 1;
+ }
+ if (TZ_MAX_CHARS < nchs + nchs_incr)
{
error(_("too many, or too long, time zone abbreviations"));
exit(EXIT_FAILURE);
}
- strcpy(&chars[charcnt], string);
- charcnt += i;
+ memmove(&chs[i + nchs_incr], &chs[i], nchs - i);
+ memcpy(&chs[i], abbr, nchs_incr);
+ *pnchs = nchs + nchs_incr;
+ return i;
}
/*
* Ensure that the directories of ARGNAME exist, by making any missing
* ones. If ANCESTORS, do this only for ARGNAME's ancestors; otherwise,
* do it for ARGNAME too. Exit with failure if there is trouble.
- * Do not consider an existing non-directory to be trouble.
+ * Do not consider an existing file to be trouble.
*/
static void
mkdirs(char const *argname, bool ancestors)
{
- char *name;
- char *cp;
-
- cp = name = ecpyalloc(argname);
-
/*
- * On MS-Windows systems, do not worry about drive letters or backslashes,
- * as this should suffice in practice. Time zone names do not use drive
- * letters and backslashes. If the -d option of zic does not name an
- * already-existing directory, it can use slashes to separate the
- * already-existing ancestor prefix from the to-be-created subdirectories.
+ * If -D was specified, do not create directories. If a file operation's
+ * parent directory is missing, the operation will fail and be diagnosed.
*/
-
- /* Do not mkdir a root directory, as it must exist. */
- while (*cp == '/')
- cp++;
-
- while (cp && ((cp = strchr(cp, '/')) || !ancestors))
+ if (!skip_mkdir)
{
- if (cp)
- *cp = '\0';
+
+ char *name = xstrdup(argname);
+ char *cp = name;
/*
- * Try to create it. It's OK if creation fails because the directory
- * already exists, perhaps because some other process just created it.
- * For simplicity do not check first whether it already exists, as
- * that is checked anyway if the mkdir fails.
+ * On MS-Windows systems, do not worry about drive letters or
+ * backslashes, as this should suffice in practice. Time zone names
+ * do not use drive letters and backslashes. If the -d option of zic
+ * does not name an already-existing directory, it can use slashes to
+ * separate the already-existing ancestor prefix from the
+ * to-be-created subdirectories.
*/
- if (mkdir(name, MKDIR_UMASK) != 0)
+
+ /* Do not mkdir a root directory, as it must exist. */
+ while (*cp == '/')
+ cp++;
+
+ while (cp && ((cp = strchr(cp, '/')) || !ancestors))
{
+ if (cp)
+ *cp = '\0';
+
/*
- * For speed, skip itsdir if errno == EEXIST. Since mkdirs is
- * called only after open fails with ENOENT on a subfile, EEXIST
- * implies itsdir here.
+ * Try to create it. It's OK if creation fails because the
+ * directory already exists, perhaps because some other process
+ * just created it. For simplicity do not check first whether it
+ * already exists, as that is checked anyway if the mkdir fails.
*/
- int err = errno;
-
- if (err != EEXIST && !itsdir(name))
+ if (mkdir(name, MKDIR_PERMS) < 0)
{
- error(_("%s: Cannot create directory %s: %s"),
- progname, name, strerror(err));
- exit(EXIT_FAILURE);
+ /*
+ * Do not report an error if err == EEXIST, because some other
+ * process might have made the directory in the meantime.
+ * Likewise for ENOSYS, because Solaris 10 mkdir fails with
+ * ENOSYS if the directory is an automounted mount point.
+ * Likewise for EACCES, since mkdir can fail with EACCES
+ * merely because the parent directory is unwritable. Likewise
+ * for most other error numbers.
+ */
+ int err = errno;
+
+ if (err == ELOOP || err == ENAMETOOLONG
+ || err == ENOENT || err == ENOTDIR)
+ {
+ error(_("%s: Cannot create directory %s: %s"),
+ progname, name, strerror(err));
+ exit(EXIT_FAILURE);
+ }
}
+ if (cp)
+ *cp++ = '/';
}
- if (cp)
- *cp++ = '/';
- }
- free(name);
-}
-
-
-#ifdef WIN32
-/*
- * To run on win32
- */
-int
-link(const char *oldpath, const char *newpath)
-{
- if (!CopyFile(oldpath, newpath, false))
- {
- _dosmaperr(GetLastError());
- return -1;
+ free(name);
}
- return 0;
}
-#endif