commit ddf8e23ffc25bb96791e037efa3f4c6fcf8916d4
parent 043ba68601ec7884f745b72b052ab0a1d0030e3d
Author: Vincent Dagonneau <v@vda.io>
Date: Mon, 25 Apr 2022 16:24:33 -0400
linux/timerfd: interface for Linux's timerfd
Diffstat:
5 files changed, 182 insertions(+), 4 deletions(-)
diff --git a/linux/timerfd/README b/linux/timerfd/README
@@ -0,0 +1,4 @@
+This module provides support for Linux's timerfd interface. For details,
+consult the Linux man page timer_create(2).
+
+
diff --git a/linux/timerfd/timerfd.ha b/linux/timerfd/timerfd.ha
@@ -0,0 +1,131 @@
+// License: MPL-2.0
+// (c) 2021 Vincent Dagonneau <v@vda.io>
+
+use errors;
+use rt;
+use time;
+use io;
+use endian;
+
+// The timer will trigger only once, after the set duration and never after.
+export type oneshot = time::duration;
+
+// The timer will trigger once after a configured delay, then periodically at
+// the given interval.
+export type interval_delayed = (time::duration, time::duration);
+
+// The timer will trigger periodically at the given interval.
+export type interval = time::duration;
+
+// The expiration configuration for the timer.
+export type expiration = (oneshot | interval | interval_delayed);
+
+const empty_timerspec: rt::itimerspec = rt::itimerspec {
+ it_interval = rt::timespec { tv_sec = 0, tv_nsec = 0 },
+ it_value = rt::timespec { tv_sec = 0, tv_nsec = 0 },
+};
+
+// Creates a new timerfd. The timer is initially configured without an
+// expiration; see [[set]] to configure it.
+export fn new(
+ clockid: time::clock,
+ flags: int
+) (io::file | errors::error) = {
+ match (rt::timerfd_create(clockid, flags)) {
+ case let fd: int =>
+ return fd;
+ case let err: rt::errno =>
+ return errors::errno(err);
+ };
+};
+
+// Sets the expiration configuration for a timerfd, overwriting any
+// previously set expiration.
+export fn set(
+ t: io::file,
+ exp: expiration,
+ flags: int
+) (void | errors::error) = {
+ let interval_timespec = rt::timespec { ... };
+ const timerspec = match (exp) {
+ case let o: oneshot =>
+ time::duration_to_timespec(o, &interval_timespec);
+ yield rt::itimerspec {
+ it_interval = rt::timespec { tv_sec = 0, tv_nsec = 0 },
+ it_value = interval_timespec,
+ };
+ case let i: interval =>
+ time::duration_to_timespec(i, &interval_timespec);
+ yield rt::itimerspec {
+ it_interval = interval_timespec,
+ it_value = interval_timespec,
+ };
+ case let id: interval_delayed =>
+ let delay_timespec = rt::timespec { ... };
+ time::duration_to_timespec(id.0, &interval_timespec);
+ time::duration_to_timespec(id.1, &delay_timespec);
+ yield rt::itimerspec {
+ it_interval = interval_timespec,
+ it_value = delay_timespec,
+ };
+ };
+
+ match (rt::timerfd_settime(t, flags, &timerspec, null)) {
+ case let ok: int =>
+ return;
+ case let err: rt::errno =>
+ return errors::errno(err);
+ };
+};
+
+// Unsets any expiration that was previously set on the given timer.
+export fn unset(
+ t: io::file,
+) (void | errors::error) = {
+ match (rt::timerfd_settime(t, 0, &empty_timerspec, null)) {
+ case int =>
+ return;
+ case let err: rt::errno =>
+ return errors::errno(err);
+ };
+};
+
+// Reading from the timerfd returns the number of time the timer expired. This
+// call can be blocking or not depending on the flags passed to [[timerfd_create]].
+// Reading from a blocking unset timerfd will block forever.
+export fn read(
+ t: io::file
+) (u64 | errors::error) = {
+ let expirations: [8]u8 = [0...];
+ io::read(t, expirations)!;
+ return endian::host.getu64(expirations);
+};
+
+@test fn timerfd() void = {
+ let blocking_fd = new(time::clock::MONOTONIC, 0)!;
+
+ // one-shot blocking
+ // the first read should block and eventually return 1
+ // subsequent reads will block indefinitely
+ set(blocking_fd, 100: oneshot, 0)!;
+ let one = read(blocking_fd)!;
+ assert(one == 1);
+
+ // interval blocking
+ // the first read should block and eventually return the number of time
+ // the timer expired
+ // subsequent reads should return instantly with the number of time the
+ // timer expired since the last read
+ set(blocking_fd, 100: interval, 0)!;
+ let first = read(blocking_fd)!;
+ let second = read(blocking_fd)!;
+
+ assert(first > 0);
+ assert(second > 0);
+
+ // unset blocking timer
+ // reading here would block us forever
+ unset(blocking_fd)!;
+
+ io::close(blocking_fd);
+};
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -572,7 +572,7 @@ glob() {
else
gen_srcs glob glob.ha +test.ha
fi
- gen_ssa glob fnmatch fs io os sort strings strio
+ gen_ssa glob fnmatch fs io os sort strings strio
}
hare_ast() {
@@ -801,6 +801,12 @@ linux_keyctl() {
gen_ssa -plinux linux::keyctl rt errors strings bytes
}
+linux_timerfd() {
+ gen_srcs -plinux linux::timerfd \
+ timerfd.ha
+ gen_ssa -plinux linux::timerfd errors
+}
+
linux_vdso() {
gen_srcs -plinux linux::vdso \
vdso.ha
@@ -1048,7 +1054,7 @@ gensrcs_strconv() {
numeric.ha \
ftos.ha \
stof.ha \
- stof_data.ha \
+ stof_data.ha \
$*
}
@@ -1261,7 +1267,7 @@ unix_signal() {
unix_tty() {
gen_srcs -plinux unix::tty \
- types.ha \
+ types.ha \
+linux/isatty.ha \
+linux/open.ha \
+linux/termios.ha \
@@ -1269,7 +1275,7 @@ unix_tty() {
gen_ssa -plinux unix::tty rt fs io os
gen_srcs -pfreebsd unix::tty \
- types.ha \
+ types.ha \
+freebsd/isatty.ha \
+freebsd/open.ha \
+freebsd/winsize.ha
@@ -1338,6 +1344,7 @@ hash::fnv
io linux freebsd
linux linux
linux::keyctl linux
+linux::timerfd linux
linux::vdso linux
log linux freebsd
math
diff --git a/stdlib.mk b/stdlib.mk
@@ -450,6 +450,10 @@ stdlib_deps_linux += $(stdlib_linux_linux)
stdlib_linux_keyctl_linux = $(HARECACHE)/linux/keyctl/linux_keyctl-linux.o
stdlib_deps_linux += $(stdlib_linux_keyctl_linux)
+# gen_lib linux::timerfd (linux)
+stdlib_linux_timerfd_linux=$(HARECACHE)/linux/timerfd/linux_timerfd-linux.o
+stdlib_deps_linux+=$(stdlib_linux_timerfd_linux)
+
# gen_lib linux::vdso (linux)
stdlib_linux_vdso_linux = $(HARECACHE)/linux/vdso/linux_vdso-linux.o
stdlib_deps_linux += $(stdlib_linux_vdso_linux)
@@ -1353,6 +1357,16 @@ $(HARECACHE)/linux/keyctl/linux_keyctl-linux.ssa: $(stdlib_linux_keyctl_linux_sr
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nlinux::keyctl \
-t$(HARECACHE)/linux/keyctl/linux_keyctl.td $(stdlib_linux_keyctl_linux_srcs)
+# linux::timerfd (+linux)
+stdlib_linux_timerfd_linux_srcs= \
+ $(STDLIB)/linux/timerfd/timerfd.ha
+
+$(HARECACHE)/linux/timerfd/linux_timerfd-linux.ssa: $(stdlib_linux_timerfd_linux_srcs) $(stdlib_rt) $(stdlib_errors_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/linux/timerfd
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nlinux::timerfd \
+ -t$(HARECACHE)/linux/timerfd/linux_timerfd.td $(stdlib_linux_timerfd_linux_srcs)
+
# linux::vdso (+linux)
stdlib_linux_vdso_linux_srcs = \
$(STDLIB)/linux/vdso/vdso.ha
@@ -2446,6 +2460,10 @@ testlib_deps_linux += $(testlib_linux_linux)
testlib_linux_keyctl_linux = $(TESTCACHE)/linux/keyctl/linux_keyctl-linux.o
testlib_deps_linux += $(testlib_linux_keyctl_linux)
+# gen_lib linux::timerfd (linux)
+testlib_linux_timerfd_linux=$(TESTCACHE)/linux/timerfd/linux_timerfd-linux.o
+testlib_deps_linux+=$(testlib_linux_timerfd_linux)
+
# gen_lib linux::vdso (linux)
testlib_linux_vdso_linux = $(TESTCACHE)/linux/vdso/linux_vdso-linux.o
testlib_deps_linux += $(testlib_linux_vdso_linux)
@@ -3383,6 +3401,16 @@ $(TESTCACHE)/linux/keyctl/linux_keyctl-linux.ssa: $(testlib_linux_keyctl_linux_s
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nlinux::keyctl \
-t$(TESTCACHE)/linux/keyctl/linux_keyctl.td $(testlib_linux_keyctl_linux_srcs)
+# linux::timerfd (+linux)
+testlib_linux_timerfd_linux_srcs= \
+ $(STDLIB)/linux/timerfd/timerfd.ha
+
+$(TESTCACHE)/linux/timerfd/linux_timerfd-linux.ssa: $(testlib_linux_timerfd_linux_srcs) $(testlib_rt) $(testlib_errors_$(PLATFORM))
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/linux/timerfd
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nlinux::timerfd \
+ -t$(TESTCACHE)/linux/timerfd/linux_timerfd.td $(testlib_linux_timerfd_linux_srcs)
+
# linux::vdso (+linux)
testlib_linux_vdso_linux_srcs = \
$(STDLIB)/linux/vdso/vdso.ha
diff --git a/time/+linux/functions.ha b/time/+linux/functions.ha
@@ -70,6 +70,14 @@ export type clock = enum {
// Time since the system was booted. Increases monotonically and, unlike
// [[MONOTONIC]], continues to tick while the system is suspended.
BOOT = 7,
+
+ // This clock is like [[REALTIME]], but will wake the system if it is suspended.
+ REALTIME_ALARM = 8,
+ // This clock is like [[BOOT]], but will wake the system if it is suspended.
+ BOOT_ALARM = 9,
+
+ // A system-wide clock derived from wall-clock time but ignoring leap seconds.
+ TAI = 11,
};
fn cgt_vdso() nullable *fn(_: int, _: *rt::timespec) int = {