/*
 * libyesterday.c
 *
 * Captures gettimeofday(), time() and clock_gettime() to simulate yesterday's date
 *
 * History:
 *      01/11/2025 Creation.
 *
 * Documentation:
 *      https://rafalcieslak.wordpress.com/2013/04/02/dynamic-linker-tricks-using-ld_preload-to-cheat-inject-features-and-investigate-programs/
 *
 * Usage examples:
 *      # load date program with day set to yesterday
 *      LD_PRELOAD=/path/to/libyesterday.so /bin/date
 *      # load date program with day set to a week ago
 *      LIBYESTERDAY=$((3600*24*7)) LD_PRELOAD=/path/to/libyesterday.so /bin/date
 *
 * Author: Dario Rodriguez antartica@whereismybit.com
 * This program is licensed under the terms of the MIT/X license.
 */

#define _GNU_SOURCE
#include <dlfcn.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>

typedef time_t (*time_fn_type)(time_t *tloc);
typedef int (*gettimeofday_fn_type)(struct timeval *restrict tv, void *restrict tz);
typedef int (*clock_gettime_fn_type)(clockid_t clockid, struct timespec *tp);

static int
libyesterdaydata(time_fn_type *paramtime_fn, gettimeofday_fn_type *paramgettimeofday_fn, clock_gettime_fn_type *paramclock_gettime_fn)
{
        static int init=0;
        static int seconds=3600*24;
        static time_fn_type realtimefn=(time_fn_type)NULL;
        static gettimeofday_fn_type realgettimeofdayfn=(gettimeofday_fn_type)NULL;
        static clock_gettime_fn_type realclock_gettimefn=(clock_gettime_fn_type)NULL;
        if(init==0) {
                char *ptr;
                realtimefn = (time_fn_type)dlsym(RTLD_NEXT,"time");
                realgettimeofdayfn = (gettimeofday_fn_type)dlsym(RTLD_NEXT,"gettimeofday");
                realclock_gettimefn = (clock_gettime_fn_type)dlsym(RTLD_NEXT,"clock_gettime");
                if((ptr=getenv("LIBYESTERDAY"))!=NULL)
                        seconds=atoi(ptr);
                init=1;
        }
        if(paramtime_fn!=NULL)
                *paramtime_fn=realtimefn;
        if(paramgettimeofday_fn!=NULL)
                *paramgettimeofday_fn=realgettimeofdayfn;
        if(paramclock_gettime_fn!=NULL)
                *paramclock_gettime_fn=realclock_gettimefn;
        return(seconds);
}

extern time_t time(time_t *tloc)
{
        time_fn_type realtimefn=(time_fn_type)NULL;
        int seconds;
        int res;
        seconds=libyesterdaydata(&realtimefn,NULL,NULL);
        res=realtimefn(NULL);
        res-=seconds;
        if(tloc!=NULL)
                *tloc=res;
        return(res);
}

extern int gettimeofday(struct timeval *restrict tv, void *restrict tz)
{
        static gettimeofday_fn_type realgettimeofdayfn=(gettimeofday_fn_type)NULL;
        int seconds;
        int res;
        seconds=libyesterdaydata(NULL,&realgettimeofdayfn,NULL);
        res=realgettimeofdayfn(tv,tz);
        tv->tv_sec-=seconds;
        return(res);
}

extern int clock_gettime(clockid_t clockid, struct timespec *tp)
{
        static clock_gettime_fn_type realclock_gettimefn=(clock_gettime_fn_type)NULL;
        int seconds;
        int res;
        seconds=libyesterdaydata(NULL,NULL,&realclock_gettimefn);
        res=realclock_gettimefn(clockid,tp);
        tp->tv_sec-=seconds;
        return(res);
}