DRAFT: Y2038 Proofness Design
Contents
History
Revision 24 was reviewed on the libc-alpha mailing list as 1st draft: https://www.sourceware.org/ml/libc-alpha/2015-10/msg00893.html
Revision 55 was reviewed on the libc-alpha mailing list as 2nd draft: https://www.sourceware.org/ml/libc-alpha/2016-01/msg00832.html
Scope
The intent of this page is to serve as a central point for describing the Y2038 proofness design.
Y2038 proofness means that application calls to glibc-provided function should never return wrong results when UTC times outside -231..231-1 seconds from the Unix Epoch are involved.
Useful Definitions
32-bit time denotes a representation of time as a 32-bit signed quantity of seconds relative to the Unix Epoch of 1970-01-01 00:00:00 UTC;
64-bit time denotes a representation of time as a 64-bit signed quantity of seconds relative to the Unix Epoch of 1970-01-01 00:00:00 UTC;
Y2038 denotes the problem where a 32-bit time value rolls over beyond +2147483647 into negative values.
- This will happen to the system clock time at 2038-01-19 03:14:07 UTC (possibly throwing it back to 1901-12-13 20:45:52 UTC);
This will also happen if, on 2038-01-16 12:00:00 UTC, the command at now + 5 days is executed.
- etc.
Goals
- Provide Y2038-safe 64-bit time support to 32-bit.
- Keep providing legacy Y2038-unsafe 32-bit time support to 32-bit.
Constraints
There are a number of constraints which dictate the direction of the design. They are either definite or debatable. Debatable design preclusions should be finalized before this design document leaves DRAFT.
Definite
64-bit time would be supported as a replacement of 32-bit time; user code (including libraries) would require either 32-bit or 64-bit time support, with 32-bit remaining the default for now.
Any newly introduced function MUST be declared in the implementation, not user, namespace (see https://sourceware.org/bugzilla/show_bug.cgi?id=14106).
Any newly introduced struct MUST have a name (see https://sourceware.org/bugzilla/show_bug.cgi?id=15766).
Debatable
- 64-bit time should only be supported if 64-bit file offsets are.
This sounds like something totally unrelated, but the underlying idea is to not multiply the possible combinations.
- 32-bit time support will not be modified; 32-bit time functions which returns wrong results now will keep returning wrong results after introduction of 64-bit time support.
IOW, this design does not aim at trying to fix existing 32-bit time support at all; any fix to the Y2038 problem will be within new 64-bit time support.
Make sure no change affects 64-bit platforms where time_t is already 64-bit (see http://www.sourceware.org/ml/libc-alpha/2015-08/msg00038.html).
API changes
This sections describes the API changes required for supporting Y2038 in GLIBC.
Time-related GLIBC APIs
This section lists all time-related API symbols based on section 21 of the glibc manual (https://www.gnu.org/software/libc/manual/html_node/Date-and-Time.html#Date-and-Time) and indicates which ones are Y2038-unsafe and which ones are Y2038-safe.
* A "Y2038-unsafe" symbol is one which, in 32-bit builds, might fail to handle calendar times beyond Y2038; whether it actually fails or succeeds will be determined by analyzing the implementation.
* A "Y2038-safe" symbol is one which, even in a 32-bit build, is definitely not susceptible to Y2038-related bugs.
Function |
Y2038-unsafe? |
time_t |
YES, as time_t is 32-bit signed on 32-bit builds |
double difftime (time_t time1, time_t time0) |
YES, as time_t is Y2038-unsafe |
struct timeval { time_t tv_sec; long int tv_usec; } |
YES, as time_t is Y2038-unsafe |
struct timespec { time_t tv_sec; long int tv_nsec; } |
YES, as time_t is Y2038-unsafe |
clock_t clock(void) |
No, return value is not clock time in seconds since the epoch but CPU time in clock ticks |
struct tms { clock_t tms_utime; clock_t tms_stime; clock_t tms_cutime; clock_t tms_cstime; } |
No, as all members are Y2038-safe |
clock_t times(struct tms *buffer) |
No, as struct tms is Y2038-safe |
time_t time (time_t *result) |
YES, as time_t is Y2038-unsafe |
int stime (const time_t *newtime) |
YES, as time_t is Y2038-unsafe |
struct timezone { int tz_minuteswest; int tz_dsttime; } |
No |
struct timeval |
YES, as struct timeval depends on time_t which is Y2038-unsafe |
int gettimeofday (struct timeval *tp, struct timezone *tzp) |
YES, as struct timeval is Y2038-unsafe |
int settimeofday (const struct timeval *tp, const struct timezone *tzp) |
YES, as struct timeval is Y2038-unsafe |
int adjtime (const struct timeval *delta, struct timeval *olddelta) |
YES, as struct timeval is Y2038-unsafe |
struct timex |
YES, as struct timex depends on struct timeval which is Y0238-unsafe |
int adjtimex (struct timex *timex) |
YES, as struct timex is Y0238-unsafe |
struct tm { int tm_sec; int tm_min; ... } |
No, as all members are Y2038-safe |
struct tm * localtime (const time_t *time) |
YES, as time_t is Y2038-unsafe |
struct tm * localtime_r (const time_t *time, struct tm *resultp) |
YES, as time_t is Y2038-unsafe |
struct tm * gmtime (const time_t *time) |
YES, as time_t is Y2038-unsafe |
struct tm * gmtime_r (const time_t *time, struct tm *resultp) |
YES, as time_t is Y2038-unsafe |
time_t mktime (struct tm *brokentime) |
YES, as time_t is Y2038-unsafe |
time_t timelocal (struct tm *brokentime) |
YES, as time_t is Y2038-unsafe |
time_t timegm (struct tm *brokentime) |
YES, as time_t is Y2038-unsafe |
struct ntptimeval { struct timeval time; long int maxerror; long int esterror; } |
YES, as struct timeval is Y2038-unsafe |
struct ntptimeval |
YES, as struct ntptimeval depends on struct timeval which is Y2038-unsafe |
ntp_gettime (struct ntptimeval *tptr) |
YES, as struct ntptimeval is Y2038-unsafe |
struct timex { ... struct timeval time; ... } |
YES, as struct timeval time is Y2038-unsafe |
int ntp_adjtime (struct timex *tptr) |
YES, as struct timex is Y2038-unsafe |
char * asctime (const struct tm *brokentime) |
No, as struct tm is Y2038-safe |
char * asctime_r (const struct tm *brokentime, char *buffer) |
No, as struct tm is Y2038-safe |
char * ctime (const time_t *time) |
YES, as time_t is Y2038-unsafe |
char * ctime_r (const time_t *time, char *buffer) |
YES, as time_t is Y2038-unsafe |
size_t strftime (char *s, size_t size, const char *template, const struct tm *brokentime) |
No, as struct tm is Y2038-safe |
size_t wcsftime (wchar_t *s, size_t size, const wchar_t *template, const struct tm *brokentime) |
No, as struct tm is Y2038-safe |
char * strptime (const char *s, const char *fmt, struct tm *tp) |
No, as struct tm is Y2038-safe |
struct tm * getdate (const char *string) |
No, as struct tm is Y2038-safe |
int getdate_r (const char *string, struct tm *tp) |
No, as struct tm is Y2038-safe |
struct itimerval |
YES, as struct timeval depends on struct timeval which is Y2038-unsafe |
int setitimer (int which, const struct itimerval *new, struct itimerval *old) |
YES, as struct itimerval is Y2038-unsafe |
int getitimer (int which, struct itimerval *old) |
YES as struct itimerval is Y2038-unsafe |
unsigned int alarm (unsigned int seconds) |
TBC depending on implementation |
unsigned int sleep (unsigned int seconds) |
TBC depending on implementation |
int nanosleep (const struct timespec *requested_time, struct timespec *remaining) |
YES, as struct timespec is Y2038-unsafe |
Other GLIBC APIs
Some other areas of the GLIB API use time_t directly or as a member of struct timeval and struct timespec, for instance struct stat which is used in the stat() function. The following table lists these.
Function |
Y2038-unsafe? |
Section 13.8 - Waiting for Input or Output |
|
int select (... struct timeval *timeout) |
YES, as struct timeval is Y2038-unsafe |
Section 14.9.2 - Reading the Attributes of a File |
|
struct stat |
YES, as struct stat depends on struct time_t which is Y2038-unsafe |
int stat (const char *filename, struct stat *buf) |
YES, as struct stat is Y2038-unsafe |
int fstat (int filedes, struct stat *buf) |
YES, as struct stat is Y2038-unsafe |
int lstat (const char *filename, struct stat *buf) |
YES, as struct stat is Y2038-unsafe |
Section 14.9.9 - File times |
|
struct utimbuf { time_t actime; time_t modtime; } |
YES, as time_t is Y2038-unsafe |
int utime (const char *filename, const struct utimbuf *times) |
YES, as struct utimbuf is Y2038-unsafe |
int utimes (const char *filename, const struct timeval tvp[2]) |
YES, as struct timeval is Y2038-unsafe |
int lutimes (const char *filename, const struct timeval tvp[2]) |
YES, as struct timeval is Y2038-unsafe |
int futimes (int fd, const struct timeval tvp[2]) |
YES, as struct timeval is Y2038-unsafe |
Section 22.1 - Resource Usage |
|
struct rusage { struct timeval ru_utime; struct timeval ru_stime; ... } |
YES, as struct_timeval is Y2038-unsafe |
int getrusage (int processes, struct rusage *rusage) |
YES, as struct_rusage is Y2038-unsafe |
struct vtimes { struct timeval vm_utime; struct timeval vm_stime; ... } |
YES, as struct_timeval is Y2038-unsafe |
int vtimes (struct vtimes *current, struct vtimes *child) |
YES, as struct_vtimes is Y2038-unsafe |
Section 22.2 - Resource Limitation |
|
rlim_t |
No, as the value held is an amount of seconds, not an absolute date |
int sched_rr_get_interval (pid_t pid, struct timespec *interval) |
No, as the value returned is a small interval, typically 150 µs) |
IOCTLs and Y2038
Some Linux IOCTLs may be Y2038-unsafe. However, at the GLIBC level, none of the IOCTLs argument types described are time_t or derived from it.
ABI changes
Time-related ABI changes
On 64-bits systems, time_t is already 64 bit wide Therefore, 64-bit applications should be able to directly use the (already 64-bit) time ABI, and a 64-bit GLIBC needs only provide 64-bit time support (applications might have internal bugs in their handling of time_t values, but this is not a GLIBC concern; as long as applications can exchange 64-bit time_t values with GLIBC and GLIBC processes them correctly, the existing "64-bit-time_t ABI" is safe)
On 32-bit systems, the size of time_t will be different for 32-bit and 64-bit time support. Consequently, structures containing time_t will have different sizes and field offsets. Therefore, the 64-bit-time ABI will be incompatible with the 32-bit-time ABI; and in order to remain backward-compatible with existing application binaries, a 32-bit GLIBC must keep providing the 32-bit time ABI unchanged.
Other ABI changes
(to be completed)
Implementation
In order to avoid duplicating APIs for 32-bit and 64-bit time, GLIBC will provide either one but not both for a given application; the application code will have to choose between 32-bit or 64-bit time support, and the same set of symbols (e.g. time_t or clock_gettime) will be provided in both cases.
The following is proposed:
User code defines _TIME_BITS=64 to get 64-bit time support instead of the legacy 32-bit time.
If glibc sees _TIME_BITS=64, then it defines __USE_TIME_BITS64 to indicate that time support is 64-bit rather than 32-bit.
This allows user code to check, by verifying whether __USE_TIME_BITS64 is defined once glibc headers are #included, that they are using a GLIBC which actually supports 64-bit time (or claims to).
On 64-bit systems, only 64-bit time is supported, __USE_TIME_BITS64 is defined regardless of _TIME_BITS, and the same symbols are used as they were in 32-bit time.
On 32-bit systems, if __USE_TIME_BITS64 is defined, time support is provided for 64-bit time; otherwise, it is provided for 32-bit time.
For instance, providing 64-bit time support for the time() function would result in the following:
on a 64-bit system, GLIBC would provide an already Y2038-safe time_t and a Y2038-safe time_t time (time_t *result)
on a 32-bit system with 32-bit time support GLIBC would continue providing the existing Y2038-unsafe 32-bit time_t and time_t time (time_t *result)
on a 32-bit system with 64-bit time support GLIBC would provide a Y2038-safe 64-bit time_t and a Y2038-safe time_t time (time_t *result)
There is an open problem whereby incompatible kernel and GLIBC headers might be used, e.g. new, 64-bit-time, kernel headers and old, 32-bit-time, GLIBC headers. In this case, there will be a mismatch between what the GLIBC uses and what the kernel expects; for instance, the kernel will expect a 64-bit time_t while GLIBC will use a 32-bit time_t.
Support for non-Y2038-safe kernels
When running above 32-bit kernels without 64-bit time support, GLIBC will emulate a 64-bit time application API using the 32-bit kernel time API, converting 64-bit GLIBC types to and from 32-bit kernel types. Out-of-range conversions will be notified to application as appropriate using EOVERFLOW.
This will allow 64-bit-time applications to run on older kernels until (around) 2038 or until these kernels are retired, whichever happens first.
Notes
* tv_usec: to be addressed. May need GLIBC to convert structs.
* tv_nsec size: to be addressed, needs to be long as per Posix / C11.
* The implementation needs further thinking about, as application code defining _TIME_BITS=64 and gets built against
new kernel headers and old GLIBC headers, then GLIBC will use 32-bit time_t and kernel will expect 64-bit time_t, and there is no way to ensure detection of this case.
References
* Y2038 information: http://kernelnewbies.org/y2038
* Y2038 Linux patch series (WIP): https://lists.linaro.org/pipermail/y2038/2015-May/000233.html