diff options
Diffstat (limited to 'src/timezone/zic.c')
-rw-r--r-- | src/timezone/zic.c | 712 |
1 files changed, 442 insertions, 270 deletions
diff --git a/src/timezone/zic.c b/src/timezone/zic.c index b546a17372..5fa0a81262 100644 --- a/src/timezone/zic.c +++ b/src/timezone/zic.c @@ -8,9 +8,10 @@ #include "postgres_fe.h" -#include <locale.h> +#include <fcntl.h> #include <sys/stat.h> #include <time.h> +#include <unistd.h> #include "pg_getopt.h" @@ -35,11 +36,24 @@ typedef int64 zic_t; #define MKDIR_UMASK 0755 #endif #endif +#ifndef AT_SYMLINK_FOLLOW +#define linkat(fromdir, from, todir, to, flag) \ + (itssymlink(from) ? (errno = ENOTSUP, -1) : link(from, to)) +#endif + +/* The maximum ptrdiff_t value, for pre-C99 platforms. */ +#ifndef PTRDIFF_MAX +static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t)); +#endif + +/* The type and printf format for line numbers. */ +typedef int lineno_t; +#define PRIdLINENO "d" struct rule { const char *r_filename; - int r_linenum; + lineno_t r_linenum; const char *r_name; zic_t r_loyear; /* for example, 1986 */ @@ -61,7 +75,7 @@ struct rule zic_t r_stdoff; /* offset from standard time */ const char *r_abbrvar; /* variable part of abbreviation */ - int r_todo; /* a rule to do (used in outzone) */ + bool r_todo; /* a rule to do (used in outzone) */ zic_t r_temp; /* used in outzone */ }; @@ -76,7 +90,7 @@ struct rule struct zone { const char *z_filename; - int z_linenum; + lineno_t z_linenum; const char *z_name; zic_t z_gmtoff; @@ -87,7 +101,7 @@ struct zone zic_t z_stdoff; struct rule *z_rules; - int z_nrules; + ptrdiff_t z_nrules; struct rule z_untilrule; zic_t z_untiltime; @@ -105,7 +119,7 @@ static int addtype(zic_t, char const *, bool, bool, bool); static void leapadd(zic_t, bool, int, int); static void adjleap(void); static void associate(void); -static void dolink(const char *fromfield, const char *tofield); +static void dolink(const char *, const char *, bool); static char **getfields(char *buf); static zic_t gethms(const char *string, const char *errstring, bool); @@ -116,26 +130,36 @@ 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 int itsdir(const char *name); +static bool itsdir(char const *); +static bool itssymlink(char const *); static bool is_alpha(char a); static char lowerit(char); -static bool mkdirs(char *); +static void mkdirs(char const *, bool); static void newabbr(const char *abbr); static zic_t oadd(zic_t t1, zic_t t2); -static void outzone(const struct zone * zp, int ntzones); +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, 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(int year, const char *type); +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 + tz binary 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. */ +enum +{ +WORK_AROUND_QTBUG_53071 = true}; + static int charcnt; static bool errors; static bool warnings; @@ -144,7 +168,7 @@ static int leapcnt; static bool leapseen; static zic_t leapminyear; static zic_t leapmaxyear; -static int linenum; +static lineno_t linenum; static int max_abbrvar_len = PERCENT_Z_LEN_BOUND; static int max_format_len; static zic_t max_year; @@ -153,10 +177,10 @@ static bool noise; static bool print_abbrevs; static zic_t print_cutoff; static const char *rfilename; -static int rlinenum; +static lineno_t rlinenum; static const char *progname; -static int timecnt; -static int timecnt_alloc; +static ptrdiff_t timecnt; +static ptrdiff_t timecnt_alloc; static int typecnt; /* @@ -241,24 +265,24 @@ static int typecnt; #define YR_ONLY 2 static struct rule *rules; -static int nrules; /* number of rules */ -static int nrules_alloc; +static ptrdiff_t nrules; /* number of rules */ +static ptrdiff_t nrules_alloc; static struct zone *zones; -static int nzones; /* number of zones */ -static int nzones_alloc; +static ptrdiff_t nzones; /* number of zones */ +static ptrdiff_t nzones_alloc; struct link { const char *l_filename; - int l_linenum; + lineno_t l_linenum; const char *l_from; const char *l_to; }; static struct link *links; -static int nlinks; -static int nlinks_alloc; +static ptrdiff_t nlinks; +static ptrdiff_t nlinks_alloc; struct lookup { @@ -346,6 +370,7 @@ static const int len_years[2] = { static struct attype { zic_t at; + bool dontmerge; unsigned char type; } *attypes; static zic_t gmtoffs[TZ_MAX_TYPES]; @@ -404,17 +429,17 @@ ecpyalloc(char const * str) } static void * -growalloc(void *ptr, size_t itemsize, int nitems, int *nitems_alloc) +growalloc(void *ptr, size_t itemsize, ptrdiff_t nitems, ptrdiff_t *nitems_alloc) { if (nitems < *nitems_alloc) return ptr; else { - int amax = INT_MAX < SIZE_MAX ? INT_MAX : SIZE_MAX; + ptrdiff_t amax = PTRDIFF_MAX - WORK_AROUND_QTBUG_53071; if ((amax - 1) / 3 * 2 < *nitems_alloc) - memory_exhausted(_("int overflow")); - *nitems_alloc = *nitems_alloc + (*nitems_alloc >> 1) + 1; + memory_exhausted(_("integer overflow")); + *nitems_alloc += (*nitems_alloc >> 1) + 1; return erealloc(ptr, size_product(*nitems_alloc, itemsize)); } } @@ -424,7 +449,7 @@ growalloc(void *ptr, size_t itemsize, int nitems, int *nitems_alloc) */ static void -eats(const char *name, int num, const char *rname, int rnum) +eats(char const * name, lineno_t num, char const * rname, lineno_t rnum) { filename = name; linenum = num; @@ -433,7 +458,7 @@ eats(const char *name, int num, const char *rname, int rnum) } static void -eat(const char *name, int num) +eat(char const * name, lineno_t num) { eats(name, num, NULL, -1); } @@ -446,10 +471,10 @@ verror(const char *string, va_list args) * "*" -v on BSD systems. */ if (filename) - fprintf(stderr, _("\"%s\", line %d: "), filename, linenum); + fprintf(stderr, _("\"%s\", line %" PRIdLINENO ": "), filename, linenum); vfprintf(stderr, string, args); if (rfilename != NULL) - fprintf(stderr, _(" (rule from \"%s\", line %d)"), + fprintf(stderr, _(" (rule from \"%s\", line %" PRIdLINENO ")"), rfilename, rlinenum); fprintf(stderr, "\n"); } @@ -478,17 +503,17 @@ warning(const char *string,...) } static void -close_file(FILE *stream, char const * name) +close_file(FILE *stream, char const * dir, char const * name) { char const *e = (ferror(stream) ? _("I/O error") : fclose(stream) != 0 ? strerror(errno) : NULL); if (e) { - fprintf(stderr, "%s: ", progname); - if (name) - fprintf(stderr, "%s: ", name); - fprintf(stderr, "%s\n", e); + fprintf(stderr, "%s: %s%s%s%s%s\n", progname, + dir ? dir : "", dir ? "/" : "", + name ? name : "", name ? ": " : "", + e); exit(EXIT_FAILURE); } } @@ -503,10 +528,34 @@ usage(FILE *stream, int status) "Report bugs to %s.\n"), progname, progname, PACKAGE_BUGREPORT); if (status == EXIT_SUCCESS) - close_file(stream, NULL); + close_file(stream, NULL, NULL); exit(status); } +/* Change the working directory to DIR, possibly creating DIR and its + ancestors. After this is done, all files are accessed with names + relative to DIR. */ +static void +change_directory(char const * dir) +{ + if (chdir(dir) != 0) + { + int chdir_errno = errno; + + if (chdir_errno == ENOENT) + { + mkdirs(dir, false); + chdir_errno = chdir(dir) == 0 ? 0 : errno; + } + if (chdir_errno != 0) + { + fprintf(stderr, _("%s: Can't chdir to %s: %s\n"), + progname, dir, strerror(chdir_errno)); + exit(EXIT_FAILURE); + } + } +} + static const char *psxrules; static const char *lcltime; static const char *directory; @@ -516,9 +565,10 @@ static const char *yitcommand; int main(int argc, char *argv[]) { - int i; - int j; - int c; + int c, + k; + ptrdiff_t i, + j; #ifndef WIN32 umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH)); @@ -530,14 +580,14 @@ main(int argc, char *argv[]) _("wild compilation-time specification of zic_t")); return EXIT_FAILURE; } - for (i = 1; i < argc; ++i) - if (strcmp(argv[i], "--version") == 0) + for (k = 1; k < argc; k++) + if (strcmp(argv[k], "--version") == 0) { printf("zic %s\n", PG_VERSION); - close_file(stdout, NULL); + close_file(stdout, NULL, NULL); return EXIT_SUCCESS; } - else if (strcmp(argv[i], "--help") == 0) + else if (strcmp(argv[k], "--help") == 0) { usage(stdout, EXIT_SUCCESS); } @@ -625,11 +675,12 @@ main(int argc, char *argv[]) adjleap(); } - for (i = optind; i < argc; ++i) - infile(argv[i]); + for (k = optind; k < argc; k++) + infile(argv[k]); if (errors) return EXIT_FAILURE; associate(); + change_directory(directory); for (i = 0; i < nzones; i = j) { /* @@ -646,7 +697,7 @@ main(int argc, char *argv[]) for (i = 0; i < nlinks; ++i) { eat(links[i].l_filename, links[i].l_linenum); - dolink(links[i].l_from, links[i].l_to); + dolink(links[i].l_from, links[i].l_to, false); if (noise) for (j = 0; j < nlinks; ++j) if (strcmp(links[i].l_to, @@ -656,12 +707,12 @@ main(int argc, char *argv[]) if (lcltime != NULL) { eat(_("command line"), 1); - dolink(lcltime, TZDEFAULT); + dolink(lcltime, TZDEFAULT, true); } if (psxrules != NULL) { eat(_("command line"), 1); - dolink(psxrules, TZDEFRULES); + dolink(psxrules, TZDEFRULES, true); } if (warnings && (ferror(stderr) || fclose(stderr) != 0)) return EXIT_FAILURE; @@ -675,7 +726,7 @@ componentcheck(char const * name, char const * component, enum { component_len_max = 14}; - size_t component_len = component_end - component; + ptrdiff_t component_len = component_end - component; if (component_len == 0) { @@ -693,8 +744,10 @@ componentcheck(char const * name, char const * component, if (0 < component_len && component_len <= 2 && component[0] == '.' && component_end[-1] == '.') { + int len = component_len; + error(_("file name '%s' contains '%.*s' component"), - name, (int) component_len, component); + name, len, component); return false; } if (noise) @@ -751,131 +804,160 @@ 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 + * relative or absolute. + */ +#ifdef HAVE_SYMLINK static char * -relname(char const * dir, char const * base) +relname(char const * from, char const * to) +{ + size_t i, + taillen, + dotdotetcsize; + size_t dir_len = 0, + dotdots = 0, + linksize = SIZE_MAX; + char const *f = from; + char *result = NULL; + + if (*to == '/') + { + /* Make F absolute too. */ + size_t len = strlen(directory); + bool needslash = len && directory[len - 1] != '/'; + + linksize = len + needslash + strlen(from) + 1; + f = result = emalloc(linksize); + strcpy(result, directory); + result[len] = '/'; + strcpy(result + len + needslash, from); + } + for (i = 0; f[i] && f[i] == to[i]; i++) + if (f[i] == '/') + dir_len = i + 1; + for (; to[i]; i++) + dotdots += to[i] == '/' && to[i - 1] != '/'; + taillen = strlen(f + dir_len); + dotdotetcsize = 3 * dotdots + taillen + 1; + if (dotdotetcsize <= linksize) + { + if (!result) + result = emalloc(dotdotetcsize); + for (i = 0; i < dotdots; i++) + memcpy(result + 3 * i, "../", 3); + memmove(result + 3 * dotdots, f + dir_len, taillen + 1); + } + return result; +} +#endif /* HAVE_SYMLINK */ + +/* Hard link FROM to TO, following any symbolic links. + Return 0 if successful, an error number otherwise. */ +static int +hardlinkerr(char const * from, char const * to) { - if (*base == '/') - return ecpyalloc(base); - else - { - size_t dir_len = strlen(dir); - bool needs_slash = dir_len && dir[dir_len - 1] != '/'; - char *result = emalloc(dir_len + needs_slash + strlen(base) + 1); + int r = linkat(AT_FDCWD, from, AT_FDCWD, to, AT_SYMLINK_FOLLOW); - result[dir_len] = '/'; - strcpy(result + dir_len + needs_slash, base); - return memcpy(result, dir, dir_len); - } + return r == 0 ? 0 : errno; } static void -dolink(char const * fromfield, char const * tofield) +dolink(char const * fromfield, char const * tofield, bool staysymlink) { - char *fromname; - char *toname; - int fromisdir; - - fromname = relname(directory, fromfield); - toname = relname(directory, tofield); + bool todirs_made = false; + int link_errno; /* * We get to be careful here since there's a fair chance of root running * us. */ - fromisdir = itsdir(fromname); - if (fromisdir) + if (itsdir(fromfield)) { - char const *e = strerror(fromisdir < 0 ? errno : EPERM); + 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: link from %s failed: %s"), - progname, fromname, e); + fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"), + progname, directory, tofield, e); exit(EXIT_FAILURE); } - if (link(fromname, toname) != 0) + link_errno = staysymlink ? ENOTSUP : hardlinkerr(fromfield, tofield); + if (link_errno == ENOENT && !todirs_made) + { + mkdirs(tofield, true); + todirs_made = true; + link_errno = hardlinkerr(fromfield, tofield); + } + if (link_errno != 0) { - int link_errno = errno; - bool retry_if_link_supported = false; +#ifdef HAVE_SYMLINK + 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 (link_errno == ENOENT || link_errno == ENOTSUP) + if (symlink_errno == ENOENT && !todirs_made) { - if (!mkdirs(toname)) - exit(EXIT_FAILURE); - retry_if_link_supported = true; + mkdirs(tofield, true); + symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno; } - if ((link_errno == EEXIST || link_errno == ENOTSUP) - && itsdir(toname) == 0 - && (remove(toname) == 0 || errno == ENOENT)) - retry_if_link_supported = true; - if (retry_if_link_supported && link_errno != ENOTSUP) - link_errno = link(fromname, toname) == 0 ? 0 : errno; - if (link_errno != 0) + free(linkalloc); + if (symlink_errno == 0) { -#ifdef HAVE_SYMLINK - const char *s = fromfield; - const char *t; - char *p; - size_t dotdots = 0; - char *symlinkcontents; - int symlink_result; - - do - t = s; - while ((s = strchr(s, '/')) - && strncmp(fromfield, tofield, ++s - fromfield) == 0); - - for (s = tofield + (t - fromfield); *s; s++) - dotdots += *s == '/'; - symlinkcontents = emalloc(3 * dotdots + strlen(t) + 1); - for (p = symlinkcontents; dotdots-- != 0; p += 3) - memcpy(p, "../", 3); - strcpy(p, t); - symlink_result = symlink(symlinkcontents, toname); - free(symlinkcontents); - if (symlink_result == 0) - { - if (link_errno != ENOTSUP) - warning(_("symbolic link used because hard link failed: %s"), - strerror(link_errno)); - } - else + if (link_errno != ENOTSUP) + warning(_("symbolic link used because hard link failed: %s"), + strerror(link_errno)); + } + else #endif /* HAVE_SYMLINK */ - { - FILE *fp, - *tp; - int c; + { + FILE *fp, + *tp; + int c; - fp = fopen(fromname, "rb"); - if (!fp) - { - const char *e = strerror(errno); + fp = fopen(fromfield, "rb"); + if (!fp) + { + char const *e = strerror(errno); - fprintf(stderr, - _("%s: Can't read %s: %s\n"), - progname, fromname, e); - exit(EXIT_FAILURE); - } - tp = fopen(toname, "wb"); - if (!tp) - { - const char *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\n"), - progname, toname, e); - exit(EXIT_FAILURE); - } - while ((c = getc(fp)) != EOF) - putc(c, tp); - close_file(fp, fromname); - close_file(tp, toname); - if (link_errno != ENOTSUP) - warning(_("copy used because hard link failed: %s"), - strerror(link_errno)); + fprintf(stderr, _("%s: Can't create %s/%s: %s\n"), + progname, directory, tofield, e); + exit(EXIT_FAILURE); } + while ((c = getc(fp)) != EOF) + putc(c, tp); + close_file(fp, directory, fromfield); + close_file(tp, directory, tofield); + if (link_errno != ENOTSUP) + warning(_("copy used because hard link failed: %s"), + strerror(link_errno)); +#ifdef HAVE_SYMLINK + else if (symlink_errno != ENOTSUP) + warning(_("copy used because symbolic link failed: %s"), + strerror(symlink_errno)); +#endif } } - free(fromname); - free(toname); } #define TIME_T_BITS_IN_FILE 64 @@ -888,10 +970,6 @@ static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); * rounded downward to the negation of a power of two that is * comfortably outside the error bounds. * - * zic does not output time stamps before this, partly because they - * are physically suspect, and partly because GNOME mishandles them; see - * GNOME bug 730332 <https://bugzilla.gnome.org/show_bug.cgi?id=730332>. - * * For the time of the Big Bang, see: * * Ade PAR, Aghanim N, Armitage-Caplan C et al. Planck 2013 results. @@ -913,28 +991,58 @@ static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); #define BIG_BANG (- (((zic_t) 1) << 59)) #endif -static const zic_t big_bang_time = BIG_BANG; +/* If true, work around GNOME bug 730332 + <https://bugzilla.gnome.org/show_bug.cgi?id=730332> + by refusing to output time stamps before BIG_BANG. + Such time stamps are physically suspect anyway. -/* Return 1 if NAME is a directory, 0 if it's something else, -1 if trouble. */ -static int + The GNOME bug is scheduled to be fixed in GNOME 3.22, and if so + this workaround will no longer be needed when GNOME 3.21 and + earlier are obsolete, say in the year 2021. */ +enum +{ +WORK_AROUND_GNOME_BUG_730332 = true}; + +static const zic_t early_time = (WORK_AROUND_GNOME_BUG_730332 + ? BIG_BANG + : MINVAL(zic_t, TIME_T_BITS_IN_FILE)); + +/* 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) { - char *nameslashdot = relname(name, "."); - bool dir = stat(nameslashdot, &st) == 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 -1; + return false; +} + +/* Return true if NAME is a symbolic link. */ +static bool +itssymlink(char const * name) +{ +#ifdef HAVE_SYMLINK + char c; + + return 0 <= readlink(name, &c, 1); +#else + return false; +#endif } /* @@ -957,10 +1065,10 @@ associate(void) { struct zone *zp; struct rule *rp; - int base, + ptrdiff_t i, + j, + base, out; - int i, - j; if (nrules != 0) { @@ -1047,7 +1155,7 @@ infile(const char *name) const struct lookup *lp; int nfields; bool wantcont; - int num; + lineno_t num; char buf[BUFSIZ]; if (strcmp(name, "-") == 0) @@ -1098,7 +1206,7 @@ infile(const char *name) if (lp == NULL) error(_("input line of unknown type")); else - switch ((int) (lp->l_value)) + switch (lp->l_value) { case LC_RULE: inrule(fields, nfields); @@ -1129,7 +1237,7 @@ infile(const char *name) } free(fields); } - close_file(fp, filename); + close_file(fp, NULL, filename); if (wantcont) error(_("expected continuation line not found")); } @@ -1225,7 +1333,7 @@ inrule(char **fields, int nfields) static bool inzone(char **fields, int nfields) { - int i; + ptrdiff_t i; if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) { @@ -1250,8 +1358,8 @@ inzone(char **fields, int nfields) if (zones[i].z_name != NULL && strcmp(zones[i].z_name, fields[ZF_NAME]) == 0) { - error( - _("duplicate zone name %s (file \"%s\", line %d)"), + error(_("duplicate zone name %s" + " (file \"%s\", line %" PRIdLINENO ")"), fields[ZF_NAME], zones[i].z_filename, zones[i].z_linenum); @@ -1313,7 +1421,7 @@ inzsub(char **fields, int nfields, bool iscont) z.z_filename = filename; z.z_linenum = linenum; z.z_gmtoff = gethms(fields[i_gmtoff], _("invalid UT offset"), true); - if ((cp = strchr(fields[i_format], '%')) != 0) + if ((cp = strchr(fields[i_format], '%')) != NULL) { if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%') || strchr(fields[i_format], '/')) @@ -1375,7 +1483,7 @@ inleap(char **fields, int nfields) { const char *cp; const struct lookup *lp; - int i, + zic_t i, j; /* PG: make year be int not zic_t to avoid sscanf portability issues */ @@ -1491,7 +1599,7 @@ inleap(char **fields, int nfields) return; } t = tadd(t, tod); - if (t < big_bang_time) + if (t < early_time) { error(_("leap second precedes Big Bang")); return; @@ -1582,7 +1690,7 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp, lp = byword(cp, begin_years); rp->r_lowasnum = lp == NULL; if (!rp->r_lowasnum) - switch ((int) lp->l_value) + switch (lp->l_value) { case YR_MINIMUM: rp->r_loyear = ZIC_MIN; @@ -1607,7 +1715,7 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp, lp = byword(cp, end_years); rp->r_hiwasnum = lp == NULL; if (!rp->r_hiwasnum) - switch ((int) lp->l_value) + switch (lp->l_value) { case YR_MINIMUM: rp->r_hiyear = ZIC_MIN; @@ -1757,18 +1865,21 @@ static void writezone(const char *const name, const char *const string, char version) { FILE *fp; - int i, + ptrdiff_t i, j; int leapcnt32, leapi32; - int timecnt32, + ptrdiff_t timecnt32, timei32; int pass; - char *fullname; static const struct tzhead tzh0; static struct tzhead tzh; - zic_t *ats = emalloc(size_product(timecnt, sizeof *ats + 1)); - void *typesptr = ats + timecnt; + bool dir_checked = false; + zic_t one = 1; + zic_t y2038_boundary = one << 31; + ptrdiff_t nats = timecnt + WORK_AROUND_QTBUG_53071; + zic_t *ats = emalloc(size_product(nats, sizeof *ats + 1)); + void *typesptr = ats + nats; unsigned char *types = typesptr; /* @@ -1781,12 +1892,12 @@ writezone(const char *const name, const char *const string, char version) * Optimize. */ { - int fromi; - int toi; + ptrdiff_t fromi, + toi; toi = 0; fromi = 0; - while (fromi < timecnt && attypes[fromi].at < big_bang_time) + while (fromi < timecnt && attypes[fromi].at < early_time) ++fromi; for (; fromi < timecnt; ++fromi) { @@ -1799,15 +1910,24 @@ writezone(const char *const name, const char *const string, char version) attypes[fromi].type; continue; } - if (toi == 0 || - attypes[toi - 1].type != attypes[fromi].type) + if (toi == 0 + || attypes[fromi].dontmerge + || attypes[toi - 1].type != attypes[fromi].type) attypes[toi++] = attypes[fromi]; } timecnt = toi; } + if (noise && timecnt > 1200) - warning(_("pre-2014 clients may mishandle" - " more than 1200 transition times")); + { + if (timecnt > TZ_MAX_TIMES) + warning(_("reference clients mishandle" + " more than %d transition times"), + TZ_MAX_TIMES); + else + warning(_("pre-2014 clients may mishandle" + " more than 1200 transition times")); + } /* * Transfer. @@ -1819,6 +1939,20 @@ writezone(const char *const name, const char *const string, char version) } /* + * Work around QTBUG-53071 for time stamps 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. + */ + if (WORK_AROUND_QTBUG_53071 && timecnt != 0 + && ats[timecnt - 1] < y2038_boundary - 1 && strchr(string, '<')) + { + ats[timecnt] = y2038_boundary - 1; + types[timecnt] = types[timecnt - 1]; + timecnt++; + } + + /* * Correct for leap seconds. */ for (i = 0; i < timecnt; ++i) @@ -1862,51 +1996,59 @@ writezone(const char *const name, const char *const string, char version) --leapcnt32; ++leapi32; } - fullname = relname(directory, name); /* * Remove old file, if any, to snap links. */ - if (itsdir(fullname) == 0 && remove(fullname) != 0 && errno != ENOENT) + if (remove(name) == 0) + dir_checked = true; + else if (errno != ENOENT) { const char *e = strerror(errno); - fprintf(stderr, _("%s: Cannot remove %s: %s\n"), - progname, fullname, e); + fprintf(stderr, _("%s: Cannot remove %s/%s: %s\n"), + progname, directory, name, e); exit(EXIT_FAILURE); } - if ((fp = fopen(fullname, "wb")) == NULL) + fp = fopen(name, "wb"); + if (!fp) { - if (!mkdirs(fullname)) - exit(EXIT_FAILURE); - if ((fp = fopen(fullname, "wb")) == NULL) - { - const char *e = strerror(errno); + int fopen_errno = errno; - fprintf(stderr, _("%s: Cannot create %s: %s\n"), - progname, fullname, e); + if (fopen_errno == ENOENT && !dir_checked) + { + mkdirs(name, true); + fp = fopen(name, "wb"); + fopen_errno = errno; + } + if (!fp) + { + fprintf(stderr, _("%s: Cannot create %s/%s: %s\n"), + progname, directory, name, strerror(fopen_errno)); exit(EXIT_FAILURE); } } for (pass = 1; pass <= 2; ++pass) { - int thistimei, - thistimecnt; + ptrdiff_t thistimei, + thistimecnt, + thistimelim; int thisleapi, - thisleapcnt; - int thistimelim, + thisleapcnt, thisleaplim; int writetype[TZ_MAX_TYPES]; int typemap[TZ_MAX_TYPES]; int thistypecnt; char thischars[TZ_MAX_CHARS]; - char thischarcnt; + int thischarcnt; + bool toomanytimes; int indmap[TZ_MAX_CHARS]; if (pass == 1) { thistimei = timei32; thistimecnt = timecnt32; + toomanytimes = thistimecnt >> 31 >> 1 != 0; thisleapi = leapi32; thisleapcnt = leapcnt32; } @@ -1914,9 +2056,12 @@ writezone(const char *const name, const char *const string, char version) { thistimei = 0; thistimecnt = timecnt; + toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0; thisleapi = 0; thisleapcnt = leapcnt; } + if (toomanytimes) + error(_("too many transition times")); thistimelim = thistimei + thistimecnt; thisleaplim = thisleapi + thisleapcnt; for (i = 0; i < typecnt; ++i) @@ -2017,8 +2162,7 @@ writezone(const char *const name, const char *const string, char version) break; if (j == thischarcnt) { - strcpy(&thischars[(int) thischarcnt], - thisabbr); + strcpy(&thischars[thischarcnt], thisabbr); thischarcnt += strlen(thisabbr) + 1; } indmap[abbrinds[i]] = j; @@ -2130,9 +2274,8 @@ writezone(const char *const name, const char *const string, char version) putc(ttisgmts[i], fp); } fprintf(fp, "\n%s\n", string); - close_file(fp, fullname); + close_file(fp, directory, name); free(ats); - free(fullname); } static char const * @@ -2366,13 +2509,13 @@ enum YEAR_BY_YEAR_ZONE = 1}; static int -stringzone(char *result, const struct zone * const zpfirst, const int zonecount) +stringzone(char *result, struct zone const * zpfirst, ptrdiff_t zonecount) { const struct zone *zp; struct rule *rp; struct rule *stdrp; struct rule *dstrp; - int i; + ptrdiff_t i; const char *abbrvar; int compat = 0; int c; @@ -2501,11 +2644,11 @@ stringzone(char *result, const struct zone * const zpfirst, const int zonecount) } static void -outzone(const struct zone * zpfirst, int zonecount) +outzone(const struct zone * zpfirst, ptrdiff_t zonecount) { const struct zone *zp; struct rule *rp; - int i, + ptrdiff_t i, j; bool usestart, useuntil; @@ -2527,6 +2670,10 @@ outzone(const struct zone * zpfirst, int zonecount) int compat; bool do_extend; char version; + ptrdiff_t lastatmax = -1; + zic_t one = 1; + zic_t y2038_boundary = one << 31; + zic_t max_year0; max_abbr_len = 2 + max_format_len + max_abbrvar_len; max_envvar_len = 2 * max_abbr_len + 5 * 9; @@ -2636,12 +2783,13 @@ outzone(const struct zone * zpfirst, int zonecount) } /* - * For the benefit of older systems, generate data from 1900 through 2037. + * For the benefit of older systems, generate data from 1900 through 2038. */ if (min_year > 1900) min_year = 1900; - if (max_year < 2037) - max_year = 2037; + max_year0 = max_year; + if (max_year < 2038) + max_year = 2038; for (i = 0; i < zonecount; ++i) { /* @@ -2649,9 +2797,9 @@ outzone(const struct zone * zpfirst, int zonecount) */ stdoff = 0; zp = &zpfirst[i]; - usestart = i > 0 && (zp - 1)->z_untiltime > big_bang_time; + usestart = i > 0 && (zp - 1)->z_untiltime > early_time; useuntil = i < (zonecount - 1); - if (useuntil && zp->z_untiltime <= big_bang_time) + if (useuntil && zp->z_untiltime <= early_time) continue; gmtoff = zp->z_gmtoff; eat(zp->z_filename, zp->z_linenum); @@ -2670,7 +2818,7 @@ outzone(const struct zone * zpfirst, int zonecount) usestart = false; } else - addtt(big_bang_time, type); + addtt(early_time, type); } else for (year = min_year; year <= max_year; ++year) @@ -2691,11 +2839,16 @@ outzone(const struct zone * zpfirst, int zonecount) year <= rp->r_hiyear && yearistype(year, rp->r_yrtype); if (rp->r_todo) + { rp->r_temp = rpytime(rp, year); + rp->r_todo + = (rp->r_temp < y2038_boundary + || year <= max_year0); + } } for (;;) { - int k; + ptrdiff_t k; zic_t jtime, ktime = 0; zic_t offset; @@ -2792,6 +2945,10 @@ outzone(const struct zone * zpfirst, int zonecount) offset = oadd(zp->z_gmtoff, rp->r_stdoff); type = addtype(offset, ab, rp->r_stdoff != 0, rp->r_todisstd, rp->r_todisgmt); + if (rp->r_hiyear == ZIC_MAX + && !(0 <= lastatmax + && ktime < attypes[lastatmax].at)) + lastatmax = timecnt; addtt(ktime, type); } } @@ -2827,6 +2984,8 @@ outzone(const struct zone * zpfirst, int zonecount) starttime = tadd(starttime, -gmtoff); } } + if (0 <= lastatmax) + attypes[lastatmax].dontmerge = true; if (do_extend) { /* @@ -2850,22 +3009,8 @@ outzone(const struct zone * zpfirst, int zonecount) lastat = &attypes[i]; if (lastat->at < rpytime(&xr, max_year - 1)) { - /* - * Create new type code for the redundant entry, to prevent it - * being optimized away. - */ - if (typecnt >= TZ_MAX_TYPES) - { - error(_("too many local time types")); - exit(EXIT_FAILURE); - } - gmtoffs[typecnt] = gmtoffs[lastat->type]; - isdsts[typecnt] = isdsts[lastat->type]; - ttisstds[typecnt] = ttisstds[lastat->type]; - ttisgmts[typecnt] = ttisgmts[lastat->type]; - abbrinds[typecnt] = abbrinds[lastat->type]; - ++typecnt; addtt(rpytime(&xr, max_year + 1), typecnt - 1); + attypes[timecnt - 1].dontmerge = true; } } writezone(zpfirst->z_name, envvar, version); @@ -2877,8 +3022,8 @@ outzone(const struct zone * zpfirst, int zonecount) static void addtt(zic_t starttime, int type) { - if (starttime <= big_bang_time || - (timecnt == 1 && attypes[0].at < big_bang_time)) + if (starttime <= early_time + || (timecnt == 1 && attypes[0].at < early_time)) { gmtoffs[0] = gmtoffs[type]; isdsts[0] = isdsts[type]; @@ -2894,6 +3039,7 @@ addtt(zic_t starttime, int type) } attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc); attypes[timecnt].at = starttime; + attypes[timecnt].dontmerge = false; attypes[timecnt].type = type; ++timecnt; } @@ -2997,30 +3143,52 @@ adjleap(void) } } +static char * +shellquote(char *b, char const * s) +{ + *b++ = '\''; + while (*s) + { + if (*s == '\'') + *b++ = '\'', *b++ = '\\', *b++ = '\''; + *b++ = *s++; + } + *b++ = '\''; + return b; +} + static bool -yearistype(int year, const char *type) +yearistype(zic_t year, const char *type) { - static char *buf; + char *buf; + char *b; int result; if (type == NULL || *type == '\0') return true; - buf = erealloc(buf, 132 + strlen(yitcommand) + strlen(type)); - sprintf(buf, "%s %d %s", yitcommand, year, type); + 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, INT64_FORMAT, year); + *b++ = ' '; + b = shellquote(b, type); + *b = '\0'; result = system(buf); if (WIFEXITED(result)) - switch (WEXITSTATUS(result)) + { + int status = WEXITSTATUS(result); + + if (status <= 1) { - case 0: - return true; - case 1: - return false; + free(buf); + return status == 0; } + } error(_("Wild result from command execution")); fprintf(stderr, _("%s: command was '%s', result was %d\n"), progname, buf, result); - for (;;) - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } /* Is A a space character in the C locale? */ @@ -3254,7 +3422,7 @@ getfields(char *cp) else { error(_("Odd number of quotation marks")); - exit(1); + exit(EXIT_FAILURE); } } while (*cp && *cp != '#' && !is_space(*cp)); if (is_space(*cp)) @@ -3441,53 +3609,57 @@ newabbr(const char *string) charcnt += i; } -static bool -mkdirs(char *argname) +/* 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. */ +static void +mkdirs(char const * argname, bool ancestors) { char *name; char *cp; - if (argname == NULL || *argname == '\0') - return true; cp = name = ecpyalloc(argname); - while ((cp = strchr(cp + 1, '/')) != NULL) - { - *cp = '\0'; + + /* Do not mkdir a root directory, as it must exist. */ #ifdef WIN32 + if (is_alpha(name[0]) && name[1] == ':') + cp += 2; +#endif + while (*cp == '/') + cp++; - /* - * DOS drive specifier? - */ - if (is_alpha(name[0]) && name[1] == ':' && name[2] == '\0') - { - *cp = '/'; - continue; - } -#endif /* WIN32 */ + while (cp && ((cp = strchr(cp, '/')) || !ancestors)) + { + if (cp) + *cp = '\0'; /* * Try to create it. It's OK if creation fails because the directory * already exists, perhaps because some other process just created it. + * For simplicity do not check first whether it already exists, as + * that is checked anyway if the mkdir fails. */ 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. + */ int err = errno; - if (itsdir(name) <= 0) + if (err != EEXIST && !itsdir(name)) { - char const *e = strerror(err); - - warning(_("%s: Can't create directory" - " %s: %s"), - progname, name, e); - free(name); - return false; + error(_("%s: Cannot create directory %s: %s"), + progname, name, strerror(err)); + exit(EXIT_FAILURE); } } - *cp = '/'; + if (cp) + *cp++ = '/'; } free(name); - return true; } |