From: H.Merijn Brand Date: Sat, 18 Oct 2008 15:26:02 +0000 (+0000) Subject: y2038 time checks have overflow checks. Added documentation and X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=1b289682be66bb0b7b4ce61ed8cef35e32797e7b;p=p5sagit%2Fp5-mst-13.2.git y2038 time checks have overflow checks. Added documentation and test programs in Porting/ p4raw-id: //depot/perl@34504 --- diff --git a/Configure b/Configure index 42baaba..0d54123 100755 --- a/Configure +++ b/Configure @@ -25,7 +25,7 @@ # $Id: Head.U 6 2006-08-25 22:21:46Z rmanfredi $ # -# Generated on Fri Oct 3 17:54:11 CEST 2008 [metaconfig 3.5 PL0] +# Generated on Sat Oct 18 17:13:43 CEST 2008 [metaconfig 3.5 PL0] # (with additional metaconfig patches by perlbug@perl.org) cat >c1$$ <tm_year < -1900) + if ( tmp == NULL || + /* Check tm_year overflow */ + tmp->tm_year < min_year || tmp->tm_year > max_year) tmp = NULL; else pt = t; @@ -20083,31 +20085,31 @@ int check_max () tmp = NULL; pt = 0; #ifdef MAXLONG - gm_check (MAXLONG); + gm_check (MAXLONG, 69, 0x7fffffff); #endif if (tmp == NULL || tmp->tm_year < 0) { for (i = 63; i >= 0; i--) { time_t x = pt | ((time_t)1 << i); - if (x < 0) continue; - gm_check (x); + if (x < 0 || x < pt) continue; + gm_check (x, 69, 0x7fffffff); } } printf ("sGMTIME_max=%ld\n", pt); return (0); - } /* check_max */ + } /* check_max */ int check_min () { tmp = NULL; pt = 0; #ifdef MINLONG - gm_check (MINLONG); + gm_check (MINLONG, -1900, 70); #endif if (tmp == NULL) { for (i = 36; i >= 0; i--) { time_t x = pt - ((time_t)1 << i); if (x > 0) continue; - gm_check (x); + gm_check (x, -1900, 70); } } printf ("sGMTIME_min=%ld\n", pt); @@ -20146,13 +20148,15 @@ int i; struct tm *tmp; time_t pt; -void local_check (time_t t) +void local_check (time_t t, int min_year, int max_year) { if (sizeof (time_t) > 4 && t > 0x7ffffffffffff000LL) tmp = NULL; else tmp = localtime (&t); - if (tmp == NULL || tmp->tm_year < -1900) + if ( tmp == NULL || + /* Check tm_year overflow */ + tmp->tm_year < min_year || tmp->tm_year > max_year) tmp = NULL; else pt = t; @@ -20163,31 +20167,31 @@ int check_max () tmp = NULL; pt = 0; #ifdef MAXLONG - local_check (MAXLONG); + local_check (MAXLONG, 69, 0x7fffffff); #endif if (tmp == NULL || tmp->tm_year < 0) { for (i = 63; i >= 0; i--) { time_t x = pt | ((time_t)1 << i); - if (x < 0) continue; - local_check (x); + if (x < 0 || x < pt) continue; + local_check (x, 69, 0x7fffffff); } } printf ("sLOCALTIME_max=%ld\n", pt); return (0); - } /* check_max */ + } /* check_max */ int check_min () { tmp = NULL; pt = 0; #ifdef MINLONG - local_check (MINLONG); + local_check (MINLONG, -1900, 70); #endif if (tmp == NULL) { for (i = 36; i >= 0; i--) { time_t x = pt - ((time_t)1 << i); if (x > 0) continue; - local_check (x); + local_check (x, -1900, 70); } } printf ("sLOCALTIME_min=%ld\n", pt); diff --git a/Porting/README.y2038 b/Porting/README.y2038 new file mode 100644 index 0000000..a3530d2 --- /dev/null +++ b/Porting/README.y2038 @@ -0,0 +1,84 @@ +The y2038 implementation for perl +=========================================================================== +This is an implementation of POSIX time.h which solves the year 2038 bug on +systems where time_t is only 32 bits. It is implemented in bog-standard +ANSI C. The latest version can be found at http://y2038.googlecode.com/ + +It makes use of the system's native 32 bit functions to perform time zone +and daylight savings time calculations and thus does *not* need to ship its +own time zone table. + +time64.h currently implements three public functions, localtime64_r(), +gmtime64_r() and timegm64(). They are implementations of localtime_r(), +gmtime_r() and timegm64(). + +To install, simply copy time64.c and time64.h into your project and make +use of the functions. + +To test, run "make test". You must have Perl, prove (which comes with a +recent version of the Test::Harness Perl module) and bzdiff installed to +run the full test suite. It will do a number of unit tests, plus test +against a large table of known good values in different time zones. + +Limitations, Issues, etc... +--------------------------- +localtime64_r() gets its time zone and daylight savings time information by +mappping the future year back to a similar one between 2010 and 2037, safe +for localtime_r(). The calculations are accurate according to current time +zone and daylight savings information, but may become inaccurate if a +change is made that takes place after 2010. + +Future versions will probe for a 64 bit safe system localtime_r() and +gmtime_r() and use that. + +The maximum date is still limited by your tm struct. Most 32 bit systems +use a signed integer tm_year which means the practical upper limit is the +year 2147483647 which is somewhere around 2**54. You can use a 64 bit +clean tm struct by setting USE_TM64 in time64.h + +Portability +----------- +I would like to add some configuration detection stuff in the future, but +for now all I can do is document the assumptions... + +This code assumes that long longs are 64 bit integers which is technically +in violation of the C standard. This can be changed in time64.h by +changing the Time64_T and Int64 typedefs. + +There are a number of configuration options in time64.h. + +Configure variables +------------------- +Configure probes for the maximum and minimum values that gmtime () and +localtime () accept on the local system. Configure however is only used on +unix-like systems. For windows and VMS these values are hard-coded. You can +use timecheck.c in the Porting directory to check those values yourself, +using the same technique that is used in Configure based on bit-shifting: + + $ cd perl/Porting + $ cc -O -o timecheck timecheck.c + $ ./timecheck + ====================== + Sizeof time_t = 8 + gmtime () boundaries: + 8: 0x00f0c2ab7c54a97f: 2147485547-12-31 23:59:59 + 8: -0x0000000e79747c00: 0-01-01 00:00:00 + localtime () boundaries: + 8: 0x00f0c2ab7c549b6f: 2147485547-12-31 23:59:59 + 8: -0x0000000e79748094: 0-01-01 00:00:00 + Configure variables: + sGMTIME_max='67768036191676799' + sGMTIME_min='-62167219200' + sLOCALTIME_max='67768036191673199' + sLOCALTIME_min='-62167220372' + +In the rare case that your system uses a double for time_t, you can use the +alternate approach to test for these values: + + $ cd perl/Porting + $ cc -O -o timecheck2{,.c} + $ ./timecheck2 + gmtime max 67768036191676800 + localtime max 67768036191673200 + gmtime min -67768040609740800 + localtime min -67768040609741968 diff --git a/Porting/timecheck.c b/Porting/timecheck.c new file mode 100644 index 0000000..07f5872 --- /dev/null +++ b/Porting/timecheck.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +int opt_v = 0; +int i; +struct tm *tmp; +time_t pt, pt_max, pt_min; + +static char hexbuf[80]; +char *hex (time_t t) +{ + if ((long long)t < 0) + sprintf (hexbuf, " -0x%016lx", -t); + else + sprintf (hexbuf, " 0x%016lx", t); + return (hexbuf); + } /* hex */ + +void gm_check (time_t t, int min_year, int max_year) +{ + tmp = gmtime (&t); + if ( tmp == NULL || + /* Check tm_year overflow */ + tmp->tm_year < min_year || tmp->tm_year > max_year) { + if (opt_v) + fprintf (stderr, "gmtime (%ld) failed with errno %d\n", t, errno); + } + else { + if (opt_v) + fprintf (stderr, "%3d:%s: %12ld-%02d-%02d %02d:%02d:%02d\n", + i, hex (t), + tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday, + tmp->tm_hour, tmp->tm_min, tmp->tm_sec); + pt = t; + } + } /* gm_check */ + +int check_gm_max () +{ + tmp = NULL; + pt = 0; + if (tmp == NULL || tmp->tm_year < 0) { + for (i = 63; i >= 0; i--) { + time_t x = pt | ((time_t)1 << i); + if (x < 0 || x < pt) continue; + gm_check (x, 69, 0x7fffffff); + } + } + pt_max = pt; + return (0); + } /* check_gm_max */ + +int check_gm_min () +{ + tmp = NULL; + pt = 0; + if (tmp == NULL) { + for (i = 36; i >= 0; i--) { + time_t x = pt - ((time_t)1 << i); + if (x > 0) continue; + gm_check (x, -1900, 70); + } + } + pt_min = pt; + return (0); + } /* check_gm_min */ + +void lt_check (time_t t, int min_year, int max_year) +{ + if (sizeof (time_t) > 4 && t > 0x7ffffffffffff000LL) + tmp = NULL; + else + tmp = localtime (&t); + if ( tmp == NULL || + /* Check tm_year overflow */ + tmp->tm_year < min_year || tmp->tm_year > max_year) { + if (opt_v) + fprintf (stderr, "localtime (%ld) failed with errno %d\n", t, errno); + } + else { + if (opt_v) + fprintf (stderr, "%3d:%s: %12ld-%02d-%02d %02d:%02d:%02d\n", + i, hex (t), + tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday, + tmp->tm_hour, tmp->tm_min, tmp->tm_sec); + pt = t; + } + } /* lt_check */ + +int check_lt_max () +{ + tmp = NULL; + pt = 0; + if (tmp == NULL || tmp->tm_year < 0) { + for (i = 63; i >= 0; i--) { + time_t x = pt | ((time_t)1 << i); + if (x < 0 || x < pt) continue; + lt_check (x, 69, 0x7fffffff); + } + } + pt_max = pt; + return (0); + } /* check_lt_max */ + +int check_lt_min () +{ + tmp = NULL; + pt = 0; + if (tmp == NULL) { + for (i = 36; i >= 0; i--) { + time_t x = pt - ((time_t)1 << i); + if (x > 0) continue; + lt_check (x, -1900, 70); + } + } + pt_min = pt; + return (0); + } /* check_lt_min */ + +int main (int argc, char *argv[]) +{ + time_t gm_max, gm_min, lt_max, lt_min; + if (argc > 1 && strcmp (argv[1], "-v") == 0) opt_v++; + + check_gm_max (); gm_max = pt_max; + check_gm_min (); gm_min = pt_min; + check_lt_max (); lt_max = pt_max; + check_lt_min (); lt_min = pt_min; + + opt_v++; + printf ("======================\n"); + printf ("Sizeof time_t = %ld\n", (i = sizeof (time_t))); + printf ("gmtime () boundaries:\n"); + gm_check (gm_max, 69, 0x7fffffff); + gm_check (gm_min, -1900, 70); + printf ("localtime () boundaries:\n"); + lt_check (lt_max, 69, 0x7fffffff); + lt_check (lt_min, -1900, 70); + printf ("Configure variables:\n"); + printf ("sGMTIME_max='%ld'\n", gm_max); + printf ("sGMTIME_min='%ld'\n", gm_min); + printf ("sLOCALTIME_max='%ld'\n", lt_max); + printf ("sLOCALTIME_min='%ld'\n", lt_min); + return (0); + } /* main */ diff --git a/Porting/timecheck2.c b/Porting/timecheck2.c new file mode 100644 index 0000000..a4445ef --- /dev/null +++ b/Porting/timecheck2.c @@ -0,0 +1,112 @@ +/* A little program to test the limits of your system's time functions */ + +#include +#include +#include + +time_t Time_Zero = 0; + +/* Visual C++ 2008's difftime() can't do negative times */ +double my_difftime(time_t left, time_t right) { + double diff = (double)left - (double)right; + return diff; +} + +void check_date_max( struct tm * (*date_func)(const time_t *), char *func_name ) { + struct tm *date; + time_t time = 0; + time_t last_time = 0; + time_t time_change; + int i; + + for (i = 0; i <= 63; i++) { + date = (*date_func)(&time); + + /* date_func() broke or tm_year overflowed */ + if(date == NULL || date->tm_year < 69) + break; + + last_time = time; + time += time + 1; + + /* time_t overflowed */ + if( time < last_time ) + break; + } + + /* Binary search for the exact failure point */ + time = last_time; + time_change = last_time / 2; + + do { + time += time_change; + + date = (*date_func)(&time); + + /* date_func() broke or tm_year overflowed or time_t overflowed */ + if(date == NULL || date->tm_year < 69 || time < last_time) { + time = last_time; + time_change = time_change / 2; + } + else { + last_time = time; + } + } while(time_change > 0); + + printf("%20s max %.0f\n", func_name, my_difftime(last_time, Time_Zero)); +} + + +void check_date_min( struct tm * (*date_func)(const time_t *), char *func_name ) { + struct tm *date; + time_t time = -1; + time_t last_time = 0; + time_t time_change; + int i; + + for (i = 1; i <= 63; i++) { + date = (*date_func)(&time); + + /* date_func() broke or tm_year underflowed */ + if(date == NULL || date->tm_year > 70) + break; + + last_time = time; + time += time; + + /* time_t underflowed */ + if( time > last_time ) + break; + } + + /* Binary search for the exact failure point */ + time = last_time; + time_change = last_time / 2; + + do { + time += time_change; + + date = (*date_func)(&time); + + /* gmtime() broke or tm_year overflowed or time_t overflowed */ + if(date == NULL || date->tm_year > 70 || time > last_time) { + time = last_time; + time_change = time_change / 2; + } + else { + last_time = time; + } + } while(time_change < 0); + + printf("%20s min %.0f\n", func_name, my_difftime(last_time, Time_Zero)); +} + + +int main(void) { + check_date_max(gmtime, "gmtime"); + check_date_max(localtime, "localtime"); + check_date_min(gmtime, "gmtime"); + check_date_min(localtime, "localtime"); + + return 0; +}