From 8ff6ad7454d9da1dff91c8b827c54008e83cc150 Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Thu, 17 Jan 2019 11:06:26 +0100 Subject: [PATCH] lib/util: fix timespec normalization When fixing up timespec structs, negative values for the ns part should be taken into account. Also, the range for a valid ns part is [0, 1000000000), not [0, 1000000000]. Signed-off-by: Philipp Gesang Reviewed-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- lib/util/tests/time.c | 53 +++++++++++++++++++++++++++++++++++++++++++ lib/util/time.c | 53 ++++++++++++++++++++++++++++++++++++------- lib/util/time.h | 1 + 3 files changed, 99 insertions(+), 8 deletions(-) diff --git a/lib/util/tests/time.c b/lib/util/tests/time.c index fce0eef5e2e..039f7f4ccf8 100644 --- a/lib/util/tests/time.c +++ b/lib/util/tests/time.c @@ -82,6 +82,57 @@ static bool test_timestring(struct torture_context *tctx) return true; } +static bool test_normalize_timespec(struct torture_context *tctx) +{ + const struct { + time_t in_s; long in_ns; + time_t out_s; long out_ns; + } data [] = { + { 0, 0, 0, 0 } + , { 1, 0, 1, 0 } + , { -1, 0, -1, 0 } + , { 0, 1000000000, 1, 0 } + , { 0, 2000000000, 2, 0 } + , { 0, 1000000001, 1, 1 } + , { 0, 2000000001, 2, 1 } + , { 0, -1000000000, -1, 0 } + , { 0, -2000000000, -2, 0 } + , { 0, -1000000001, -2, 999999999 } + , { 0, -2000000001, -3, 999999999 } + , { 0, -1, -1, 999999999 } + , { 1, -1, 0, 999999999 } + , { -1, -1, -2, 999999999 } + , { 0, 999999999, 0, 999999999 } + , { 0, 1999999999, 1, 999999999 } + , { 0, 2999999999, 2, 999999999 } + , { 0, -999999999, -1, 1 } + , { 0, -1999999999, -2, 1 } + , { 0, -2999999999, -3, 1 } + , { LONG_MAX, 1000000001, LONG_MAX, 999999999 } /* overflow */ + , { LONG_MAX, 999999999, LONG_MAX, 999999999 } /* harmless */ + , { LONG_MAX, -1, LONG_MAX-1, 999999999 } /* -1 */ + , { LONG_MIN, -1000000001, LONG_MIN, 0 } /* overflow */ + , { LONG_MIN, 0, LONG_MIN, 0 } /* harmless */ + , { LONG_MIN, 1000000000, LONG_MIN+1, 0 } /* +1 */ + }; + int i; + + for (i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { + struct timespec ts = (struct timespec) + { .tv_sec = data[i].in_s + , .tv_nsec = data[i].in_ns }; + + normalize_timespec(&ts); + + torture_assert_int_equal(tctx, ts.tv_sec, data[i].out_s, + "mismatch in tv_sec"); + torture_assert_int_equal(tctx, ts.tv_nsec, data[i].out_ns, + "mismatch in tv_nsec"); + } + + return true; +} + struct torture_suite *torture_local_util_time(TALLOC_CTX *mem_ctx) { struct torture_suite *suite = torture_suite_create(mem_ctx, "time"); @@ -92,6 +143,8 @@ struct torture_suite *torture_local_util_time(TALLOC_CTX *mem_ctx) test_http_timestring); torture_suite_add_simple_test(suite, "timestring", test_timestring); + torture_suite_add_simple_test(suite, "normalize_timespec", + test_normalize_timespec); return suite; } diff --git a/lib/util/time.c b/lib/util/time.c index e8b58e87268..53bf194fe0b 100644 --- a/lib/util/time.c +++ b/lib/util/time.c @@ -43,6 +43,7 @@ #endif +#define NSEC_PER_SEC 1000000000 /** External access to time_t_min and time_t_max. @@ -92,10 +93,7 @@ _PUBLIC_ time_t time_mono(time_t *t) time_t convert_timespec_to_time_t(struct timespec ts) { /* Ensure tv_nsec is less than 1sec. */ - while (ts.tv_nsec > 1000000000) { - ts.tv_sec += 1; - ts.tv_nsec -= 1000000000; - } + normalize_timespec(&ts); /* 1 ns == 1,000,000,000 - one thousand millionths of a second. increment if it's greater than 500 millionth of a second. */ @@ -1015,10 +1013,7 @@ void round_timespec_to_usec(struct timespec *ts) { struct timeval tv = convert_timespec_to_timeval(*ts); *ts = convert_timeval_to_timespec(tv); - while (ts->tv_nsec > 1000000000) { - ts->tv_sec += 1; - ts->tv_nsec -= 1000000000; - } + normalize_timespec(ts); } /**************************************************************************** @@ -1462,3 +1457,45 @@ struct timespec get_ctimespec(const struct stat *pst) ret.tv_nsec = get_ctimensec(pst); return ret; } + +/**************************************************************************** + Deal with nanoseconds overflow. +****************************************************************************/ + +void normalize_timespec(struct timespec *ts) +{ + lldiv_t dres; + + /* most likely case: nsec is valid */ + if ((unsigned long)ts->tv_nsec < NSEC_PER_SEC) { + return; + } + + dres = lldiv(ts->tv_nsec, NSEC_PER_SEC); + + /* if the operation would result in overflow, max out values and bail */ + if (dres.quot > 0) { + if ((int64_t)LONG_MAX - dres.quot < ts->tv_sec) { + ts->tv_sec = LONG_MAX; + ts->tv_nsec = NSEC_PER_SEC - 1; + return; + } + } else { + if ((int64_t)LONG_MIN - dres.quot > ts->tv_sec) { + ts->tv_sec = LONG_MIN; + ts->tv_nsec = 0; + return; + } + } + + ts->tv_nsec = dres.rem; + ts->tv_sec += dres.quot; + + /* if the ns part was positive or a multiple of -1000000000, we're done */ + if (ts->tv_nsec > 0 || dres.rem == 0) { + return; + } + + ts->tv_nsec += NSEC_PER_SEC; + --ts->tv_sec; +} diff --git a/lib/util/time.h b/lib/util/time.h index 04945b5f25f..6726f39c7cc 100644 --- a/lib/util/time.h +++ b/lib/util/time.h @@ -360,6 +360,7 @@ void round_timespec_to_sec(struct timespec *ts); void round_timespec_to_usec(struct timespec *ts); void round_timespec_to_nttime(struct timespec *ts); NTTIME unix_timespec_to_nt_time(struct timespec ts); +void normalize_timespec(struct timespec *ts); /* * Functions supporting the full range of time_t and struct timespec values, -- 2.47.3