musl time64 Release Notes

musl 1.2.0 changes the definition of time_t, and thereby the definitions of all derived types, to be 64-bit across all archs. This and related changes are collectively referred to as "time64", and are necessary so that data types and functions dealing with time can represent time past early 2038, where the existing 32-bit type on 32-bit archs would overflow.

Caveats

Individual users and distributions upgrading 32-bit systems to musl 1.2.x need to be aware of a number of ways things can break with the time64 transition, most of them outside the scope and control of musl itself. These are discussed in detail below.

64-bit systems are unchanged and are not affected by any of the following.

Library ABIs

musl 1.2.0 does not change or remove any part of the existing ABI between libc and libc consumers (that is, applications or libraries using libc-defined types/interfaces). In this sense, it is non-ABI-breaking. Existing binaries built against musl prior to the time64 change will run against new musl libc.so, and existing object files can even be static-linked against the new libc.a.

However, the types defined by libc can also participate in the interface boundaries between pairs of libc consumers (application-library or library-library), and when this happens, an ABI mismatch will arise if they were built with mismatching definitions of time_t.

Prior to the start of the time64 conversion of musl, a list of potentially affected libraries was generated programmatically from Debian package repository header files and metadata. Applications and libraries that do not use any of these libraries should be safe to rebuild for time64 or hold back independently. Applications and libraries which do use one of the affected libraries may need to be rebuilt in sync with that library, to avoid ABI mismatch.

System integrators and distributions need to make their own determination as to whether it makes sense to use package dependency/conflict tracking to ensure that such upgrades are made in sync where needed, or to impose global updates to time64.

Kernel Headers

musl itself does not use kernel headers whatsoever. However, if building applications or libraries which do use them, particularly anything making use of ALSA, v4l, or evdev/input, you must build against sufficiently new kernel headers.

As of Linux 4.19, all important kernel header fixes except those needed by asound/ALSA are upstream. The latter were not ready for 5.4, and are expected to land in 5.6. An aggregate patch to make the necessary header changes is shipped with musl-cross-make, but it's incompatible with actually building the kernel, so headers need to be built/installed separately if using it.

Application Compatibility

Some applications are not ready upstream for running in a time64 environment on 32-bit. Possible issues include:

Adélie Linux and Yoe/Yocto/OpenEmbedded did the early groundwork building large package corpuses against time64 musl during the 1.2.0 release cycle and found and patched all build-time breakage found, as well as obvious run-time problems. Some of these patches are now upstream in the corresponding projects. The Adélie time64 wiki page is a good starting point for information about packages that need patching.

Implementation of the transition

New time64 symbols

To change the definition of time_t and all derived types without breaking the ABI boundary between libc-consumers and libc, musl 1.2.0 redirects all functions using these types as part of their interface to alternate symbol names via the __asm__("name") construct in their declarations in the public headers. To be namespace-clean, the redirected names all begin with double-underscore. Otherwise, the pattern for naming follows the Linux kernel's pattern for syscall names: if the name ends in "time", "64" is appended; otherwise, "time64" is appended. A final "r", if present, is removed and re-added before and after the renaming, so that, for example, gmtime_r becomes __gmtime64_r, not __gmtime_r_time64.

This symbol redirection introduces a depedency (for existing 32-bit archs) on an additional "GNU C" feature, but it is one which glibc has depended on for handling its _FILE_OFFSET_BITS=64 feature for decades, and one which any viable alternative-compiler already needs to implement.

Since these functions depend on time_t or derived types defined by the headers, conforming applications cannot bypass the headers and use their own declarations; they're required by the standards (C and POSIX) to include the headers.

The dlsym function is also redirected, to a version that redirects names for affected functions, so that programs which lookup one of them by its symbol name get the correct version using matching types.

Legacy compatibility

musl does not retain duplicate implementations for legacy ("time32") versions of the functions. Instead, they are all thin wrappers defined in the new compat/time32 source tree. These wrappers generally just convert the affected input structures from time32 to time64 form, convert the affected output structures from time64 to time32 form, and if possible, translate values that overflow into errors. (Such translation is not possible, however, if the function already performed an operation with side effects.)

Syscall usage

Internally, functions which use time only as an input perform the old time32 syscall as long as the value is representable in 32 bits. This avoids having to fail and fallback on older kernels. Functions that produce time as an output have to start with the time64 syscall, in case the value does not fit in 32 bits, and fallback if the new syscall is not available. clock_gettime would be the most-impacted by this in terms of performance, but as long as vdso version is available, it's used to avoid the need for making a syscall at all. Both the time64 and legacy time32 versions of the vdso function are supported.

Socket option, ancillary data, and ioctl translation

A number of socket options and ioctl commands involve time_t-derived types. These all have new values defined by Linux, which will necessarily be missing from older kernels. musl contains code to handle the case where they are missing and translate to/from the appropriate argument forms for the old 32-bit versions.

A few socket options, particularly the SO_TIMESTAMP family, cause ancillary data to be delivered to the application along with socket reads via recvmsg and recvmmsg. These functions now translate 32-bit versions of the ancillary data if found, and append a 64-bit translation of it, so that both legacy time32 binaries and time64 binaries running on old kernels work correctly. On new kernels with the native time64 socket options, no translation is necessary.