/* 
 * faketime.c - Main part of libfaketime.so
 *
 * Michal Ludvig <michal@logix.cz>, (c) 2003
 * http://www.logix.cz/michal/devel/faketime
 * 
 * Preload to a program calling time(2) or gettimeofday(2) 
 * functions if you need to make it think that it's not 
 * the current date but something different.
 *
 * Run ./libfaketime.so to get a more detailed help or
 * check __faketime_main() function below.
 *
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#include <dlfcn.h>

#include "ld-so.h"

/* On ELF systems the library can print a short help 
 * is invoked directly.  */

#define HAVE_ELF

/* Here we store pointers to orginal time() and gettimeofday()
 * functions.  */
time_t (*orig_time) (time_t *t);
int (*orig_gettimeofday) (struct timeval *tv, struct timezone *tz);
int (*orig_clock_gettime) (clockid_t clk_id, struct timespec *tp);

time_t correction = 0;
int need_parse_env = 1;

/* Look for and store pointers to orginal time() and gettimeofday()
 * functions.  */

static int
open_shlib (void)
{
  orig_time = dlsym (RTLD_NEXT, "time");
  if (dlerror() != NULL)
    return -1;

  orig_gettimeofday = dlsym (RTLD_NEXT, "gettimeofday");
  if (dlerror() != NULL)
    return -1;

  orig_clock_gettime = dlsym (RTLD_NEXT, "clock_gettime");
  if (dlerror() != NULL)
    return -1;

  return 0;
}

/* Look for FAKE_TIME_OFFSET or FAKE_TIME_START environment 
 * variables. */

static void
parse_env (time_t curr_time)
{
  char *fake_env = NULL;
  time_t fake_long = 0;

  need_parse_env = 0;

  fake_env = getenv ("FAKE_TIME_OFFSET");
  if (fake_env)
  {
    correction = -atoll (fake_env);
    return;
  }

  fake_env = getenv ("FAKE_TIME_START");
  if (fake_env)
    fake_long = atoll (fake_env);

  if (fake_long)
    correction = curr_time - fake_long;
}

/* Here we go - wrapper for time(2).  */
extern time_t
time (time_t *t)
{
  time_t curr_time;

  if (! orig_time && open_shlib () != 0)
    return -1;

  curr_time = (*orig_time) (t);

  if (need_parse_env)
    parse_env (curr_time);

  if (t)
    *t -= correction;

  return (curr_time - correction);
}

/* Wrapper for gettimeofday (2).  */
extern int
gettimeofday (struct timeval *tv, struct timezone *tz)
{
  int ret;

  if (! orig_gettimeofday && open_shlib () != 0)
    return -1;

  ret = (*orig_gettimeofday) (tv, tz);

  if (ret == 0 && tv)
  {
    if (need_parse_env)
      parse_env (tv->tv_sec);

    tv->tv_sec -= correction;
  }

  return ret;
}

/* Wrapper for clock_gettime(3).  */
extern int
clock_gettime(clockid_t clk_id, struct timespec *tp)
{
  int ret;

  if (! orig_clock_gettime && open_shlib () != 0)
    return -1;

  ret = (*orig_clock_gettime) (clk_id, tp);

  if (ret == 0 && tp)
  {
    if (need_parse_env)
      parse_env (tp->tv_sec);

    tp->tv_sec -= correction;
  }

  return ret;
}

#ifdef HAVE_ELF
/* This is an entrypoint if the library is invoked as a program, i.e.
 * $ ./libfaketime.so 
 * In this case it prints a short help.
 * NOTE: This is only usable on ELF systems!  */

extern void __faketime_main (void) __attribute__ ((noreturn));
void
__faketime_main (void)
{
  char *banner = ""
    " libfaketime.so - Library for subverting current time.\n"
    "\n"
    " Michal Ludvig <michal@logix.cz>, (c) 2003\n"
    " http://www.logix.cz/michal/devel/faketime\n"
    " \n"
    " Preload to a program calling time(2), gettimeofday(2) \n"
    " or clock_gettime(3) functions if you need to make it think \n"
    " that it's not the current date but a different one.\n"
    "\n"
    " Usage:\n"
    "    \n"
    "    Set one of these environment variables:\n"
    "    \n"
    "        FAKE_TIME_START=<sec-since-epoch>\n"
    "            First call to time() or gettimeofday() will return\n"
    "            the given <sec-since-epoch>. Subsequent calls will\n"
    "            return a time with appropriate correction, i.e. if\n"
    "            the second call to time() occurs five seconds after\n"
    "            the first one, it will return <sec-since-epoch>+5.\n"
    "            \n"
    "        FAKE_TIME_OFFSET=<offset_sec_from_now>\n"
    "            Each call to time() or gettimeofday() returns \n"
    "            current time + <offset_sec_from_now>. \n"
    "\n"
    "    If both variables are set, FAKE_TIME_OFFSET has a precedence.\n"
    "\n"
    "    To convert a given time to <sec-since-epoch> use:\n"
    "        $ date -d \"2000-01-01 12:13:14\" +%s\n"
    "        946725194\n"
    "\n"
    " Example:\n"
    "\n"
    "    $ date\n"
    "    Wed Sep 24 14:41:20 CEST 2003\n"
    "    \n"
    "    $ FAKE_TIME_OFFSET=-86400 LD_PRELOAD=./libfaketime.so date\n"
    "    Tue Sep 23 14:41:20 CEST 2003\n"
    "    \n"
    "    $ FAKE_TIME_START=946725194 LD_PRELOAD=./libfaketime.so date\n"
    "    Sat Jan  1 12:13:14 CET 2000\n"
    "\n";

  printf ("%s", banner);
  _exit (0);
}

const char __invoke_dynamic_linker__[] __attribute__ ((section (".interp")))
  = LD_SO;

#endif  /* HAVE_ELF */