From 6c7aa3966c65cd9b42549e56741d2e03ca943c51 Mon Sep 17 00:00:00 2001 From: Mike Rushe Date: Tue, 17 Feb 2026 10:51:56 -0500 Subject: [PATCH] Implement FAKETIME_FOLLOW_ABSOLUTE feature See README for feature intent. Simple regression test at test_follow_absolute.sh. --- README | 9 +++ src/libfaketime.c | 25 +++++++- test/functests/test_follow_absolute.sh | 79 ++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 test/functests/test_follow_absolute.sh diff --git a/README b/README index dce911f..be74e6e 100644 --- a/README +++ b/README @@ -420,6 +420,15 @@ LD_PRELOAD=/path/to/libfaketime.so.1 \ # (in a different terminal window or whatever) touch -t 2002290123.45 /tmp/my-demo-file.tmp +Setting the environment variable FAKETIME_FOLLOW_ABSOLUTE=1 enables a submode +of FAKETIME_FOLLOW_FILE behavior where fake time ONLY advances when the follow +file's timestamp advances. In this mode an application that is not subject to +libfaketime LD_PRELOAD intercept can absolutely control time for applications +that are hooked by libfaketime. For example, a host application can control the +timestamp of a follow file mapped into a container to implement (relatively) +clean pause/resume behavior for fake time applications running within the +container. + Changing the 'x' modifier during run-time ----------------------------------------- diff --git a/src/libfaketime.c b/src/libfaketime.c index 74caccb..f8d7479 100644 --- a/src/libfaketime.c +++ b/src/libfaketime.c @@ -2701,12 +2701,33 @@ static void parse_ft_string(const char *user_faked_time) else { user_faked_time_timespec.tv_sec = master_file_stats.st_mtime; - user_faked_time_timespec.tv_nsec = 0; + if (NULL == getenv("FAKETIME_FOLLOW_ABSOLUTE")) + { + /* Normal FAKETIME_FOLLOW_FILE behavior; fake time coasts between mtime updates */ + user_faked_time_timespec.tv_nsec = 0; + } + else + { + /* Set fake time to nanosecond-precision mtime */ + user_faked_time_timespec.tv_nsec = master_file_stats.st_mtim.tv_nsec; + + /* Freeze fake time (mtime is absolute truth in this mode) */ + if (ft_mode != FT_NOOP) + { + ft_mode = FT_FREEZE; + } + + /* Bypass parse_modifiers since we have absolute time */ + user_faked_time_set = true; + } } } if (NULL == getenv("FAKETIME_DONT_RESET")) reset_time(); - goto parse_modifiers; + if (!user_faked_time_set) + { + goto parse_modifiers; + } break; case 'i': diff --git a/test/functests/test_follow_absolute.sh b/test/functests/test_follow_absolute.sh new file mode 100644 index 0000000..5159ef2 --- /dev/null +++ b/test/functests/test_follow_absolute.sh @@ -0,0 +1,79 @@ +# Tests for FAKETIME_FOLLOW_ABSOLUTE feature. +# +# When FAKETIME_FOLLOW_ABSOLUTE=1 is set alongside FAKETIME="%" and +# FAKETIME_FOLLOW_FILE, time freezes at the follow file's mtime +# and only advances when the file's mtime changes. + +FOLLOW_FILE=".follow_absolute_test_file" + +init() +{ + typeset testsuite="$1" + PLATFORM=$(platform) + if [ -z "$PLATFORM" ]; then + echo "$testsuite: unknown platform! quitting" + return 1 + fi + echo "# PLATFORM=$PLATFORM" + return 0 +} + +run() +{ + init + + run_testcase follow_absolute_basic + run_testcase follow_absolute_freeze + run_testcase follow_absolute_tracks_mtime + + rm -f "$FOLLOW_FILE" +} + +# Helper to run a command with follow-absolute configuration +follow_absolute_cmd() +{ + FAKETIME_FOLLOW_FILE="$FOLLOW_FILE" \ + FAKETIME_FOLLOW_ABSOLUTE=1 \ + fakecmd "%" "$@" +} + +# Test that time matches the follow file's mtime +follow_absolute_basic() +{ + touch -d "2020-03-15 10:30:00 UTC" "$FOLLOW_FILE" + typeset actual + actual=$(follow_absolute_cmd date -u +"%Y-%m-%d %H:%M:%S") + asserteq "$actual" "2020-03-15 10:30:00" \ + "time should match follow file mtime" +} + +# Test that time stays frozen (does not advance with real time) +follow_absolute_freeze() +{ + touch -d "2020-03-15 10:30:00 UTC" "$FOLLOW_FILE" + typeset timestamps + timestamps=$(follow_absolute_cmd \ + perl -e 'print time(), "\n"; sleep(2); print time(), "\n"') + typeset first second + first=$(echo "$timestamps" | head -1) + second=$(echo "$timestamps" | tail -1) + asserteq "$first" "$second" \ + "time should stay frozen within a single process" +} + +# Test that time tracks file mtime changes at millisecond precision +follow_absolute_tracks_mtime() +{ + touch -d "2020-03-15 10:30:00.000 UTC" "$FOLLOW_FILE" + typeset first + first=$(follow_absolute_cmd \ + perl -MTime::HiRes=time -e 'printf "%.3f\n", time()') + + touch -d "2020-03-15 10:30:00.005 UTC" "$FOLLOW_FILE" + typeset second + second=$(follow_absolute_cmd \ + perl -MTime::HiRes=time -e 'printf "%.3f\n", time()') + + assertneq "$first" "$second" \ + "time should advance with file mtime (ms precision)" +}