aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaytan Laats <laytanlaats@hotmail.com>2026-01-11 20:12:28 +0100
committerLaytan Laats <laytanlaats@hotmail.com>2026-01-11 20:21:25 +0100
commit872fe3ff73b4673ac3e393da6a69b98ae5c4dcf2 (patch)
tree8d718a907b639fc6dc5d484fcf33f8b4068e4e0f
parentbc7040209364f06817ceeff4d5effd599f70f5f5 (diff)
kqueue: fix bsds, add user filter, add struct tests
-rw-r--r--core/sys/kqueue/kqueue.odin93
-rw-r--r--tests/core/normal.odin1
-rw-r--r--tests/core/sys/kqueue/structs.odin56
-rw-r--r--tests/core/sys/kqueue/structs/structs.c63
-rw-r--r--tests/core/sys/kqueue/structs/structs.odin58
5 files changed, 244 insertions, 27 deletions
diff --git a/core/sys/kqueue/kqueue.odin b/core/sys/kqueue/kqueue.odin
index 25ee9bdce..b51f3426f 100644
--- a/core/sys/kqueue/kqueue.odin
+++ b/core/sys/kqueue/kqueue.odin
@@ -32,6 +32,7 @@ kevent :: proc(kq: KQ, change_list: []KEvent, event_list: []KEvent, timeout: ^po
timeout,
)
if n_events == -1 {
+ n_events = 0
err = posix.errno()
}
return
@@ -60,6 +61,7 @@ Filter :: enum _Filter_Backing {
Proc = _FILTER_PROC, // Check for changes to the subject process.
Signal = _FILTER_SIGNAL, // Check for signals delivered to the process.
Timer = _FILTER_TIMER, // Timers.
+ User = _FILTER_USER, // User events.
}
RW_Flag :: enum u32 {
@@ -82,18 +84,30 @@ Proc_Flag :: enum u32 {
Exit = log2(0x80000000), // Process exited.
Fork = log2(0x40000000), // Process forked.
Exec = log2(0x20000000), // Process exec'd.
- Signal = log2(0x08000000), // Shared with `Filter.Signal`.
}
Proc_Flags :: bit_set[Proc_Flag; u32]
Timer_Flag :: enum u32 {
Seconds = log2(0x00000001), // Data is seconds.
- USeconds = log2(0x00000002), // Data is microseconds.
- NSeconds = log2(_NOTE_NSECONDS), // Data is nanoseconds.
+ USeconds = log2(_NOTE_USECONDS), // Data is microseconds.
Absolute = log2(_NOTE_ABSOLUTE), // Absolute timeout.
}
Timer_Flags :: bit_set[Timer_Flag; u32]
+User_Flag :: enum u32 {
+ Trigger = log2(0x01000000),
+ FFAnd = log2(0x40000000),
+ FFOr = log2(0x80000000),
+}
+User_Flags :: bit_set[User_Flag; u32]
+
+USER_FLAGS_COPY :: User_Flags{.FFOr, .FFAnd}
+USER_FLAGS_CONTROL_MASK :: transmute(User_Flags)u32(0xc0000000)
+USER_FLAGS_MASK :: transmute(User_Flags)u32(0x00FFFFFF)
+
+// Data is nanoseconds.
+TIMER_FLAGS_NSECONDS :: _TIMER_FLAGS_NSECONDS
+
when ODIN_OS == .Darwin {
_Filter_Backing :: distinct i16
@@ -106,10 +120,14 @@ when ODIN_OS == .Darwin {
_FILTER_PROC :: -5
_FILTER_SIGNAL :: -6
_FILTER_TIMER :: -7
+ _FILTER_USER :: -10
+ _NOTE_USECONDS :: 0x00000002
_NOTE_NSECONDS :: 0x00000004
_NOTE_ABSOLUTE :: 0x00000008
+ _TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))}
+
KEvent :: struct #align(4) {
// Value used to identify this event. The exact interpretation is determined by the attached filter.
ident: uintptr,
@@ -119,11 +137,12 @@ when ODIN_OS == .Darwin {
flags: Flags,
// Filter specific flags.
fflags: struct #raw_union {
- rw: RW_Flags,
- vnode: VNode_Flags,
- fproc: Proc_Flags,
+ rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`,
+ vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
+ fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`,
// vm: VM_Flags,
- timer: Timer_Flags,
+ timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
+ user: User_Flags `raw_union_tag:"filter=.User"`,
},
// Filter specific data.
data: c.long /* intptr_t */,
@@ -143,9 +162,13 @@ when ODIN_OS == .Darwin {
_FILTER_PROC :: -5
_FILTER_SIGNAL :: -6
_FILTER_TIMER :: -7
+ _FILTER_USER :: -11
- _NOTE_NSECONDS :: 0x00000004
- _NOTE_ABSOLUTE :: 0x00000008
+ _NOTE_USECONDS :: 0x00000004
+ _NOTE_NSECONDS :: 0x00000008
+ _NOTE_ABSOLUTE :: 0x00000010
+
+ _TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))}
KEvent :: struct {
// Value used to identify this event. The exact interpretation is determined by the attached filter.
@@ -156,11 +179,12 @@ when ODIN_OS == .Darwin {
flags: Flags,
// Filter specific flags.
fflags: struct #raw_union {
- rw: RW_Flags,
- vnode: VNode_Flags,
- fproc: Proc_Flags,
+ rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`,
+ vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
+ fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`,
// vm: VM_Flags,
- timer: Timer_Flags,
+ timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
+ user: User_Flags `raw_union_tag:"filter=.User"`,
},
// Filter specific data.
data: i64,
@@ -181,11 +205,14 @@ when ODIN_OS == .Darwin {
_FILTER_PROC :: 4
_FILTER_SIGNAL :: 5
_FILTER_TIMER :: 6
+ _FILTER_USER :: 8
- _NOTE_NSECONDS :: 0x00000003
+ _NOTE_USECONDS :: 0x00000002
_NOTE_ABSOLUTE :: 0x00000010
- KEvent :: struct #align(4) {
+ _TIMER_FLAGS_NSECONDS :: Timer_Flags{.Seconds, .USeconds}
+
+ KEvent :: struct {
// Value used to identify this event. The exact interpretation is determined by the attached filter.
ident: uintptr,
// Filter for event.
@@ -194,18 +221,17 @@ when ODIN_OS == .Darwin {
flags: Flags,
// Filter specific flags.
fflags: struct #raw_union {
- rw: RW_Flags,
- vnode: VNode_Flags,
- fproc: Proc_Flags,
+ rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`,
+ vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
+ fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`,
// vm: VM_Flags,
- timer: Timer_Flags,
+ timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
+ user: User_Flags `raw_union_tag:"filter=.User"`,
},
// Filter specific data.
data: i64,
// Opaque user data passed through the kernel unchanged.
udata: rawptr,
- // Extensions.
- ext: [4]u64,
}
} else when ODIN_OS == .OpenBSD {
@@ -219,10 +245,14 @@ when ODIN_OS == .Darwin {
_FILTER_PROC :: -5
_FILTER_SIGNAL :: -6
_FILTER_TIMER :: -7
+ _FILTER_USER :: -10
- _NOTE_NSECONDS :: 0x00000003
+ _NOTE_USECONDS :: 0x00000002
+ _NOTE_NSECONDS :: 0x00000004
_NOTE_ABSOLUTE :: 0x00000010
+ _TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))}
+
KEvent :: struct #align(4) {
// Value used to identify this event. The exact interpretation is determined by the attached filter.
ident: uintptr,
@@ -232,11 +262,12 @@ when ODIN_OS == .Darwin {
flags: Flags,
// Filter specific flags.
fflags: struct #raw_union {
- rw: RW_Flags,
- vnode: VNode_Flags,
- fproc: Proc_Flags,
+ rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`,
+ vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
+ fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`,
// vm: VM_Flags,
- timer: Timer_Flags,
+ timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
+ user: User_Flags `raw_union_tag:"filter=.User"`,
},
// Filter specific data.
data: i64,
@@ -245,12 +276,20 @@ when ODIN_OS == .Darwin {
}
}
+when ODIN_OS == .NetBSD {
+ @(private)
+ LKEVENT :: "__kevent50"
+} else {
+ @(private)
+ LKEVENT :: "kevent"
+}
+
@(private)
log2 :: intrinsics.constant_log2
foreign lib {
@(link_name="kqueue")
_kqueue :: proc() -> KQ ---
- @(link_name="kevent")
+ @(link_name=LKEVENT)
_kevent :: proc(kq: KQ, change_list: [^]KEvent, n_changes: c.int, event_list: [^]KEvent, n_events: c.int, timeout: ^posix.timespec) -> c.int ---
}
diff --git a/tests/core/normal.odin b/tests/core/normal.odin
index e8b61fee8..42da389d2 100644
--- a/tests/core/normal.odin
+++ b/tests/core/normal.odin
@@ -45,6 +45,7 @@ download_assets :: proc "contextless" () {
@(require) import "sync"
@(require) import "sync/chan"
@(require) import "sys/posix"
+@(require) import "sys/kqueue"
@(require) import "sys/windows"
@(require) import "text/i18n"
@(require) import "text/match"
diff --git a/tests/core/sys/kqueue/structs.odin b/tests/core/sys/kqueue/structs.odin
new file mode 100644
index 000000000..edf1fdd1e
--- /dev/null
+++ b/tests/core/sys/kqueue/structs.odin
@@ -0,0 +1,56 @@
+#+build darwin, freebsd, openbsd, netbsd
+package tests_core_sys_kqueue
+
+import "core:strings"
+import "core:testing"
+import os "core:os/os2"
+
+@(test)
+structs :: proc(t: ^testing.T) {
+ {
+ c_compiler := os.get_env("CC", context.temp_allocator)
+ if c_compiler == "" {
+ c_compiler = "clang"
+ }
+
+ c_compilation, c_start_err := os.process_start({
+ command = {c_compiler, #directory + "/structs/structs.c", "-o", #directory + "/structs/c_structs"},
+ stdout = os.stdout,
+ stderr = os.stderr,
+ })
+ testing.expect_value(t, c_start_err, nil)
+
+ o_compilation, o_start_err := os.process_start({
+ command = {ODIN_ROOT + "/odin", "build", #directory + "/structs", "-out:" + #directory + "/structs/odin_structs"},
+ stdout = os.stdout,
+ stderr = os.stderr,
+ })
+ testing.expect_value(t, o_start_err, nil)
+
+ c_status, c_err := os.process_wait(c_compilation)
+ testing.expect_value(t, c_err, nil)
+ testing.expect_value(t, c_status.exit_code, 0)
+
+ o_status, o_err := os.process_wait(o_compilation)
+ testing.expect_value(t, o_err, nil)
+ testing.expect_value(t, o_status.exit_code, 0)
+ }
+
+ c_status, c_stdout, c_stderr, c_err := os.process_exec({command={#directory + "/structs/c_structs"}}, context.temp_allocator)
+ testing.expect_value(t, c_err, nil)
+ testing.expect_value(t, c_status.exit_code, 0)
+ testing.expect_value(t, string(c_stderr), "")
+
+ o_status, o_stdout, o_stderr, o_err := os.process_exec({command={#directory + "/structs/odin_structs"}}, context.temp_allocator)
+ testing.expect_value(t, o_err, nil)
+ testing.expect_value(t, o_status.exit_code, 0)
+ testing.expect_value(t, string(o_stderr), "")
+
+ testing.expect(t, strings.trim_space(string(c_stdout)) != "")
+
+ testing.expect_value(
+ t,
+ strings.trim_space(string(o_stdout)),
+ strings.trim_space(string(c_stdout)),
+ )
+}
diff --git a/tests/core/sys/kqueue/structs/structs.c b/tests/core/sys/kqueue/structs/structs.c
new file mode 100644
index 000000000..e7620c994
--- /dev/null
+++ b/tests/core/sys/kqueue/structs/structs.c
@@ -0,0 +1,63 @@
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/event.h>
+
+int main(int argc, char *argv[])
+{
+ printf("kevent %zu %zu\n", sizeof(struct kevent), _Alignof(struct kevent));
+ printf("kevent.ident %zu\n", offsetof(struct kevent, ident));
+ printf("kevent.filter %zu\n", offsetof(struct kevent, filter));
+ printf("kevent.flags %zu\n", offsetof(struct kevent, flags));
+ printf("kevent.fflags %zu\n", offsetof(struct kevent, fflags));
+ printf("kevent.data %zu\n", offsetof(struct kevent, data));
+ printf("kevent.udata %zu\n", offsetof(struct kevent, udata));
+
+ printf("EV_ADD %d\n", EV_ADD);
+ printf("EV_DELETE %d\n", EV_DELETE);
+ printf("EV_ENABLE %d\n", EV_ENABLE);
+ printf("EV_DISABLE %d\n", EV_DISABLE);
+ printf("EV_ONESHOT %d\n", EV_ONESHOT);
+ printf("EV_CLEAR %d\n", EV_CLEAR);
+ printf("EV_RECEIPT %d\n", EV_RECEIPT);
+ printf("EV_DISPATCH %d\n", EV_DISPATCH);
+ printf("EV_ERROR %d\n", EV_ERROR);
+ printf("EV_EOF %d\n", EV_EOF);
+
+ printf("EVFILT_READ %d\n", EVFILT_READ);
+ printf("EVFILT_WRITE %d\n", EVFILT_WRITE);
+ printf("EVFILT_AIO %d\n", EVFILT_AIO);
+ printf("EVFILT_VNODE %d\n", EVFILT_VNODE);
+ printf("EVFILT_PROC %d\n", EVFILT_PROC);
+ printf("EVFILT_SIGNAL %d\n", EVFILT_SIGNAL);
+ printf("EVFILT_TIMER %d\n", EVFILT_TIMER);
+ printf("EVFILT_USER %d\n", EVFILT_USER);
+
+ printf("NOTE_SECONDS %u\n", NOTE_SECONDS);
+ printf("NOTE_USECONDS %u\n", NOTE_USECONDS);
+ printf("NOTE_NSECONDS %u\n", NOTE_NSECONDS);
+#if defined(NOTE_ABSOLUTE)
+ printf("NOTE_ABSOLUTE %u\n", NOTE_ABSOLUTE);
+#else
+ printf("NOTE_ABSOLUTE %u\n", NOTE_ABSTIME);
+#endif
+
+ printf("NOTE_LOWAT %u\n", NOTE_LOWAT);
+
+ printf("NOTE_DELETE %u\n", NOTE_DELETE);
+ printf("NOTE_WRITE %u\n", NOTE_WRITE);
+ printf("NOTE_EXTEND %u\n", NOTE_EXTEND);
+ printf("NOTE_ATTRIB %u\n", NOTE_ATTRIB);
+ printf("NOTE_LINK %u\n", NOTE_LINK);
+ printf("NOTE_RENAME %u\n", NOTE_RENAME);
+ printf("NOTE_REVOKE %u\n", NOTE_REVOKE);
+
+ printf("NOTE_EXIT %u\n", NOTE_EXIT);
+ printf("NOTE_FORK %u\n", NOTE_FORK);
+ printf("NOTE_EXEC %u\n", NOTE_EXEC);
+
+ printf("NOTE_TRIGGER %u\n", NOTE_TRIGGER);
+ printf("NOTE_FFAND %u\n", NOTE_FFAND);
+ printf("NOTE_FFOR %u\n", NOTE_FFOR);
+ printf("NOTE_FFCOPY %u\n", NOTE_FFCOPY);
+ return 0;
+}
diff --git a/tests/core/sys/kqueue/structs/structs.odin b/tests/core/sys/kqueue/structs/structs.odin
new file mode 100644
index 000000000..4886f63e4
--- /dev/null
+++ b/tests/core/sys/kqueue/structs/structs.odin
@@ -0,0 +1,58 @@
+package main
+
+import "core:fmt"
+import "core:sys/kqueue"
+
+main :: proc() {
+ fmt.println("kevent", size_of(kqueue.KEvent), align_of(kqueue.KEvent))
+ fmt.println("kevent.ident", offset_of(kqueue.KEvent, ident))
+ fmt.println("kevent.filter", offset_of(kqueue.KEvent, filter))
+ fmt.println("kevent.flags", offset_of(kqueue.KEvent, flags))
+ fmt.println("kevent.fflags", offset_of(kqueue.KEvent, fflags))
+ fmt.println("kevent.data", offset_of(kqueue.KEvent, data))
+ fmt.println("kevent.udata", offset_of(kqueue.KEvent, udata))
+
+ fmt.println("EV_ADD", transmute(kqueue._Flags_Backing)kqueue.Flags{.Add})
+ fmt.println("EV_DELETE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Delete})
+ fmt.println("EV_ENABLE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Enable})
+ fmt.println("EV_DISABLE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Disable})
+ fmt.println("EV_ONESHOT", transmute(kqueue._Flags_Backing)kqueue.Flags{.One_Shot})
+ fmt.println("EV_CLEAR", transmute(kqueue._Flags_Backing)kqueue.Flags{.Clear})
+ fmt.println("EV_RECEIPT", transmute(kqueue._Flags_Backing)kqueue.Flags{.Receipt})
+ fmt.println("EV_DISPATCH", transmute(kqueue._Flags_Backing)kqueue.Flags{.Dispatch})
+ fmt.println("EV_ERROR", transmute(kqueue._Flags_Backing)kqueue.Flags{.Error})
+ fmt.println("EV_EOF", transmute(kqueue._Flags_Backing)kqueue.Flags{.EOF})
+
+ fmt.println("EVFILT_READ", int(kqueue.Filter.Read))
+ fmt.println("EVFILT_WRITE", int(kqueue.Filter.Write))
+ fmt.println("EVFILT_AIO", int(kqueue.Filter.AIO))
+ fmt.println("EVFILT_VNODE", int(kqueue.Filter.VNode))
+ fmt.println("EVFILT_PROC", int(kqueue.Filter.Proc))
+ fmt.println("EVFILT_SIGNAL", int(kqueue.Filter.Signal))
+ fmt.println("EVFILT_TIMER", int(kqueue.Filter.Timer))
+ fmt.println("EVFILT_USER", int(kqueue.Filter.User))
+
+ fmt.println("NOTE_SECONDS", transmute(u32)kqueue.Timer_Flags{.Seconds})
+ fmt.println("NOTE_USECONDS", transmute(u32)kqueue.Timer_Flags{.USeconds})
+ fmt.println("NOTE_NSECONDS", transmute(u32)kqueue.TIMER_FLAGS_NSECONDS)
+ fmt.println("NOTE_ABSOLUTE", transmute(u32)kqueue.Timer_Flags{.Absolute})
+
+ fmt.println("NOTE_LOWAT", transmute(u32)kqueue.RW_Flags{.Low_Water_Mark})
+
+ fmt.println("NOTE_DELETE", transmute(u32)kqueue.VNode_Flags{.Delete})
+ fmt.println("NOTE_WRITE", transmute(u32)kqueue.VNode_Flags{.Write})
+ fmt.println("NOTE_EXTEND", transmute(u32)kqueue.VNode_Flags{.Extend})
+ fmt.println("NOTE_ATTRIB", transmute(u32)kqueue.VNode_Flags{.Attrib})
+ fmt.println("NOTE_LINK", transmute(u32)kqueue.VNode_Flags{.Link})
+ fmt.println("NOTE_RENAME", transmute(u32)kqueue.VNode_Flags{.Rename})
+ fmt.println("NOTE_REVOKE", transmute(u32)kqueue.VNode_Flags{.Revoke})
+
+ fmt.println("NOTE_EXIT", transmute(u32)kqueue.Proc_Flags{.Exit})
+ fmt.println("NOTE_FORK", transmute(u32)kqueue.Proc_Flags{.Fork})
+ fmt.println("NOTE_EXEC", transmute(u32)kqueue.Proc_Flags{.Exec})
+
+ fmt.println("NOTE_TRIGGER", transmute(u32)kqueue.User_Flags{.Trigger})
+ fmt.println("NOTE_FFAND", transmute(u32)kqueue.User_Flags{.FFAnd})
+ fmt.println("NOTE_FFOR", transmute(u32)kqueue.User_Flags{.FFOr})
+ fmt.println("NOTE_FFCOPY", transmute(u32)kqueue.USER_FLAGS_COPY)
+}