diff --git a/repos/gems/run/depot_autopilot.run b/repos/gems/run/depot_autopilot.run
index f97c01e993..c93006ac5f 100644
--- a/repos/gems/run/depot_autopilot.run
+++ b/repos/gems/run/depot_autopilot.run
@@ -672,6 +672,7 @@ set default_test_pkgs {
test-init_loop
test-ldso
test-libc
+ test-libc_alarm
test-libc_connect_lwip
test-libc_connect_lxip
test-libc_connect_vfs_server_lwip
@@ -681,7 +682,7 @@ set default_test_pkgs {
test-libc_fifo_pipe
test-libc_fork
test-libc_getenv
- test-libc_alarm
+ test-libc_kqueue
test-libc_pipe
test-libc_vfs
test-libc_vfs_audit
diff --git a/repos/libports/lib/mk/libc.mk b/repos/libports/lib/mk/libc.mk
index 687b903433..efa6a790a8 100644
--- a/repos/libports/lib/mk/libc.mk
+++ b/repos/libports/lib/mk/libc.mk
@@ -19,7 +19,7 @@ SRC_CC = atexit.cc dummies.cc rlimit.cc sysctl.cc \
vfs_plugin.cc dynamic_linker.cc signal.cc \
socket_operations.cc socket_fs_plugin.cc syscall.cc \
getpwent.cc getrandom.cc fork.cc execve.cc kernel.cc component.cc \
- genode.cc spinlock.cc
+ genode.cc spinlock.cc kqueue.cc
#
# Pthreads
diff --git a/repos/libports/lib/symbols/libc b/repos/libports/lib/symbols/libc
index 445452a50d..0bde0b6ea0 100644
--- a/repos/libports/lib/symbols/libc
+++ b/repos/libports/lib/symbols/libc
@@ -5,6 +5,7 @@
#
# libc
#
+_Exit T
___mb_cur_max D 50
___runetype T
___tolower T
@@ -39,7 +40,6 @@ __stdoutp D 8
__swbuf T
__test_sse T
__xuname T
-_Exit T
_exit T
_getlong T
_getshort T
@@ -412,8 +412,10 @@ iswupper T
iswxdigit T
isxdigit T
jrand48 T
+kevent T
kill W
killpg T
+kqueue T
ksem_init T
l64a T
l64a_r T
@@ -500,8 +502,8 @@ posix_fadvise T
posix_madvise T
posix_memalign T
posix_spawn T
-posix_spawn_file_actions_addclose T
posix_spawn_file_actions_addchdir_np T
+posix_spawn_file_actions_addclose T
posix_spawn_file_actions_adddup2 T
posix_spawn_file_actions_addopen T
posix_spawn_file_actions_destroy T
diff --git a/repos/libports/recipes/pkg/test-libc_kqueue/README b/repos/libports/recipes/pkg/test-libc_kqueue/README
new file mode 100644
index 0000000000..441a3c17f2
--- /dev/null
+++ b/repos/libports/recipes/pkg/test-libc_kqueue/README
@@ -0,0 +1 @@
+Libc kqueue() test.
diff --git a/repos/libports/recipes/pkg/test-libc_kqueue/archives b/repos/libports/recipes/pkg/test-libc_kqueue/archives
new file mode 100644
index 0000000000..ba509bbbea
--- /dev/null
+++ b/repos/libports/recipes/pkg/test-libc_kqueue/archives
@@ -0,0 +1,5 @@
+_/src/init
+_/src/libc
+_/src/posix
+_/src/test-libc_kqueue
+_/src/vfs
diff --git a/repos/libports/recipes/pkg/test-libc_kqueue/hash b/repos/libports/recipes/pkg/test-libc_kqueue/hash
new file mode 100644
index 0000000000..42d8ea8649
--- /dev/null
+++ b/repos/libports/recipes/pkg/test-libc_kqueue/hash
@@ -0,0 +1 @@
+2024-09-06 84548c3bc95921f81f27f1561b2d79475e09558d
diff --git a/repos/libports/recipes/pkg/test-libc_kqueue/runtime b/repos/libports/recipes/pkg/test-libc_kqueue/runtime
new file mode 100644
index 0000000000..c2f00c7caf
--- /dev/null
+++ b/repos/libports/recipes/pkg/test-libc_kqueue/runtime
@@ -0,0 +1,24 @@
+
+
+
+
+
+ --- test succeeded ---
+
+
+
+
+
+
+
+
+
+
+
+ 2019-08-20 15:01
+
+
+
+
+
+
diff --git a/repos/libports/recipes/src/test-libc_kqueue/content.mk b/repos/libports/recipes/src/test-libc_kqueue/content.mk
new file mode 100644
index 0000000000..8fcda74a85
--- /dev/null
+++ b/repos/libports/recipes/src/test-libc_kqueue/content.mk
@@ -0,0 +1,2 @@
+SRC_DIR := src/test/libc_kqueue
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/repos/libports/recipes/src/test-libc_kqueue/hash b/repos/libports/recipes/src/test-libc_kqueue/hash
new file mode 100644
index 0000000000..c3b59468e7
--- /dev/null
+++ b/repos/libports/recipes/src/test-libc_kqueue/hash
@@ -0,0 +1 @@
+2024-09-06 f378e168a5fc152eab217d9d61fc636867d4ca39
diff --git a/repos/libports/recipes/src/test-libc_kqueue/used_apis b/repos/libports/recipes/src/test-libc_kqueue/used_apis
new file mode 100644
index 0000000000..0c483273a8
--- /dev/null
+++ b/repos/libports/recipes/src/test-libc_kqueue/used_apis
@@ -0,0 +1,2 @@
+libc
+posix
diff --git a/repos/libports/src/lib/libc/internal/init.h b/repos/libports/src/lib/libc/internal/init.h
index bd3d880bb9..405472a347 100644
--- a/repos/libports/src/lib/libc/internal/init.h
+++ b/repos/libports/src/lib/libc/internal/init.h
@@ -156,6 +156,11 @@ namespace Libc {
* Atexit handling
*/
void init_atexit(Atexit &);
+
+ /**
+ * Kqueue support
+ */
+ void init_kqueue(Genode::Allocator &, Monitor &);
}
#endif /* _LIBC__INTERNAL__INIT_H_ */
diff --git a/repos/libports/src/lib/libc/internal/kqueue.h b/repos/libports/src/lib/libc/internal/kqueue.h
new file mode 100644
index 0000000000..715cc22dd7
--- /dev/null
+++ b/repos/libports/src/lib/libc/internal/kqueue.h
@@ -0,0 +1,40 @@
+/*
+ * \brief kqueue plugin interface
+ * \author Benjamin Lamowski
+ * \date 2024-08-07
+ */
+
+/*
+ * Copyright (C) 2024 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _LIBC__INTERNAL__KQUEUE_H_
+#define _LIBC__INTERNAL__KQUEUE_H_
+
+/* Libc includes */
+#include
+
+#include
+#include
+
+namespace Libc { class Kqueue_plugin; }
+
+
+class Libc::Kqueue_plugin : public Libc::Plugin
+{
+ private:
+
+ Genode::Allocator & _alloc;
+
+ public:
+
+ Kqueue_plugin(Genode::Allocator & alloc) : _alloc(alloc) { }
+
+ int create_kqueue();
+ int close(File_descriptor *) override;
+};
+
+#endif /* _LIBC__INTERNAL__KQUEUE_H_ */
diff --git a/repos/libports/src/lib/libc/kernel.cc b/repos/libports/src/lib/libc/kernel.cc
index be38ebe987..4b1e236059 100644
--- a/repos/libports/src/lib/libc/kernel.cc
+++ b/repos/libports/src/lib/libc/kernel.cc
@@ -514,6 +514,7 @@ Libc::Kernel::Kernel(Genode::Env &env, Genode::Allocator &heap)
init_socket_fs(*this, *this);
init_passwd(_passwd_config());
init_signal(_signal);
+ init_kqueue(_heap, *this);
_init_file_descriptors();
diff --git a/repos/libports/src/lib/libc/kqueue.cc b/repos/libports/src/lib/libc/kqueue.cc
new file mode 100644
index 0000000000..1b69939aa9
--- /dev/null
+++ b/repos/libports/src/lib/libc/kqueue.cc
@@ -0,0 +1,563 @@
+/*
+ * \brief kqueue/kevent implementation
+ * \author Benjamin Lamowski
+ * \date 2024-06-12
+ */
+
+/*
+ * Copyright (C) 2024 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* Libc includes */
+#include
+#include
+#include
+
+/* internal includes */
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* Genode includes */
+#include
+#include
+#include
+
+using namespace Libc;
+
+namespace Libc {
+ class Kqueue;
+
+ bool read_ready_from_kernel(File_descriptor *);
+ void notify_read_ready_from_kernel(File_descriptor *);
+ bool write_ready_from_kernel(File_descriptor *);
+}
+
+namespace { using Fn = Libc::Monitor::Function_result; }
+
+
+static Monitor *_monitor_ptr;
+static Libc::Kqueue_plugin *_kqueue_plugin_ptr;
+
+
+static Libc::Monitor & monitor()
+{
+ struct Missing_call_of_init_kqueue_support : Genode::Exception { };
+ if (!_monitor_ptr)
+ throw Missing_call_of_init_kqueue_support();
+ return *_monitor_ptr;
+}
+
+
+/*
+ * kqueue(2): "A kevent is identified by the (ident, filter) pair;
+ * there may only be one unique kevent per kqueue.
+ */
+bool operator==(struct kevent a, struct kevent b)
+{
+ return ((a.ident == b.ident) && (a.filter == b.filter));
+}
+
+
+bool operator>(struct kevent a, struct kevent b)
+{
+ if (a.ident > b.ident)
+ return true;
+ if ((a.ident == b.ident) && (a.filter > b.filter))
+ return true;
+
+ return false;
+}
+
+
+static consteval int pos(int flag)
+{
+ for (size_t i = 0; i < sizeof(flag) * 8; i++)
+ if ((1 << i) & flag)
+ return i;
+}
+
+
+/*
+ * Kqueue backend implementation
+ */
+
+struct Libc::Kqueue
+{
+ const unsigned short flags_whitelist =
+ EV_ADD |
+ EV_DELETE |
+ EV_CLEAR |
+ EV_ONESHOT |
+ EV_ENABLE |
+ EV_DISABLE;
+
+ const int filter_whitelist =
+ EVFILT_READ |
+ EVFILT_WRITE;
+
+ struct Kqueue_flags : Genode::Register<32> {
+ struct Add : Bitfield< pos(EV_ADD), 1> { };
+ struct Delete : Bitfield< pos(EV_DELETE), 1> { };
+ struct Enable : Bitfield< pos(EV_ENABLE), 1> { };
+ struct Disable : Bitfield { };
+ struct Clear : Bitfield< pos(EV_CLEAR), 1> { };
+ struct Oneshot : Bitfield { };
+ };
+
+ struct Kqueue_elements;
+
+ struct Kqueue_element : kevent, public Avl_node
+ {
+ Kqueue_element *find_by_kevent(struct kevent const & k)
+ {
+ if (*this == k) return this;
+ Kqueue_element *ele = this->child(k > *this);
+ return ele ? ele->find_by_kevent(k) : nullptr;
+ }
+
+ bool higher(Kqueue_element *e)
+ {
+ return *e > *this;
+ }
+
+ /*
+ * Run fn arcoss the element's subtree until fn returns false.
+ */
+ bool with_all_elements(auto const &fn)
+ {
+ if (!fn(*this))
+ return false;
+
+ if (Kqueue_element * l = child(Avl_node::LEFT))
+ if (!l->with_all_elements(fn))
+ return false;
+
+ if (Kqueue_element * r = child(Avl_node::RIGHT))
+ if (!r->with_all_elements(fn))
+ return false;
+
+ return true;
+ }
+ };
+
+ struct Kqueue_elements : Avl_tree
+ {
+ bool with_any_element(auto const &fn)
+ {
+ Kqueue_element *curr_ptr = first();
+ if (!curr_ptr)
+ return false;
+
+ fn(*curr_ptr);
+ return true;
+ }
+
+ template
+ auto with_element(struct kevent const & k, FN const &match_fn, auto const &no_match_fn)
+ -> typename Trait::Functor::Return_type
+ {
+ Kqueue_element *ele = (this->first()) ?
+ this->first()->find_by_kevent(k) :
+ nullptr;
+ if (ele)
+ return match_fn(*ele);
+ else
+ return no_match_fn();
+ }
+
+ /*
+ * Run fn arcoss the tree until fn returns false.
+ */
+ void with_all_elements(auto const &fn) const
+ {
+ if (first()) first()->with_all_elements(fn);
+ }
+ };
+
+
+ Genode::Allocator &_alloc;
+ Mutex _requests_mutex;
+ Kqueue_elements _requests;
+
+ /*
+ * Collect invalid elements for deletion.
+ * This needs to be done out of band because otherwise the reshuffling of the AVL tree
+ * might lead to missed valid events.
+ */
+ struct Kqueue_element_container : Fifo::Element
+ {
+ Kqueue_element const & ele;
+ Kqueue_element_container(Kqueue_element const & e) : ele(e)
+ { }
+ };
+
+ Fifo _delete_queue;
+
+ void _queue_for_deletion(Kqueue_element &ele)
+ {
+ Kqueue_element_container &c = *new (_alloc) Kqueue_element_container(ele);
+ _delete_queue.enqueue(c);
+ }
+
+ /*
+ * This may be called only when we can safely reshuffle the AVL tree
+ */
+ void _delete_elements()
+ {
+ auto cancel_fn = [&](Kqueue_element_container& c) {
+ /* At this point we are sure that we can reshuffle the AVL tree. */
+ Kqueue_element * non_const_ptr = &(const_cast(c.ele));
+ _requests.remove(non_const_ptr);
+ destroy(_alloc, non_const_ptr);
+ destroy(_alloc, &c);
+ };
+
+ {
+ Mutex::Guard guard(_requests_mutex);
+ _delete_queue.dequeue_all(cancel_fn);
+ }
+ }
+
+ int _add_event(struct kevent const& k)
+ {
+ if (k.filter & ~filter_whitelist) {
+ warning("kqueue: filter not implemented: ", k.filter);
+ return EINVAL;
+ }
+
+ Kqueue_element *ele = new (_alloc) Kqueue_element(k);
+ {
+ Mutex::Guard guard(_requests_mutex);
+ _requests.insert(ele);
+ }
+
+ return 0;
+ }
+
+ int _delete_event(struct kevent const& k)
+ {
+ Mutex::Guard guard(_requests_mutex);
+
+ auto match_fn = [&](Kqueue_element & ele) {
+ /* Since we know we won't match another element, we can safely remove the element here. */
+ _requests.remove(&ele);
+ destroy(_alloc, &ele);
+
+ return 0;
+ };
+
+ auto no_match_fn = [&]() {
+ error("kqueue: did not find kevent to delete: ident: ", k.ident, " filter: ", k.filter);
+ return EINVAL;
+ };
+
+ return _requests.with_element(k, match_fn, no_match_fn);
+ }
+
+ int _enable_event(struct kevent const& k)
+ {
+ auto match_fn = [&](Kqueue_element & ele) {
+ Kqueue_flags::Disable::clear((Kqueue_flags::access_t &)ele.flags);
+ Kqueue_flags::Enable::set((Kqueue_flags::access_t &)ele.flags);
+ return 0;
+ };
+
+ auto no_match_fn = [&]() {
+ error("kqueue: did not find kevent to enable: ident: ", k.ident, " filter: ", k.filter);
+ return EINVAL;
+ };
+
+ return _requests.with_element(k, match_fn, no_match_fn);
+ }
+
+ int _disable_event(struct kevent const& k)
+ {
+ auto match_fn = [&](Kqueue_element & ele) {
+ Kqueue_flags::Enable::clear((Kqueue_flags::access_t &)ele.flags);
+ Kqueue_flags::Disable::set((Kqueue_flags::access_t &)ele.flags);
+ return 0;
+ };
+
+ auto no_match_fn = [&]() {
+ error("kqueue: did not find kevent to disable: ident: ", k.ident, " filter: ", k.filter);
+ return EINVAL;
+ };
+
+ return _requests.with_element(k, match_fn, no_match_fn);
+ }
+
+ Kqueue(Genode::Allocator & alloc) : _alloc(alloc)
+ { }
+
+ ~Kqueue()
+ {
+ auto destroy_fn = [&] (Kqueue_element &e) {
+ _requests.remove(&e);
+ destroy(_alloc, &e); };
+ while (_requests.with_any_element(destroy_fn));
+ }
+
+ int process_events(const struct kevent * changelist, int nchanges,
+ struct kevent * eventlist, int nevents)
+ {
+ int num_errors { 0 };
+
+ for (int i = 0; i < nchanges; i++) {
+ const int flags = changelist[i].flags;
+ int err { 0 };
+
+ if (flags & ~flags_whitelist) {
+ error("kqueue: unsupported flags detected: ", flags & ~flags_whitelist);
+ return Errno(EINVAL);
+ }
+
+ if (Kqueue_flags::Add::get(flags))
+ err = _add_event(changelist[i]);
+ else if (Kqueue_flags::Delete::get(flags))
+ err = _delete_event(changelist[i]);
+ else if (Kqueue_flags::Enable::get(flags))
+ err = _enable_event(changelist[i]);
+ else if (Kqueue_flags::Disable::get(flags))
+ err = _disable_event(changelist[i]);
+ /* We ignore setting EV_CLEAR for now. */
+
+ if (err) {
+ if (num_errors < nevents) {
+ eventlist[num_errors] = changelist[i];
+ eventlist[num_errors].flags = EV_ERROR;
+ eventlist[num_errors].data = err;
+ num_errors++;
+ } else {
+ return Errno(err);
+ }
+ }
+ }
+
+ return num_errors;
+ }
+
+ int collect_completed_events(struct kevent * eventlist, int nevents, const struct timespec *timeout)
+ {
+ if (nevents == 0)
+ return 0;
+
+ /*
+ * event collection mode depending ont 'timeout'
+ *
+ * - timeout pointer == nullptr ... block infinitely for events
+ * - timeout value == 0 ... poll for events and return
+ * immediately
+ * - timeout value != 0 ... block for events but don't return
+ * later than timeout
+ */
+ enum class Mode { INFINITE, POLL, TIMEOUT };
+
+ Mode mode = Mode::INFINITE;
+ uint64_t timeout_ms = 0;
+
+ if (timeout) {
+ timeout_ms = timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000;
+ mode = (timeout_ms == 0) ? Mode::POLL : Mode::TIMEOUT;
+ }
+
+ int num_events { 0 };
+
+ auto monitor_fn = [&] ()
+ {
+ /*
+ * kqueue(2):
+ * "The filter is also run when the user attempts to retrieve the kevent
+ * from the kqueue. If the filter indicates that the condition that triggered
+ * the event no longer holds, the kevent is removed from the kqueue and is
+ * not returned."
+ *
+ * Since we need to check the condition on retrieval anyway, we *only* check
+ * the condition on retrieval and not asynchronously.
+ */
+ auto check_fn = [&](Kqueue_element &ele) {
+ File_descriptor *fd = libc_fd_to_fd(ele.ident, "kevent_collect");
+
+ /*
+ * kqueue(2): "Calling close() on a file descriptor will remove any
+ * kevents that reference the descriptor."
+ *
+ * Instead of removing the kqueue entry from close(), we collect
+ * invalid entries for deletion here.
+ */
+ if (!fd || !fd->plugin || !fd->context) {
+ _queue_for_deletion(ele);
+ return true;
+ }
+
+ /*
+ * If an event is disabled, ignore it.
+ */
+ if (Kqueue_flags::Disable::get(ele.flags))
+ return true;
+
+ /*
+ * Right now we do not support tracking newly available read data via
+ * the clear flag, as that would entail tracking the availability of new
+ * data across file system implementations. For the case that a kqueue
+ * client sets EV_CLEAR and does not read the available data after receiving
+ * a kevent, this will lead to extraneous kevents for the already existing data.
+ */
+ switch (ele.filter) {
+ case EVFILT_READ:
+ if (Libc::read_ready_from_kernel(fd)) {
+ eventlist[num_events] = ele;
+ eventlist[num_events].flags = 0;
+ num_events++;
+ } else {
+ Libc::notify_read_ready_from_kernel(fd);
+ }
+ break;
+ case EVFILT_WRITE:
+ if (Libc::write_ready_from_kernel(fd)) {
+ eventlist[num_events] = ele;
+ eventlist[num_events].flags = 0;
+ num_events++;
+ }
+ break;
+ default:
+ assert(false && "Element with unknown filter inserted");
+ }
+
+ /* Delete oneshot event */
+ if (Kqueue_flags::Oneshot::get(ele.flags))
+ _queue_for_deletion(ele);
+
+ return num_events < nevents;
+ };
+
+ {
+ Mutex::Guard guard(_requests_mutex);
+ _requests.with_all_elements(check_fn);
+
+ }
+
+ _delete_elements();
+
+ if (mode != Mode::POLL && num_events == 0)
+ return Monitor::Function_result::INCOMPLETE;
+
+ return Monitor::Function_result::COMPLETE;
+ };
+
+ Monitor::Result const monitor_result =
+ monitor().monitor(monitor_fn, timeout_ms);
+
+ if (monitor_result == Monitor::Result::TIMEOUT)
+ return 0;
+
+ return num_events;
+ }
+};
+
+
+void Libc::init_kqueue(Genode::Allocator &alloc, Monitor &monitor)
+{
+ _kqueue_plugin_ptr = new (alloc) Kqueue_plugin(alloc);
+ _monitor_ptr = &monitor;
+}
+
+
+static Kqueue_plugin *kqueue_plugin()
+{
+ if (!_kqueue_plugin_ptr) {
+ error("libc kqueue not initialized - aborting");
+ exit(1);
+ }
+
+ return _kqueue_plugin_ptr;
+}
+
+
+
+int Libc::Kqueue_plugin::create_kqueue()
+{
+ Kqueue *kq = new (_alloc) Kqueue(_alloc);
+
+ Plugin_context *context = reinterpret_cast(kq);
+ File_descriptor *fd =
+ file_descriptor_allocator()->alloc(this, context, Libc::ANY_FD);
+
+ return fd->libc_fd;
+}
+
+
+int Libc::Kqueue_plugin::close(File_descriptor *fd)
+{
+ if (fd->plugin != this)
+ return -1;
+
+ if (fd->context)
+ _alloc.free(fd->context, sizeof(Kqueue));
+
+
+ file_descriptor_allocator()->free(fd);
+
+ return 0;
+}
+
+
+extern "C" int
+kevent(int libc_fd, const struct kevent *changelist, int nchanges,
+ struct kevent *eventlist, int nevents, const struct timespec *timeout)
+{
+ File_descriptor *fd = libc_fd_to_fd(libc_fd, "kevent");
+
+ if (fd->plugin != kqueue_plugin()) {
+ error("File descriptor not reqistered to kqueue plugin");
+ return Errno(EBADF);
+ }
+
+ Kqueue *kq = reinterpret_cast(fd->context);
+ assert(kq && "Kqueue not set in kqueue file descriptor");
+
+ if (nchanges < 0 || nevents < 0)
+ return Errno(EINVAL);
+
+ int err { 0 };
+
+ if (changelist && nchanges) {
+ int num_errors = kq->process_events(changelist, nchanges, eventlist, nevents);
+
+ /*
+ * kqueue(2):
+ * If an error occurs while processing an element of the
+ * changelist and there is enough roomin the eventlist, then the
+ * event will be placed in the eventlist with EV_ERROR set in
+ * flags and the system error in data. Otherwise, -1 will be
+ * returned, and errno will be set to indicate the error
+ * condition.
+ */
+ if (num_errors < 0)
+ return -1;
+
+ /* reduce space available in the eventlist */
+ if (num_errors) {
+ nevents -= num_errors;
+ eventlist = &eventlist[num_errors];
+ }
+ }
+
+ if (eventlist && nevents)
+ err = kq->collect_completed_events(eventlist, nevents, timeout);
+
+ return err;
+}
+
+
+extern "C"
+int kqueue(void)
+{
+ return kqueue_plugin()->create_kqueue();
+}
diff --git a/repos/libports/src/lib/libc/vfs_plugin.cc b/repos/libports/src/lib/libc/vfs_plugin.cc
index bdd5d58839..33ea35b6b3 100644
--- a/repos/libports/src/lib/libc/vfs_plugin.cc
+++ b/repos/libports/src/lib/libc/vfs_plugin.cc
@@ -220,6 +220,13 @@ namespace Libc {
return handle->fs().read_ready(*handle);
}
+ void notify_read_ready_from_kernel(File_descriptor *fd)
+ {
+ Vfs::Vfs_handle *handle = vfs_handle(fd);
+ if (handle)
+ handle->fs().notify_read_ready(handle);
+ }
+
bool write_ready_from_kernel(File_descriptor *fd)
{
Vfs::Vfs_handle const *handle = vfs_handle(fd);
diff --git a/repos/libports/src/test/libc_kqueue/kqueue.c b/repos/libports/src/test/libc_kqueue/kqueue.c
new file mode 100644
index 0000000000..d0de8bbf1c
--- /dev/null
+++ b/repos/libports/src/test/libc_kqueue/kqueue.c
@@ -0,0 +1,505 @@
+/*
+ * \brief kqueue test
+ * \author Benjamin Lamowski
+ * \date 2024-08-20
+ */
+
+/*
+ * Copyright (C) 2024 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/*
+ * Note that our kqueue implementation does support EV_CLEAR
+ * for EVFILT_READ and EVFILT_WRITE,
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+/*
+ * Information about the test.
+ */
+struct Test_info {
+ char const *name; /* Name of the test */
+ char const *path; /* Path of the file to open */
+ int open_flags; /* Flags for opening the file */
+ int filter; /* kqueue filter */
+ int flags; /* kqueue flags */
+};
+
+
+/*
+ * Structure to pass around open file descriptors.
+ */
+struct Fildes {
+ int fd;
+ int kq;
+};
+
+
+/*
+ * Set up a kqueue, open the file and insert the initial kevent.
+ */
+int bringup(struct Test_info *test, struct Fildes *fildes)
+{
+ int ret = 0;
+ struct kevent event;
+
+ fildes->kq = kqueue();
+ if (fildes->kq == -1) {
+ printf("%s: Failed to create kqueue: %s\n", test->name,
+ strerror(errno));
+ return ret;
+ }
+
+
+ fildes->fd = open(test->path, test->open_flags);
+
+ if (fildes->fd == -1) {
+ close(fildes->kq);
+ printf("%s: Failed to open file %s: %s\n", test->name,
+ test->path, strerror(errno));
+
+ return ret;
+ }
+
+ EV_SET(&event, fildes->fd, test->filter, test->flags, 0, 0, NULL);
+
+ ret = kevent(fildes->kq, &event, 1, NULL, 0, NULL);
+ if (ret) {
+ close(fildes->fd);
+ close(fildes->kq);
+ printf("%s: Failed to register event: %s\n",
+ test->name, strerror(errno));
+ return ret;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Get a result from the kqueue and close the file descriptors.
+ */
+int get_result(struct Test_info *test, struct Fildes *fildes)
+{
+ int ret = 0;
+ struct kevent result;
+ ret = kevent(fildes->kq, NULL, 0, &result, 1, NULL);
+ if (ret == -1) {
+ close(fildes->fd);
+ close(fildes->kq);
+ printf("%s: Failed to retrieve result: %s\n", test->name,
+ strerror(errno));
+ return ret;
+ }
+
+ close(fildes->fd);
+ close(fildes->kq);
+
+ if (ret > 0) {
+ if (result.flags & EV_ERROR) {
+ printf("%s: ", test->name);
+ printf("%s: Event indcated failure: %s\n", test->name,
+ strerror(result.data));
+ return -1;
+ } else {
+ printf("%s: Test successful.\n", test->name);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Test getting a kevent if a file can be written to.
+ */
+int test_write()
+{
+ int ret = 0;
+ struct Test_info test = {
+ "Write test",
+ "/dev/log",
+ O_WRONLY,
+ EVFILT_WRITE,
+ EV_ADD
+ };
+ struct Fildes fildes;
+ ret = bringup(&test, &fildes);
+ if (ret)
+ return ret;
+
+ return get_result(&test, &fildes);
+}
+
+
+/*
+ * Test getting a kevent if a file can be read.
+ */
+int test_read()
+{
+ int ret = 0;
+ struct Test_info test = {
+ "Read test",
+ "/dev/rtc",
+ O_RDONLY,
+ EVFILT_READ,
+ EV_ADD
+ };
+ struct Fildes fildes;
+ ret = bringup(&test, &fildes);
+ if (ret)
+ return ret;
+
+ return get_result(&test, &fildes);
+}
+
+
+/*
+ * Test deleting a queued kevent.
+ */
+int test_delete()
+{
+ int ret = 0;
+ struct kevent event;
+ struct Test_info test = {
+ "Cancel test",
+ "/dev/rtc",
+ O_RDONLY,
+ EVFILT_READ,
+ EV_ADD
+ };
+ struct Fildes fildes;
+ const struct timespec timeout = {.tv_sec = 0, .tv_nsec = 1000000};
+ ret = bringup(&test, &fildes);
+ if (ret)
+ return ret;
+
+ /* Delete the event */
+ EV_SET(&event, fildes.fd, test.filter, EV_DELETE, 0, 0, NULL);
+ ret = kevent(fildes.kq, &event, 1, NULL, 0, NULL);
+ if (ret) {
+ close(fildes.fd);
+ close(fildes.kq);
+ printf("%s: Failed to delete event: %s\n", test.name,
+ strerror(errno));
+
+ return ret;
+ }
+
+ ret = kevent(fildes.kq, NULL, 0, &event, 1, &timeout);
+ if (ret == -1) {
+ close(fildes.fd);
+ close(fildes.kq);
+ printf("%s: Failed to retrieve result: %s\n", test.name,
+ strerror(errno));
+
+ return ret;
+ }
+
+ /*
+ * Since we can always read /dev/rtc, wetting the results will only
+ * timeout if the event has been deleted sucessfuly.
+ */
+ if (ret == 0) {
+ printf("%s: Test successful.\n", test.name);
+ return 0;
+ } else {
+ printf("%s: Event was not deleted: %s\n", test.name,
+ strerror(errno));
+
+ return -1;
+ }
+}
+
+
+/*
+ * Test repeating a non-oneshot event.
+ */
+int test_repeat()
+{
+ int ret = 0;
+ struct Test_info test = {
+ "Repeat test",
+ "/dev/rtc",
+ O_RDONLY,
+ EVFILT_READ,
+ EV_ADD
+ };
+ struct Fildes fildes;
+ struct kevent event1, event2;
+
+ ret = bringup(&test, &fildes);
+ if (ret)
+ return ret;
+
+ ret = kevent(fildes.kq, NULL, 0, &event1, 1, NULL);
+ if (ret == -1) {
+ close(fildes.fd);
+ close(fildes.kq);
+ printf("%s: Failed to retrieve result 1: %s\n", test.name,
+ strerror(errno));
+
+ return ret;
+ }
+
+ ret = kevent(fildes.kq, NULL, 0, &event2, 1, NULL);
+
+ close(fildes.fd);
+ close(fildes.kq);
+
+ switch (ret) {
+ case 1:
+ printf("%s: Test successful.\n", test.name);
+ return 0;
+ case -1:
+ printf("%s: Failed to retrieve result 2: %s\n",
+ test.name, strerror(errno));
+ break;
+ default:
+ printf("%s: Non-oneshot event was not repeated: %s\n",
+ test.name, strerror(errno));
+ return -1;
+ }
+
+ return ret;
+}
+
+
+/*
+ * Test queuing a oneshot event.
+ */
+int test_oneshot()
+{
+ int ret = 0;
+ struct Test_info test = {
+ "Oneshot test",
+ "/dev/rtc",
+ O_RDONLY,
+ EVFILT_READ,
+ EV_ADD | EV_ONESHOT
+ };
+ struct Fildes fildes;
+ const struct timespec timeout = {.tv_sec = 0, .tv_nsec = 1000000};
+ struct kevent event1, event2;
+
+ ret = bringup(&test, &fildes);
+ if (ret)
+ return ret;
+
+ ret = kevent(fildes.kq, NULL, 0, &event1, 1, NULL);
+ if (ret == -1) {
+ close(fildes.fd);
+ close(fildes.kq);
+ printf("%s: Failed to retrieve result 1: %s\n", test.name,
+ strerror(errno));
+ return ret;
+ }
+
+ ret = kevent(fildes.kq, NULL, 0, &event2, 1, &timeout);
+ if (ret == -1) {
+ close(fildes.fd);
+ close(fildes.kq);
+ printf("%s: Failed to retrieve result 2: %s\n", test.name,
+ strerror(errno));
+ return ret;
+ }
+
+ if (ret == 0) {
+ printf("%s: Test successful.\n", test.name);
+ return 0;
+ } else {
+ printf("%s: Oneshot event was repeated: %s\n", test.name,
+ strerror(errno));
+
+ return -1;
+ }
+}
+
+
+/*
+ * Test disabling a queued event.
+ */
+int test_disable()
+{
+ int ret = 0;
+ struct Test_info test = {
+ "Disable test",
+ "/dev/rtc",
+ O_RDONLY,
+ EVFILT_READ,
+ EV_ADD
+ };
+ struct Fildes fildes;
+ const struct timespec timeout = {.tv_sec = 0, .tv_nsec = 1000000};
+ struct kevent event;
+
+ ret = bringup(&test, &fildes);
+ if (ret)
+ return ret;
+
+ /* Disable the event */
+ EV_SET(&event, fildes.fd, test.filter, EV_DISABLE, 0, 0, NULL);
+ ret = kevent(fildes.kq, &event, 1, NULL, 0, NULL);
+ if (ret) {
+ close(fildes.fd);
+ close(fildes.kq);
+ printf("%s: Failed to disable event: %s\n", test.name,
+ strerror(errno));
+ return ret;
+ }
+
+ ret = kevent(fildes.kq, NULL, 0, &event, 1, &timeout);
+
+ close(fildes.fd);
+ close(fildes.kq);
+
+ switch (ret) {
+ case 0:
+ printf("%s: Test successful.\n", test.name);
+ return 0;
+ case -1:
+ printf("%s: Failed to retrieve result: %s\n", test.name,
+ strerror(errno));
+ return -1;
+ default:
+ printf("%s: Event was not disabled: %s\n", test.name,
+ strerror(errno));
+ return -1;
+ }
+}
+
+
+/*
+ * Test (re-)enabling a disabled event.
+ */
+int test_enable()
+{
+ int ret = 0;
+ struct Test_info test = {
+ "Enable test",
+ "/dev/rtc",
+ O_RDONLY,
+ EVFILT_READ,
+ EV_ADD
+ };
+ struct Fildes fildes;
+ const struct timespec timeout = {.tv_sec = 0, .tv_nsec = 1000000};
+ struct kevent event;
+
+ ret = bringup(&test, &fildes);
+ if (ret)
+ return ret;
+
+ /* Disable the event */
+ EV_SET(&event, fildes.fd, test.filter, EV_DISABLE, 0, 0, NULL);
+ ret = kevent(fildes.kq, &event, 1, NULL, 0, NULL);
+ if (ret) {
+ close(fildes.fd);
+ close(fildes.kq);
+ printf("%s: Failed to disable event: %s\n", test.name,
+ strerror(errno));
+
+ return ret;
+ }
+
+ ret = kevent(fildes.kq, NULL, 0, &event, 1, &timeout);
+ if (ret == -1) {
+ close(fildes.fd);
+ close(fildes.kq);
+ printf("%s: Failed to retrieve result 2: %s\n", test.name,
+ strerror(errno));
+ return ret;
+ }
+
+ if (ret != 0) {
+ printf("%s: Event was not disabled: %s\n", test.name,
+ strerror(errno));
+ return -1;
+ }
+
+ /* (Re-)Enable the event */
+ EV_SET(&event, fildes.fd, test.filter, EV_ENABLE, 0, 0, NULL);
+ ret = kevent(fildes.kq, &event, 1, NULL, 0, NULL);
+ if (ret) {
+ close(fildes.fd);
+ close(fildes.kq);
+ printf("%s: Failed to enable event: %s\n", test.name,
+ strerror(errno));
+ return ret;
+ }
+
+ return get_result(&test, &fildes);
+}
+
+
+/*
+ * Test queuing a disabled event.
+ */
+int test_queue_disabled()
+{
+ int ret = 0;
+ struct Test_info test = {
+ "Queue disabled test",
+ "/dev/rtc",
+ O_RDONLY,
+ EVFILT_READ,
+ EV_ADD | EV_DISABLE
+ };
+ struct Fildes fildes;
+ const struct timespec timeout = {.tv_sec = 0, .tv_nsec = 1000000};
+ struct kevent event;
+
+ ret = bringup(&test, &fildes);
+ if (ret)
+ return ret;
+
+ ret = kevent(fildes.kq, NULL, 0, &event, 1, &timeout);
+
+ close(fildes.fd);
+ close(fildes.kq);
+
+ switch (ret) {
+ case 0:
+ printf("%s: Test successful.\n", test.name);
+ return 0;
+ case -1:
+ printf("%s: Failed to retrieve result: %s\n", test.name,
+ strerror(errno));
+ return -1;
+ default:
+ printf("%s: Event was not disabled: %s\n", test.name,
+ strerror(errno));
+ return -1;
+ }
+}
+
+
+int main(int argc, char **argv)
+{
+ int retval = 0;
+
+ retval += test_write();
+ retval += test_read();
+ retval += test_delete();
+ retval += test_repeat();
+ retval += test_oneshot();
+ retval += test_disable();
+ retval += test_enable();
+ retval += test_queue_disabled();
+
+ if (!retval)
+ printf("--- test succeeded ---\n");
+
+ return retval;
+}
diff --git a/repos/libports/src/test/libc_kqueue/target.mk b/repos/libports/src/test/libc_kqueue/target.mk
new file mode 100644
index 0000000000..100980f875
--- /dev/null
+++ b/repos/libports/src/test/libc_kqueue/target.mk
@@ -0,0 +1,5 @@
+TARGET = test-libc_kqueue
+SRC_C = kqueue.c
+LIBS = posix
+
+CC_CXX_WARN_STRICT =