mirror of
https://github.com/wolfcw/libfaketime.git
synced 2026-05-17 08:36:28 +03:00
Save faked timestamps to file, load faked timestamps from file
The files can be set using FAKETIME_SAVE_FILE and FAKETIME_LOAD_FILE environment variables respectively.
This commit is contained in:
23
README
23
README
@@ -19,6 +19,7 @@ Content of this file:
|
||||
g) Using the "faketime" wrapper script
|
||||
h) "Limiting" libfaketime
|
||||
i) Spawning an external process
|
||||
j) Saving timestamps to file, loading them from file
|
||||
5. License
|
||||
6. Contact
|
||||
|
||||
@@ -424,11 +425,31 @@ This will run the "echo" command with the given parameter during the first
|
||||
time-related system function call that "myprogram" performs after running for 5
|
||||
seconds.
|
||||
|
||||
4j) Saving timestamps to file, loading them from file
|
||||
--------------------------------
|
||||
|
||||
Faketime can save faked timestamps to a file specified by FAKETIME_SAVE_FILE
|
||||
environment variable. It can also use the file specified by FAKETIME_LOAD_FILE
|
||||
to replay timestamps from it. After consuming the whole file faketime returns
|
||||
to using the rule set in FAKETIME variable, but the timestamp processes will
|
||||
start counting from will be the last timestamp in the file.
|
||||
|
||||
The file stores each timestamp in a stream of saved_timestamp structs
|
||||
without any metadata or padding:
|
||||
|
||||
/** Storage format for timestamps written to file. Big endian.*/
|
||||
struct saved_timestamp {
|
||||
int64_t sec;
|
||||
uint64_t nsec;
|
||||
};
|
||||
|
||||
|
||||
Faketime needs to be run using the faketime wrapper to use the files.
|
||||
|
||||
5. License
|
||||
----------
|
||||
|
||||
FTPL has been released under the GNU Public License, GPL. Please see the
|
||||
FTPL has been released under the GNU Public License, GPL. Please see xthe
|
||||
included COPYING file.
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include <sys/mman.h>
|
||||
#include <semaphore.h>
|
||||
|
||||
#include "faketime_common.h"
|
||||
|
||||
const char version[] = "0.8";
|
||||
|
||||
#ifdef __APPLE__
|
||||
@@ -161,7 +163,7 @@ int main (int argc, char **argv)
|
||||
/* create semaphores and shared memory */
|
||||
int shm_fd;
|
||||
sem_t *sem;
|
||||
uint64_t *ticks;
|
||||
struct ft_shared_s *ft_shared;
|
||||
char shared_objs[PATH_BUFSIZE];
|
||||
|
||||
snprintf(sem_name, PATH_BUFSIZE -1 ,"/faketime_sem_%d", getpid());
|
||||
@@ -189,7 +191,7 @@ int main (int argc, char **argv)
|
||||
}
|
||||
|
||||
/* map shm */
|
||||
if (MAP_FAILED == (ticks = mmap(NULL, sizeof(uint64_t), PROT_READ|PROT_WRITE,
|
||||
if (MAP_FAILED == (ft_shared = mmap(NULL, sizeof(struct ft_shared_s), PROT_READ|PROT_WRITE,
|
||||
MAP_SHARED, shm_fd, 0))) {
|
||||
perror("mmap");
|
||||
cleanup_shobjs();
|
||||
@@ -203,8 +205,9 @@ int main (int argc, char **argv)
|
||||
}
|
||||
|
||||
/* init elapsed time ticks to zero */
|
||||
*ticks = 0;
|
||||
if (-1 == munmap(ticks, (sizeof(uint64_t)))) {
|
||||
ft_shared->ticks = 0;
|
||||
ft_shared->file_idx = 0;
|
||||
if (-1 == munmap(ft_shared, (sizeof(uint64_t)))) {
|
||||
perror("munmap");
|
||||
cleanup_shobjs();
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
36
src/faketime_common.h
Normal file
36
src/faketime_common.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Time operation macros based on sys/time.h
|
||||
* Copyright 2013 Balint Reczey <balint@balintreczey.hu>
|
||||
*
|
||||
* This file is part of the FakeTime Preload Library.
|
||||
*
|
||||
* The FakeTime Preload Library is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License v2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The FakeTime Preload Library is distributed in the hope that it will
|
||||
* be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License v2
|
||||
* along with the FakeTime Preload Library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef FAKETIME_COMMON_H
|
||||
#define FAKETIME_COMMON_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** Data shared among faketime-spawned processes */
|
||||
struct ft_shared_s {
|
||||
/**
|
||||
* When advancing time linearly with each time(), etc. call, the calls are
|
||||
* counted here */
|
||||
uint64_t ticks;
|
||||
/** Index of timstamp to be loaded from file */
|
||||
uint64_t file_idx;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -23,15 +23,20 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <semaphore.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include "time_ops.h"
|
||||
#include "faketime_common.h"
|
||||
|
||||
/* pthread-handling contributed by David North, TDI in version 0.7 */
|
||||
#ifdef PTHREAD
|
||||
@@ -89,12 +94,48 @@ int fake_clock_gettime(clockid_t clk_id, struct timespec *tp);
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* When advancing time linearly with each time(), etc. call, the calls are
|
||||
* counted in shared memory pointed at by ticks and protected by ticks_sem
|
||||
* semaphore */
|
||||
static sem_t *ticks_sem = NULL;
|
||||
static uint64_t *ticks = NULL;
|
||||
/** Semaphore protecting shared data */
|
||||
static sem_t *shared_sem = NULL;
|
||||
|
||||
/** Data shared among faketime-spawned processes */
|
||||
static struct ft_shared_s *ft_shared = NULL;
|
||||
|
||||
/** Storage format for timestamps written to file. Big endian.*/
|
||||
struct saved_timestamp {
|
||||
int64_t sec;
|
||||
uint64_t nsec;
|
||||
};
|
||||
|
||||
static inline void timespec_from_saved (struct timespec *tp,
|
||||
struct saved_timestamp *saved)
|
||||
{
|
||||
/* read as big endian */
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
tp->tv_sec = saved->sec;
|
||||
tp->tv_nsec = saved->nsec;
|
||||
#else
|
||||
if (saved->sec < 0) {
|
||||
uint64_t abs_sec = 0 - saved->sec;
|
||||
((uint32_t*)&(tp->tv_sec))[0] = ntohl(((uint32_t*)&abs_sec)[1]);
|
||||
((uint32_t*)&(tp->tv_sec))[1] = ntohl(((uint32_t*)&abs_sec)[0]);
|
||||
tp->tv_sec = 0 - tp->tv_sec;
|
||||
} else {
|
||||
((uint32_t*)&(tp->tv_sec))[0] = ntohl(((uint32_t*)&(saved->sec))[1]);
|
||||
((uint32_t*)&(tp->tv_sec))[1] = ntohl(((uint32_t*)&(saved->sec))[0]);
|
||||
}
|
||||
((uint32_t*)&(tp->tv_nsec))[0] = ntohl(((uint32_t*)&(saved->nsec))[1]);
|
||||
((uint32_t*)&(tp->tv_nsec))[1] = ntohl(((uint32_t*)&(saved->nsec))[0]);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Saved timestamps */
|
||||
static struct saved_timestamp *stss = NULL;
|
||||
static size_t infile_size;
|
||||
static bool infile_set = false;
|
||||
|
||||
/** File fd to save timestamps to */
|
||||
static int outfile = -1;
|
||||
|
||||
static bool limited_faking = false;
|
||||
static long callcounter = 0;
|
||||
@@ -140,14 +181,14 @@ void ft_cleanup (void) __attribute__ ((destructor));
|
||||
static void ft_shm_init (void)
|
||||
{
|
||||
int ticks_shm_fd;
|
||||
char sem_name[256], shm_name[256], *ft_shared = getenv("FAKETIME_SHARED");
|
||||
if (ft_shared != NULL) {
|
||||
if (sscanf(ft_shared, "%255s %255s", sem_name, shm_name) < 2 ) {
|
||||
printf("Error parsing semaphor name and shared memory id from string: %s", ft_shared);
|
||||
char sem_name[256], shm_name[256], *ft_shared_env = getenv("FAKETIME_SHARED");
|
||||
if (ft_shared_env != NULL) {
|
||||
if (sscanf(ft_shared_env, "%255s %255s", sem_name, shm_name) < 2 ) {
|
||||
printf("Error parsing semaphor name and shared memory id from string: %s", ft_shared_env);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (SEM_FAILED == (ticks_sem = sem_open(sem_name, 0))) {
|
||||
if (SEM_FAILED == (shared_sem = sem_open(sem_name, 0))) {
|
||||
perror("sem_open");
|
||||
exit(1);
|
||||
}
|
||||
@@ -156,7 +197,7 @@ static void ft_shm_init (void)
|
||||
perror("shm_open");
|
||||
exit(1);
|
||||
}
|
||||
if (MAP_FAILED == (ticks = mmap(NULL, sizeof(uint64_t), PROT_READ|PROT_WRITE,
|
||||
if (MAP_FAILED == (ft_shared = mmap(NULL, sizeof(struct ft_shared_s), PROT_READ|PROT_WRITE,
|
||||
MAP_SHARED, ticks_shm_fd, 0))) {
|
||||
perror("mmap");
|
||||
exit(1);
|
||||
@@ -167,32 +208,123 @@ static void ft_shm_init (void)
|
||||
void ft_cleanup (void)
|
||||
{
|
||||
/* detach from shared memory */
|
||||
munmap(ticks, sizeof(uint64_t));
|
||||
sem_close(ticks_sem);
|
||||
if (ft_shared != NULL) {
|
||||
munmap(ft_shared, sizeof(uint64_t));
|
||||
}
|
||||
if (stss != NULL) {
|
||||
munmap(stss, infile_size);
|
||||
}
|
||||
if (shared_sem != NULL) {
|
||||
sem_close(shared_sem);
|
||||
}
|
||||
}
|
||||
|
||||
static void next_time(struct timespec *tp, struct timespec *ticklen)
|
||||
{
|
||||
if (ticks_sem != NULL) {
|
||||
if (shared_sem != NULL) {
|
||||
struct timespec inc;
|
||||
/* lock */
|
||||
if (sem_wait(ticks_sem) == -1) {
|
||||
if (sem_wait(shared_sem) == -1) {
|
||||
perror("sem_wait");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* calculate and update elapsed time */
|
||||
timespecmul(ticklen, *ticks, &inc);
|
||||
timespecmul(ticklen, ft_shared->ticks, &inc);
|
||||
timespecadd(&user_faked_time_timespec, &inc, tp);
|
||||
(*ticks)++;
|
||||
(ft_shared->ticks)++;
|
||||
/* unlock */
|
||||
if (sem_post(ticks_sem) == -1) {
|
||||
if (sem_post(shared_sem) == -1) {
|
||||
perror("sem_post");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void save_time(struct timespec *tp)
|
||||
{
|
||||
if ((shared_sem != NULL) && (outfile != -1)) {
|
||||
struct saved_timestamp time_write;
|
||||
ssize_t n = 0;
|
||||
|
||||
// write as big endian
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
time_write = {tp->tv_sec, tp->tv_nsec};
|
||||
#else
|
||||
if (tp->tv_sec < 0) {
|
||||
uint64_t abs_sec = 0 - tp->tv_sec;
|
||||
((uint32_t*)&(time_write.sec))[0] = htonl(((uint32_t*)&abs_sec)[1]);
|
||||
((uint32_t*)&(time_write.sec))[1] = htonl(((uint32_t*)&abs_sec)[0]);
|
||||
tp->tv_sec = 0 - tp->tv_sec;
|
||||
} else {
|
||||
((uint32_t*)&(time_write.sec))[0] = htonl(((uint32_t*)&(tp->tv_sec))[1]);
|
||||
((uint32_t*)&(time_write.sec))[1] = htonl(((uint32_t*)&(tp->tv_sec))[0]);
|
||||
}
|
||||
((uint32_t*)&(time_write.nsec))[0] = htonl(((uint32_t*)&(tp->tv_nsec))[1]);
|
||||
((uint32_t*)&(time_write.nsec))[1] = htonl(((uint32_t*)&(tp->tv_nsec))[0]);
|
||||
#endif
|
||||
/* lock */
|
||||
if (sem_wait(shared_sem) == -1) {
|
||||
perror("sem_wait");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
lseek(outfile, 0, SEEK_END);
|
||||
while ((sizeof(time_write) < (n += write(outfile, &(((char*)&time_write)[n]),
|
||||
sizeof(time_write) - n))) &&
|
||||
(errno == EINTR));
|
||||
|
||||
if ((n == -1) || (n < sizeof(time_write))) {
|
||||
perror("Saving timestamp to file failed");
|
||||
}
|
||||
|
||||
/* unlock */
|
||||
if (sem_post(shared_sem) == -1) {
|
||||
perror("sem_post");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide faked time from file.
|
||||
* @return time is set from filen
|
||||
*/
|
||||
static bool load_time(struct timespec *tp)
|
||||
{
|
||||
bool ret = false;
|
||||
if ((shared_sem != NULL) && (infile_set)) {
|
||||
|
||||
/* lock */
|
||||
if (sem_wait(shared_sem) == -1) {
|
||||
perror("sem_wait");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((sizeof(stss[0]) * (ft_shared->file_idx + 1)) > infile_size) {
|
||||
/* we are out of timstamps to replay, return to faking time by rules
|
||||
* using last timestamp from file as the user provided timestamp */
|
||||
timespec_from_saved(&user_faked_time_timespec, &stss[(infile_size / sizeof(stss[0])) - 1 ]);
|
||||
ftpl_starttime = *tp;
|
||||
if (ft_shared->ticks == 0) {
|
||||
ft_shared->ticks = 1;
|
||||
}
|
||||
munmap(stss, infile_size);
|
||||
infile_set = false;
|
||||
} else {
|
||||
timespec_from_saved(tp, &stss[ft_shared->file_idx]);
|
||||
ft_shared->file_idx++;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
/* unlock */
|
||||
if (sem_post(shared_sem) == -1) {
|
||||
perror("sem_post");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef FAKE_STAT
|
||||
|
||||
#ifndef NO_ATFILE
|
||||
@@ -621,6 +753,42 @@ void __attribute__ ((constructor)) ftpl_init(void)
|
||||
}
|
||||
}
|
||||
|
||||
if ((tmp_env = getenv("FAKETIME_SAVE_FILE")) != NULL) {
|
||||
if (-1 == (outfile = open(tmp_env, O_RDWR | O_APPEND | O_CLOEXEC | O_CREAT,
|
||||
S_IWUSR | S_IRUSR))) {
|
||||
perror("Opening file for saving timestamps failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* load file only if reading timstamps from it is not finished yet */
|
||||
if ((tmp_env = getenv("FAKETIME_LOAD_FILE")) != NULL) {
|
||||
int infile = -1;
|
||||
struct stat sb;
|
||||
if (-1 == (infile = open(tmp_env, O_RDONLY|O_CLOEXEC))) {
|
||||
perror("Opening file for loading timestamps failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fstat(infile, &sb);
|
||||
if (sizeof(stss[0]) > (infile_size = sb.st_size)) {
|
||||
printf("There are no timstamps in the provided file to load timesamps from");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if ((infile_size % sizeof(stss[0])) != 0) {
|
||||
printf("File size is not multiple of timstamp size. It is probably damaged.");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
stss = mmap(NULL, infile_size, PROT_READ, MAP_SHARED, infile, 0);
|
||||
if (stss == MAP_FAILED) {
|
||||
perror("Mapping file for loading timestamps failed");
|
||||
exit(EXIT_FAILURE);
|
||||
};
|
||||
infile_set = true;
|
||||
}
|
||||
|
||||
tmp_env = getenv("FAKETIME_FMT");
|
||||
if (tmp_env == NULL) {
|
||||
strcpy(user_faked_time_fmt, "%Y-%m-%d %T");
|
||||
@@ -772,6 +940,12 @@ static pthread_mutex_t time_mutex=PTHREAD_MUTEX_INITIALIZER;
|
||||
} /* read fake time from file */
|
||||
} /* cache had expired */
|
||||
|
||||
if (infile_set) {
|
||||
if (load_time(tp)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* check whether the user gave us an absolute time to fake */
|
||||
switch (ft_mode) {
|
||||
case FT_FREEZE: /* a specified time */
|
||||
@@ -799,6 +973,7 @@ static pthread_mutex_t time_mutex=PTHREAD_MUTEX_INITIALIZER;
|
||||
#ifdef PTHREAD_SINGLETHREADED_TIME
|
||||
pthread_cleanup_pop(1);
|
||||
#endif
|
||||
save_time(tp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user