From 2a91b14cf2eea39b32d17a0ab14dd7243f36b79f Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 10 Sep 2012 16:13:11 +0930 Subject: [PATCH] time: add time_check() call and test. Times can be invalid: non-normalized, or negative. --- ccan/time/test/run-check.c | 199 +++++++++++++++++++++++++++++++++++++ ccan/time/time.c | 73 ++++++++++---- ccan/time/time.h | 21 ++++ 3 files changed, 273 insertions(+), 20 deletions(-) create mode 100644 ccan/time/test/run-check.c diff --git a/ccan/time/test/run-check.c b/ccan/time/test/run-check.c new file mode 100644 index 00000000..37763ab4 --- /dev/null +++ b/ccan/time/test/run-check.c @@ -0,0 +1,199 @@ +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include + +/* If we really abort, we don't get coverage info! */ +void abort(void) +{ + exit(7); +} + +int main(void) +{ + struct timespec t1, t2, t3, zero = { 0, 0 }; + int fds[2]; + + plan_tests(62); + + /* Test time_now */ + t1 = time_now(); + t2 = time_now(); + + /* Test time_sub. */ + t3 = time_sub(t2, t1); + ok1(t3.tv_sec > 0 || t3.tv_nsec >= 0); + t3 = time_sub(t2, t2); + ok1(t3.tv_sec == 0 && t3.tv_nsec == 0); + t3 = time_sub(t1, t1); + ok1(t3.tv_sec == 0 && t3.tv_nsec == 0); + + /* Test time_eq */ + ok1(time_eq(t1, t1)); + ok1(time_eq(t2, t2)); + ok1(!time_eq(t1, t3)); + ok1(!time_eq(t2, t3)); + + /* Make sure t2 > t1. */ + t3.tv_sec = 0; + t3.tv_nsec = 1; + t2 = time_add(t2, t3); + + /* Test time_less and time_greater. */ + ok1(!time_eq(t1, t2)); + ok1(!time_greater(t1, t2)); + ok1(time_less(t1, t2)); + ok1(time_greater(t2, t1)); + ok1(!time_less(t2, t1)); + t3.tv_sec = 0; + t3.tv_nsec = 999999999; + t2 = time_add(t2, t3); + ok1(!time_eq(t1, t2)); + ok1(!time_greater(t1, t2)); + ok1(time_less(t1, t2)); + ok1(time_greater(t2, t1)); + ok1(!time_less(t2, t1)); + + t3 = time_sub(t2, zero); + ok1(time_eq(t3, t2)); + t3 = time_sub(t2, t2); + ok1(time_eq(t3, zero)); + + /* time_from_msec / time_to_msec */ + t3 = time_from_msec(500); + ok1(t3.tv_sec == 0); + ok1(t3.tv_nsec == 500000000); + ok1(time_to_msec(t3) == 500); + + t3 = time_from_msec(1000); + ok1(t3.tv_sec == 1); + ok1(t3.tv_nsec == 0); + ok1(time_to_msec(t3) == 1000); + + t3 = time_from_msec(1500); + ok1(t3.tv_sec == 1); + ok1(t3.tv_nsec == 500000000); + ok1(time_to_msec(t3) == 1500); + + /* time_from_usec */ + t3 = time_from_usec(500000); + ok1(t3.tv_sec == 0); + ok1(t3.tv_nsec == 500000000); + ok1(time_to_usec(t3) == 500000); + + t3 = time_from_usec(1000000); + ok1(t3.tv_sec == 1); + ok1(t3.tv_nsec == 0); + ok1(time_to_usec(t3) == 1000000); + + t3 = time_from_usec(1500000); + ok1(t3.tv_sec == 1); + ok1(t3.tv_nsec == 500000000); + ok1(time_to_usec(t3) == 1500000); + + /* time_from_nsec */ + t3 = time_from_nsec(500000000); + ok1(t3.tv_sec == 0); + ok1(t3.tv_nsec == 500000000); + ok1(time_to_nsec(t3) == 500000000); + + t3 = time_from_nsec(1000000000); + ok1(t3.tv_sec == 1); + ok1(t3.tv_nsec == 0); + ok1(time_to_nsec(t3) == 1000000000); + + t3 = time_from_nsec(1500000000); + ok1(t3.tv_sec == 1); + ok1(t3.tv_nsec == 500000000); + ok1(time_to_nsec(t3) == 1500000000); + + /* Test wrapunder */ + t3 = time_sub(time_sub(t2, time_from_msec(500)), time_from_msec(500)); + ok1(t3.tv_sec == t2.tv_sec - 1); + ok1(t3.tv_nsec == t2.tv_nsec); + + /* time_divide and time_multiply */ + t1.tv_nsec = 100; + t1.tv_sec = 100; + + t3 = time_divide(t1, 2); + ok1(t3.tv_sec == 50); + ok1(t3.tv_nsec == 50); + + t3 = time_divide(t1, 100); + ok1(t3.tv_sec == 1); + ok1(t3.tv_nsec == 1); + + t3 = time_multiply(t3, 100); + ok1(time_eq(t3, t1)); + + t3 = time_divide(t1, 200); + ok1(t3.tv_sec == 0); + ok1(t3.tv_nsec == 500000000); + + /* Divide by huge number. */ + t1.tv_sec = (1U << 31) - 1; + t1.tv_nsec = 999999999; + t2 = time_divide(t1, 1 << 30); + /* Allow us to round either way. */ + ok1((t2.tv_sec == 2 && t2.tv_nsec == 0) + || (t2.tv_sec == 1 && t2.tv_nsec == 999999999)); + + /* Multiply by huge number. */ + t1.tv_sec = 0; + t1.tv_nsec = 1; + t2 = time_multiply(t1, 1UL << 31); + ok1(t2.tv_sec == 2); + ok1(t2.tv_nsec == 147483648); + + pipe(fds); + + fflush(stdout); + switch (fork()) { + case 0: + close(fds[0]); + dup2(fds[1], 1); + dup2(fds[1], 2); + t1.tv_sec = 7; + t1.tv_nsec = 1000000001; + t2 = time_check(t1, NULL); + if (t2.tv_sec != 8 || t2.tv_nsec != 1) + exit(1); + t1.tv_sec = -1; + t1.tv_nsec = 5; + t2 = time_check(t1, NULL); + if (t2.tv_sec != 0 || t2.tv_nsec != 5) + exit(1); + t1.tv_sec = 8; + t1.tv_nsec = 1000000002; + /* We expect this to abort! */ + t2 = time_check(t1, "abortstr"); + exit(1); + + default: { + char readbuf[1024]; + int r, len = 0; + + close(fds[1]); + while ((r = read(fds[0], readbuf + len, 1023 - len)) > 0) + len += r; + readbuf[len] = '\0'; + ok1(strcmp(readbuf, + "WARNING: malformed time" + " 7 seconds 1000000001 ns converted to 8.000000001.\n" + "WARNING: malformed time" + " -1 seconds 5 ns converted to 0.000000005.\n" + "abortstr: malformed time 8.1000000002\n") == 0); + ok1(wait(&r) != -1); + ok1(WIFEXITED(r)); + ok1(WEXITSTATUS(r) == 7); + } + } + + return exit_status(); +} diff --git a/ccan/time/time.c b/ccan/time/time.c index 65a257b3..a6960335 100644 --- a/ccan/time/time.c +++ b/ccan/time/time.c @@ -1,7 +1,14 @@ /* Licensed under BSD-MIT - see LICENSE file for details */ #include #include -#include +#include + +#ifdef DEBUG +#include +#define TIME_CHECK(t) time_check((t), __FILE__ ":" stringify(__LINE__)) +#else +#define TIME_CHECK(t) (t) +#endif #if !HAVE_CLOCK_GETTIME && !HAVE_CLOCK_GETTIME_IN_LIBRT #include @@ -13,7 +20,7 @@ struct timespec time_now(void) gettimeofday(&now, NULL); ret.tv_sec = now.tv_sec; ret.tv_nsec = now.tv_usec * 1000; - return ret; + return TIME_CHECK(ret); } #else #include @@ -21,13 +28,13 @@ struct timespec time_now(void) { struct timespec ret; clock_gettime(CLOCK_REALTIME, &ret); - return ret; + return TIME_CHECK(ret); } #endif /* HAVE_CLOCK_GETTIME || HAVE_CLOCK_GETTIME_IN_LIBRT */ bool time_greater(struct timespec a, struct timespec b) { - if (a.tv_sec > b.tv_sec) + if (TIME_CHECK(a).tv_sec > TIME_CHECK(b).tv_sec) return true; else if (a.tv_sec < b.tv_sec) return false; @@ -37,7 +44,7 @@ bool time_greater(struct timespec a, struct timespec b) bool time_less(struct timespec a, struct timespec b) { - if (a.tv_sec < b.tv_sec) + if (TIME_CHECK(a).tv_sec < TIME_CHECK(b).tv_sec) return true; else if (a.tv_sec > b.tv_sec) return false; @@ -47,35 +54,34 @@ bool time_less(struct timespec a, struct timespec b) bool time_eq(struct timespec a, struct timespec b) { - return a.tv_sec == b.tv_sec && a.tv_nsec == b.tv_nsec; + return TIME_CHECK(a).tv_sec == TIME_CHECK(b).tv_sec && a.tv_nsec == b.tv_nsec; } struct timespec time_sub(struct timespec recent, struct timespec old) { struct timespec diff; - diff.tv_sec = recent.tv_sec - old.tv_sec; + diff.tv_sec = TIME_CHECK(recent).tv_sec - TIME_CHECK(old).tv_sec; if (old.tv_nsec > recent.tv_nsec) { diff.tv_sec--; diff.tv_nsec = 1000000000 + recent.tv_nsec - old.tv_nsec; } else diff.tv_nsec = recent.tv_nsec - old.tv_nsec; - assert(diff.tv_sec >= 0); - return diff; + return TIME_CHECK(diff); } struct timespec time_add(struct timespec a, struct timespec b) { struct timespec sum; - sum.tv_sec = a.tv_sec + b.tv_sec; + sum.tv_sec = TIME_CHECK(a).tv_sec + TIME_CHECK(b).tv_sec; sum.tv_nsec = a.tv_nsec + b.tv_nsec; if (sum.tv_nsec >= 1000000000) { sum.tv_sec++; sum.tv_nsec -= 1000000000; } - return sum; + return TIME_CHECK(sum); } struct timespec time_divide(struct timespec t, unsigned long div) @@ -84,7 +90,7 @@ struct timespec time_divide(struct timespec t, unsigned long div) uint64_t rem, ns; /* Dividing seconds is simple. */ - res.tv_sec = t.tv_sec / div; + res.tv_sec = TIME_CHECK(t).tv_sec / div; rem = t.tv_sec % div; /* If we can't fit remainder * 1,000,000,000 in 64 bits? */ @@ -104,7 +110,7 @@ struct timespec time_divide(struct timespec t, unsigned long div) ns = rem * 1000000000 + t.tv_nsec; res.tv_nsec = ns / div; } - return res; + return TIME_CHECK(res); } struct timespec time_multiply(struct timespec t, unsigned long mult) @@ -124,15 +130,15 @@ struct timespec time_multiply(struct timespec t, unsigned long mult) res.tv_nsec = nsec % 1000000000; res.tv_sec = nsec / 1000000000; } - res.tv_sec += t.tv_sec * mult; - return res; + res.tv_sec += TIME_CHECK(t).tv_sec * mult; + return TIME_CHECK(res); } uint64_t time_to_msec(struct timespec t) { uint64_t msec; - msec = t.tv_nsec / 1000000 + (uint64_t)t.tv_sec * 1000; + msec = TIME_CHECK(t).tv_nsec / 1000000 + (uint64_t)t.tv_sec * 1000; return msec; } @@ -140,7 +146,7 @@ uint64_t time_to_usec(struct timespec t) { uint64_t usec; - usec = t.tv_nsec / 1000 + (uint64_t)t.tv_sec * 1000000; + usec = TIME_CHECK(t).tv_nsec / 1000 + (uint64_t)t.tv_sec * 1000000; return usec; } @@ -148,7 +154,7 @@ uint64_t time_to_nsec(struct timespec t) { uint64_t nsec; - nsec = t.tv_nsec + (uint64_t)t.tv_sec * 1000000000; + nsec = TIME_CHECK(t).tv_nsec + (uint64_t)t.tv_sec * 1000000000; return nsec; } @@ -158,7 +164,7 @@ struct timespec time_from_msec(uint64_t msec) t.tv_nsec = (msec % 1000) * 1000000; t.tv_sec = msec / 1000; - return t; + return TIME_CHECK(t); } struct timespec time_from_usec(uint64_t usec) @@ -167,7 +173,7 @@ struct timespec time_from_usec(uint64_t usec) t.tv_nsec = (usec % 1000000) * 1000; t.tv_sec = usec / 1000000; - return t; + return TIME_CHECK(t); } struct timespec time_from_nsec(uint64_t nsec) @@ -176,5 +182,32 @@ struct timespec time_from_nsec(uint64_t nsec) t.tv_nsec = nsec % 1000000000; t.tv_sec = nsec / 1000000000; + return TIME_CHECK(t); +} + +struct timespec time_check(struct timespec t, const char *abortstr) +{ + if (t.tv_sec < 0 || t.tv_nsec >= 1000000000) { + if (abortstr) { + fprintf(stderr, "%s: malformed time %li.%09li\n", + abortstr, + (long)t.tv_sec, (long)t.tv_nsec); + abort(); + } else { + struct timespec old = t; + + if (t.tv_nsec >= 1000000000) { + t.tv_sec += t.tv_nsec / 1000000000; + t.tv_nsec %= 1000000000; + } + if (t.tv_sec < 0) + t.tv_sec = 0; + + fprintf(stderr, "WARNING: malformed time" + " %li seconds %li ns converted to %li.%09li.\n", + (long)old.tv_sec, (long)old.tv_nsec, + (long)t.tv_sec, (long)t.tv_nsec); + } + } return t; } diff --git a/ccan/time/time.h b/ccan/time/time.h index 6fb622c6..1ef62fef 100644 --- a/ccan/time/time.h +++ b/ccan/time/time.h @@ -249,4 +249,25 @@ static inline struct timespec timeval_to_timespec(struct timeval tv) ts.tv_nsec = tv.tv_usec * 1000; return ts; } + +/** + * time_check - check if a time is malformed. + * @in: the time to check (returned) + * @abortstr: the string to print to stderr before aborting (if set). + * + * This can be used to make sure a time isn't negative and doesn't + * have a tv_nsec >= 1000000000. If it is, and @abortstr is non-NULL, + * that will be printed and abort() is called. Otherwise, if + * @abortstr is NULL then the returned timespec will be normalized and + * tv_sec set to 0 if it was negative. + * + * Note that if ccan/time is compiled with DEBUG, then it will call this + * for all passed and returned times. + * + * Example: + * printf("Now is %lu seconds since epoch\n", + * (long)time_check(time_now(), "time_now() failed?").tv_sec); + */ +struct timespec time_check(struct timespec in, const char *abortstr); + #endif /* CCAN_TIME_H */ -- 2.39.2