From 5d1e6325f26380aea9a12d126571fcee3c30af31 Mon Sep 17 00:00:00 2001 From: Wolfgang Hommel Date: Tue, 3 Sep 2019 12:01:33 +0200 Subject: [PATCH] Add FAKE_SETTIME to CFLAGS to intercept time-setting calls (#179) --- NEWS | 11 ++++++ README | 10 ++++++ src/Makefile | 3 ++ src/libfaketime.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+) diff --git a/NEWS b/NEWS index cba2534..0161eb7 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,14 @@ +Since 0.9.8: + - Compile-time CFLAG FAKE_SETTIME can be enabled to + intercept calls to clock_settime(), settimeofday(), and + adjtime(). (suggested and prototyped by @ojura) + - Additional compile-time CFLAGs can be passed via the + environment variable FAKETIME_COMPILE_CFLAGS when + running 'make'. + - src/Makefile CFLAG FORCE_PTHREAD_NONVER should be set on + systems that hang on CLOCK_REALTIME, or that hang on + CLOCK_MONOTONIC where FORCE_MONOTONIC_FIX is not sufficient. + Since 0.9.7: - Passthrough for unknown clock ids to avoid error messages - Fixes for multithreaded operations (mliertzer, qnox) diff --git a/README b/README index 66cc135..d146c45 100644 --- a/README +++ b/README @@ -456,6 +456,16 @@ a lot of processes are started (e.g., servers handling many containers or similar virtualization mechanisms). +Intercepting time-setting calls +------------------------------- + +libfaketime can be compiled with the CFLAG "-DFAKE_SETTIME" in order +to also intercept time-setting functions, i.e., clock_settime(), +settimeofday(), and adjtime(). Instead of passing the timestamp a +program sets through to the system, only the FAKETIME environment +variable will be adjusted accordingly. + + 4f) Faking the date and time system-wide ---------------------------------------- diff --git a/src/Makefile b/src/Makefile index f13a6bb..4f67ca3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -30,6 +30,9 @@ # FAKE_PTHREAD # - Intercept pthread_cond_timedwait # +# FAKE_SETTIME +# - Intercept clock_settime(), settimeofday(), and adjtime() +# # FORCE_MONOTONIC_FIX # - If the test program hangs forever on # " pthread_cond_timedwait: CLOCK_MONOTONIC test diff --git a/src/libfaketime.c b/src/libfaketime.c index b70a3d3..119ae4d 100644 --- a/src/libfaketime.c +++ b/src/libfaketime.c @@ -255,6 +255,7 @@ static int fake_monotonic_clock = 1; #endif static int cache_enabled = 1; static int cache_duration = 10; /* cache fake time input for 10 seconds */ +static int force_cache_expiration = 0; /* * Static timespec to store our startup time, followed by a load-time library @@ -2607,6 +2608,12 @@ int fake_clock_gettime(clockid_t clk_id, struct timespec *tp) cache_expired = 1; } + if (force_cache_expiration != 0) + { + cache_expired = 1; + force_cache_expiration = 0; + } + if (cache_expired == 1) { static char user_faked_time[BUFFERLEN]; /* changed to static for caching in v0.6 */ @@ -3169,6 +3176,89 @@ __asm__(".symver pthread_cond_destroy_232, pthread_cond_destroy@@GLIBC_2.3.2"); #endif +/* + * Intercept calls to time-setting functions if compiled with FAKE_SETTIME set. + * Based on suggestion and prototype by @ojura, see https://github.com/wolfcw/libfaketime/issues/179 + */ +#ifdef FAKE_SETTIME +int clock_settime(clockid_t clk_id, const struct timespec *tp) { + + /* only CLOCK_REALTIME can be set */ + if (clk_id != CLOCK_REALTIME) { + errno = EPERM; + return -1; + } + + /* sanity check for the pointer */ + if (tp == NULL) { + errno = EFAULT; + return -1; + } + + /* When setting the FAKETIME environment variable to the new timestamp, + we do not have to care about 'x' or 'i' modifiers given previously, + as they are not erased when parsing them. */ + struct timespec current_time; + DONT_FAKE_TIME(clock_gettime(clk_id, ¤t_time)) + ; + + time_t sec_diff = tp->tv_sec - current_time.tv_sec; + long nsec_diff = tp->tv_nsec - current_time.tv_nsec; + char newenv_string[256]; + double offset = (double) sec_diff; + offset += (double) nsec_diff/SEC_TO_nSEC; + snprintf(newenv_string, 255, "%+f", offset); + + setenv("FAKETIME", newenv_string, 1); + force_cache_expiration = 1; /* make sure it becomes effective immediately */ + + return 0; +} + +int settimeofday(const struct timeval *tv, void *tz) +{ + /* The use of timezone *tz is obsolete and simply ignored here. */ + if (tz == NULL) tz = NULL; + + if (tv == NULL) + { + errno = EFAULT; + return -1; + } + else + { + struct timespec tp; + tp.tv_sec = tv->tv_sec; + tp.tv_nsec = tv->tv_usec * 1000; + clock_settime(CLOCK_REALTIME, &tp); + } + return 0; +} + +int adjtime (const struct timeval *delta, struct timeval *olddelta) +{ + /* Always signal true full success when olddelta is requested. */ + if (olddelta != NULL) + { + olddelta->tv_sec = 0; + olddelta->tv_usec = 0; + } + + if (delta != NULL) + { + struct timespec tp; + clock_gettime(CLOCK_REALTIME, &tp); + tp.tv_sec += delta->tv_sec; + tp.tv_nsec += delta->tv_usec * 1000; + /* This actually will make the clock jump instead of gradually + adjusting it, but we fulfill the caller's intention and an + additional thread just for the gradual changes does not seem + to be worth the effort presently. */ + clock_settime(CLOCK_REALTIME, &tp); + } + return 0; +} +#endif /* * Editor modelines