diff options
-rw-r--r-- | README | 23 | ||||
-rw-r--r-- | src/faketime.c | 11 | ||||
-rw-r--r-- | src/faketime_common.h | 36 | ||||
-rw-r--r-- | src/libfaketime.c | 215 |
4 files changed, 260 insertions, 25 deletions
@@ -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. diff --git a/src/faketime.c b/src/faketime.c index 163ed5c..8dfd518 100644 --- a/src/faketime.c +++ b/src/faketime.c @@ -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); diff --git a/src/faketime_common.h b/src/faketime_common.h new file mode 100644 index 0000000..e0497eb --- /dev/null +++ b/src/faketime_common.h @@ -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 diff --git a/src/libfaketime.c b/src/libfaketime.c index 3324cf4..0ef9af1 100644 --- a/src/libfaketime.c +++ b/src/libfaketime.c @@ -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; } |