diff options
Diffstat (limited to 'timezone/zic.c')
-rw-r--r-- | timezone/zic.c | 2210 |
1 files changed, 1342 insertions, 868 deletions
diff --git a/timezone/zic.c b/timezone/zic.c index 2875b5544c..00f00e307a 100644 --- a/timezone/zic.c +++ b/timezone/zic.c @@ -5,29 +5,42 @@ ** 2006-07-17 by Arthur David Olson. */ +/* Use the system 'time' function, instead of any private replacement. + This avoids creating an unnecessary dependency on localtime.c. */ +#undef EPOCH_LOCAL +#undef EPOCH_OFFSET +#undef RESERVE_STD_EXT_IDS +#undef time_tz + #include "version.h" #include "private.h" +#include "tzdir.h" #include "tzfile.h" #include <fcntl.h> #include <locale.h> +#include <signal.h> #include <stdarg.h> -#include <stddef.h> #include <stdio.h> -#define ZIC_VERSION_PRE_2013 '2' -#define ZIC_VERSION '3' - 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) 0x7fffffff, + ZIC32_MAX = 0x7fffffff; #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 */ +/* Minimum and maximum years, assuming signed 32-bit 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 }; + #ifdef HAVE_DIRECT_H # include <direct.h> # include <io.h> @@ -35,38 +48,40 @@ typedef int_fast64_t zic_t; # define mkdir(name, mode) _mkdir(name) #endif +#ifndef HAVE_GETRANDOM +# ifdef __has_include +# if __has_include(<sys/random.h>) +# include <sys/random.h> +# endif +# elif 2 < __GLIBC__ + (25 <= __GLIBC_MINOR__) +# include <sys/random.h> +# endif +# define HAVE_GETRANDOM GRND_RANDOM +#elif HAVE_GETRANDOM +# include <sys/random.h> +#endif + #if HAVE_SYS_STAT_H -#include <sys/stat.h> +# include <sys/stat.h> #endif #ifdef S_IRUSR -#define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) +# define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) #else -#define MKDIR_UMASK 0755 +# define MKDIR_UMASK 0755 #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) -#endif - -#if HAVE_SYS_WAIT_H -#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */ -#endif /* HAVE_SYS_WAIT_H */ - -#ifndef WIFEXITED -#define WIFEXITED(status) (((status) & 0xff) == 0) -#endif /* !defined WIFEXITED */ -#ifndef WEXITSTATUS -#define WEXITSTATUS(status) (((status) >> 8) & 0xff) -#endif /* !defined WEXITSTATUS */ -/* 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 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 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 /* The type for line numbers. Use PRIdMAX to format them; formerly @@ -75,14 +90,12 @@ static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t)); typedef intmax_t lineno; struct rule { - const char * r_filename; + int r_filenum; lineno r_linenum; const char * r_name; zic_t r_loyear; /* for example, 1986 */ zic_t r_hiyear; /* for example, 1986 */ - const char * r_yrtype; - bool r_lowasnum; bool r_hiwasnum; int r_month; /* 0..11 */ @@ -103,15 +116,16 @@ struct rule { }; /* -** 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 z_linenum; const char * z_name; @@ -133,22 +147,28 @@ struct zone { #if !HAVE_POSIX_DECLS extern int getopt(int argc, char * const argv[], const char * options); -extern int link(const char * fromname, const char * toname); +extern int link(const char * target, const char * linkname); extern char * optarg; extern int optind; #endif -#if ! HAVE_LINK -# define link(from, to) (errno = ENOTSUP, -1) -#endif #if ! HAVE_SYMLINK -# define readlink(file, buf, size) (errno = ENOTSUP, -1) -# define symlink(from, to) (errno = ENOTSUP, -1) -# define S_ISLNK(m) 0 +static ssize_t +readlink(char const *restrict file, char *restrict buf, size_t size) +{ + errno = ENOTSUP; + return -1; +} +static int +symlink(char const *target, char const *linkname) +{ + errno = ENOTSUP; + return -1; +} #endif #ifndef AT_SYMLINK_FOLLOW -# define linkat(fromdir, from, todir, to, flag) \ - (itssymlink(from) ? (errno = ENOTSUP, -1) : link(from, to)) +# define linkat(targetdir, target, linknamedir, linkname, flag) \ + (errno = ENOTSUP, -1) #endif static void addtt(zic_t starttime, int type); @@ -157,19 +177,18 @@ static void leapadd(zic_t, int, int); static void adjleap(void); static void associate(void); static void dolink(const char *, const char *, bool); -static char ** getfields(char * buf); +static int getfields(char *, char **, int); static zic_t gethms(const char * string, const char * errstring); static zic_t getsave(char *, bool *); static void inexpires(char **, int); -static void infile(const char * filename); +static void infile(int, char const *); 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 **, int, bool); -static bool itsdir(char const *); -static bool itssymlink(char const *); +static int itssymlink(char const *, int *); static bool is_alpha(char a); static char lowerit(char); static void mkdirs(char const *, bool); @@ -177,29 +196,19 @@ static void newabbr(const char * abbr); static zic_t oadd(zic_t t1, zic_t t2); static void outzone(const struct zone * zp, ptrdiff_t ntzones); 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); -static bool yearistype(zic_t year, const char * type); /* Bound on length of what %z can expand to. */ enum { PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1 }; -/* 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 -enum { WORK_AROUND_QTBUG_53071 = true }; -#endif - static int charcnt; static bool errors; static bool warnings; -static const char * filename; +static int filenum; static int leapcnt; static bool leapseen; static zic_t leapminyear; @@ -210,97 +219,121 @@ static int max_format_len; static zic_t max_year; static zic_t min_year; static bool noise; -static const char * rfilename; +static int rfilenum; static lineno 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_FROM 1 -#define LF_TO 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. */ + EXPIRES_FIELDS = LP_TIME + 1 +}; -/* Expires lines are like Leap lines, except without CORR and ROLL fields. */ -#define EXPIRES_FIELDS 5 +/* 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 */ @@ -311,10 +344,10 @@ static ptrdiff_t nzones; /* number of zones */ static ptrdiff_t nzones_alloc; struct link { - const char * l_filename; + int l_filenum; lineno l_linenum; - const char * l_from; - const char * l_to; + const char * l_target; + const char * l_linkname; }; static struct link * links; @@ -381,12 +414,10 @@ static struct lookup const lasts[] = { 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 } @@ -426,29 +457,53 @@ static char roll[TZ_MAX_LEAPS]; ** 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 ATTRIBUTE_PURE 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 ATTRIBUTE_PURE size_t -align_to(size_t size, size_t alignment) +ATTRIBUTE_REPRODUCIBLE static ptrdiff_t +size_sum(size_t a, size_t b) { - size_t aligned_size = size + alignment - 1; - aligned_size -= aligned_size % alignment; - if (aligned_size < size) - memory_exhausted(_("alignment overflow")); - return aligned_size; +#ifdef ckd_add + ptrdiff_t sum; + 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_REPRODUCIBLE 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_REPRODUCIBLE 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; } #if !HAVE_STRDUP @@ -464,11 +519,11 @@ static void * memcheck(void *ptr) { if (ptr == NULL) - memory_exhausted(strerror(errno)); + memory_exhausted(strerror(HAVE_MALLOC_ERRNO ? errno : ENOMEM)); return ptr; } -static void * ATTRIBUTE_MALLOC +ATTRIBUTE_MALLOC static void * emalloc(size_t size) { return memcheck(malloc(size)); @@ -480,47 +535,76 @@ erealloc(void *ptr, size_t size) return memcheck(realloc(ptr, size)); } -static char * ATTRIBUTE_MALLOC -ecpyalloc (char const *str) +ATTRIBUTE_MALLOC static char * +estrdup(char const *str) { return memcheck(strdup(str)); } +static ptrdiff_t +grow_nitems_alloc(ptrdiff_t *nitems_alloc, ptrdiff_t itemsize) +{ + ptrdiff_t addend = (*nitems_alloc >> 1) + 1; +#if defined ckd_add && defined ckd_mul + ptrdiff_t product; + 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, size_t itemsize, ptrdiff_t nitems, ptrdiff_t *nitems_alloc) +growalloc(void *ptr, ptrdiff_t itemsize, ptrdiff_t nitems, + ptrdiff_t *nitems_alloc) { - 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; - 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)); - } + return (nitems < *nitems_alloc + ? ptr + : erealloc(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 num, char const *rname, lineno rnum) +eats(int fnum, lineno num, int rfnum, lineno rnum) { - filename = name; + filenum = fnum; linenum = num; - rfilename = rname; + rfilenum = rfnum; rlinenum = rnum; } static void -eat(char const *name, lineno num) +eat(int fnum, lineno num) { - eats(name, num, NULL, -1); + eats(fnum, num, 0, -1); } -static void ATTRIBUTE_FORMAT((printf, 1, 0)) +ATTRIBUTE_FORMAT((printf, 1, 0)) static void verror(const char *const string, va_list args) { /* @@ -528,16 +612,17 @@ verror(const char *const string, va_list args) ** 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"); } -static void ATTRIBUTE_FORMAT((printf, 1, 2)) +ATTRIBUTE_FORMAT((printf, 1, 2)) static void error(const char *const string, ...) { va_list args; @@ -547,7 +632,7 @@ error(const char *const string, ...) errors = true; } -static void ATTRIBUTE_FORMAT((printf, 1, 2)) +ATTRIBUTE_FORMAT((printf, 1, 2)) static void warning(const char *const string, ...) { va_list args; @@ -558,8 +643,11 @@ warning(const char *const string, ...) warnings = true; } +/* Close STREAM. If it had an I/O error, report it against DIR/NAME, + remove TEMPNAME if nonnull, and then exit. */ 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); @@ -568,23 +656,26 @@ close_file(FILE *stream, char const *dir, char const *name) dir ? dir : "", dir ? "/" : "", name ? name : "", name ? ": " : "", e); + if (tempname) + remove(tempname); exit(EXIT_FAILURE); } } -static _Noreturn void +ATTRIBUTE_NORETURN static void usage(FILE *stream, int status) { fprintf(stream, _("%s: usage is %s [ --version ] [ --help ] [ -v ] \\\n" "\t[ -b {slim|fat} ] [ -d directory ] [ -l localtime ]" " [ -L leapseconds ] \\\n" - "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -t localtime-link ] \\\n" + "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -R '@hi' ] \\\n" + "\t[ -t localtime-link ] \\\n" "\t[ filename ... ]\n\n" "Report bugs to %s.\n"), progname, progname, REPORT_BUGS_TO); if (status == EXIT_SUCCESS) - close_file(stream, NULL, NULL); + close_file(stream, NULL, NULL, NULL); exit(status); } @@ -592,7 +683,7 @@ usage(FILE *stream, int status) ancestors. After this is done, all files are accessed with names relative to DIR. */ static void -change_directory (char const *dir) +change_directory(char const *dir) { if (chdir(dir) != 0) { int chdir_errno = errno; @@ -608,7 +699,194 @@ change_directory (char const *dir) } } -#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); + } + } +} + +/* 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); @@ -619,12 +897,13 @@ static zic_t const max_time = MAXVAL(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. */ static bool @@ -634,35 +913,49 @@ timerange_option(char *timerange) char *lo_end = timerange, *hi_end; if (*timerange == '@') { errno = 0; - lo = strtoimax (timerange + 1, &lo_end, 10); + 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); + 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; + 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; -static const char * yitcommand; /* -1 if the TZif output file should be slim, 0 if default, 1 if the - output should be fat for backward compatibility. Currently the - default is fat, although this may change. */ + output should be fat for backward compatibility. ZIC_BLOAT_DEFAULT + determines the default. */ static int bloat; static bool @@ -672,7 +965,7 @@ want_bloat(void) } #ifndef ZIC_BLOAT_DEFAULT -# define ZIC_BLOAT_DEFAULT "fat" +# define ZIC_BLOAT_DEFAULT "slim" #endif int @@ -687,12 +980,13 @@ main(int argc, char **argv) #endif #if HAVE_GETTEXT setlocale(LC_ALL, ""); -#ifdef TZ_DOMAINDIR +# ifdef TZ_DOMAINDIR bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR); -#endif /* defined TEXTDOMAINDIR */ +# endif /* defined TEXTDOMAINDIR */ textdomain(TZ_DOMAIN); #endif /* HAVE_GETTEXT */ - progname = argv[0]; + main_argv = argv; + progname = argv[0] ? argv[0] : "zic"; if (TYPE_BIT(zic_t) < 64) { fprintf(stderr, "%s: %s\n", progname, _("wild compilation-time specification of zic_t")); @@ -701,12 +995,13 @@ main(int argc, char **argv) for (k = 1; k < argc; k++) if (strcmp(argv[k], "--version") == 0) { printf("zic %s%s\n", PKGVERSION, TZVERSION); - 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:r:st:vy:")) != EOF && c != -1) + while ((c = getopt(argc, argv, "b:d:l:L:p:r:R:st:vy:")) != EOF + && c != -1) switch (c) { default: usage(stderr, EXIT_FAILURE); @@ -727,7 +1022,8 @@ main(int argc, char **argv) directory = optarg; else { fprintf(stderr, -_("%s: More than one -d option specified\n"), + _("%s: More than one -d option" + " specified\n"), progname); return EXIT_FAILURE; } @@ -737,7 +1033,8 @@ _("%s: More than one -d option specified\n"), lcltime = optarg; else { fprintf(stderr, -_("%s: More than one -l option specified\n"), + _("%s: More than one -l option" + " specified\n"), progname); return EXIT_FAILURE; } @@ -747,7 +1044,8 @@ _("%s: More than one -l option specified\n"), psxrules = optarg; else { fprintf(stderr, -_("%s: More than one -p option specified\n"), + _("%s: More than one -p option" + " specified\n"), progname); return EXIT_FAILURE; } @@ -763,22 +1061,15 @@ _("%s: More than one -p option specified\n"), tzdefault = optarg; break; case 'y': - if (yitcommand == NULL) { - warning(_("-y is obsolescent")); - yitcommand = optarg; - } else { - fprintf(stderr, -_("%s: More than one -y option specified\n"), - progname); - return EXIT_FAILURE; - } + warning(_("-y ignored")); break; case 'L': if (leapsec == NULL) leapsec = optarg; else { fprintf(stderr, -_("%s: More than one -L option specified\n"), + _("%s: More than one -L option" + " specified\n"), progname); return EXIT_FAILURE; } @@ -789,44 +1080,64 @@ _("%s: More than one -L option specified\n"), case 'r': if (timerange_given) { fprintf(stderr, -_("%s: More than one -r option specified\n"), + _("%s: More than one -r option" + " specified\n"), progname); return EXIT_FAILURE; } if (! timerange_option(optarg)) { fprintf(stderr, -_("%s: invalid time range: %s\n"), + _("%s: invalid time range: %s\n"), progname, optarg); return EXIT_FAILURE; } 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 (bloat == 0) - bloat = strcmp(ZIC_BLOAT_DEFAULT, "slim") == 0 ? -1 : 1; + 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 (strcmp(bloat_default, "slim") == 0) + bloat = -1; + else if (strcmp(bloat_default, "fat") == 0) + bloat = 1; + else + abort(); /* Configuration error. */ + } if (directory == NULL) directory = TZDIR; if (tzdefault == NULL) tzdefault = TZDEFAULT; - if (yitcommand == NULL) - yitcommand = "yearistype"; 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(); change_directory(directory); + catch_signals(); for (i = 0; i < nzones; i = j) { /* ** Find the next non-continuation zone entry. @@ -835,24 +1146,13 @@ _("%s: invalid time range: %s\n"), continue; outzone(&zones[i], j - i); } - /* - ** Make links. - */ - for (i = 0; i < nlinks; ++i) { - eat(links[i].l_filename, links[i].l_linenum); - dolink(links[i].l_from, links[i].l_to, false); - if (noise) - for (j = 0; j < nlinks; ++j) - if (strcmp(links[i].l_to, - links[j].l_from) == 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)) @@ -868,9 +1168,9 @@ componentcheck(char const *name, char const *component, ptrdiff_t component_len = component_end - component; if (component_len == 0) { if (!*name) - error (_("empty file name")); + error(_("empty file name")); else - error (_(component == name + error(_(component == name ? "file name '%s' begins with '/'" : *component_end ? "file name '%s' contains '//'" @@ -931,34 +1231,197 @@ namecheck(const char *name) 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 +/* 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; + do + s = getrandom(entropy_buffer, sizeof entropy_buffer, 0); + while (s < 0 && errno == EINTR); + + 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; + + do { + 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; + } while (rmax < UINT_FAST64_MAX); + + 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) { + dst = emalloc(size_sum(dirlen, prefixlen + suffixlen + 1)); + memcpy(dst, src, dirlen); + memcpy(dst + dirlen, prefix, prefixlen); + dst[dirlen + prefixlen + suffixlen] = '\0'; + *name = *namealloc = dst; + } + + do + r = get_rand_u64(); + while (unfair_min <= r); + + for (i = 0; i < suffixlen; i++) { + dst[dirlen + prefixlen + i] = alphabet[r % alphabetlen]; + r /= alphabetlen; + } +} + +/* 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) +{ +#if __STDC_VERSION__ < 201112 + static char const fopen_mode[] = "wb"; +#else + static char const fopen_mode[] = "wbx"; +#endif + + FILE *fp; + bool dirs_made = false; + if (!*tempname) + random_dirent(outname, tempname); + + while (! (fp = fopen(*outname, fopen_mode))) { + int fopen_errno = errno; + if (fopen_errno == ENOENT && !dirs_made) { + mkdirs(*outname, true); + dirs_made = true; + } else if (fopen_errno == EEXIST) + random_dirent(outname, tempname); + else { + fprintf(stderr, _("%s: Can't create %s/%s: %s\n"), + progname, directory, *outname, strerror(fopen_errno)); + exit(EXIT_FAILURE); + } + } + + return fp; +} + +/* 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\n"), + progname, directory, 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. */ static char * -relname(char const *from, char const *to) +relname(char const *target, char const *linkname) { - size_t i, taillen, dotdotetcsize; - size_t dir_len = 0, dotdots = 0, linksize = SIZE_MAX; - char const *f = from; + size_t i, taillen, dir_len = 0, dotdots = 0; + ptrdiff_t dotdotetcsize, linksize = INDEX_MAX; + char const *f = target; char *result = NULL; - if (*to == '/') { + if (*linkname == '/') { /* Make F absolute too. */ size_t len = strlen(directory); - bool needslash = len && directory[len - 1] != '/'; - linksize = len + needslash + strlen(from) + 1; + size_t lenslash = len + (len && directory[len - 1] != '/'); + size_t targetsize = strlen(target) + 1; + linksize = size_sum(lenslash, targetsize); f = result = emalloc(linksize); - strcpy(result, directory); + memcpy(result, directory, len); result[len] = '/'; - strcpy(result + len + needslash, from); + memcpy(result + lenslash, target, targetsize); } - for (i = 0; f[i] && f[i] == to[i]; i++) + for (i = 0; f[i] && f[i] == linkname[i]; i++) if (f[i] == '/') dir_len = i + 1; - for (; to[i]; i++) - dotdots += to[i] == '/' && to[i - 1] != '/'; + 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) { if (!result) result = emalloc(dotdotetcsize); @@ -969,83 +1432,124 @@ relname(char const *from, char const *to) return result; } -/* Hard link FROM to TO, following any symbolic links. - Return 0 if successful, an error number otherwise. */ -static int -hardlinkerr(char const *from, char const *to) +/* 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 bool +same_parent_dirs(char const *a, char const *b) { - int r = linkat(AT_FDCWD, from, AT_FDCWD, to, 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 *fromfield, char const *tofield, bool staysymlink) +dolink(char const *target, char const *linkname, bool staysymlink) { - bool todirs_made = false; + 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 (itsdir(fromfield)) { - fprintf(stderr, _("%s: link from %s/%s failed: %s\n"), - progname, directory, fromfield, strerror(EPERM)); - exit(EXIT_FAILURE); - } - if (staysymlink) - staysymlink = itssymlink(tofield); - if (remove(tofield) == 0) - todirs_made = true; - else if (errno != ENOENT) { - char const *e = strerror(errno); - fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"), - progname, directory, tofield, e); - exit(EXIT_FAILURE); + check_for_signal(); + + if (strcmp(target, "-") == 0) { + 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); + } } - link_errno = staysymlink ? ENOTSUP : hardlinkerr(fromfield, tofield); - if (link_errno == ENOENT && !todirs_made) { - mkdirs(tofield, true); - todirs_made = true; - link_errno = hardlinkerr(fromfield, tofield); + + while (true) { + 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 HAVE_LINK + /* 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; + } +#endif + 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 to %s/%s: %s\n"), + progname, directory, target, directory, outname, + strerror(link_errno)); + exit(EXIT_FAILURE); + } } if (link_errno != 0) { - bool absolute = *fromfield == '/'; - char *linkalloc = absolute ? NULL : relname(fromfield, tofield); - char const *contents = absolute ? fromfield : linkalloc; - int symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno; - if (!todirs_made - && (symlink_errno == ENOENT || symlink_errno == ENOTSUP)) { - mkdirs(tofield, true); - if (symlink_errno == ENOENT) - symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno; + bool absolute = *target == '/'; + char *linkalloc = absolute ? NULL : relname(target, linkname); + char const *contents = absolute ? target : linkalloc; + int symlink_errno; + + while (true) { + 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)); } else { FILE *fp, *tp; int c; - fp = fopen(fromfield, "rb"); + fp = fopen(target, "rb"); if (!fp) { char const *e = strerror(errno); fprintf(stderr, _("%s: Can't read %s/%s: %s\n"), - progname, directory, fromfield, e); - exit(EXIT_FAILURE); - } - tp = fopen(tofield, "wb"); - if (!tp) { - char const *e = strerror(errno); - fprintf(stderr, _("%s: Can't create %s/%s: %s\n"), - progname, directory, tofield, e); + progname, directory, target, e); exit(EXIT_FAILURE); } + tp = open_outfile(&outname, &tempname); while ((c = getc(fp)) != EOF) putc(c, tp); - close_file(fp, directory, fromfield); - close_file(tp, directory, tofield); + 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)); @@ -1054,37 +1558,20 @@ dolink(char const *fromfield, char const *tofield, bool staysymlink) strerror(symlink_errno)); } } + rename_dest(tempname, linkname); } -/* Return true if NAME is a directory. */ -static bool -itsdir(char const *name) -{ - 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) { - size_t n = strlen(name); - char *nameslashdot = emalloc(n + 3); - bool dir; - memcpy(nameslashdot, name, n); - strcpy(&nameslashdot[n], &"/."[! (n && name[n - 1] != '/')]); - dir = stat(nameslashdot, &st) == 0 || errno == EOVERFLOW; - free(nameslashdot); - return dir; - } - return false; -} - -/* Return true if NAME is a symbolic link. */ -static bool -itssymlink(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) { - char c; - return 0 <= readlink(name, &c, 1); + if (*cache == -2) { + char c = '\0'; + *cache = readlink(name, &c, 1) < 0 ? 0 : c == '/' ? 1 : -1; + } + return *cache; } /* @@ -1098,8 +1585,8 @@ itssymlink(char const *name) 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 @@ -1109,28 +1596,26 @@ associate(void) register struct rule * rp; register ptrdiff_t i, j, 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; } @@ -1161,7 +1646,7 @@ associate(void) /* ** 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); /* ** Note, though, that if there's no rule, @@ -1175,20 +1660,48 @@ associate(void) 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; + while ((ch = getc(fp)) != '\n') { + 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) { register FILE * fp; - register char ** fields; - register char * cp; register const struct lookup * lp; - register int nfields; register bool wantcont; register lineno num; - char buf[BUFSIZ]; if (strcmp(name, "-") == 0) { - name = _("standard input"); fp = stdin; } else if ((fp = fopen(name, "r")) == NULL) { const char *e = strerror(errno); @@ -1199,32 +1712,23 @@ infile(const char *name) } 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) { - static char nada; - - if (strcmp(fields[nfields], "-") == 0) - fields[nfields] = &nada; - ++nfields; - } + enum { 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) { wantcont = inzcont(fields, nfields); } 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) error(_("input line of unknown type")); @@ -1248,16 +1752,11 @@ infile(const char *name) 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); } - close_file(fp, NULL, filename); + close_file(fp, NULL, filename(fnum), NULL); if (wantcont) error(_("expected continuation line not found")); } @@ -1291,15 +1790,15 @@ gethms(char const *string, char const *errstring) default: ok = false; break; case 8: ok = '0' <= xr && xr <= '9'; - /* fallthrough */ + ATTRIBUTE_FALLTHROUGH; case 7: ok &= ssx == '.'; if (ok && noise) warning(_("fractional seconds rejected by" " pre-2018 versions of zic")); - /* fallthrough */ - case 5: ok &= mmx == ':'; /* fallthrough */ - case 3: ok &= hhx == ':'; /* fallthrough */ + ATTRIBUTE_FALLTHROUGH; + case 5: ok &= mmx == ':'; ATTRIBUTE_FALLTHROUGH; + case 3: ok &= hhx == ':'; ATTRIBUTE_FALLTHROUGH; case 1: break; } if (!ok) { @@ -1329,7 +1828,7 @@ getsave(char *field, bool *isdst) { int dst = -1; zic_t save; - size_t fieldlen = strlen(field); + ptrdiff_t fieldlen = strlen(field); if (fieldlen != 0) { char *ep = field + fieldlen - 1; switch (*ep) { @@ -1345,7 +1844,7 @@ getsave(char *field, bool *isdst) static void inrule(char **fields, int nfields) { - static struct rule r; + struct rule r; if (nfields != RULE_FIELDS) { error(_("wrong number of fields on Rule line")); @@ -1360,13 +1859,15 @@ inrule(char **fields, int nfields) 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 = estrdup(fields[RF_NAME]); + r.r_abbrvar = estrdup(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); @@ -1383,25 +1884,23 @@ inzone(char **fields, int nfields) return false; } if (lcltime != NULL && strcmp(fields[ZF_NAME], tzdefault) == 0) { - error( -_("\"Zone %s\" line and -l option are mutually exclusive"), - tzdefault); - return false; + 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"), - TZDEFRULES); - return false; + error(_("\"Zone %s\" line and -p option are mutually exclusive"), + TZDEFRULES); + return false; } for (i = 0; i < nzones; ++i) if (zones[i].z_name != NULL && strcmp(zones[i].z_name, fields[ZF_NAME]) == 0) { error(_("duplicate zone name %s" " (file \"%s\", line %"PRIdMAX")"), - fields[ZF_NAME], - zones[i].z_filename, - zones[i].z_linenum); + fields[ZF_NAME], + filename(zones[i].z_filenum), + zones[i].z_linenum); return false; } return inzsub(fields, nfields, false); @@ -1422,7 +1921,8 @@ inzsub(char **fields, int nfields, bool iscont) { register char * cp; char * cp1; - static struct zone z; + struct zone z; + int format_len; register int i_stdoff, i_rule, i_format; register int i_untilyear, i_untilmonth; register int i_untilday, i_untiltime; @@ -1436,7 +1936,6 @@ inzsub(char **fields, int nfields, bool iscont) i_untilmonth = ZFC_TILMONTH; i_untilday = ZFC_TILDAY; i_untiltime = ZFC_TILTIME; - z.z_name = NULL; } else if (!namecheck(fields[ZF_NAME])) return false; else { @@ -1447,9 +1946,8 @@ inzsub(char **fields, int nfields, bool iscont) 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], '%')) != 0) { @@ -1459,29 +1957,24 @@ inzsub(char **fields, int nfields, bool iscont) 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, + 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"); + (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 && @@ -1490,12 +1983,20 @@ inzsub(char **fields, int nfields, bool iscont) 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" - )); - return false; + error(_("Zone continuation line end time is" + " not after end time of previous line")); + return false; } } + z.z_name = iscont ? NULL : estrdup(fields[ZF_NAME]); + z.z_rule = estrdup(fields[i_rule]); + z.z_format = cp1 = estrdup(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; /* @@ -1506,7 +2007,7 @@ inzsub(char **fields, int nfields, bool iscont) } static zic_t -getleapdatetime(char **fields, int nfields, bool expire_line) +getleapdatetime(char **fields, bool expire_line) { register const char * cp; register const struct lookup * lp; @@ -1584,7 +2085,7 @@ inleap(char **fields, int nfields) if (nfields != LEAP_FIELDS) 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) { struct lookup const *lp = byword(fields[LP_ROLL], leap_types); if (!lp) @@ -1612,7 +2113,7 @@ inexpires(char **fields, int nfields) else if (0 <= leapexpires) error(_("multiple Expires lines")); else - leapexpires = getleapdatetime(fields, nfields, true); + leapexpires = getleapdatetime(fields, true); } static void @@ -1624,21 +2125,21 @@ inlink(char **fields, int nfields) error(_("wrong number of fields on Link line")); return; } - if (*fields[LF_FROM] == '\0') { - error(_("blank FROM field on Link line")); + if (*fields[LF_TARGET] == '\0') { + error(_("blank TARGET field on Link line")); return; } - if (! namecheck(fields[LF_TO])) + if (! namecheck(fields[LF_LINKNAME])) return; - l.l_filename = filename; + l.l_filenum = filenum; l.l_linenum = linenum; - l.l_from = ecpyalloc(fields[LF_FROM]); - l.l_to = ecpyalloc(fields[LF_TO]); + l.l_target = estrdup(fields[LF_TARGET]); + l.l_linkname = estrdup(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) @@ -1651,12 +2152,12 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp, 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 = estrdup(timep); if (*dp != '\0') { ep = dp + strlen(dp) - 1; switch (lowerit(*ep)) { @@ -1686,59 +2187,41 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp, */ cp = loyearp; lp = byword(cp, begin_years); - rp->r_lowasnum = lp == NULL; - if (!rp->r_lowasnum) switch (lp->l_value) { + if (lp) switch (lp->l_value) { case YR_MINIMUM: - rp->r_loyear = ZIC_MIN; + warning(_("FROM year \"%s\" is obsolete;" + " treated as %d"), + cp, YEAR_32BIT_MIN - 1); + rp->r_loyear = YEAR_32BIT_MIN - 1; break; - case YR_MAXIMUM: - rp->r_loyear = ZIC_MAX; - 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); rp->r_hiwasnum = lp == NULL; 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') - rp->r_yrtype = NULL; - else { - if (rp->r_loyear == rp->r_hiyear) { - error(_("typed single year")); - return; - } - warning(_("year type \"%s\" is obsolete; use \"-\" instead"), + if (*typep != '\0') { + error(_("year type \"%s\" is unsupported; use \"-\" instead"), typep); - rp->r_yrtype = ecpyalloc(typep); + return false; } /* ** Day work. @@ -1749,7 +2232,7 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp, ** Sun<=20 ** Sun>=7 */ - dp = ecpyalloc(dayp); + dp = estrdup(dayp); if ((lp = byword(dp, lasts)) != NULL) { rp->r_dycode = DC_DOWLEQ; rp->r_wday = lp->l_value; @@ -1768,12 +2251,12 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp, if (*ep++ != '=') { 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; } @@ -1782,36 +2265,37 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp, (rp->r_dayofmonth > len_months[1][rp->r_month])) { 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) { register int i; register 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) { register int i; register 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]; @@ -1835,39 +2319,57 @@ puttzcodepass(zic_t val, FILE *fp, int pass) 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; - - return (a < b) ? -1 : (a > b); + struct attype const *ap = avp, *bp = bvp; + zic_t a = ap->at, b = bp->at; + return a < b ? -1 : a > b; } struct timerange { int defaulttype; ptrdiff_t base, count; int 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 && trans[r.leapbase + 1] <= lo) { r.leapcount--; r.leapbase++; } + while (0 < r.leapbase + && ((corr[r.leapbase - 1] < corr[r.leapbase]) + != (0 < corr[r.leapbase]))) { + 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]) r.leapcount--; } + /* Determine whether to append an expiration to the leap second table. */ + r.leapexpiry = 0 <= leapexpires && leapexpires - 1 <= hi; + return r; } @@ -1878,20 +2380,18 @@ writezone(const char *const name, const char *const string, char version, register FILE * fp; register ptrdiff_t i, j; register 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. */ - zic_t *ats = emalloc(align_to(size_product(nats, sizeof *ats + 1), - _Alignof(zic_t))); - void *typesptr = ats + nats; + as this is a bit faster. Do not malloc(0) if !timecnt, + as that might return NULL even on success. */ + zic_t *ats = emalloc(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. @@ -1959,60 +2459,50 @@ writezone(const char *const name, const char *const string, char version, } } - /* 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 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); - - /* - ** Remove old file, if any, to snap links. - */ - if (remove(name) == 0) - dir_checked = true; - else if (errno != ENOENT) { - const char *e = strerror(errno); - - fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"), - progname, directory, name, e); - exit(EXIT_FAILURE); - } - fp = fopen(name, "wb"); - if (!fp) { - int fopen_errno = errno; - if (fopen_errno == ENOENT && !dir_checked) { - mkdirs(name, true); - fp = fopen(name, "wb"); - fopen_errno = errno; + 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); + + /* 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. */ + for (pass = 1; pass <= 2; pass++) { + struct timerange const *r = pass == 1 ? &range32 : &range64; + if (pass == 1 && !want_bloat()) + continue; + if (r->leapexpiry) { + if (noise) + warning(_("%s: pre-2021b clients may mishandle" + " leap second expiry"), + name); + version = '4'; } - if (!fp) { - fprintf(stderr, _("%s: Can't create %s/%s: %s\n"), - progname, directory, name, strerror(fopen_errno)); - exit(EXIT_FAILURE); + if (0 < r->leapcount + && corr[r->leapbase] != 1 && corr[r->leapbase] != -1) { + 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) { register ptrdiff_t thistimei, thistimecnt, thistimelim; register int thisleapi, thisleapcnt, thisleaplim; - int currenttype, thisdefaulttype; - bool locut, hicut; - zic_t lo; + struct tzhead tzh; + int pretranstype = -1, thisdefaulttype; + bool locut, hicut, thisleapexpiry; + zic_t lo, thismin, thismax; int old0; char omittype[TZ_MAX_TYPES]; int typemap[TZ_MAX_TYPES]; @@ -2023,27 +2513,15 @@ writezone(const char *const name, const char *const string, char version, int indmap[TZ_MAX_CHARS]; 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 { thisdefaulttype = range64.defaulttype; thistimei = range64.base; @@ -2051,37 +2529,49 @@ writezone(const char *const name, const char *const string, char version, 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")); - /* 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 + locut = thismin < lo_time && lo_time <= thismax; + hicut = thismin <= hi_time && hi_time < thismax; + thistimelim = thistimei + thistimecnt; + memset(omittype, true, typecnt); + + /* 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) { - thistimei--; - thistimecnt++; - locut = false; + if ((locut || (pass == 1 && thistimei)) + && ! (thistimecnt && ats[thistimei] == lo_time)) { + 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 OLD0 and THISDEFAULTTYPE so that @@ -2102,7 +2592,13 @@ writezone(const char *const name, const char *const string, char version, register int mrudst, mrustd, hidst, histd, 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 mrustd = types[i]; @@ -2172,19 +2668,20 @@ writezone(const char *const name, const char *const string, char version, indmap[desigidx[i]] = j; } 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); @@ -2209,25 +2706,21 @@ writezone(const char *const name, const char *const string, char version, /* 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++) { int h = (i == old0 ? thisdefaulttype @@ -2241,6 +2734,7 @@ writezone(const char *const name, const char *const string, char version, if (thischarcnt != 0) fwrite(thischars, sizeof thischars[0], thischarcnt, fp); + thisleaplim = thisleapi + thisleapcnt; for (i = thisleapi; i < thisleaplim; ++i) { register zic_t todo; @@ -2264,6 +2758,15 @@ writezone(const char *const name, const char *const string, char version, puttzcodepass(todo, fp, pass); puttzcode(corr[i], fp); } + if (thisleapexpiry) { + /* Append a no-op leap correction indicating when the leap + second table expires. Although this does not conform to + Internet RFC 8536, 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 ? corr[thisleaplim - 1] : 0, fp); + } if (stdcnt != 0) for (i = old0; i < typecnt; i++) if (!omittype[i]) @@ -2274,7 +2777,8 @@ writezone(const char *const name, const char *const string, char version, 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); } @@ -2314,13 +2818,15 @@ abbroffset(char *buf, zic_t offset) } } -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) { register char * cp; register char * slashp; - register size_t len; + ptrdiff_t len; char const *format = zp->z_format; slashp = strchr(format, '/'); @@ -2330,6 +2836,8 @@ doabbr(char *abbr, struct zone const *zp, char const *letters, 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); @@ -2467,11 +2975,17 @@ rule_cmp(struct rule const *a, struct rule const *b) 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 POSIX.1-2017 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 exissts. */ static int stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) { @@ -2480,12 +2994,16 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) register struct rule * stdrp; register struct rule * dstrp; register ptrdiff_t i; - register const char * abbrvar; register int compat = 0; register 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'; @@ -2495,65 +3013,64 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) 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_yrtype != NULL) - continue; - if (!rp->r_isdst) { - if (stdrp == NULL) - stdrp = rp; - else return -1; - } else { - if (dstrp == NULL) - dstrp = rp; - else return -1; - } - } - if (stdrp == NULL && dstrp == NULL) { - /* - ** There are no rules running through "max". - ** Find the latest std rule in stdabbrrp - ** and latest rule of any type in stdrp. - */ - register 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); + 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; + } else if (0 < dstcmp) { + /* DST all year. Use an abbreviation like + "XXX3EDT4,0/0,J365/23" for EDT (-04) all year. */ + 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'; return -1; @@ -2561,11 +3078,11 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) 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'; return -1; @@ -2573,7 +3090,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) 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'; return -1; @@ -2582,7 +3099,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) 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'; return -1; @@ -2595,35 +3112,29 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) static void outzone(const struct zone *zpfirst, ptrdiff_t zonecount) { - register const struct zone * zp; - register struct rule * rp; register ptrdiff_t i, j; - register bool usestart, useuntil; register zic_t starttime, untiltime; - register zic_t stdoff; - register zic_t save; - register zic_t year; - register zic_t startoff; register bool startttisstd; register bool startttisut; - register int type; register char * startbuf; register char * ab; register char * envvar; register int max_abbr_len; register int max_envvar_len; - register bool prodstic; /* all rules are min to max */ register int compat; register bool do_extend; register 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; + 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); @@ -2635,7 +3146,6 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) timecnt = 0; typecnt = 0; charcnt = 0; - prodstic = zonecount == 1; /* ** Thanks to Earl Chew ** for noting the need to unconditionally initialize startttisstd. @@ -2648,29 +3158,27 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) updateminmax(leapmaxyear + (leapmaxyear < ZIC_MAX)); } 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 POSIX.1-2017 environment variable" + " for zone"), zpfirst->z_name); else if (compat != 0) { /* Circa-COMPAT clients, and earlier clients, might @@ -2682,64 +3190,44 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) } } 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 min_year = ZIC_MIN; if (max_year <= ZIC_MAX - years_of_observations) 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), @@ -2747,23 +3235,31 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) startttisut); if (usestart) { addtt(starttime, type); + if (useuntil && nonTZlimtime < starttime) { + nonTZlimtime = starttime; + nonTZlimtype = type; + } usestart = false; } else defaulttype = type; - } else for (year = min_year; year <= max_year; ++year) { + } else { + zic_t year; + for (year = min_year; year <= max_year; ++year) { if (useuntil && year > zp->z_untilrule.r_hiyear) break; /* ** Mark which rules to do in the current year. ** For those to do, calculate rpytime(rp, year); + ** The former TYPE field was also considered here. */ 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 && - yearistype(year, rp->r_yrtype); + year <= rp->r_hiyear; if (rp->r_todo) { rp->r_temp = rpytime(rp, year); rp->r_todo @@ -2775,6 +3271,8 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) register ptrdiff_t k; register zic_t jtime, ktime; register zic_t offset; + struct rule *rp; + int type; INITIALIZE(ktime); if (useuntil) { @@ -2797,15 +3295,15 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) */ 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; + jtime = r->r_temp; if (jtime == min_time || jtime == max_time) continue; @@ -2816,12 +3314,12 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) } else if (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); } } @@ -2829,8 +3327,15 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) break; /* go on to next year */ rp = &zp->z_rules[k]; rp->r_todo = false; - if (useuntil && ktime >= untiltime) + 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; @@ -2856,41 +3361,36 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) 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(_("can't determine time zone abbreviation to use just after until time")); + error(_("can't determine time zone abbreviation" + " to use just after until time")); else { - bool isdst = startoff != zp->z_stdoff; - type = addtype(startoff, startbuf, isdst, - startttisstd, startttisut); + int type = addtype(startoff, startbuf, isdst, + startttisstd, startttisut); if (defaulttype < 0 && !isdst) defaulttype = type; addtt(starttime, type); @@ -2911,12 +3411,38 @@ error(_("can't determine time zone abbreviation to use just after until time")); } 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, + ** If we're extending the explicitly listed observations for + ** 400 years because we can't fill the POSIX.1-2017 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 @@ -3005,6 +3531,10 @@ leapadd(zic_t t, int correction, int rolling) error(_("too many leap seconds")); exit(EXIT_FAILURE); } + if (rolling && (lo_time != min_time || hi_time != max_time)) { + error(_("Rolling leap seconds not supported with -r")); + exit(EXIT_FAILURE); + } for (i = 0; i < leapcnt; ++i) if (t <= trans[i]) break; @@ -3037,67 +3567,15 @@ adjleap(void) last = corr[i] += last; } - if (leapexpires < 0) { - leapexpires = comment_leapexpires; - if (0 <= leapexpires) - warning(_("\"#expires\" is obsolescent; use \"Expires\"")); - } - if (0 <= leapexpires) { leapexpires = oadd(leapexpires, last); if (! (leapcnt == 0 || (trans[leapcnt - 1] < leapexpires))) { error(_("last Leap time does not precede Expires time")); exit(EXIT_FAILURE); } - if (leapexpires <= hi_time) - hi_time = leapexpires - 1; } } -static char * -shellquote(char *b, char const *s) -{ - *b++ = '\''; - while (*s) { - if (*s == '\'') - *b++ = '\'', *b++ = '\\', *b++ = '\''; - *b++ = *s++; - } - *b++ = '\''; - return b; -} - -static bool -yearistype(zic_t year, const char *type) -{ - char *buf; - char *b; - int result; - - if (type == NULL || *type == '\0') - return true; - buf = emalloc(1 + 4 * strlen(yitcommand) + 2 - + INT_STRLEN_MAXIMUM(zic_t) + 2 + 4 * strlen(type) + 2); - b = shellquote(buf, yitcommand); - *b++ = ' '; - b += sprintf(b, "%"PRIdZIC, year); - *b++ = ' '; - b = shellquote(b, type); - *b = '\0'; - result = system(buf); - if (WIFEXITED(result)) { - int status = WEXITSTATUS(result); - if (status <= 1) { - free(buf); - return status == 0; - } - } - error(_("Wild result from command execution")); - fprintf(stderr, _("%s: command was '%s', result was %d\n"), - progname, buf, result); - exit(EXIT_FAILURE); -} - /* Is A a space character in the C locale? */ static bool is_space(char a) @@ -3149,7 +3627,7 @@ lowerit(char a) } /* case-insensitive equality */ -static ATTRIBUTE_PURE bool +ATTRIBUTE_REPRODUCIBLE static bool ciequal(register const char *ap, register const char *bp) { while (lowerit(*ap) == lowerit(*bp++)) @@ -3158,7 +3636,7 @@ ciequal(register const char *ap, register const char *bp) return false; } -static ATTRIBUTE_PURE bool +ATTRIBUTE_REPRODUCIBLE static bool itsabbr(register const char *abbr, register const char *word) { if (lowerit(*abbr) != lowerit(*word)) @@ -3174,7 +3652,7 @@ itsabbr(register const char *abbr, register const char *word) /* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case. */ -static ATTRIBUTE_PURE bool +ATTRIBUTE_REPRODUCIBLE static bool ciprefix(char const *abbr, char const *word) { do @@ -3240,23 +3718,20 @@ byword(const char *word, const struct lookup *table) return foundlp; } -static char ** -getfields(register char *cp) +static int +getfields(char *cp, char **array, int arrayelts) { register char * dp; - register char ** array; register 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++) != '"') ++dp; @@ -3271,43 +3746,50 @@ getfields(register char *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); } -static ATTRIBUTE_PURE zic_t +ATTRIBUTE_REPRODUCIBLE 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(); } -static ATTRIBUTE_PURE zic_t +ATTRIBUTE_REPRODUCIBLE 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; +#ifdef ckd_add + zic_t sum; + if (!ckd_add(&sum, t1, t2) && min_time <= sum && sum <= max_time) + return sum; +#else + if (t1 < 0 ? min_time - t1 <= t2 : t2 <= max_time - t1) + return t1 + t2; +#endif + if (t1 == min_time || t1 == max_time) + return t1; + time_overflow(); } /* @@ -3321,32 +3803,28 @@ rpytime(const struct rule *rp, zic_t wantedy) register int m, i; register zic_t dayoff; /* with a nod to Margaret O. */ register 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) { i = len_months[isleap(y)][m]; @@ -3365,30 +3843,21 @@ rpytime(const struct rule *rp, zic_t wantedy) --i; dayoff = oadd(dayoff, i); if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ) { - register 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; } else { dayoff = oadd(dayoff, -1); if (--wday < 0) - wday = LDAYSPERWEEK - 1; + wday = DAYSPERWEEK - 1; --i; } if (i < 0 || i >= len_months[isleap(y)][m]) { @@ -3440,14 +3909,12 @@ mp = _("time zone abbreviation differs from POSIX standard"); /* 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) { - register char * name; - register char * cp; - - cp = name = ecpyalloc(argname); + char *name = estrdup(argname); + char *cp = name; /* On MS-Windows systems, do not worry about drive letters or backslashes, as this should suffice in practice. Time zone @@ -3471,11 +3938,18 @@ mkdirs(char const *argname, bool ancestors) ** is checked anyway if the mkdir fails. */ if (mkdir(name, MKDIR_UMASK) != 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. */ + /* 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 != EEXIST && !itsdir(name)) { + if (err == ELOOP || err == ENAMETOOLONG + || err == ENOENT || err == ENOTDIR) { error(_("%s: Can't create directory %s: %s"), progname, name, strerror(err)); exit(EXIT_FAILURE); |