// calogTime.c -- calog time library (see calogTime.h). Three stateless natives over the // POSIX clocks: a wall-clock read, a monotonic read, and a millisecond sleep. No shared // state and no timers/threads -- each native is a pure function of its arguments (or a // plain block of the calling context thread). #define _POSIX_C_SOURCE 200809L #include "calogTime.h" #include #include // Conversion factors shared by timeSleepNative and timeToSeconds. #define TIME_NS_PER_MS 1000000.0 #define TIME_NS_PER_SEC 1000000000 static int32_t timeMonotonicNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t timeNowNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t timeSleepNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static double timeToSeconds(const struct timespec *ts); int32_t calogTimeRegister(CalogT *calog) { calogRegisterInline(calog, "timeNow", timeNowNative, NULL); calogRegisterInline(calog, "timeMonotonic", timeMonotonicNative, NULL); calogRegisterInline(calog, "timeSleep", timeSleepNative, NULL); return calogOkE; } static int32_t timeMonotonicNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { struct timespec ts; (void)args; (void)userData; calogValueNil(result); if (argCount != 0) { return calogFail(result, calogErrArgE, "timeMonotonic expects ()"); } if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { return calogFail(result, calogErrUnsupportedE, "timeMonotonic: clock_gettime failed"); } calogValueReal(result, timeToSeconds(&ts)); return calogOkE; } static int32_t timeNowNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { struct timespec ts; (void)args; (void)userData; calogValueNil(result); if (argCount != 0) { return calogFail(result, calogErrArgE, "timeNow expects ()"); } if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { return calogFail(result, calogErrUnsupportedE, "timeNow: clock_gettime failed"); } calogValueReal(result, timeToSeconds(&ts)); return calogOkE; } static int32_t timeSleepNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { struct timespec request; struct timespec remaining; double ms; int64_t nanos; (void)userData; calogValueNil(result); if (argCount != 1 || (args[0].type != calogIntE && args[0].type != calogRealE)) { return calogFail(result, calogErrArgE, "timeSleep expects (milliseconds)"); } ms = (args[0].type == calogIntE) ? (double)args[0].as.i : args[0].as.r; if (!(ms >= 0.0)) { return calogFail(result, calogErrRangeE, "timeSleep: milliseconds must be non-negative"); } // Reject ms large enough that ms * TIME_NS_PER_MS would overflow int64_t -- // the cast below is UB otherwise (C11 6.3.1.4p1). if (ms > (double)INT64_MAX / TIME_NS_PER_MS) { return calogFail(result, calogErrRangeE, "timeSleep: milliseconds out of range"); } nanos = (int64_t)(ms * TIME_NS_PER_MS); request.tv_sec = (time_t)(nanos / TIME_NS_PER_SEC); request.tv_nsec = (long)(nanos % TIME_NS_PER_SEC); while (nanosleep(&request, &remaining) != 0) { if (errno != EINTR) { return calogFail(result, calogErrUnsupportedE, "timeSleep: nanosleep failed"); } request = remaining; } return calogOkE; } static double timeToSeconds(const struct timespec *ts) { return (double)ts->tv_sec + (double)ts->tv_nsec / TIME_NS_PER_SEC; }