DRAFT: Y2038 Proofness Design
Contents
History
Revision 24 was reviewed on the libc-alpha mailing list.
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 19:55:13 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-proof 64-bit time support to 32-bit and 64-bit applications.
- Keep providing legacy non-Y2038-proof 32-bit time support to 32-bit and 64-bit applications.
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).
On some architectures, time_t is already 64-bit wide even though it still contains a signed value ranging from -2**31 to (2**31)-1. Introducing 64-bit support should only change the range from -2**63 to (2**63)-1.
API changes
This sections describes the API changes required for supporting Y2038 in GLIBC.
Y2038-sensitive GLIBC date/time 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 sensitive to Y2038 and which ones are not.
"Y2038-sentive" means the symbol might fail to handle calendar times beyond Y2038; whether it actually fails or succeeds will be determined by analyzing the implementation.
"Y2038-insensitive" symbols are définitely not susceptible to Y2038-related bugs.
Function |
Y2038-sensitive? |
double difftime (time_t time1, time_t time0) |
No, assuming time_t is Y2038-proof (see note 1) |
struct timeval { time_t tv_sec; long int tv_usec; } |
YES, due to time_t tv_sec |
struct timespec { time_t tv_sec; long int tv_nsec; } |
YES, due to time_t tv_sec |
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 processor time in clock ticks |
clock_t times(struct tms *buffer) |
No, as its argument is not Y20338-sensitive |
time_t |
YES on 32-bit systems, No on 64-bit systems (see note 2) |
time_t time (time_t *result) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
int stime (const time_t *newtime) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 4) |
struct timezone { int tz_minuteswest; int tz_dsttime; } |
No |
int gettimeofday (struct timeval *tp, struct timezone *tzp) |
YES, due to struct timeval *tp (see notes 2 and 3) |
int settimeofday (const struct timeval *tp, const struct timezone *tzp) |
YES, due to struct timeval *tp (see notes 2 and 4) |
int adjtime (const struct timeval *delta, struct timeval *olddelta) |
No, as the struct timeval contains a small value |
int adjtimex (struct timex *timex) |
No, as the struct timex contains a small value |
struct tm { int tm_sec; int tm_min; ... } |
No, as the structure contains no count of seconds from epoch |
struct tm * localtime (const time_t *time) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
struct tm * localtime_r (const time_t *time, struct tm *resultp) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
struct tm * gmtime (const time_t *time) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
struct tm * gmtime_r (const time_t *time, struct tm *resultp) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
time_t mktime (struct tm *brokentime) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 4) |
time_t timelocal (struct tm *brokentime) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 4) |
time_t timegm (struct tm *brokentime) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 4) |
struct ntptimeval { struct timeval time; long int maxerror; long int esterror; } |
YES, due to struct timeval time |
ntp_gettime (struct ntptimeval *tptr) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
struct timex { ... struct timeval time; ... } |
YES, due to struct timeval time |
int ntp_adjtime (struct timex *tptr) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
char * asctime (const struct tm *brokentime) |
No, as struct tm is not Y2038-sensitive |
char * asctime_r (const struct tm *brokentime, char *buffer) |
No, as struct tm is not Y2038-sensitive |
char * ctime (const time_t *time) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
char * ctime_r (const time_t *time, char *buffer) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
size_t strftime (char *s, size_t size, const char *template, const struct tm *brokentime) |
No, as struct tm is not Y2038-sensitive |
size_t wcsftime (wchar_t *s, size_t size, const wchar_t *template, const struct tm *brokentime) |
No, as struct tm is not Y2038-sensitive |
char * strptime (const char *s, const char *fmt, struct tm *tp) |
No, as struct tm is not Y2038-sensitive |
struct tm * getdate (const char *string) |
No, as struct tm is not Y2038-sensitive |
int getdate_r (const char *string, struct tm *tp) |
No, as struct tm is not Y2038-sensitive |
struct itimerval { struct timeval it_interval; struct timeval it_value; } |
No, as the struct timeval fields hold intervals, not epoch-based times |
int setitimer (int which, const struct itimerval *new, struct itimerval *old) |
| No, as struct itimerval is not Y2038-sentitive |
int getitimer (int which, struct itimerval *old) |
No, as struct itimerval is not Y2038-sentitive |
unsigned int alarm (unsigned int seconds) |
No, as the input is an interval, not an epoch-based time |
unsigned int sleep (unsigned int seconds) |
No, as the input is an interval, not an epoch-based time |
int nanosleep (const struct timespec *requested_time, struct timespec *remaining) |
No, as the struct timespec fields hold intervals, not epoch-based times |
Notes:
The result is limited in precision because of its double type. However this is not Y2038-related.
time_t is defined as a long int, which on a 64-bit system, has no practical limit. This extends to struct timeval and struct timespec.
Regardless of the time_t size, the function may not return a time_t value beyond Y2038.
Regardless of the time_t size, the function may not accept an time_t input value beyond Y2038.
Other Y2038-sensitive 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-sensitive? |
Section 14.9.9 - File times |
|
struct utimbuf { time_t actime; time_t modtime; } |
YES, as time_t is Y2038-sensitive |
int utime (const char *filename, const struct utimbuf *times) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
int utimes (const char *filename, const struct timeval tvp[2]) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
int lutimes (const char *filename, const struct timeval tvp[2]) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
int futimes (int fd, const struct timeval tvp[2]) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
Section 22.1 |
|
struct rusage { struct timeval ru_utime; struct timeval ru_stime; ... } |
YES, as struct_timeval is Y2038-sensitive |
int getrusage (int processes, struct rusage *rusage) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
struct vtimes { struct timeval vm_utime; struct timeval vm_stime; ... } |
YES, as struct_timeval is Y2038-sensitive |
int vtimes (struct vtimes *current, struct vtimes *child) |
YES on 32-bit systems, TBC on 64-bit systems (see notes 2 & 3) |
(to be completed) |
Y2038-sensitive IOCTLs
Some Linux IOCTLs are Y2038-sensitive. However, at the GLIBC level, none of the IOCTLs argument types described are time_t or derived from it.
Changes to Y2038-sensitive APIs
type time_t needs to grow from 32 to 64 bits, to allow for a wider range of epoch-relative offsets.
structs containing time_t, directly or indirectly, will grow too. Thins includes:
struct timeval
struct timespec
struct ntptimeval
struct utimbuf
struct rusage
- (to be completed based on Y2038-sensitive API tables above)
- functions receiving, using or returning any of the above types may or may not need modifications.
ABI changes
On 64-bits systems, time_t is already 64 bit wide even though its range is limited to -231..231-1. Therefore, 64-bit applications built for 32-bit time support should be able to directly use the 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 just receive {{{time_t values]]] from GLIBC and pass them back to it, the existing "64-bit-time_t ABI" can be used for actual 64-bit-time support.)
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. In order to support both ABIs, and especially in order to remain backward-compatible with existing application binaries, a 32-bit GLIBC must always provide 32-bit time support through the same set of symbols, whether or not it also supports 64-bit time (through new symbols).
Implementation
User code defines _TIME_BITS=64 to ask that 64-bit time be the default rather than 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, 32-bit time support is built unchanged and, if __USE_TIME_BITS64 is defined, 64-bit time is build and provided through a new set of symbols.
For instance, providing 64-bit time support for the time() function would result in the following:
on a 64-bit system, GLIBC would provide a Y2038-proof time_t and a Y2038-proof time_t time (time_t *result)
on a 32-bit system, GLIBC would keep providing the existing Y2038-sensitive time_t and time_t time (time_t *result), and would also provide a Y2038-proof time64_t and a Y2038-proof time64_t time64 (time64_t *result)
Support for non-64-bit-time kernels
On 64-bit kernels without 64-bit time support (64-bit time_t ranging from -231..231-1) , GLIBC will "map" its 64-bit time API to the 64-bit-wide, 32-bit-effective kernel time API.
On 32-bit kernels without 64-bit time support (32-bit time_t) , 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 EDOM if the application tries to feed GLIBC with an epoch offset which does not fit in 32 bits, and ERANGE for other (GLIBC-internal) overflow cases.
This will allow 64-bit-time applications to run on older kernels until (around) 2038 or until these kernels are retired, whichever happens first.
Symbol versioning appears unnecessary so far.
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.
References
[1] http://git.kernel.org/cgit/linux/kernel/git/arnd/playground.git/log/?h=y2038-syscalls
Annex: Linux kernel 64-bit time support
This support is work in progress; the following is a bona fide attempt to provide some background information on it.
Resources
Modified and added syscalls
Modified:
- sys_time → compat_sys_time
- sys_stime → compat_sys_stime
- sys_utime → compat_sys_utime
- sys_getrusage → compat_sys_getrusage
- sys_gettimeofday → compat_sys_gettimeofday
- sys_settimeofday → compat_sys_settimeofday
- sys_old_select → compat_sys_old_select
- sys_setitimer → compat_sys_setitimer
- sys_getitimer → compat_sys_getitimer
- sys_newstat → compat_sys_newstat
- sys_newlstat → compat_sys_newlstat
- sys_newfstat → compat_sys_newfstat
- sys_wait4 → compat_sys_wait4
- sys_adjtimex → compat_sys_adjtimex
- sys_select → compat_sys_select
- sys_sched_rr_get_interval → compat_sys_sched_rr_get_interval
- sys_nanosleep → compat_sys_nanosleep
- sys_sigtimedwait → compat_sys_sigtimedwait
- sys_futex → compat_sys_futex
- sys_io_getevents → compat_sys_io_getevents
- sys_timer_settime → compat_sys_timer_settime
- sys_timer_gettime → compat_sys_timer_gettime
- sys_clock_settime → compat_sys_clock_settime
- sys_clock_gettime → compat_sys_clock_gettime
- sys_clock_getres → compat_sys_clock_getres
- sys_clock_nanosleep → compat_sys_clock_nanosleep
- sys_utimes → compat_sys_utimes
- sys_mq_timedsend → compat_sys_mq_timedsend
- sys_mq_timedreceive → compat_sys_mq_timedreceive
- sys_waitid → compat_sys_waitid
- sys_semtimedop → compat_sys_semtimedop
- sys_futimesat → compat_sys_futimesat
- sys_pselect6 → compat_sys_pselect6
- sys_ppoll → compat_sys_ppoll
- sys_utimesat → compat_sys_utimesat
- sys_timerfd_settime → compat_sys_timerfd_settime
- sys_timerfd_gettime → compat_sys_timerfd_gettime
- sys_recvmmsg → compat_sys_recvmmsg
- sys_clock_adjtime → compat_sys_clock_adjtime
- sys_clock_adjtime → compat_sys_clock_adjtime
Added:
- sys_clock_gettime
- sys_clock_settime
- sys_clock_adjtime
- sys_clock_getres
- sys_clock_nanosleep
- sys_timer_gettime
- sys_timer_settime
- sys_timerfd_gettime
- sys_timerfd_settime
- sys_pselect
- sys_ppoll
- sys_io_getevents
- sys_recvmmsg
- sys_semtimedop
- sys_mq_timedsend
- sys_mq_timedreceive
- sys_utimensat
- sys_newfstat
- sys_newfstatat
- sys_rt_sigtimedwait
- sys_getrusage
- sys_waitid