diff options
Diffstat (limited to 'src/lib/hooks/library_manager.cc')
-rw-r--r-- | src/lib/hooks/library_manager.cc | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/src/lib/hooks/library_manager.cc b/src/lib/hooks/library_manager.cc new file mode 100644 index 0000000..447c9eb --- /dev/null +++ b/src/lib/hooks/library_manager.cc @@ -0,0 +1,435 @@ +// Copyright (C) 2013-2020 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/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <hooks/hooks.h> +#include <hooks/hooks_log.h> +#include <hooks/callout_manager.h> +#include <hooks/library_handle.h> +#include <hooks/library_manager.h> +#include <hooks/pointer_converter.h> +#include <hooks/server_hooks.h> +#include <log/logger_manager.h> +#include <log/logger_support.h> +#include <log/message_initializer.h> +#include <util/multi_threading_mgr.h> + +#include <string> +#include <vector> + +#include <dlfcn.h> + +using namespace std; + +namespace isc { +namespace hooks { + +// Constructor (used by external agency) +LibraryManager::LibraryManager(const std::string& name, int index, + const boost::shared_ptr<CalloutManager>& manager) + : dl_handle_(NULL), index_(index), manager_(manager), + library_name_(name), + server_hooks_(ServerHooks::getServerHooksPtr()) +{ + if (!manager) { + isc_throw(NoCalloutManager, "must specify a CalloutManager when " + "instantiating a LibraryManager object"); + } +} + +// Constructor (used by "validate" for library validation). Note that this +// sets "manager_" to not point to anything, which means that methods such as +// registerStandardCallout() will fail, probably with a segmentation fault. +// There are no checks for this condition in those methods: this constructor +// is declared "private", so can only be executed by a method in this class. +// The only method to do so is "validateLibrary", which takes care not to call +// methods requiring a non-NULL manager. +LibraryManager::LibraryManager(const std::string& name) + : dl_handle_(NULL), index_(-1), manager_(), library_name_(name) +{} + +// Destructor. +LibraryManager::~LibraryManager() { + if (index_ >= 0) { + // LibraryManager instantiated to load a library, so ensure that + // it is unloaded before exiting. + static_cast<void>(prepareUnloadLibrary()); + } + + // LibraryManager instantiated to validate a library, so just ensure + // that it is closed before exiting. + static_cast<void>(closeLibrary()); +} + +// Open the library + +bool +LibraryManager::openLibrary() { + + // Open the library. We'll resolve names now, so that if there are any + // issues we don't bugcheck in the middle of apparently unrelated code. + dl_handle_ = dlopen(library_name_.c_str(), RTLD_NOW | RTLD_LOCAL); + if (dl_handle_ == NULL) { + LOG_ERROR(hooks_logger, HOOKS_OPEN_ERROR).arg(library_name_) + .arg(dlerror()); + } + + return (dl_handle_ != NULL); +} + +// Close the library if not already open + +bool +LibraryManager::closeLibrary() { + + // Close the library if it is open. (If not, this is a no-op.) + int status = 0; + if (dl_handle_ != NULL) { + status = dlclose(dl_handle_); + dl_handle_ = NULL; + if (status != 0) { + LOG_ERROR(hooks_logger, HOOKS_CLOSE_ERROR).arg(library_name_) + .arg(dlerror()); + } else { + LOG_INFO(hooks_logger, HOOKS_LIBRARY_CLOSED).arg(library_name_); + } + } + + return (status == 0); +} + +// Check the version of the library + +bool +LibraryManager::checkVersion() const { + + // Get the pointer to the "version" function. + PointerConverter pc(dlsym(dl_handle_, VERSION_FUNCTION_NAME)); + if (pc.versionPtr() != NULL) { + int version = KEA_HOOKS_VERSION - 1; // This is an invalid value + try { + version = (*pc.versionPtr())(); + } catch (...) { + LOG_ERROR(hooks_logger, HOOKS_VERSION_EXCEPTION).arg(library_name_); + return (false); + } + + if (version == KEA_HOOKS_VERSION) { + // All OK, version checks out + LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_LIBRARY_VERSION) + .arg(library_name_).arg(version); + return (true); + + } else { + LOG_ERROR(hooks_logger, HOOKS_INCORRECT_VERSION).arg(library_name_) + .arg(version).arg(KEA_HOOKS_VERSION); + } + } else { + LOG_ERROR(hooks_logger, HOOKS_NO_VERSION).arg(library_name_); + } + + return (false); +} + +// Check the multi-threading compatibility of the library + +bool +LibraryManager::checkMultiThreadingCompatible() const { + + // Compatible with single-threaded. + if (!util::MultiThreadingMgr::instance().getMode()) { + return (true); + } + + // Get the pointer to the "multi_threading_compatible" function. + PointerConverter pc(dlsym(dl_handle_, MULTI_THREADING_COMPATIBLE_FUNCTION_NAME)); + int compatible = 0; + if (pc.multiThreadingCompatiblePtr()) { + try { + compatible = (*pc.multiThreadingCompatiblePtr())(); + } catch (...) { + LOG_ERROR(hooks_logger, HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION) + .arg(library_name_); + return (false); + } + + LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, + HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE) + .arg(library_name_) + .arg(compatible); + } + if (compatible == 0) { + LOG_ERROR(hooks_logger, HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE) + .arg(library_name_); + } + return (compatible != 0); +} + +// Register the standard callouts + +void +LibraryManager::registerStandardCallouts() { + // Set the library index for doing the registration. This is picked up + // when the library handle is created. + manager_->setLibraryIndex(index_); + + // Iterate through the list of known hooks + vector<string> hook_names = ServerHooks::getServerHooks().getHookNames(); + for (size_t i = 0; i < hook_names.size(); ++i) { + + // Look up the symbol + void* dlsym_ptr = dlsym(dl_handle_, hook_names[i].c_str()); + PointerConverter pc(dlsym_ptr); + if (pc.calloutPtr() != NULL) { + // Found a symbol, so register it. + manager_->getLibraryHandle().registerCallout(hook_names[i], + pc.calloutPtr()); + LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, + HOOKS_STD_CALLOUT_REGISTERED).arg(library_name_) + .arg(hook_names[i]).arg(dlsym_ptr); + + } + } +} + +// Run the "load" function if present. + +bool +LibraryManager::runLoad() { + + // Get the pointer to the "load" function. + PointerConverter pc(dlsym(dl_handle_, LOAD_FUNCTION_NAME)); + if (pc.loadPtr() != NULL) { + + // Call the load() function with the library handle. We need to set + // the CalloutManager's index appropriately. We'll invalidate it + // afterwards. + + int status = -1; + try { + manager_->setLibraryIndex(index_); + status = (*pc.loadPtr())(manager_->getLibraryHandle()); + } catch (const isc::Exception& ex) { + LOG_ERROR(hooks_logger, HOOKS_LOAD_FRAMEWORK_EXCEPTION) + .arg(library_name_).arg(ex.what()); + return (false); + } catch (...) { + LOG_ERROR(hooks_logger, HOOKS_LOAD_EXCEPTION).arg(library_name_); + return (false); + } + + if (status != 0) { + LOG_ERROR(hooks_logger, HOOKS_LOAD_ERROR).arg(library_name_) + .arg(status); + return (false); + } else { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LOAD_SUCCESS) + .arg(library_name_); + } + + } else { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_LOAD) + .arg(library_name_); + } + + return (true); +} + + +// Run the "unload" function if present. + +bool +LibraryManager::prepareUnloadLibrary() { + + // Nothing to do. + if (dl_handle_ == NULL) { + return (true); + } + + // Call once. + if (index_ < 0) { + return (true); + } + + // Get the pointer to the "load" function. + bool result = false; + PointerConverter pc(dlsym(dl_handle_, UNLOAD_FUNCTION_NAME)); + if (pc.unloadPtr() != NULL) { + + // Call the load() function with the library handle. We need to set + // the CalloutManager's index appropriately. We'll invalidate it + // afterwards. + int status = -1; + try { + status = (*pc.unloadPtr())(); + result = true; + } catch (const isc::Exception& ex) { + LOG_ERROR(hooks_logger, HOOKS_UNLOAD_FRAMEWORK_EXCEPTION) + .arg(library_name_).arg(ex.what()); + } catch (...) { + // Exception generated. Note a warning as the unload will occur + // anyway. + LOG_WARN(hooks_logger, HOOKS_UNLOAD_EXCEPTION).arg(library_name_); + } + + if (result) { + if (status != 0) { + LOG_ERROR(hooks_logger, HOOKS_UNLOAD_ERROR).arg(library_name_) + .arg(status); + result = false; + } else { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_UNLOAD_SUCCESS) + .arg(library_name_); + } + } + } else { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_UNLOAD) + .arg(library_name_); + result = true; + } + + // Regardless of status, remove all callouts associated with this + // library on all hooks. + vector<string> hooks = ServerHooks::getServerHooks().getHookNames(); + manager_->setLibraryIndex(index_); + for (size_t i = 0; i < hooks.size(); ++i) { + bool removed = manager_->deregisterAllCallouts(hooks[i], index_); + if (removed) { + LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_REMOVED) + .arg(hooks[i]).arg(library_name_); + } + } + + // Mark as unload() ran. + index_ = -1; + + return (result); +} + +// The main library loading function. + +bool +LibraryManager::loadLibrary() { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_LOADING) + .arg(library_name_); + + // In the following, if a method such as openLibrary() fails, it will + // have issued an error message so there is no need to issue another one + // here. + + // Open the library (which is a check that it exists and is accessible). + if (openLibrary()) { + + // The hook libraries provide their own log messages and logger + // instances. This step is required to register log messages for + // the library being loaded in the global dictionary. Ideally, this + // should be called after all libraries have been loaded but we're + // going to call the version() and load() functions here and these + // functions may already contain logging statements. + isc::log::MessageInitializer::loadDictionary(); + + // The log messages registered by the new hook library may duplicate + // some of the existing messages. Log warning for each duplicated + // message now. + isc::log::LoggerManager::logDuplicatedMessages(); + + // Library opened OK, see if a version function is present and if so, + // check what value it returns. Check multi-threading compatibility. + if (checkVersion() && checkMultiThreadingCompatible()) { + // Version OK, so now register the standard callouts and call the + // library's load() function if present. + registerStandardCallouts(); + if (runLoad()) { + + // Success - the library has been successfully loaded. + LOG_INFO(hooks_logger, HOOKS_LIBRARY_LOADED).arg(library_name_); + return (true); + + } else { + + // The load function failed, so back out. We can't just close + // the library as (a) we need to call the library's "unload" + // function (if present) in case "load" allocated resources that + // need to be freed and (b) we need to remove any callouts that + // have been installed. + static_cast<void>(prepareUnloadLibrary()); + } + } + + // Either the version check or call to load() failed, so close the + // library and free up resources. Ignore the status return here - we + // already know there's an error and will have output a message. + static_cast<void>(closeLibrary()); + } + + return (false); +} + +// The library unloading function. Call the unload() function (if present), +// remove callouts from the callout manager, then close the library. This is +// only run if the library is still loaded and is a no-op if the library is +// not open. + +bool +LibraryManager::unloadLibrary() { + bool result = true; + if (dl_handle_ != NULL) { + LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_UNLOADING) + .arg(library_name_); + + // Call the unload() function if present. Note that this is done first + // - operations take place in the reverse order to which they were done + // when the library was loaded. + if (index_ >= 0) { + result = prepareUnloadLibrary(); + } + + // ... and close the library. + result = closeLibrary() && result; + if (result) { + + // Issue the informational message only if the library was unloaded + // with no problems. If there was an issue, an error message would + // have been issued. + LOG_INFO(hooks_logger, HOOKS_LIBRARY_UNLOADED).arg(library_name_); + } + } + return (result); +} + +// Validate the library. We must be able to open it, and the version function +// must both exist and return the right number. Note that this is a static +// method. + +bool +LibraryManager::validateLibrary(const std::string& name) { + // Instantiate a library manager for the validation. We use the private + // constructor as we don't supply a CalloutManager. + LibraryManager manager(name); + + // Try to open it and, if we succeed, check the version. + bool validated = manager.openLibrary() && manager.checkVersion() && + manager.checkMultiThreadingCompatible(); + + // Regardless of whether the version checked out, close the library. (This + // is a no-op if the library failed to open.) + static_cast<void>(manager.closeLibrary()); + + return (validated); +} + +// @note Moved from its own hooks.cc file to avoid undefined reference +// with static link. +void hooksStaticLinkInit() { + if (!isc::log::isLoggingInitialized()) { + isc::log::initLogger(std::string("userlib")); + } +} + +} // namespace hooks +} // namespace isc |