Add chez-epoll: Linux epoll for Chez Scheme + jerboa wrapper

ober

964cd0bc40ebabe6c54e090172960ed32205dfe6

diff --git a/Makefile b/Makefile
index 0d17e9e..03d8a6c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,9 @@
 SCHEME = scheme
 LIBDIRS = lib
 # External chez-* library paths for wrapper modules
-CHEZ_EXT_LIBDIRS = $(HOME)/mine/chez-https/src:$(HOME)/mine/chez-ssl/src:$(HOME)/mine/chez-zlib/src:$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-yaml:$(HOME)/mine/chez-leveldb
+CHEZ_EXT_LIBDIRS = $(HOME)/mine/chez-https/src:$(HOME)/mine/chez-ssl/src:$(HOME)/mine/chez-zlib/src:$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-yaml:$(HOME)/mine/chez-leveldb:$(HOME)/mine/chez-epoll/src
 # Shared object paths for FFI-based chez-* libraries
-CHEZ_EXT_LDPATH = $(HOME)/mine/chez-ssl:$(HOME)/mine/chez-zlib:$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-leveldb
+CHEZ_EXT_LDPATH = $(HOME)/mine/chez-ssl:$(HOME)/mine/chez-zlib:$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-leveldb:$(HOME)/mine/chez-epoll
 
 .PHONY: test test-reader test-core test-runtime test-stdlib test-ffi test-modules test-expanded test-wrappers clean
 
@@ -57,6 +57,9 @@ test-wrappers:
 	@LD_LIBRARY_PATH="$(CHEZ_EXT_LDPATH):$$LD_LIBRARY_PATH" CHEZ_PCRE2_LIB="$(HOME)/mine/chez-pcre2" \
 		$(SCHEME) --libdirs "$(LIBDIRS):$(CHEZ_EXT_LIBDIRS)" --script tests/test-wrapper-pcre2.ss 2>/dev/null \
 		|| echo "  pcre2: SKIP (requires pcre2_shim.so)"
+	@LD_LIBRARY_PATH="$(CHEZ_EXT_LDPATH):$$LD_LIBRARY_PATH" \
+		$(SCHEME) --libdirs "$(LIBDIRS):$(CHEZ_EXT_LIBDIRS)" --script tests/test-wrapper-epoll.ss 2>/dev/null \
+		|| echo "  epoll: SKIP (requires chez_epoll_shim.so)"
 
 test-all: test test-wrappers
 
diff --git a/lib/std/os/epoll.sls b/lib/std/os/epoll.sls
new file mode 100644
index 0000000..0ff87a1
--- /dev/null
+++ b/lib/std/os/epoll.sls
@@ -0,0 +1,18 @@
+#!chezscheme
+;;; :std/os/epoll -- Linux epoll (wraps chez-epoll)
+;;; Requires: chez_epoll_shim.so
+
+(library (std os epoll)
+  (export
+    epoll-create epoll-close
+    epoll-add! epoll-modify! epoll-remove!
+    epoll-wait
+    make-epoll-events free-epoll-events
+    epoll-event-fd epoll-event-events
+    fd-pipe fd-write fd-read fd-close
+    EPOLLIN EPOLLOUT EPOLLERR EPOLLHUP
+    EPOLLET EPOLLONESHOT EPOLLRDHUP EPOLLPRI)
+
+  (import (chez-epoll))
+
+  ) ;; end library
diff --git a/tests/test-wrapper-epoll.ss b/tests/test-wrapper-epoll.ss
new file mode 100644
index 0000000..d491499
--- /dev/null
+++ b/tests/test-wrapper-epoll.ss
@@ -0,0 +1,41 @@
+#!chezscheme
+(import (chezscheme) (std os epoll))
+
+(define pass-count 0)
+(define fail-count 0)
+
+(define-syntax chk
+  (syntax-rules (=>)
+    [(_ expr => expected)
+     (let ([result expr] [exp expected])
+       (if (equal? result exp) (set! pass-count (+ pass-count 1))
+         (begin (set! fail-count (+ fail-count 1))
+                (display "FAIL: ") (write 'expr)
+                (display " => ") (write result)
+                (display " expected ") (write exp) (newline))))]))
+
+;; Constants
+(chk (> EPOLLIN 0) => #t)
+(chk (> EPOLLOUT 0) => #t)
+
+;; Create/close
+(let ([epfd (epoll-create)])
+  (chk (> epfd 0) => #t)
+  (epoll-close epfd))
+
+;; Pipe + epoll
+(let-values ([(rfd wfd) (fd-pipe)])
+  (let ([epfd (epoll-create)]
+        [evts (make-epoll-events 4)])
+    (epoll-add! epfd wfd EPOLLOUT)
+    (let ([n (epoll-wait epfd evts 4 0)])
+      (chk (> n 0) => #t))
+    (free-epoll-events evts)
+    (epoll-close epfd)
+    (fd-close rfd)
+    (fd-close wfd)))
+
+(display "  epoll: ") (display pass-count) (display " passed")
+(when (> fail-count 0) (display ", ") (display fail-count) (display " failed"))
+(newline)
+(when (> fail-count 0) (exit 1))