Call updateFunctionalAssociation on symlink change

Call updateFunctionalAssociation, to change the functional
association to the new "running" PNOR image, on a symlink change.
The symlink change occurs on chassis poweron.
Look at the RO symlink, /var/lib/phosphor-software-manager/pnor/ro,
for a symlink change.

Change-Id: I2c673635af55da1c642e6d96ab6e12951b3a4fd3
Signed-off-by: Gunnar Mills <gmills@us.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index d9880fb..0b94db4 100755
--- a/Makefile.am
+++ b/Makefile.am
@@ -9,6 +9,7 @@
 	activation.cpp \
 	version.cpp \
 	serialize.cpp \
+	watch.cpp \
 	item_updater.cpp \
 	item_updater_main.cpp
 
diff --git a/item_updater_main.cpp b/item_updater_main.cpp
index c6f7dba..0933505 100755
--- a/item_updater_main.cpp
+++ b/item_updater_main.cpp
@@ -1,23 +1,56 @@
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server/manager.hpp>
+#include <system_error>
 #include "config.h"
 #include "item_updater.hpp"
+#include <phosphor-logging/log.hpp>
+#include "watch.hpp"
 
 int main(int argc, char* argv[])
 {
+    using namespace openpower::software::updater;
+    using namespace phosphor::logging;
     auto bus = sdbusplus::bus::new_default();
 
+    sd_event* loop = nullptr;
+    auto rc = sd_event_default(&loop);
+    if (rc < 0)
+    {
+        log<level::ERR>("Error occurred during the sd_event_default",
+                        entry("RC=%d", rc));
+        return -1;
+    }
+
     // Add sdbusplus ObjectManager.
     sdbusplus::server::manager::manager objManager(bus, SOFTWARE_OBJPATH);
 
-    openpower::software::updater::ItemUpdater updater(bus, SOFTWARE_OBJPATH);
+    ItemUpdater updater(bus, SOFTWARE_OBJPATH);
 
     bus.request_name(BUSNAME_UPDATER);
-
-    while (true)
+    try
     {
-        bus.process_discard();
-        bus.wait();
+        openpower::software::updater::Watch watch(
+                loop,
+                std::bind(std::mem_fn(
+                                  &ItemUpdater::updateFunctionalAssociation),
+                          &updater,
+                          std::placeholders::_1));
+        bus.attach_event(loop, SD_EVENT_PRIORITY_NORMAL);
+        auto rc = sd_event_loop(loop);
+        if (rc < 0)
+        {
+            log<level::ERR>("Error occurred during the sd_event_loop",
+                            entry("RC=%d", rc));
+            return -1;
+        }
     }
+    catch (const std::system_error& e)
+    {
+        log<level::ERR>(e.what());
+        return -1;
+    }
+
+    sd_event_unref(loop);
+
     return 0;
 }
diff --git a/watch.cpp b/watch.cpp
new file mode 100644
index 0000000..da06bbc
--- /dev/null
+++ b/watch.cpp
@@ -0,0 +1,133 @@
+#include <stdexcept>
+#include <cstddef>
+#include <cstring>
+#include <string>
+#include <sys/inotify.h>
+#include <unistd.h>
+#include <experimental/filesystem>
+#include <phosphor-logging/log.hpp>
+#include "config.h"
+#include "watch.hpp"
+
+namespace openpower
+{
+namespace software
+{
+namespace updater
+{
+
+using namespace phosphor::logging;
+namespace fs = std::experimental::filesystem;
+
+Watch::Watch(sd_event* loop,
+             std::function<void(std::string&)> functionalCallback) :
+        functionalCallback(functionalCallback),
+        fd(inotifyInit())
+
+{
+    // Create PNOR_ACTIVE_PATH if doesn't exist.
+    if (!fs::is_directory(PNOR_ACTIVE_PATH))
+    {
+        fs::create_directories(PNOR_ACTIVE_PATH);
+    }
+
+    wd = inotify_add_watch(fd(), PNOR_ACTIVE_PATH, IN_CREATE);
+    if (-1 == wd)
+    {
+        auto error = errno;
+        throw std::system_error(error,
+                                std::generic_category(),
+                                "Error occurred during the inotify_init1");
+    }
+
+    decltype(eventSource.get()) sourcePtr = nullptr;
+    auto rc = sd_event_add_io(loop,
+                              &sourcePtr,
+                              fd(),
+                              EPOLLIN,
+                              callback,
+                              this);
+
+    eventSource.reset(sourcePtr);
+
+    if (0 > rc)
+    {
+        throw std::system_error(-rc,
+                                std::generic_category(),
+                                "Error occurred during the inotify_init1");
+    }
+}
+
+Watch::~Watch()
+{
+    if ((-1 != fd()) && (-1 != wd))
+    {
+        inotify_rm_watch(fd(), wd);
+    }
+}
+
+int Watch::callback(sd_event_source* s,
+                    int fd,
+                    uint32_t revents,
+                    void* userdata)
+{
+    if (!(revents & EPOLLIN))
+    {
+        return 0;
+    }
+
+    constexpr auto maxBytes = 1024;
+    uint8_t buffer[maxBytes];
+    auto bytes = read(fd, buffer, maxBytes);
+    if (0 > bytes)
+    {
+        auto error = errno;
+        throw std::system_error(error,
+                                std::generic_category(),
+                                "failed to read inotify event");
+    }
+
+    auto offset = 0;
+    while (offset < bytes)
+    {
+        auto event = reinterpret_cast<inotify_event*>(&buffer[offset]);
+        // Update the functional association on a RO
+        // active image symlink change
+        fs::path path(PNOR_ACTIVE_PATH);
+        path /= event->name;
+        if (fs::equivalent(path, PNOR_RO_ACTIVE_PATH))
+        {
+            auto target = fs::canonical(path).string();
+
+            // Get the image <id> from the symlink target
+            // for example /media/ro-2a1022fe
+            static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX);
+            auto id = target.substr(PNOR_RO_PREFIX_LEN);
+            auto objPath = std::string{SOFTWARE_OBJPATH} + '/' + id;
+
+            static_cast<Watch*>(userdata)->functionalCallback(objPath);
+        }
+        offset += offsetof(inotify_event, name) + event->len;
+    }
+
+    return 0;
+}
+
+int Watch::inotifyInit()
+{
+    auto fd = inotify_init1(IN_NONBLOCK);
+
+    if (-1 == fd)
+    {
+        auto error = errno;
+        throw std::system_error(error,
+                                std::generic_category(),
+                                "Error occurred during the inotify_init1");
+    }
+
+    return fd;
+}
+
+} // namespace updater
+} // namespace software
+} // namespace openpower
diff --git a/watch.hpp b/watch.hpp
new file mode 100644
index 0000000..bdea792
--- /dev/null
+++ b/watch.hpp
@@ -0,0 +1,123 @@
+#pragma once
+
+#include <systemd/sd-event.h>
+#include <unistd.h>
+
+namespace openpower
+{
+namespace software
+{
+namespace updater
+{
+
+/* Need a custom deleter for freeing up sd_event_source */
+struct EventSourceDeleter
+{
+    void operator()(sd_event_source* eventSource) const
+    {
+        eventSource = sd_event_source_unref(eventSource);
+    }
+};
+using EventSourcePtr = std::unique_ptr<sd_event_source, EventSourceDeleter>;
+
+/** @struct CustomFd
+ *
+ *  RAII wrapper for file descriptor.
+ */
+struct CustomFd
+{
+    public:
+        CustomFd() = delete;
+        CustomFd(const CustomFd&) = delete;
+        CustomFd& operator=(const CustomFd&) = delete;
+        CustomFd(CustomFd&&) = delete;
+        CustomFd& operator=(CustomFd&&) = delete;
+
+        /** @brief Saves File descriptor and uses it to do file operation
+         *
+         *  @param[in] fd - File descriptor
+         */
+        CustomFd(int fd) : fd(fd) {}
+
+        ~CustomFd()
+        {
+            if (fd >= 0)
+            {
+                close(fd);
+            }
+        }
+
+        int operator()() const
+        {
+            return fd;
+        }
+
+    private:
+        /** @brief File descriptor */
+        int fd = -1;
+};
+
+/** @class Watch
+ *
+ *  @brief Adds inotify watch on PNOR symlinks file to monitor for changes in
+ *         "running" PNOR version
+ *
+ *  The inotify watch is hooked up with sd-event, so that on call back,
+ *  appropriate actions related to a change in the "running" PNOR version
+ *  can be taken.
+ */
+class Watch
+{
+    public:
+        /** @brief ctor - hook inotify watch with sd-event
+         *
+         *  @param[in] loop - sd-event object
+         *  @param[in] functionalCallback - The callback function for updating
+         *                                  the functional associations.
+         */
+        Watch(sd_event* loop,
+              std::function<void(std::string&)> functionalCallback);
+
+        Watch(const Watch&) = delete;
+        Watch& operator=(const Watch&) = delete;
+        Watch(Watch&&) = delete;
+        Watch& operator=(Watch&&) = delete;
+
+        /** @brief dtor - remove inotify watch
+         */
+        ~Watch();
+
+    private:
+        /** @brief sd-event callback
+         *
+         *  @param[in] s - event source, floating (unused) in our case
+         *  @param[in] fd - inotify fd
+         *  @param[in] revents - events that matched for fd
+         *  @param[in] userdata - pointer to Watch object
+         *  @returns 0 on success, -1 on fail
+         */
+        static int callback(sd_event_source* s,
+                            int fd,
+                            uint32_t revents,
+                            void* userdata);
+
+        /**  initialize an inotify instance and returns file descriptor */
+        int inotifyInit();
+
+        /** @brief PNOR symlink file watch descriptor */
+        int wd = -1;
+
+        /** @brief event source */
+        EventSourcePtr eventSource;
+
+        /** @brief The callback function for updating the
+                   functional associations. */
+        std::function<void(std::string&)> functionalCallback;
+
+        /** @brief inotify file descriptor */
+        CustomFd fd;
+};
+
+} // namespace updater
+} // namespace software
+} // namespace openpower