# HG changeset patch # User Paul Boddie # Date 1679347717 -3600 # Node ID 79bb98c3403be1235ee03770b97b49f1ae19613c # Parent 89e3e3e0864bbaf278381137d7f66023e93e640b Moved notifier support into its own library. diff -r 89e3e3e0864b -r 79bb98c3403b libfsclient/Control --- a/libfsclient/Control Mon Mar 20 14:52:03 2023 +0100 +++ b/libfsclient/Control Mon Mar 20 22:28:37 2023 +0100 @@ -1,3 +1,3 @@ -requires: libstdc++ libc libipc libmem +requires: libstdc++ libc libipc libmem libnotifier provides: libfsclient maintainer: paul@boddie.org.uk diff -r 89e3e3e0864b -r 79bb98c3403b libfsclient/include/fsclient/notifier.h --- a/libfsclient/include/fsclient/notifier.h Mon Mar 20 14:52:03 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +0,0 @@ -/* - * Generic object event notification support. - * - * Copyright (C) 2021, 2022, 2023 Paul Boddie - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA - */ - -#pragma once - -#include -#include -#include -#include - -#include -#include - - - -/* Object-specific notification details. */ - -class ObjectNotificationState -{ -public: - /* Synchronisation primitives for state access and notification. */ - - std::mutex lock; - std::condition_variable condition; - - /* Pending notifications for monitored objects. */ - - notify_flags_t pending_notifications = 0; - notify_values_t pending_values = NOTIFY_VALUES_NULL; - - /* Endpoints associated with monitored objects. */ - - l4_cap_idx_t endpoint = L4_INVALID_CAP; - - bool is_null() { return l4_is_invalid_cap(endpoint); } -}; - - - -/* Collection types. */ - -typedef std::map ObjectNotificationStates; -typedef std::map ObjectStateLocks; - - - -/* An object for monitoring object event notifications. */ - -class ObjectNotifier -{ -protected: - /* General state access locking. */ - - std::mutex _state_lock; - - /* Object-specific state locking. */ - - ObjectStateLocks _object_locks; - - /* Notification state. */ - - ObjectNotificationStates _state; - - /* Notifier thread details. */ - - l4_cap_idx_t _thread = L4_INVALID_CAP; - bool _started = false; - - /* Convenience method to access object state. */ - - virtual ObjectNotificationState &object_state(notifiable_t *object, bool create); - - /* Helper methods. */ - - virtual void _notify(notifiable_t *object, notify_flags_t flags, - notify_values_t values) = 0; - - virtual bool _transfer(ObjectNotificationState &state, notifiable_t *object); - -public: - virtual ~ObjectNotifier(); - - /* Local operations. */ - - virtual long start(); - - virtual long subscribe(notifiable_t *object, notify_flags_t flags); - - virtual long unsubscribe(notifiable_t *object); - - virtual long get_endpoint(notifiable_t *object, l4_cap_idx_t *endpoint, bool create); - - virtual long remove_endpoint(notifiable_t *object, l4_cap_idx_t endpoint); - - /* Event handling support. */ - - virtual void mainloop(); -}; - - - -/* An object monitoring notifications for a collection of different objects. */ - -class GeneralObjectNotifier : public ObjectNotifier -{ -protected: - /* Locking to protect pending notification members and to coordinate access - to notifications. */ - - std::mutex _general_lock; - - /* General lock synchronisation. */ - - std::condition_variable _general_condition; - - /* Objects affected by notifications. */ - - std::list _affected; - - /* Helper methods. */ - - virtual void _notify(notifiable_t *object, notify_flags_t flags, - notify_values_t values); - - virtual bool _retrieve(notifiable_t **object); - - virtual bool _retrieve_for_object(notifiable_t *object); - -public: - virtual long wait(notifiable_t **object); -}; - - - -/* An object monitoring notifications for specific objects. */ - -class SpecificObjectNotifier : public ObjectNotifier -{ -protected: - /* Helper methods. */ - - virtual void _notify(notifiable_t *object, notify_flags_t flags, - notify_values_t values); - -public: - virtual long wait_object(notifiable_t *object); -}; - - - -/* Helper functions. */ - -SpecificObjectNotifier *notifier_get_task_notifier(); - -GeneralObjectNotifier *notifier_get_local_notifier(); - -// vim: tabstop=4 expandtab shiftwidth=4 diff -r 89e3e3e0864b -r 79bb98c3403b libfsclient/lib/src/Makefile --- a/libfsclient/lib/src/Makefile Mon Mar 20 14:52:03 2023 +0100 +++ b/libfsclient/lib/src/Makefile Mon Mar 20 22:28:37 2023 +0100 @@ -16,14 +16,14 @@ # Individual interfaces. CLIENT_INTERFACES_CC = dataspace directory file filesystem flush \ - mapped_file notifier notification opener \ - opener_context pipe pipe_opener process_creator_context + mapped_file opener opener_context \ + pipe pipe_opener process_creator_context # Generated and plain source files. CLIENT_INTERFACES_SRC_CC = $(call interfaces_to_client_cc,$(CLIENT_INTERFACES_CC)) -PLAIN_SRC_CC = client.cc file.cc notifier.cc process.cc +PLAIN_SRC_CC = client.cc file.cc process.cc # Normal definitions. @@ -31,7 +31,7 @@ $(CLIENT_INTERFACES_SRC_CC) \ $(PLAIN_SRC_CC) -REQUIRES_LIBS = l4re_c-util libipc libstdc++ libsystypes libmem +REQUIRES_LIBS = l4re_c-util libipc libstdc++ libsystypes libmem libnotifier PRIVATE_INCDIR = $(PKGDIR)/include/fsclient $(IDL_BUILD_DIR) $(IDL_EXPORT_DIR) CONTRIB_INCDIR = libfsclient diff -r 89e3e3e0864b -r 79bb98c3403b libfsclient/lib/src/file.cc --- a/libfsclient/lib/src/file.cc Mon Mar 20 14:52:03 2023 +0100 +++ b/libfsclient/lib/src/file.cc Mon Mar 20 22:28:37 2023 +0100 @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -39,7 +40,6 @@ #include "mapped_file_client.h" #include "file.h" -#include "notifier.h" diff -r 89e3e3e0864b -r 79bb98c3403b libfsclient/lib/src/notifier.cc --- a/libfsclient/lib/src/notifier.cc Mon Mar 20 14:52:03 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,458 +0,0 @@ -/* - * Generic object event notification support. - * - * Copyright (C) 2021, 2022, 2023 Paul Boddie - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA - */ - -#include -#include - -#include -#include - -#include -#include - -#include "notification_client.h" -#include "notifier.h" -#include "notifier_interface.h" - - - -/* Null notification state. */ - -static ObjectNotificationState _null_state; - - - -/* Lock protecting per-task notifier access. */ - -static std::mutex _lock; - -/* Per-task storage for specific waiting operations. */ - -static SpecificObjectNotifier *_notifier = NULL; - - - -/* Return the per-task notifier for object-specific waiting operations. */ - -SpecificObjectNotifier *notifier_get_task_notifier() -{ - std::lock_guard guard(_lock); - - /* Start any new notifier. */ - - if (_notifier == NULL) - { - _notifier = new SpecificObjectNotifier; - _notifier->start(); - } - - return _notifier; -} - -/* Return a local notifier for general object waiting operations. */ - -GeneralObjectNotifier *notifier_get_local_notifier() -{ - GeneralObjectNotifier *notifier = new GeneralObjectNotifier; - - notifier->start(); - return notifier; -} - - - -/* Invoke the mainloop in a thread. */ - -static void *notifier_mainloop(void *data) -{ - ObjectNotifier *notifier = reinterpret_cast(data); - - notifier->mainloop(); - return 0; -} - - - -/* Virtual destructor required for base class instance reference deletion. */ - -ObjectNotifier::~ObjectNotifier() -{ -} - -/* Listen for notifications. */ - -void ObjectNotifier::mainloop() -{ - ipc_message_t msg; - l4_umword_t label; - - while (1) - { - ipc_message_wait(&msg, &label); - - /* Clear lower label bits. */ - - label = label & ~3UL; - - /* Ignore erroneous messages. */ - - if (l4_ipc_error(msg.tag, l4_utcb())) - continue; - - /* Interpret gate labels as notifiable objects. */ - - notifiable_t *object = (notifiable_t *) label; - - /* Obtain message details. */ - - ipc_message_open(&msg); - - struct in_words_Notifier_notify *in_words = (struct in_words_Notifier_notify *) ipc_message_get_word_address(&msg, 0); - - /* Reply to notifications. */ - - ipc_message_reply(&msg); - ipc_message_discard(&msg); - - /* Register the notification. */ - - _notify(object, in_words->flags, in_words->values); - } - - ipc_message_free(&msg); -} - -/* Start listening for notifications. */ - -long ObjectNotifier::start() -{ - if (_started) - return L4_EOK; - - pthread_t thread; - pthread_attr_t attr; - long err; - - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - - err = pthread_create(&thread, &attr, notifier_mainloop, this); - if (err) - return err; - - _thread = pthread_l4_cap(thread); - _started = true; - - return L4_EOK; -} - - - -/* Return notification state for the given object or null state if no record - existed for the object. */ - -ObjectNotificationState &ObjectNotifier::object_state(notifiable_t *object, bool create) -{ - ObjectNotificationStates::iterator it = _state.find(object); - - if (it == _state.end()) - { - if (create) - return _state[object]; - else - return _null_state; - } - - return it->second; -} - -/* Subscribe to notification events on an object. */ - -long ObjectNotifier::subscribe(notifiable_t *object, notify_flags_t flags) -{ - l4_cap_idx_t endpoint; - long err = get_endpoint(object, &endpoint, true); - - if (err) - return err; - - /* Subscribe, sending the notification endpoint via the principal reference - for the object. */ - - client_Notification notify(object->base->ref); - - return notify.subscribe(endpoint, flags); -} - -/* Unsubscribe from notification events on an object. */ - -long ObjectNotifier::unsubscribe(notifiable_t *object) -{ - l4_cap_idx_t endpoint; - long err = get_endpoint(object, &endpoint, false); - - if (err) - return err; - - /* Unsubscribe via the notification interface. */ - - client_Notification notify(object->base->ref); - - notify.unsubscribe(endpoint); - - return remove_endpoint(object, endpoint); -} - -/* Obtain a notification endpoint for an object. */ - -long ObjectNotifier::get_endpoint(notifiable_t *object, l4_cap_idx_t *endpoint, bool create) -{ - /* Acquire the lock for state lookup. */ - - std::unique_lock state_guard(_state_lock); - - ObjectNotificationState &state = object_state(object, create); - - /* Create a notification endpoint, if necessary. */ - - if (state.is_null()) - { - if (create) - { - long err = ipc_server_new_for_thread(&state.endpoint, object, _thread); - - if (err) - return err; - } - else - return -L4_ENOENT; - } - - *endpoint = state.endpoint; - return L4_EOK; -} - -/* Remove a notification endpoint for an object. */ - -long ObjectNotifier::remove_endpoint(notifiable_t *object, l4_cap_idx_t endpoint) -{ - if (l4_is_invalid_cap(endpoint)) - return -L4_EINVAL; - - ipc_cap_free_um(endpoint); - - _state.erase(object); - - /* Remove the lock for updating object state. */ - - _object_locks.erase(object); - - return L4_EOK; -} - - - -/* Handle a notification event for an object. Ideally, this would be invoked by - the generic server dispatch mechanism, with the gate label being interpreted - and provided as the first parameter. */ - -void GeneralObjectNotifier::_notify(notifiable_t *object, notify_flags_t flags, - notify_values_t values) -{ - /* Enter critical section for the notifier (affecting all objects). */ - - std::unique_lock general_guard(_general_lock); - - /* Acquire the lock for state lookup. */ - - std::unique_lock state_guard(_state_lock); - - ObjectNotificationState &state = object_state(object, false); - - if (state.is_null()) - return; - - /* Acquire the lock for the object state itself. */ - - std::unique_lock object_guard(state.lock); - - /* Record flags and note previous flags. */ - - notify_flags_t recorded = state.pending_notifications; - - state.pending_notifications |= flags; - state.pending_values = values; - - /* Add an object queue entry for any objects without previous notifications. */ - - if (!recorded) - _affected.push_back(object); - - /* Notify any waiting caller. */ - - _general_condition.notify_one(); -} - -void SpecificObjectNotifier::_notify(notifiable_t *object, notify_flags_t flags, - notify_values_t values) -{ - /* Acquire the lock for state lookup. */ - - std::unique_lock state_guard(_state_lock); - - ObjectNotificationState &state = object_state(object, false); - - if (state.is_null()) - return; - - /* Acquire the lock for the object state itself. */ - - std::unique_lock object_guard(state.lock); - - state.pending_notifications |= flags; - state.pending_values = values; - - /* Notify any waiting caller. */ - - state.condition.notify_one(); -} - - - -/* Transfer pending notifications to the given object. This must be called with - a lock acquired on the object notification state. */ - -bool ObjectNotifier::_transfer(ObjectNotificationState &state, notifiable_t *object) -{ - notify_flags_t recorded = state.pending_notifications; - - if (recorded) - { - object->notifications = recorded; - object->values = state.pending_values; - state.pending_notifications = 0; - return true; - } - - return false; -} - - - -/* Obtain object state and transfer notifications. */ - -bool GeneralObjectNotifier::_retrieve_for_object(notifiable_t *object) -{ - /* Acquire the lock for state lookup. */ - - std::unique_lock state_guard(_state_lock); - - ObjectNotificationState &state = object_state(object, false); - - if (state.is_null()) - return false; - - /* Acquire the lock for the object state itself, then release the state lock. */ - - std::unique_lock object_guard(state.lock); - - state_guard.unlock(); - - /* Call generic method to transfer notifications, if possible. */ - - return _transfer(state, object); -} - -/* Obtain queued objects until one is found that still has events recorded for - it. This must be called with the notifier's general lock acquired. */ - -bool GeneralObjectNotifier::_retrieve(notifiable_t **object) -{ - while (!_affected.empty()) - { - *object = _affected.front(); - _affected.pop_front(); - - if (_retrieve_for_object(*object)) - return true; - } - - return false; -} - - - -/* Wait for notification events on objects. */ - -long GeneralObjectNotifier::wait(notifiable_t **object) -{ - std::unique_lock general_guard(_general_lock); - - while (1) - { - /* With pending notifications, update the first object and exit. */ - - if (_retrieve(object)) - break; - - /* Otherwise, wait for notifications. */ - - _general_condition.wait(general_guard); - } - - return L4_EOK; -} - -/* Wait for notifications from a single object. */ - -long SpecificObjectNotifier::wait_object(notifiable_t *object) -{ - /* Acquire the lock for reading object state. */ - - std::unique_lock state_guard(_state_lock); - - ObjectNotificationState &state = object_state(object, false); - - if (state.is_null()) - return -L4_EINVAL; - - /* Acquire the lock for the object state itself, then release the state lock. */ - - std::unique_lock object_guard(state.lock); - - state_guard.unlock(); - - while (1) - { - /* With pending notifications, update the object and exit. */ - - if (_transfer(state, object)) - break; - - /* Otherwise, wait for notifications. */ - - state.condition.wait(object_guard); - } - - return L4_EOK; -} - -// vim: tabstop=2 expandtab shiftwidth=2 diff -r 89e3e3e0864b -r 79bb98c3403b libfsclient/lib/src/process.cc --- a/libfsclient/lib/src/process.cc Mon Mar 20 14:52:03 2023 +0100 +++ b/libfsclient/lib/src/process.cc Mon Mar 20 22:28:37 2023 +0100 @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -30,7 +31,6 @@ #include "file.h" #include "process.h" -#include "notifier.h" diff -r 89e3e3e0864b -r 79bb98c3403b libnotifier/Control --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libnotifier/Control Mon Mar 20 22:28:37 2023 +0100 @@ -0,0 +1,3 @@ +requires: libstdc++ libc libipc +provides: libnotifier +maintainer: paul@boddie.org.uk diff -r 89e3e3e0864b -r 79bb98c3403b libnotifier/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libnotifier/Makefile Mon Mar 20 22:28:37 2023 +0100 @@ -0,0 +1,4 @@ +PKGDIR ?= . +L4DIR ?= $(PKGDIR)/../../.. + +include $(L4DIR)/mk/subdir.mk diff -r 89e3e3e0864b -r 79bb98c3403b libnotifier/include/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libnotifier/include/Makefile Mon Mar 20 22:28:37 2023 +0100 @@ -0,0 +1,7 @@ +PKGDIR ?= .. +L4DIR ?= $(PKGDIR)/../../.. + +PKGNAME = libnotifier +CONTRIB_HEADERS = 1 + +include $(L4DIR)/mk/include.mk diff -r 89e3e3e0864b -r 79bb98c3403b libnotifier/include/notifier/notifier.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libnotifier/include/notifier/notifier.h Mon Mar 20 22:28:37 2023 +0100 @@ -0,0 +1,174 @@ +/* + * Generic object event notification support. + * + * Copyright (C) 2021, 2022, 2023 Paul Boddie + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA + */ + +#pragma once + +#include +#include +#include +#include + +#include + + + +/* Object-specific notification details. */ + +class ObjectNotificationState +{ +public: + /* Synchronisation primitives for state access and notification. */ + + std::mutex lock; + std::condition_variable condition; + + /* Pending notifications for monitored objects. */ + + notify_flags_t pending_notifications = 0; + notify_values_t pending_values = NOTIFY_VALUES_NULL; + + /* Endpoints associated with monitored objects. */ + + l4_cap_idx_t endpoint = L4_INVALID_CAP; + + bool is_null() { return l4_is_invalid_cap(endpoint); } +}; + + + +/* Collection types. */ + +typedef std::map ObjectNotificationStates; +typedef std::map ObjectStateLocks; + + + +/* An object for monitoring object event notifications. */ + +class ObjectNotifier +{ +protected: + /* General state access locking. */ + + std::mutex _state_lock; + + /* Object-specific state locking. */ + + ObjectStateLocks _object_locks; + + /* Notification state. */ + + ObjectNotificationStates _state; + + /* Notifier thread details. */ + + l4_cap_idx_t _thread = L4_INVALID_CAP; + bool _started = false; + + /* Convenience method to access object state. */ + + virtual ObjectNotificationState &object_state(notifiable_t *object, bool create); + + /* Helper methods. */ + + virtual void _notify(notifiable_t *object, notify_flags_t flags, + notify_values_t values) = 0; + + virtual bool _transfer(ObjectNotificationState &state, notifiable_t *object); + +public: + virtual ~ObjectNotifier(); + + /* Local operations. */ + + virtual long start(); + + virtual long subscribe(notifiable_t *object, notify_flags_t flags); + + virtual long unsubscribe(notifiable_t *object); + + virtual long get_endpoint(notifiable_t *object, l4_cap_idx_t *endpoint, bool create); + + virtual long remove_endpoint(notifiable_t *object, l4_cap_idx_t endpoint); + + /* Event handling support. */ + + virtual void mainloop(); +}; + + + +/* An object monitoring notifications for a collection of different objects. */ + +class GeneralObjectNotifier : public ObjectNotifier +{ +protected: + /* Locking to protect pending notification members and to coordinate access + to notifications. */ + + std::mutex _general_lock; + + /* General lock synchronisation. */ + + std::condition_variable _general_condition; + + /* Objects affected by notifications. */ + + std::list _affected; + + /* Helper methods. */ + + virtual void _notify(notifiable_t *object, notify_flags_t flags, + notify_values_t values); + + virtual bool _retrieve(notifiable_t **object); + + virtual bool _retrieve_for_object(notifiable_t *object); + +public: + virtual long wait(notifiable_t **object); +}; + + + +/* An object monitoring notifications for specific objects. */ + +class SpecificObjectNotifier : public ObjectNotifier +{ +protected: + /* Helper methods. */ + + virtual void _notify(notifiable_t *object, notify_flags_t flags, + notify_values_t values); + +public: + virtual long wait_object(notifiable_t *object); +}; + + + +/* Helper functions. */ + +SpecificObjectNotifier *notifier_get_task_notifier(); + +GeneralObjectNotifier *notifier_get_local_notifier(); + +// vim: tabstop=4 expandtab shiftwidth=4 diff -r 89e3e3e0864b -r 79bb98c3403b libnotifier/lib/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libnotifier/lib/Makefile Mon Mar 20 22:28:37 2023 +0100 @@ -0,0 +1,4 @@ +PKGDIR ?= .. +L4DIR ?= $(PKGDIR)/../../.. + +include $(L4DIR)/mk/subdir.mk diff -r 89e3e3e0864b -r 79bb98c3403b libnotifier/lib/src/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libnotifier/lib/src/Makefile Mon Mar 20 22:28:37 2023 +0100 @@ -0,0 +1,40 @@ +PKGDIR ?= ../.. +L4DIR ?= $(PKGDIR)/../../.. + +TARGET = libnotifier.so libnotifier.a +PC_FILENAME = libnotifier + +# Locations for interface input and generated output. + +IDL_DIR = $(PKGDIR)/../libsystypes/idl +IDL_MK_DIR = $(L4DIR)/idl4re/mk +IDL_BUILD_DIR = . +IDL_EXPORT_DIR = . + +include $(IDL_MK_DIR)/idl.mk + +# Individual interfaces. + +CLIENT_INTERFACES_CC = notifier notification + +# Generated and plain source files. + +CLIENT_INTERFACES_SRC_CC = $(call interfaces_to_client_cc,$(CLIENT_INTERFACES_CC)) + +PLAIN_SRC_CC = notifier.cc + +# Normal definitions. + +SRC_CC = \ + $(CLIENT_INTERFACES_SRC_CC) \ + $(PLAIN_SRC_CC) + +REQUIRES_LIBS = l4re_c-util libipc libstdc++ libsystypes + +PRIVATE_INCDIR = $(PKGDIR)/include/notifier $(IDL_BUILD_DIR) $(IDL_EXPORT_DIR) +CONTRIB_INCDIR = libnotifier + +include $(L4DIR)/mk/lib.mk +include $(IDL_MK_DIR)/interface_rules.mk + +$(PLAIN_SRC_CC): $(CLIENT_INTERFACES_SRC_CC) diff -r 89e3e3e0864b -r 79bb98c3403b libnotifier/lib/src/notifier.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libnotifier/lib/src/notifier.cc Mon Mar 20 22:28:37 2023 +0100 @@ -0,0 +1,458 @@ +/* + * Generic object event notification support. + * + * Copyright (C) 2021, 2022, 2023 Paul Boddie + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA + */ + +#include +#include + +#include +#include + +#include +#include + +#include "notification_client.h" +#include "notifier.h" +#include "notifier_interface.h" + + + +/* Null notification state. */ + +static ObjectNotificationState _null_state; + + + +/* Lock protecting per-task notifier access. */ + +static std::mutex _lock; + +/* Per-task storage for specific waiting operations. */ + +static SpecificObjectNotifier *_notifier = NULL; + + + +/* Return the per-task notifier for object-specific waiting operations. */ + +SpecificObjectNotifier *notifier_get_task_notifier() +{ + std::lock_guard guard(_lock); + + /* Start any new notifier. */ + + if (_notifier == NULL) + { + _notifier = new SpecificObjectNotifier; + _notifier->start(); + } + + return _notifier; +} + +/* Return a local notifier for general object waiting operations. */ + +GeneralObjectNotifier *notifier_get_local_notifier() +{ + GeneralObjectNotifier *notifier = new GeneralObjectNotifier; + + notifier->start(); + return notifier; +} + + + +/* Invoke the mainloop in a thread. */ + +static void *notifier_mainloop(void *data) +{ + ObjectNotifier *notifier = reinterpret_cast(data); + + notifier->mainloop(); + return 0; +} + + + +/* Virtual destructor required for base class instance reference deletion. */ + +ObjectNotifier::~ObjectNotifier() +{ +} + +/* Listen for notifications. */ + +void ObjectNotifier::mainloop() +{ + ipc_message_t msg; + l4_umword_t label; + + while (1) + { + ipc_message_wait(&msg, &label); + + /* Clear lower label bits. */ + + label = label & ~3UL; + + /* Ignore erroneous messages. */ + + if (l4_ipc_error(msg.tag, l4_utcb())) + continue; + + /* Interpret gate labels as notifiable objects. */ + + notifiable_t *object = (notifiable_t *) label; + + /* Obtain message details. */ + + ipc_message_open(&msg); + + struct in_words_Notifier_notify *in_words = (struct in_words_Notifier_notify *) ipc_message_get_word_address(&msg, 0); + + /* Reply to notifications. */ + + ipc_message_reply(&msg); + ipc_message_discard(&msg); + + /* Register the notification. */ + + _notify(object, in_words->flags, in_words->values); + } + + ipc_message_free(&msg); +} + +/* Start listening for notifications. */ + +long ObjectNotifier::start() +{ + if (_started) + return L4_EOK; + + pthread_t thread; + pthread_attr_t attr; + long err; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + err = pthread_create(&thread, &attr, notifier_mainloop, this); + if (err) + return err; + + _thread = pthread_l4_cap(thread); + _started = true; + + return L4_EOK; +} + + + +/* Return notification state for the given object or null state if no record + existed for the object. */ + +ObjectNotificationState &ObjectNotifier::object_state(notifiable_t *object, bool create) +{ + ObjectNotificationStates::iterator it = _state.find(object); + + if (it == _state.end()) + { + if (create) + return _state[object]; + else + return _null_state; + } + + return it->second; +} + +/* Subscribe to notification events on an object. */ + +long ObjectNotifier::subscribe(notifiable_t *object, notify_flags_t flags) +{ + l4_cap_idx_t endpoint; + long err = get_endpoint(object, &endpoint, true); + + if (err) + return err; + + /* Subscribe, sending the notification endpoint via the principal reference + for the object. */ + + client_Notification notify(object->base->ref); + + return notify.subscribe(endpoint, flags); +} + +/* Unsubscribe from notification events on an object. */ + +long ObjectNotifier::unsubscribe(notifiable_t *object) +{ + l4_cap_idx_t endpoint; + long err = get_endpoint(object, &endpoint, false); + + if (err) + return err; + + /* Unsubscribe via the notification interface. */ + + client_Notification notify(object->base->ref); + + notify.unsubscribe(endpoint); + + return remove_endpoint(object, endpoint); +} + +/* Obtain a notification endpoint for an object. */ + +long ObjectNotifier::get_endpoint(notifiable_t *object, l4_cap_idx_t *endpoint, bool create) +{ + /* Acquire the lock for state lookup. */ + + std::unique_lock state_guard(_state_lock); + + ObjectNotificationState &state = object_state(object, create); + + /* Create a notification endpoint, if necessary. */ + + if (state.is_null()) + { + if (create) + { + long err = ipc_server_new_for_thread(&state.endpoint, object, _thread); + + if (err) + return err; + } + else + return -L4_ENOENT; + } + + *endpoint = state.endpoint; + return L4_EOK; +} + +/* Remove a notification endpoint for an object. */ + +long ObjectNotifier::remove_endpoint(notifiable_t *object, l4_cap_idx_t endpoint) +{ + if (l4_is_invalid_cap(endpoint)) + return -L4_EINVAL; + + ipc_cap_free_um(endpoint); + + _state.erase(object); + + /* Remove the lock for updating object state. */ + + _object_locks.erase(object); + + return L4_EOK; +} + + + +/* Handle a notification event for an object. Ideally, this would be invoked by + the generic server dispatch mechanism, with the gate label being interpreted + and provided as the first parameter. */ + +void GeneralObjectNotifier::_notify(notifiable_t *object, notify_flags_t flags, + notify_values_t values) +{ + /* Enter critical section for the notifier (affecting all objects). */ + + std::unique_lock general_guard(_general_lock); + + /* Acquire the lock for state lookup. */ + + std::unique_lock state_guard(_state_lock); + + ObjectNotificationState &state = object_state(object, false); + + if (state.is_null()) + return; + + /* Acquire the lock for the object state itself. */ + + std::unique_lock object_guard(state.lock); + + /* Record flags and note previous flags. */ + + notify_flags_t recorded = state.pending_notifications; + + state.pending_notifications |= flags; + state.pending_values = values; + + /* Add an object queue entry for any objects without previous notifications. */ + + if (!recorded) + _affected.push_back(object); + + /* Notify any waiting caller. */ + + _general_condition.notify_one(); +} + +void SpecificObjectNotifier::_notify(notifiable_t *object, notify_flags_t flags, + notify_values_t values) +{ + /* Acquire the lock for state lookup. */ + + std::unique_lock state_guard(_state_lock); + + ObjectNotificationState &state = object_state(object, false); + + if (state.is_null()) + return; + + /* Acquire the lock for the object state itself. */ + + std::unique_lock object_guard(state.lock); + + state.pending_notifications |= flags; + state.pending_values = values; + + /* Notify any waiting caller. */ + + state.condition.notify_one(); +} + + + +/* Transfer pending notifications to the given object. This must be called with + a lock acquired on the object notification state. */ + +bool ObjectNotifier::_transfer(ObjectNotificationState &state, notifiable_t *object) +{ + notify_flags_t recorded = state.pending_notifications; + + if (recorded) + { + object->notifications = recorded; + object->values = state.pending_values; + state.pending_notifications = 0; + return true; + } + + return false; +} + + + +/* Obtain object state and transfer notifications. */ + +bool GeneralObjectNotifier::_retrieve_for_object(notifiable_t *object) +{ + /* Acquire the lock for state lookup. */ + + std::unique_lock state_guard(_state_lock); + + ObjectNotificationState &state = object_state(object, false); + + if (state.is_null()) + return false; + + /* Acquire the lock for the object state itself, then release the state lock. */ + + std::unique_lock object_guard(state.lock); + + state_guard.unlock(); + + /* Call generic method to transfer notifications, if possible. */ + + return _transfer(state, object); +} + +/* Obtain queued objects until one is found that still has events recorded for + it. This must be called with the notifier's general lock acquired. */ + +bool GeneralObjectNotifier::_retrieve(notifiable_t **object) +{ + while (!_affected.empty()) + { + *object = _affected.front(); + _affected.pop_front(); + + if (_retrieve_for_object(*object)) + return true; + } + + return false; +} + + + +/* Wait for notification events on objects. */ + +long GeneralObjectNotifier::wait(notifiable_t **object) +{ + std::unique_lock general_guard(_general_lock); + + while (1) + { + /* With pending notifications, update the first object and exit. */ + + if (_retrieve(object)) + break; + + /* Otherwise, wait for notifications. */ + + _general_condition.wait(general_guard); + } + + return L4_EOK; +} + +/* Wait for notifications from a single object. */ + +long SpecificObjectNotifier::wait_object(notifiable_t *object) +{ + /* Acquire the lock for reading object state. */ + + std::unique_lock state_guard(_state_lock); + + ObjectNotificationState &state = object_state(object, false); + + if (state.is_null()) + return -L4_EINVAL; + + /* Acquire the lock for the object state itself, then release the state lock. */ + + std::unique_lock object_guard(state.lock); + + state_guard.unlock(); + + while (1) + { + /* With pending notifications, update the object and exit. */ + + if (_transfer(state, object)) + break; + + /* Otherwise, wait for notifications. */ + + state.condition.wait(object_guard); + } + + return L4_EOK; +} + +// vim: tabstop=2 expandtab shiftwidth=2