Commit 8e94ab0a authored by Stephen Morris's avatar Stephen Morris

[2980] Extended library manager functionality

Added ability to run the load function and checked that it can
register callouts.
parent 390c1a76
......@@ -29,9 +29,25 @@ This is most likely due to the installation of a new version of BIND 10
without rebuilding the hook library. A rebuild and re-install of the library
should fix the problem in most cases.
% HOOKS_NO_VERSION no version() function found in hook library %1
% HOOKS_LOAD 'load' function in hook library %1 found and successfully called
This is a debug message issued when the "load" function has been found in a
hook library and has been successfully called.
% HOOKS_LOAD_ERROR hook library %1 has 'load' function returing error code %2
A "load" function was found in the library named in the message and was
called. The function returned a non-zero status (also given in the message)
which was interpreted as an error. The library has been unloaded and
no callouts from it will be installed.
% HOOKS_NO_LOAD no 'load' function found in hook library %1
This is a debug message saying that the specified library was loaded but
no function called "load" was found in it. Providing the library contained
some "standard" functions (i.e. functions with the names of the hooks for
the given server), this is not an issue.
% HOOKS_NO_VERSION no 'version' function found in hook library %1
The shared library named in the message was found and successfully loaded, but
BIND 10 did not find a function named 'version' in it. This function is
BIND 10 did not find a function named "version" in it. This function is
required and should return the version of BIND 10 against which the library
was built. The value is used to check that the library was built against a
compatible version of BIND 10. The library has not been loaded.
......@@ -41,7 +57,7 @@ BIND 10 failed to open the specified hook library for the stated reason. The
library has not been loaded. BIND 10 will continue to function, but without
the services offered by the library.
% HOOKS_REGISTER_CALLOUT library %1 has registered a callout for hook %2
% HOOKS_REGISTER_CALLOUT library %1 has registered a callout for hook '%2'
This is a debug message, output when the library loading function has located
a standard callout (a callout with the same name as a hook point) and
registered it.
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#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/server_hooks.h>
#include <string>
#include <vector>
#include <dlfcn.h>
namespace {
// String constants
const char* LOAD_FUNCTION_NAME = "load";
// const char* UNLOAD = "unload";
const char* VERSION_FUNCTION_NAME = "version";
}
using namespace std;
namespace isc {
namespace hooks {
// 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_DEEPBIND);
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());
}
}
return (status == 0);
}
// Check the version of the library
bool
LibraryManager::checkVersion() const {
// Look up the "version" string in the library. This is returned as
// "void*": without any other information, we must assume that it is of
// the correct type of version_function_ptr.
//
// Note that converting between void* and function pointers in C++ is
// fraught with difficulty and pitfalls (e.g. see
// https://groups.google.com/forum/?hl=en&fromgroups#!topic/
// comp.lang.c++/37o0l8rtEE0)
// The method given in that article - convert using a union is used here.
union {
version_function_ptr ver_ptr;
void* dlsym_ptr;
} pointers;
// Zero the union, whatever the size of the pointers.
pointers.ver_ptr = NULL;
pointers.dlsym_ptr = NULL;
// Get the pointer to the "version" function.
pointers.dlsym_ptr = dlsym(dl_handle_, VERSION_FUNCTION_NAME);
if (pointers.ver_ptr != NULL) {
int version = (*pointers.ver_ptr)();
if (version == BIND10_HOOKS_VERSION) {
// All OK, version checks out
return (true);
} else {
LOG_ERROR(hooks_logger, HOOKS_INCORRECT_VERSION).arg(library_name_)
.arg(version).arg(BIND10_HOOKS_VERSION);
}
} else {
LOG_ERROR(hooks_logger, HOOKS_NO_VERSION).arg(library_name_);
}
return (false);
}
// Register the standard callouts
void
LibraryManager::registerStandardCallouts() {
// Create a library handle for doing the registration. We also need to
// set the current library index to indicate the current library.
manager_->setLibraryIndex(index_);
LibraryHandle library_handle(manager_.get());
// Iterate through the list of known hooks
vector<string> hook_names = ServerHooks::getServerHooks().getHookNames();
for (int i = 0; i < hook_names.size(); ++i) {
// Convert void* to function pointers using the same tricks as
// described above.
union {
CalloutPtr callout_ptr;
void* dlsym_ptr;
} pointers;
pointers.callout_ptr = NULL;
pointers.dlsym_ptr = NULL;
// Look up the symbol
pointers.dlsym_ptr = dlsym(dl_handle_, hook_names[i].c_str());
if (pointers.callout_ptr != NULL) {
// Found a symbol, so register it.
//library_handle.registerCallout(hook_names[i], callout_ptr);
LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_REGISTER_CALLOUT)
.arg(library_name_).arg(hook_names[i]);
library_handle.registerCallout(hook_names[i], pointers.callout_ptr);
}
}
}
// Run the "load" function if present.
bool
LibraryManager::runLoad() {
// Look up the "load" function in the library. The code here is similar
// to that in "checkVersion".
union {
load_function_ptr load_ptr;
void* dlsym_ptr;
} pointers;
// Zero the union, whatever the size of the pointers.
pointers.load_ptr = NULL;
pointers.dlsym_ptr = NULL;
// Get the pointer to the "load" function.
pointers.dlsym_ptr = dlsym(dl_handle_, LOAD_FUNCTION_NAME);
if (pointers.load_ptr != NULL) {
// Call the load() function with the library handle. We need to set
// the CalloutManager's index appropriately. We'll invalidate it
// afterwards.
manager_->setLibraryIndex(index_);
int status = (*pointers.load_ptr)(manager_->getLibraryHandle());
manager_->setLibraryIndex(index_);
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)
.arg(library_name_);
}
} else {
LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_LOAD)
.arg(library_name_);
}
return (true);
}
} // namespace hooks
} // namespace isc
......@@ -33,17 +33,38 @@ class LibraryManager;
/// with the "version" method. If all is OK, it iterates through the list of
/// known hooks and locates their symbols, registering each callout as it
/// does so. Finally it locates the "load" and "unload" functions (if present),
/// calling the "load" callouts if present.
/// calling the "load" callout if present.
///
/// On unload, it calls the "unload" method if one was located, clears the
/// callouts from all hooks and closes the library.
///
/// @note Caution needs to be exercised whtn using the unload method. During
/// use, data will pass between the server and the library. In this
/// process, the library may allocate memory and pass it back to the
/// server. This could happen by the server setting arguments or context
/// in the CalloutHandle object, or by the library modifying the content
/// of pointed-to data. A problem arises when the library is unloaded,
/// because the addresses of allocated day may lie in the virtual
/// address space deleted in that process. If this happens, any
/// reference to the memory will cause a segmentation fault. This can
/// occur in a quite obscure place, for example in the middle of a
/// destructor of an STL class when it is deleting memory allocated
/// when the data structure was extended.
///
/// @par The only safe way to run the "unload" function is to ensure that all
/// possible references to it are removed first. This means that all
/// CalloutHandles must be destroyed, as must any data items that were
/// passed to the callouts. In practice, it could mean that a server
/// suspends processing of new requests until all existing ones have
/// been serviced and all packet/context structures destroyed before
/// reloading the libraries.
class LibraryManager {
private:
/// Useful typedefs for the framework functions
typedef int (*version_function_ptr)(); ///< version() signature
typedef int (*load_function_ptr)(); ///< load() signature
typedef int (*unload_function_ptr)(LibraryHandle&); ///< unload() signature
typedef int (*version_function_ptr)(); ///< version() signature
typedef int (*load_function_ptr)(LibraryHandle&); ///< load() signature
typedef int (*unload_function_ptr)(); ///< unload() signature
public:
/// @brief Constructor
......@@ -57,7 +78,7 @@ public:
LibraryManager(const std::string& name, int index,
const boost::shared_ptr<CalloutManager>& manager)
: dl_handle_(NULL), index_(index), manager_(manager),
library_name_(name), load_func_(NULL), unload_func_(NULL)
library_name_(name)
{}
/// @brief Destructor
......@@ -65,21 +86,27 @@ public:
/// If the library is open, closes it. This is principally a safety
/// feature to ensure closure in the case of an exception destroying
/// this object.
///
/// However, see the caveat in the class header about when it is safe
/// to unload libraries.
~LibraryManager() {
static_cast<void>(closeLibrary());
static_cast<void>(unloadLibrary());
}
/// @brief Loads a library
///
/// Open the library and check the version. If all is OK, load all
/// standard symbols then call "load" if present.
void loadLibrary() {}
bool loadLibrary() {return true;}
/// @brief Unloads a library
///
/// Calls the libraries "unload" function if present, the closes the
/// library.
void unloadLibrary() {}
///
/// However, see the caveat in the class header about when it is safe
/// to unload libraries.
bool unloadLibrary() {return false;}
/// @brief Return library name
///
......@@ -128,6 +155,15 @@ protected:
/// callouts for that hook.
void registerStandardCallouts();
/// @brief Run the load function if present
///
/// Searches for the "load" framework function and, if present, runs it.
///
/// @return bool true if not found or found and run successfully,
/// false on an error. In this case, an error message will
/// have been output.
bool runLoad();
private:
void* dl_handle_; ///< Handle returned by dlopen
int index_; ///< Index associated with this library
......@@ -135,8 +171,6 @@ private:
///< Callout manager for registration
std::string library_name_; ///< Name of the library
load_function_ptr load_func_; ///< Pointer to "load" function
unload_function_ptr unload_func_; ///< Pointer to "unload" function
};
} // namespace hooks
......
......@@ -25,28 +25,33 @@ TESTS_ENVIRONMENT = \
$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
# Build shared libraries for testing.
lib_LTLIBRARIES = libnv.la libiv.la libbco.la
lib_LTLIBRARIES = libnvl.la libivl.la libbcl.la liblcl.la liblecl.la
# No version function
libnv_la_SOURCES = no_version_library.cc
libnv_la_CXXFLAGS = $(AM_CXXFLAGS)
libnv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libnvl_la_SOURCES = no_version_library.cc
libnvl_la_CXXFLAGS = $(AM_CXXFLAGS)
libnvl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
# Incorrect version function
libiv_la_SOURCES = incorrect_version_library.cc
libiv_la_CXXFLAGS = $(AM_CXXFLAGS)
libiv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libivl_la_SOURCES = incorrect_version_library.cc
libivl_la_CXXFLAGS = $(AM_CXXFLAGS)
libilv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
# The basic callout library - contains standard callouts
libbcl_la_SOURCES = basic_callout_library.cc
libbcl_la_CXXFLAGS = $(AM_CXXFLAGS)
libbcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
# The basic callout library
libbco_la_SOURCES = basic_callout_library.cc
libbco_la_CXXFLAGS = $(AM_CXXFLAGS)
libbco_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libbco_la_LIBADD =
libbco_la_LIBADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
libbco_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
libbco_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
libbco_la_LIBADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
# The load callout library - contains a load function
liblcl_la_SOURCES = load_callout_library.cc
liblcl_la_CXXFLAGS = $(AM_CXXFLAGS)
liblcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
# The load error callout library - contains a load function that returns
# an error.
liblecl_la_SOURCES = load_error_callout_library.cc
liblecl_la_CXXFLAGS = $(AM_CXXFLAGS)
liblecl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
TESTS =
if HAVE_GTEST
......
......@@ -20,14 +20,22 @@
///
/// The characteristics of this library are:
///
/// - Only the "version" framework function is supplied. The other callouts
/// are assumed to be "standard" callouts.
/// - Only the "version" framework function is supplied.
///
/// - A context_create callout is supplied.
///
/// - Three other callouts are supplied. All do some trivial calculationsll
/// - Three "standard" callouts are supplied corresponding to the hooks
/// "lm_one", "lm_two", "lm_three". All do some trivial calculations
/// on the arguments supplied to it and the context variables, returning
/// intermediate results through the "result" argument.
/// intermediate results through the "result" argument. The result of
/// the calculation is:
///
/// @f[ (10 + data_1) * data_2 - data_3 @f]
///
/// ...where data_1, data_2 and data_3 are the values passed in arguments
/// of the same name to the three callouts (data_1 passed to lm_one,
/// data_2 to lm_two etc.) and the result is returned in the argument
/// "result".
#include <hooks/hooks.h>
#include <iostream>
......@@ -36,11 +44,6 @@ using namespace isc::hooks;
extern "C" {
int
version() {
return (BIND10_HOOKS_VERSION);
}
// Callouts
int
......@@ -53,7 +56,8 @@ context_create(CalloutHandle& handle) {
// First callout adds the passed "data_1" argument to the initialized context
// value of 10.
int lm_one(CalloutHandle& handle) {
int
lm_one(CalloutHandle& handle) {
int data;
handle.getArgument("data_1", data);
......@@ -101,5 +105,12 @@ lm_three(CalloutHandle& handle) {
return (0);
}
// Framework functions
int
version() {
return (BIND10_HOOKS_VERSION);
}
};
......@@ -101,16 +101,20 @@ public:
using LibraryManager::closeLibrary;
using LibraryManager::checkVersion;
using LibraryManager::registerStandardCallouts;
using LibraryManager::runLoad;
};
// Names of the libraries used in these tests. These libraries are built using
// libtool, so we need to look in the hidden ".libs" directory to locate the
// .so file. Note that we access the .so file - libtool creates this as a
// like to the real shared library.
static const char* BASIC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libbcl.so";
static const char* INCORRECT_VERSION_LIBRARY = "@abs_builddir@/.libs/libivl.so";
static const char* LOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/liblcl.so";
static const char* LOAD_ERROR_CALLOUT_LIBRARY =
"@abs_builddir@/.libs/liblecl.so";
static const char* NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so";
static const char* NO_VERSION_LIBRARY = "@abs_builddir@/.libs/libnv.so";
static const char* INCORRECT_VERSION_LIBRARY = "@abs_builddir@/.libs/libiv.so";
static const char* BASIC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libbco.so";
static const char* NO_VERSION_LIBRARY = "@abs_builddir@/.libs/libnvl.so";
namespace {
......@@ -213,13 +217,13 @@ TEST_F(LibraryManagerTest, RegisterStandardCallouts) {
callout_handle_->getArgument("result", result);
EXPECT_EQ(15, result);
// Second callout multiples the context value by 7
// Second callout multiples the running total by 7
callout_handle_->setArgument("data_2", static_cast<int>(7));
callout_manager_->callCallouts(lm_two_index_, *callout_handle_);
callout_handle_->getArgument("result", result);
EXPECT_EQ(105, result);
// Third callout subtracts 17.
// Third callout subtracts 17 from the running total.
callout_handle_->setArgument("data_3", static_cast<int>(17));
callout_manager_->callCallouts(lm_three_index_, *callout_handle_);
callout_handle_->getArgument("result", result);
......@@ -233,4 +237,94 @@ TEST_F(LibraryManagerTest, RegisterStandardCallouts) {
EXPECT_TRUE(lib_manager.closeLibrary());
}
// Test that the "load" function is called correctly.
TEST_F(LibraryManagerTest, CheckLoadCalled) {
// Load the only library, specifying the index of 0 as it's the only
// library. This should load all callouts.
PublicLibraryManager lib_manager(std::string(LOAD_CALLOUT_LIBRARY),
0, callout_manager_);
EXPECT_TRUE(lib_manager.openLibrary());
// Check the version of the library.
EXPECT_TRUE(lib_manager.checkVersion());
// Load the standard callouts
EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
int result = 0;
// Check that only context_create and lm_one have callouts registered.
EXPECT_TRUE(callout_manager_->calloutsPresent(
ServerHooks::CONTEXT_CREATE));
EXPECT_TRUE(callout_manager_->calloutsPresent(lm_one_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(lm_two_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(lm_three_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(
ServerHooks::CONTEXT_DESTROY));
// Call the runLoad() method to run the load() function.
EXPECT_TRUE(lib_manager.runLoad());
EXPECT_TRUE(callout_manager_->calloutsPresent(
ServerHooks::CONTEXT_CREATE));
EXPECT_TRUE(callout_manager_->calloutsPresent(lm_one_index_));
EXPECT_TRUE(callout_manager_->calloutsPresent(lm_two_index_));
EXPECT_TRUE(callout_manager_->calloutsPresent(lm_three_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(
ServerHooks::CONTEXT_DESTROY));
// Now execute the callouts in the order expected.
// only the first callout should be executed and the
// always comes first. This sets the context value to 10.
callout_manager_->callCallouts(ServerHooks::CONTEXT_CREATE,
*callout_handle_);
// First callout multiplies the passed data by 5.
callout_handle_->setArgument("data_1", static_cast<int>(5));
callout_manager_->callCallouts(lm_one_index_, *callout_handle_);
callout_handle_->getArgument("result", result);
EXPECT_EQ(25, result);
// Second callout adds 7 to the stored data.
callout_handle_->setArgument("data_2", static_cast<int>(7));
callout_manager_->callCallouts(lm_two_index_, *callout_handle_);
callout_handle_->getArgument("result", result);
EXPECT_EQ(32, result);
// Third callout multiplies the running total by 10
callout_handle_->setArgument("data_3", static_cast<int>(10));
callout_manager_->callCallouts(lm_three_index_, *callout_handle_);
callout_handle_->getArgument("result", result);
EXPECT_EQ(320, result);
// Explicitly clear the callout_handle_ so that we can delete the library.
// This is the only object to contain memory allocated by it.
callout_handle_.reset();
// Tidy up
EXPECT_TRUE(lib_manager.closeLibrary());
}
// Check handling of a "load" function that returns an error.
TEST_F(LibraryManagerTest, CheckLoadError) {
// Load the only library, specifying the index of 0 as it's the only
// library. This should load all callouts.
PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY),
0, callout_manager_);
EXPECT_TRUE(lib_manager.openLibrary());
// Check that we catch a load error
EXPECT_FALSE(lib_manager.runLoad());
// Explicitly clear the callout_handle_ so that we can delete the library.
// This is the only object to contain memory allocated by it.
callout_handle_.reset();
// Tidy up
EXPECT_TRUE(lib_manager.closeLibrary());
}
} // Anonymous namespace
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
/// @file
/// @brief Basic Load Library
///
/// This is a test file for the LibraryManager test. It produces a library
/// that allows for tests of the basic library manager functions.
///
/// The characteristics of this library are:
///
/// - The "version" and "load" framework functions are supplied. One "standard"
/// callout is supplied ("lm_one") and two non-standard ones which are
/// registered during the call to "load" on the hooks "lm_two" and
/// "lm_three".
///
/// All callouts do trivial calculations, the result of the calculation being
///
/// @f[ ((5 * data_1) + data_2) * data_3 @f]
///
/// ...where data_1, data_2 and data_3 are the values passed in arguments
/// of the same name to the three callouts (data_1 passed to lm_one,
/// data_2 to lm_two etc.) and the result is returned in the argument
/// "result".
#include <hooks/hooks.h>
#include <iostream>
using namespace isc::hooks;