Load IPMI provider libraries into net-ipmid

The logic to scan /usr/lib/net-ipmid for shared libraries
and load them and the callback function to register commands
to command table.

Change-Id: Ib09cce5a9b418171822208d1d7b322e4b1c8b2b9
Signed-off-by: Tom Joseph <tomjoseph@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 31e7a87..8050126 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -35,8 +35,11 @@
 	main.hpp \
 	main.cpp \
 	integrity_algo.hpp \
-	integrity_algo.cpp
+	integrity_algo.cpp \
+	provider_registration.hpp \
+	provider_registration.cpp
 
-netipmid_LDFLAGS = $(SYSTEMD_LIBS) $(CRYPTO_LIBS) $(libmapper_LIBS)
+netipmid_CPPFLAGS = -DNET_IPMID_LIB_PATH=\"/usr/lib/net-ipmid/\"
+netipmid_LDFLAGS = $(SYSTEMD_LIBS) $(CRYPTO_LIBS) $(libmapper_LIBS) $(LIBADD_DLOPEN) -export-dynamic
 netipmid_CXXFLAGS = $(SYSTEMD_CFLAGS) $(libmapper_CFLAGS)
 
diff --git a/configure.ac b/configure.ac
index 1b12c59..0248c39 100644
--- a/configure.ac
+++ b/configure.ac
@@ -26,5 +26,9 @@
 # Checks for header files.
 AC_CHECK_HEADER(systemd/sd-bus.h, ,[AC_MSG_ERROR([Could not find systemd/sd-bus.h...systemd developement package required])])
 
+# Checks for library functions.
+LT_INIT([dlopen disable-static shared])
+LT_LIB_DLLOAD
+
 AC_CONFIG_FILES([Makefile])
 AC_OUTPUT
diff --git a/main.cpp b/main.cpp
index 0fae8a7..108cf92 100644
--- a/main.cpp
+++ b/main.cpp
@@ -16,6 +16,7 @@
 #include "command_table.hpp"
 #include "message.hpp"
 #include "message_handler.hpp"
+#include "provider_registration.hpp"
 #include "sessions_manager.hpp"
 #include "socket_channel.hpp"
 
@@ -180,6 +181,9 @@
         goto finish;
     }
 
+    // Register all the IPMI provider libraries applicable for net-ipmid
+    provider::registerCallbackHandlers(NET_IPMID_LIB_PATH);
+
     // Register the phosphor-net-ipmid session setup commands
     command::sessionSetupCommands();
 
diff --git a/provider_registration.cpp b/provider_registration.cpp
new file mode 100644
index 0000000..aaa397c
--- /dev/null
+++ b/provider_registration.cpp
@@ -0,0 +1,103 @@
+#include <dirent.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <iostream>
+
+#include <host-ipmid/ipmid-api.h>
+#include "command_table.hpp"
+#include "main.hpp"
+#include "provider_registration.hpp"
+
+namespace provider
+{
+
+int handler_select(const struct dirent* entry)
+{
+    // Check for versioned libraries .so.*
+    if (strstr(entry->d_name, PROVIDER_SONAME_EXTN))
+    {
+        return 1;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+void registerCallbackHandlers(const char* providerLibPath)
+{
+    if (providerLibPath == NULL)
+    {
+        std::cerr << "Path not provided for registering IPMI provider libraries"
+                  << "\n";
+        return;
+    }
+
+    struct dirent** handlerList = nullptr;
+    std::string handlerPath(providerLibPath);
+
+    auto numLibs = scandir(providerLibPath, &handlerList, handler_select,
+                           alphasort);
+    if (numLibs < 0)
+    {
+        return;
+    }
+
+    // dlopen each IPMI provider shared library
+    while (numLibs--)
+    {
+        handlerPath = providerLibPath;
+        handlerPath += handlerList[numLibs]->d_name;
+        std::cout << "Registering handler: " << handlerPath << "\n";
+
+        auto lib_handler = dlopen(handlerPath.c_str(), RTLD_NOW);
+
+        if (lib_handler == NULL)
+        {
+            std::cerr << "Error opening " << handlerPath << dlerror() << "\n";
+        }
+        free(handlerList[numLibs]);
+    }
+
+    free(handlerList);
+}
+
+} // namespace provider
+
+/*
+ * @brief Method that gets called from IPMI provider shared libraries to get
+ *        the command handlers registered.
+ *
+ * When the IPMI provider shared library is loaded, the dynamic loader program
+ * looks for special section(.ctors on ELF) which contains references to the
+ * functions marked with the constructor attributes. This function is invoked
+ * in such manner.
+ *
+ * @param[in] netfn - Network Function code
+ * @param[in] cmd - Command
+ * @param[in] context - User specific data
+ * @param[in] handler - The callback routine for the command
+ * @param[in] priv - IPMI Command Prvilege
+ */
+void ipmi_register_callback(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                            ipmi_context_t context,
+                            ipmid_callback_t handler, ipmi_cmd_privilege priv)
+{
+    uint16_t netFn = netfn << 10;
+
+    // The payload type of IPMI commands provided by the shared libraries
+    // is IPMI
+    command::CommandID command =
+    {
+        ((static_cast<uint32_t>(message::PayloadType::IPMI)) << 16) |
+        netFn | cmd
+    };
+
+    std::get<command::Table&>(singletonPool).registerCommand(command,
+            std::make_unique<command::ProviderIpmidEntry>
+            (command, handler, static_cast<session::Privilege>(priv)));
+}
+
+
diff --git a/provider_registration.hpp b/provider_registration.hpp
new file mode 100644
index 0000000..6c05dde
--- /dev/null
+++ b/provider_registration.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+namespace provider
+{
+
+/*
+ * @brief Provider Library filename extension
+ *
+ * Autotools versions the shared libraries, so the shared libraries end with
+ * extension name .so.*
+ */
+
+constexpr auto PROVIDER_SONAME_EXTN = ".so.";
+
+/*
+ * @brief Register Callback handlers for IPMI provider libraries
+ *
+ * Open the directory path for net-ipmid provider libraries and scan the
+ * directory for files that end with .so.*. and dlopen the shared libraries
+ * to register the handlers for the callback routines.
+ *
+ * @param[in] providerLibPath - Directory path for reading the IPMI provider
+ *                              libraries
+ */
+void registerCallbackHandlers(const char* providerLibPath);
+
+} // namespace provider