Parcourir la source

[master] Merge branch 'trac2980'

Stephen Morris il y a 11 ans
Parent
commit
82c997a728
47 fichiers modifiés avec 5139 ajouts et 432 suppressions
  1. 16 0
      configure.ac
  2. 1 0
      doc/Doxyfile
  3. 36 9
      doc/devel/mainpage.dox
  4. 1 1
      src/lib/Makefile.am
  5. 56 0
      src/lib/hooks/Makefile.am
  6. 49 8
      src/lib/util/hooks/callout_handle.cc
  7. 42 12
      src/lib/util/hooks/callout_handle.h
  8. 87 13
      src/lib/util/hooks/callout_manager.cc
  9. 144 41
      src/lib/util/hooks/callout_manager.h
  10. 38 0
      src/lib/hooks/hooks.h
  11. 483 0
      src/lib/hooks/hooks_component_developer.dox
  12. 26 0
      src/lib/hooks/hooks_log.cc
  13. 50 0
      src/lib/hooks/hooks_log.h
  14. 173 0
      src/lib/hooks/hooks_manager.cc
  15. 276 0
      src/lib/hooks/hooks_manager.h
  16. 167 0
      src/lib/hooks/hooks_messages.mes
  17. 76 0
      src/lib/hooks/library_handle.cc
  18. 36 2
      src/lib/util/hooks/library_handle.h
  19. 292 0
      src/lib/hooks/library_manager.cc
  20. 190 0
      src/lib/hooks/library_manager.h
  21. 114 0
      src/lib/hooks/library_manager_collection.cc
  22. 133 0
      src/lib/hooks/library_manager_collection.h
  23. 121 0
      src/lib/hooks/pointer_converter.h
  24. 57 38
      src/lib/util/hooks/server_hooks.cc
  25. 50 84
      src/lib/util/hooks/server_hooks.h
  26. 104 0
      src/lib/hooks/tests/Makefile.am
  27. 115 0
      src/lib/hooks/tests/basic_callout_library.cc
  28. 11 11
      src/lib/util/tests/callout_handle_unittest.cc
  29. 165 44
      src/lib/util/tests/callout_manager_unittest.cc
  30. 142 0
      src/lib/hooks/tests/common_test_class.h
  31. 24 16
      src/lib/util/hooks/library_handle.cc
  32. 137 0
      src/lib/hooks/tests/full_callout_library.cc
  33. 65 26
      src/lib/util/tests/handles_unittest.cc
  34. 454 0
      src/lib/hooks/tests/hooks_manager_unittest.cc
  35. 33 0
      src/lib/hooks/tests/incorrect_version_library.cc
  36. 184 0
      src/lib/hooks/tests/library_manager_collection_unittest.cc
  37. 555 0
      src/lib/hooks/tests/library_manager_unittest.cc
  38. 119 0
      src/lib/hooks/tests/load_callout_library.cc
  39. 49 0
      src/lib/hooks/tests/load_error_callout_library.cc
  40. 27 0
      src/lib/hooks/tests/marker_file.h.in
  41. 30 0
      src/lib/hooks/tests/no_version_library.cc
  42. 25 0
      src/lib/hooks/tests/run_unittests.cc
  43. 55 119
      src/lib/util/tests/server_hooks_unittest.cc
  44. 79 0
      src/lib/hooks/tests/test_libraries.h.in
  45. 52 0
      src/lib/hooks/tests/unload_callout_library.cc
  46. 0 4
      src/lib/util/Makefile.am
  47. 0 4
      src/lib/util/tests/Makefile.am

+ 16 - 0
configure.ac

@@ -430,6 +430,7 @@ AC_SUBST(B10_CXXFLAGS)
 AC_SEARCH_LIBS(inet_pton, [nsl])
 AC_SEARCH_LIBS(recvfrom, [socket])
 AC_SEARCH_LIBS(nanosleep, [rt])
+AC_SEARCH_LIBS(dlsym, [dl])
 
 # Checks for header files.
 
@@ -469,6 +470,17 @@ AM_COND_IF([OS_BSD], [AC_DEFINE([OS_BSD], [1], [Running on BSD?])])
 AM_CONDITIONAL(OS_SOLARIS, test $OS_TYPE = Solaris)
 AM_COND_IF([OS_SOLARIS], [AC_DEFINE([OS_SOLARIS], [1], [Running on Solaris?])])
 
+# Deal with variants
+AM_CONDITIONAL(OS_FREEBSD, test $system = FreeBSD)
+AM_COND_IF([OS_FREEBSD], [AC_DEFINE([OS_FREEBSD], [1], [Running on FreeBSD?])])
+AM_CONDITIONAL(OS_NETBSD, test $system = NetBSD)
+AM_COND_IF([OS_NETBSD], [AC_DEFINE([OS_NETBSD], [1], [Running on NetBSD?])])
+AM_CONDITIONAL(OS_OPENBSD, test $system = OpenBSD)
+AM_COND_IF([OS_OPENBSD], [AC_DEFINE([OS_OPENBSD], [1], [Running on OpenBSD?])])
+AM_CONDITIONAL(OS_OSX, test $system = Darwin)
+AM_COND_IF([OS_OSX], [AC_DEFINE([OS_OSX], [1], [Running on OSX?])])
+
+
 AC_MSG_CHECKING(for sa_len in struct sockaddr)
 AC_TRY_COMPILE([
 #include <sys/types.h>
@@ -1317,6 +1329,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/datasrc/tests/memory/testdata/Makefile
                  src/lib/xfr/Makefile
                  src/lib/xfr/tests/Makefile
+                 src/lib/hooks/Makefile
+                 src/lib/hooks/tests/Makefile
                  src/lib/log/Makefile
                  src/lib/log/interprocess/Makefile
                  src/lib/log/interprocess/tests/Makefile
@@ -1423,6 +1437,8 @@ AC_OUTPUT([doc/version.ent
            src/lib/cc/session_config.h.pre
            src/lib/cc/tests/session_unittests_config.h
            src/lib/datasrc/datasrc_config.h.pre
+           src/lib/hooks/tests/marker_file.h
+           src/lib/hooks/tests/test_libraries.h
            src/lib/log/tests/console_test.sh
            src/lib/log/tests/destination_test.sh
            src/lib/log/tests/init_logger_test.sh

+ 1 - 0
doc/Doxyfile

@@ -679,6 +679,7 @@ INPUT                  = ../src/lib/exceptions \
                          ../src/lib/cache \
                          ../src/lib/server_common/ \
                          ../src/bin/sockcreator/ \
+                         ../src/lib/hooks/ \
                          ../src/lib/util/ \
                          ../src/lib/util/io/ \
                          ../src/lib/util/threads/ \

+ 36 - 9
doc/devel/mainpage.dox

@@ -1,11 +1,33 @@
+// Copyright (C) 2012-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.
+
 /**
- *
  * @mainpage BIND10 Developer's Guide
  *
  * Welcome to BIND10 Developer's Guide. This documentation is addressed
- * at existing and prospecting developers and programmers, who would like
- * to gain insight into internal workings of BIND 10. It could also be useful
- * for existing and prospective contributors.
+ * at existing and prospecting developers and programmers and provides
+ * information needed to both extend and maintain BIND 10.
+ *
+ * If you wish to write "hook" code - code that is loaded by BIND 10 at
+ * run-time and modifies its behavior you should read the section
+ * @ref hookDevelopersGuide.
+ *
+ * BIND 10 maintanenace information is divided into a number of sections
+ * depending on focus.  DNS-specific issues are covered in the
+ * @ref dnsMaintenanceGuide while information on DHCP-specific topics can
+ * be found in the @ref dhcpMaintenanceGuide.  General BIND 10 topics, not
+ * specific to any protocol, are discussed in @ref miscellaneousTopics.
  *
  * If you are a user or system administrator, rather than software engineer,
  * you should read <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND10
@@ -13,13 +35,15 @@
  *
  * Regardless of your field of expertise, you are encouraged to visit
  * <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
+ * @section hooksFramework Hooks Framework
+ * - @subpage hooksComponentDeveloperGuide
  *
- * @section DNS
+ * @section dnsMaintenanceGuide DNS Maintenance Guide
  * - Authoritative DNS (todo)
  * - Recursive resolver (todo)
  * - @subpage DataScrubbing
  *
- * @section DHCP
+ * @section dhcpMaintenanceGuide DHCP Maintenance Guide
  * - @subpage dhcp4
  *   - @subpage dhcpv4Session
  *   - @subpage dhcpv4ConfigParser
@@ -39,7 +63,7 @@
  * - @subpage dhcpDatabaseBackends
  * - @subpage perfdhcpInternals
  *
- * @section misc Miscellaneous topics
+ * @section miscellaneousTopics Miscellaneous topics
  * - @subpage LoggingApi
  *   - @subpage LoggingApiOverview
  *   - @subpage LoggingApiLoggerNames
@@ -47,7 +71,10 @@
  * - @subpage SocketSessionUtility
  * - <a href="./doxygen-error.log">Documentation warnings and errors</a>
  *
- * @todo: Move this logo to the right (and possibly up). Not sure what
- * is the best way to do it in Doxygen, without using CSS hacks.
  * @image html isc-logo.png
  */
+/*
+ * @todo: Move the logo to the right (and possibly up). Not sure what
+ * is the best way to do it in Doxygen, without using CSS hacks.
+ */
+

+ 1 - 1
src/lib/Makefile.am

@@ -1,3 +1,3 @@
-SUBDIRS = exceptions util log cryptolink dns cc config acl xfr bench \
+SUBDIRS = exceptions util log hooks cryptolink dns cc config acl xfr bench \
           asiolink asiodns nsas cache resolve testutils datasrc \
           server_common python dhcp dhcpsrv statistics

+ 56 - 0
src/lib/hooks/Makefile.am

@@ -0,0 +1,56 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS  = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS  = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+
+# Define rule to build logging source files from message file
+hooks_messages.h hooks_messages.cc: hooks_messages.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/hooks/hooks_messages.mes
+
+# Tell automake that the message files are built as part of the build process
+# (so that they are built before the main library is built).
+BUILT_SOURCES = hooks_messages.h hooks_messages.cc
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = hooks_messages.mes
+
+# Get rid of generated message files on a clean
+CLEANFILES = *.gcno *.gcda hooks_messages.h hooks_messages.cc
+
+lib_LTLIBRARIES = libb10-hooks.la
+libb10_hooks_la_SOURCES  =
+libb10_hooks_la_SOURCES += callout_handle.cc callout_handle.h
+libb10_hooks_la_SOURCES += callout_manager.cc callout_manager.h
+libb10_hooks_la_SOURCES += hooks.h
+libb10_hooks_la_SOURCES += hooks_log.cc hooks_log.h
+libb10_hooks_la_SOURCES += hooks_manager.cc hooks_manager.h
+libb10_hooks_la_SOURCES += library_handle.cc library_handle.h
+libb10_hooks_la_SOURCES += library_manager.cc library_manager.h
+libb10_hooks_la_SOURCES += library_manager_collection.cc library_manager_collection.h
+libb10_hooks_la_SOURCES += pointer_converter.h
+libb10_hooks_la_SOURCES += server_hooks.cc server_hooks.h
+
+nodist_libb10_hooks_la_SOURCES = hooks_messages.cc hooks_messages.h
+
+libb10_hooks_la_CXXFLAGS = $(AM_CXXFLAGS)
+libb10_hooks_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libb10_hooks_la_LDFLAGS  = $(AM_LDFLAGS)
+libb10_hooks_la_LIBADD  =
+libb10_hooks_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
+libb10_hooks_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
+libb10_hooks_la_LIBADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+
+
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+libb10_hooks_la_CXXFLAGS += -Wno-unused-parameter
+endif

+ 49 - 8
src/lib/util/hooks/callout_handle.cc

@@ -12,24 +12,25 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <util/hooks/callout_handle.h>
-#include <util/hooks/callout_manager.h>
-#include <util/hooks/library_handle.h>
-#include <util/hooks/server_hooks.h>
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
 
 #include <string>
 #include <utility>
 #include <vector>
 
 using namespace std;
-using namespace isc::util;
 
 namespace isc {
-namespace util {
+namespace hooks {
 
 // Constructor.
-CalloutHandle::CalloutHandle(const boost::shared_ptr<CalloutManager>& manager)
-    : arguments_(), context_collection_(), manager_(manager), skip_(false) {
+CalloutHandle::CalloutHandle(const boost::shared_ptr<CalloutManager>& manager,
+                    const boost::shared_ptr<LibraryManagerCollection>& lmcoll)
+    : lm_collection_(lmcoll), arguments_(), context_collection_(),
+      manager_(manager), skip_(false) {
 
     // Call the "context_create" hook.  We should be OK doing this - although
     // the constructor has not finished running, all the member variables
@@ -44,6 +45,24 @@ CalloutHandle::~CalloutHandle() {
     // the destructor is being called, all the member variables are still in
     // existence.
     manager_->callCallouts(ServerHooks::CONTEXT_DESTROY, *this);
+
+    // Explicitly clear the argument and context objects.  This should free up
+    // all memory that could have been allocated by libraries that were loaded.
+    arguments_.clear();
+    context_collection_.clear();
+
+    // Normal destruction of the remaining variables will include the
+    // destruction of lm_collection_, an action that decrements the reference
+    // count on the library manager collection (which holds the libraries that
+    // could have allocated memory in the argument and context members.)  When
+    // that goes to zero, the libraries will be unloaded: at that point nothing
+    // in the hooks framework will be pointing to memory in the libraries'
+    // address space.
+    //
+    // It is possible that some other data structure in the server (the program
+    // using the hooks library) still references the address space and attempts
+    // to access it causing a segmentation fault. That issue is outside the
+    // scope of this framework and is not addressed by it.
 }
 
 // Return the name of all argument items.
@@ -117,5 +136,27 @@ CalloutHandle::getContextNames() const {
     return (names);
 }
 
+// Return name of current hook (the hook to which the current callout is
+// attached) or the empty string if not called within the context of a
+// callout.
+
+string
+CalloutHandle::getHookName() const {
+    // Get the current hook index.
+    int index = manager_->getHookIndex();
+
+    // ... and look up the hook.
+    string hook = "";
+    try {
+        hook = ServerHooks::getServerHooks().getName(index);
+    } catch (const NoSuchHook&) {
+        // Hook index is invalid, so this methods probably called from outside
+        // a callout being executed via a call to CalloutManager::callCallouts.
+        // In this case, the empty string is returned.
+    }
+
+    return (hook);
+}
+
 } // namespace util
 } // namespace isc

+ 42 - 12
src/lib/util/hooks/callout_handle.h

@@ -16,7 +16,7 @@
 #define CALLOUT_HANDLE_H
 
 #include <exceptions/exceptions.h>
-#include <util/hooks/library_handle.h>
+#include <hooks/library_handle.h>
 
 #include <boost/any.hpp>
 #include <boost/shared_ptr.hpp>
@@ -26,7 +26,7 @@
 #include <vector>
 
 namespace isc {
-namespace util {
+namespace hooks {
 
 /// @brief No such argument
 ///
@@ -54,6 +54,7 @@ public:
 
 class CalloutManager;
 class LibraryHandle;
+class LibraryManagerCollection;
 
 /// @brief Per-packet callout handle
 ///
@@ -79,11 +80,11 @@ class LibraryHandle;
 ///   "context_destroy" callout.  The information is accessed through the
 ///   {get,set}Context() methods.
 ///
-/// - Per-library handle.  Allows the callout to dynamically register and
-///   deregister callouts. (In the latter case, only functions registered by
-///   functions in the same library as the callout doing the deregistration
-///   can be removed: callouts registered by other libraries cannot be
-///   modified.)
+/// - Per-library handle (LibraryHandle). The library handle allows the
+///   callout to dynamically register and deregister callouts. In the latter
+///   case, only functions registered by functions in the same library as the
+///   callout doing the deregistration can be removed: callouts registered by
+///   other libraries cannot be modified.
 
 class CalloutHandle {
 public:
@@ -105,18 +106,32 @@ public:
     /// need to be set when the CalloutHandle is constructed.
     typedef std::map<int, ElementCollection> ContextCollection;
 
-
     /// @brief Constructor
     ///
     /// Creates the object and calls the callouts on the "context_create"
     /// hook.
     ///
+    /// Of the two arguments passed, only the pointer to the callout manager is
+    /// actively used.  The second argument, the pointer to the library manager
+    /// collection, is used for lifetime control: after use, the callout handle
+    /// may contain pointers to memory allocated by the loaded libraries.  The
+    /// used of a shared pointer to the collection of library managers means
+    /// that the libraries that could have allocated memory in a callout handle
+    /// will not be unloaded until all such handles have been destroyed.  This
+    /// issue is discussed in more detail in the documentation for
+    /// isc::hooks::LibraryManager.
+    ///
     /// @param manager Pointer to the callout manager object.
-    CalloutHandle(const boost::shared_ptr<CalloutManager>& manager);
+    /// @param lmcoll Pointer to the library manager collection.  This has a
+    ///        null default for testing purposes.
+    CalloutHandle(const boost::shared_ptr<CalloutManager>& manager,
+                  const boost::shared_ptr<LibraryManagerCollection>& lmcoll =
+                        boost::shared_ptr<LibraryManagerCollection>());
 
     /// @brief Destructor
     ///
     /// Calls the context_destroy callback to release any per-packet context.
+    /// It also clears stored data to avoid problems during member destruction.
     ~CalloutHandle();
 
     /// @brief Set argument
@@ -153,7 +168,7 @@ public:
 
         value = boost::any_cast<T>(element_ptr->second);
     }
-    
+
     /// @brief Get argument names
     ///
     /// Returns a vector holding the names of arguments in the argument
@@ -258,7 +273,7 @@ public:
 
         value = boost::any_cast<T>(element_ptr->second);
     }
-    
+
     /// @brief Get context names
     ///
     /// Returns a vector holding the names of items in the context associated
@@ -292,6 +307,14 @@ public:
         getContextForLibrary().clear();
     }
 
+    /// @brief Get hook name
+    ///
+    /// Get the name of the hook to which the current callout is attached.
+    /// This can be the null string if the CalloutHandle is being accessed
+    /// outside of the CalloutManager's "callCallouts" method.
+    ///
+    /// @return Name of the current hook or the empty string if none.
+    std::string getHookName() const;
 
 private:
     /// @brief Check index
@@ -333,6 +356,10 @@ private:
 
     // Member variables
 
+    /// Pointer to the collection of libraries for which this handle has been
+    /// created.
+    boost::shared_ptr<LibraryManagerCollection> lm_collection_;
+
     /// Collection of arguments passed to the callouts
     ElementCollection arguments_;
 
@@ -346,7 +373,10 @@ private:
     bool skip_;
 };
 
-} // namespace util
+/// A shared pointer to a CalloutHandle object.
+typedef boost::shared_ptr<CalloutHandle> CalloutHandlePtr;
+
+} // namespace hooks
 } // namespace isc
 
 

+ 87 - 13
src/lib/util/hooks/callout_manager.cc

@@ -12,31 +12,66 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <util/hooks/callout_handle.h>
-#include <util/hooks/callout_manager.h>
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks_log.h>
+#include <hooks/pointer_converter.h>
 
 #include <boost/static_assert.hpp>
 
 #include <algorithm>
+#include <climits>
 #include <functional>
 #include <utility>
 
 using namespace std;
-using namespace isc::util;
 
 namespace isc {
-namespace util {
+namespace hooks {
+
+// Check that the index of a library is valid.  It can range from 1 - n
+// (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX
+// (post-user library callouts).  It can also be -1 to indicate an invalid
+// value.
+
+void
+CalloutManager::checkLibraryIndex(int library_index) const {
+    if (((library_index >= -1) && (library_index <= num_libraries_)) ||
+        (library_index == INT_MAX)) {
+        return;
+    }
+
+    isc_throw(NoSuchLibrary, "library index " << library_index <<
+              " is not valid for the number of loaded libraries (" <<
+              num_libraries_ << ")");
+}
+
+// Set the number of libraries handled by the CalloutManager.
+
+void
+CalloutManager::setNumLibraries(int num_libraries) {
+    if (num_libraries < 0) {
+        isc_throw(isc::BadValue, "number of libraries passed to the "
+                  "CalloutManager must be >= 0");
+    }
+
+    num_libraries_ = num_libraries;
+}
 
 // Register a callout for the current library.
 
 void
 CalloutManager::registerCallout(const std::string& name, CalloutPtr callout) {
+    // Note the registration.
+    LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION)
+        .arg(current_library_).arg(name);
+
     // Sanity check that the current library index is set to a valid value.
     checkLibraryIndex(current_library_);
 
     // Get the index associated with this hook (validating the name in the
     // process).
-    int hook_index = hooks_->getIndex(name);
+    int hook_index = ServerHooks::getServerHooks().getIndex(name);
 
     // Iterate through the callout vector for the hook from start to end,
     // looking for the first entry where the library index is greater than
@@ -84,6 +119,10 @@ CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
         // Clear the "skip" flag so we don't carry state from a previous call.
         callout_handle.setSkip(false);
 
+        // Set the current hook index.  This is used should a callout wish to
+        // determine to what hook it is attached.
+        current_hook_ = hook_index;
+
         // Duplicate the callout vector for this hook and work through that.
         // This step is needed because we allow dynamic registration and
         // deregistration of callouts.  If a callout attached to a hook modified
@@ -100,12 +139,34 @@ CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
             current_library_ = i->first;
 
             // Call the callout
-            // @todo Log the return status if non-zero
-            static_cast<void>((*i->second)(callout_handle));
+            try {
+                int status = (*i->second)(callout_handle);
+                if (status == 0) {
+                    LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
+                              HOOKS_CALLOUT_CALLED).arg(current_library_)
+                        .arg(ServerHooks::getServerHooks()
+                            .getName(current_hook_))
+                        .arg(PointerConverter(i->second).dlsymPtr());
+                } else {
+                    LOG_ERROR(hooks_logger, HOOKS_CALLOUT_ERROR)
+                        .arg(current_library_)
+                        .arg(ServerHooks::getServerHooks()
+                            .getName(current_hook_))
+                        .arg(PointerConverter(i->second).dlsymPtr());
+                }
+            } catch (...) {
+                // Any exception, not just ones based on isc::Exception
+                LOG_ERROR(hooks_logger, HOOKS_CALLOUT_EXCEPTION)
+                    .arg(current_library_)
+                    .arg(ServerHooks::getServerHooks().getName(current_hook_))
+                    .arg(PointerConverter(i->second).dlsymPtr());
+            }
+
         }
 
-        // Reset the current library index to an invalid value to catch any
-        // programming errors.
+        // Reset the current hook and library indexs to an invalid value to
+        // catch any programming errors.
+        current_hook_ = -1;
         current_library_ = -1;
     }
 }
@@ -119,7 +180,7 @@ CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout) {
 
     // Get the index associated with this hook (validating the name in the
     // process).
-    int hook_index = hooks_->getIndex(name);
+    int hook_index = ServerHooks::getServerHooks().getIndex(name);
 
     /// Construct a CalloutEntry matching the current library and the callout
     /// we want to remove.
@@ -146,7 +207,13 @@ CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout) {
                                    hook_vector_[hook_index].end());
 
     // Return an indication of whether anything was removed.
-    return (initial_size != hook_vector_[hook_index].size());
+    bool removed = initial_size != hook_vector_[hook_index].size();
+    if (removed) {
+        LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
+                  HOOKS_CALLOUT_DEREGISTERED).arg(current_library_).arg(name);
+    }
+
+    return (removed);
 }
 
 // Deregister all callouts on a given hook.
@@ -156,7 +223,7 @@ CalloutManager::deregisterAllCallouts(const std::string& name) {
 
     // Get the index associated with this hook (validating the name in the
     // process).
-    int hook_index = hooks_->getIndex(name);
+    int hook_index = ServerHooks::getServerHooks().getIndex(name);
 
     /// Construct a CalloutEntry matching the current library (the callout
     /// pointer is NULL as we are not checking that).
@@ -175,7 +242,14 @@ CalloutManager::deregisterAllCallouts(const std::string& name) {
                                    hook_vector_[hook_index].end());
 
     // Return an indication of whether anything was removed.
-    return (initial_size != hook_vector_[hook_index].size());
+    bool removed = initial_size != hook_vector_[hook_index].size();
+    if (removed) {
+        LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
+                  HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(current_library_)
+                                                .arg(name);
+    }
+
+    return (removed);
 }
 
 } // namespace util

+ 144 - 41
src/lib/util/hooks/callout_manager.h

@@ -16,16 +16,17 @@
 #define CALLOUT_MANAGER_H
 
 #include <exceptions/exceptions.h>
-#include <util/hooks/library_handle.h>
-#include <util/hooks/server_hooks.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 util {
+namespace hooks {
 
 /// @brief No such library
 ///
@@ -37,11 +38,11 @@ public:
         isc::Exception(file, line, what) {}
 };
 
-
 /// @brief Callout Manager
 ///
 /// This class manages the registration, deregistration and execution of the
-/// library callouts.
+/// library callouts.  It is part of the hooks framework used by the BIND 10
+/// 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:
 ///
@@ -59,14 +60,51 @@ public:
 ///   deregister callouts in the same library (including themselves): they
 ///   cannot affect callouts registered by another library.  When calling a
 ///   callout, the callout manager maintains the idea of a "current library
-///   index": if the callout calls one of the callout registration functions 
+///   index": if the callout calls one of the callout registration functions
 ///   (indirectly via the @ref LibraryHandle object), the registration
 ///   functions use the "current library index" in their processing.
 ///
 /// These two items of data are supplied when an object of this class is
-/// constructed.
+/// 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
+/// the are called.  If B now registers another callout (B3), it is added to
+/// the vector after the list of callouts associated with B: the new order is
+/// therefore A1, A2, B1, B2, B3, C1 and C2.
+///
+/// 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::util::HooksManager) class.  However, two other indexes are recognised,
+/// 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 recognised index values are:
 ///
-/// Note that the callout function do not access the library manager: instead,
+/// - < 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.
+///
+/// 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.
@@ -92,26 +130,20 @@ public:
     /// Initializes member variables, in particular sizing the hook vector
     /// (the vector of callout vectors) to the appropriate size.
     ///
-    /// @param hooks Collection of known hook names.
     /// @param num_libraries Number of loaded libraries.
     ///
     /// @throw isc::BadValue if the number of libraries is less than or equal
     ///        to 0, or if the pointer to the server hooks object is empty.
-    CalloutManager(const boost::shared_ptr<ServerHooks>& hooks,
-                   int num_libraries)
-        : current_library_(-1), hooks_(hooks), hook_vector_(),
-          library_handle_(this), num_libraries_(num_libraries)
+    CalloutManager(int num_libraries = 0)
+        : current_hook_(-1), current_library_(-1),
+          hook_vector_(ServerHooks::getServerHooks().getCount()),
+          library_handle_(this), pre_library_handle_(this, 0),
+          post_library_handle_(this, INT_MAX), num_libraries_(num_libraries)
     {
-        if (!hooks) {
-            isc_throw(isc::BadValue, "must pass a pointer to a valid server "
-                      "hooks object to the CalloutManager");
-        } else if (num_libraries <= 0) {
-            isc_throw(isc::BadValue, "number of libraries passed to the "
-                      "CalloutManager must be >= 0");
-        }
-
-        // Parameters OK, do operations that depend on them.
-        hook_vector_.resize(hooks_->getCount());
+        // Check that the number of libraries is OK.  (This does a redundant
+        // set of the number of libraries, but it's only a single assignment
+        // and avoids the need for a separate "check" method.
+        setNumLibraries(num_libraries);
     }
 
     /// @brief Register a callout on a hook for the current library
@@ -184,12 +216,36 @@ public:
     ///        current object being processed.
     void callCallouts(int hook_index, CalloutHandle& callout_handle);
 
+    /// @brief Get current hook index
+    ///
+    /// Made available during callCallouts, this is the index of the hook
+    /// on which callouts are being called.
+    int getHookIndex() const {
+        return (current_hook_);
+    }
+
+    /// @brief Set number of libraries
+    ///
+    /// Sets the number of libraries.  Although the value is passed to the
+    /// constructor, in some cases that is only an estimate and the number
+    /// can only be determined after the CalloutManager is created.
+    ///
+    /// @note If the number if libraries is reset, it must be done *before*
+    ///       any callouts are registered.
+    ///
+    /// @param num_libraries Number of libraries served by this CalloutManager.
+    ///
+    /// @throw BadValue Number of libraries must be >= 0.
+    /// @throw LibraryCountChanged Number of libraries has been changed after
+    ///        callouts have been registered.
+    void setNumLibraries(int num_libraries);
+
     /// @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 server by this CalloutManager.
+    /// @return Number of libraries served by this CalloutManager.
     int getNumLibraries() const {
         return (num_libraries_);
     }
@@ -212,9 +268,14 @@ public:
 
     /// @brief Set current library index
     ///
-    /// Sets the current library index.  This must be in the range 0 to
-    /// (numlib - 1), where "numlib" is the number of libraries loaded in the
-    /// system, this figure being passed to this object at construction time.
+    /// 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.
     ///
@@ -224,6 +285,19 @@ public:
         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
@@ -232,11 +306,33 @@ public:
     /// library of which it is part, whilst denying access to anything that
     /// may affect other libraries.
     ///
-    /// @return Reference to callout handle for this manager
+    /// @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_);
+    }
+
+    //@}
+
 private:
     /// @brief Check library index
     ///
@@ -244,15 +340,11 @@ private:
     /// 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 {
-        if ((library_index < 0) || (library_index >= num_libraries_)) {
-            isc_throw(NoSuchLibrary, "library index " << library_index <<
-                      " is not valid for the number of loaded libraries (" <<
-                      num_libraries_ << ")");
-        }
-    }
+    void checkLibraryIndex(int library_index) const;
 
     /// @brief Compare two callout entries for library equality
     ///
@@ -273,26 +365,37 @@ private:
         }
     };
 
+    /// Current hook.  When a call is made to callCallouts, this holds the
+    /// index of the current hook.  It is set to an invalid value (-1)
+    /// otherwise.
+    int current_hook_;
+
     /// 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_;
 
-    /// List of server hooks.
-    boost::shared_ptr<ServerHooks>  hooks_;
-
     /// 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_;
+    std::vector<CalloutVector> hook_vector_;
 
     /// LibraryHandle object user by the callout to access the callout
-    /// registration methods on this CalloutManager object.
+    /// 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

+ 38 - 0
src/lib/hooks/hooks.h

@@ -0,0 +1,38 @@
+// 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.
+
+#ifndef HOOKS_H
+#define HOOKS_H
+
+#include <hooks/callout_handle.h>
+#include <hooks/library_handle.h>
+
+namespace {
+
+// Version 1 of the hooks framework.
+const int BIND10_HOOKS_VERSION = 1;
+
+// Names of the framework functions.
+const char* const LOAD_FUNCTION_NAME = "load";
+const char* const UNLOAD_FUNCTION_NAME = "unload";
+const char* const VERSION_FUNCTION_NAME = "version";
+
+// Typedefs for pointers to the framework functions.
+typedef int (*version_function_ptr)();
+typedef int (*load_function_ptr)(isc::hooks::LibraryHandle&);
+typedef int (*unload_function_ptr)();
+
+} // Anonymous namespace
+
+#endif  // HOOKS_H

+ 483 - 0
src/lib/hooks/hooks_component_developer.dox

@@ -0,0 +1,483 @@
+// 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.
+
+/**
+@page hooksComponentDeveloperGuide Guide to Hooks for the BIND 10 Component Developer
+
+@section hooksComponentIntroduction Introduction
+
+The hooks framework is a BIND 10 system that simplifies the way that
+users can write code to modify the behavior of BIND 10.  Instead of
+altering the BIND 10 source code, they write functions that are compiled
+and linked into a shared library.  The library is specified in the BIND 10
+configuration database and run time, BIND 10 dynamically loads the library
+into its address space.  At various points in the processing, the component
+"calls out" to functions in the library, passing to them the data is it
+currently working on.  They can examine and modify the data as required.
+
+This guide is aimed at BIND 10 developers who want to write or modify a
+BIND 10 component to use hooks.  It shows how the component should be written
+to load a shared library at run-time and how to call functions in it.
+
+For information about writing a hooks library containing functions called by BIND 10
+during its execution, see the document @ref hooksDevelopersGuide.
+
+@subsection hooksComponentTerminology Terminology
+
+In the remainder of this guide, the following terminology is used:
+
+- Component - a BIND 10 process, e.g. the authoritative DNS server or the
+DHCPv4 server.
+
+- Hook/Hook Point - used interchageably, this is a point in the code at
+which a call to user-written functions is made. Each hook has a name and
+each hook can have any number (including 0) of user-written functions
+attached to it.
+
+- Callout - a user-written function called by the component at a hook
+point. This is so-named because the component "calls out" to the library
+to execute a user-written function.
+
+- User code/user library - non-BIND 10 code that is compiled into a
+shared library and loaded by BIND 10 into its address space.  Multiple
+user libraries can be loaded at the same time, each containing callouts for
+the same hooks.  The hooks framework calls these libraries one after the
+other. (See the document @ref hooksDevelopersGuide for more details.)
+
+@subsection hooksComponentLanguages Languages
+
+The core of BIND 10 is written in C++ with some parts in Python.  While it is
+the intention to provide the hooks framework for all languages, the initial
+version is for C++.  All examples in this guide are in that language.
+
+@section hooksComponentBasicIdeas Basic Ideas
+
+From the point of view of the component author, the basic ideas of the hooks
+framework are quite simple:
+
+- The location of hook points in the code need to be determined.
+
+- Name the hook points and register them.
+
+- At each hook point, the component needs to complete the following steps to
+  execute callouts registered by the user-library:
+  -# copy data into the object used to pass information to the callout.
+  -# call the callout.
+  -# copy data back from the object used to exchange information.
+  -# take action based on information returned.
+
+Of course, to set up the system the libraries need to be loaded in the first
+place.  The component also needs to:
+
+- Define the configuration item that specifies the user libraries for this
+component.
+
+- Handle configuration changes and load/unload the user libraries.
+
+The following sections will describe these tasks in more detail.
+
+@section hooksComponentDefinition Determing the Hook Points
+
+Before any other action takes place, the location of the hook points
+in the code need to be determined.  This of course depends on the
+component but as a general guideline, hook locations should be chosen
+where a callout is able to obtain useful information from BIND 10 and/or
+affect processing.  Typically this means at the start or end of a major
+step in the processing of a request, at a point where either useful
+information can be passed to a callout and/or the callout can affect
+the processing of the component. The latter is achieved in either or both
+of the following eays:
+
+- Setting the "skip" flag.  This is a boolean flag that the callout can set
+  and is a quick way of passing information back to the component.  It is used
+  to indicate that the component should skip the processing step associated with
+  the hook.  The exact action is up to the component, but is likely to be one
+  of skipping the processing step (probably because the callout has
+  done its own processing for the action) or dropping the current packet
+  and starting on a new request.
+
+- Modifying data passed to it.  The component should be prepared to continue
+  processing with the data returned by the callout.  It is up to the component
+  author whether the data is validated before being used, but doing so will
+  have performance implications.
+
+@section hooksComponentRegistration Naming and Registering the Hooks Points
+
+Once the location of the hook point has been determined, it should be
+given a name.  This name should be unique amongst all hook points and is
+subject to certain restrictions (see below).
+
+Before the callouts at any hook point are called and any user libraries
+loaded - so typically during component initialization - the component must
+register the names of all the hooks.  The registration is done using
+the static method isc::hooks::HooksManager::registerHook():
+
+@code
+
+#include <hooks/hooks_manager.h>
+    :
+    int example_index = HooksManager::registerHook("lease_allocate");
+@endcode
+
+The name of the hook is passed as the sole argument to the registerHook()
+method.  The value returned is the index of that hook point and should
+be retained - it is needed to call the callouts attached to that hook.
+
+Note that a hook only needs to be registered once.  There is no mechanism for
+unregistering a hook and there is no need to do so.
+
+@subsection hooksComponentAutomaticRegistration Automatic Registration of Hooks
+
+In some components, it may be convenient to set up a single initialization
+function that registers all hooks.  For others, it may be more convenient
+for each module within the component to perform its own initialization.
+Since the isc::hooks::HooksManager object is a singleton and is created when first
+accessed, a useful trick is to automatically register the hooks when
+the module is loaded.
+
+This technique involves declaring an object outside of any execution
+unit in the module.  When the module is loaded, the object's constructor
+is run.  By placing the hook registration calls in the constructor,
+the hooks in the module are defined at load time, before any function in
+the module is run.  The code for such an initialization sequence would
+be similar to:
+
+@code
+#include <hooks/hooks_manager.h>
+
+namespace {
+
+// Declare structure to perform initialization and store the hook indexes.
+//
+struct MyHooks {
+    int pkt_rcvd;   // Index of "packet received" hook
+    int pkt_sent;   // Index of "packet sent" hook
+
+    // Constructor
+    MyHooks() {
+        pkt_rcvd = HooksManager::registerHook("pkt_rcvd");
+        pkt_sent = HooksManager::registerHook("pkt_sent");
+    }
+};
+
+// Declare a "MyHooks" object.  As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+MyHooks my_hooks;
+
+} // Anonymous namespace
+
+void Someclass::someFunction() {
+    :
+    // Check if any callouts are defined on the pkt_rcvd hook.
+    if (HooksManager::calloutPresent(my_hooks.pkt_rcvd)) {
+          :
+    }
+    :
+}
+@endcode
+
+@subsection hooksComponentHookNames Hook Names
+
+Hook names are strings and in principle, any string can be used as the
+name of a hook, even one containing spaces and non-printable characters.
+However, the following guidelines should be observed:
+
+- The names <b>context_create</b> and <b>context_destroy</b> are reserved to
+the hooks system and are automatically registered: an attempt to register
+one of these will lead to a isc::hooks::DuplicateHook exception being thrown.
+
+- The hook name should be a valid "C" function name.  If a user gives a
+callout the same name as one of the hooks, the hooks framework will
+automatically load that callout and attach it to the hook: the user does not
+have to explicitly register it.
+
+- The hook name should not conflict with the name of a function in any of
+the system libraries (e.g. naming a hook "sqrt" could lead to the
+square-root function in the system's maths library being attached to the hook
+as a callout).
+
+- Although hook names can be in any case (including mixed case), the BIND 10
+convention is that they are lower-case.
+
+@section hooksComponentCallingCallouts Calling Callouts on a Hook
+
+@subsection hooksComponentArgument The Callout Handle
+
+Before describing how to call user code at a hook point, we must first consider
+how to pass data to it.
+
+Each user callout has the signature:
+@code
+int callout_name(isc::hooks::CalloutHandle& handle);
+@endcode
+
+The isc::hooks::CalloutHandle object is the object used to pass data to
+and from the callout.  This holds the data as a set of name/value pairs,
+each pair being considered an argument to the callout.  If there are
+multiple callouts attached to a hook, the CalloutHandle is passed to
+each in turn. Should a callout modify an argument, the updated data is
+passed subsequent callouts (each of which could also modify it) before
+being returned to the component.
+
+Two methods are provided to get and set the arguments passed to
+the callout called (naturally enough) getArgument and SetArgument.
+Their usage is illustrated by the following code snippets.
+
+@code
+    int count = 10;
+    boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value
+
+    // Assume that "handle_ptr" has been created and is a pointer to a
+    // CalloutHandle.
+    handle_ptr->setArgument("data_count", count);
+    handle_ptr->setArgument("inpacket", pktptr);
+
+    // Call the hook code.  lease_assigned_index is the value returned from
+    // HooksManager::registerHook() when the hook was registered.
+    HooksManager::callCallouts(lease_assigned_index, *handle_ptr);
+
+    // Retrieve the modified values
+    handle_ptr->getArgument("data_count", count);
+    handle_ptr->getArgument("inpacket", pktptr);
+@endcode
+
+As can be seen "getArgument" is used to retrieve data from the
+CalloutHandle, and "setArgument" used to put data into it.  If a callout
+wishes to alter data and pass it back to the component, it should retrieve
+the data with getArgument, modify it, and call setArgument to send
+it back.
+
+There are a couple points to be aware of:
+
+- The data type of the variable in the call to getArgument must
+match the data type of the variable passed to the corresponding
+setArgument <B>exactly</B>: using what would normally be considered
+to be a "compatible" type is not enough.  For example, if the callout
+passed an argument back to the component as an "int" and the component
+attempted to retrieve it as a "long", an exception would be thrown even
+though any value that can be stored in an "int" will fit into a "long".
+This restriction also applies the "const" attribute but only as applied to
+data pointed to by pointers, e.g. if an argument is defined as a "char*",
+an exception will be thrown if an attempt is made to retrieve it into
+a variable of type "const char*".  (However, if an argument is set as a
+"const int", it can be retrieved into an "int".)  The documentation of
+a hook point should detail the exact data type of each argument.
+
+- If a pointer to an object is passed to a callout (either a "raw"
+pointer, or a boost smart pointer (as in the example above), and the
+underlying object is altered through that pointer, the change will be
+reflected in the component even if the callout makes no call to setArgument.
+This can be avoided by passing a pointer to a "const" object.
+
+@subsection hooksComponentSkipFlag The Skip Flag
+
+Although information is passed back to the component from callouts through
+CalloutHandle arguments, a common action for callouts is to inform the component
+that its flow of control should be altered.  For example:
+
+- In the DHCP servers, there is a hook at the point at which a lease is
+  about to be assigned.  Callouts attached to this hooks may handle the
+  lease assignment in special cases, in which case they set the skip flag
+  to indicate that the server should not perform lease assignment in this
+  case.
+- A server may define a hook just after a packet is received.  A callout
+  attached to the hook might inspect the source address and compare it
+  against a blacklist.  If the address is on the list, the callout could set
+  the skip flag to indicate to the server that the packet should be dropped.
+
+For ease of processing, the CalloutHandle contains
+two methods, isc::hooks::CalloutHandle::getSkip() and
+isc::hooks::CalloutHandle::setSkip().  It is only meaningful for the
+component to use the "get" method.  The skip flag is cleared by the hooks
+framework when the component requests that callouts be executed, so any
+value set by the component is lost.  Callouts can both inspect the flag (it
+might have been set by callouts earlier in the callout list for the hook)
+and set it.  Note that the setting of the flag by a callout does not
+prevent callouts later in the list from being called: the skip flag is
+just a boolean flag - the only significance comes from its interpretation
+by the component.
+
+An example of use could be:
+@code
+// Set up arguments for DHCP lease assignment.
+handle->setArgument("query", query);
+handle->setArgument("response", response);
+HooksManager::callCallouts(lease_hook_index, *handle_ptr);
+if (! handle_ptr->getSkip()) {
+    // Skip flag not set, do the address allocation
+    :
+}
+@endcode
+
+
+@subsection hooksComponentGettingHandle Getting the Callout Handle
+
+The CalloutHandle object is linked to the loaded libraries
+for lifetime reasons (described below).  Components
+should retrieve a isc::hooks::CalloutHandle using
+isc::hooks::HooksManager::createCalloutHandle():
+@code
+    CalloutHandlePtr handle_ptr = HooksManager::createCalloutHandle();
+@endcode
+(isc::hooks::CalloutHandlePtr is a typedef for a Boost shared pointer to a
+CalloutHandle.)  The CalloutHandle so retrieved may be used for as
+long as the libraries are loaded.
+
+The handle is deleted by resetting the pointer:
+@code
+    handle_ptr.reset();
+@endcode
+... or by letting the handle pointer go out of scope.  The actual deletion
+occurs when the CallHandle's reference count goes to zero. (The
+current version of the hooks framework does not maintain any other
+pointer to the returned CalloutHandle, so it gets destroyed when the
+shared pointer to it is cleared or destroyed.  However, this may change
+in a future version.)
+
+@subsection hooksComponentCallingCallout Calling the Callout
+
+Calling the callout is a simple matter of executing the
+isc::hooks::HooksManager::callCallouts() method for the hook index in
+question.  For example, with the hook index pkt_sent defined as above,
+the hook can be executed by:
+@code
+    HooksManager::callCallouts(pkt_sent, *handle_ptr);
+@endcode
+... where "*handle_ptr" is a reference (note: not a pointer) to the
+isc::hooks::CalloutHandle object holding the arguments.  No status code
+is returned.  If a component needs to get data returned (other than that
+provided by the "skip" flag), it should define an argument through which
+the callout can do so.
+
+@subsubsection hooksComponentConditionalCallout Conditionally Calling Hook Callouts
+
+Most hooks in a component will not have callouts attached to them. To
+avoid the overhead of setting up arguments in the CalloutHandle, a
+component can check for callouts before doing that processing using
+isc::hooks::HooksManager::calloutsPresent().  Taking the index of a
+hook as its sole argument, the function returns true if there are any
+callouts attached to the hook and false otherwise.
+
+With this check, the code in the component for calling a hook would look
+something like:
+@code
+if (HooksManager::calloutsPresent(lease_hook_index)) {
+    // Set up arguments for lease assignment
+    handle->setArgument("query", query);
+    handle->setArgument("response", response);
+    HooksManager::callCallouts(lease_hook_index, *handle);
+    if (! handle->getSkip()) {
+        // Skip flag not set, do the address allocation
+        :
+    }
+}
+@endcode
+
+@section hooksComponentLoadLibraries Loading the User Libraries
+
+Once hooks are defined, all the hooks code described above will
+work, even if no libraries are loaded (and even if the library
+loading method is not called).  The CalloutHandle returned by
+isc::hooks::HooksManager::createCalloutHandle() will be valid,
+isc::hooks::HooksManager::calloutsPresent() will return false for every
+index, and isc::hooks::HooksManager::callCallouts() will be a no-op.
+
+However, if user libraries are specified in the BIND 10 configuration,
+the component should load them.  (Note the term "libraries": the hooks
+framework allows multiple user libraries to be loaded.) This should take
+place after the component's configuration has been read, and is achieved
+by the isc::hooks::HooksManager::loadLibraries() method.  The method is
+passed a vector of strings, each giving the full file specification of
+a user library:
+@code
+    std::vector<std::string> libraries = ... // Get array of libraries
+    bool success = HooksManager::loadLibraries(libraries);
+@endcode
+loadLibraries() returns a boolean status which is true if all libraries
+loaded successfully or false if one or more failed to load.  Appropriate
+error messages will have been logged in the latter case, the status
+being more to allow the developer to decide whether the execution
+should proceed in such circumstances.
+
+If loadLibraries() is called a second or subsequent time (as a result
+of a reconfiguration), all existing libraries are unloaded and a new
+set loaded.  Libraries can be explicitly unloaded either by calling
+isc::hooks::HooksManager::unloadLibraries() or by calling
+loadLibraries() with an empty vector as an argument.
+
+@subsection hooksComponentUnloadIssues Unload and Reload Issues
+
+Unloading a shared library works by unmapping the part of the process's
+virtual address space in which the library lies.  This may lead to
+problems if there are still references to that address space elsewhere
+in the process.
+
+In many operating systems, heap storage allowed by a shared library will
+lie in the virtual address allocated to the library.  This has implications
+in the hooks framework because:
+
+- Argument information stored in a CalloutHandle by a callout in a library
+may lie in the library's address space.
+- Data modified in objects passed as arguments may lie in the address
+space.  For example, it is common for a DHCP callout to add "options"
+to a packet: the memory allocated for those options will most likely
+lie in library address space.
+
+The problem really arises because of the extensive use by BIND 10 of boost
+smart pointers.  When the pointer is destroyed, the pointed-to memory is
+deallocated.  If the pointer points to address space that is unmapped because
+a library has been unloaded, the deletion causes a segmentation fault.
+
+The hooks framework addresses the issue for CalloutHandles by keeping in
+that object a shared pointer to the object controlling library unloading.
+Although a library can be unloaded at any time, it is only when all
+CalloutHandles that could possibly reference address space in the library
+have been deleted that the library will actually be unloaded and the
+address space unmapped.
+
+The hooks framework cannot solve the second issue as the objects in
+question are under control of the component.  It is up to the component
+developer to ensure that all such objects have been destroyed before
+libraries are reloaded.  In extreme cases this may mean the component
+suspending all processing of incoming requests until all currently
+executing requests have completed and data object destroyed, reloading
+the libraries, then resuming processing.
+
+@section hooksComponentCallouts Component-Defined Callouts
+
+Previous sections have discussed callout registration by user libraries.
+It is possible for a component to register its own functions (i.e. within
+its own address space) as hook callouts.  These functions are called
+in eactly the same way as user callouts, being passed their arguments
+though a CalloutHandle object.  (Guidelines for writing callouts can be
+found in @ref hooksDevelopersGuide.)
+
+A component can associate with a hook callouts that run either before
+user-registered callouts or after them.  Registration is done via a
+isc::hooks::LibraryHandle object, a reference to one being obtained
+through the methods isc::hooks::HooksManager::preCalloutLibraryHandle()
+(for a handle to register callouts to run before the user library
+callouts) or isc::hooks::HooksManager::postCalloutLibraryHandle() (for
+a handle to register callouts to run after the user callouts).  Use of
+the LibraryHandle to register and deregister callouts is described in
+@ref hooksLibraryHandle.
+
+Finally, it should be noted that callouts registered in this way only
+remain registered until the next call to isc::hooks::loadLibraries().
+It is up to the component to re-register the callouts after this
+method has been called.
+
+*/

+ 26 - 0
src/lib/hooks/hooks_log.cc

@@ -0,0 +1,26 @@
+// Copyright (C) 2011  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.
+
+/// Defines the logger used by the NSAS
+
+#include "hooks/hooks_log.h"
+
+namespace isc {
+namespace hooks {
+
+isc::log::Logger hooks_logger("hooks");
+
+} // namespace hooks
+} // namespace isc
+

+ 50 - 0
src/lib/hooks/hooks_log.h

@@ -0,0 +1,50 @@
+// 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.
+
+#ifndef HOOKS_LOG_H
+#define HOOKS_LOG_H
+
+#include <log/macros.h>
+#include <hooks/hooks_messages.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Hooks debug Logging levels
+///
+/// Defines the levels used to output debug messages in the Hooks framework.
+/// Note that higher numbers equate to more verbose (and detailed) output.
+
+// The first level traces normal operations,
+const int HOOKS_DBG_TRACE = DBGLVL_TRACE_BASIC;
+
+// The next level traces each call to hook code.
+const int HOOKS_DBG_CALLS = DBGLVL_TRACE_BASIC_DATA;
+
+// Additional information on the calls.  Report each call to a callout (even
+// if there are multiple callouts on a hook) and each status return.
+const int HOOKS_DBG_EXTENDED_CALLS = DBGLVL_TRACE_DETAIL_DATA;
+
+
+/// @brief Hooks Logger
+///
+/// Define the logger used to log messages.  We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger hooks_logger;
+
+} // namespace hooks
+} // namespace isc
+
+#endif // HOOKS_LOG_H

+ 173 - 0
src/lib/hooks/hooks_manager.cc

@@ -0,0 +1,173 @@
+// 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/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/library_manager_collection.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Constructor
+
+HooksManager::HooksManager() {
+}
+
+// Return reference to singleton hooks manager.
+
+HooksManager&
+HooksManager::getHooksManager() {
+    static HooksManager manager;
+    return (manager);
+}
+
+// Are callouts present?
+
+bool
+HooksManager::calloutsPresentInternal(int index) {
+    conditionallyInitialize();
+    return (callout_manager_->calloutsPresent(index));
+}
+
+bool
+HooksManager::calloutsPresent(int index) {
+    return (getHooksManager().calloutsPresentInternal(index));
+}
+
+// Call the callouts
+
+void
+HooksManager::callCalloutsInternal(int index, CalloutHandle& handle) {
+    conditionallyInitialize();
+    return (callout_manager_->callCallouts(index, handle));
+}
+
+void
+HooksManager::callCallouts(int index, CalloutHandle& handle) {
+    return (getHooksManager().callCalloutsInternal(index, handle));
+}
+
+// Load the libraries.  This will delete the previously-loaded libraries
+// (if present) and load new ones.
+
+bool
+HooksManager::loadLibrariesInternal(const std::vector<std::string>& libraries) {
+    // Unload current set of libraries (if any are loaded).
+    unloadLibrariesInternal();
+
+    // Create the library manager and load the libraries.
+    lm_collection_.reset(new LibraryManagerCollection(libraries));
+    bool status = lm_collection_->loadLibraries();
+
+    // ... and obtain the callout manager for them.
+    callout_manager_ = lm_collection_->getCalloutManager();
+
+    return (status);
+}
+
+bool
+HooksManager::loadLibraries(const std::vector<std::string>& libraries) {
+    return (getHooksManager().loadLibrariesInternal(libraries));
+}
+
+// Unload the libraries.  This just deletes all internal objects which will
+// cause the libraries to be unloaded.
+
+void
+HooksManager::unloadLibrariesInternal() {
+    // The order of deletion does not matter here, as each library manager
+    // holds its own pointer to the callout manager.  However, we may as
+    // well delete the library managers first: if there are no other references
+    // to the callout manager, the second statement will delete it, which may
+    // ease debugging.
+    lm_collection_.reset();
+    callout_manager_.reset();
+}
+
+void HooksManager::unloadLibraries() {
+    getHooksManager().unloadLibrariesInternal();
+}
+
+// Create a callout handle
+
+boost::shared_ptr<CalloutHandle>
+HooksManager::createCalloutHandleInternal() {
+    conditionallyInitialize();
+    return (boost::shared_ptr<CalloutHandle>(
+            new CalloutHandle(callout_manager_, lm_collection_)));
+}
+
+boost::shared_ptr<CalloutHandle>
+HooksManager::createCalloutHandle() {
+    return (getHooksManager().createCalloutHandleInternal());
+}
+
+// Perform conditional initialization if nothing is loaded.
+
+void
+HooksManager::performConditionalInitialization() {
+
+    // Nothing present, so create the collection with any empty set of
+    // libraries, and get the CalloutManager.
+    vector<string> libraries;
+    lm_collection_.reset(new LibraryManagerCollection(libraries));
+    lm_collection_->loadLibraries();
+
+    callout_manager_ = lm_collection_->getCalloutManager();
+}
+
+// Shell around ServerHooks::registerHook()
+
+int
+HooksManager::registerHook(const std::string& name) {
+    return (ServerHooks::getServerHooks().registerHook(name));
+}
+
+// Return pre- and post- library handles.
+
+isc::hooks::LibraryHandle&
+HooksManager::preCalloutsLibraryHandleInternal() {
+    conditionallyInitialize();
+    return (callout_manager_->getPreLibraryHandle());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::preCalloutsLibraryHandle() {
+    return (getHooksManager().preCalloutsLibraryHandleInternal());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::postCalloutsLibraryHandleInternal() {
+    conditionallyInitialize();
+    return (callout_manager_->getPostLibraryHandle());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::postCalloutsLibraryHandle() {
+    return (getHooksManager().postCalloutsLibraryHandleInternal());
+}
+
+} // namespace util
+} // namespace isc

+ 276 - 0
src/lib/hooks/hooks_manager.h

@@ -0,0 +1,276 @@
+// 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.
+
+#ifndef HOOKS_MANAGER_H
+#define HOOKS_MANAGER_H
+
+#include <hooks/server_hooks.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+// Forward declarations
+class CalloutHandle;
+class CalloutManager;
+class LibraryHandle;
+class LibraryManagerCollection;
+
+/// @brief Hooks Manager
+///
+/// This is the overall manager of the hooks framework and is the main class
+/// used by a BIND 10 module when handling hooks.  It is responsible for the
+/// loading and unloading of user libraries, and for calling the callouts on
+/// each hook point.
+///
+/// The class is a singleton, the single instance of the object being accessed
+/// through the static getHooksManager() method.
+
+class HooksManager : boost::noncopyable {
+public:
+    /// @brief Get singleton hooks manager
+    ///
+    /// @return Reference to the singleton hooks manager.
+    static HooksManager& getHooksManager();
+
+    /// @brief Load and reload libraries
+    ///
+    /// Loads the list of libraries into the server address space.  For each
+    /// library, the "standard" functions (ones with the same names as the
+    /// hook points) are configured and the libraries' "load" function
+    /// called.
+    ///
+    /// If libraries are already loaded, they are unloaded and the new
+    /// libraries loaded.
+    ///
+    /// If any library fails to load, an error message will be logged.  The
+    /// remaining libraries will be loaded if possible.
+    ///
+    /// @param libraries List of libraries to be loaded.  The order is
+    ///        important, as it determines the order that callouts on the same
+    ///        hook will be called.
+    ///
+    /// @return true if all libraries loaded without a problem, false if one or
+    ///        more libraries failed to load.  In the latter case, message will
+    ///        be logged that give the reason.
+    static bool loadLibraries(const std::vector<std::string>& libraries);
+
+    /// @brief Unload libraries
+    ///
+    /// Unloads the loaded libraries and leaves the hooks subsystem in the
+    /// state it was after construction but before loadLibraries() is called.
+    ///
+    /// @note: This method should be used with caution - see the notes for
+    ///        the class LibraryManager for pitfalls.  In general, a server
+    ///        should not call this method: library unloading will automatically
+    ///        take place when new libraries are loaded, and when appropriate
+    ///        objects are destroyed.
+    ///
+    /// @return true if all libraries unloaded successfully, false on an error.
+    ///         In the latter case, an error message will have been output.
+    static void unloadLibraries();
+
+    /// @brief Are callouts present?
+    ///
+    /// Checks loaded libraries and returns true if at lease one callout
+    /// has been registered by them for the given hook.
+    ///
+    /// @param index Hooks 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.
+    static bool calloutsPresent(int index);
+
+    /// @brief Calls the callouts for a given hook
+    ///
+    /// Iterates through the libray handles and calls the callouts associated
+    /// with the given hook index.
+    ///
+    /// @note This method invalidates the current library index set with
+    ///       setLibraryIndex().
+    ///
+    /// @param index Index of the hook to call.
+    /// @param handle Reference to the CalloutHandle object for the current
+    ///        object being processed.
+    static void callCallouts(int index, CalloutHandle& handle);
+
+    /// @brief Return pre-callouts library handle
+    ///
+    /// Returns a library handle that can be used by the server to register
+    /// callouts on a hook that are called _before_ any callouts belonging
+    /// to a library.
+    ///
+    /// @note Both the reference returned and the callouts registered with
+    ///       this handle only remain valid until the next loadLibraries() or
+    ///       unloadLibraries() call.  If the callouts are to remain registered
+    ///       after this time, a new handle will need to be obtained and the
+    ///       callouts re-registered.
+    ///
+    /// @return Reference to library handle associated with pre-library callout
+    ///         registration.
+    static LibraryHandle& preCalloutsLibraryHandle();
+
+    /// @brief Return post-callouts library handle
+    ///
+    /// Returns a library handle that can be used by the server to register
+    /// callouts on a hook that are called _after any callouts belonging
+    /// to a library.
+    ///
+    /// @note Both the reference returned and the callouts registered with
+    ///       this handle only remain valid until the next loadLibraries() or
+    ///       unloadLibraries() call.  If the callouts are to remain registered
+    ///       after this time, a new handle will need to be obtained and the
+    ///       callouts re-registered.
+    ///
+    /// @return Reference to library handle associated with post-library callout
+    ///         registration.
+    static LibraryHandle& postCalloutsLibraryHandle();
+
+    /// @brief Return callout handle
+    ///
+    /// Returns a callout handle to be associated with a request passed round
+    /// the system.
+    ///
+    /// @note This handle is valid only after a loadLibraries() call and then
+    ///       only up to the next loadLibraries() call.
+    ///
+    /// @return Shared pointer to a CalloutHandle object.
+    static boost::shared_ptr<CalloutHandle> createCalloutHandle();
+
+    /// @brief Register Hook
+    ///
+    /// This is just a convenience shell around the ServerHooks::registerHook()
+    /// method.  It - along with the definitions of the two hook indexes for
+    /// the context_create and context_destroy methods - means that server
+    /// authors only need to deal with HooksManager and CalloutHandle, and not
+    /// include any other hooks framework classes.
+    ///
+    /// @param name Name of the hook
+    ///
+    /// @return Index of the hook, to be used in subsequent hook-related calls.
+    ///         This will be greater than or equal to zero (so allowing a
+    ///         negative value to indicate an invalid index).
+    ///
+    /// @throws DuplicateHook A hook with the same name has already been
+    ///         registered.
+    static int registerHook(const std::string& name);
+
+    /// Index numbers for pre-defined hooks.
+    static const int CONTEXT_CREATE = ServerHooks::CONTEXT_CREATE;
+    static const int CONTEXT_DESTROY = ServerHooks::CONTEXT_DESTROY;
+
+private:
+
+    /// @brief Constructor
+    ///
+    /// This is private as the object is a singleton and can only be addessed
+    /// through the getHooksManager() static method.
+    HooksManager();
+
+    //@{
+    /// The following methods correspond to similarly-named static methods,
+    /// but actually do the work on the singleton instance of the HooksManager.
+    /// See the descriptions of the static methods for more details.
+
+    /// @brief Load and reload libraries
+    ///
+    /// @param libraries List of libraries to be loaded.  The order is
+    ///        important, as it determines the order that callouts on the same
+    ///        hook will be called.
+    ///
+    /// @return true if all libraries loaded without a problem, false if one or
+    ///        more libraries failed to load.  In the latter case, message will
+    ///        be logged that give the reason.
+    bool loadLibrariesInternal(const std::vector<std::string>& libraries);
+
+    /// @brief Unload libraries
+    void unloadLibrariesInternal();
+
+    /// @brief Are callouts present?
+    ///
+    /// @param index Hooks 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 calloutsPresentInternal(int index);
+
+    /// @brief Calls the callouts for a given hook
+    ///
+    /// @param index Index of the hook to call.
+    /// @param handle Reference to the CalloutHandle object for the current
+    ///        object being processed.
+    void callCalloutsInternal(int index, CalloutHandle& handle);
+
+    /// @brief Return callout handle
+    ///
+    /// @return Shared pointer to a CalloutHandle object.
+    boost::shared_ptr<CalloutHandle> createCalloutHandleInternal();
+
+    /// @brief Return pre-callouts library handle
+    ///
+    /// @return Reference to library handle associated with pre-library callout
+    ///         registration.
+    LibraryHandle& preCalloutsLibraryHandleInternal();
+
+    /// @brief Return post-callouts library handle
+    ///
+    /// @return Reference to library handle associated with post-library callout
+    ///         registration.
+    LibraryHandle& postCalloutsLibraryHandleInternal();
+
+    //@}
+
+    /// @brief Initialization to No Libraries
+    ///
+    /// Initializes the hooks manager with an "empty set" of libraries.  This
+    /// method is called if conditionallyInitialize() determines that such
+    /// initialization is needed.
+    void performConditionalInitialization();
+
+    /// @brief Conditional initialization of the  hooks manager
+    ///
+    /// loadLibraries() performs the initialization of the HooksManager,
+    /// setting up the internal structures and loading libraries.  However,
+    /// in some cases, server authors may not do that.  This method is called
+    /// whenever any hooks execution function is invoked (checking callouts,
+    /// calling callouts or returning a callout handle).  If the HooksManager
+    /// is unitialised, it will initialize it with an "empty set" of libraries.
+    ///
+    /// For speed, the test of whether initialization is required is done
+    /// in-line here.  The actual initialization is performed in
+    /// performConditionalInitialization().
+    void conditionallyInitialize() {
+        if (!lm_collection_) {
+            performConditionalInitialization();
+        }
+    }
+
+    // Members
+
+    /// Set of library managers.
+    boost::shared_ptr<LibraryManagerCollection> lm_collection_;
+
+    /// Callout manager for the set of library managers.
+    boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+} // namespace util
+} // namespace hooks
+
+#endif // HOOKS_MANAGER_H

+ 167 - 0
src/lib/hooks/hooks_messages.mes

@@ -0,0 +1,167 @@
+# 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.
+
+$NAMESPACE isc::hooks
+
+% HOOKS_CALLOUT_CALLED hooks library with index %1 has called a callout on hook %2 that has address %3
+Only output at a high debugging level, this message indicates that
+a callout on the named hook registered by the library with the given
+index (in the list of loaded libraries) has been called and returned a
+success state.  The address of the callout is given in the message
+
+% HOOKS_CALLOUT_ERROR error returned by callout on hook %1 registered by library with index %2 (callout address %3)
+If a callout returns an error status when called, this error message
+is issued.  It identifies the hook to which the callout is attached, the
+index of the library (in the list of loaded libraries) that registered
+it and the address of the callout.  The error is otherwise ignored.
+
+% HOOKS_CALLOUTS_REMOVED callouts removed from hook %1 for library %2
+This is a debug message issued during library unloading.  It notes that
+one of more callouts registered by that library have been removed from
+the specified hook.  This is similar to the HOOKS_DEREGISTER_ALL_CALLOUTS
+message (and the two are likely to be seen together), but is issued at a
+higher-level in the hook framework.
+
+% HOOKS_CLOSE_ERROR failed to close hook library %1: %2
+BIND 10 has failed to close the named hook library for the stated reason.
+Although this is an error, this should not affect the running system
+other than as a loss of resources.  If this error persists, you should
+restart BIND 10.
+
+% HOOKS_CALLOUT_EXCEPTION exception thrown by callout on hook %1 registered by library with index %2 (callout address %3)
+If a callout throws an exception when called, this error message is
+issued.  It identifies the hook to which the callout is attached, the
+index of the library (in the list of loaded libraries) that registered
+it and the address of the callout.  The error is otherwise ignored.
+
+% HOOKS_ALL_CALLOUTS_DEREGISTERED hook library at index %1 removed all callouts on hook %2
+A debug message issued when all callouts on the specified hook registered
+by the library with the given index were removed.  This is similar to
+the HOOKS_CALLOUTS_REMOVED message (and the two are likely to be seen
+together), but is issued at a lower-level in the hook framework.
+
+% HOOKS_CALLOUT_DEREGISTERED hook library at index %1 deregistered a callout on hook %2
+A debug message issued when all instances of a particular callouts on
+the hook identified in the message that were registered by the library
+with the given index have been removed.
+
+% HOOKS_INCORRECT_VERSION hook library %1 is at version %2, require version %3
+BIND 10 has detected that the named hook library has been built against
+a version of BIND 10 that is incompatible with the version of BIND 10
+running on your system.  It has not loaded the library.
+
+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_LIBRARY_LOADED hooks library %1 successfully loaded
+This information message is issued when a user-supplied hooks library
+has been successfully loaded.
+
+% HOOKS_LIBRARY_UNLOADED hooks library %1 successfully unloaded
+This information message is issued when a user-supplied hooks library
+has been successfully unloaded.
+
+% HOOKS_LIBRARY_VERSION hooks library %1 reports its version as %2
+A debug  message issued when the version check on the hooks library
+has succeeded.
+
+% HOOKS_LOAD_SUCCESS 'load' function in hook library %1 returned success
+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 'load' function in hook library %1 returned error %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_LOAD_EXCEPTION 'load' function in hook library %1 threw an exception
+A "load" function was found in the library named in the message and
+was called.  The function threw an exception (an error indication)
+during execution, which is an error condition.  The library has been
+unloaded and no callouts from it will be installed.
+
+% HOOKS_LIBRARY_LOADING loading hooks library %1
+This is a debug message output just before the specified library is loaded.
+If the action is successfully, it will be followed by the
+HOOKS_LIBRARY_LOADED informational message.
+
+% 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_UNLOAD no 'unload' function found in hook library %1
+This is a debug message issued when the library is being unloaded.
+It merely states that the library did not contain an "unload" function.
+
+% 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 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.
+
+% HOOKS_OPEN_ERROR failed to open hook library %1: %2
+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_CALLOUT_REGISTRATION hooks library with index %1 registering callout for hook '%2'
+This is a debug message, output when a library (whose index in the list
+of libraries (being) loaded is given) registers a callout.
+
+% HOOKS_HOOK_REGISTERED hook %1 was registered
+This is a debug message indicating that a hook of the specified name
+was registered by a BIND 10 server.  The server doing the logging is
+indicated by the full name of the logger used to log this message.
+
+% HOOKS_STD_CALLOUT_REGISTERED hooks library %1 registered standard callout for hook %2 at address %3
+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.  The address of the callout is indicated.
+
+% HOOKS_HOOK_LIST_RESET the list of hooks has been reset
+This is a message indicating that the list of hooks has been reset.
+While this is usual when running the BIND 10 test suite, it should not be
+seen when running BIND 10 in a producion environment.  If this appears,
+please report a bug through the usual channels.
+
+% HOOKS_UNLOAD_SUCCESS 'unload' function in hook library %1 returned success
+This is a debug message issued when an "unload" function has been found
+in a hook library during the unload process, called, and returned success.
+
+% HOOKS_UNLOAD_ERROR 'unload' function in hook library %1 returned error %2
+During the unloading of a library, an "unload" function was found.
+It was called, but returned an error (non-zero) status, resulting in
+the issuing of this message.  The unload process continued after this
+message and the library has been unloaded.
+
+% HOOKS_UNLOAD_EXCEPTION 'unload' function in hook library %1 threw an exception
+During the unloading of a library, an "unload" function was found.  It was
+called, but in the process generated an exception (an error indication).
+The unload process continued after this message and the library has
+been unloaded.
+
+% HOOKS_LIBRARY_UNLOADING unloading library %1
+This is a debug message called when the specified library is
+being unloaded.  If all is successful, it will be followed by the
+HOOKS_LIBRARY_UNLOADED informational message.
+
+% HOOKS_VERSION_EXCEPTION 'version' function in hook library %1 threw an exception
+This error message is issued if the version() function in the specified
+hooks library was called and generated an exception.  The library is
+considered unusable and will not be loaded.

+ 76 - 0
src/lib/hooks/library_handle.cc

@@ -0,0 +1,76 @@
+// 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/callout_manager.h>
+#include <hooks/library_handle.h>
+
+namespace isc {
+namespace hooks {
+
+// Callout manipulation - all deferred to the CalloutManager.
+
+void
+LibraryHandle::registerCallout(const std::string& name, CalloutPtr callout) {
+    // Reset library index if required, saving the current value.
+    int saved_index = callout_manager_->getLibraryIndex();
+    if (index_ >= 0) {
+        callout_manager_->setLibraryIndex(index_);
+    }
+
+    // Register the callout.
+    callout_manager_->registerCallout(name, callout);
+
+    // Restore the library index if required.  We know that the saved index
+    // is valid for the number of libraries (or is -1, which is an internal
+    // state indicating there is no current library index) as we obtained it
+    // from the callout manager.
+    if (index_ >= 0) {
+        callout_manager_->setLibraryIndex(saved_index);
+    }
+}
+
+bool
+LibraryHandle::deregisterCallout(const std::string& name, CalloutPtr callout) {
+    int saved_index = callout_manager_->getLibraryIndex();
+    if (index_ >= 0) {
+        callout_manager_->setLibraryIndex(index_);
+    }
+
+    bool status = callout_manager_->deregisterCallout(name, callout);
+
+    if (index_ >= 0) {
+        callout_manager_->setLibraryIndex(saved_index);
+    }
+
+    return (status);
+}
+
+bool
+LibraryHandle::deregisterAllCallouts(const std::string& name) {
+    int saved_index = callout_manager_->getLibraryIndex();
+    if (index_ >= 0) {
+        callout_manager_->setLibraryIndex(index_);
+    }
+
+    bool status = callout_manager_->deregisterAllCallouts(name);
+
+    if (index_ >= 0) {
+        callout_manager_->setLibraryIndex(saved_index);
+    }
+
+    return (status);
+}
+
+} // namespace util
+} // namespace isc

+ 36 - 2
src/lib/util/hooks/library_handle.h

@@ -18,7 +18,7 @@
 #include <string>
 
 namespace isc {
-namespace util {
+namespace hooks {
 
 // Forward declarations
 class CalloutHandle;
@@ -58,7 +58,18 @@ public:
     ///        object.  Note that the "raw" pointer is safe - the only
     ///        instance of the LibraryHandle in the system is as a member of
     ///        the CalloutManager to which it points.
-    LibraryHandle(CalloutManager* manager) : callout_manager_(manager)
+    ///
+    /// @param index Index of the library to which the LibraryHandle applies.
+    ///        If negative, the library index as set in the CalloutManager is
+    ///        used.  Otherwise the current library index is saved, this value
+    ///        is set as the current index, the registration call is made, and
+    ///        the CalloutManager's library index is reset.  Note: although -1
+    ///        is a valid argument value for
+    ///        isc::hooks::CalloutManager::setLibraryIndex(), in this class is
+    ///        is used as a sentinel to indicate that the library index in
+    ///        isc::util::CalloutManager should not be set or reset.
+    LibraryHandle(CalloutManager* manager, int index = -1)
+        : callout_manager_(manager), index_(index)
     {}
 
     /// @brief Register a callout on a hook
@@ -105,8 +116,31 @@ public:
     bool deregisterAllCallouts(const std::string& name);
 
 private:
+    /// @brief Copy constructor
+    ///
+    /// Private (with no implementation) as it makes no sense to copy an object
+    /// of this type.  All code receives a reference to an existing handle which
+    /// is tied to a particular CalloutManager.  Creating a copy of that handle
+    /// runs the risk of a "dangling pointer" to the original handle's callout
+    /// manager.
+    ///
+    /// @param Unused - should be the object to copy.
+    LibraryHandle(const LibraryHandle&);
+
+    /// @brief Assignment operator
+    ///
+    /// Declared private like the copy constructor for the same reasons. It too
+    /// has no implementation.
+    ///
+    /// @param Unused - should be the object to copy.
+    LibraryHandle& operator=(const LibraryHandle&);
+
     /// Back pointer to the collection object for the library
     CalloutManager* callout_manager_;
+
+    /// Library index to which this handle applies.  -1 indicates that it
+    /// applies to whatever index is current in the CalloutManager.
+    int index_;
 };
 
 } // namespace util

+ 292 - 0
src/lib/hooks/library_manager.cc

@@ -0,0 +1,292 @@
+// 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/pointer_converter.h>
+#include <hooks/server_hooks.h>
+
+#include <string>
+#include <vector>
+
+#include <dlfcn.h>
+
+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_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());
+        }
+    }
+
+    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 = BIND10_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 == BIND10_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(BIND10_HOOKS_VERSION);
+        }
+    } else {
+        LOG_ERROR(hooks_logger, HOOKS_NO_VERSION).arg(library_name_);
+    }
+
+    return (false);
+}
+
+// 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 (int 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 (...) {
+            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::runUnload() {
+
+    // Get the pointer to the "load" function.
+    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())();
+        } catch (...) {
+            // Exception generated.  Note a warning as the unload will occur
+            // anyway.
+            LOG_WARN(hooks_logger, HOOKS_UNLOAD_EXCEPTION).arg(library_name_);
+            return (false);
+        }
+
+        if (status != 0) {
+            LOG_ERROR(hooks_logger, HOOKS_UNLOAD_ERROR).arg(library_name_)
+                      .arg(status);
+            return (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_);
+    }
+
+    return (true);
+}
+
+// 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()) {
+
+        // Library opened OK, see if a version function is present and if so,
+        // check what value it returns.
+        if (checkVersion()) {
+
+            // 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>(unloadLibrary());
+            }
+        }
+
+        // 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.
+
+bool
+LibraryManager::unloadLibrary() {
+    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.
+    bool result = runUnload();
+
+    // Regardless of status, remove all callouts associated with this library
+    // on all hooks.
+    vector<string> hooks = ServerHooks::getServerHooks().getHookNames();
+    manager_->setLibraryIndex(index_);
+    for (int i = 0; i < hooks.size(); ++i) {
+        bool removed = manager_->deregisterAllCallouts(hooks[i]);
+        if (removed) {
+            LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_REMOVED)
+                .arg(hooks[i]).arg(library_name_);
+        }
+    }
+
+    // ... 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);
+}
+
+} // namespace hooks
+} // namespace isc

+ 190 - 0
src/lib/hooks/library_manager.h

@@ -0,0 +1,190 @@
+// 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.
+
+#ifndef LIBRARY_MANAGER_H
+#define LIBRARY_MANAGER_H
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+
+namespace isc {
+namespace hooks {
+
+class CalloutManager;
+class LibraryHandle;
+class LibraryManager;
+
+/// @brief Library manager
+///
+/// This class handles the loading and unloading of a specific library.
+///
+/// On loading, it opens the library using dlopen and checks the version (set
+/// 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" function (if present) and calls it.
+///
+/// On unload, it calls the "unload" method if present, clears the callouts on
+/// all hooks, and closes the library.
+///
+/// @note Caution needs to be exercised when using the unload method. During
+///       normal 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. If the library is unloaded, this memory may lie
+///       in the virtual address space deleted in that process. (The word "may"
+///       is used, as this could be operating-system specific.) Should this
+///       happen, 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 by a function in the library.
+///
+/// @note 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 {
+public:
+    /// @brief Constructor
+    ///
+    /// Stores the library name.  The actual loading is done in loadLibrary().
+    ///
+    /// @param name Name of the library to load.  This should be an absolute
+    ///        path name.
+    /// @param index Index of this library
+    /// @param manager CalloutManager object
+    LibraryManager(const std::string& name, int index,
+                   const boost::shared_ptr<CalloutManager>& manager)
+        : dl_handle_(NULL), index_(index), manager_(manager),
+          library_name_(name)
+    {}
+
+    /// @brief Destructor
+    ///
+    /// 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>(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.
+    ///
+    /// @return true if the library loaded successfully, false otherwise. In the
+    ///         latter case, the library will be unloaded if possible.
+    bool loadLibrary();
+
+    /// @brief Unloads a library
+    ///
+    /// Calls the libraries "unload" function if present, the closes the
+    /// library.
+    ///
+    /// However, see the caveat in the class header about when it is safe to
+    /// unload libraries.
+    ///
+    /// @return true if the library unloaded successfully, false if an error
+    ///         occurred in the process (most likely the unload() function
+    ///         (if present) returned an error).  Even if an error did occur,
+    ///         the library is closed if possible.
+    bool unloadLibrary();
+
+    /// @brief Return library name
+    ///
+    /// @return Name of this library
+    std::string getName() const {
+        return (library_name_);
+    }
+
+protected:
+    // The following methods are protected as they are accessed in testing.
+
+    /// @brief Open library
+    ///
+    /// Opens the library associated with this LibraryManager.  A message is
+    /// logged on an error.
+    ///
+    /// @return true if the library opened successfully, false otherwise.
+    bool openLibrary();
+
+    /// @brief Close library
+    ///
+    /// Closes the library associated with this LibraryManager.  A message is
+    /// logged on an error.
+    ///
+    /// @return true if the library closed successfully, false otherwise. "true"
+    ///         is also returned if the library were already closed when this
+    ///         method was called.
+    bool closeLibrary();
+
+    /// @brief Check library version
+    ///
+    /// With the library open, accesses the "version()" function and, if
+    /// present, checks the returned value against the hooks version symbol
+    /// for the currently running BIND 10.  The "version()" function is
+    /// mandatory and must be present (and return the correct value) for the
+    /// library to load.
+    ///
+    /// If there is no version() function, or if there is a mismatch in
+    /// version number, a message logged.
+    ///
+    /// @return bool true if the check succeeded
+    bool checkVersion() const;
+
+    /// @brief Register standard callouts
+    ///
+    /// Loops through the list of hook names and searches the library for
+    /// functions with those names.  Any that are found are registered as
+    /// 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();
+
+    /// @brief Run the unload function if present
+    ///
+    /// Searches for the "unload" 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 runUnload();
+
+private:
+    void*       dl_handle_;     ///< Handle returned by dlopen
+    int         index_;         ///< Index associated with this library
+    boost::shared_ptr<CalloutManager> manager_;
+                                ///< Callout manager for registration
+    std::string library_name_;  ///< Name of the library
+
+};
+
+} // namespace hooks
+} // namespace isc
+
+#endif  // LIBRARY_MANAGER_H

+ 114 - 0
src/lib/hooks/library_manager_collection.cc

@@ -0,0 +1,114 @@
+// 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/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/library_manager_collection.h>
+
+namespace isc {
+namespace hooks {
+
+// Return callout manager for the loaded libraries.  This call is only valid
+// after one has been created for the loaded libraries (which includes the
+// case of no loaded libraries).
+//
+// Note that there is no real connection between the callout manager and the
+// libraries, other than it knows the number of libraries so can do sanity
+// checks on values passed to it.  However, this may change in the future,
+// so the hooks framework is written such that a callout manager is used only
+// with the LibraryManagerCollection that created it.  It is also the reason
+// why each LibraryManager contains a pointer to this CalloutManager.
+
+boost::shared_ptr<CalloutManager>
+LibraryManagerCollection::getCalloutManager() const {
+
+    // Only return a pointer if we have a CalloutManager created.
+    if (! callout_manager_) {
+        isc_throw(LoadLibrariesNotCalled, "must load hooks libraries before "
+                  "attempting to retrieve a CalloutManager for them");
+    }
+
+    return (callout_manager_);
+}
+
+// Load a set of libraries
+
+bool
+LibraryManagerCollection::loadLibraries() {
+
+    // Unload libraries if any are loaded.
+    static_cast<void>(unloadLibraries());
+
+    // Create the callout manager.  A pointer to this is maintained by
+    // each library.  Note that the callout manager does not hold any memory
+    // allocated by a library: although a library registers a callout (and so
+    // causes the creation of an entry in the CalloutManager's callout list),
+    // that creation is done by the CalloutManager itself.  The CalloutManager
+    // is created within the server.
+    //
+    // The upshot of this is that it is therefore safe for the CalloutManager
+    // to be deleted after all associated libraries are deleted, hence this
+    // link (LibraryManager -> CalloutManager) is safe.
+    callout_manager_.reset(new CalloutManager(library_names_.size()));
+
+    // Now iterate through the libraries are load them one by one.  We'll
+    for (int i = 0; i < library_names_.size(); ++i) {
+        // Create a pointer to the new library manager.  The index of this
+        // library is determined by the number of library managers currently
+        // loaded: note that the library indexes run from 1 to (number of loaded
+        // libraries).
+        boost::shared_ptr<LibraryManager> manager(
+                new LibraryManager(library_names_[i], lib_managers_.size() + 1,
+                                   callout_manager_));
+
+        // Load the library.  On success, add it to the list of loaded
+        // libraries.  On failure, an error will have been logged and the
+        // library closed.
+        if (manager->loadLibrary()) {
+            lib_managers_.push_back(manager);
+        }
+    }
+
+    // Update the CalloutManager's idea of the number of libraries it is
+    // handling.
+    callout_manager_->setNumLibraries(lib_managers_.size());
+
+    // Get an indication of whether all libraries loaded successfully.
+    bool status = (library_names_.size() == lib_managers_.size());
+
+    // Don't need the library names any more, so free up the space.
+    library_names_.clear();
+
+    return (status);
+}
+
+// Unload the libraries.
+
+void
+LibraryManagerCollection::unloadLibraries() {
+
+    // Delete the library managers in the reverse order to which they were
+    // created, then clear the library manager vector.
+    for (int i = lib_managers_.size() - 1; i >= 0; --i) {
+        lib_managers_[i].reset();
+    }
+    lib_managers_.clear();
+
+    // Get rid of the callout manager. (The other member, the list of library
+    // names, was cleared when the libraries were loaded.)
+    callout_manager_.reset();
+}
+
+} // namespace hooks
+} // namespace isc

+ 133 - 0
src/lib/hooks/library_manager_collection.h

@@ -0,0 +1,133 @@
+// 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.
+
+#ifndef LIBRARY_MANAGER_COLLECTION_H
+#define LIBRARY_MANAGER_COLLECTION_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+/// @brief LoadLibraries not called
+///
+/// Thrown if an attempt is made get a CalloutManager before the libraries
+/// have been loaded.
+class LoadLibrariesNotCalled : public Exception {
+public:
+    LoadLibrariesNotCalled(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+
+// Forward declarations
+class CalloutManager;
+class LibraryManager;
+
+/// @brief Library manager collection
+///
+/// The LibraryManagerCollection class, as the name implies, is responsible for
+/// managing the collection of LibraryManager objects that describe the loaded
+/// libraries.  As such, it converts a single operation (e.g load libraries)
+/// into multiple operations, one per library.  However, the class does more
+/// than that - it provides a single object with which to manage lifetimes.
+///
+/// As described in the LibraryManager documentation, a CalloutHandle may end
+/// up with pointers to memory within the address space of a loaded library.
+/// If the library is unloaded before this address space is deleted, the
+/// deletion of the CalloutHandle may attempt to free memory into the newly-
+/// unmapped address space and cause a segmentation fault.
+///
+/// To prevent this, each CalloutHandle maintains a shared pointer to the
+/// LibraryManagerCollection current when it was created.  In addition, the
+/// containing HooksManager object also maintains a shared pointer to it.  A
+/// a LibraryManagerCollection is never explicitly deleted: when a new set
+/// of libraries is loaded, the HooksManager clears its pointer to the
+/// collection.  The LibraryManagerCollection is only destroyed when all
+/// CallHandle objects referencing it are destroyed.
+///
+/// Note that this does not completely solve the problem - a hook function may
+/// have modified a packet being processed by the server and that packet may
+/// hold a pointer to memory in the library's virtual address space. To avoid
+/// a segmentation fault, that packet needs to free the memory before the
+/// LibraryManagerCollection is destroyed and this places demands on the server
+/// code.  However, the link with the CalloutHandle does at least mean that
+/// authors of server code do not need to be so careful about when they destroy
+/// CalloutHandles.
+
+class LibraryManagerCollection {
+public:
+    /// @brief Constructor
+    ///
+    /// @param List of libraries that this collection will manage.  The order
+    ///        of the libraries is important.
+    LibraryManagerCollection(const std::vector<std::string>& libraries)
+        : library_names_(libraries)
+    {}
+
+    /// @brief Destructor
+    ///
+    /// Unloads all loaded libraries.
+    ~LibraryManagerCollection() {
+        static_cast<void>(unloadLibraries());
+    }
+
+    /// @brief Load libraries
+    ///
+    /// Loads the libraries.  This creates the LibraryManager associated with
+    /// each library and calls its loadLibrary() method.  If a library fails
+    /// to load, the fact is noted but attempts are made to load the remaining
+    /// libraries.
+    bool loadLibraries();
+
+    /// @brief Get callout manager
+    ///
+    /// Returns a callout manager that can be used with this set of loaded
+    /// libraries (even if the number of loaded libraries is zero).  This
+    /// method may only be caslled after loadLibraries() has been called.
+    ///
+    /// @return Pointer to a callout manager for this set of libraries.
+    ///
+    /// @throw LoadLibrariesNotCalled Thrown if this method is called between
+    ///        construction and the time loadLibraries() is called.
+    boost::shared_ptr<CalloutManager> getCalloutManager() const;
+
+protected:
+    /// @brief Unload libraries
+    ///
+    /// Unloads and closes all loaded libraries.  They are unloaded in the
+    /// reverse order to the order in which they were loaded.
+    void unloadLibraries();
+
+private:
+
+    /// Vector of library names
+    std::vector<std::string>                        library_names_;
+
+    /// Vector of library managers
+    std::vector<boost::shared_ptr<LibraryManager> > lib_managers_;
+
+    /// Callout manager to be associated with the libraries
+    boost::shared_ptr<CalloutManager>               callout_manager_;
+};
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // LIBRARY_MANAGER_COLLECTION_H

+ 121 - 0
src/lib/hooks/pointer_converter.h

@@ -0,0 +1,121 @@
+// 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.
+
+#ifndef POINTER_CONVERTER_H
+#define POINTER_CONVERTER_H
+
+#include <hooks/hooks.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Local class for conversion of void pointers to function pointers
+///
+/// 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.  A
+/// union is declared (and zeroed) and the appropriate member extracted when
+/// needed.
+
+class PointerConverter {
+public:
+    /// @brief Constructor
+    ///
+    /// Zeroes the union and stores the void* pointer we wish to convert (the
+    /// one returned by dlsym).
+    ///
+    /// @param dlsym_ptr void* pointer returned by call to dlsym()
+    PointerConverter(void* dlsym_ptr) {
+        memset(&pointers_, 0, sizeof(pointers_));
+        pointers_.dlsym_ptr = dlsym_ptr;
+    }
+
+    /// @brief Constructor
+    ///
+    /// Zeroes the union and stores the CalloutPtr pointer we wish to convert.
+    /// This constructor is used in debug messages; output of a pointer to
+    /// an object (including to a function) is, on some compilers, printed as
+    /// "1".
+    ///
+    /// @param callout_ptr Pointer to callout function
+    PointerConverter(CalloutPtr callout_ptr) {
+        memset(&pointers_, 0, sizeof(pointers_));
+        pointers_.callout_ptr = callout_ptr;
+    }
+
+    /// @name Pointer accessor functions
+    ///
+    /// It is up to the caller to ensure that the correct member is called so
+    /// that the correct type of pointer is returned.
+    ///
+    ///@{
+
+    /// @brief Return pointer returned by dlsym call
+    ///
+    /// @return void* pointer returned by the call to dlsym().  This can be
+    ///         used in statements that print the hexadecimal value of the
+    ///         symbol.
+    void* dlsymPtr() const {
+        return (pointers_.dlsym_ptr);
+    }
+
+    /// @brief Return pointer to callout function
+    ///
+    /// @return Pointer to the callout function
+    CalloutPtr calloutPtr() const {
+        return (pointers_.callout_ptr);
+    }
+
+    /// @brief Return pointer to load function
+    ///
+    /// @return Pointer to the load function
+    load_function_ptr loadPtr() const {
+        return (pointers_.load_ptr);
+    }
+
+    /// @brief Return pointer to unload function
+    ///
+    /// @return Pointer to the unload function
+    unload_function_ptr unloadPtr() const {
+        return (pointers_.unload_ptr);
+    }
+
+    /// @brief Return pointer to version function
+    ///
+    /// @return Pointer to the version function
+    version_function_ptr versionPtr() const {
+        return (pointers_.version_ptr);
+    }
+
+    ///@}
+
+private:
+
+    /// @brief Union linking void* and pointers to functions.
+    union {
+        void*                   dlsym_ptr;      // void* returned by dlsym
+        CalloutPtr              callout_ptr;    // Pointer to callout
+        load_function_ptr       load_ptr;       // Pointer to load function
+        unload_function_ptr     unload_ptr;     // Pointer to unload function
+        version_function_ptr    version_ptr;    // Pointer to version function
+    } pointers_;
+};
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // POINTER_CONVERTER_H

+ 57 - 38
src/lib/util/hooks/server_hooks.cc

@@ -13,7 +13,8 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <exceptions/exceptions.h>
-#include <util/hooks/server_hooks.h>
+#include <hooks/hooks_log.h>
+#include <hooks/server_hooks.h>
 
 #include <utility>
 #include <vector>
@@ -22,22 +23,13 @@ using namespace std;
 using namespace isc;
 
 namespace isc {
-namespace util {
+namespace hooks {
 
 // Constructor - register the pre-defined hooks and check that the indexes
 // assigned to them are as expected.
 
 ServerHooks::ServerHooks() {
-    int create = registerHook("context_create");
-    int destroy = registerHook("context_destroy");
-
-    if ((create != CONTEXT_CREATE) || (destroy != CONTEXT_DESTROY)) {
-        isc_throw(Unexpected, "pre-defined hook indexes are not as expected. "
-                  "context_create: expected = " << CONTEXT_CREATE <<
-                  ", actual = " << create <<
-                  ". context_destroy: expected = " << CONTEXT_DESTROY <<
-                  ", actual = " << destroy);
-    }
+    reset();
 }
 
 // Register a hook.  The index assigned to the hook is the current number
@@ -59,10 +51,57 @@ ServerHooks::registerHook(const string& name) {
                   " is already registered");
     }
 
-    // New element inserted, return numeric index.
+    // Element was inserted, so add to the inverse hooks collection.
+    inverse_hooks_[index] = name;
+
+    // Log it if debug is enabled
+    LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_HOOK_REGISTERED).arg(name);
+
+    // ... and return numeric index.
     return (index);
 }
 
+// Reset ServerHooks object to initial state.
+
+void
+ServerHooks::reset() {
+
+    // Clear out the name->index and index->name maps.
+    hooks_.clear();
+    inverse_hooks_.clear();
+
+    // Register the pre-defined hooks.
+    int create = registerHook("context_create");
+    int destroy = registerHook("context_destroy");
+
+    // Check registration went as expected.
+    if ((create != CONTEXT_CREATE) || (destroy != CONTEXT_DESTROY)) {
+        isc_throw(Unexpected, "pre-defined hook indexes are not as expected. "
+                  "context_create: expected = " << CONTEXT_CREATE <<
+                  ", actual = " << create <<
+                  ". context_destroy: expected = " << CONTEXT_DESTROY <<
+                  ", actual = " << destroy);
+    }
+
+    // Log a warning - although this is done during testing, it should never be
+    // seen in a production system.
+    LOG_WARN(hooks_logger, HOOKS_HOOK_LIST_RESET);
+}
+
+// Find the name associated with a hook index.
+
+std::string
+ServerHooks::getName(int index) const {
+
+    // Get iterator to matching element.
+    InverseHookCollection::const_iterator i = inverse_hooks_.find(index);
+    if (i == inverse_hooks_.end()) {
+        isc_throw(NoSuchHook, "hook index " << index << " is not recognised");
+    }
+
+    return (i->second);
+}
+
 // Find the index associated with a hook name.
 
 int
@@ -92,34 +131,14 @@ ServerHooks::getHookNames() const {
     return (names);
 }
 
-// Hook registration function methods
-
-// Access the hook registration function vector itself
-
-std::vector<HookRegistrationFunction::RegistrationFunctionPtr>&
-HookRegistrationFunction::getFunctionVector() {
-    static std::vector<RegistrationFunctionPtr> reg_functions;
-    return (reg_functions);
-}
-
-// Constructor - add a registration function to the function vector
+// Return global ServerHooks object
 
-HookRegistrationFunction::HookRegistrationFunction(
-    HookRegistrationFunction::RegistrationFunctionPtr reg_func) {
-    getFunctionVector().push_back(reg_func);
+ServerHooks&
+ServerHooks::getServerHooks() {
+    static ServerHooks hooks;
+    return (hooks);
 }
 
-// Execute all registered registration functions
-
-void
-HookRegistrationFunction::execute(ServerHooks& hooks) {
-    std::vector<RegistrationFunctionPtr>& reg_functions = getFunctionVector();
-    for (int i = 0; i < reg_functions.size(); ++i) {
-        (*reg_functions[i])(hooks);
-    }
-}
-
-
 
 } // namespace util
 } // namespace isc

+ 50 - 84
src/lib/util/hooks/server_hooks.h

@@ -17,12 +17,14 @@
 
 #include <exceptions/exceptions.h>
 
+#include <boost/noncopyable.hpp>
+
 #include <map>
 #include <string>
 #include <vector>
 
 namespace isc {
-namespace util {
+namespace hooks {
 
 /// @brief Duplicate hook
 ///
@@ -57,24 +59,27 @@ public:
 /// (Although it would be feasible to use a name as an index, using an integer
 /// will speed up the time taken to locate the callouts, which may make a
 /// difference in a frequently-executed piece of code.)
+///
+/// ServerHooks is a singleton object and is only accessible by the static
+/// method getServerHooks().
 
-class ServerHooks {
+class ServerHooks : public boost::noncopyable {
 public:
 
     /// Index numbers for pre-defined hooks.
     static const int CONTEXT_CREATE = 0;
     static const int CONTEXT_DESTROY = 1;
 
-    /// @brief Constructor
+    /// @brief Reset to Initial State
     ///
-    /// This pre-registers two hooks, context_create and context_destroy, which
-    /// are called by the server before processing a packet and after processing
-    /// for the packet has completed.  They allow the server code to allocate
-    /// and destroy per-packet context.
+    /// Resets the collection of hooks to the initial state, with just the
+    /// context_create and context_destroy hooks set.  This used during
+    /// construction. It is also used during testing to reset the global
+    /// ServerHooks object.
     ///
     /// @throws isc::Unexpected if the registration of the pre-defined hooks
     ///         fails in some way.
-    ServerHooks();
+    void reset();
 
     /// @brief Register a hook
     ///
@@ -90,6 +95,18 @@ public:
     ///         registered.
     int registerHook(const std::string& name);
 
+    /// @brief Get hook name
+    ///
+    /// Returns the name of a hook given the index.  This is most likely to be
+    /// used in log messages.
+    ///
+    /// @param index Index of the hoold
+    ///
+    /// @return Name of the hook.
+    ///
+    /// @throw NoSuchHook if the hook index is invalid.
+    std::string getName(int index) const;
+
     /// @brief Get hook index
     ///
     /// Returns the index of a hook.
@@ -117,88 +134,37 @@ public:
     /// @return Vector of strings holding hook names.
     std::vector<std::string> getHookNames() const;
 
-private:
-    typedef std::map<std::string, int> HookCollection;
-
-    HookCollection  hooks_;     ///< Hook name/index collection
-};
-
-
-/// @brief Hooks Registration
-///
-/// All hooks must be registered before libraries are loaded and callouts
-/// assigned to them.  One way of doing this is to have a global list of hooks:
-/// the addition of any hook anywhere would require updating the list. This
-/// is possible and, if desired, the author of a server can do it.
-///
-/// An alternative is the option provided here, where each component of BIND 10
-/// registers the hooks they are using.  To do this, the component should
-/// create a hook registration function of the form:
-///
-/// @code
-/// static int hook1_num = -1;  // Initialize number for hook 1
-/// static int hook2_num = -1;  // Initialize number for hook 2
-///
-/// void myModuleRegisterHooks(ServerHooks& hooks) {
-///     hook1_num = hooks.registerHook("hook1");
-///     hook2_num = hooks.registerHook("hook2");
-/// }
-/// @endcode
-///
-/// ... which registers the hooks and stores the associated hook index. To
-/// avoid the need to add an explicit call to each of the hook registration
-/// functions to the server initialization code, the component should declare
-/// an object of this class in the same file as the registration function,
-/// but outside of any function.  The declaration should include the name
-/// of the registration function, i.e.
-///
-/// @code
-/// HookRegistrationFunction f(myModuleRegisterHooks);
-/// @code
-///
-/// The constructor of this object will run prior to main() getting called and
-/// will add the registration function to a list of such functions.  The server
-/// then calls the static class method "execute()" to run all the declared
-/// registration functions.
-
-class HookRegistrationFunction {
-public:
-    /// @brief Pointer to a hook registration function
-    typedef void (*RegistrationFunctionPtr)(ServerHooks&);
-
-    /// @brief Constructor
+    /// @brief Return ServerHooks object
     ///
-    /// For variables declared outside functions or methods, the constructors
-    /// are run after the program is loaded and before main() is called. This
-    /// constructor adds the passed pointer to a vector of such pointers.
-    HookRegistrationFunction(RegistrationFunctionPtr reg_func);
-
-    /// @brief Access registration function vector
+    /// Returns the global ServerHooks object.
     ///
-    /// One of the problems with functions run prior to starting main() is the
-    /// "static initialization fiasco".  This occurs because the order in which
-    /// objects outside functions are constructed is not defined.  So if this
-    /// constructor were to depend on a vector declared externally, we would
-    /// not be able to guarantee that the vector had been initialised properly
-    /// before we used it.
+    /// @return Reference to the global ServerHooks object.
+    static ServerHooks& getServerHooks();
+
+private:
+    /// @brief Constructor
     ///
-    /// To get round this situation, the vector is declared statically within
-    /// a static function.  The first time the function is called, the vector
-    /// is initialized before it is used.
+    /// This pre-registers two hooks, context_create and context_destroy, which
+    /// are called by the server before processing a packet and after processing
+    /// for the packet has completed.  They allow the server code to allocate
+    /// and destroy per-packet context.
     ///
-    /// This function returns a reference to the vector used to hold the
-    /// pointers.
+    /// Constructor is declared private to enforce the singleton nature of
+    /// the object.  A reference to the singleton is obtainable through the
+    /// getServerHooks() static method.
     ///
-    /// @return Reference to the (static) list of registration functions
-    static std::vector<RegistrationFunctionPtr>& getFunctionVector();
+    /// @throws isc::Unexpected if the registration of the pre-defined hooks
+    ///         fails in some way.
+    ServerHooks();
 
-    /// @brief Execute registration functions
-    ///
-    /// Called by the server initialization code, this function executes all
-    /// registered hook registration functions.
-    ///
-    /// @param hooks ServerHooks object to which hook information will be added.
-    static void execute(ServerHooks& hooks);
+    /// Useful typedefs.
+    typedef std::map<std::string, int> HookCollection;
+    typedef std::map<int, std::string> InverseHookCollection;
+
+    /// Two maps, one for name->index, the other for index->name.  (This is
+    /// simpler than using a multi-indexed container.)
+    HookCollection  hooks_;                 ///< Hook name/index collection
+    InverseHookCollection inverse_hooks_;   ///< Hook index/name collection
 };
 
 } // namespace util

+ 104 - 0
src/lib/hooks/tests/Makefile.am

@@ -0,0 +1,104 @@
+SUBDIRS = .
+
+AM_CPPFLAGS  = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.     
+AM_CXXFLAGS  = $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# see ../Makefile.am
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+# Files to clean include the file created by testing.
+CLEANFILES = *.gcno *.gcda $(builddir)/marker_file.dat
+
+TESTS_ENVIRONMENT = \
+	$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+# Build shared libraries for testing.
+lib_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la liblecl.la \
+                  libucl.la libfcl.la
+
+# No version function
+libnvl_la_SOURCES  = no_version_library.cc
+libnvl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libnvl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# Incorrect version function
+libivl_la_SOURCES  = incorrect_version_library.cc
+libivl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libivl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# All framework functions throw an exception
+libfxl_la_SOURCES = framework_exception_library.cc
+libfxl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libfxl_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 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)
+
+# The unload callout library - contains an unload function that
+# creates a marker file.
+libucl_la_SOURCES  = unload_callout_library.cc
+libucl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libucl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The full callout library - contains all three framework functions.
+libfcl_la_SOURCES  = full_callout_library.cc
+libfcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libfcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+TESTS += run_unittests
+run_unittests_SOURCES  = run_unittests.cc
+run_unittests_SOURCES += callout_handle_unittest.cc
+run_unittests_SOURCES += callout_manager_unittest.cc
+run_unittests_SOURCES += common_test_class.h
+run_unittests_SOURCES += handles_unittest.cc
+run_unittests_SOURCES += hooks_manager_unittest.cc
+run_unittests_SOURCES += library_manager_collection_unittest.cc
+run_unittests_SOURCES += library_manager_unittest.cc
+run_unittests_SOURCES += server_hooks_unittest.cc
+
+nodist_run_unittests_SOURCES  = marker_file.h
+nodist_run_unittests_SOURCES += test_libraries.h
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
+if USE_STATIC_LINK
+# If specified, only link unit tests static - the test libraries must be
+# build as shared libraries.
+run_unittests_LDFLAGS  += -static
+endif
+
+run_unittests_LDADD    = $(AM_LDADD)    $(GTEST_LDADD)
+
+run_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
+
+EXTRA_DIST = marker_file.h.in test_libraries.h.in

+ 115 - 0
src/lib/hooks/tests/basic_callout_library.cc

@@ -0,0 +1,115 @@
+// 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 callout library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager).  The characteristics of the library produced from this
+/// file are:
+///
+/// - Only the "version" framework function is supplied.
+///
+/// - A context_create callout is supplied.
+///
+/// - Three "standard" callouts are supplied corresponding to the hooks
+///   "hookpt_one", "hookpt_two", "hookpt_three".  All do some trivial calculations
+///   on the arguments supplied to it and the context variables, returning
+///   intermediate results through the "result" argument. The result of
+///   executing all four callouts in order 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 hookpt_one, data_2 to
+///   hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <hooks/hooks.h>
+#include <fstream>
+
+using namespace isc::hooks;
+using namespace std;
+
+extern "C" {
+
+// Callouts.  All return their result through the "result" argument.
+
+int
+context_create(CalloutHandle& handle) {
+    handle.setContext("result", static_cast<int>(10));
+    handle.setArgument("result", static_cast<int>(10));
+    return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 10. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+    int data;
+    handle.getArgument("data_1", data);
+
+    int result;
+    handle.getArgument("result", result);
+
+    result += data;
+    handle.setArgument("result", result);
+
+    return (0);
+}
+
+// Second callout multiplies the current context value by the "data_2"
+// argument.
+
+int
+hookpt_two(CalloutHandle& handle) {
+    int data;
+    handle.getArgument("data_2", data);
+
+    int result;
+    handle.getArgument("result", result);
+
+    result *= data;
+    handle.setArgument("result", result);
+
+    return (0);
+}
+
+// Final callout subtracts the result in "data_3".
+
+int
+hookpt_three(CalloutHandle& handle) {
+    int data;
+    handle.getArgument("data_3", data);
+
+    int result;
+    handle.getArgument("result", result);
+
+    result -= data;
+    handle.setArgument("result", result);
+
+    return (0);
+}
+
+// Framework functions.  Only version() is supplied here.
+
+int
+version() {
+    return (BIND10_HOOKS_VERSION);
+}
+
+};
+

+ 11 - 11
src/lib/util/tests/callout_handle_unittest.cc

@@ -12,16 +12,16 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <util/hooks/callout_handle.h>
-#include <util/hooks/callout_manager.h>
-#include <util/hooks/library_handle.h>
-#include <util/hooks/server_hooks.h>
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
 
 #include <boost/shared_ptr.hpp>
 
 #include <gtest/gtest.h>
 
-using namespace isc::util;
+using namespace isc::hooks;
 using namespace std;
 
 namespace {
@@ -40,8 +40,7 @@ public:
     /// Sets up a callout manager to be referenced by the CalloutHandle in
     /// these tests. (The "4" for the number of libraries in the
     /// CalloutManager is arbitrary - it is not used in these tests.)
-    CalloutHandleTest()
-        : hooks_(new ServerHooks()), manager_(new CalloutManager(hooks_, 4))
+    CalloutHandleTest() : manager_(new CalloutManager(4))
     {}
 
     /// Obtain hook manager
@@ -50,9 +49,6 @@ public:
     }
 
 private:
-    /// List of server hooks
-    boost::shared_ptr<ServerHooks> hooks_;
-
     /// Callout manager accessed by this CalloutHandle.
     boost::shared_ptr<CalloutManager> manager_;
 };
@@ -87,7 +83,7 @@ TEST_F(CalloutHandleTest, ArgumentDistinctSimpleType) {
     EXPECT_EQ(142, d);
 
     // Add a short (random value).
-    short e = -81; 
+    short e = -81;
     handle.setArgument("short", e);
     EXPECT_EQ(-81, e);
 
@@ -326,4 +322,8 @@ TEST_F(CalloutHandleTest, SkipFlag) {
     EXPECT_FALSE(handle.getSkip());
 }
 
+// Further tests of the "skip" flag and tests of getting the name of the
+// hook to which the current callout is attached is in the "handles_unittest"
+// module.
+
 } // Anonymous namespace

+ 165 - 44
src/lib/util/tests/callout_manager_unittest.cc

@@ -13,15 +13,16 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <exceptions/exceptions.h>
-#include <util/hooks/callout_handle.h>
-#include <util/hooks/callout_manager.h>
-#include <util/hooks/library_handle.h>
-#include <util/hooks/server_hooks.h>
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
 
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
 #include <algorithm>
+#include <climits>
 #include <string>
 #include <vector>
 
@@ -34,7 +35,7 @@
 /// structure for the tests is common.
 
 using namespace isc;
-using namespace isc::util;
+using namespace isc::hooks;
 using namespace std;
 
 namespace {
@@ -44,17 +45,20 @@ public:
     /// @brief Constructor
     ///
     /// Sets up a collection of three LibraryHandle objects to use in the test.
-    CalloutManagerTest() : hooks_(new ServerHooks()) {
+    CalloutManagerTest() {
 
-        // Set up the server hooks
-        alpha_index_ = hooks_->registerHook("alpha");
-        beta_index_ = hooks_->registerHook("beta");
-        gamma_index_ = hooks_->registerHook("gamma");
-        delta_index_ = hooks_->registerHook("delta");
+        // Set up the server hooks.  There is sone singleton for all tests,
+        // so reset it and explicitly set up the hooks for the test.
+        ServerHooks& hooks = ServerHooks::getServerHooks();
+        hooks.reset();
+        alpha_index_ = hooks.registerHook("alpha");
+        beta_index_ = hooks.registerHook("beta");
+        gamma_index_ = hooks.registerHook("gamma");
+        delta_index_ = hooks.registerHook("delta");
 
         // Set up the callout manager with these hooks.  Assume a maximum of
         // four libraries.
-        callout_manager_.reset(new CalloutManager(hooks_, 10));
+        callout_manager_.reset(new CalloutManager(10));
 
         // Set up the callout handle.
         callout_handle_.reset(new CalloutHandle(callout_manager_));
@@ -73,10 +77,6 @@ public:
         return (callout_manager_);
     }
 
-    boost::shared_ptr<ServerHooks> getServerHooks() {
-        return (hooks_);
-    }
-
     /// Static variable used for accumulating information
     static int callout_value_;
 
@@ -93,9 +93,6 @@ private:
 
     /// Callout manager used for the test
     boost::shared_ptr<CalloutManager> callout_manager_;
-
-    /// Server hooks
-    boost::shared_ptr<ServerHooks> hooks_;
 };
 
 // Definition of the static variable.
@@ -181,39 +178,55 @@ TEST_F(CalloutManagerTest, BadConstructorParameters) {
     boost::scoped_ptr<CalloutManager> cm;
 
     // Invalid number of libraries
-    EXPECT_THROW(cm.reset(new CalloutManager(getServerHooks(), 0)), BadValue);
-    EXPECT_THROW(cm.reset(new CalloutManager(getServerHooks(), -1)), BadValue);
-
-    // Invalid server hooks pointer.
-    boost::shared_ptr<ServerHooks> sh;
-    EXPECT_THROW(cm.reset(new CalloutManager(sh, 4)), BadValue);
+    EXPECT_THROW(cm.reset(new CalloutManager(-1)), BadValue);
 }
 
 // Check the number of libraries is reported successfully.
 
-TEST_F(CalloutManagerTest, GetNumLibraries) {
+TEST_F(CalloutManagerTest, NumberOfLibraries) {
     boost::scoped_ptr<CalloutManager> cm;
 
     // Check two valid values of number of libraries to ensure that the
     // GetNumLibraries() returns the value set.
-    EXPECT_NO_THROW(cm.reset(new CalloutManager(getServerHooks(), 4)));
+    EXPECT_NO_THROW(cm.reset(new CalloutManager()));
+    EXPECT_EQ(0, cm->getNumLibraries());
+
+    EXPECT_NO_THROW(cm.reset(new CalloutManager(0)));
+    EXPECT_EQ(0, cm->getNumLibraries());
+
+    EXPECT_NO_THROW(cm.reset(new CalloutManager(4)));
     EXPECT_EQ(4, cm->getNumLibraries());
 
-    EXPECT_NO_THROW(cm.reset(new CalloutManager(getServerHooks(), 42)));
+    EXPECT_NO_THROW(cm.reset(new CalloutManager(42)));
     EXPECT_EQ(42, cm->getNumLibraries());
+
+    // Check that setting the number of libraries alterns the number reported.
+    EXPECT_NO_THROW(cm->setNumLibraries(27));
+    EXPECT_EQ(27, cm->getNumLibraries());
 }
 
 // Check that we can only set the current library index to the correct values.
 
 TEST_F(CalloutManagerTest, CheckLibraryIndex) {
-    // Check valid indexes
-    for (int i = 0; i < 4; ++i) {
+    // Check valid indexes.  As the callout manager is sized for 10 libraries,
+    // we expect:
+    //
+    // -1 to be valid as it is the standard "invalid" value.
+    // 0 to be valid for the pre-user library callouts
+    // 1-10 to be valid for the user-library callouts
+    // INT_MAX to be valid for the post-user library callouts
+    //
+    // All other values to be invalid.
+    for (int i = -1; i < 11; ++i) {
         EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(i));
+        EXPECT_EQ(i, getCalloutManager()->getLibraryIndex());
     }
+    EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(INT_MAX));
+    EXPECT_EQ(INT_MAX, getCalloutManager()->getLibraryIndex());
 
     // Check invalid ones
-    EXPECT_THROW(getCalloutManager()->setLibraryIndex(-1), NoSuchLibrary);
-    EXPECT_THROW(getCalloutManager()->setLibraryIndex(15), NoSuchLibrary);
+    EXPECT_THROW(getCalloutManager()->setLibraryIndex(-2), NoSuchLibrary);
+    EXPECT_THROW(getCalloutManager()->setLibraryIndex(11), NoSuchLibrary);
 }
 
 // Check that we can only register callouts on valid hook names.
@@ -245,7 +258,7 @@ TEST_F(CalloutManagerTest, RegisterCallout) {
     EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Check that calling the callouts returns as expected. (This is also a
     // test of the callCallouts method.)
     callout_value_ = 0;
@@ -299,7 +312,7 @@ TEST_F(CalloutManagerTest, CalloutsPresent) {
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Set up so that hooks "alpha", "beta" and "delta" have callouts attached
     // to them, and callout  "gamma" does not. (In the statements below, the
     // exact callouts attached to a hook are not relevant - only the fact
@@ -335,7 +348,7 @@ TEST_F(CalloutManagerTest, CallNoCallouts) {
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Call the callouts on an arbitrary hook and ensure that nothing happens.
     callout_value_ = 475;
     getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
@@ -352,7 +365,7 @@ TEST_F(CalloutManagerTest, CallCalloutsSuccess) {
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Each library contributes one callout on hook "alpha".
     callout_value_ = 0;
     getCalloutManager()->setLibraryIndex(1);
@@ -396,7 +409,7 @@ TEST_F(CalloutManagerTest, CallCalloutsError) {
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Each library contributing one callout on hook "alpha". The first callout
     // returns an error (after adding its value to the result).
     callout_value_ = 0;
@@ -468,7 +481,7 @@ TEST_F(CalloutManagerTest, DeregisterSingleCallout) {
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Add a callout to hook "alpha" and check it is added correctly.
     callout_value_ = 0;
     getCalloutManager()->setLibraryIndex(0);
@@ -494,7 +507,7 @@ TEST_F(CalloutManagerTest, DeregisterSingleCalloutSameLibrary) {
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Add multiple callouts to hook "alpha".
     callout_value_ = 0;
     getCalloutManager()->setLibraryIndex(0);
@@ -530,7 +543,7 @@ TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) {
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Each library contributes one callout on hook "alpha".
     callout_value_ = 0;
     getCalloutManager()->setLibraryIndex(0);
@@ -586,7 +599,7 @@ TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsMultipleLibraries) {
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Each library contributes two callouts to hook "alpha".
     callout_value_ = 0;
     getCalloutManager()->setLibraryIndex(0);
@@ -615,7 +628,7 @@ TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsMultipleLibraries) {
 TEST_F(CalloutManagerTest, DeregisterAllCallouts) {
     // Ensure that no callouts are attached to hook one.
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
-                 
+
     // Each library contributes two callouts to hook "alpha".
     callout_value_ = 0;
     getCalloutManager()->setLibraryIndex(0);
@@ -655,7 +668,7 @@ TEST_F(CalloutManagerTest, MultipleCalloutsLibrariesHooks) {
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Register callouts on the alpha hook.
     callout_value_ = 0;
     getCalloutManager()->setLibraryIndex(0);
@@ -731,7 +744,7 @@ TEST_F(CalloutManagerTest, LibraryHandleRegistration) {
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-                 
+
     // Check that calling the callouts returns as expected. (This is also a
     // test of the callCallouts method.)
     callout_value_ = 0;
@@ -760,6 +773,114 @@ TEST_F(CalloutManagerTest, LibraryHandleRegistration) {
     EXPECT_EQ(1, callout_value_);
 }
 
+// A repeat of the test above, but using the alternate constructor for the
+// LibraryHandle.
+TEST_F(CalloutManagerTest, LibraryHandleAlternateConstructor) {
+    // Ensure that no callouts are attached to any of the hooks.
+    EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+    // Set up so that hooks "alpha" and "beta" have callouts attached from a
+    // different libraries.
+    LibraryHandle lh0(getCalloutManager().get(), 0);
+    lh0.registerCallout("alpha", callout_one);
+    lh0.registerCallout("alpha", callout_two);
+
+    LibraryHandle lh1(getCalloutManager().get(), 1);
+    lh1.registerCallout("alpha", callout_three);
+    lh1.registerCallout("alpha", callout_four);
+
+    // Check all is as expected.
+    EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+    EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+    EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+    EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+    // Check that calling the callouts returns as expected. (This is also a
+    // test of the callCallouts method.)
+    callout_value_ = 0;
+    getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+    EXPECT_EQ(1234, callout_value_);
+
+    // Deregister a callout on library index 0 (after we check we can't
+    // deregister it through library index 1).
+    EXPECT_FALSE(lh1.deregisterCallout("alpha", callout_two));
+    callout_value_ = 0;
+    getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+    EXPECT_EQ(1234, callout_value_);
+
+    EXPECT_TRUE(lh0.deregisterCallout("alpha", callout_two));
+    callout_value_ = 0;
+    getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+    EXPECT_EQ(134, callout_value_);
+
+    // Deregister all callouts on library index 1.
+    EXPECT_TRUE(lh1.deregisterAllCallouts("alpha"));
+    callout_value_ = 0;
+    getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+    EXPECT_EQ(1, callout_value_);
+}
+
+// Check that the pre- and post- user callout library handles work
+// appropriately with no user libraries.
+
+TEST_F(CalloutManagerTest, LibraryHandlePrePostNoLibraries) {
+    // Create a local callout manager and callout handle to reflect no libraries
+    // being loaded.
+    boost::shared_ptr<CalloutManager> manager(new CalloutManager(0));
+    CalloutHandle handle(manager);
+
+    // Ensure that no callouts are attached to any of the hooks.
+    EXPECT_FALSE(manager->calloutsPresent(alpha_index_));
+
+    // Setup the pre-and post callouts.
+    manager->getPostLibraryHandle().registerCallout("alpha", callout_four);
+    manager->getPreLibraryHandle().registerCallout("alpha", callout_one);
+    // Check all is as expected.
+    EXPECT_TRUE(manager->calloutsPresent(alpha_index_));
+    EXPECT_FALSE(manager->calloutsPresent(beta_index_));
+    EXPECT_FALSE(manager->calloutsPresent(gamma_index_));
+    EXPECT_FALSE(manager->calloutsPresent(delta_index_));
+
+    // Check that calling the callouts returns as expected.
+    callout_value_ = 0;
+    manager->callCallouts(alpha_index_, handle);
+    EXPECT_EQ(14, callout_value_);
+
+    // Deregister the pre- library callout.
+    EXPECT_TRUE(manager->getPreLibraryHandle().deregisterAllCallouts("alpha"));
+    callout_value_ = 0;
+    manager->callCallouts(alpha_index_, handle);
+    EXPECT_EQ(4, callout_value_);
+}
+
+// Repeat the tests with one user library.
+
+TEST_F(CalloutManagerTest, LibraryHandlePrePostUserLibrary) {
+
+    // Setup the pre-, library and post callouts.
+    getCalloutManager()->getPostLibraryHandle().registerCallout("alpha",
+                                                                callout_four);
+    getCalloutManager()->getPreLibraryHandle().registerCallout("alpha",
+                                                                callout_one);
+
+    // ... and set up a callout in between, on library number 2.
+    LibraryHandle lh1(getCalloutManager().get(), 2);
+    lh1.registerCallout("alpha", callout_five);
+
+    // Check all is as expected.
+    EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+    EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+    EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+    EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+    // Check that calling the callouts returns as expected.
+    callout_value_ = 0;
+    getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+    EXPECT_EQ(154, callout_value_);
+}
 
+// The setting of the hook index is checked in the handles_unittest
+// set of tests, as access restrictions mean it is not easily tested
+// on its own.
 
 } // Anonymous namespace

+ 142 - 0
src/lib/hooks/tests/common_test_class.h

@@ -0,0 +1,142 @@
+// 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.
+
+#ifndef COMMON_HOOKS_TEST_CLASS_H
+#define COMMON_HOOKS_TEST_CLASS_H
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/server_hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+/// @brief Common hooks test class
+///
+/// This class is a shared parent of the test fixture class in the tests of the
+/// higher-level hooks classes (LibraryManager, LibraryManagerCollection and
+/// HooksManager).  It
+///
+/// - sets the the ServerHooks object with three hooks and stores their
+///   indexes.
+/// - executes the callouts (which are assumed to perform a calculation)
+///   and checks the results.
+
+class HooksCommonTestClass {
+public:
+    /// @brief Constructor
+    HooksCommonTestClass() {
+
+        // Set up the server hooks.  ServerHooks is a singleton, so we reset it
+        // between each test.
+        isc::hooks::ServerHooks& hooks =
+            isc::hooks::ServerHooks::getServerHooks();
+        hooks.reset();
+        hookpt_one_index_ = hooks.registerHook("hookpt_one");
+        hookpt_two_index_ = hooks.registerHook("hookpt_two");
+        hookpt_three_index_ = hooks.registerHook("hookpt_three");
+    }
+
+    /// @brief Call callouts test
+    ///
+    /// All of the loaded libraries for which callouts are called register four
+    /// callouts: a context_create callout and three callouts that are attached
+    /// to hooks hookpt_one, hookpt_two and hookpt_three.  These four callouts,
+    /// executed in sequence, perform a series of calculations. Data is passed
+    /// between callouts in the argument list, in a variable named "result".
+    ///
+    /// context_create initializes the calculation by setting a seed
+    /// value, called r0 here.  This value is dependent on the library being
+    /// loaded.  Prior to that, the argument "result" is initialized to -1,
+    /// the purpose being to avoid exceptions when running this test with no
+    /// libraries loaded.
+    ///
+    /// Callout hookpt_one is passed a value d1 and performs a simple arithmetic
+    /// operation on it and r0 yielding a result r1.  Hence we can say that
+    /// @f[ r1 = hookpt_one(r0, d1) @f]
+    ///
+    /// Callout hookpt_two is passed a value d2 and peforms another simple
+    /// arithmetic operation on it and d2, yielding r2, i.e.
+    /// @f[ r2 = hookpt_two(d1, d2) @f]
+    ///
+    /// hookpt_three does a similar operation giving
+    /// @f[ r3 = hookpt_three(r2, d3) @f].
+    ///
+    /// The details of the operations hookpt_one, hookpt_two and hookpt_three
+    /// depend on the library, so the results obtained not only depend on
+    /// the data, but also on the library loaded.  This method is passed both
+    /// data and expected results.  It executes the three callouts in sequence,
+    /// checking the intermediate and final results.  Only if the expected
+    /// library has been loaded correctly and the callouts in it registered
+    /// correctly will be the results be as expected.
+    ///
+    /// It is assumed that callout_manager_ has been set up appropriately.
+    ///
+    /// @note The CalloutHandle used in the calls is declared locally here.
+    ///       The advantage of this (apart from scope reduction) is that on
+    ///       exit, it is destroyed.  This removes any references to memory
+    ///       allocated by loaded libraries while they are still loaded.
+    ///
+    /// @param manager CalloutManager to use for the test
+    /// @param r0...r3, d1..d3 Data (dN) and expected results (rN) - both
+    ///        intermediate and final.  The arguments are ordered so that they
+    ///        appear in the argument list in the order they are used.
+    void executeCallCallouts(
+            const boost::shared_ptr<isc::hooks::CalloutManager>& manager,
+            int r0, int d1, int r1, int d2, int r2, int d3, int r3) {
+        static const char* COMMON_TEXT = " callout returned the wong value";
+        static const char* RESULT = "result";
+
+        int result;
+
+        // Set up a callout handle for the calls.
+        isc::hooks::CalloutHandle handle(manager);
+
+        // Initialize the argument RESULT.  This simplifies testing by
+        // eliminating the generation of an exception when we try the unload
+        // test.  In that case, RESULT is unchanged.
+        handle.setArgument(RESULT, -1);
+
+        // Seed the calculation.
+        manager->callCallouts(isc::hooks::ServerHooks::CONTEXT_CREATE, handle);
+        handle.getArgument(RESULT, result);
+        EXPECT_EQ(r0, result) << "context_create" << COMMON_TEXT;
+
+        // Perform the first calculation.
+        handle.setArgument("data_1", d1);
+        manager->callCallouts(hookpt_one_index_, handle);
+        handle.getArgument(RESULT, result);
+        EXPECT_EQ(r1, result) << "hookpt_one" << COMMON_TEXT;
+
+        // ... the second ...
+        handle.setArgument("data_2", d2);
+        manager->callCallouts(hookpt_two_index_, handle);
+        handle.getArgument(RESULT, result);
+        EXPECT_EQ(r2, result) << "hookpt_two" << COMMON_TEXT;
+
+        // ... and the third.
+        handle.setArgument("data_3", d3);
+        manager->callCallouts(hookpt_three_index_, handle);
+        handle.getArgument(RESULT, result);
+        EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
+    }
+
+    /// Hook indexes.  These are are made public for ease of reference.
+    int hookpt_one_index_;
+    int hookpt_two_index_;
+    int hookpt_three_index_;
+};
+
+#endif // COMMON_HOOKS_TEST_CLASS_H

+ 24 - 16
src/lib/util/hooks/library_handle.cc

@@ -12,28 +12,36 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <util/hooks/callout_manager.h>
-#include <util/hooks/library_handle.h>
+/// @file
+/// @brief Framework exception library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager).  The characteristics of the library produced from this
+/// file are:
+///
+/// - All three framework functions are supplied (version(), load() and
+///   unload()) and all generate an exception.
 
-namespace isc {
-namespace util {
+#include <hooks/hooks.h>
 
-// Callout manipulation - all deferred to the CalloutManager.
+#include <exception>
 
-void
-LibraryHandle::registerCallout(const std::string& name, CalloutPtr callout) {
-    callout_manager_->registerCallout(name, callout);
+extern "C" {
+
+int
+version() {
+    throw std::exception();
 }
 
-bool
-LibraryHandle::deregisterCallout(const std::string& name, CalloutPtr callout) {
-    return (callout_manager_->deregisterCallout(name, callout));
+int
+load(isc::hooks::LibraryHandle& handle) {
+    throw std::exception();
 }
 
-bool
-LibraryHandle::deregisterAllCallouts(const std::string& name) {
-    return (callout_manager_->deregisterAllCallouts(name));
+int
+unload() {
+    throw std::exception();
 }
 
-} // namespace util
-} // namespace isc
+};
+

+ 137 - 0
src/lib/hooks/tests/full_callout_library.cc

@@ -0,0 +1,137 @@
+// 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 Full callout library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager).  The characteristics of the library produced from this
+/// file are:
+///
+/// The characteristics of this library are:
+///
+/// - All three framework functions are supplied (version(), load() and
+///   unload()), with unload() creating a marker file.  The test code checks
+///   for the presence of this file, so verifying that unload() has been run.
+///
+/// - One standard and two non-standard callouts are supplied, with the latter
+///   being registered by the load() function.
+///
+///   All callouts do trivial calculations, the result of all being called in
+///   sequence being
+///
+///   @f[ ((7 * 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 hookpt_one, data_2 to
+///   hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <hooks/hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <fstream>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Callouts
+
+int
+context_create(CalloutHandle& handle) {
+    handle.setContext("result", static_cast<int>(7));
+    handle.setArgument("result", static_cast<int>(7));
+    return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 7. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+    int data;
+    handle.getArgument("data_1", data);
+
+    int result;
+    handle.getArgument("result", result);
+
+    result *= data;
+    handle.setArgument("result", result);
+
+    return (0);
+}
+
+// Second callout subtracts the passed value of data_2 from the current
+// running total.
+
+static int
+hook_nonstandard_two(CalloutHandle& handle) {
+    int data;
+    handle.getArgument("data_2", data);
+
+    int result;
+    handle.getArgument("result", result);
+
+    result -= data;
+    handle.setArgument("result", result);
+
+    return (0);
+}
+
+// Final callout multplies the current running total by data_3.
+
+static int
+hook_nonstandard_three(CalloutHandle& handle) {
+    int data;
+    handle.getArgument("data_3", data);
+
+    int result;
+    handle.getArgument("result", result);
+
+    result *= data;
+    handle.setArgument("result", result);
+
+    return (0);
+}
+
+// Framework functions
+
+int
+version() {
+    return (BIND10_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle& handle) {
+    // Register the non-standard functions
+    handle.registerCallout("hookpt_two", hook_nonstandard_two);
+    handle.registerCallout("hookpt_three", hook_nonstandard_three);
+
+    return (0);
+}
+
+int
+unload() {
+    // Create the marker file.
+    std::fstream marker;
+    marker.open(MARKER_FILE, std::fstream::out);
+    marker.close();
+
+    return (0);
+}
+
+};
+

+ 65 - 26
src/lib/util/tests/handles_unittest.cc

@@ -12,10 +12,10 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <util/hooks/callout_handle.h>
-#include <util/hooks/callout_manager.h>
-#include <util/hooks/library_handle.h>
-#include <util/hooks/server_hooks.h>
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
 
 #include <boost/lexical_cast.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -40,7 +40,7 @@
 /// - An active callout can only modify the registration of callouts registered
 ///   by its own library.
 
-using namespace isc::util;
+using namespace isc::hooks;
 using namespace std;
 
 namespace {
@@ -50,16 +50,17 @@ public:
     /// @brief Constructor
     ///
     /// Sets up the various elements used in each test.
-    HandlesTest() : hooks_(new ServerHooks()), manager_()
-    {
+    HandlesTest() {
         // Set up four hooks, although through gamma
-        alpha_index_ = hooks_->registerHook("alpha");
-        beta_index_ = hooks_->registerHook("beta");
-        gamma_index_ = hooks_->registerHook("gamma");
-        delta_index_ = hooks_->registerHook("delta");
+        ServerHooks& hooks = ServerHooks::getServerHooks();
+        hooks.reset();
+        alpha_index_ = hooks.registerHook("alpha");
+        beta_index_ = hooks.registerHook("beta");
+        gamma_index_ = hooks.registerHook("gamma");
+        delta_index_ = hooks.registerHook("delta");
 
         // Set up for three libraries.
-        manager_.reset(new CalloutManager(hooks_, 3));
+        manager_.reset(new CalloutManager(3));
 
         // Initialize remaining variables.
         common_string_ = "";
@@ -80,9 +81,6 @@ public:
     static std::string common_string_;
 
 private:
-    /// Server hooks 
-    boost::shared_ptr<ServerHooks> hooks_;
-
     /// Callout manager.  Declared static so that the callout functions can
     /// access it.
     boost::shared_ptr<CalloutManager> manager_;
@@ -303,10 +301,10 @@ TEST_F(HandlesTest, ContextAccessCheck) {
     // Create the callout handles and distinguish them by setting the
     // "handle_num" argument.
     CalloutHandle callout_handle_1(getCalloutManager());
-    callout_handle_1.setArgument("handle_num", static_cast<int>(1)); 
+    callout_handle_1.setArgument("handle_num", static_cast<int>(1));
 
     CalloutHandle callout_handle_2(getCalloutManager());
-    callout_handle_2.setArgument("handle_num", static_cast<int>(2)); 
+    callout_handle_2.setArgument("handle_num", static_cast<int>(2));
 
     // Now call the callouts attached to the first three hooks.  Each hook is
     // called twice (once for each callout handle) before the next hook is
@@ -603,7 +601,7 @@ TEST_F(HandlesTest, DynamicRegistrationAnotherHook) {
 
     // See what we get for calling the callouts on alpha first.
     CalloutHandle callout_handle_1(getCalloutManager());
-    callout_handle_1.setArgument("handle_num", static_cast<int>(1)); 
+    callout_handle_1.setArgument("handle_num", static_cast<int>(1));
     getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
 
     zero_results();
@@ -619,7 +617,7 @@ TEST_F(HandlesTest, DynamicRegistrationAnotherHook) {
 
     // Use a new callout handle so as to get fresh callout context.
     CalloutHandle callout_handle_2(getCalloutManager());
-    callout_handle_2.setArgument("handle_num", static_cast<int>(2)); 
+    callout_handle_2.setArgument("handle_num", static_cast<int>(2));
     getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
 
     zero_results();
@@ -651,7 +649,7 @@ TEST_F(HandlesTest, DynamicRegistrationSameHook) {
 
     // See what we get for calling the callouts on alpha first.
     CalloutHandle callout_handle_1(getCalloutManager());
-    callout_handle_1.setArgument("handle_num", static_cast<int>(1)); 
+    callout_handle_1.setArgument("handle_num", static_cast<int>(1));
     getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
     zero_results();
     getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
@@ -659,7 +657,7 @@ TEST_F(HandlesTest, DynamicRegistrationSameHook) {
 
     // Run it again - we should have added something to this hook.
     CalloutHandle callout_handle_2(getCalloutManager());
-    callout_handle_2.setArgument("handle_num", static_cast<int>(2)); 
+    callout_handle_2.setArgument("handle_num", static_cast<int>(2));
     getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
     zero_results();
     getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
@@ -667,7 +665,7 @@ TEST_F(HandlesTest, DynamicRegistrationSameHook) {
 
     // And a third time...
     CalloutHandle callout_handle_3(getCalloutManager());
-    callout_handle_3.setArgument("handle_num", static_cast<int>(3)); 
+    callout_handle_3.setArgument("handle_num", static_cast<int>(3));
     getCalloutManager()->callCallouts(alpha_index_, callout_handle_3);
     zero_results();
     getCalloutManager()->callCallouts(delta_index_, callout_handle_3);
@@ -691,7 +689,7 @@ TEST_F(HandlesTest, DynamicDeregistrationDifferentHook) {
 
     // Call the callouts on alpha
     CalloutHandle callout_handle_1(getCalloutManager());
-    callout_handle_1.setArgument("handle_num", static_cast<int>(1)); 
+    callout_handle_1.setArgument("handle_num", static_cast<int>(1));
     getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
 
     zero_results();
@@ -704,7 +702,7 @@ TEST_F(HandlesTest, DynamicDeregistrationDifferentHook) {
     // The run of the callouts should have altered the callout list on the
     // first library for hook alpha, so call again to make sure.
     CalloutHandle callout_handle_2(getCalloutManager());
-    callout_handle_2.setArgument("handle_num", static_cast<int>(2)); 
+    callout_handle_2.setArgument("handle_num", static_cast<int>(2));
     getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
 
     zero_results();
@@ -731,7 +729,7 @@ TEST_F(HandlesTest, DynamicDeregistrationSameHook) {
 
     // Call the callouts on alpha
     CalloutHandle callout_handle_1(getCalloutManager());
-    callout_handle_1.setArgument("handle_num", static_cast<int>(1)); 
+    callout_handle_1.setArgument("handle_num", static_cast<int>(1));
     getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
 
     zero_results();
@@ -742,7 +740,7 @@ TEST_F(HandlesTest, DynamicDeregistrationSameHook) {
     // The run of the callouts should have altered the callout list on the
     // first library for hook alpha, so call again to make sure.
     CalloutHandle callout_handle_2(getCalloutManager());
-    callout_handle_2.setArgument("handle_num", static_cast<int>(2)); 
+    callout_handle_2.setArgument("handle_num", static_cast<int>(2));
     getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
 
     zero_results();
@@ -912,6 +910,47 @@ TEST_F(HandlesTest, CheckModifiedArgument) {
     EXPECT_EQ(std::string("YNN" "YYNN" "YNY"), modified_arg);
 }
 
+// Test that the CalloutHandle provides the name of the hook to which the
+// callout is attached.
+
+int
+callout_hook_name(CalloutHandle& callout_handle) {
+    HandlesTest::common_string_ = callout_handle.getHookName();
+    return (0);
+}
+
+int
+callout_hook_dummy(CalloutHandle&) {
+    return (0);
+}
+
+TEST_F(HandlesTest, HookName) {
+    getCalloutManager()->setLibraryIndex(0);
+    getCalloutManager()->registerCallout("alpha", callout_hook_name);
+    getCalloutManager()->registerCallout("beta", callout_hook_name);
+
+    // Call alpha and beta callouts and check the hook to which they belong.
+    CalloutHandle callout_handle(getCalloutManager());
+
+    EXPECT_EQ(std::string(""), HandlesTest::common_string_);
+
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+    EXPECT_EQ(std::string("alpha"), HandlesTest::common_string_);
+
+    getCalloutManager()->callCallouts(beta_index_, callout_handle);
+    EXPECT_EQ(std::string("beta"), HandlesTest::common_string_);
+
+    // Make sure that the callout accesses the name even if it is not the
+    // only callout in the list.
+    getCalloutManager()->setLibraryIndex(1);
+    getCalloutManager()->registerCallout("gamma", callout_hook_dummy);
+    getCalloutManager()->registerCallout("gamma", callout_hook_name);
+    getCalloutManager()->registerCallout("gamma", callout_hook_dummy);
+
+    EXPECT_EQ(std::string("beta"), HandlesTest::common_string_);
+    getCalloutManager()->callCallouts(gamma_index_, callout_handle);
+    EXPECT_EQ(std::string("gamma"), HandlesTest::common_string_);
+}
 
 } // Anonymous namespace
 

+ 454 - 0
src/lib/hooks/tests/hooks_manager_unittest.cc

@@ -0,0 +1,454 @@
+// 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/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief Hooks manager collection test class
+
+class HooksManagerTest : public ::testing::Test,
+                         public HooksCommonTestClass {
+public:
+    /// @brief Constructor
+    ///
+    /// Reset the hooks manager.  The hooks manager is a singleton, so needs
+    /// to be reset for each test.
+    HooksManagerTest() {
+        HooksManager::unloadLibraries();
+    }
+
+    /// @brief Destructor
+    ///
+    /// Unload all libraries.
+    ~HooksManagerTest() {
+        HooksManager::unloadLibraries();
+    }
+
+
+    /// @brief Call callouts test
+    ///
+    /// See the header for HooksCommonTestClass::execute for details.
+    ///
+    /// @param r0...r3, d1..d3 Values and intermediate values expected.  They
+    ///        are ordered so that the variables appear in the argument list in
+    ///        the order they are used.
+    void executeCallCallouts(int r0, int d1, int r1, int d2, int r2, int d3,
+                             int r3) {
+        static const char* COMMON_TEXT = " callout returned the wong value";
+        static const char* RESULT = "result";
+
+        // Get a CalloutHandle for the calculation.
+        CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+        // Initialize the argument RESULT.  This simplifies testing by
+        // eliminating the generation of an exception when we try the unload
+        // test.  In that case, RESULT is unchanged.
+        int result = -1;
+        handle->setArgument(RESULT, result);
+
+        // Seed the calculation.
+        HooksManager::callCallouts(isc::hooks::ServerHooks::CONTEXT_CREATE,
+                                   *handle);
+        handle->getArgument(RESULT, result);
+        EXPECT_EQ(r0, result) << "context_create" << COMMON_TEXT;
+
+        // Perform the first calculation.
+        handle->setArgument("data_1", d1);
+        HooksManager::callCallouts(hookpt_one_index_, *handle);
+        handle->getArgument(RESULT, result);
+        EXPECT_EQ(r1, result) << "hookpt_one" << COMMON_TEXT;
+
+        // ... the second ...
+        handle->setArgument("data_2", d2);
+        HooksManager::callCallouts(hookpt_two_index_, *handle);
+        handle->getArgument(RESULT, result);
+        EXPECT_EQ(r2, result) << "hookpt_two" << COMMON_TEXT;
+
+        // ... and the third.
+        handle->setArgument("data_3", d3);
+        HooksManager::callCallouts(hookpt_three_index_, *handle);
+        handle->getArgument(RESULT, result);
+        EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
+    }
+
+};
+
+// This is effectively the same test as for LibraryManager, but using the
+// HooksManager object.
+
+TEST_F(HooksManagerTest, LoadLibraries) {
+
+    // Set up the list of libraries to be loaded.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+    library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+    // Load the libraries.
+    EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+    // Execute the callouts.  The first library implements the calculation.
+    //
+    // r3 = (7 * d1 - d2) * d3
+    //
+    // The last-loaded library implements the calculation
+    //
+    // r3 = (10 + d1) * d2 - d3
+    //
+    // Putting the processing for each library together in the appropriate
+    // order, we get:
+    //
+    // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+    {
+        SCOPED_TRACE("Calculation with libraries loaded");
+        executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+    }
+
+    // Try unloading the libraries.
+    EXPECT_NO_THROW(HooksManager::unloadLibraries());
+
+    // Re-execute the calculation - callouts can be called but as nothing
+    // happens, the result should always be -1.
+    {
+        SCOPED_TRACE("Calculation with libraries not loaded");
+        executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
+    }
+}
+
+// This is effectively the same test as above, but with a library generating
+// an error when loaded. It is expected that the failing library will not be
+// loaded, but others will be.
+
+TEST_F(HooksManagerTest, LoadLibrariesWithError) {
+
+    // Set up the list of libraries to be loaded.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+    library_names.push_back(std::string(INCORRECT_VERSION_LIBRARY));
+    library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+    // Load the libraries.  We expect a failure return because one of the
+    // libraries fails to load.
+    EXPECT_FALSE(HooksManager::loadLibraries(library_names));
+
+    // Execute the callouts.  The first library implements the calculation.
+    //
+    // r3 = (7 * d1 - d2) * d3
+    //
+    // The last-loaded library implements the calculation
+    //
+    // r3 = (10 + d1) * d2 - d3
+    //
+    // Putting the processing for each library together in the appropriate
+    // order, we get:
+    //
+    // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+    {
+        SCOPED_TRACE("Calculation with libraries loaded");
+        executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+    }
+
+    // Try unloading the libraries.
+    EXPECT_NO_THROW(HooksManager::unloadLibraries());
+
+    // Re-execute the calculation - callouts can be called but as nothing
+    // happens, the result should always be -1.
+    {
+        SCOPED_TRACE("Calculation with libraries not loaded");
+        executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
+    }
+}
+
+// Test that we can unload a set of libraries while we have a CalloutHandle
+// created on them in existence, and can delete the handle afterwards.
+
+TEST_F(HooksManagerTest, CalloutHandleUnloadLibrary) {
+
+    // Set up the list of libraries to be loaded.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+
+    // Load the libraries.
+    EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+    // Execute the callouts.  Thiis library implements:
+    //
+    // r3 = (7 * d1 - d2) * d3
+    {
+        SCOPED_TRACE("Calculation with full callout library loaded");
+        executeCallCallouts(7, 4, 28, 8, 20, 2, 40);
+    }
+
+    // Get an outstanding callout handle on this library.
+    CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+    // Execute once of the callouts again to ensure that the handle contains
+    // memory allocated by the library.
+    HooksManager::callCallouts(ServerHooks::CONTEXT_CREATE, *handle);
+
+    // Unload the libraries.
+    HooksManager::unloadLibraries();
+
+    // Deleting the callout handle should not cause a segmentation fault.
+    handle.reset();
+}
+
+// Test that we can load a new set of libraries while we have a CalloutHandle
+// created on them in existence, and can delete the handle afterwards.
+
+TEST_F(HooksManagerTest, CalloutHandleLoadLibrary) {
+
+    // Set up the list of libraries to be loaded.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+
+    // Load the libraries.
+    EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+    // Execute the callouts.  Thiis library implements:
+    //
+    // r3 = (7 * d1 - d2) * d3
+    {
+        SCOPED_TRACE("Calculation with full callout library loaded");
+        executeCallCallouts(7, 4, 28, 8, 20, 2, 40);
+    }
+
+    // Get an outstanding callout handle on this library and execute one of
+    // the callouts again to ensure that the handle contains memory allocated
+    // by the library.
+    CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+    HooksManager::callCallouts(ServerHooks::CONTEXT_CREATE, *handle);
+
+    // Load a new library that implements the calculation
+    //
+    // r3 = (10 + d1) * d2 - d3
+    std::vector<std::string> new_library_names;
+    new_library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+    // Load the libraries.
+    EXPECT_TRUE(HooksManager::loadLibraries(new_library_names));
+
+    // Execute the calculation.  Note that we still have the CalloutHandle
+    // for the old library: however, this should not affect the new calculation.
+    {
+        SCOPED_TRACE("Calculation with basic callout library loaded");
+        executeCallCallouts(10, 7, 17, 3, 51, 16, 35);
+    }
+
+    // Deleting the old callout handle should not cause a segmentation fault.
+    handle.reset();
+}
+
+// This is effectively the same test as the LoadLibraries test.
+
+TEST_F(HooksManagerTest, ReloadSameLibraries) {
+
+    // Set up the list of libraries to be loaded.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+    library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+    // Load the libraries.
+    EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+    // Execute the callouts.  See the LoadLibraries test for an explanation of
+    // the calculation.
+    {
+        SCOPED_TRACE("Calculation with libraries loaded");
+        executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+    }
+
+    // Try reloading the libraries and re-execute the calculation - we should
+    // get the same results.
+    EXPECT_NO_THROW(HooksManager::loadLibraries(library_names));
+    {
+        SCOPED_TRACE("Calculation with libraries reloaded");
+        executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+    }
+}
+
+TEST_F(HooksManagerTest, ReloadLibrariesReverseOrder) {
+
+    // Set up the list of libraries to be loaded and load them.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+    library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+    EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+    // Execute the callouts.  The first library implements the calculation.
+    //
+    // r3 = (7 * d1 - d2) * d3
+    //
+    // The last-loaded library implements the calculation
+    //
+    // r3 = (10 + d1) * d2 - d3
+    //
+    // Putting the processing for each library together in the given order
+    // gives.
+    //
+    // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+    {
+        SCOPED_TRACE("Calculation with libraries loaded");
+        executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+    }
+
+    // Reload the libraries in the reverse order.
+    std::reverse(library_names.begin(), library_names.end());
+    EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+    // The calculation in the reverse order gives:
+    //
+    // r3 = ((((7 + d1) * d1) * d2 - d2) - d3) * d3
+    {
+        SCOPED_TRACE("Calculation with libraries loaded in reverse order");
+        executeCallCallouts(7, 3, 30, 3, 87, 7, 560);
+    }
+}
+
+// Local callouts for the test of server-registered callouts.
+
+namespace {
+
+    int
+testPreCallout(CalloutHandle& handle) {
+    handle.setArgument("result", static_cast<int>(1027));
+    return (0);
+}
+
+int
+testPostCallout(CalloutHandle& handle) {
+    int result;
+    handle.getArgument("result", result);
+    result *= 2;
+    handle.setArgument("result", result);
+    return (0);
+}
+
+}
+
+// The next test registers the pre and post- callouts above for hook hookpt_two,
+// and checks they are called.
+
+TEST_F(HooksManagerTest, PrePostCalloutTest) {
+
+    // Load a single library.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+    EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+    // Load the pre- and post- callouts.
+    HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two",
+                                                             testPreCallout);
+    HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two",
+                                                              testPostCallout);
+
+    // Execute the callouts.  hookpt_two implements the calculation:
+    //
+    //  "result - data_2"
+    //
+    // With the pre- and post- callouts above, the result expected is
+    //
+    // (1027 - data_2) * 2
+    CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+    handle->setArgument("result", static_cast<int>(0));
+    handle->setArgument("data_2", static_cast<int>(15));
+
+    HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+    int result = 0;
+    handle->getArgument("result", result);
+    EXPECT_EQ(2024, result);
+
+    // ... and check that the pre- and post- callout functions don't survive a
+    // reload.
+    EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+    handle = HooksManager::createCalloutHandle();
+
+    handle->setArgument("result", static_cast<int>(0));
+    handle->setArgument("data_2", static_cast<int>(15));
+
+    HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+    result = 0;
+    handle->getArgument("result", result);
+    EXPECT_EQ(-15, result);
+}
+
+// Check that everything works even with no libraries loaded.  First that
+// calloutsPresent() always returns false.
+
+TEST_F(HooksManagerTest, NoLibrariesCalloutsPresent) {
+    // No callouts should be present on any hooks.
+    EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_one_index_));
+    EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_two_index_));
+    EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_three_index_));
+}
+
+TEST_F(HooksManagerTest, NoLibrariesCallCallouts) {
+    executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
+}
+
+// Test the encapsulation of the ServerHooks::registerHook() method.
+
+TEST_F(HooksManagerTest, RegisterHooks) {
+    ServerHooks::getServerHooks().reset();
+    EXPECT_EQ(2, ServerHooks::getServerHooks().getCount());
+
+    // Check that the hook indexes are as expected. (Use temporary variables
+    // as it appears that Google test can't access the constants.)
+    int sh_cc = ServerHooks::CONTEXT_CREATE;
+    int hm_cc = HooksManager::CONTEXT_CREATE;
+    EXPECT_EQ(sh_cc, hm_cc);
+
+    int sh_cd = ServerHooks::CONTEXT_DESTROY;
+    int hm_cd = HooksManager::CONTEXT_DESTROY;
+    EXPECT_EQ(sh_cd, hm_cd);
+
+    // Register a few hooks and check we have the indexes as expected.
+    EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
+    EXPECT_EQ(3, HooksManager::registerHook(string("beta")));
+    EXPECT_EQ(4, HooksManager::registerHook(string("gamma")));
+    EXPECT_THROW(static_cast<void>(HooksManager::registerHook(string("alpha"))),
+                 DuplicateHook);
+
+    // ... an check the hooks are as we expect.
+    EXPECT_EQ(5, ServerHooks::getServerHooks().getCount());
+    vector<string> names = ServerHooks::getServerHooks().getHookNames();
+    sort(names.begin(), names.end());
+
+    EXPECT_EQ(string("alpha"), names[0]);
+    EXPECT_EQ(string("beta"), names[1]);
+    EXPECT_EQ(string("context_create"), names[2]);
+    EXPECT_EQ(string("context_destroy"), names[3]);
+    EXPECT_EQ(string("gamma"), names[4]);
+}
+
+
+} // Anonymous namespace

+ 33 - 0
src/lib/hooks/tests/incorrect_version_library.cc

@@ -0,0 +1,33 @@
+// 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 Incorrect version function test
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager).  The characteristics of the library produced from this
+/// file are:
+///
+/// - It contains the version() framework function only, which returns an
+///   incorrect version number.
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+int version() {
+    return (BIND10_HOOKS_VERSION + 1);
+}
+
+};

+ 184 - 0
src/lib/hooks/tests/library_manager_collection_unittest.cc

@@ -0,0 +1,184 @@
+// 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/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/library_manager_collection.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief Library manager collection test class
+
+class LibraryManagerCollectionTest : public ::testing::Test,
+                                     public HooksCommonTestClass {
+};
+
+/// @brief Public library manager collection class
+///
+/// This is an instance of the LibraryManagerCollection class but with the
+/// protected methods made public for test purposes.
+
+class PublicLibraryManagerCollection
+                : public isc::hooks::LibraryManagerCollection {
+public:
+    /// @brief Constructor
+    ///
+    /// @param List of libraries that this collection will manage.  The order
+    ///        of the libraries is important.
+    PublicLibraryManagerCollection(const std::vector<std::string>& libraries)
+        : LibraryManagerCollection(libraries)
+    {}
+
+    /// Public methods that call protected methods on the superclass.
+    using LibraryManagerCollection::unloadLibraries;
+};
+
+
+// This is effectively the same test as for LibraryManager, but using the
+// LibraryManagerCollection object.
+
+TEST_F(LibraryManagerCollectionTest, LoadLibraries) {
+
+    // Set up the list of libraries to be loaded.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+    library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+    // Set up the library manager collection and get the callout manager we'll
+    // be using.
+    PublicLibraryManagerCollection lm_collection(library_names);
+
+    // Load the libraries.
+    EXPECT_TRUE(lm_collection.loadLibraries());
+    boost::shared_ptr<CalloutManager> manager =
+                                      lm_collection.getCalloutManager();
+
+    // Execute the callouts.  The first library implements the calculation.
+    //
+    // r3 = (7 * d1 - d2) * d3
+    //
+    // The last-loaded library implements the calculation
+    //
+    // r3 = (10 + d1) * d2 - d3
+    //
+    // Putting the processing for each library together in the appropriate
+    // order, we get:
+    //
+    // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+    {
+        SCOPED_TRACE("Doing calculation with libraries loaded");
+        executeCallCallouts(manager, 10, 3, 33, 2, 62, 3, 183);
+    }
+
+    // Try unloading the libraries.
+    EXPECT_NO_THROW(lm_collection.unloadLibraries());
+
+    // Re-execute the calculation - callouts can be called but as nothing
+    // happens, the result should always be -1.
+    {
+        SCOPED_TRACE("Doing calculation with libraries not loaded");
+        executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
+    }
+}
+
+// This is effectively the same test as above, but with a library generating
+// an error when loaded. It is expected that the failing library will not be
+// loaded, but others will be.
+
+TEST_F(LibraryManagerCollectionTest, LoadLibrariesWithError) {
+
+    // Set up the list of libraries to be loaded.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+    library_names.push_back(std::string(INCORRECT_VERSION_LIBRARY));
+    library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+    // Set up the library manager collection and get the callout manager we'll
+    // be using.
+    PublicLibraryManagerCollection lm_collection(library_names);
+
+    // Load the libraries.  We expect a failure status to be returned as
+    // one of the libraries failed to load.
+    EXPECT_FALSE(lm_collection.loadLibraries());
+    boost::shared_ptr<CalloutManager> manager =
+                                      lm_collection.getCalloutManager();
+
+    // Expect only two libraries were loaded.
+    EXPECT_EQ(2, manager->getNumLibraries());
+
+    // Execute the callouts.  The first library implements the calculation.
+    //
+    // r3 = (7 * d1 - d2) * d3
+    //
+    // The last-loaded library implements the calculation
+    //
+    // r3 = (10 + d1) * d2 - d3
+    //
+    // Putting the processing for each library together in the appropriate
+    // order, we get:
+    //
+    // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+    {
+        SCOPED_TRACE("Doing calculation with libraries loaded");
+        executeCallCallouts(manager, 10, 3, 33, 2, 62, 3, 183);
+    }
+
+    // Try unloading the libraries.
+    EXPECT_NO_THROW(lm_collection.unloadLibraries());
+
+    // Re-execute the calculation - callouts can be called but as nothing
+    // happens, the result should always be -1.
+    {
+        SCOPED_TRACE("Doing calculation with libraries not loaded");
+        executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
+    }
+}
+
+// Check that everything works even with no libraries loaded.
+
+TEST_F(LibraryManagerCollectionTest, NoLibrariesLoaded) {
+    // Set up the list of libraries to be loaded.
+    std::vector<std::string> library_names;
+
+    // Set up the library manager collection and get the callout manager we'll
+    // be using.
+    LibraryManagerCollection lm_collection(library_names);
+    EXPECT_TRUE(lm_collection.loadLibraries());
+    boost::shared_ptr<CalloutManager> manager =
+                                      lm_collection.getCalloutManager();
+
+    // Load the libraries.
+    EXPECT_TRUE(lm_collection.loadLibraries());
+
+    // Eecute the calculation - callouts can be called but as nothing
+    // happens, the result should always be -1.
+    executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
+}
+
+} // Anonymous namespace

+ 555 - 0
src/lib/hooks/tests/library_manager_unittest.cc

@@ -0,0 +1,555 @@
+// 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/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/marker_file.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <fstream>
+#include <string>
+
+#include <unistd.h>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief Library manager test class
+
+class LibraryManagerTest : public ::testing::Test,
+                           public HooksCommonTestClass {
+public:
+    /// @brief Constructor
+    ///
+    /// Initializes the CalloutManager object used in the tests.  It sets it
+    /// up with the hooks initialized in the HooksCommonTestClass object and
+    /// with four libraries.
+    LibraryManagerTest() {
+        callout_manager_.reset(new CalloutManager(4));
+
+        // Ensure the marker file is not present at the start of a test.
+        static_cast<void>(unlink(MARKER_FILE));
+    }
+
+    /// @brief Destructor
+    ///
+    /// Ensures a marker file is removed after each test.
+    ~LibraryManagerTest() {
+        static_cast<void>(unlink(MARKER_FILE));
+    }
+
+    /// @brief Marker file present
+    ///
+    /// Convenience function to check whether a marker file is present.  It
+    /// does this by opening the file.
+    ///
+    /// @return true if the marker file is present.
+    bool markerFilePresent() const {
+
+        // Try to open it.
+        std::fstream marker;
+        marker.open(MARKER_FILE, std::fstream::in);
+
+        // Check if it is open and close it if so.
+        bool exists = marker.is_open();
+        if (exists) {
+            marker.close();
+        }
+
+        return (exists);
+    }
+
+    /// @brief Call callouts test
+    ///
+    /// A wrapper around the method of the same name in the HooksCommonTestClass
+    /// object, this passes this class's CalloutManager to that method.
+    ///
+    /// @param r0...r3, d1..d3 Values and intermediate values expected.  They
+    ///        are ordered so that the variables appear in the argument list in
+    ///        the order they are used.  See HooksCommonTestClass::execute for
+    ///        a full description. (rN is used to indicate an expected result,
+    ///        dN is data to be passed to the calculation.)
+    void executeCallCallouts(int r0, int d1, int r1, int d2, int r2, int d3,
+                             int r3) {
+        HooksCommonTestClass::executeCallCallouts(callout_manager_, r0, d1,
+                                                  r1, d2, r2, d3, r3);
+    }
+
+    /// Callout manager used for the test.
+    boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+/// @brief Library manager class
+///
+/// This is an instance of the LibraryManager class but with the protected
+/// methods made public for test purposes.
+
+class PublicLibraryManager : public isc::hooks::LibraryManager {
+public:
+    /// @brief Constructor
+    ///
+    /// Stores the library name.  The actual loading is done in loadLibrary().
+    ///
+    /// @param name Name of the library to load.  This should be an absolute
+    ///        path name.
+    /// @param index Index of this library.  For all these tests, it will be
+    ///        zero, as we are only using one library.
+    /// @param manager CalloutManager object
+    PublicLibraryManager(const std::string& name, int index,
+                         const boost::shared_ptr<CalloutManager>& manager)
+        : LibraryManager(name, index, manager)
+    {}
+
+    /// Public methods that call protected methods on the superclass.
+    using LibraryManager::openLibrary;
+    using LibraryManager::closeLibrary;
+    using LibraryManager::checkVersion;
+    using LibraryManager::registerStandardCallouts;
+    using LibraryManager::runLoad;
+    using LibraryManager::runUnload;
+};
+
+
+// Check that openLibrary() reports an error when it can't find the specified
+// library.
+
+TEST_F(LibraryManagerTest, NoLibrary) {
+    PublicLibraryManager lib_manager(std::string(NOT_PRESENT_LIBRARY),
+                                     0, callout_manager_);
+    EXPECT_FALSE(lib_manager.openLibrary());
+}
+
+// Check that the openLibrary() and closeLibrary() methods work.
+
+TEST_F(LibraryManagerTest, OpenClose) {
+    PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+                                     0, callout_manager_);
+
+    // Open and close the library
+    EXPECT_TRUE(lib_manager.openLibrary());
+    EXPECT_TRUE(lib_manager.closeLibrary());
+
+    // Check that a second close on an already closed library does not report
+    // an error.
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library with no version function.
+
+TEST_F(LibraryManagerTest, NoVersion) {
+    PublicLibraryManager lib_manager(std::string(NO_VERSION_LIBRARY),
+                                     0, callout_manager_);
+    // Open should succeed.
+    EXPECT_TRUE(lib_manager.openLibrary());
+
+    // Version check should fail.
+    EXPECT_FALSE(lib_manager.checkVersion());
+
+    // Tidy up.
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library with a version function
+// that returns an incorrect version number.
+
+TEST_F(LibraryManagerTest, WrongVersion) {
+    PublicLibraryManager lib_manager(std::string(INCORRECT_VERSION_LIBRARY),
+                                     0, callout_manager_);
+    // Open should succeed.
+    EXPECT_TRUE(lib_manager.openLibrary());
+
+    // Version check should fail.
+    EXPECT_FALSE(lib_manager.checkVersion());
+
+    // Tidy up.
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library where the version function
+// throws an exception.
+
+TEST_F(LibraryManagerTest, VersionException) {
+    PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+                                     0, callout_manager_);
+    // Open should succeed.
+    EXPECT_TRUE(lib_manager.openLibrary());
+
+    // Version check should fail.
+    EXPECT_FALSE(lib_manager.checkVersion());
+
+    // Tidy up.
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Tests that checkVersion() function succeeds in the case of a library with a
+// version function that returns the correct version number.
+
+TEST_F(LibraryManagerTest, CorrectVersionReturned) {
+    PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+                                     0, callout_manager_);
+    // Open should succeed.
+    EXPECT_TRUE(lib_manager.openLibrary());
+
+    // Version check should succeed.
+    EXPECT_TRUE(lib_manager.checkVersion());
+
+    // Tidy up.
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Checks the registration of standard callouts.
+
+TEST_F(LibraryManagerTest, RegisterStandardCallouts) {
+
+    // 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(BASIC_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());
+
+    // Now execute the callouts in the order expected.  The library performs
+    // the calculation:
+    //
+    // r3 = (10 + d1) * d2 - d3
+    executeCallCallouts(10, 5, 15, 7, 105, 17, 88);
+
+    // Tidy up
+    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());
+
+    // Check that only context_create and hookpt_one have callouts registered.
+    EXPECT_TRUE(callout_manager_->calloutsPresent(
+                ServerHooks::CONTEXT_CREATE));
+    EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_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(hookpt_one_index_));
+    EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_));
+    EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(
+                 ServerHooks::CONTEXT_DESTROY));
+
+    // Now execute the callouts in the order expected.  The library performs
+    // the calculation:
+    //
+    // r3 = (5 * d1 + d2) * d3
+    executeCallCallouts(5, 5, 25, 7, 32, 10, 320);
+
+    // Tidy up
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check handling of a "load" function that throws an exception
+
+TEST_F(LibraryManagerTest, CheckLoadException) {
+
+    // 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(FRAMEWORK_EXCEPTION_LIBRARY),
+                                     0, callout_manager_);
+    EXPECT_TRUE(lib_manager.openLibrary());
+
+    // Running the load function should fail.
+    EXPECT_FALSE(lib_manager.runLoad());
+
+    // 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());
+
+    // Tidy up
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// No unload function
+
+TEST_F(LibraryManagerTest, CheckNoUnload) {
+
+    // 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(BASIC_CALLOUT_LIBRARY),
+                                     0, callout_manager_);
+    EXPECT_TRUE(lib_manager.openLibrary());
+
+    // Check that no unload function returns true.
+    EXPECT_TRUE(lib_manager.runUnload());
+
+    // Tidy up
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Unload function returns an error
+
+TEST_F(LibraryManagerTest, CheckUnloadError) {
+
+    // 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 unload function returning an error returns false.
+    EXPECT_FALSE(lib_manager.runUnload());
+
+    // Tidy up
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Unload function throws an exception.
+
+TEST_F(LibraryManagerTest, CheckUnloadException) {
+
+    // 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(FRAMEWORK_EXCEPTION_LIBRARY),
+                                     0, callout_manager_);
+    EXPECT_TRUE(lib_manager.openLibrary());
+
+    // Check that we detect that the unload function throws an exception.
+    EXPECT_FALSE(lib_manager.runUnload());
+
+    // Tidy up
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the case of the library's unload() function returning a
+// success is handled correcty.
+
+TEST_F(LibraryManagerTest, CheckUnload) {
+
+    // 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(UNLOAD_CALLOUT_LIBRARY),
+                                     0, callout_manager_);
+    EXPECT_TRUE(lib_manager.openLibrary());
+
+
+    // Check that the marker file is not present (at least that the file
+    // open fails).
+    EXPECT_FALSE(markerFilePresent());
+
+    // Check that unload function runs and returns a success
+    EXPECT_TRUE(lib_manager.runUnload());
+
+    // Check that the marker file was created.
+    EXPECT_TRUE(markerFilePresent());
+
+    // Tidy up
+    EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Test the operation of unloadLibrary().  We load a library with a set
+// of callouts then unload it.  We need to check that the callouts have been
+// removed.  We'll also check that the library's unload() function was called
+// as well.
+
+TEST_F(LibraryManagerTest, LibUnload) {
+
+    // 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(FULL_CALLOUT_LIBRARY),
+                               0, callout_manager_);
+    EXPECT_TRUE(lib_manager.openLibrary());
+
+    // Check the version of the library.
+    EXPECT_TRUE(lib_manager.checkVersion());
+
+    // No callouts should be registered at the moment.
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+
+    // Load the single standard callout and check it is registered correctly.
+    EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
+    EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+
+    // Call the load function to load the other callouts.
+    EXPECT_TRUE(lib_manager.runLoad());
+    EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+    EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_));
+    EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_));
+
+    // Unload the library and check that the callouts have been removed from
+    // the CalloutManager.
+    lib_manager.unloadLibrary();
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+}
+
+// Now come the loadLibrary() tests that make use of all the methods tested
+// above.  These tests are really to make sure that the methods have been
+// tied together correctly.
+
+// First test the basic error cases - no library, no version function, version
+// function returning an error.
+
+TEST_F(LibraryManagerTest, LoadLibraryNoLibrary) {
+    LibraryManager lib_manager(std::string(NOT_PRESENT_LIBRARY), 0,
+                                           callout_manager_);
+    EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the code handles the case of a library with no version function.
+
+TEST_F(LibraryManagerTest, LoadLibraryNoVersion) {
+    LibraryManager lib_manager(std::string(NO_VERSION_LIBRARY), 0,
+                                           callout_manager_);
+    EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the code handles the case of a library with a version function
+// that returns an incorrect version number.
+
+TEST_F(LibraryManagerTest, LoadLibraryWrongVersion) {
+    LibraryManager lib_manager(std::string(INCORRECT_VERSION_LIBRARY), 0,
+                                           callout_manager_);
+    EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the full loadLibrary call works.
+
+TEST_F(LibraryManagerTest, LoadLibrary) {
+    LibraryManager lib_manager(std::string(FULL_CALLOUT_LIBRARY), 0,
+                                           callout_manager_);
+    EXPECT_TRUE(lib_manager.loadLibrary());
+
+    // Now execute the callouts in the order expected.  The library performs
+    // the calculation:
+    //
+    // r3 = (7 * d1 - d2) * d3
+    executeCallCallouts(7, 5, 35, 9, 26, 3, 78);
+
+    EXPECT_TRUE(lib_manager.unloadLibrary());
+
+    // Check that the callouts have been removed from the callout manager.
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+    EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+}
+
+// Now test for multiple libraries.  We'll load the full callout library
+// first, then load some of the libraries with missing framework functions.
+// This will check that when searching for framework functions, only the
+// specified library is checked, not other loaded libraries. We will
+// load a second library with suitable callouts and check that the callouts
+// are added correctly. Finally, we'll unload one of the libraries and
+// check that only the callouts belonging to that library were removed.
+
+TEST_F(LibraryManagerTest, LoadMultipleLibraries) {
+    // Load a library with all framework functions.
+    LibraryManager lib_manager_1(std::string(FULL_CALLOUT_LIBRARY), 0,
+                                 callout_manager_);
+    EXPECT_TRUE(lib_manager_1.loadLibrary());
+
+    // Attempt to load a library with no version() function.  We should detect
+    // this and not end up calling the function from the already loaded
+    // library.
+    LibraryManager lib_manager_2(std::string(NO_VERSION_LIBRARY), 1,
+                                 callout_manager_);
+    EXPECT_FALSE(lib_manager_2.loadLibrary());
+
+    // Attempt to load the library with an incorrect version.  This should
+    // be detected.
+    LibraryManager lib_manager_3(std::string(INCORRECT_VERSION_LIBRARY), 1,
+                                 callout_manager_);
+    EXPECT_FALSE(lib_manager_3.loadLibrary());
+
+    // Load the basic callout library.  This only has standard callouts so,
+    // if the first library's load() function gets called, some callouts
+    // will be registered twice and lead to incorrect results.
+    LibraryManager lib_manager_4(std::string(BASIC_CALLOUT_LIBRARY), 1,
+                                 callout_manager_);
+    EXPECT_TRUE(lib_manager_4.loadLibrary());
+
+    // Execute the callouts.  The first library implements the calculation.
+    //
+    // r3 = (7 * d1 - d2) * d3
+    //
+    // The last-loaded library implements the calculation
+    //
+    // r3 = (10 + d1) * d2 - d3
+    //
+    // Putting the processing for each library together in the appropriate
+    // order, we get:
+    //
+    // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+    executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+
+    // All done, so unload the first library.
+    EXPECT_TRUE(lib_manager_1.unloadLibrary());
+
+    // Now execute the callouts again and check that the results are as
+    // expected for the new calculation.
+    executeCallCallouts(10, 5, 15, 7, 105, 17, 88);
+
+    // ... and tidy up.
+    EXPECT_TRUE(lib_manager_4.unloadLibrary());
+}
+
+} // Anonymous namespace

+ 119 - 0
src/lib/hooks/tests/load_callout_library.cc

@@ -0,0 +1,119 @@
+// 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 library with load() function
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager).  The characteristics of the library produced from this
+/// file are:
+///
+/// - The "version" and "load" framework functions are supplied.  One "standard"
+///   callout is supplied ("hookpt_one") and two non-standard ones which are
+///   registered during the call to "load" on the hooks "hookpt_two" and
+///   "hookpt_three".
+///
+///   All callouts do trivial calculations, the result of all being called in
+///   sequence 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 hookpt_one, data_2 to
+///   hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Callouts
+
+int
+context_create(CalloutHandle& handle) {
+    handle.setContext("result", static_cast<int>(5));
+    handle.setArgument("result", static_cast<int>(5));
+    return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 5. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+    int data;
+    handle.getArgument("data_1", data);
+
+    int result;
+    handle.getContext("result", result);
+
+    result *= data;
+    handle.setArgument("result", result);
+
+    return (0);
+}
+
+// Second callout multiplies the current context value by the "data_2"
+// argument.
+
+static int
+hook_nonstandard_two(CalloutHandle& handle) {
+    int data;
+    handle.getArgument("data_2", data);
+
+    int result;
+    handle.getArgument("result", result);
+
+    result += data;
+    handle.setArgument("result", result);
+
+    return (0);
+}
+
+// Final callout adds "data_3" to the result.
+
+static int
+hook_nonstandard_three(CalloutHandle& handle) {
+    int data;
+    handle.getArgument("data_3", data);
+
+    int result;
+    handle.getArgument("result", result);
+
+    result *= data;
+    handle.setArgument("result", result);
+
+    return (0);
+}
+
+// Framework functions
+
+int
+version() {
+    return (BIND10_HOOKS_VERSION);
+}
+
+int load(LibraryHandle& handle) {
+    // Register the non-standard functions
+    handle.registerCallout("hookpt_two", hook_nonstandard_two);
+    handle.registerCallout("hookpt_three", hook_nonstandard_three);
+
+    return (0);
+}
+
+};
+

+ 49 - 0
src/lib/hooks/tests/load_error_callout_library.cc

@@ -0,0 +1,49 @@
+// 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 Error load library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager).  The characteristics of the library produced from this
+/// file are:
+///
+/// - All framework functions are supplied.  "version" returns the correct
+///   value, but "load" and unload return an error.
+
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Framework functions
+
+int
+version() {
+    return (BIND10_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle&) {
+    return (1);
+}
+
+int
+unload() {
+    return (1);
+}
+
+};
+

+ 27 - 0
src/lib/hooks/tests/marker_file.h.in

@@ -0,0 +1,27 @@
+// 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.
+
+#ifndef MARKER_FILE_H
+#define MARKER_FILE_H
+
+/// @file
+/// Define a marker file that is used in tests to prove that an "unload"
+/// function has been called.
+
+namespace {
+const char* MARKER_FILE = "@abs_builddir@/marker_file.dat";
+}
+
+#endif // MARKER_FILE_H
+

+ 30 - 0
src/lib/hooks/tests/no_version_library.cc

@@ -0,0 +1,30 @@
+// 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 No version function library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager).  The characteristics of the library produced from this
+/// file are:
+///
+/// - No version() function is present.
+
+extern "C" {
+
+int no_version() {
+    return (0);
+}
+
+};

+ 25 - 0
src/lib/hooks/tests/run_unittests.cc

@@ -0,0 +1,25 @@
+// Copyright (C) 2009  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 <log/logger_support.h>
+#include <util/unittests/run_all.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::log::initLogger();
+    return (isc::util::unittests::run_all());
+}

+ 55 - 119
src/lib/util/tests/server_hooks_unittest.cc

@@ -12,7 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <util/hooks/server_hooks.h>
+#include <hooks/server_hooks.h>
 
 #include <gtest/gtest.h>
 
@@ -21,7 +21,7 @@
 #include <vector>
 
 using namespace isc;
-using namespace isc::util;
+using namespace isc::hooks;
 using namespace std;
 
 namespace {
@@ -30,7 +30,8 @@ namespace {
 // constructor registers two hooks, this is also a test of the constructor.
 
 TEST(ServerHooksTest, RegisterHooks) {
-    ServerHooks hooks;
+    ServerHooks& hooks = ServerHooks::getServerHooks();
+    hooks.reset();
 
     // There should be two hooks already registered, with indexes 0 and 1.
     EXPECT_EQ(2, hooks.getCount());
@@ -63,7 +64,8 @@ TEST(ServerHooksTest, RegisterHooks) {
 // Check that duplicate names cannot be registered.
 
 TEST(ServerHooksTest, DuplicateHooks) {
-    ServerHooks hooks;
+    ServerHooks& hooks = ServerHooks::getServerHooks();
+    hooks.reset();
 
     // Ensure we can't duplicate one of the existing names.
     EXPECT_THROW(hooks.registerHook("context_create"), DuplicateHook);
@@ -77,8 +79,9 @@ TEST(ServerHooksTest, DuplicateHooks) {
 // Checks that we can get the name of the hooks.
 
 TEST(ServerHooksTest, GetHookNames) {
+    ServerHooks& hooks = ServerHooks::getServerHooks();
+    hooks.reset();
     vector<string> expected_names;
-    ServerHooks hooks;
 
     // Add names into the hooks object and to the set of expected names.
     expected_names.push_back("alpha");
@@ -104,139 +107,72 @@ TEST(ServerHooksTest, GetHookNames) {
     EXPECT_TRUE(expected_names == actual_names);
 }
 
-// Check that getting an unknown name throws an exception.
-
-TEST(ServerHooksTest, UnknownHookName) {
-    ServerHooks hooks;
+// Test the inverse hooks functionality (i.e. given an index, get the name).
 
-    EXPECT_THROW(static_cast<void>(hooks.getIndex("unknown")), NoSuchHook);
-}
+TEST(ServerHooksTest, GetHookIndexes) {
+    ServerHooks& hooks = ServerHooks::getServerHooks();
+    hooks.reset();
 
-// Check that the count of hooks is correct.
-
-TEST(ServerHooksTest, HookCount) {
-    ServerHooks hooks;
-
-    // Insert the names into the hooks object
-    hooks.registerHook("alpha");
-    hooks.registerHook("beta");
-    hooks.registerHook("gamma");
-    hooks.registerHook("delta");
+    int alpha = hooks.registerHook("alpha");
+    int beta = hooks.registerHook("beta");
+    int gamma = hooks.registerHook("gamma");
 
-    // Should be two more hooks that the number we have registered.
-    EXPECT_EQ(6, hooks.getCount());
+    EXPECT_EQ(std::string("context_create"),
+              hooks.getName(ServerHooks::CONTEXT_CREATE));
+    EXPECT_EQ(std::string("context_destroy"),
+              hooks.getName(ServerHooks::CONTEXT_DESTROY));
+    EXPECT_EQ(std::string("alpha"), hooks.getName(alpha));
+    EXPECT_EQ(std::string("beta"), hooks.getName(beta));
+    EXPECT_EQ(std::string("gamma"), hooks.getName(gamma));
+
+    // Check for an invalid index
+    EXPECT_THROW(hooks.getName(-1), NoSuchHook);
+    EXPECT_THROW(hooks.getName(42), NoSuchHook);
 }
 
-// HookRegistrationFunction tests
+// Test the reset functionality.
 
-// Declare some hook registration functions.
+TEST(ServerHooksTest, Reset) {
+    ServerHooks& hooks = ServerHooks::getServerHooks();
+    hooks.reset();
 
-int alpha = 0;
-int beta = 0;
-int gamma = 0;
-int delta = 0;
+    int alpha = hooks.registerHook("alpha");
+    int beta = hooks.registerHook("beta");
+    int gamma = hooks.registerHook("gamma");
 
-void registerAlphaBeta(ServerHooks& hooks) {
-    alpha = hooks.registerHook("alpha");
-    beta = hooks.registerHook("beta");
-}
+    // Check the counts before and after a reset.
+    EXPECT_EQ(5, hooks.getCount());
+    hooks.reset();
+    EXPECT_EQ(2, hooks.getCount());
 
-void registerGammaDelta(ServerHooks& hooks) {
-    gamma = hooks.registerHook("gamma");
-    delta = hooks.registerHook("delta");
+    // ... and check that the hooks are as expected.
+    EXPECT_EQ(0, hooks.getIndex("context_create"));
+    EXPECT_EQ(1, hooks.getIndex("context_destroy"));
 }
 
-// Add them to the registration vector.  This addition should happen before
-// any tests are run, so we should start off with two functions in the
-// registration vector.
-
-HookRegistrationFunction f1(registerAlphaBeta);
-HookRegistrationFunction f2(registerGammaDelta);
+// Check that getting an unknown name throws an exception.
 
-// This is not registered statically: it is used in the latter part of the
-// test.
+TEST(ServerHooksTest, UnknownHookName) {
+    ServerHooks& hooks = ServerHooks::getServerHooks();
+    hooks.reset();
 
-int epsilon = 0;
-void registerEpsilon(ServerHooks& hooks) {
-    epsilon = hooks.registerHook("epsilon");
+    EXPECT_THROW(static_cast<void>(hooks.getIndex("unknown")), NoSuchHook);
 }
 
-// Test that the registration functions were defined and can be executed.
-
-TEST(HookRegistrationFunction, Registration) {
-
-    // The first part of the tests checks the static registration.  As there
-    // is only one list of registration functions, we have to do this first
-    // as the static registration is done outside our control, before the
-    // tests are loaded.
+// Check that the count of hooks is correct.
 
-    // Ensure that the hook numbers are initialized.
-    EXPECT_EQ(0, alpha);
-    EXPECT_EQ(0, beta);
-    EXPECT_EQ(0, gamma);
-    EXPECT_EQ(0, delta);
+TEST(ServerHooksTest, HookCount) {
+    ServerHooks& hooks = ServerHooks::getServerHooks();
+    hooks.reset();
 
-    // Should have two hook registration functions registered.
-    EXPECT_EQ(2, HookRegistrationFunction::getFunctionVector().size());
+    // Insert the names into the hooks object
+    hooks.registerHook("alpha");
+    hooks.registerHook("beta");
+    hooks.registerHook("gamma");
+    hooks.registerHook("delta");
 
-    // Execute the functions and check that four new hooks were defined (two
-    // from each function).
-    ServerHooks hooks;
-    EXPECT_EQ(2, hooks.getCount());
-    HookRegistrationFunction::execute(hooks);
+    // Should be two more hooks that the number we have registered.
     EXPECT_EQ(6, hooks.getCount());
-
-    // Check the hook names are as expected.
-    vector<string> names = hooks.getHookNames();
-    ASSERT_EQ(6, names.size());
-    sort(names.begin(), names.end());
-    EXPECT_EQ(string("alpha"), names[0]);
-    EXPECT_EQ(string("beta"), names[1]);
-    EXPECT_EQ(string("context_create"), names[2]);
-    EXPECT_EQ(string("context_destroy"), names[3]);
-    EXPECT_EQ(string("delta"), names[4]);
-    EXPECT_EQ(string("gamma"), names[5]);
-
-    // Check that numbers in the range 2-5 inclusive were assigned as the
-    // hook indexes (0 and 1 being reserved for context_create and
-    // context_destroy).
-    vector<int> indexes;
-    indexes.push_back(alpha);
-    indexes.push_back(beta);
-    indexes.push_back(gamma);
-    indexes.push_back(delta);
-    sort(indexes.begin(), indexes.end());
-    EXPECT_EQ(2, indexes[0]);
-    EXPECT_EQ(3, indexes[1]);
-    EXPECT_EQ(4, indexes[2]);
-    EXPECT_EQ(5, indexes[3]);
-
-    // One last check.  We'll test that the constructor of does indeed
-    // add a function to the function vector and that the static initialization
-    // was not somehow by chance.
-    HookRegistrationFunction::getFunctionVector().clear();
-    EXPECT_TRUE(HookRegistrationFunction::getFunctionVector().empty());
-    epsilon = 0;
-
-    // Register a single registration function.
-    HookRegistrationFunction f3(registerEpsilon);
-    EXPECT_EQ(1, HookRegistrationFunction::getFunctionVector().size());
-
-    // Execute it...
-    ServerHooks hooks2;
-    EXPECT_EQ(0, epsilon);
-    EXPECT_EQ(2, hooks2.getCount());
-    HookRegistrationFunction::execute(hooks2);
-
-    // There should be three hooks, with the new one assigned an index of 2.
-    names = hooks2.getHookNames();
-    ASSERT_EQ(3, names.size());
-    sort(names.begin(), names.end());
-    EXPECT_EQ(string("context_create"), names[0]);
-    EXPECT_EQ(string("context_destroy"), names[1]);
-    EXPECT_EQ(string("epsilon"), names[2]);
-
-    EXPECT_EQ(2, epsilon);
 }
 
 } // Anonymous namespace

+ 79 - 0
src/lib/hooks/tests/test_libraries.h.in

@@ -0,0 +1,79 @@
+// 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.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// 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.
+
+// Basic library with context_create and three "standard" callouts.
+static const char* BASIC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libbcl"
+                                           DLL_SUFFIX;
+
+// Library with context_create and three "standard" callouts, as well as
+// load() and unload() functions.
+static const char* FULL_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libfcl"
+                                          DLL_SUFFIX;
+
+// Library where the all framework functions throw an exception
+static const char* FRAMEWORK_EXCEPTION_LIBRARY = "@abs_builddir@/.libs/libfxl"
+                                                 DLL_SUFFIX;
+
+// Library where the version() function returns an incorrect result.
+static const char* INCORRECT_VERSION_LIBRARY = "@abs_builddir@/.libs/libivl"
+                                               DLL_SUFFIX;
+
+// Library where some of the callout registration is done with the load()
+// function.
+static const char* LOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/liblcl"
+                                          DLL_SUFFIX;
+
+// Library where the load() function returns an error.
+static const char* LOAD_ERROR_CALLOUT_LIBRARY =
+    "@abs_builddir@/.libs/liblecl" DLL_SUFFIX;
+
+// Name of a library which is not present.
+static const char* NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere"
+                                         DLL_SUFFIX;
+
+// Library that does not include a version function.
+static const char* NO_VERSION_LIBRARY = "@abs_builddir@/.libs/libnvl"
+                                        DLL_SUFFIX;
+
+// Library where there is an unload() function.
+static const char* UNLOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libucl"
+                                            DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H

+ 52 - 0
src/lib/hooks/tests/unload_callout_library.cc

@@ -0,0 +1,52 @@
+// 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 unload library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager).  The characteristics of the library produced from this
+/// file are:
+///
+/// - The "version" and "unload" framework functions are supplied. "version"
+///   returns a valid value and "unload" creates a marker file and returns
+///   success.
+
+#include <hooks/hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <fstream>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Framework functions
+
+int
+version() {
+    return (BIND10_HOOKS_VERSION);
+}
+
+int
+unload() {
+    // Create the marker file.
+    std::fstream marker;
+    marker.open(MARKER_FILE, std::fstream::out);
+    marker.close();
+
+    return (0);
+}
+
+};

+ 0 - 4
src/lib/util/Makefile.am

@@ -37,10 +37,6 @@ libb10_util_la_SOURCES += encode/base32hex_from_binary.h
 libb10_util_la_SOURCES += encode/base_n.cc encode/hex.h
 libb10_util_la_SOURCES += encode/binary_from_base32hex.h
 libb10_util_la_SOURCES += encode/binary_from_base16.h
-libb10_util_la_SOURCES += hooks/callout_manager.h hooks/callout_manager.cc
-libb10_util_la_SOURCES += hooks/callout_handle.h hooks/callout_handle.cc
-libb10_util_la_SOURCES += hooks/library_handle.h hooks/library_handle.cc
-libb10_util_la_SOURCES += hooks/server_hooks.h hooks/server_hooks.cc
 libb10_util_la_SOURCES += random/qid_gen.h random/qid_gen.cc
 libb10_util_la_SOURCES += random/random_number_generator.h
 

+ 0 - 4
src/lib/util/tests/Makefile.am

@@ -25,13 +25,10 @@ run_unittests_SOURCES  = run_unittests.cc
 run_unittests_SOURCES += base32hex_unittest.cc
 run_unittests_SOURCES += base64_unittest.cc
 run_unittests_SOURCES += buffer_unittest.cc
-run_unittests_SOURCES += callout_handle_unittest.cc
-run_unittests_SOURCES += callout_manager_unittest.cc
 run_unittests_SOURCES += fd_share_tests.cc
 run_unittests_SOURCES += fd_tests.cc
 run_unittests_SOURCES += filename_unittest.cc
 run_unittests_SOURCES += hex_unittest.cc
-run_unittests_SOURCES += handles_unittest.cc
 run_unittests_SOURCES += io_utilities_unittest.cc
 run_unittests_SOURCES += lru_list_unittest.cc
 run_unittests_SOURCES += memory_segment_local_unittest.cc
@@ -42,7 +39,6 @@ run_unittests_SOURCES += memory_segment_common_unittest.h
 run_unittests_SOURCES += memory_segment_common_unittest.cc
 run_unittests_SOURCES += qid_gen_unittest.cc
 run_unittests_SOURCES += random_number_generator_unittest.cc
-run_unittests_SOURCES += server_hooks_unittest.cc
 run_unittests_SOURCES += sha1_unittest.cc
 run_unittests_SOURCES += socketsession_unittest.cc
 run_unittests_SOURCES += strutil_unittest.cc