diff options
Diffstat (limited to 'src/lib/hooks/callout_manager.h')
-rw-r--r-- | src/lib/hooks/callout_manager.h | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/src/lib/hooks/callout_manager.h b/src/lib/hooks/callout_manager.h new file mode 100644 index 0000000..3ee1efd --- /dev/null +++ b/src/lib/hooks/callout_manager.h @@ -0,0 +1,438 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef CALLOUT_MANAGER_H +#define CALLOUT_MANAGER_H + +#include <exceptions/exceptions.h> +#include <hooks/library_handle.h> +#include <hooks/server_hooks.h> + +#include <boost/shared_ptr.hpp> + +#include <climits> +#include <map> +#include <string> + +namespace isc { +namespace hooks { + +/// @brief No such library +/// +/// Thrown if an attempt is made to set the current library index to a value +/// that is invalid for the number of loaded libraries. +class NoSuchLibrary : public Exception { +public: + NoSuchLibrary(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Callout Manager +/// +/// This class manages the registration, deregistration and execution of the +/// library callouts. It is part of the hooks framework used by the Kea +/// server, and is not for use by user-written code in a hooks library. +/// +/// In operation, the class needs to know two items of data: +/// +/// - The list of server hooks, which is used in two ways. Firstly, when a +/// library registers or deregisters a hook, it does so by name: the +/// @ref isc::hooks::ServerHooks object supplies the names of registered +/// hooks. Secondly, when the callouts associated with a hook are called by +/// the server, the server supplies the index of the relevant hook: this is +/// validated by reference to the list of hooks. +/// +/// - The number of loaded libraries. Each callout registered by a user +/// library is associated with that library, the callout manager storing both +/// a pointer to the callout and the index of the library in the list of +/// loaded libraries. When calling a callout, the callout manager maintains +/// the idea of a "current library index": this is used to access the context +/// associated with the library. +/// +/// These two items of data are supplied when an object of this class is +/// constructed. The latter (number of libraries) can be updated after the +/// class is constructed. (Such an update is used during library loading where +/// the CalloutManager has to be constructed before the libraries are loaded, +/// but one of the libraries subsequently fails to load.) +/// +/// The library index is important because it determines in what order callouts +/// on a particular hook are called. For each hook, the CalloutManager +/// maintains a vector of callouts ordered by library index. When a callout +/// is added to the list, it is added at the end of the callouts associated +/// with the current library. To clarify this further, suppose that three +/// libraries are loaded, A (assigned an index 1), B (assigned an index 2) and +/// C (assigned an index 3). Suppose A registers two callouts on a given hook, +/// A1 and A2 (in that order) B registers B1 and B2 (in that order) and C +/// registers C1 and C2 (in that order). Internally, the callouts are stored +/// in the order A1, A2, B1, B2, C1, and C2: this is also the order in which +/// they are called. +/// +/// Indexes range between 1 and n (where n is the number of the libraries +/// loaded) and are assigned to libraries based on the order the libraries +/// presented to the hooks framework for loading (something that occurs in the +/// isc::hooks::HooksManager) class. However, two other indexes are recognized, +/// 0 and INT_MAX. These are used when the server itself registers callouts - +/// the server is able to register callouts that get called before any +/// user-library callouts, and ones that get called after user-library callouts. +/// In other words, assuming the callouts on a hook are A1, A2, B1, B2, B3, C2, +/// C2 as before, and that the server registers S1 (to run before the +/// user-registered callouts) and S2 (to run after them), the callouts are +/// stored (and executed) in the order S1, A1, A2, B1, B2, B3, C2, C2, S2. In +/// summary, the recognized index values are: +/// +/// - < 0: invalid. +/// - 0: used for server-registered callouts that are called before +/// user-registered callouts. +/// - 1 - n: callouts from user libraries. +/// - INT_MAX: used for server-registered callouts called after +/// user-registered callouts. +/// +/// Since Kea 1.3.0 release hook libraries can register callouts as control +/// command handlers. Such handlers are associated with dynamically created +/// hook points which names are created after command names. For example, +/// if a command name is 'foo-bar', the name of the hook point to which +/// callouts/command handlers are registered is '$foo_bar'. Prefixing the +/// hook point name with the dollar sign eliminates potential conflicts +/// between hook points dedicated to commands handling and other (fixed) +/// hook points. +/// +/// Prefixing hook names for command handlers with a dollar sign precludes +/// auto registration of command handlers, i.e. hooks framework is unable +/// to match hook points with names of functions implementing command +/// handlers, because the dollar sign is not legal in C++ function names. +/// This is intended because we want hook libraries to explicitly register +/// commands handlers for supported commands and not rely on Kea to register +/// hook points for them. Should we find use cases for auto registration of +/// command handlers, we may modify the +/// @ref ServerHooks::commandToHookName to use an encoding of hook +/// point names for command handlers that would only contain characters +/// allowed in function names. +/// +/// The @ref CalloutManager::registerCommandHook has been added to allow for +/// dynamically creating hook points for which command handlers are registered. +/// This method is called from the @ref LibraryHandle::registerCommandCallout +/// as a result of registering the command handlers by the hook library in +/// its @c load() function. If the hook point for the given command already +/// exists, this function doesn't do anything. The +/// @ref LibraryHandle::registerCommandCallout can install callouts on this +/// hook point. +/// +/// Note that the callout functions do not access the CalloutManager: instead, +/// they use a LibraryHandle object. This contains an internal pointer to +/// the CalloutManager, but provides a restricted interface. In that way, +/// callouts are unable to affect callouts supplied by other libraries. + +class CalloutManager { +private: + + // Private typedefs + + /// Element in the vector of callouts. The elements in the pair are the + /// index of the library from which this callout was registered, and a# + /// pointer to the callout itself. + typedef std::pair<int, CalloutPtr> CalloutEntry; + + /// An element in the hook vector. Each element is a vector of callouts + /// associated with a given hook. + typedef std::vector<CalloutEntry> CalloutVector; + +public: + + /// @brief Constructor + /// + /// Initializes member variables, in particular sizing the hook vector + /// (the vector of callout vectors) to the appropriate size. + /// + /// @param num_libraries Number of loaded libraries. + /// + /// @throw isc::BadValue if the number of libraries is less than 0, + CalloutManager(int num_libraries = 0); + + /// @brief Register a callout on a hook for the current library + /// + /// Registers a callout function for the current library with a given hook. + /// The callout is added to the end of the callouts for this library that + /// are associated with that hook. + /// + /// @param name Name of the hook to which the callout is added. + /// @param callout Pointer to the callout function to be registered. + /// @param library_index Library index used for registering the callout. + /// + /// @throw NoSuchHook The hook name is unrecognized. + /// @throw Unexpected The hook name is valid but an internal data structure + /// is of the wrong size. + void registerCallout(const std::string& name, + CalloutPtr callout, + int library_index); + + /// @brief De-Register a callout on a hook for the current library + /// + /// Searches through the functions registered by the current library + /// with the named hook and removes all entries matching the + /// callout. + /// + /// @param name Name of the hook from which the callout is removed. + /// @param callout Pointer to the callout function to be removed. + /// @param library_index Library index used for deregistering the callout. + /// + /// @return true if a one or more callouts were deregistered. + /// + /// @throw NoSuchHook The hook name is unrecognized. + /// @throw Unexpected The hook name is valid but an internal data structure + /// is of the wrong size. + bool deregisterCallout(const std::string& name, + CalloutPtr callout, + int library_index); + + /// @brief Removes all callouts on a hook for the current library + /// + /// Removes all callouts associated with a given hook that were registered + /// by the current library. + /// + /// @param name Name of the hook from which the callouts are removed. + /// @param library_index Library index used for deregistering all callouts. + /// + /// @return true if one or more callouts were deregistered. + /// + /// @throw NoSuchHook Thrown if the hook name is unrecognized. + bool deregisterAllCallouts(const std::string& name, int library_index); + + /// @brief Checks if callouts are present on a hook + /// + /// Checks all loaded libraries and returns true if at least one callout + /// has been registered by any of them for the given hook. + /// + /// @param hook_index Hook index for which callouts are checked. + /// + /// @return true if callouts are present, false if not. + /// + /// @throw NoSuchHook Given index does not correspond to a valid hook. + bool calloutsPresent(int hook_index) const; + + /// @brief Checks if control command handlers are present for the + /// specified command. + /// + /// @param command_name Command name for which handlers' presence should + /// be checked. + /// + /// @return true if there is a hook point associated with the specified + /// command and callouts/command handlers are installed for this hook + /// point, false otherwise. + bool commandHandlersPresent(const std::string& command_name) const; + + /// @brief Calls the callouts for a given hook + /// + /// Iterates through the library handles and calls the callouts associated + /// with the given hook index. + /// + /// @note This method invalidates the current library index set with + /// setLibraryIndex(). + /// + /// @param hook_index Index of the hook to call. + /// @param callout_handle Reference to the CalloutHandle object for the + /// current object being processed. + void callCallouts(int hook_index, CalloutHandle& callout_handle); + + /// @brief Calls the callouts/command handlers for a given command name. + /// + /// Iterates through the library handles and calls the command handlers + /// associated with the given command. It expects that the hook point + /// for this command exists (with a name being a command_name prefixed + /// with a dollar sign and with hyphens replaced with underscores). + /// + /// @param command_name Command name for which handlers should be called. + /// @param callout_handle Reference to the CalloutHandle object for the + /// current object being processed. + /// + /// @throw NoSuchHook if the hook point for the specified command does + /// not exist. + void callCommandHandlers(const std::string& command_name, + CalloutHandle& callout_handle); + + /// @brief Registers a hook point for the specified command name. + /// + /// If the hook point for such command already exists, this function + /// doesn't do anything. The registered hook point name is created + /// after command_name by prefixing it with a dollar sign and replacing + /// all hyphens with underscores, e.g. for the 'foo-bar' command the + /// following hook point name will be generated: '$foo_bar'. + /// + /// @param command_name Command name for which the hook point should be + /// registered. + void registerCommandHook(const std::string& command_name); + + /// @brief Get number of libraries + /// + /// Returns the number of libraries that this CalloutManager is expected + /// to serve. This is the number passed to its constructor. + /// + /// @return Number of libraries served by this CalloutManager. + int getNumLibraries() const { + return (num_libraries_); + } + + /// @brief Get current library index + /// + /// Returns the index of the "current" library. This the index associated + /// with the currently executing callout when callCallouts is executing. + /// When callCallouts() is not executing (as is the case when the load() + /// function in a user-library is called during the library load process), + /// the index can be set by setLibraryIndex(). + /// + /// @note The value set by this method is lost after a call to + /// callCallouts. + /// + /// @return Current library index. + int getLibraryIndex() const { + return (current_library_); + } + + /// @brief Set current library index + /// + /// Sets the current library index. This has the following valid values: + /// + /// - -1: invalidate current index. + /// - 0: pre-user library callout. + /// - 1 - numlib: user-library callout (where "numlib" is the number of + /// libraries loaded in the system, this figure being passed to this + /// object at construction time). + /// - INT_MAX: post-user library callout. + /// + /// @param library_index New library index. + /// + /// @throw NoSuchLibrary if the index is not valid. + void setLibraryIndex(int library_index) { + checkLibraryIndex(library_index); + current_library_ = library_index; + } + + /// @defgroup calloutManagerLibraryHandles Callout manager library handles + /// + /// The CalloutManager offers three library handles: + /// + /// - a "standard" one, used to register and deregister callouts for + /// the library index that is marked as current in the CalloutManager. + /// When a callout is called, it is passed this one. + /// - a pre-library callout handle, used by the server to register + // callouts to run prior to user-library callouts. + /// - a post-library callout handle, used by the server to register + /// callouts to run after the user-library callouts. + //@{ + + /// @brief Return library handle + /// + /// The library handle is available to the user callout via the callout + /// handle object. It provides a cut-down view of the CalloutManager, + /// allowing the callout to register and deregister callouts in the + /// library of which it is part, whilst denying access to anything that + /// may affect other libraries. + /// + /// @return Reference to library handle for this manager + LibraryHandle& getLibraryHandle() { + return (library_handle_); + } + + /// @brief Return pre-user callouts library handle + /// + /// The LibraryHandle to affect callouts that will run before the + /// user-library callouts. + /// + /// @return Reference to pre-user library handle for this manager + LibraryHandle& getPreLibraryHandle() { + return (pre_library_handle_); + } + + /// @brief Return post-user callouts library handle + /// + /// The LibraryHandle to affect callouts that will run before the + /// user-library callouts. + /// + /// @return Reference to post-user library handle for this manager + LibraryHandle& getPostLibraryHandle() { + return (post_library_handle_); + } + + //@} + + /// @brief Return number of currently available hooks + size_t getHookLibsVectorSize() const { + return (hook_vector_.size()); + } + +private: + + /// @brief This method checks whether the hook_vector_ size is sufficient + /// and extends it if necessary. + /// + /// The problem is as follows: some hooks are initialized statically + /// from global static objects. ServerHooks object creates hooks_ collection + /// and CalloutManager creates its own hook_vector_ and both are initialized + /// to the same size. All works well so far. However, if some code at a + /// later time (e.g. a hook library) registers new hook point, then + /// ServerHooks::registerHook() will extend its hooks_ collection, but + /// the CalloutManager will keep the old hook_vector_ that is too small by + /// one. Now when the library is unloaded, deregisterAllCallouts will + /// go through all hook points and will eventually hit the one that + /// will return index greater than the hook_vector_ size. + /// + /// To solve this problem, ensureVectorSize was implemented. It should + /// be called (presumably from ServerHooks) every time a new hook point + /// is registered. It checks whether the vector size is sufficient and + /// extends it if necessary. It is safe to call it multiple times. It + /// may grow the vector size, but will never shrink it. + void ensureHookLibsVectorSize(); + + /// @brief Check library index + /// + /// Ensures that the current library index is valid. This is called by + /// the hook registration functions. + /// + /// @param library_index Value to check for validity as a library index. + /// Valid values are 0 -> numlib + 1 and -1: see @ref setLibraryIndex + /// for the meaning of the various values. + /// + /// @throw NoSuchLibrary Library index is not valid. + void checkLibraryIndex(int library_index) const; + + // Member variables + + /// Reference to the singleton ServerHooks object. See the + /// @ref hooksmgMaintenanceGuide for information as to why the class holds + /// a reference instead of accessing the singleton within the code. + ServerHooks& server_hooks_; + + /// Current library index. When a call is made to any of the callout + /// registration methods, this variable indicates the index of the user + /// library that should be associated with the call. + int current_library_; + + /// Vector of callout vectors. There is one entry in this outer vector for + /// each hook. Each element is itself a vector, with one entry for each + /// callout registered for that hook. + std::vector<CalloutVector> hook_vector_; + + /// LibraryHandle object user by the callout to access the callout + /// registration methods on this CalloutManager object. The object is set + /// such that the index of the library associated with any operation is + /// whatever is currently set in the CalloutManager. + LibraryHandle library_handle_; + + /// LibraryHandle for callouts to be registered as being called before + /// the user-registered callouts. + LibraryHandle pre_library_handle_; + + /// LibraryHandle for callouts to be registered as being called after + /// the user-registered callouts. + LibraryHandle post_library_handle_; + + /// Number of libraries. + int num_libraries_; +}; + +} // namespace util +} // namespace isc + +#endif // CALLOUT_MANAGER_H |