aboutsummaryrefslogtreecommitdiff
path: root/zic.c
diff options
context:
space:
mode:
Diffstat (limited to 'zic.c')
-rw-r--r--zic.c285
1 files changed, 168 insertions, 117 deletions
diff --git a/zic.c b/zic.c
index 501718f459d3..892414af7bf5 100644
--- a/zic.c
+++ b/zic.c
@@ -34,6 +34,9 @@ static zic_t const
# define ZIC_MAX_ABBR_LEN_WO_WARN 6
#endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */
+/* 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>
@@ -41,7 +44,16 @@ static zic_t const
# define mkdir(name, mode) _mkdir(name)
#endif
-#if HAVE_GETRANDOM
+#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
@@ -54,11 +66,6 @@ static zic_t const
# define MKDIR_UMASK 0755
#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 minimum alignment of a type, for pre-C23 platforms. */
#if __STDC_VERSION__ < 201112
# define alignof(type) offsetof(struct { char a; type b; }, b)
@@ -452,29 +459,54 @@ static char roll[TZ_MAX_LEAPS];
** Memory allocation.
*/
-static _Noreturn void
+static ATTRIBUTE_NORETURN 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)
+static ATTRIBUTE_NORETURN void
+size_overflow(void)
+{
+ memory_exhausted(_("size overflow"));
+}
+
+static ATTRIBUTE_REPRODUCIBLE ptrdiff_t
+size_sum(size_t a, size_t b)
+{
+#ifdef ckd_add
+ ptrdiff_t sum;
+ if (!ckd_add(&sum, a, b) && sum <= SIZE_MAX)
+ return sum;
+#else
+ ptrdiff_t sum_max = min(PTRDIFF_MAX, SIZE_MAX);
+ if (a <= sum_max && b <= sum_max - a)
+ return a + b;
+#endif
+ size_overflow();
+}
+
+static ATTRIBUTE_REPRODUCIBLE ptrdiff_t
+size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
{
- if (SIZE_MAX / itemsize < nitems)
- memory_exhausted(_("size overflow"));
- return nitems * itemsize;
+#ifdef ckd_mul
+ ptrdiff_t product;
+ if (!ckd_mul(&product, nitems, itemsize) && product <= SIZE_MAX)
+ return product;
+#else
+ ptrdiff_t nitems_max = min(PTRDIFF_MAX, SIZE_MAX) / itemsize;
+ if (nitems <= nitems_max)
+ return nitems * itemsize;
+#endif
+ size_overflow();
}
-static ATTRIBUTE_PURE size_t
-align_to(size_t size, size_t alignment)
+static ATTRIBUTE_REPRODUCIBLE ptrdiff_t
+align_to(ptrdiff_t size, ptrdiff_t alignment)
{
- size_t aligned_size = size + alignment - 1;
- aligned_size -= aligned_size % alignment;
- if (aligned_size < size)
- memory_exhausted(_("alignment overflow"));
- return aligned_size;
+ ptrdiff_t lo_bits = alignment - 1, sum = size_sum(size, lo_bits);
+ return sum & ~lo_bits;
}
#if !HAVE_STRDUP
@@ -507,23 +539,37 @@ erealloc(void *ptr, size_t size)
}
static char * ATTRIBUTE_MALLOC
-ecpyalloc(char const *str)
+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 <= SIZE_MAX)
+ return product;
+#else
+ ptrdiff_t amax = min(PTRDIFF_MAX, SIZE_MAX);
+ if (*nitems_alloc <= ((amax - 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 amax = min(PTRDIFF_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)));
}
/*
@@ -620,7 +666,7 @@ close_file(FILE *stream, char const *dir, char const *name,
}
}
-static _Noreturn void
+static ATTRIBUTE_NORETURN void
usage(FILE *stream, int status)
{
fprintf(stream,
@@ -943,7 +989,7 @@ main(int argc, char **argv)
textdomain(TZ_DOMAIN);
#endif /* HAVE_GETTEXT */
main_argv = argv;
- progname = argv[0];
+ 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"));
@@ -1201,21 +1247,12 @@ get_rand_u64(void)
#endif
/* getrandom didn't work, so fall back on portable code that is
- not the best because the seed doesn't necessarily have enough bits,
- the seed isn't cryptographically random on platforms lacking
- getrandom, and 'rand' might not be cryptographically secure. */
+ not the best because the seed isn't cryptographically random and
+ 'rand' might not be cryptographically secure. */
{
static bool initialized;
if (!initialized) {
- unsigned seed;
-#ifdef CLOCK_REALTIME
- struct timespec now;
- clock_gettime (CLOCK_REALTIME, &now);
- seed = now.tv_sec ^ now.tv_nsec;
-#else
- seed = time(NULL);
-#endif
- srand(seed);
+ srand(time(NULL));
initialized = true;
}
}
@@ -1224,13 +1261,21 @@ get_rand_u64(void)
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,
- multiplier = rand_max + 1, /* It's OK if this overflows to 0. */
+ 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 * multiplier + rand_max;
- r = r * multiplier + rand();
+ 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);
@@ -1272,7 +1317,7 @@ random_dirent(char const **name, char **namealloc)
uint_fast64_t unfair_min = - ((UINTMAX_MAX % base__6 + 1) % base__6);
if (!dst) {
- dst = emalloc(dirlen + prefixlen + suffixlen + 1);
+ dst = emalloc(size_sum(dirlen, prefixlen + suffixlen + 1));
memcpy(dst, src, dirlen);
memcpy(dst + dirlen, prefix, prefixlen);
dst[dirlen + prefixlen + suffixlen] = '\0';
@@ -1351,19 +1396,20 @@ rename_dest(char *tempname, char const *name)
static char *
relname(char const *target, char const *linkname)
{
- size_t i, taillen, dotdotetcsize;
- size_t dir_len = 0, dotdots = 0, linksize = SIZE_MAX;
+ size_t i, taillen, dir_len = 0, dotdots = 0;
+ ptrdiff_t dotdotetcsize, linksize = min(PTRDIFF_MAX, SIZE_MAX);
char const *f = target;
char *result = NULL;
if (*linkname == '/') {
/* Make F absolute too. */
size_t len = strlen(directory);
- bool needslash = len && directory[len - 1] != '/';
- linksize = len + needslash + strlen(target) + 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, target);
+ memcpy(result + lenslash, target, targetsize);
}
for (i = 0; f[i] && f[i] == linkname[i]; i++)
if (f[i] == '/')
@@ -1371,7 +1417,7 @@ relname(char const *target, char const *linkname)
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);
@@ -1575,10 +1621,9 @@ associate(void)
/* Read a text line from FP into BUF, which is of size BUFSIZE.
Terminate it with a NUL byte instead of a newline.
- Return the line's length, not counting the NUL byte.
- On EOF, return a negative number.
+ Return true if successful, false if EOF.
On error, report the error and exit. */
-static ptrdiff_t
+static bool
inputline(FILE *fp, char *buf, ptrdiff_t bufsize)
{
ptrdiff_t linelen = 0, ch;
@@ -1589,7 +1634,7 @@ inputline(FILE *fp, char *buf, ptrdiff_t bufsize)
exit(EXIT_FAILURE);
}
if (linelen == 0)
- return -1;
+ return false;
error(_("unterminated line"));
exit(EXIT_FAILURE);
}
@@ -1604,7 +1649,7 @@ inputline(FILE *fp, char *buf, ptrdiff_t bufsize)
}
}
buf[linelen] = '\0';
- return linelen;
+ return true;
}
static void
@@ -1626,13 +1671,14 @@ infile(int fnum, char const *name)
}
wantcont = false;
for (num = 1; ; ++num) {
- ptrdiff_t linelen;
- char buf[_POSIX2_LINE_MAX];
+ enum { bufsize_bound
+ = (min(INT_MAX, min(PTRDIFF_MAX, SIZE_MAX))
+ / FORMAT_LEN_GROWTH_BOUND) };
+ char buf[min(_POSIX2_LINE_MAX, bufsize_bound)];
int nfields;
char *fields[MAX_FIELDS];
eat(fnum, num);
- linelen = inputline(fp, buf, sizeof buf);
- if (linelen < 0)
+ if (!inputline(fp, buf, sizeof buf))
break;
nfields = getfields(buf, fields,
sizeof fields / sizeof *fields);
@@ -1704,15 +1750,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) {
@@ -1742,7 +1788,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) {
@@ -1780,8 +1826,8 @@ inrule(char **fields, int nfields)
fields[RF_COMMAND], fields[RF_MONTH], fields[RF_DAY],
fields[RF_TOD]))
return;
- r.r_name = ecpyalloc(fields[RF_NAME]);
- r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]);
+ 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);
@@ -1838,7 +1884,7 @@ inzsub(char **fields, int nfields, bool iscont)
register char * cp;
char * cp1;
struct zone z;
- size_t format_len;
+ int format_len;
register int i_stdoff, i_rule, i_format;
register int i_untilyear, i_untilmonth;
register int i_untilday, i_untiltime;
@@ -1905,9 +1951,9 @@ inzsub(char **fields, int nfields, bool iscont)
return false;
}
}
- z.z_name = iscont ? NULL : ecpyalloc(fields[ZF_NAME]);
- z.z_rule = ecpyalloc(fields[i_rule]);
- z.z_format = cp1 = ecpyalloc(fields[i_format]);
+ 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)
@@ -1924,7 +1970,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;
@@ -2002,7 +2048,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)
@@ -2030,7 +2076,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
@@ -2050,8 +2096,8 @@ inlink(char **fields, int nfields)
return;
l.l_filenum = filenum;
l.l_linenum = linenum;
- l.l_target = ecpyalloc(fields[LF_TARGET]);
- l.l_linkname = ecpyalloc(fields[LF_LINKNAME]);
+ 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;
}
@@ -2074,7 +2120,7 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
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)) {
@@ -2153,7 +2199,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;
@@ -2216,7 +2262,7 @@ convert64(uint_fast64_t val, char *buf)
}
static void
-puttzcode(const int_fast32_t val, FILE *const fp)
+puttzcode(zic_t val, FILE *fp)
{
char buf[4];
@@ -2305,8 +2351,10 @@ writezone(const char *const name, const char *const string, char version,
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(timecnt, sizeof *ats + 1),
+ 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;
@@ -2739,13 +2787,13 @@ abbroffset(char *buf, zic_t offset)
static char const disable_percent_s[] = "";
-static size_t
+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, '/');
@@ -2911,9 +2959,9 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
register ptrdiff_t i;
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];
@@ -3046,8 +3094,10 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
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);
@@ -3547,7 +3597,7 @@ lowerit(char a)
}
/* case-insensitive equality */
-static ATTRIBUTE_PURE bool
+static ATTRIBUTE_REPRODUCIBLE bool
ciequal(register const char *ap, register const char *bp)
{
while (lowerit(*ap) == lowerit(*bp++))
@@ -3556,7 +3606,7 @@ ciequal(register const char *ap, register const char *bp)
return false;
}
-static ATTRIBUTE_PURE bool
+static ATTRIBUTE_REPRODUCIBLE bool
itsabbr(register const char *abbr, register const char *word)
{
if (lowerit(*abbr) != lowerit(*word))
@@ -3572,7 +3622,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
+static ATTRIBUTE_REPRODUCIBLE bool
ciprefix(char const *abbr, char const *word)
{
do
@@ -3675,38 +3725,41 @@ getfields(char *cp, char **array, int arrayelts)
return nsubs;
}
-static _Noreturn void
+static ATTRIBUTE_NORETURN void
time_overflow(void)
{
error(_("time overflow"));
exit(EXIT_FAILURE);
}
-static ATTRIBUTE_PURE zic_t
+static ATTRIBUTE_REPRODUCIBLE 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
+static ATTRIBUTE_REPRODUCIBLE 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();
}
/*
@@ -3830,10 +3883,8 @@ mp = _("time zone abbreviation differs from POSIX standard");
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