Extend timetest to exercise utime(path, NULL) and utimes(path, NULL), so
the older file timestamp wrappers are covered alongside the existing
utimensat()/futimens() "set to now" checks.
Resolve utime(..., NULL) and utimes(..., NULL) through the current fake
CLOCK_REALTIME instead of deriving "now" from local timeval state.
This matches the real dpkg-source failure mode seen under
FAKETIME='@2037-01-01 00:00:00', where touching .pc/applied-patches
ended up reaching utimensat() with an invalid explicit timespec and
failing with EINVAL.
Follow-up to #535
Use fake_clock_gettime(CLOCK_REALTIME) to resolve NULL/UTIME_NOW
timestamps instead of reconstructing them from user_offset. This
avoids invalid timestamp handling in start-at faketime mode, where
"now" is derived from the configured base time plus elapsed real time.
Closes: #535
POSIX named semaphores (sem_t) have architecture-dependent internal
layout in glibc: 32 bytes on 64-bit, 16 bytes on 32-bit. When a
64-bit faketime wrapper creates a semaphore and spawns a 32-bit child,
the child misinterprets the counter and hangs on sem_wait forever.
Extract ft_sem_* abstraction into shared ft_sem.h/ft_sem.c with three
backends: FT_POSIX (existing), FT_SYSV (existing), FT_FLOCK (new
default). The flock backend uses kernel-mediated file locking on
/dev/shm/faketime_lock_<pid>, which is architecture-independent and
auto-releases on process death.
Both libfaketime.so and the faketime wrapper now use the same shared
abstraction, ensuring protocol agreement regardless of backend.
Replace struct timespec (arch-dependent long/time_t) with fixed-width
int64_t pairs in ft_shared_s so that 32-bit and 64-bit processes
interpret the same shared memory layout identically.
Fix ftruncate calls that allocated sizeof(uint64_t) (8 bytes) instead
of sizeof(struct ft_shared_s) (64-80 bytes) for the shared memory
region. Fix munmap in ft_cleanup using the wrong size.
Add struct layout test and cross-process shared memory functional test.
Compiling tests with GCC 16 results into this warning:
gcc -shared -o libmallocintercept.so -fpic -std=gnu99 -Wall -DFAKE_STAT -Werror -Wextra -U_FILE_OFFSET_BITS -U_TIME_BITS libmallocintercept.c
libmallocintercept.c: In function ‘free’:
libmallocintercept.c:79:12: error: variable ‘ptr2’ set but not used [-Werror=unused-but-set-variable=]
79 | long int ptr2 = (long int) ptr; ptr2 -= (long int) ptr;
| ^~~~
cc1: all warnings being treated as errors
The ptr2 variable was added in the past to silence compiler warnings,
probably for the very same reason (commits
75cbe8e507 and
2bfbe19f71). GCC 16 is smarter and
discovers that the ptr2 variables is not needed.
This patch changes the work around to "(void) unused_variable;" idiom
recommended by GCC manual and already used in print_msg().
I noticed a bug in the semaphore handling, when using the System V semaphore
backend:
$ LD_PRELOAD=./src/libfaketime.so.1 bash -c "echo foo | sed s/foo/bar/"
libfaketime: In lock_for_stat(), ft_sem_lock failed: Invalid argument
[...exited with error...]
(Beware, the above command-line is not 100% deterministic; sometimes it
succeeds.)
Looking at the strace for the above command-line, it seems the bash echo
builtin process (or thread?) decides to remove the semaphore upon
exiting, while it's still in use by the sed process. sed then gets
EINVAL error ("Invalid argument") on its next semop call.
The root cause is a semantic difference between POSIX sem_unlink and
SysV semop(..., IPC_RMID), the two implementations for ft_sem_unlink:
* sem_unlink allows the semaphore to be used afterwards, as long as a
process has a reference to the semaphore.
* semop(..., IPC_RMID) removes the semaphore immediately, and further
use results in EINVAL error.
AFAICT, the simplest fix is to only let the owner of the semaphore (and
shared memory) do the clean up, which is what this patch does. Both
semaphore backends pass the tests with this change.
ft_sem_create() is called with an argument located on the stack, which
means it's a bad idea to keep a reference to it in the 'name' field of
ft_sem_t -- the pointed to data goes out of scope and results in
unpredictable behaviour.
Fix it by making a copy of the semaphore name. Allocate a 256 char
buffer, to match existing code.
Fixes: 2649cdb156 ("Add semaphore abstraction layer")
musl defines stat64 as stat, leading to this build error:
gcc -o libfaketime.o -c -std=gnu99 -Wall -Wextra -Werror -DFAKE_PTHREAD -DFAKE_STAT -DFAKE_UTIME -DFAKE_SLEEP -DFAKE_TIMERS -DFAKE_INTERNAL_CALLS -fPIC -DPREFIX='"'/nix/store/qpyvvrcas950da98mssw6ixlw7ckvyrb-libfaketime-0.9.11'"' -DLIBDIRNAME='"'/lib'"' -Wno-nonnull-compare libfaketime.c
In file included from libfaketime.c:55:
libfaketime.c:1276:5: error: redefinition of ‘stat’
1276 | int stat64 (const char *path, struct stat64 *buf)
| ^~~~~~
/nix/store/g9cgi4yyn5vrd1f9axj8gxdvwzv5ssvk-musl-1.2.5-dev/include/sys/stat.h:80:5: note: previous definition of ‘stat’ with type ‘int(const char *, struct stat *)’
80 | int stat(const char *__restrict, struct stat *__restrict);
| ^~~~
make[1]: *** [Makefile:161: libfaketime.o] Error 1
Fix it by only defining stat64 when building against glibc, since it's
not straight forward to detect musl, and it's the safest approach; there
might be other libc implementations that behave like musl.
Fixes: 53ba71e547 ("Handle stat64() call")
Add ft_sem_*() functions that use the POSIX semaphore API.
In preparation for adding System V semaphores as an alternative to POSIX
semaphores, because glibc breaks POSIX semaphores when operating in
mixed 32- and 64-bit environments[1].
[1] https://sourceware.org/bugzilla/show_bug.cgi?id=17980