/* * timerfdtest.c -- stage 20: timerfd, a timer you read() like a file. * Built with the stock `musl-gcc -static` (a plain ET_EXEC). * * Each expiration adds 1 to a counter; read() returns it as 8 bytes and * resets it. poll/select/epoll say "readable" while it isn't 0. Rustbelt's * timers tick every ~55 ms, so the checks leave room for that. * * Exit code = number of failed checks. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include static int fails; #define CHECK(cond, ...) \ do { \ if (!(cond)) { \ fails++; \ printf("timerfdtest: FAIL "); \ printf(__VA_ARGS__); \ printf("\n"); \ } \ } while (0) static long long now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000LL + ts.tv_nsec / 1000000; } static void ms_sleep(long ms) { struct timespec ts = { ms / 1000, ms % 1000 * 1000000L }; nanosleep(&ts, NULL); } /* Arm `fd`: first expiry after `ms`, then every `every_ms` (0 = once). */ static int arm(int fd, long ms, long every_ms) { struct itimerspec its = { { every_ms / 1000, every_ms % 1000 * 1000000L }, { ms / 1000, ms % 1000 * 1000000L } }; return timerfd_settime(fd, 0, &its, NULL); } static void one_shot(void) { int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); CHECK(fd >= 0, "timerfd_create: errno %d", errno); long long t0 = now_ms(); CHECK(arm(fd, 150, 0) == 0, "settime: errno %d", errno); struct itimerspec cur; CHECK(timerfd_gettime(fd, &cur) == 0 && cur.it_value.tv_nsec + cur.it_value.tv_sec > 0, "gettime while armed"); uint64_t n = 0; CHECK(read(fd, &n, sizeof n) == 8, "blocking read: errno %d", errno); /* waits for it */ long long took = now_ms() - t0; CHECK(n == 1, "one-shot count %llu", (unsigned long long)n); CHECK(took >= 140 && took < 1000, "one-shot fired after %lld ms", took); CHECK(timerfd_gettime(fd, &cur) == 0 && cur.it_value.tv_sec == 0 && cur.it_value.tv_nsec == 0, "disarmed after firing"); close(fd); } static void interval(void) { int fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK); CHECK(fd >= 0, "timerfd_create(BOOTTIME): errno %d", errno); CHECK(arm(fd, 60, 60) == 0, "settime interval"); struct itimerspec cur; timerfd_gettime(fd, &cur); CHECK(cur.it_interval.tv_sec == 0 && cur.it_interval.tv_nsec == 60000000, "gettime interval %ld", cur.it_interval.tv_nsec); ms_sleep(700); /* ~11 periods go by without a read: they add up */ uint64_t n = 0; CHECK(read(fd, &n, sizeof n) == 8, "read interval: errno %d", errno); CHECK(n >= 5 && n <= 14, "interval count %llu after 700 ms of 60 ms periods", (unsigned long long)n); /* Read reset it; the next expirations count from 0 again. */ ms_sleep(250); uint64_t m = 0; CHECK(read(fd, &m, sizeof m) == 8 && m >= 1 && m <= 7, "count after reset %llu", (unsigned long long)m); /* Disarm: no more expirations. */ struct itimerspec off = { 0 }; CHECK(timerfd_settime(fd, 0, &off, &cur) == 0 && cur.it_interval.tv_nsec == 60000000, "disarm returns old"); ms_sleep(150); CHECK(read(fd, &m, sizeof m) == -1 && errno == EAGAIN, "disarmed: EAGAIN"); close(fd); } static void nonblocking_and_errors(void) { int fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC); CHECK(fd >= 0, "timerfd_create(REALTIME)"); uint64_t n; CHECK(read(fd, &n, sizeof n) == -1 && errno == EAGAIN, "disarmed read: EAGAIN (errno %d)", errno); arm(fd, 5000, 0); CHECK(read(fd, &n, sizeof n) == -1 && errno == EAGAIN, "not yet expired: EAGAIN"); char small[4]; CHECK(read(fd, small, sizeof small) == -1 && errno == EINVAL, "4-byte buffer: EINVAL (errno %d)", errno); CHECK(write(fd, &n, sizeof n) == -1 && errno == EINVAL, "write: EINVAL"); /* TFD_TIMER_ABSTIME on CLOCK_REALTIME: a wall-clock time 120 ms ahead. */ struct timespec wall; clock_gettime(CLOCK_REALTIME, &wall); wall.tv_nsec += 120000000; if (wall.tv_nsec >= 1000000000) { wall.tv_sec++; wall.tv_nsec -= 1000000000; } struct itimerspec abs = { { 0, 0 }, wall }; CHECK(timerfd_settime(fd, TFD_TIMER_ABSTIME, &abs, NULL) == 0, "ABSTIME settime"); struct pollfd p = { fd, POLLIN, 0 }; CHECK(poll(&p, 1, 2000) == 1 && (p.revents & POLLIN), "poll: ABSTIME timer fired"); CHECK(read(fd, &n, sizeof n) == 8 && n == 1, "ABSTIME count"); CHECK(poll(&p, 1, 0) == 0, "poll: not readable after read"); /* Unsupported / bad arguments. */ /* CANCEL_ON_SET ("wake me if someone sets the clock"): Linux has it, * Rustbelt says EINVAL. (uname's version is "stage N: ..." on Rustbelt.) */ struct utsname u; int rustbelt = uname(&u) == 0 && strncmp(u.version, "stage", 5) == 0; int r = timerfd_settime(fd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &abs, NULL); CHECK(rustbelt ? r == -1 && errno == EINVAL : r == 0, "CANCEL_ON_SET: r %d errno %d", r, errno); CHECK(timerfd_create(CLOCK_PROCESS_CPUTIME_ID, 0) == -1 && errno == EINVAL, "bad clock: EINVAL"); CHECK(timerfd_create(CLOCK_MONOTONIC, 1) == -1 && errno == EINVAL, "bad flags: EINVAL"); struct itimerspec bad = { { 0, 0 }, { 0, 1000000000 } }; CHECK(timerfd_settime(fd, 0, &bad, NULL) == -1 && errno == EINVAL, "tv_nsec too big: EINVAL"); struct itimerspec cur; CHECK(timerfd_gettime(1, &cur) == -1 && errno == EINVAL, "not a timerfd: EINVAL"); CHECK(timerfd_gettime(999, &cur) == -1 && errno == EBADF, "bad fd: EBADF"); close(fd); } static void readiness(void) { int fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); int ep = epoll_create1(EPOLL_CLOEXEC); struct epoll_event ev = { .events = EPOLLIN, .data.u64 = 0x1234 }; CHECK(epoll_ctl(ep, EPOLL_CTL_ADD, fd, &ev) == 0, "epoll_ctl ADD: errno %d", errno); struct epoll_event out; CHECK(epoll_wait(ep, &out, 1, 0) == 0, "epoll: nothing yet"); long long t0 = now_ms(); arm(fd, 100, 0); int r = epoll_wait(ep, &out, 1, 3000); /* sleeps until the timer wakes it */ long long took = now_ms() - t0; CHECK(r == 1 && out.data.u64 == 0x1234 && (out.events & EPOLLIN), "epoll_wait woke: r %d", r); CHECK(took >= 90 && took < 1500, "epoll woke after %lld ms", took); uint64_t n = 0; CHECK(read(fd, &n, sizeof n) == 8 && n == 1, "count after epoll"); CHECK(epoll_wait(ep, &out, 1, 0) == 0, "epoll: drained"); /* select too. */ arm(fd, 80, 0); fd_set set; FD_ZERO(&set); FD_SET(fd, &set); struct timeval tv = { 2, 0 }; CHECK(select(fd + 1, &set, NULL, NULL, &tv) == 1 && FD_ISSET(fd, &set), "select woke"); read(fd, &n, sizeof n); close(ep); close(fd); } /* Lots of armed timerfds, closed (and some left open: exit closes them). * The kernel test checks no timer or memory is left behind. */ static void many(void) { int fds[40]; for (int i = 0; i < 40; i++) { fds[i] = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); CHECK(fds[i] >= 0, "timerfd %d", i); arm(fds[i], 10 + i, 20); } ms_sleep(120); for (int i = 0; i < 30; i++) close(fds[i]); } int main(void) { one_shot(); interval(); nonblocking_and_errors(); readiness(); many(); printf("timerfdtest: %d failed\n", fails); return fails; }