wolfcw / libfaketime

libfaketime modifies the system time for a single application
https://github.com/wolfcw/libfaketime
GNU General Public License v2.0
2.64k stars 321 forks source link

Support for TAI offset / leap seconds #393

Open maxnoe opened 2 years ago

maxnoe commented 2 years ago

Running the following program using libfaketime has the wrong UTC - TAI offset for all dates before the last leap second has been introduced and also does not show the leap second on the leap second day.

It would be really great if this library supported that, as we need to test our software behavior on days with leap seconds.

#include "time.h"
#include "stdio.h"
#include "unistd.h"

int main() {
    struct timespec utc;
    struct timespec tai;

    for (int i = 0; i < 5; i++) {
        clock_gettime(CLOCK_REALTIME, &utc);
        clock_gettime(CLOCK_TAI, &tai);

        printf("UTC: %ld.%ld\n", utc.tv_sec, utc.tv_nsec);
        printf("TAI: %ld.%ld\n", tai.tv_sec, tai.tv_nsec);
        double delta_ns = tai.tv_nsec - utc.tv_nsec;
        double delta = tai.tv_sec - utc.tv_sec + 1e-9 * delta_ns;
        printf("Delta: %f\n", delta);

        sleep(1);
    }

    return 0;
}

Compiler call:

$ gcc -o get_time --std=gnu99 get_time.c

Program call:

❯ LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1 FAKETIME="@2016-12-31 23:59:58" ./get_time
UTC: 1483225198.2674
TAI: 1483225235.3305
Delta: 37.000001
UTC: 1483225199.116320
TAI: 1483225236.118172
Delta: 37.000002
UTC: 1483225200.275030
TAI: 1483225237.276682
Delta: 37.000002
UTC: 1483225201.519567
TAI: 1483225238.521155
Delta: 37.000002
UTC: 1483225202.768766
TAI: 1483225239.770229
Delta: 37.000001

This shows the current delta (37 seconds), not the correct delta before midnight (36).

Even more wrong for dates further in the past:

❯ LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1 FAKETIME="@2006-12-31 23:59:58" ./get_time
UTC: 1167605998.3051
TAI: 1167606035.4117
Delta: 37.000001
UTC: 1167605999.286252
TAI: 1167606036.288094
Delta: 37.000002
UTC: 1167606000.426703
TAI: 1167606037.428177
Delta: 37.000001
UTC: 1167606001.654888
TAI: 1167606038.656603
Delta: 37.000002
UTC: 1167606002.851362
TAI: 1167606039.853065
Delta: 37.000002

Correct would be 33 before midnight, 34 after.

More details and explanations in this file here: https://www.ietf.org/timezones/data/leap-seconds.list

maxnoe commented 2 years ago

I haven't yet looked into how this could be done, I am not familar with the code base at all, but I'd be happy to give it a shot if you decide that could be done.

wolfcw commented 2 years ago

It's an interesting use case, but unfortunately libfaketime does not have support for CLOCK_TAI yet; it treats it just like any other unknown clock_id.

Full support for CLOCK_TAI would probably mean to go through all functions in src/libfaketime.c and check where the various clock_ids are handled differently (based on case/switch or if statements).

However, for starters and testing with your example code, it probably should be sufficient to extend int fake_clock_gettime(...) and likely handle CLOCK_TAI similar to CLOCK_REALTIME. This in turn will probably require to extend the global variables ftpl_starttime, ftpl_timecache, and ftpl_faketimecache with yet another field for CLOCK_TAI, which then must be initialized in system_time_from_system() by making a call to real_clock_gettime with CLOCK_TAI, and this new field should also be handled in any other functions writing to these variables.

So far I'd assume that libfaketime's approach of passing arguments through to the real system calls is also sufficient to handle CLOCK_TAI, so we don't need a hardcoded list of leap seconds.

maxnoe commented 2 years ago

Hi,

thanks for the quick feedback.

Unfortunately I think this is not going to work. At least to my knowledge, the linux kernel does not keep a leap second table but only the current UTC - TAI offset, which needs to be updated by e.g. ntp.

So I think this library would need to know about when leap seconds occured to provide the correct behavior.

wolfcw commented 2 years ago

OK, I wasn't aware of that limitation.

The steps outlined above would be required anyway to make libfaketime handle the CLOCK_TAI clock_id explicitly. Then, in fake_clock_gettime(), the result can be arbitrarily manipulated further. Eventually the value resulting so far is sufficient to determine which offset of seconds has to be added/subtracted when a table of leap seconds gets integrated/hardcoded into libfaketime. You can also memorize previous values across calls to (fake_)clock_gettime by using static variables if that is necessary to determine when another leap second has to be added as an offset.

The key here is probably knowing that libfaketime does nothing else but calling the 'real' clock_gettime() internally and then adds (or subtracts) an offset based on the current FAKETIME setting (and, if you like, anything else). In theory, one could even connect to external sources for input, but I don't think that's necessary in this case.

cappe987 commented 10 months ago

So I think this library would need to know about when leap seconds occured to provide the correct behavior.

The Time Zone Database in Linux provides two files, /usr/share/timezone/leapseconds and /usr/share/timezone/leap-seconds.list, which contains all past leap seconds. Checking in a copy of the files to this repo and keeping them updated here could also be an alternative if you don't want to rely on the host system to have them updated.