zdump -v crashes with 64-bit time_t
Paul Eggert
eggert at CS.UCLA.EDU
Tue Jun 15 05:34:30 UTC 2004
Ken Pizzini <"tz."@explicate.org> writes:
> I'd feel more comfortable with a patch that takes care to avoid
> passing a NULL to asctime() in the first place.
Yes, asctime has undefined behavior if you give it null pointer. But
your patch doesn't suffice either, since asctime also has undefined
behavior if you give it a struct tm whose tm_year member is less than
-999 - 1900 or greater than 9999 - 1900. (This is because it can
overrun its 26-byte buffer in that case.) Worse, HP-UX asctime
rejects any tm_year that is negative; this violates the C standard,
but HP won't take my bug reports since I'm not a HP-UX customer.
I looked and found several other problems with 64-bit time_t in zdump.c.
Here is a proposed patch. The basic ideas are as follows.
* Use 'long' when computing years, not 'int', to avoid spurious
overflow on 64-bit hosts.
* Use our (more generous) asctime implementation than relying on the
system asctime, so that we can print years before 1900 and after
9999 (even on HP-UX).
* When localtime/gmtime fails, print the raw time_t value as an
integer than the less-informative "NULL".
* Don't attempt to print out all the 64-bit time_t space, as that will
take nearly forever; instead, default to printing about 50 years
into the future, and don't bother to print times before about 1833.
This suffices for all the current tz data and is good enough for
POSIX tz values until the year 2106. We can revisit this when we
add true 64-bit support for tz data.
===================================================================
RCS file: RCS/asctime.c,v
retrieving revision 2004.1
retrieving revision 2004.1.0.1
diff -pu -r2004.1 -r2004.1.0.1
--- asctime.c 1998/05/28 13:56:06 2004.1
+++ asctime.c 2004/06/15 04:23:43 2004.1.0.1
@@ -41,14 +41,14 @@ char * buf;
else mn = mon_name[timeptr->tm_mon];
/*
** The X3J11-suggested format is
- ** "%.3s %.3s%3d %02.2d:%02.2d:%02.2d %d\n"
- ** Since the .2 in 02.2d is ignored, we drop it.
+ ** "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n"
+ ** Use "%02d", as it is a bit more portable than "%.2d".
*/
- (void) sprintf(buf, "%.3s %.3s%3d %02d:%02d:%02d %d\n",
+ (void) sprintf(buf, "%.3s %.3s%3d %02d:%02d:%02d %ld\n",
wn, mn,
timeptr->tm_mday, timeptr->tm_hour,
timeptr->tm_min, timeptr->tm_sec,
- TM_YEAR_BASE + timeptr->tm_year);
+ timeptr->tm_year + (long) TM_YEAR_BASE);
return buf;
}
===================================================================
RCS file: RCS/zdump.c,v
retrieving revision 2003.3
retrieving revision 2003.3.0.2
diff -pu -r2003.3 -r2003.3.0.2
--- zdump.c 2003/09/16 11:12:41 2003.3
+++ zdump.c 2004/06/15 05:24:21 2003.3.0.2
@@ -6,6 +6,7 @@ static char elsieid[] = "@(#)zdump.c 7.3
** You can use this code to help in verifying other implementations.
*/
+#include "limits.h" /* for CHAR_BIT */
#include "stdio.h" /* for stdout, stderr, perror */
#include "string.h" /* for strcpy */
#include "sys/types.h" /* for time_t */
@@ -24,6 +25,25 @@ static char elsieid[] = "@(#)zdump.c 7.3
#define FALSE 0
#endif /* !defined FALSE */
+#ifndef TYPE_BIT
+#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT)
+#endif /* !defined TYPE_BIT */
+
+#ifndef TYPE_SIGNED
+#define TYPE_SIGNED(type) (((type) -1) < 0)
+#endif /* !defined TYPE_SIGNED */
+
+#ifndef INT_STRLEN_MAXIMUM
+/*
+** 302 / 1000 is log10(2.0) rounded up.
+** Subtract one for the sign bit if the type is signed;
+** add one for integer division truncation;
+** add one more for a minus sign if the type is signed.
+*/
+#define INT_STRLEN_MAXIMUM(type) \
+ ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + 1 + TYPE_SIGNED(type))
+#endif /* !defined INT_STRLEN_MAXIMUM */
+
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif /* !defined EXIT_SUCCESS */
@@ -56,10 +76,18 @@ static char elsieid[] = "@(#)zdump.c 7.3
#define TM_YEAR_BASE 1900
#endif /* !defined TM_YEAR_BASE */
+#ifndef DAYSPERWEEK
+#define DAYSPERWEEK 7
+#endif /* !defined DAYSPERWEEK */
+
#ifndef DAYSPERNYEAR
#define DAYSPERNYEAR 365
#endif /* !defined DAYSPERNYEAR */
+#ifndef MONSPERYEAR
+#define MONSPERYEAR 12
+#endif /* !defined MONSPERYEAR */
+
#ifndef isleap
#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
#endif /* !defined isleap */
@@ -128,6 +156,9 @@ static long delta P((struct tm * newp, s
static time_t hunt P((char * name, time_t lot, time_t hit));
static size_t longest;
static char * progname;
+static char * my_asctime_r P((const struct tm *, char *));
+static char * my_asctime P((const struct tm *));
+static void showtime P((time_t, const struct tm *));
static void show P((char * zone, time_t t, int v));
int
@@ -140,16 +171,17 @@ char * argv[];
register int vflag;
register char * cutoff;
register int cutyear;
- register long cuttime;
+ register time_t lasttime;
char ** fakeenv;
time_t now;
time_t t;
time_t newt;
time_t hibit;
+ time_t time_t_min;
+ time_t time_t_max;
struct tm tm;
- struct tm newtm;
+ struct tm * newtm;
- INITIALIZE(cuttime);
#if HAVE_GETTEXT - 0
(void) setlocale(LC_MESSAGES, "");
#ifdef TZ_DOMAINDIR
@@ -176,22 +208,39 @@ _("%s: usage is %s [ --version ] [ -v ]
argv[0], argv[0]);
(void) exit(EXIT_FAILURE);
}
+ for (hibit = 1; (hibit << 1) != 0; hibit <<= 1)
+ continue;
+ time_t_max = ~((time_t) 0);
+ if (TYPE_SIGNED(time_t))
+ time_t_max &= ~hibit;
+ time_t_min = TYPE_SIGNED(time_t) ? hibit : 0;
+ (void) time(&now);
if (cutoff != NULL) {
int y;
cutyear = atoi(cutoff);
- cuttime = 0;
+ lasttime = 0;
for (y = EPOCH_YEAR; y < cutyear; ++y)
- cuttime += DAYSPERNYEAR + isleap(y);
- cuttime *= SECSPERHOUR * HOURSPERDAY;
+ lasttime += DAYSPERNYEAR + isleap(y);
+ lasttime *= SECSPERHOUR * HOURSPERDAY;
+ lasttime -= TYPE_SIGNED(time_t) || lasttime != 0;
+ } else {
+ /*
+ ** By default, the cutoff is the maximum time value,
+ ** or about 50 years from now, whichever comes first.
+ ** This prevents us from generating too much output if
+ ** time_t is 64 bits. 31556952 is 365.2425 * 24 * 60
+ ** * 60, but don't trust the C compiler to calculate
+ ** it as there may be bugs or rounding problems.
+ */
+ long seconds_per_mean_gregorian_year = 31556952;
+ time_t now50 = now + 50 * seconds_per_mean_gregorian_year - 1;
+ lasttime = now < now50 ? now50 : time_t_max;
}
- (void) time(&now);
longest = 0;
for (i = optind; i < argc; ++i)
if (strlen(argv[i]) > longest)
longest = strlen(argv[i]);
- for (hibit = 1; (hibit << 1) != 0; hibit <<= 1)
- continue;
{
register int from;
register int to;
@@ -224,40 +273,47 @@ _("%s: usage is %s [ --version ] [ -v ]
/*
** Get lowest value of t.
*/
- t = hibit;
- if (t > 0) /* time_t is unsigned */
- t = 0;
+ t = time_t_min;
show(argv[i], t, TRUE);
t += SECSPERHOUR * HOURSPERDAY;
show(argv[i], t, TRUE);
+ /*
+ ** But don't go below - 2**32 in the following loop,
+ ** as that predates standard time, would cause us to
+ ** generate too much output, and could dump core if
+ ** localtime returns NULL. The earliest known use of
+ ** standard time is 1837 (Iceland) and - 2**32 is a
+ ** bit before that.
+ */
+ if ((t + 1) / 2 < -2147483647) {
+ t = -1 - 2147483647;
+ t *= 2;
+ }
tm = *localtime(&t);
(void) strncpy(buf, abbr(&tm), (sizeof buf) - 1);
for ( ; ; ) {
- if (cutoff != NULL && t >= cuttime)
+ if (lasttime < t)
break;
newt = t + SECSPERHOUR * 12;
- if (cutoff != NULL && newt >= cuttime)
+ if (lasttime < newt)
break;
if (newt <= t)
break;
- newtm = *localtime(&newt);
- if (delta(&newtm, &tm) != (newt - t) ||
- newtm.tm_isdst != tm.tm_isdst ||
- strcmp(abbr(&newtm), buf) != 0) {
+ newtm = localtime(&newt);
+ if (newtm == NULL)
+ break;
+ if (delta(newtm, &tm) != (newt - t) ||
+ newtm->tm_isdst != tm.tm_isdst ||
+ strcmp(abbr(newtm), buf) != 0) {
newt = hunt(argv[i], t, newt);
- newtm = *localtime(&newt);
- (void) strncpy(buf, abbr(&newtm),
+ newtm = localtime(&newt);
+ (void) strncpy(buf, abbr(newtm),
(sizeof buf) - 1);
}
t = newt;
- tm = newtm;
+ tm = *newtm;
}
- /*
- ** Get highest value of t.
- */
- t = ~((time_t) 0);
- if (t < 0) /* time_t is signed */
- t &= ~hibit;
+ t = time_t_max;
t -= SECSPERHOUR * HOURSPERDAY;
show(argv[i], t, TRUE);
t += SECSPERHOUR * HOURSPERDAY;
@@ -323,7 +379,7 @@ struct tm * oldp;
return -delta(oldp, newp);
result = 0;
for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
- result += DAYSPERNYEAR + isleap(tmy + TM_YEAR_BASE);
+ result += DAYSPERNYEAR + isleap(tmy + (long) TM_YEAR_BASE);
result += newp->tm_yday - oldp->tm_yday;
result *= HOURSPERDAY;
result += newp->tm_hour - oldp->tm_hour;
@@ -334,6 +390,80 @@ struct tm * oldp;
return result;
}
+/*
+** my_asctime_r and my_asctime are stolen from asctime.c. Unlike
+** Standard C asctime, my_asctime has well-defined behavior even if
+** the year is less than -999 or greater than 9999.
+*/
+
+static char *
+my_asctime_r(timeptr, buf)
+register const struct tm * timeptr;
+char * buf;
+{
+ static const char wday_name[][3] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+ static const char mon_name[][3] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+ register const char * wn;
+ register const char * mn;
+
+ if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK)
+ wn = "???";
+ else wn = wday_name[timeptr->tm_wday];
+ if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR)
+ mn = "???";
+ else mn = mon_name[timeptr->tm_mon];
+ /*
+ ** The X3J11-suggested format is
+ ** "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n"
+ ** Use "%02d", as it is a bit more portable than "%.2d".
+ */
+ (void) sprintf(buf, "%.3s %.3s%3d %02d:%02d:%02d %ld\n",
+ wn, mn,
+ timeptr->tm_mday, timeptr->tm_hour,
+ timeptr->tm_min, timeptr->tm_sec,
+ timeptr->tm_year + (long) TM_YEAR_BASE);
+ return buf;
+}
+
+static char *
+my_asctime(timeptr)
+register const struct tm * timeptr;
+{
+ /*
+ ** Big enough for something such as
+ ** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n
+ ** (two three-character abbreviations, five strings denoting integers,
+ ** three explicit spaces, two explicit colons, a newline,
+ ** and a trailing ASCII nul).
+ */
+ static char result[3 * 2 + 5 * INT_STRLEN_MAXIMUM(int) +
+ 3 + 2 + 1 + 1];
+
+ return my_asctime_r(timeptr, result);
+}
+
+static void
+showtime(t, timeptr)
+time_t t;
+const struct tm * timeptr;
+{
+ if (timeptr != NULL) {
+ const char *p;
+ for (p = my_asctime (timeptr); *p != '\n'; p++)
+ putchar (*p);
+ } else {
+ if (TYPE_SIGNED(time_t))
+ printf ("%ld\n", (long) t);
+ else
+ printf ("%lu\n", (unsigned long) t);
+ }
+}
+
static void
show(zone, t, v)
char * zone;
@@ -343,13 +473,15 @@ int v;
struct tm * tmp;
(void) printf("%-*s ", (int) longest, zone);
- if (v)
- (void) printf("%.24s UTC = ", asctime(gmtime(&t)));
+ if (v) {
+ showtime(t, gmtime(&t));
+ (void) printf(" UTC = ");
+ }
tmp = localtime(&t);
- (void) printf("%.24s", asctime(tmp));
+ showtime(t, tmp);
if (*abbr(tmp) != '\0')
(void) printf(" %s", abbr(tmp));
- if (v) {
+ if (v && tmp != NULL) {
(void) printf(" isdst=%d", tmp->tm_isdst);
#ifdef TM_GMTOFF
(void) printf(" gmtoff=%ld", tmp->TM_GMTOFF);
@@ -365,7 +497,7 @@ struct tm * tmp;
register char * result;
static char nada;
- if (tmp->tm_isdst != 0 && tmp->tm_isdst != 1)
+ if (!tmp || (tmp->tm_isdst != 0 && tmp->tm_isdst != 1))
return &nada;
result = tzname[tmp->tm_isdst];
return (result == NULL) ? &nada : result;
===================================================================
RCS file: RCS/zdump.8,v
retrieving revision 2004.1
retrieving revision 2004.1.0.1
diff -pu -r2004.1 -r2004.1.0.1
--- zdump.8 2003/09/16 11:12:41 2004.1
+++ zdump.8 2004/06/15 05:24:21 2004.1.0.1
@@ -40,6 +40,12 @@ otherwise.
.TP
.BI "\-c " cutoffyear
Cut off the verbose output near the start of the given year.
+By default, verbose output is cut off approximately 50 years after the
+current date and time.
.SH "SEE ALSO"
newctime(3), tzfile(5), zic(8)
+.SH "BUGS"
+Time discontinuities are detected via a heuristic that suffices for
+all historically valid discontinuities, but one can construct
+artificial time zones for which the heuristic fails.
.\" @(#)zdump.8 7.4
More information about the tz
mailing list