diff options
Diffstat (limited to 'tz/zic.c')
-rw-r--r-- | tz/zic.c | 264 |
1 files changed, 192 insertions, 72 deletions
@@ -575,7 +575,8 @@ usage(FILE *stream, int status) fprintf(stream, _("%s: usage is %s [ --version ] [ --help ] [ -v ] \\\n" "\t[ -l localtime ] [ -p posixrules ] [ -d directory ] \\\n" - "\t[ -t localtime-link ] [ -L leapseconds ] [ filename ... ]\n\n" + "\t[ -t localtime-link ] [ -L leapseconds ] [ -r '[@lo][/@hi]' ] \\\n" + "\t[ filename ... ]\n\n" "Report bugs to %s.\n"), progname, progname, REPORT_BUGS_TO); if (status == EXIT_SUCCESS) @@ -603,6 +604,45 @@ change_directory (char const *dir) } } +#define 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 const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); + +/* The minimum, and one less than the maximum, values specified by + the -r option. These default to MIN_TIME and MAX_TIME. */ +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); + +/* Set the time range of the output to TIMERANGE. + Return true if successful. */ +static bool +timerange_option(char *timerange) +{ + intmax_t lo = min_time, hi = max_time; + char *lo_end = timerange, *hi_end; + if (*timerange == '@') { + errno = 0; + lo = strtoimax (timerange + 1, &lo_end, 10); + if (lo_end == timerange + 1 || (lo == INTMAX_MAX && errno == ERANGE)) + return false; + } + hi_end = lo_end; + if (lo_end[0] == '/' && lo_end[1] == '@') { + errno = 0; + hi = strtoimax (lo_end + 2, &hi_end, 10); + if (hi_end == lo_end + 2 || hi == INTMAX_MIN) + return false; + hi -= ! (hi == INTMAX_MAX && errno == ERANGE); + } + 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; + return true; +} + static const char * psxrules; static const char * lcltime; static const char * directory; @@ -615,6 +655,7 @@ main(int argc, char **argv) { register int c, k; register ptrdiff_t i, j; + bool timerange_given = false; #ifdef S_IWGRP umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH)); @@ -640,7 +681,7 @@ main(int argc, char **argv) } else if (strcmp(argv[k], "--help") == 0) { usage(stdout, EXIT_SUCCESS); } - while ((c = getopt(argc, argv, "d:l:L:p:st:vy:")) != EOF && c != -1) + while ((c = getopt(argc, argv, "d:l:L:p:r:st:vy:")) != EOF && c != -1) switch (c) { default: usage(stderr, EXIT_FAILURE); @@ -708,6 +749,21 @@ _("%s: More than one -L option specified\n"), case 'v': noise = true; break; + case 'r': + if (timerange_given) { + fprintf(stderr, +_("%s: More than one -r option specified\n"), + progname); + return EXIT_FAILURE; + } + if (! timerange_option(optarg)) { + fprintf(stderr, +_("%s: invalid time range: %s\n"), + progname, optarg); + return EXIT_FAILURE; + } + timerange_given = true; + break; case 's': warning(_("-s ignored")); break; @@ -961,11 +1017,6 @@ dolink(char const *fromfield, char const *tofield, bool staysymlink) } } -#define TIME_T_BITS_IN_FILE 64 - -static zic_t const min_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE); -static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); - /* Return true if NAME is a directory. */ static bool itsdir(char const *name) @@ -1714,12 +1765,16 @@ puttzcode(const int_fast32_t val, FILE *const fp) } static void -puttzcode64(const zic_t val, FILE *const fp) +puttzcodepass(zic_t val, FILE *fp, int pass) { + if (pass == 1) + puttzcode(val, fp); + else { char buf[8]; convert64(val, buf); fwrite(buf, sizeof buf, 1, fp); + } } static int @@ -1742,14 +1797,42 @@ swaptypes(int i, int j) { bool t = ttisgmts[i]; ttisgmts[i] = ttisgmts[j]; ttisgmts[j] = t; } } +struct timerange { + int defaulttype; + ptrdiff_t base, count; + int leapbase, leapcount; +}; + +static struct timerange +limitrange(struct timerange r, zic_t lo, zic_t hi, + zic_t const *ats, unsigned char const *types) +{ + 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) { + r.leapcount--; + r.leapbase++; + } + + if (hi < ZIC_MAX) { + 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]) + r.leapcount--; + } + + return r; +} + static void writezone(const char *const name, const char *const string, char version, int defaulttype) { register FILE * fp; register ptrdiff_t i, j; - register int leapcnt32, leapi32; - register ptrdiff_t timecnt32, timei32; register int pass; static const struct tzhead tzh0; static struct tzhead tzh; @@ -1764,6 +1847,7 @@ writezone(const char *const name, const char *const string, char version, _Alignof(zic_t))); void *typesptr = ats + nats; unsigned char *types = typesptr; + struct timerange rangeall, range32, range64; /* ** Sort. @@ -1839,32 +1923,13 @@ writezone(const char *const name, const char *const string, char version, timecnt++; } - /* - ** Figure out 32-bit-limited starts and counts. - */ - timecnt32 = timecnt; - timei32 = 0; - leapcnt32 = leapcnt; - leapi32 = 0; - while (0 < timecnt32 && INT32_MAX < ats[timecnt32 - 1]) - --timecnt32; - while (1 < timecnt32 && ats[timei32] < INT32_MIN - && ats[timei32 + 1] <= INT32_MIN) { - /* Discard too-low transitions, except keep any last too-low - transition if no transition is exactly at INT32_MIN. - The kept transition will be output as an INT32_MIN - "transition" appropriate for buggy 32-bit clients that do - not use time type 0 for timestamps before the first - transition; see below. */ - --timecnt32; - ++timei32; - } - while (0 < leapcnt32 && INT32_MAX < trans[leapcnt32 - 1]) - --leapcnt32; - while (0 < leapcnt32 && trans[leapi32] < INT32_MIN) { - --leapcnt32; - ++leapi32; - } + 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); + /* ** Remove old file, if any, to snap links. */ @@ -1894,6 +1959,9 @@ writezone(const char *const name, const char *const string, char version, for (pass = 1; pass <= 2; ++pass) { register ptrdiff_t thistimei, thistimecnt, thistimelim; register int thisleapi, thisleapcnt, thisleaplim; + int currenttype, thisdefaulttype; + bool locut, hicut; + zic_t lo; int old0; char omittype[TZ_MAX_TYPES]; int typemap[TZ_MAX_TYPES]; @@ -1904,33 +1972,72 @@ writezone(const char *const name, const char *const string, char version, int indmap[TZ_MAX_CHARS]; if (pass == 1) { - thistimei = timei32; - thistimecnt = timecnt32; + /* 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); + + thistimei = range32.base; + thistimecnt = range32.count; toomanytimes = thistimecnt >> 31 >> 1 != 0; - thisleapi = leapi32; - thisleapcnt = leapcnt32; + thisleapi = range32.leapbase; + thisleapcnt = range32.leapcount; + locut = INT32_MIN < lo_time; + hicut = hi_time < INT32_MAX; } else { - thistimei = 0; - thistimecnt = timecnt; + thisdefaulttype = range64.defaulttype; + thistimei = range64.base; + thistimecnt = range64.count; toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0; - thisleapi = 0; - thisleapcnt = leapcnt; + thisleapi = range64.leapbase; + thisleapcnt = range64.leapcount; + locut = min_time < lo_time; + hicut = hi_time < max_time; } if (toomanytimes) error(_("too many transition times")); + + /* 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. */ + if (0 < thistimei && ats[thistimei] != lo_time) { + thistimei--; + thistimecnt++; + locut = 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); - omittype[defaulttype] = false; + omittype[thisdefaulttype] = false; for (i = thistimei; i < thistimelim; i++) omittype[types[i]] = false; - /* Reorder types to make DEFAULTTYPE type 0. - Use TYPEMAP to swap OLD0 and DEFAULTTYPE so that - DEFAULTTYPE appears as type 0 in the output instead + /* Reorder types to make THISDEFAULTTYPE type 0. + Use TYPEMAP to swap OLD0 and THISDEFAULTTYPE so that + THISDEFAULTTYPE appears as type 0 in the output instead of OLD0. TYPEMAP also omits unused types. */ old0 = strlen(omittype); - swaptypes(old0, defaulttype); + swaptypes(old0, thisdefaulttype); #ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH /* @@ -1982,8 +2089,8 @@ writezone(const char *const name, const char *const string, char version, thistypecnt = 0; for (i = old0; i < typecnt; i++) if (!omittype[i]) - typemap[i == old0 ? defaulttype - : i == defaulttype ? old0 : i] + typemap[i == old0 ? thisdefaulttype + : i == thisdefaulttype ? old0 : i] = thistypecnt++; for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i) @@ -2013,7 +2120,7 @@ writezone(const char *const name, const char *const string, char version, convert(thistypecnt, tzh.tzh_ttisgmtcnt); convert(thistypecnt, tzh.tzh_ttisstdcnt); convert(thisleapcnt, tzh.tzh_leapcnt); - convert(thistimecnt, tzh.tzh_timecnt); + convert(locut + thistimecnt + hicut, tzh.tzh_timecnt); convert(thistypecnt, tzh.tzh_typecnt); convert(thischarcnt, tzh.tzh_charcnt); DO(tzh_magic); @@ -2026,21 +2133,29 @@ writezone(const char *const name, const char *const string, char version, DO(tzh_typecnt); DO(tzh_charcnt); #undef DO - for (i = thistimei; i < thistimelim; ++i) - if (pass == 1) - /* - ** Output an INT32_MIN "transition" - ** if appropriate; see above. - */ - puttzcode(((ats[i] < INT32_MIN) ? - INT32_MIN : ats[i]), fp); - else puttzcode64(ats[i], fp); - for (i = thistimei; i < thistimelim; ++i) { - unsigned char uc; + /* 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; - uc = typemap[types[i]]; - fwrite(&uc, sizeof uc, 1, fp); + if (locut) + puttzcodepass(lo, fp, pass); + for (i = thistimei; i < thistimelim; ++i) { + zic_t at = ats[i] < lo ? lo : ats[i]; + puttzcodepass(at, 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 (hicut) + putc(currenttype, fp); + for (i = old0; i < typecnt; i++) if (!omittype[i]) { puttzcode(gmtoffs[i], fp); @@ -2070,9 +2185,7 @@ writezone(const char *const name, const char *const string, char version, } todo = tadd(trans[i], -gmtoffs[j]); } else todo = trans[i]; - if (pass == 1) - puttzcode(todo, fp); - else puttzcode64(todo, fp); + puttzcodepass(todo, fp, pass); puttzcode(corr[i], fp); } for (i = old0; i < typecnt; i++) @@ -2081,7 +2194,7 @@ writezone(const char *const name, const char *const string, char version, for (i = old0; i < typecnt; i++) if (!omittype[i]) putc(ttisgmts[i], fp); - swaptypes(old0, defaulttype); + swaptypes(old0, thisdefaulttype); } fprintf(fp, "\n%s\n", string); close_file(fp, directory, name); @@ -2301,6 +2414,12 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) struct rule stdr, dstr; result[0] = '\0'; + + /* Internet RFC 8536 section 5.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) { @@ -2737,11 +2856,12 @@ error(_("can't determine time zone abbreviation to use just after until time")); xr.r_dycode = DC_DOM; xr.r_dayofmonth = 1; xr.r_tod = 0; - for (lastat = &attypes[0], i = 1; i < timecnt; i++) + for (lastat = attypes, i = 1; i < timecnt; i++) if (attypes[i].at > lastat->at) lastat = &attypes[i]; - if (lastat->at < rpytime(&xr, max_year - 1)) { - addtt(rpytime(&xr, max_year + 1), lastat->type); + if (!lastat || lastat->at < rpytime(&xr, max_year - 1)) { + addtt(rpytime(&xr, max_year + 1), + lastat ? lastat->type : defaulttype); attypes[timecnt - 1].dontmerge = true; } } |