diff --git a/src/libfaketime.c b/src/libfaketime.c index 3acd571..955b868 100644 --- a/src/libfaketime.c +++ b/src/libfaketime.c @@ -340,6 +340,11 @@ static int outfile = -1; static bool limited_faking = false; static long callcounter = 0; + +static bool ft_taper = 0; +static long long ft_taper_begin_nsec_since_epoch = -1; +static long long ft_taper_end_nsec_since_epoch = -1; + static long ft_start_after_secs = -1; static long ft_stop_after_secs = -1; static long ft_start_after_ncalls = -1; @@ -1156,6 +1161,149 @@ int statx(int dirfd, const char *pathname, int flags, unsigned int mask, struct } #endif +static long long faketime_offset_ns() +{ + return ((long long)user_offset.tv_sec) * 1000000000 + (long long)user_offset.tv_nsec; +} + +// figure out what combination of ifdefs is appropriate, or use +// this unconditionally +#if 1 + +static long long mult_div_avoid_overflow(long long a, long long b, long long d) +{ + __int128_t acc = a; + acc *= b; + acc /= d; + return (long long)acc; +} + +#else + +// (a * b + c) / d, but do something to avoid multiplication overflow +// (only "mostly" prevents overflow, not if the values are already +// over 2^62) +// +// precondition: a < d, r < d +static long long mad_div_avoid_overflow_inner(long long a, long long b, long long c, long long d) +{ + if (a <= 1 || b <= 1) { + return (a * b + c) / d; + } + // how many multiples of [a] we need to make a full [d] + long long k = (d + a - 1) / a; // 2 <= k <= d/2+1 + long long rem_per_f = a * k - d; + // how many occurrences of [a*k] (and therefore [d]) we can + // easily take from [a*b] + long long f = b / k; + // remaining: rem_per_f * f + (b % k) * a + c + long long rem = (b % k) * a + c; + long long res = f + rem / d; + rem = rem % d; + return res + mad_div_avoid_overflow_inner(rem_per_f, f, rem, d); +} + +static long long mult_div_avoid_overflow(long long a, long long b, long long d) +{ + long long res = a / d; + return res + mad_div_avoid_overflow_inner(a%d, b, 0, d); +} + +#endif + +static long long faketime_do_tapered_transform_gen(long long t, long long taper_begin, long long taper_end, long long offset) +{ + if (t <= taper_begin) + { + return t; + } + if (t >= taper_end) + { + return t + offset; + } + long long w = taper_end - taper_begin; + long long h = w + offset; + // [h] output time passes over [w] input time + long long dt1 = t - taper_begin; + // so we're looking for dt1 * h / w + long long dt2 = mult_div_avoid_overflow(dt1, h, w); + return taper_begin + dt2; +} + +static long long faketime_do_tapered_offset(long long t) +{ + if (!ft_taper) + { + return t + faketime_offset_ns(); + } + return faketime_do_tapered_transform_gen(t, ft_taper_begin_nsec_since_epoch, ft_taper_end_nsec_since_epoch, faketime_offset_ns()); +} + +static long long faketime_undo_tapered_offset(long long t) +{ + if (!ft_taper) + { + return t - faketime_offset_ns(); + } + long long offset = faketime_offset_ns(); + return faketime_do_tapered_transform_gen(t, ft_taper_begin_nsec_since_epoch, ft_taper_end_nsec_since_epoch + offset, -offset); +} + +static void faketime_div_mod(long long ns, long long d, long long *res, long long *rem) +{ + *res = ns / d; + *rem = ns % d; + if(*rem < 0) { + (*rem) += d; + (*res)--; + } +} + +static struct timeval faketime_undo_tapered_offset_timeval(struct timeval t) +{ + long long nsec = ((long long)t.tv_sec) * 1000000000 + ((long long)t.tv_usec) * 1000; + nsec = faketime_undo_tapered_offset(nsec); + long long sec; + long long nsec_rem; + faketime_div_mod(nsec, 1000000000, &sec, &nsec_rem); + t.tv_sec = sec; + t.tv_usec = nsec_rem / 1000; + return t; +} + +static struct timespec faketime_undo_tapered_offset_timespec(struct timespec t) +{ + long long nsec = ((long long)t.tv_sec) * 1000000000 + ((long long)t.tv_nsec); + nsec = faketime_undo_tapered_offset(nsec); + long long sec; + long long nsec_rem; + faketime_div_mod(nsec, 1000000000, &sec, &nsec_rem); + t.tv_sec = sec; + t.tv_nsec = nsec_rem; + return t; +} + +static struct timespec faketime_do_tapered_offset_timespec(struct timespec t) +{ + long long nsec = ((long long)t.tv_sec) * 1000000000 + ((long long)t.tv_nsec); + nsec = faketime_do_tapered_offset(nsec); + long long sec; + long long nsec_rem; + faketime_div_mod(nsec, 1000000000, &sec, &nsec_rem); + t.tv_sec = sec; + t.tv_nsec = nsec_rem; + return t; +} + +// the precision here will be really atrocious, +// if tapering compresses time significantly +static int faketime_undo_tapered_offset_sec(int t) +{ + long long nsec = ((long long)t) * 1000000000; + nsec = faketime_undo_tapered_offset(nsec); + return nsec / 1000000000; +} + #ifdef FAKE_FILE_TIMESTAMPS #ifdef MACOS_DYLD_INTERPOSE int macos_utime(const char *filename, const struct utimbuf *times) @@ -1180,6 +1328,13 @@ int utime(const char *filename, const struct utimbuf *times) { ntbuf.actime = times->actime - user_offset.tv_sec; ntbuf.modtime = times->modtime - user_offset.tv_sec; + + if (ft_taper) + { + ntbuf.actime = faketime_undo_tapered_offset_sec(times->actime); + ntbuf.modtime = faketime_undo_tapered_offset_sec(times->modtime); + } + times = &ntbuf; } #ifdef MACOS_DYLD_INTERPOSE @@ -1217,6 +1372,11 @@ int utimes(const char *filename, const struct timeval times[2]) user_offset2.tv_usec = user_offset.tv_nsec / 1000; timersub(×[0], &user_offset2, &tn[0]); timersub(×[1], &user_offset2, &tn[1]); + if (ft_taper) + { + tn[0] = faketime_undo_tapered_offset_timeval(times[0]); + tn[1] = faketime_undo_tapered_offset_timeval(times[1]); + } times = tn; } #ifdef MACOS_DYLD_INTERPOSE @@ -1261,6 +1421,11 @@ static void fake_two_timespec(const struct timespec in_times[2], struct timespec else { timersub2(&in_times[j], &user_offset, &out_times[j], n); + if (ft_taper) + { + out_times[j] = faketime_undo_tapered_offset_timespec(in_times[j]); + } + } } } @@ -2897,6 +3062,24 @@ static void ftpl_really_init(void) } } + if ((tmp_env = getenv("FAKETIME_TAPER_BEGIN_NSEC_SINCE_EPOCH")) != NULL) + { + ft_taper_begin_nsec_since_epoch = atoll(tmp_env); + if(!ft_taper) + { + ft_taper = true; + ft_taper_end_nsec_since_epoch = ft_taper_begin_nsec_since_epoch; + } + } + if ((tmp_env = getenv("FAKETIME_TAPER_END_NSEC_SINCE_EPOCH")) != NULL) + { + ft_taper_end_nsec_since_epoch = atoll(tmp_env); + if(!ft_taper) + { + ft_taper = true; + ft_taper_begin_nsec_since_epoch = ft_taper_end_nsec_since_epoch; + } + } if ((tmp_env = getenv("FAKETIME_START_AFTER_SECONDS")) != NULL) { ft_start_after_secs = atol(tmp_env); @@ -3220,7 +3403,6 @@ int fake_clock_gettime(clockid_t clk_id, struct timespec *tp) timespecsub(tp, &ftpl_starttime.real, &tmp_ts); break; } - if (limited_faking) { /* Check whether we actually should be faking the returned timestamp. */ @@ -3373,7 +3555,20 @@ int fake_clock_gettime(clockid_t clk_id, struct timespec *tp) { timeadj = tdiff; } - timespecadd(&user_faked_time_timespec, &timeadj, tp); + + if(ft_taper) + { + // Tapered offset time transition ignores the speed up/slow + // down setting. Someone should figure out how to do speed + // up / slow down correctly in that case (ideally, a global + // speed-up, as a part of a global time mapping routine, not + // relying on a per-process state). + *tp = faketime_do_tapered_offset_timespec(*tp); + } + else + { + timespecadd(&user_faked_time_timespec, &timeadj, tp); + } } break;