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);
static int itsdir(const char *name);
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);
{
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;
static struct attype
{
zic_t at;
+ bool dontmerge;
unsigned char type;
} *attypes;
static zic_t gmtoffs[TZ_MAX_TYPES];
return ptr;
else
{
- int amax = INT_MAX < SIZE_MAX ? INT_MAX : SIZE_MAX;
+ int nitems_max = INT_MAX - WORK_AROUND_QTBUG_53071;
+ int amax = nitems_max < SIZE_MAX ? nitems_max : SIZE_MAX;
if ((amax - 1) / 3 * 2 < *nitems_alloc)
memory_exhausted(_("int overflow"));
}
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);
}
}
"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;
if (strcmp(argv[i], "--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)
if (errors)
return EXIT_FAILURE;
associate();
+ change_directory(directory);
for (i = 0; i < nzones; i = j)
{
/*
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,
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;
return componentcheck(name, component, cp);
}
-static char *
-relname(char const * dir, char const * base)
-{
- 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);
-
- result[dir_len] = '/';
- strcpy(result + dir_len + needs_slash, base);
- return memcpy(result, dir, dir_len);
- }
-}
-
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);
+ fromisdir = itsdir(fromfield);
if (fromisdir)
{
char const *e = strerror(fromisdir < 0 ? errno : EPERM);
- fprintf(stderr, _("%s: link from %s failed: %s"),
- progname, fromname, e);
+ fprintf(stderr, _("%s: link from %s/%s failed: %s\n"),
+ progname, directory, fromfield, e);
exit(EXIT_FAILURE);
}
- if (link(fromname, toname) != 0)
+ if (staysymlink)
+ staysymlink = itsdir(tofield) == 2;
+ if (remove(tofield) == 0)
+ todirs_made = true;
+ else if (errno != ENOENT)
{
- int link_errno = errno;
- bool retry_if_link_supported = false;
+ char const *e = strerror(errno);
- if (link_errno == ENOENT || link_errno == ENOTSUP)
+ fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"),
+ progname, directory, tofield, e);
+ exit(EXIT_FAILURE);
+ }
+ link_errno = (staysymlink ? ENOTSUP
+ : link(fromfield, tofield) == 0 ? 0 : errno);
+ if (link_errno == ENOENT && !todirs_made)
+ {
+ mkdirs(tofield, true);
+ todirs_made = true;
+ link_errno = link(fromfield, tofield) == 0 ? 0 : errno;
+ }
+ if (link_errno != 0)
+ {
+#ifdef HAVE_SYMLINK
+ const char *s = fromfield;
+ const char *t;
+ char *p;
+ size_t dotdots = 0;
+ char *symlinkcontents;
+ int symlink_errno;
+
+ 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_errno = symlink(symlinkcontents, tofield) == 0 ? 0 : errno;
+ if (symlink_errno == ENOENT && !todirs_made)
{
- if (!mkdirs(toname))
- exit(EXIT_FAILURE);
- retry_if_link_supported = true;
+ mkdirs(tofield, true);
+ symlink_errno = symlink(symlinkcontents, 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(symlinkcontents);
+ 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));
+ else if (symlink_errno != ENOTSUP)
+ warning(_("copy used because symbolic link failed: %s"),
+ strerror(symlink_errno));
}
}
- free(fromname);
- free(toname);
}
#define TIME_T_BITS_IN_FILE 64
* 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.
#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. */
+ 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 1 if NAME is a directory, 2 if a symbolic link, 0 if
+ something else, -1 (setting errno) if trouble. */
static int
itsdir(char const * name)
{
struct stat st;
- int res = stat(name, &st);
+ int res = lstat(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;
+#ifdef S_ISDIR
+ return S_ISDIR(st.st_mode) ? 1 : S_ISLNK(st.st_mode) ? 2 : 0;
+#else
+ size_t n = strlen(name);
+ char *nameslashdot = emalloc(n + 3);
+ bool dir;
+ memcpy(nameslashdot, name, n);
+ strcpy(&nameslashdot[n], &"/."[!(n && name[n - 1] != '/')]);
+ dir = lstat(nameslashdot, &st) == 0;
free(nameslashdot);
return dir;
+#endif
}
return -1;
}
}
free(fields);
}
- close_file(fp, filename);
+ close_file(fp, NULL, filename);
if (wantcont)
error(_("expected continuation line not found"));
}
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], '/'))
return;
}
t = tadd(t, tod);
- if (t < big_bang_time)
+ if (t < early_time)
{
error(_("leap second precedes Big Bang"));
return;
int 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;
+ int nats = timecnt + WORK_AROUND_QTBUG_53071;
+ zic_t *ats = emalloc(size_product(nats, sizeof *ats + 1));
+ void *typesptr = ats + nats;
unsigned char *types = typesptr;
/*
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)
{
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;
types[i] = attypes[i].type;
}
+ /*
+ * 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.
*/
--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);
}
}
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 *
int compat;
bool do_extend;
char version;
+ int lastatmax = -1;
max_abbr_len = 2 + max_format_len + max_abbrvar_len;
max_envvar_len = 2 * max_abbr_len + 5 * 9;
*/
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);
usestart = false;
}
else
- addtt(big_bang_time, type);
+ addtt(early_time, type);
}
else
for (year = min_year; year <= max_year; ++year)
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);
}
}
starttime = tadd(starttime, -gmtoff);
}
}
+ if (0 <= lastatmax)
+ attypes[lastatmax].dontmerge = true;
if (do_extend)
{
/*
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);
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];
}
attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc);
attypes[timecnt].at = starttime;
+ attypes[timecnt].dontmerge = false;
attypes[timecnt].type = type;
++timecnt;
}
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. */
+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)
{
int err = errno;
- if (itsdir(name) <= 0)
+ if (err != EEXIST && itsdir(name) < 0)
{
- 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;
}