Refactor: Move watch to ubi

watch is ubi specific functions, move it to ubi dir.

Tested: On the last commit of the patch series, run code update and
        factory reset on Witherspoon and all work fine.

Change-Id: Ia7f6b6de845ddd40e2a32ad626d3b7af9074c7f3
Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/ubi/Makefile.am.include b/ubi/Makefile.am.include
index fba30d9..a836661 100644
--- a/ubi/Makefile.am.include
+++ b/ubi/Makefile.am.include
@@ -1,2 +1,3 @@
 openpower_update_manager_SOURCES += \
-	%reldir%/item_updater_ubi.cpp
+	%reldir%/item_updater_ubi.cpp \
+	%reldir%/watch.cpp
diff --git a/ubi/watch.cpp b/ubi/watch.cpp
new file mode 100644
index 0000000..192d3d9
--- /dev/null
+++ b/ubi/watch.cpp
@@ -0,0 +1,121 @@
+#include "config.h"
+
+#include "watch.hpp"
+
+#include "item_updater.hpp"
+
+#include <sys/inotify.h>
+#include <unistd.h>
+
+#include <cstddef>
+#include <cstring>
+#include <experimental/filesystem>
+#include <functional>
+#include <phosphor-logging/log.hpp>
+#include <stdexcept>
+#include <string>
+
+namespace openpower
+{
+namespace software
+{
+namespace updater
+{
+
+using namespace phosphor::logging;
+namespace fs = std::experimental::filesystem;
+
+Watch::Watch(sd_event* loop,
+             std::function<void(const 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 id = ItemUpdater::determineId(path);
+            static_cast<Watch*>(userdata)->functionalCallback(id);
+        }
+        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/ubi/watch.hpp b/ubi/watch.hpp
new file mode 100644
index 0000000..a04967e
--- /dev/null
+++ b/ubi/watch.hpp
@@ -0,0 +1,126 @@
+#pragma once
+
+#include <systemd/sd-event.h>
+#include <unistd.h>
+
+#include <functional>
+#include <memory>
+
+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(const 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