Is there any way to get 64-bit time_t in 32-bit programs in Linux?
Asked Answered
S

5

20

On Windows I can call:

_time32(__time32_t); // to get 32-bit time_t
_time64(__time64_t); // to get 64-bit time_t

(both in 32 and 64-bit programs)

Is there any way do this in Linux (compiling with GCC)?

Saito answered 16/1, 2013 at 15:17 Comment(4)
@Benoit That question is not a duplicate, it's completely different.Fabron
Not really...on 32-bit Linux, time_t is a traditional 32-bit signed quantity, with only 25 years of useful life left in it. On 64-bit Unix systems, it is already a 64-bit quantity. If you want a portable type, you'll have to define your own that maps appropriately, or just use time_t carefully. The other question referenced has relevant information (but I agree it is not a duplicate).Forsyth
see also 64-bit UNIX Timestamp ConversionGrainfield
Adjacently-related: Get a timestamp in C in microseconds?Phosphorite
S
14

Apparently, no it's not possible. For starters, there is only one time() function in Linux, no time32() or time64().

After searching for a while, I can see that it's not libc's fault, but the culprit is actually the kernel.

In order for libc to fetch the current time, it need to execute a system call for it: (Source)

time_t time (t) time_t *t;
{
    // ...
    INTERNAL_SYSCALL_DECL (err);
    time_t res = INTERNAL_SYSCALL (time, err, 1, NULL);
    // ...
    return res;
}

The system call is defined as: (Source)

SYSCALL_DEFINE1(time, time_t __user *, tloc)
{
    time_t i = get_seconds();
    // ...
    return i;
}

The function get_seconds() returns an unsigned long, like so: (Source)

unsigned long get_seconds(void)
{
    struct timekeeper *tk = &timekeeper;

    return tk->xtime_sec;
}

And timekeeper.xtime_sec is actually 64-bit: (Source)

struct timekeeper {
    // ...
    /* Current CLOCK_REALTIME time in seconds */
    u64                     xtime_sec;
    // ...
}

Now, if you know your C, you know that the size of unsigned long is actually implementation-dependant. On my 64-bit machine here, it's 64-bit; but on my 32-bit machine here, it's 32-bit. It possibly could be 64-bit on some 32-bit implementation, but there's no guarantee.

On the other hand, u64 is always 64-bit, so at the very base, the kernel keeps track of the time in a 64-bit type. Why it then proceeds to return this as an unsigned long, which is not guaranteed to be 64-bit long, is beyond me.

In the end, even if libc's would force time_t to hold a 64-bit value, it wouldn't change a thing.

You could tie your application deeply into the kernel, but I don't think it's even worth it.

Scorch answered 16/1, 2013 at 17:6 Comment(2)
So, write a patch and post it. Both for libc (typedef long long int time_t) and the kernel long long int get_seconds(void)Thistledown
@Thistledown changing the type of existing functions breaks many things, so new syscalls had to be introduced for 64-bit time instead. See belowParson
P
18

Many answers above said that this is impossible, but that's entirely incorrect. It was not possible at that time yet people had been talking about fixing it for years. Finally 64-bit time support on 32-bit platforms was introduced to the Linux 5.1 kernel with the addition of the new *time64 syscalls. Look at this table you can see that those syscalls are only available on 32-bit platforms. Now if you're writing code for 32-bit systems you can call clock_gettime64 directly (from inline assembly or C with syscall()) to get the current time

However after that you're completely on your own. If you want full userspace support you must be on Linux 5.6 or higher along with musl 1.2+ or glibc 2.32+. Just rebuild your code and your time_t will become 64-bit long

  • All user space must be compiled with a 64-bit time_t, which will be supported in the coming musl-1.2 and glibc-2.32 releases, along with installed kernel headers from linux-5.6 or higher.

  • Applications that use the system call interfaces directly need to be ported to use the time64 syscalls added in linux-5.1 in place of the existing system calls. This impacts most users of futex() and seccomp() as well as programming languages that have their own runtime environment not based on libc.

https://lkml.org/lkml/2020/1/29/355?anz=web

Unfortunately, as Debian said while they're transitioning you may still need to build your own libs in various situations

glibc 2.34 provides support for both the existing 32-bit ABI/API and a new 64-bit ABI/API. However it does not provide a switch forcing use of the new API/ABI - each build/package chooses explicitly to use the 64bit API/ABI (by setting _TIME_BITS=64). This is a problem for Debian as in a normal transition we expect that simply building against the new library will get you the new ABI. Something (glibc, dpkg, gcc?) has to say 'use 64-bit time by default'. 1030159 has implemented a DEB_BUILD_OPTIONS=abi=+time64 option as a consistent Debian mechanism.

For more information

Parson answered 16/3, 2020 at 16:8 Comment(12)
On my 32 bit Linux 5.4 i can't call clock_gettime64 etc. It tells me undefined reference. What do I need to include/define/install in order for it to work?Govern
@Govern how do you call it? It's a system call, not a C function. In C you'll need to use syscall() as I mentioned above, but you need to know how to put the syscall number and how to put all the register valuesParson
Yes, I know, I call it like this: syscall(SYS_clock_gettime64, ...);. It says "error: 'SYS_clock_gettime64' undeclared ... did you mean 'SYS_clock_gettime"Govern
@Govern you can see how it's called in musl. See also this. If that doesn't help then please ask that in another questionParson
@Parson is it possible to compile a 32 bit ARM architecture with 64 bit time_t?Benzoyl
@D.Smith yes, of course. If you have the appropriate versions of kernel and libc then just compile the code and time_t will automatically be 64-bit as I said. If you have the correct kernel but not libc then do the syscall manually: syscall(403, CLOCK_REALTIME, custom_timespec_struct); with 403 being the syscall number for clock_gettime64 in ARMParson
@Parson I have compiled a custom Linux 5.10 with custom gcc toolchain using glibc 2.32 and sizeof(time_t) still returns 4, __TIMESIZE is still set to 32. I'm not sure how to achieve 64 bit time_t.Benzoyl
@D.Smith I think there are some issues with your custom gcc. Please ask a separate question on thatParson
@Parson I have: https://mcmap.net/q/540813/-glibc-set-__timesizeBenzoyl
Just popped in to add: on musl, and I believe in glibc, it's not necessary to have a kernel with time64 support in order to use 64-bit time_t. On an older kernel, you'll be limited not to set the current time or file timestamps past Y2038, but you can still use the new ABI for applications and they'll be all ready to go when you move to a newer kernel.Kao
Apparently you actually need glibc 2.34+ (not 2.32+). Also, you must enable LFS (large file support) or the compilation will fail. This is annoying because you can't just force _TIME_BITS=64 regardless of architecture, you need to actually detect you're on 32-bits and THEN force-enable _TIME_BITS=64, and the LFS defines, otherwise you get compilation errors...Lavonia
Interestingly, even with glibc 2.36 (Debian Bookworm on armhf), it does not automatically use 64-bit time_t for me. Instead, I have to manually specify it in CFLAGS in order to use 64bit instead of 32bit time_t (e.g. DEB_CXXFLAGS_APPEND="-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64" before debuild if compiling Debian package), otherwise it continues to use 32-bit time_tGaudreau
S
14

Apparently, no it's not possible. For starters, there is only one time() function in Linux, no time32() or time64().

After searching for a while, I can see that it's not libc's fault, but the culprit is actually the kernel.

In order for libc to fetch the current time, it need to execute a system call for it: (Source)

time_t time (t) time_t *t;
{
    // ...
    INTERNAL_SYSCALL_DECL (err);
    time_t res = INTERNAL_SYSCALL (time, err, 1, NULL);
    // ...
    return res;
}

The system call is defined as: (Source)

SYSCALL_DEFINE1(time, time_t __user *, tloc)
{
    time_t i = get_seconds();
    // ...
    return i;
}

The function get_seconds() returns an unsigned long, like so: (Source)

unsigned long get_seconds(void)
{
    struct timekeeper *tk = &timekeeper;

    return tk->xtime_sec;
}

And timekeeper.xtime_sec is actually 64-bit: (Source)

struct timekeeper {
    // ...
    /* Current CLOCK_REALTIME time in seconds */
    u64                     xtime_sec;
    // ...
}

Now, if you know your C, you know that the size of unsigned long is actually implementation-dependant. On my 64-bit machine here, it's 64-bit; but on my 32-bit machine here, it's 32-bit. It possibly could be 64-bit on some 32-bit implementation, but there's no guarantee.

On the other hand, u64 is always 64-bit, so at the very base, the kernel keeps track of the time in a 64-bit type. Why it then proceeds to return this as an unsigned long, which is not guaranteed to be 64-bit long, is beyond me.

In the end, even if libc's would force time_t to hold a 64-bit value, it wouldn't change a thing.

You could tie your application deeply into the kernel, but I don't think it's even worth it.

Scorch answered 16/1, 2013 at 17:6 Comment(2)
So, write a patch and post it. Both for libc (typedef long long int time_t) and the kernel long long int get_seconds(void)Thistledown
@Thistledown changing the type of existing functions breaks many things, so new syscalls had to be introduced for 64-bit time instead. See belowParson
M
7

No time64()/time32() function are included into standard libraries.

No time32_t/time64_t defines are contemplated in standard headers.

time_t is defined into time.h as typedef __time_t time_t;

Following a long chain of redefines, you'll discover that __time_t is defined as 32 bit on 32 bit machines and 64bit on 64 bit machines.

Magnetochemistry answered 16/1, 2013 at 16:1 Comment(0)
C
1

Use this library: https://github.com/evalEmpire/y2038

The goal of this project is to provide a drop-in replacement for POSIX time.h which will work on machines which have a 32-bit time_t, yet not suffer from the 2038 bug. This will allow C programmers to be 2038-safe without having to rewrite their software to a new interface. It does this while still using the system time zone database.

Currin answered 26/10, 2018 at 14:5 Comment(0)
D
-4

If you really need this, why not roll your own?

typedef int32_t my_time32;
typedef int64_t my_time64;


my_time32 get_mytime32() {
    if (sizeof(time_t) == sizeof(my_time32))
        return time(NULL);
    else {
        /* Check for overflow etc. here... */
        return (my_time32)(time(NULL));
    }
}

And similarly for get_mytime64().

If you do not care about overflow, a simple return time(NULL); would work for both functions thanks to C's implicit numeric conversions.

Dysphasia answered 16/1, 2013 at 17:15 Comment(2)
I don't think a simple return time(NULL); would work for get_mytime64() on 32-bit platforms - it would just truncate.Jenninejennings
@Jenninejennings Yes hence the "check for overflow etc here" comment. In general you have a problem dealing with 32-bit timesDysphasia

© 2022 - 2024 — McMap. All rights reserved.