From cdc45e15f117a20eadff7c3b6e04f5fab0a41386 Mon Sep 17 00:00:00 2001 From: Benjamin Lamowski Date: Thu, 18 Jul 2024 23:43:24 +0200 Subject: [PATCH] libc: implement kqueue(2) Fixes #5301 --- repos/gems/run/depot_autopilot.run | 3 +- repos/libports/lib/mk/libc.mk | 2 +- repos/libports/lib/symbols/libc | 6 +- .../recipes/pkg/test-libc_kqueue/README | 1 + .../recipes/pkg/test-libc_kqueue/archives | 5 + .../recipes/pkg/test-libc_kqueue/hash | 1 + .../recipes/pkg/test-libc_kqueue/runtime | 24 + .../recipes/src/test-libc_kqueue/content.mk | 2 + .../recipes/src/test-libc_kqueue/hash | 1 + .../recipes/src/test-libc_kqueue/used_apis | 2 + repos/libports/src/lib/libc/internal/init.h | 5 + repos/libports/src/lib/libc/internal/kqueue.h | 40 ++ repos/libports/src/lib/libc/kernel.cc | 1 + repos/libports/src/lib/libc/kqueue.cc | 563 ++++++++++++++++++ repos/libports/src/lib/libc/vfs_plugin.cc | 7 + repos/libports/src/test/libc_kqueue/kqueue.c | 505 ++++++++++++++++ repos/libports/src/test/libc_kqueue/target.mk | 5 + 17 files changed, 1169 insertions(+), 4 deletions(-) create mode 100644 repos/libports/recipes/pkg/test-libc_kqueue/README create mode 100644 repos/libports/recipes/pkg/test-libc_kqueue/archives create mode 100644 repos/libports/recipes/pkg/test-libc_kqueue/hash create mode 100644 repos/libports/recipes/pkg/test-libc_kqueue/runtime create mode 100644 repos/libports/recipes/src/test-libc_kqueue/content.mk create mode 100644 repos/libports/recipes/src/test-libc_kqueue/hash create mode 100644 repos/libports/recipes/src/test-libc_kqueue/used_apis create mode 100644 repos/libports/src/lib/libc/internal/kqueue.h create mode 100644 repos/libports/src/lib/libc/kqueue.cc create mode 100644 repos/libports/src/test/libc_kqueue/kqueue.c create mode 100644 repos/libports/src/test/libc_kqueue/target.mk 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 =