From e78fbb7a8b5bebf518faced27372aa27f692395f Mon Sep 17 00:00:00 2001
From: Drew DeVault <sir@cmpwn.com>
Date: Wed, 30 Nov 2022 15:28:36 +0100
Subject: [PATCH] ev: implement blocking I/O fallback

---
 ev/+linux/README  |  6 ++++++
 ev/+linux/file.ha | 24 +++++++++++++++++++-----
 ev/+linux/io.ha   | 12 ++++++++++++

diff --git a/ev/+linux/README b/ev/+linux/README
new file mode 100644
index 0000000000000000000000000000000000000000..8b6ec3d48ff3a3e523ab05e844d20ee9d4e06a55
--- /dev/null
+++ b/ev/+linux/README
@@ -0,0 +1,6 @@
+The ev module provides an event loop. Use [[newloop]] to create an event loop,
+and [[finish]] to finalize it. You may add external I/O sources via functions
+like [[register]] and [[unregister]].
+
+On Linux, ev is implemented with epoll. Note that, on Linux, I/O on regular
+files is always blocking.
diff --git a/ev/+linux/file.ha b/ev/+linux/file.ha
index 5337e503b0c498f878fcf8f33a9e7b727d27df31..95afaf8cee545ded04dcbf885ae80c09dea6dc76 100644
--- a/ev/+linux/file.ha
+++ b/ev/+linux/file.ha
@@ -8,10 +8,15 @@ 	READV,
 	WRITEV,
 };
 
+export type fflags = enum uint {
+	BLOCKING = 1 << 31,
+};
+
 export type file = struct {
 	fd: io::file,
 	ev: *loop,
-	// Pending operation on this file object
+
+	flags: fflags,
 	op: op,
 	cb: nullable *void,
 	user: nullable *void,
@@ -44,6 +49,12 @@ 	match (rt::epoll_ctl(loop.fd, rt::EPOLL_CTL_ADD, fd, &ev)) {
 	case void =>
 		yield;
 	case let err: rt::errno =>
+		if (err: int == rt::EPERM) {
+			// epoll(2) does not support regular files, use blocking
+			// I/O instead
+			file.flags = fflags::BLOCKING;
+			return file;
+		};
 		return errors::errno(err);
 	};
 
@@ -54,10 +65,13 @@ // Unregisters a file object with an event loop and frees resources associated
 // with it. Does not close the underlying file descriptor.
 export fn unregister(file: *file) void = {
 	const loop = file.ev;
-	// The only way that this could fail is in the event of a use-after-free
-	// or if the user fucks around and constructs a custom [[file]] which
-	// was never registered, so assert on error.
-	rt::epoll_ctl(loop.fd, rt::EPOLL_CTL_DEL, file.fd, null)!;
+	if (file.flags & fflags::BLOCKING == 0) {
+		// The only way that this could fail is in the event of a
+		// use-after-free or if the user fucks around and constructs a
+		// custom [[file]] which was never registered, so assert on
+		// error.
+		rt::epoll_ctl(loop.fd, rt::EPOLL_CTL_DEL, file.fd, null)!;
+	};
 	free(file);
 };
 
diff --git a/ev/+linux/io.ha b/ev/+linux/io.ha
index 9572c300043d7c99c8608418c12975459d1cc18f..5b543fde7eafd25e260711d14176a6abc08ec42d 100644
--- a/ev/+linux/io.ha
+++ b/ev/+linux/io.ha
@@ -23,6 +23,12 @@ 	cb: *readcb,
 	vec: io::vector...
 ) req = {
 	assert(file.op == op::NONE);
+	if (file.flags & fflags::BLOCKING != 0) {
+		const r = io::readv(file.fd, vec...);
+		cb(file, r);
+		return req { ... };
+	};
+
 	file.op = op::READV;
 	file.cb = cb;
 	file.vec = vec;
@@ -69,6 +75,12 @@ ) req = {
 	// XXX: Should we support both pending reads and writes at the same
 	// time? (yes)
 	assert(file.op == op::NONE);
+	if (file.flags & fflags::BLOCKING != 0) {
+		const r = io::writev(file.fd, vec...);
+		cb(file, r);
+		return req { ... };
+	};
+
 	file.op = op::WRITEV;
 	file.cb = cb;
 	file.vec = vec;

-- 
2.48.1