lib/util: fix timespec normalization
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Thu, 17 Jan 2019 10:06:26 +0000 (11:06 +0100)
committerAndrew Bartlett <abartlet@samba.org>
Fri, 16 Apr 2021 09:38:35 +0000 (09:38 +0000)
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 <philipp.gesang@intra2net.com>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
lib/util/tests/time.c
lib/util/time.c
lib/util/time.h

index fce0eef5e2ed357b40d1244e65d3eeead270aae8..039f7f4ccf894ee35195870f36248c23608d5bb7 100644 (file)
@@ -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;
 }
index e8b58e87268877b7269c5e104840ca2dfa59e701..53bf194fe0b6336776e640d7c5a2404051966a2a 100644 (file)
@@ -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;
+}
index 04945b5f25f9587e6998d26fdbf02513af49b36e..6726f39c7cc3308403540d3894ced987d228af54 100644 (file)
@@ -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,