diff options
Diffstat (limited to 'src/timezone/zic.c')
-rw-r--r-- | src/timezone/zic.c | 300 |
1 files changed, 219 insertions, 81 deletions
diff --git a/src/timezone/zic.c b/src/timezone/zic.c index 4613919afe4..ab10165a267 100644 --- a/src/timezone/zic.c +++ b/src/timezone/zic.c @@ -541,7 +541,8 @@ usage(FILE *stream, int status) fprintf(stream, _("%s: usage is %s [ --version ] [ --help ] [ -v ] [ -P ] \\\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, PACKAGE_BUGREPORT); if (status == EXIT_SUCCESS) @@ -573,6 +574,50 @@ 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) +{ + int64 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; @@ -587,6 +632,7 @@ main(int argc, char **argv) k; ptrdiff_t i, j; + bool timerange_given = false; #ifndef WIN32 umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH)); @@ -609,7 +655,7 @@ main(int argc, char **argv) { usage(stdout, EXIT_SUCCESS); } - while ((c = getopt(argc, argv, "d:l:L:p:Pst:vy:")) != EOF && c != -1) + while ((c = getopt(argc, argv, "d:l:L:p:Pr:st:vy:")) != EOF && c != -1) switch (c) { default: @@ -690,6 +736,23 @@ main(int argc, char **argv) print_abbrevs = true; print_cutoff = time(NULL); 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; @@ -996,11 +1059,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) @@ -1897,12 +1955,17 @@ puttzcode(const int32 val, FILE *const fp) } static void -puttzcode64(const zic_t val, FILE *const fp) +puttzcodepass(zic_t val, FILE *fp, int pass) { - char buf[8]; + if (pass == 1) + puttzcode(val, fp); + else + { + char buf[8]; - convert64(val, buf); - fwrite(buf, sizeof buf, 1, fp); + convert64(val, buf); + fwrite(buf, sizeof buf, 1, fp); + } } static int @@ -1949,6 +2012,42 @@ swaptypes(int i, int j) } } +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) @@ -1956,10 +2055,6 @@ writezone(const char *const name, const char *const string, char version, FILE *fp; ptrdiff_t i, j; - int leapcnt32, - leapi32; - ptrdiff_t timecnt32, - timei32; int pass; static const struct tzhead tzh0; static struct tzhead tzh; @@ -1975,6 +2070,9 @@ writezone(const char *const name, const char *const string, char version, zic_t *ats = emalloc(MAXALIGN(size_product(nats, sizeof *ats + 1))); void *typesptr = ats + nats; unsigned char *types = typesptr; + struct timerange rangeall, + range32, + range64; /* * Sort. @@ -2061,35 +2159,12 @@ 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 && PG_INT32_MAX < ats[timecnt32 - 1]) - --timecnt32; - while (1 < timecnt32 && ats[timei32] < PG_INT32_MIN - && ats[timei32 + 1] <= PG_INT32_MIN) - { - /* - * Discard too-low transitions, except keep any last too-low - * transition if no transition is exactly at PG_INT32_MIN. The kept - * transition will be output as an PG_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 && PG_INT32_MAX < trans[leapcnt32 - 1]) - --leapcnt32; - while (0 < leapcnt32 && trans[leapi32] < PG_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, PG_INT32_MIN, PG_INT32_MAX, ats, types); /* * Remove old file, if any, to snap links. @@ -2130,6 +2205,11 @@ writezone(const char *const name, const char *const string, char version, 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]; @@ -2141,36 +2221,79 @@ writezone(const char *const name, const char *const string, char version, 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 + * PG_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 <= PG_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 = PG_INT32_MIN < lo_time; + hicut = hi_time < PG_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 of OLD0. TYPEMAP also omits unused types. + * 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 @@ -2231,8 +2354,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) @@ -2264,7 +2387,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); @@ -2314,24 +2437,33 @@ writezone(const char *const name, const char *const string, char version, } } - for (i = thistimei; i < thistimelim; ++i) - if (pass == 1) + /* + * 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 < PG_INT32_MIN ? PG_INT32_MIN : lo_time; - /* - * Output an PG_INT32_MIN "transition" if appropriate; see - * above. - */ - puttzcode(((ats[i] < PG_INT32_MIN) ? - PG_INT32_MIN : ats[i]), fp); - else - puttzcode64(ats[i], fp); + if (locut) + puttzcodepass(lo, fp, pass); for (i = thistimei; i < thistimelim; ++i) { - unsigned char uc; + zic_t at = ats[i] < lo ? lo : ats[i]; - uc = typemap[types[i]]; - fwrite(&uc, sizeof uc, 1, fp); + 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]) { @@ -2370,10 +2502,7 @@ writezone(const char *const name, const char *const string, char version, } 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++) @@ -2382,7 +2511,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); @@ -2636,6 +2765,14 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) 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) @@ -3131,12 +3268,13 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) 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)) + if (!lastat || lastat->at < rpytime(&xr, max_year - 1)) { - addtt(rpytime(&xr, max_year + 1), typecnt - 1); + addtt(rpytime(&xr, max_year + 1), + lastat ? lastat->type : defaulttype); attypes[timecnt - 1].dontmerge = true; } } |