Add support for generic inotify based directory watch.

Watch a directory for the changes based on user configuration
and then report changes to the user.

Change-Id: I9f53d3135dd4bff6187840c0c53d2a64509808cd
Signed-off-by: Jayanth Othayoth <ojayanth@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 72401e1..85149c4 100755
--- a/Makefile.am
+++ b/Makefile.am
@@ -6,7 +6,8 @@
 	dump_watch.hpp \
 	dump_internal.hpp \
 	dump_manager.hpp \
-	dump_utils.hpp
+	dump_utils.hpp \
+	watch.hpp
 
 nobase_nodist_include_HEADERS = \
 	xyz/openbmc_project/Dump/Monitor/error.hpp \
@@ -20,8 +21,8 @@
 	dump_manager_main.cpp \
 	dump_entry.cpp \
 	dump_manager.cpp \
-	xyz/openbmc_project/Dump/Internal/Create/server.cpp \
-	xyz/openbmc_project/Dump/Monitor/error.cpp
+	watch.cpp \
+	xyz/openbmc_project/Dump/Internal/Create/server.cpp
 
 phosphor_dump_monitor_SOURCES = \
 	dump_watch_main.cpp \
diff --git a/dump_manager.hpp b/dump_manager.hpp
index 3bbcf3e..b4c09c1 100644
--- a/dump_manager.hpp
+++ b/dump_manager.hpp
@@ -1,12 +1,14 @@
 #pragma once
 
+#include <experimental/filesystem>
+
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server/object.hpp>
 #include <xyz/openbmc_project/Dump/Create/server.hpp>
-#include <experimental/filesystem>
 
 #include "xyz/openbmc_project/Dump/Internal/Create/server.hpp"
 #include "dump_entry.hpp"
+#include "dump_utils.hpp"
 
 namespace phosphor
 {
@@ -19,22 +21,13 @@
 
 } // namespace internal
 
-namespace fs = std::experimental::filesystem;
 using Type =
     sdbusplus::xyz::openbmc_project::Dump::Internal::server::Create::Type;
 
 using CreateIface = sdbusplus::server::object::object<
                     sdbusplus::xyz::openbmc_project::Dump::server::Create>;
 
-/* Need a custom deleter for freeing up sd_event */
-struct EventDeleter
-{
-    void operator()(sd_event* event) const
-    {
-        event = sd_event_unref(event);
-    }
-};
-using EventPtr = std::unique_ptr<sd_event, EventDeleter>;
+namespace fs = std::experimental::filesystem;
 
 /** @class Manager
  *  @brief OpenBMC Dump  manager implementation.
diff --git a/dump_manager_main.cpp b/dump_manager_main.cpp
index cd1d83b..6be9a29 100644
--- a/dump_manager_main.cpp
+++ b/dump_manager_main.cpp
@@ -1,3 +1,4 @@
+#include <sdbusplus/bus.hpp>
 #include <phosphor-logging/elog-errors.hpp>
 
 #include "xyz/openbmc_project/Common/error.hpp"
@@ -7,11 +8,11 @@
 
 int main(int argc, char* argv[])
 {
-    auto bus = sdbusplus::bus::new_default();
     using namespace phosphor::logging;
     using InternalFailure =
         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
 
+    auto bus = sdbusplus::bus::new_default();
     sd_event* event = nullptr;
     auto rc = sd_event_default(&event);
     if (rc < 0)
@@ -41,7 +42,6 @@
             elog<InternalFailure>();
         }
     }
-
     catch (InternalFailure& e)
     {
         commit<InternalFailure>();
diff --git a/dump_utils.hpp b/dump_utils.hpp
index a71458c..6ebee33 100644
--- a/dump_utils.hpp
+++ b/dump_utils.hpp
@@ -1,10 +1,23 @@
 #pragma once
 
+#include <memory>
+#include <unistd.h>
+
 namespace phosphor
 {
 namespace dump
 {
 
+/* Need a custom deleter for freeing up sd_event */
+struct EventDeleter
+{
+    void operator()(sd_event* event) const
+    {
+        event = sd_event_unref(event);
+    }
+};
+using EventPtr = std::unique_ptr<sd_event, EventDeleter>;
+
 /** @struct CustomFd
  *
  *  RAII wrapper for file descriptor.
diff --git a/watch.cpp b/watch.cpp
new file mode 100644
index 0000000..bc7fb1f
--- /dev/null
+++ b/watch.cpp
@@ -0,0 +1,140 @@
+#include <phosphor-logging/elog-errors.hpp>
+
+#include "xyz/openbmc_project/Common/error.hpp"
+#include "watch.hpp"
+
+namespace phosphor
+{
+namespace dump
+{
+namespace inotify
+{
+
+using namespace std::string_literals;
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+Watch::~Watch()
+{
+    if ((fd() >= 0) && (wd >= 0))
+    {
+        inotify_rm_watch(fd(), wd);
+    }
+}
+
+Watch::Watch(const EventPtr& eventObj,
+             const int flags,
+             const uint32_t mask,
+             const uint32_t events,
+             const fs::path& path,
+             UserType userFunc):
+    flags(flags),
+    mask(mask),
+    events(events),
+    path(path),
+    fd(inotifyInit()),
+    userFunc(userFunc)
+{
+    // Check if watch DIR exists.
+    if (!fs::is_directory(path))
+    {
+        log<level::ERR>("Watch directory doesn't exist",
+                        entry("dir=%s", path.c_str()));
+        elog<InternalFailure>();
+    }
+
+    wd = inotify_add_watch(fd(), path.c_str(), mask);
+    if (-1 == wd)
+    {
+        auto error = errno;
+        log<level::ERR>("Error occurred during the inotify_add_watch call",
+                        entry("ERRNO=%d", error));
+        elog<InternalFailure>();
+    }
+
+    auto rc = sd_event_add_io(eventObj.get(),
+                              nullptr,
+                              fd(),
+                              events,
+                              callback,
+                              this);
+    if (0 > rc)
+    {
+        // Failed to add to event loop
+        log<level::ERR>("Error occurred during the sd_event_add_io call",
+                        entry("rc=%d", rc));
+        elog<InternalFailure>();
+    }
+}
+
+int Watch::inotifyInit()
+{
+    auto fd = inotify_init1(flags);
+
+    if (-1 == fd)
+    {
+        auto error = errno;
+        log<level::ERR>("Error occurred during the inotify_init1",
+                        entry("ERRNO=%d", error));
+        elog<InternalFailure>();
+    }
+
+    return fd;
+}
+
+int Watch::callback(sd_event_source* s,
+                    int fd,
+                    uint32_t revents,
+                    void* userdata)
+{
+    if (!(revents & static_cast<Watch*>(userdata)->events))
+    {
+        return 0;
+    }
+
+    //Maximum inotify events supported in the buffer
+    constexpr auto maxBytes = sizeof(struct inotify_event) + NAME_MAX + 1;
+    uint8_t buffer[maxBytes];
+
+    auto bytes = read(fd, buffer, maxBytes);
+    if (0 > bytes)
+    {
+        //Failed to read inotify event
+        //Report error and return
+        auto error = errno;
+        log<level::ERR>("Error occurred during the read",
+                        entry("ERRNO=%d", error));
+        report<InternalFailure>();
+        return 0;
+    }
+
+    auto offset = 0;
+
+    UserMap userMap;
+
+    while (offset < bytes)
+    {
+        auto event = reinterpret_cast<inotify_event*>(&buffer[offset]);
+        auto mask = event->mask & static_cast<Watch*>(userdata)->mask;
+
+        if (mask && !(event->mask & IN_ISDIR))
+        {
+            userMap.emplace(
+                    (static_cast<Watch*>(userdata)->path / event->name), mask);
+        }
+
+        offset += offsetof(inotify_event, name) + event->len;
+    }
+
+    //Call user call back function incase valid data in the map
+    if (!userMap.empty())
+    {
+        static_cast<Watch*>(userdata)->userFunc(userMap);
+    }
+
+    return 0;
+}
+
+} // namespace inotify
+} // namespace dump
+} // namespace phosphor
diff --git a/watch.hpp b/watch.hpp
new file mode 100644
index 0000000..bbfa476
--- /dev/null
+++ b/watch.hpp
@@ -0,0 +1,105 @@
+#pragma once
+
+#include <experimental/filesystem>
+#include <systemd/sd-event.h>
+#include <sys/inotify.h>
+#include <map>
+
+#include "dump_utils.hpp"
+
+namespace phosphor
+{
+namespace dump
+{
+namespace inotify
+{
+
+namespace fs = std::experimental::filesystem;
+
+//User specfic call back function input map(path:event) type.
+using UserMap = std::map<fs::path, uint32_t>;
+
+//User specific callback function wrapper type.
+using UserType = std::function<void(const UserMap&)>;
+
+/** @class Watch
+ *
+ *  @brief Adds inotify watch on directory.
+ *
+ *  The inotify watch is hooked up with sd-event, so that on call back,
+ *  appropriate actions are taken to collect files from the directory
+ *  initialized by the object.
+ */
+class Watch
+{
+    public:
+        /** @brief ctor - hook inotify watch with sd-event
+         *
+         *  @param[in] eventObj - Event loop object
+         *  @param[in] flags - inotify flags
+         *  @param[in] mask  - Mask of events
+         *  @param[in] events - Events to be watched
+         *  @param[in] path - File path to be watched
+         *  @param[in] userFunc - User specific callback fnction wrapper.
+         *
+         */
+        Watch(const EventPtr& eventObj,
+              int flags,
+              uint32_t mask,
+              uint32_t events,
+              const fs::path& path,
+              UserType userFunc);
+
+        Watch(const Watch&) = delete;
+        Watch& operator=(const Watch&) = delete;
+        Watch(Watch&&) = default;
+        Watch& operator=(Watch&&) = default;
+
+        /* @brief dtor - remove inotify watch and close fd's */
+        ~Watch();
+
+    private:
+        /** @brief sd-event callback.
+         *  @details Collects the files and event info and call the
+         *           appropriate user function for further action.
+         *
+         *  @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 inotify flags */
+        int flags;
+
+        /** @brief Mask of events */
+        uint32_t mask;
+
+        /** @brief Events to be watched */
+        uint32_t events;
+
+        /** @brief File path to be watched */
+        fs::path path;
+
+        /** @brief dump file directory watch descriptor */
+        int wd = -1;
+
+        /** @brief file descriptor manager */
+        CustomFd fd;
+
+        /** @brief The user level callback function wrapper */
+        UserType userFunc;
+};
+
+} // namespace inotify
+} // namespace dump
+} // namespace phosphor