Implement FAKETIME_FOLLOW_ABSOLUTE feature

See README for feature intent.
Simple regression test at test_follow_absolute.sh.
This commit is contained in:
Mike Rushe
2026-02-17 10:51:56 -05:00
parent 3062fb2004
commit 6c7aa3966c
3 changed files with 111 additions and 2 deletions

9
README
View File

@@ -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
-----------------------------------------

View File

@@ -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':

View File

@@ -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)"
}